Files
aksel/eslint.config.js
dependabot[bot]andKen a133f2a18d ⬆️ Bump the react group across 1 directory with 2 updates (#5334)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ken <ken.aleksander@gmail.com>
2026-09-30 14:22:30 +02:00

290 lines
8.6 KiB
JavaScript

const js = require("@eslint/js");
const nextPlugin = require("@next/eslint-plugin-next");
const vitest = require("@vitest/eslint-plugin");
const akselLocal = require("eslint-plugin-aksel-local");
const importPlugin = require("eslint-plugin-import");
const reactPlugin = require("eslint-plugin-react");
const reactHooks = require("eslint-plugin-react-hooks");
const storybook = require("eslint-plugin-storybook");
const testingLibrary = require("eslint-plugin-testing-library");
const { globalIgnores, defineConfig } = require("eslint/config");
const globals = require("globals");
const tseslint = require("typescript-eslint");
/**
* TODO:
* - Replace tseslint.configs.recommended with tseslint.configs.recommendedTypeChecked
* - Look into opinionated stylistic plugin (e.g. tseslint.configs.stylisticTypeChecked)
* - Consider adding the rule "id-length"
*/
module.exports = defineConfig([
globalIgnores([
"**/lib",
"**/public",
"**/esm",
"**/cjs",
"**/dist",
"@navikt/aksel/**/*.input.*",
"@navikt/aksel/**/*.output.*",
"**/codemod/**/*.js",
"**/playwright-report/**/*",
"**/tokens/**/plugin.js",
"**/.next",
"**/query-types.ts",
".yarn",
"**/next-env.d.ts",
// Generated/gitignored output (flat config does not read .gitignore)
"@navikt/aksel-icons/src/**",
"@navikt/aksel/src/version.ts",
"**/component-names.generated.ts",
"@navikt/core/tailwind/{src/tokens.ts,tailwind.config.js,tailwind.darkside.config.js}",
"@navikt/core/tokens/token_docs.js",
"apps/playroom/{playroom.config.js,src/auto-generated-imports.ts}",
"**/.build",
"**/coverage",
"**/storybook-static",
"**/test-results",
"tooling/analyzer/temp",
]),
js.configs.recommended,
reactPlugin.configs.flat.recommended,
{
// Uses the new JSX transform, so React does not need to be in scope
files: ["aksel.nav.no/sanity-studio/**"],
...reactPlugin.configs.flat["jsx-runtime"],
},
reactHooks.configs.flat["recommended-latest"],
storybook.configs["flat/recommended"],
importPlugin.flatConfigs.recommended,
importPlugin.flatConfigs.typescript,
{
settings: { react: { version: "detect" } }, // Allows eslint-plugin-react to detect installed react-version
languageOptions: {
// eslint-plugin-import's recommended config sets ecmaVersion 2018
ecmaVersion: "latest",
globals: {
...globals.browser,
...globals.node,
},
},
rules: {
"react/jsx-curly-brace-presence": [
"error",
{ propElementValues: "always" },
],
"react/prop-types": "off", // Temporary
"react/display-name": "off", // Temporary
"import/no-unresolved": "off",
"import/namespace": "off", // Biome has equivalent
"import/no-named-as-default": "off", // Temporary
"react-hooks/refs": "off", // Too many false positives
"react-hooks/exhaustive-deps": [
"warn",
{ additionalHooks: "(useClientLayoutEffect)" },
],
},
},
{
files: ["**/*.{ts,tsx}"],
extends: [tseslint.configs.recommended],
linterOptions: {
reportUnusedDisableDirectives: true,
},
rules: {
//
// Start: Covered by TypeScript, and slow without a TS resolver
"import/default": "off",
"import/export": "off",
"import/named": "off",
"import/no-named-as-default-member": "off",
// End
"@typescript-eslint/no-explicit-any": "off", // Temporary
"@typescript-eslint/no-unused-expressions": [
"error",
/* https://eslint.org/docs/latest/rules/no-unused-expressions#allowshortcircuit-and-allowternary */
{ allowShortCircuit: true, allowTernary: true },
],
},
},
{
files: ["**/*.test.*", "**/__tests__/*"],
extends: [testingLibrary.configs["flat/react"], vitest.configs.recommended],
},
{
files: ["aksel.nav.no/website/**"],
plugins: {
"@next/next": nextPlugin,
},
rules: {
...nextPlugin.configs.recommended.rules,
"react/no-unknown-property": [2, { ignore: ["jsx", "global"] }],
"react/react-in-jsx-scope": "off",
"@next/next/no-html-link-for-pages": [
"error",
"aksel.nav.no/website/pages/",
],
},
},
{
files: ["aksel.nav.no/website/pages/eksempler/**"],
rules: {
"@next/next/no-img-element": "off",
},
},
{
files: [
"aksel.nav.no/website/pages/eksempler/**/*.tsx",
"aksel.nav.no/website/pages/templates/**/*.tsx",
],
plugins: {
"aksel-local": akselLocal,
},
rules: {
"aksel-local/args-check": ["error"],
"aksel-local/comment-check": ["error"],
},
},
{
files: [
"**/examples/__parts*/*.tsx",
"**/pages/templates/**/*.tsx",
"**/pages/eksempler/**/*.tsx",
],
plugins: {
"aksel-local": akselLocal,
},
rules: {
"aksel-local/import-check": ["error"], // Only allow imports from @navikt and react
},
},
{
files: ["**/examples/__parts-inline/*.tsx"],
rules: {
"arrow-body-style": ["error", "never"],
"func-style": ["error", "expression"],
"import/no-named-export": "error",
},
},
{
files: ["@navikt/**"],
ignores: ["**/*.stories.tsx"],
rules: {
"no-restricted-imports": [
"error",
{
paths: [
{
name: "react",
importNames: [
// React 18
"useId",
"useInsertionEffect",
"useSyncExternalStore",
"useDeferredValue",
"useTransition",
"startTransition",
// React 19
"useOptimistic",
"useActionState",
"use",
"cache",
"useEffectEvent",
"ViewTransition",
"addTransitionType",
],
message:
"We currently only support React-features accesible in React 17. To add new features, we will need to update peerDependencies (breaking change).",
},
// react-dom 18+
{
name: "react-dom/client",
importNames: ["createRoot", "hydrateRoot"],
message: "React 18+ API not allowed (targeting React 17).",
},
{
name: "react-dom",
importNames: ["browser"],
message: "React 19+ API not allowed (targeting React 17).",
},
],
patterns: [
{
group: ["fs", "path"],
message:
'Use `node:` prefix when importing native node modules, e.g. `import path from "node:path"`.',
},
{
group: ["**/esm/**", "**/cjs/**"],
message:
"Do not import from build output directories. Import from source instead.",
},
],
},
],
"no-restricted-properties": [
"error",
// React 18
{
object: "React",
property: "useId",
message: "React 18+ API not allowed (targeting React 17).",
},
{
object: "React",
property: "useInsertionEffect",
message: "React 18+ API not allowed (targeting React 17).",
},
{
object: "React",
property: "useSyncExternalStore",
message: "React 18+ API not allowed (targeting React 17).",
},
{
object: "React",
property: "useDeferredValue",
message: "React 18+ API not allowed (targeting React 17).",
},
{
object: "React",
property: "useTransition",
message: "React 18+ API not allowed (targeting React 17).",
},
{
object: "React",
property: "startTransition",
message: "React 18+ API not allowed (targeting React 17).",
},
// React 19
{
object: "React",
property: "useOptimistic",
message: "React 19+ API not allowed (targeting React 17).",
},
{
object: "React",
property: "useActionState",
message: "React 19+ API not allowed (targeting React 17).",
},
{
object: "React",
property: "use",
message: "React 19+ API not allowed (targeting React 17).",
},
{
object: "React",
property: "cache",
message: "React 19+ API not allowed (targeting React 17).",
},
{
object: "React",
property: "useEffectEvent",
message: "React 19+ API not allowed (targeting React 17).",
},
],
},
},
]);