UNPKG

eslint-config-agent

Version:

ESLint configuration package with TypeScript support

1,014 lines (988 loc) 36 kB
import js from "@eslint/js"; import tsPlugin from "@typescript-eslint/eslint-plugin"; import tsParser from "@typescript-eslint/parser"; import reactHooks from "eslint-plugin-react-hooks"; import reactPlugin from "eslint-plugin-react"; import importPlugin from "eslint-plugin-import"; import securityPlugin from "eslint-plugin-security"; import nPlugin from "eslint-plugin-n"; import classExportPlugin from "eslint-plugin-class-export"; import storybookPlugin from "eslint-plugin-storybook"; import globals from "globals"; import allRules from "./rules/index.js"; // Conditionally import preact plugin if available let preactPlugin = null; try { preactPlugin = (await import("eslint-plugin-preact")).default; } catch (error) { // eslint-plugin-preact is not available } // Shared rules for both JS and TS files const sharedRules = { ...allRules.pluginRules, "object-curly-newline": "off", "no-shadow": "off", "comma-dangle": "off", "function-paren-newline": "off", quotes: "off", "no-unused-vars": "off", "max-lines-per-function": allRules.maxFunctionLinesWarning, "max-lines": allRules.maxFileLinesWarning, semi: "off", complexity: "off", "no-trailing-spaces": allRules.noTrailingSpacesConfig, "operator-linebreak": "off", "implicit-arrow-linebreak": "off", "arrow-body-style": "off", "no-continue": "off", }; // Shared no-restricted-syntax rules for both JS and TS const sharedRestrictedSyntax = [ { selector: "MemberExpression[optional=true]", message: "Optional chaining is not allowed.", }, { selector: "CallExpression[optional=true]", message: "Optional chaining is not allowed.", }, { selector: 'LogicalExpression[operator="??"]', message: "Nullish coalescing operator (??) is not allowed. Use explicit null/undefined checks instead.", }, { selector: "ExportNamedDeclaration[exportKind=type]:not([source]):has(ExportSpecifier)", message: "Type-only exports are not allowed. Use regular export or re-export with 'from' clause.", }, { selector: "ExportSpecifier[local.name=default][exported.name!=default]", message: "Re-exporting default as named export is not allowed. Use explicit export declaration instead.", }, { selector: "Program:has(ImportDeclaration) ExportNamedDeclaration:has(VariableDeclaration > VariableDeclarator[init.type=Identifier]):not(:has(ClassDeclaration))", message: "Exporting imported variables is not allowed. Use direct re-export with 'from' clause or define new values.", }, { selector: "SwitchStatement > SwitchCase > ReturnStatement[argument=null]", message: "Switch case functions must provide an explicit return value. Default return values are not allowed.", }, { selector: "SwitchStatement > SwitchCase > BlockStatement > ReturnStatement[argument=null]", message: "Switch case functions must provide an explicit return value. Default return values are not allowed.", }, { selector: "SwitchStatement > SwitchCase[test=null]", message: "Default cases are not allowed in switch statements. Handle all possible cases explicitly.", }, { selector: "ExportNamedDeclaration[source.value=/^[a-z]/]:not([source.value=/^@/])", message: "Exporting from external libraries is not allowed. Only re-export from relative paths or scoped packages.", }, allRules.noProcessEnvPropertiesConfig, allRules.noExportSpecifiersConfig, ]; // Required export rules (always errors) const requiredExportRules = [ { selector: "ClassDeclaration:not(ExportNamedDeclaration > ClassDeclaration):not(ExportDefaultDeclaration > ClassDeclaration)", message: "Classes must be exported. Use 'export class' or 'export default class'.", }, { selector: "TSEnumDeclaration:not(ExportNamedDeclaration > TSEnumDeclaration):not(ExportDefaultDeclaration > TSEnumDeclaration)", message: "Enums must be exported. Use 'export enum' or 'export default enum'.", }, ]; // TypeScript-specific no-restricted-syntax rules const tsOnlyRestrictedSyntax = [ { selector: 'TSTypeReference[typeName.name="Record"] > TSTypeParameterInstantiation > .params:first-child TSLiteralType', message: "Avoid using Record with string literal keys. Use a more specific interface or type instead.", }, { selector: 'TSTypeReference[typeName.name="Record"] > TSTypeParameterInstantiation > TSLiteralType:first-child', message: "Avoid using Record with string literal keys. Use a more specific interface or type instead.", }, { selector: "TSTypeAnnotation > TSUnionType:not(PropertyDefinition > .typeAnnotation > .typeAnnotation):not(TSPropertySignature > .typeAnnotation > .typeAnnotation)", message: "Use a named type declaration instead of inline union types.", }, { selector: "TSPropertySignature > TSTypeAnnotation > TSUnionType:has(TSLiteralType)", message: "Interface properties with literal unions should use a named type declaration.", }, { selector: "PropertyDefinition > TSTypeAnnotation > TSUnionType:has(TSLiteralType)", message: "Class properties with literal unions should use a named type declaration.", }, allRules.noTypeAssertionsConfig, allRules.noClassPropertyDefaultsConfig, { selector: "TSAsExpression:has(> TSIndexedAccessType > TSTypeQuery)", message: 'Type assertions with indexed access types like "as (typeof X)[number]" are not allowed. Use a named type instead.', }, { selector: "SwitchStatement > SwitchCase ArrowFunctionExpression:not([returnType])", message: "Switch case arrow functions must have explicit return type annotations.", }, { selector: "SwitchStatement > SwitchCase FunctionExpression:not([returnType])", message: "Switch case function expressions must have explicit return type annotations.", }, { selector: "SwitchStatement > SwitchCase > BlockStatement ArrowFunctionExpression:not([returnType])", message: "Switch case arrow functions must have explicit return type annotations.", }, { selector: "SwitchStatement > SwitchCase > BlockStatement FunctionExpression:not([returnType])", message: "Switch case function expressions must have explicit return type annotations.", }, { selector: "FunctionDeclaration:has(SwitchStatement):not([returnType])", message: "Functions containing switch statements must have explicit return type annotations.", }, { selector: "ArrowFunctionExpression:has(SwitchStatement):not([returnType])", message: "Arrow functions containing switch statements must have explicit return type annotations.", }, { selector: "FunctionExpression:has(SwitchStatement):not([returnType])", message: "Function expressions containing switch statements must have explicit return type annotations.", }, ]; const config = [ // Global plugin definitions { plugins: { n: nPlugin, "class-export": classExportPlugin, }, }, reactHooks.configs["recommended-latest"], js.configs.recommended, // TypeScript and TSX files { files: ["**/*.ts", "**/*.tsx"], ignores: [ "**/node_modules/**", "**/dist/**", "**/build/**", "pnpm-lock.yaml", "**/*.stories.{js,jsx,ts,tsx}", ], languageOptions: { parser: tsParser, parserOptions: { ecmaVersion: "latest", sourceType: "module", ecmaFeatures: { jsx: true, }, }, globals: { ...globals.browser, ...globals.es2021, }, }, plugins: { "@typescript-eslint": tsPlugin, react: reactPlugin, import: importPlugin, security: securityPlugin, n: nPlugin, "class-export": classExportPlugin, ...(preactPlugin && { preact: preactPlugin }), }, settings: { "import/resolver": { typescript: {}, }, }, rules: { ...sharedRules, ...allRules.typescriptEslintRules, "no-undef": "off", // TypeScript handles this "no-restricted-syntax": [ "error", ...sharedRestrictedSyntax, ...tsOnlyRestrictedSyntax, ...requiredExportRules, ], }, }, // TypeScript/TSX Rules - Switch case rules as errors, other rules as warnings { files: ["**/*.tsx"], ignores: ["**/*.stories.{js,jsx,ts,tsx}"], rules: { // Include all shared rules (like max-lines-per-function) ...sharedRules, "no-restricted-syntax": [ "error", // Switch case rules as errors { selector: "SwitchStatement > SwitchCase > ReturnStatement[argument=null]", message: "Switch case functions must provide an explicit return value. Default return values are not allowed.", }, { selector: "SwitchStatement > SwitchCase > BlockStatement > ReturnStatement[argument=null]", message: "Switch case functions must provide an explicit return value. Default return values are not allowed.", }, { selector: "SwitchStatement > SwitchCase[test=null]", message: "Default cases are not allowed in switch statements. Handle all possible cases explicitly.", }, { selector: "SwitchStatement > SwitchCase ArrowFunctionExpression:not([returnType])", message: "Switch case arrow functions must have explicit return type annotations.", }, { selector: "SwitchStatement > SwitchCase FunctionExpression:not([returnType])", message: "Switch case function expressions must have explicit return type annotations.", }, { selector: "SwitchStatement > SwitchCase > BlockStatement ArrowFunctionExpression:not([returnType])", message: "Switch case arrow functions must have explicit return type annotations.", }, { selector: "SwitchStatement > SwitchCase > BlockStatement FunctionExpression:not([returnType])", message: "Switch case function expressions must have explicit return type annotations.", }, { selector: "FunctionDeclaration:has(SwitchStatement):not([returnType])", message: "Functions containing switch statements must have explicit return type annotations.", }, { selector: "ArrowFunctionExpression:has(SwitchStatement):not([returnType])", message: "Arrow functions containing switch statements must have explicit return type annotations.", }, { selector: "FunctionExpression:has(SwitchStatement):not([returnType])", message: "Function expressions containing switch statements must have explicit return type annotations.", }, // className requirement for HTML elements { selector: 'JSXOpeningElement:not([name.name=/^[A-Z]/]):not([name.name="Fragment"]):not(:has(JSXAttribute[name.name="className"]))', message: "HTML elements must have a className attribute.", }, ], }, }, // TypeScript/TSX Rules - Other rules as warnings to accommodate className check { files: ["**/*.tsx"], ignores: ["**/*.stories.{js,jsx,ts,tsx}"], rules: { "no-restricted-syntax": [ "warn", // Include shared rules but remove the multiple exports restriction and switch case rules for TSX ...sharedRestrictedSyntax.filter( (rule) => rule.selector !== "ExportNamedDeclaration[specifiers.length>1]:not([source])" && rule.selector !== "Program:has(ExportNamedDeclaration:not([source]) ~ ExportNamedDeclaration:not([source]))" && rule.selector !== "SwitchStatement > SwitchCase > ReturnStatement[argument=null]" && rule.selector !== "SwitchStatement > SwitchCase > BlockStatement > ReturnStatement[argument=null]" && rule.selector !== "SwitchStatement > SwitchCase[test=null]" ), // TSX files: Allow max one type/interface export + one component export { selector: "Program:has(ExportNamedDeclaration:not([source]):not([exportKind=type]):not(:has(TSInterfaceDeclaration)):not(:has(TSTypeAliasDeclaration)) ~ ExportNamedDeclaration:not([source]):not([exportKind=type]):not(:has(TSInterfaceDeclaration)):not(:has(TSTypeAliasDeclaration)))", message: "Only one component export per TSX file is allowed (plus optionally one type/interface export).", }, { selector: "Program:has(ExportNamedDeclaration[exportKind=type]:not([source]) ~ ExportNamedDeclaration[exportKind=type]:not([source]))", message: "Only one type export per TSX file is allowed (plus optionally one component export).", }, { selector: "Program:has(ExportNamedDeclaration:not([source]):has(TSInterfaceDeclaration) ~ ExportNamedDeclaration:not([source]):has(TSInterfaceDeclaration))", message: "Only one interface export per TSX file is allowed (plus optionally one component export).", }, { selector: "Program:has(ExportNamedDeclaration:not([source]):has(TSTypeAliasDeclaration) ~ ExportNamedDeclaration:not([source]):has(TSTypeAliasDeclaration))", message: "Only one type alias export per TSX file is allowed (plus optionally one component export).", }, { selector: "Program:has(ExportNamedDeclaration:not([source]):has(TSInterfaceDeclaration) ~ ExportNamedDeclaration[exportKind=type]:not([source]))", message: "Cannot have both interface export and type-only export in the same TSX file.", }, { selector: "Program:has(ExportNamedDeclaration:not([source]):has(TSTypeAliasDeclaration) ~ ExportNamedDeclaration[exportKind=type]:not([source]))", message: "Cannot have both type alias export and type-only export in the same TSX file.", }, ...tsOnlyRestrictedSyntax.filter( (rule) => rule.selector !== "SwitchStatement > SwitchCase ArrowFunctionExpression:not([returnType])" && rule.selector !== "SwitchStatement > SwitchCase FunctionExpression:not([returnType])" && rule.selector !== "SwitchStatement > SwitchCase > BlockStatement ArrowFunctionExpression:not([returnType])" && rule.selector !== "SwitchStatement > SwitchCase > BlockStatement FunctionExpression:not([returnType])" && rule.selector !== "FunctionDeclaration:has(SwitchStatement):not([returnType])" && rule.selector !== "ArrowFunctionExpression:has(SwitchStatement):not([returnType])" && rule.selector !== "FunctionExpression:has(SwitchStatement):not([returnType])" ), // Required export rules - these will be warnings in TSX since we can't mix severities ...requiredExportRules, ], }, }, // JavaScript files (not JSX) { files: ["**/*.js"], ignores: [ "**/node_modules/**", "**/dist/**", "**/build/**", "pnpm-lock.yaml", "**/*.umd.js", "**/*.cjs", "**/*.mjs", "**/*.stories.{js,jsx,ts,tsx}", "**/rules/**/index.js", ], languageOptions: { parserOptions: { ecmaVersion: "latest", sourceType: "module", ecmaFeatures: { jsx: true, }, }, globals: { ...globals.browser, ...globals.es2021, ...globals.node, }, }, plugins: { import: importPlugin, react: reactPlugin, security: securityPlugin, n: nPlugin, "class-export": classExportPlugin, }, settings: { react: { version: "detect", }, }, rules: { ...sharedRules, "no-restricted-syntax": [ "error", ...sharedRestrictedSyntax, // Only class rules apply to JS files (no enums in JS) { selector: "ClassDeclaration:not(ExportNamedDeclaration > ClassDeclaration):not(ExportDefaultDeclaration > ClassDeclaration)", message: "Classes must be exported. Add 'export' before the class declaration.", }, ], }, }, // Node.js files (must come after general JS config to override) { files: ["scripts/**/*.js"], languageOptions: { globals: { ...globals.node, }, }, rules: { ...sharedRules, "no-restricted-syntax": ["error", ...sharedRestrictedSyntax], }, }, // JSX files with TypeScript support { files: ["**/*.jsx"], ignores: [ "**/node_modules/**", "**/dist/**", "**/build/**", "pnpm-lock.yaml", "**/*.stories.{js,jsx,ts,tsx}", ], languageOptions: { parser: tsParser, parserOptions: { ecmaVersion: "latest", sourceType: "module", ecmaFeatures: { jsx: true, }, }, globals: { ...globals.browser, ...globals.es2021, }, }, plugins: { "@typescript-eslint": tsPlugin, react: reactPlugin, import: importPlugin, security: securityPlugin, n: nPlugin, "class-export": classExportPlugin, ...(preactPlugin && { preact: preactPlugin }), }, settings: { react: { version: "detect", }, }, rules: { ...sharedRules, "no-undef": "off", // TypeScript handles this "no-restricted-syntax": [ "error", // Switch case rules as errors { selector: "SwitchStatement > SwitchCase > ReturnStatement[argument=null]", message: "Switch case functions must provide an explicit return value. Default return values are not allowed.", }, { selector: "SwitchStatement > SwitchCase > BlockStatement > ReturnStatement[argument=null]", message: "Switch case functions must provide an explicit return value. Default return values are not allowed.", }, { selector: "SwitchStatement > SwitchCase[test=null]", message: "Default cases are not allowed in switch statements. Handle all possible cases explicitly.", }, { selector: "SwitchStatement > SwitchCase ArrowFunctionExpression:not([returnType])", message: "Switch case arrow functions must have explicit return type annotations.", }, { selector: "SwitchStatement > SwitchCase FunctionExpression:not([returnType])", message: "Switch case function expressions must have explicit return type annotations.", }, { selector: "SwitchStatement > SwitchCase > BlockStatement ArrowFunctionExpression:not([returnType])", message: "Switch case arrow functions must have explicit return type annotations.", }, { selector: "SwitchStatement > SwitchCase > BlockStatement FunctionExpression:not([returnType])", message: "Switch case function expressions must have explicit return type annotations.", }, { selector: "FunctionDeclaration:has(SwitchStatement):not([returnType])", message: "Functions containing switch statements must have explicit return type annotations.", }, { selector: "ArrowFunctionExpression:has(SwitchStatement):not([returnType])", message: "Arrow functions containing switch statements must have explicit return type annotations.", }, { selector: "FunctionExpression:has(SwitchStatement):not([returnType])", message: "Function expressions containing switch statements must have explicit return type annotations.", }, // className requirement for HTML elements { selector: 'JSXOpeningElement:not([name.name=/^[A-Z]/]):not([name.name="Fragment"]):not(:has(JSXAttribute[name.name="className"]))', message: "HTML elements must have a className attribute.", }, // Required export rules as errors (class rule only for JSX) { selector: "ClassDeclaration:not(ExportNamedDeclaration > ClassDeclaration):not(ExportDefaultDeclaration > ClassDeclaration)", message: "Classes must be exported. Add 'export' before the class declaration.", }, ], }, }, // JSX Warning Rules - Lower priority restricted syntax rules as warnings { files: ["**/*.jsx"], ignores: [ "**/node_modules/**", "**/dist/**", "**/build/**", "pnpm-lock.yaml", "**/*.stories.{js,jsx,ts,tsx}", ], languageOptions: { parser: tsParser, parserOptions: { ecmaVersion: "latest", sourceType: "module", ecmaFeatures: { jsx: true, }, }, globals: { ...globals.browser, ...globals.es2021, }, }, plugins: { "@typescript-eslint": tsPlugin, react: reactPlugin, import: importPlugin, security: securityPlugin, n: nPlugin, "class-export": classExportPlugin, ...(preactPlugin && { preact: preactPlugin }), }, settings: { react: { version: "detect", }, }, rules: { "no-undef": "off", // TypeScript handles this "no-restricted-syntax": [ "warn", // Include shared rules but remove the multiple exports restriction and switch case rules for JSX ...sharedRestrictedSyntax.filter( (rule) => rule.selector !== "ExportNamedDeclaration[specifiers.length>1]:not([source])" && rule.selector !== "Program:has(ExportNamedDeclaration:not([source]) ~ ExportNamedDeclaration:not([source]))" && rule.selector !== "SwitchStatement > SwitchCase > ReturnStatement[argument=null]" && rule.selector !== "SwitchStatement > SwitchCase > BlockStatement > ReturnStatement[argument=null]" && rule.selector !== "SwitchStatement > SwitchCase[test=null]" ), // JSX files: Allow max one type/interface export + one component export { selector: "Program:has(ExportNamedDeclaration:not([source]):not([exportKind=type]):not(:has(TSInterfaceDeclaration)):not(:has(TSTypeAliasDeclaration)) ~ ExportNamedDeclaration:not([source]):not([exportKind=type]):not(:has(TSInterfaceDeclaration)):not(:has(TSTypeAliasDeclaration)))", message: "Only one component export per JSX file is allowed (plus optionally one type/interface export).", }, { selector: "Program:has(ExportNamedDeclaration[exportKind=type]:not([source]) ~ ExportNamedDeclaration[exportKind=type]:not([source]))", message: "Only one type export per JSX file is allowed (plus optionally one component export).", }, { selector: "Program:has(ExportNamedDeclaration:not([source]):has(TSInterfaceDeclaration) ~ ExportNamedDeclaration:not([source]):has(TSInterfaceDeclaration))", message: "Only one interface export per JSX file is allowed (plus optionally one component export).", }, { selector: "Program:has(ExportNamedDeclaration:not([source]):has(TSTypeAliasDeclaration) ~ ExportNamedDeclaration:not([source]):has(TSTypeAliasDeclaration))", message: "Only one type alias export per JSX file is allowed (plus optionally one component export).", }, ], }, }, // Disable function and file size limits for test and spec files { files: [ "**/*.test.{js,jsx,ts,tsx}", "**/*.spec.{js,jsx,ts,tsx}", "**/test/**/*.{js,jsx,ts,tsx}", "**/tests/**/*.{js,jsx,ts,tsx}", "**/__tests__/**/*.{js,jsx,ts,tsx}", ], ignores: [ "**/long-function-test.tsx", // Exception: this file tests the max-lines rule itself "**/test/export/**", // Export tests should follow strict export rules "**/test/required-exports/**", // Required export tests should follow strict export rules ], rules: { "max-lines-per-function": "off", "max-lines": "off", // Ignore file length limits in test and spec files // Allow multiple exports in test files for testing import/export patterns "no-restricted-syntax": [ "warn", ...sharedRestrictedSyntax.filter( (rule) => rule.selector !== "ExportNamedDeclaration[specifiers.length>1]:not([source])" && rule.selector !== "Program:has(ExportNamedDeclaration:not([source]) ~ ExportNamedDeclaration:not([source]))" && rule.selector !== "ExportNamedDeclaration:not([source]):not(:has(VariableDeclaration)):not(:has(FunctionDeclaration)):not(:has(ClassDeclaration)):not(:has(TSInterfaceDeclaration)):not(:has(TSTypeAliasDeclaration)):not(:has(TSEnumDeclaration))" ), ...tsOnlyRestrictedSyntax, ], }, }, // Test files that should have ERROR level rules but exclude export specifier rule { files: [ "**/test/type-assertions/**", "**/test/test-optional.ts", "**/test/test-js-optional.js", "**/test/test-record-literals.ts", "**/test/no-env-access-test.ts", "**/test/import-export-rules.ts", ], rules: { "max-lines-per-function": "off", "no-restricted-syntax": [ "error", // Error level for these test files ...sharedRestrictedSyntax.filter( (rule) => rule.selector !== "ExportNamedDeclaration:not([source]):not(:has(VariableDeclaration)):not(:has(FunctionDeclaration)):not(:has(ClassDeclaration)):not(:has(TSInterfaceDeclaration)):not(:has(TSTypeAliasDeclaration)):not(:has(TSEnumDeclaration))" ), ...tsOnlyRestrictedSyntax, ], }, }, // Test files configuration with mixed severity levels { files: [ "**/test/invalid.tsx", // Special handling for the main test file "**/test/single-export-valid.ts", // Allow export specifiers for import/group-exports testing "**/test/typescript-rules.ts", // Allow export specifiers for typescript rules testing "**/test/type-assertions/indexed-access-valid.ts", // Allow export specifiers for type assertions testing ], rules: { "max-lines-per-function": "off", "no-restricted-syntax": [ "warn", // Base level for most rules ...sharedRestrictedSyntax.filter( (rule) => rule.selector !== "ExportNamedDeclaration[specifiers.length>1]:not([source])" && rule.selector !== "Program:has(ExportNamedDeclaration:not([source]) ~ ExportNamedDeclaration:not([source]))" && rule.selector !== "ExportNamedDeclaration:not([source]):not(:has(VariableDeclaration)):not(:has(FunctionDeclaration)):not(:has(ClassDeclaration)):not(:has(TSInterfaceDeclaration)):not(:has(TSTypeAliasDeclaration)):not(:has(TSEnumDeclaration))" ), ...tsOnlyRestrictedSyntax, // For TSX files, also include required export rules as warnings since we can't mix severities ...requiredExportRules, ], }, }, // Index files configuration - allow specific export patterns { files: [ "**/index.{js,ts,tsx,jsx}", "**/test/index-files/**/*.{js,ts,tsx,jsx}", ], rules: { "import/no-default-export": "off", // Allow multiple re-exports in index files but keep other restrictions "no-restricted-syntax": [ "error", // Keep most rules but allow multiple exports and export statements for index files ...sharedRestrictedSyntax.filter( (rule) => rule.selector !== "ExportNamedDeclaration[specifiers.length>1]:not([source])" && rule.selector !== "Program:has(ExportNamedDeclaration:not([source]) ~ ExportNamedDeclaration:not([source]))" && rule.selector !== "ExportNamedDeclaration:not([source]):not([exportKind=type]):has(ExportSpecifier)" && rule.selector !== "ExportNamedDeclaration[exportKind=type]:not([source]):has(ExportSpecifier)" ), ...tsOnlyRestrictedSyntax, ], }, }, // Switch case rules as errors for all TypeScript/JSX files (must come last to override) { files: ["**/*.{ts,tsx,js,jsx}"], ignores: [ "**/*.stories.{js,jsx,ts,tsx}", "**/test/**", "!**/test/export/**", "!**/test/required-exports/**", "**/rules/**/index.js", ], rules: { "no-restricted-syntax": [ "error", // Switch case rules as errors { selector: "SwitchStatement > SwitchCase > ReturnStatement[argument=null]", message: "Switch case functions must provide an explicit return value. Default return values are not allowed.", }, { selector: "SwitchStatement > SwitchCase > BlockStatement > ReturnStatement[argument=null]", message: "Switch case functions must provide an explicit return value. Default return values are not allowed.", }, { selector: "SwitchStatement > SwitchCase[test=null]", message: "Default cases are not allowed in switch statements. Handle all possible cases explicitly.", }, { selector: "SwitchStatement > SwitchCase ArrowFunctionExpression:not([returnType])", message: "Switch case arrow functions must have explicit return type annotations.", }, { selector: "SwitchStatement > SwitchCase FunctionExpression:not([returnType])", message: "Switch case function expressions must have explicit return type annotations.", }, { selector: "SwitchStatement > SwitchCase > BlockStatement ArrowFunctionExpression:not([returnType])", message: "Switch case arrow functions must have explicit return type annotations.", }, { selector: "SwitchStatement > SwitchCase > BlockStatement FunctionExpression:not([returnType])", message: "Switch case function expressions must have explicit return type annotations.", }, { selector: "FunctionDeclaration:has(SwitchStatement):not([returnType])", message: "Functions containing switch statements must have explicit return type annotations.", }, { selector: "ArrowFunctionExpression:has(SwitchStatement):not([returnType])", message: "Arrow functions containing switch statements must have explicit return type annotations.", }, { selector: "FunctionExpression:has(SwitchStatement):not([returnType])", message: "Function expressions containing switch statements must have explicit return type annotations.", }, { selector: "MemberExpression[optional=true]", message: "Optional chaining is not allowed.", }, { selector: "CallExpression[optional=true]", message: "Optional chaining is not allowed.", }, { selector: 'LogicalExpression[operator="??"]', message: "Nullish coalescing operator (??) is not allowed. Use explicit null/undefined checks instead.", }, { selector: 'TSAsExpression[typeAnnotation.type="TSIndexedAccessType"]', message: 'Type assertions with indexed access types like "as (typeof X)[number]" are not allowed. Use a named type instead.', }, allRules.noTypeAssertionsConfig, allRules.noClassPropertyDefaultsConfig, allRules.noProcessEnvPropertiesConfig, allRules.noExportSpecifiersConfig, // Export restriction rules // Required export rules ...requiredExportRules, ], }, }, // className requirement for JSX files { files: ["**/*.{tsx,jsx}"], ignores: ["**/*.stories.{js,jsx,ts,tsx}"], rules: { "no-restricted-syntax": [ "error", { selector: 'JSXOpeningElement:not([name.name=/^[A-Z]/]):not([name.name="Fragment"]):not(:has(JSXAttribute[name.name="className"]))', message: "HTML elements must have a className attribute.", }, ], }, }, // Function and file length rules - strict error thresholds { files: ["**/*.{ts,tsx,js,jsx}"], ignores: [ "**/*.stories.{js,jsx,ts,tsx}", "**/*.test.{js,jsx,ts,tsx}", "**/*.spec.{js,jsx,ts,tsx}", "**/test/**/*.{js,jsx,ts,tsx}", "**/tests/**/*.{js,jsx,ts,tsx}", "**/__tests__/**/*.{js,jsx,ts,tsx}", ], rules: { // Function length: error at 70+ lines "max-lines-per-function": allRules.maxFunctionLinesError, // File length: error at 100+ lines "max-lines": allRules.maxFileLinesError, }, }, // Disable file length rules for configuration and spec files { files: [ "index.js", // Main configuration file "**/rules/**/*.spec.js", // Spec files in rules directory "**/scripts/**/*.js", // Script files ], rules: { "max-lines": "off", "max-lines-per-function": "off", }, }, // Allow default exports in configuration files (must be last to override) { files: [ "*.config.js", "*.config.ts", "eslint.config.js", "eslint.config.ts", ], rules: { "import/no-default-export": "off", }, }, // Storybook files configuration - only use storybook plugin rules { files: ["**/*.stories.{js,jsx,ts,tsx}"], languageOptions: { parser: tsParser, parserOptions: { ecmaVersion: "latest", sourceType: "module", ecmaFeatures: { jsx: true, }, }, globals: { ...globals.browser, ...globals.es2021, }, }, plugins: { storybook: storybookPlugin, }, rules: { // Enable recommended storybook rules only ...storybookPlugin.configs.recommended.rules, }, }, // Rules directory configuration - allow export specifiers for API definitions (but not examples) { files: ["**/rules/**/*.{js,ts}"], ignores: ["**/rules/**/examples/**"], languageOptions: { globals: { ...globals.node, }, }, rules: { "no-restricted-syntax": [ "error", // Include all shared rules EXCEPT our export specifier rule ...sharedRestrictedSyntax.filter( (rule) => rule.selector !== "ExportNamedDeclaration:not([source]):not(:has(VariableDeclaration)):not(:has(FunctionDeclaration)):not(:has(ClassDeclaration)):not(:has(TSInterfaceDeclaration)):not(:has(TSTypeAliasDeclaration)):not(:has(TSEnumDeclaration))" ), ], }, }, ]; export default config;