import babelParser from "@babel/eslint-parser"; import eslint from "@eslint/js"; import reactHooks from "eslint-plugin-react-hooks"; import globals from "globals"; const sourceExtensions = "{js,jsx,mjs,ts,tsx,mts}"; const layerPatterns = { domain: [ "**/application/**", "**/presentation/**", "**/adapters/**", "**/bootstrap/**", "react", "react-dom", "@tanstack/**", ], application: [ "**/presentation/**", "**/adapters/**", "**/bootstrap/**", "react", "react-dom", "@tanstack/**", ], presentation: [ "**/adapters/**", "**/bootstrap/**", "**/application/ports/out/**", "@tanstack/**", ], adapters: ["**/presentation/**", "**/bootstrap/**"], }; function restrictedImports(patterns) { return ["error", { patterns }]; } const commonLanguageOptions = { ecmaVersion: "latest", sourceType: "module", globals: { ...globals.browser, ...globals.node, }, }; const commonSecurityRules = { "no-eval": "error", "no-new-func": "error", "no-script-url": "error", "no-restricted-syntax": [ "error", { selector: "JSXAttribute[name.name='dangerouslySetInnerHTML']", message: "Raw HTML injection is prohibited by FE-OC-019.", }, { selector: "CallExpression[callee.object.name='document'][callee.property.name='createElement'][arguments.0.value='script']", message: "Runtime script construction is prohibited by FE-OC-019.", }, ], }; const hookRules = { "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "error", }; export default [ { ignores: [ "dist/**", "node_modules/**", "artifacts/**", "tests/fixtures/typecheck/**", "tests/fixtures/architecture/forbidden/**", "tests/fixtures/security/forbidden/**", ], }, eslint.configs.recommended, { files: [`**/*.${sourceExtensions}`], languageOptions: { ...commonLanguageOptions, parserOptions: { ecmaFeatures: { jsx: true }, }, }, plugins: { "react-hooks": reactHooks, }, rules: { ...commonSecurityRules, ...hookRules, }, }, { files: ["**/*.{ts,mts}"], languageOptions: { ...commonLanguageOptions, parser: babelParser, parserOptions: { requireConfigFile: false, babelOptions: { plugins: [ ["@babel/plugin-syntax-typescript", { isTSX: false }], ], }, }, }, rules: { "no-undef": "off", "no-unused-vars": "off", }, }, { files: ["**/*.tsx"], languageOptions: { ...commonLanguageOptions, parser: babelParser, parserOptions: { requireConfigFile: false, babelOptions: { plugins: [ [ "@babel/plugin-syntax-typescript", { allExtensions: true, isTSX: true }, ], "@babel/plugin-syntax-jsx", ], }, }, }, rules: { "no-undef": "off", "no-unused-vars": "off", }, }, { files: [`src/domain/**/*.${sourceExtensions}`], rules: { "no-restricted-imports": restrictedImports(layerPatterns.domain), "no-restricted-globals": [ "error", "window", "document", "localStorage", "fetch", ], }, }, { files: [`src/application/**/*.${sourceExtensions}`], rules: { "no-restricted-imports": restrictedImports(layerPatterns.application), "no-restricted-globals": [ "error", "window", "document", "localStorage", "fetch", ], }, }, { files: [`src/presentation/**/*.${sourceExtensions}`], rules: { "no-restricted-imports": restrictedImports(layerPatterns.presentation), "no-restricted-globals": [ "error", "fetch", "localStorage", "sessionStorage", ], }, }, { files: [ `src/presentation/adapters/query/**/*.${sourceExtensions}`, ], rules: { "no-restricted-imports": restrictedImports([ "**/adapters/http/**", "**/adapters/storage/**", "**/adapters/auth/**", "**/bootstrap/**", "**/application/ports/out/**", ]), }, }, { files: [`src/adapters/**/*.${sourceExtensions}`], rules: { "no-restricted-imports": restrictedImports(layerPatterns.adapters), }, }, { files: [`tests/**/*.${sourceExtensions}`], languageOptions: { globals: { ...globals.browser, ...globals.node, }, }, }, { files: [ `tests/fixtures/architecture/forbidden/**/*.${sourceExtensions}`, ], rules: { "no-restricted-imports": restrictedImports([ "**/adapters/**", "**/application/ports/out/**", "@tanstack/**", "react", "react-dom", ]), "no-restricted-globals": [ "error", "fetch", "localStorage", "sessionStorage", ], }, }, ];