UNPKG

eslint-config-sheriff

Version:

A comprehensive and opinionated TypeScript-first ESLint configuration.

1,214 lines (1,178 loc) 37.3 kB
// ../sheriff-constants/src/index.ts var sheriffStartingOptions = { react: false, lodash: false, remeda: false, next: false, astro: false, playwright: false, storybook: true, jest: false, vitest: false }; var jsExtensions = "js,mjs,cjs"; var jsxExtensions = "jsx,mjsx"; var tsExtensions = "ts,mts,cts"; var tsxExtensions = "tsx,mtsx"; var allJsExtensions = `${jsExtensions},${tsExtensions}`; var allJsxExtensions = `${jsxExtensions},${tsxExtensions}`; var supportedFileTypes = `**/*{${allJsExtensions},${allJsxExtensions},astro}`; var testsFilePatterns = [ `**/*.{test,spec}.{${allJsExtensions}}`, `**/tests/**/*.{${allJsExtensions}}`, `**/__tests__/**/*.{${allJsExtensions}}` ]; var ignores = [ "**/node_modules/**", "**/dist/**", "**/build/**", "**/artifacts/**", "**/coverage/**", "eslint.config.{js,mjs,cjs}" // we currently cannot lint the eslint.config.js itself. It is currently only provided as a .js file and this config currently only supports .ts files. Therefore, eslint.config.js can only be re-enabled once this config support pure .js files too, or the ESLint team support the eslint.config.ts file. ]; // src/getExportableConfig.ts import getGitignorePatterns from "eslint-config-flat-gitignore"; import lodash from "lodash"; // src/getAstroConfig.ts import astro from "eslint-plugin-astro"; import tseslint from "typescript-eslint"; var baseAstroJsxA11yConfig = astro.configs["flat/jsx-a11y-strict"].slice( 0, -1 ); var astroJsxA11yConfig = astro.configs["flat/jsx-a11y-strict"].pop(); var astroJsxA11yPlugin = astroJsxA11yConfig?.plugins?.["jsx-a11y"] ?? {}; var astroJsxA11yRules = astroJsxA11yConfig?.rules ?? {}; var getAstroConfig = (hasReact, customTSConfigPath) => { return tseslint.config( astro.configs["flat/recommended"], hasReact ? { extends: [baseAstroJsxA11yConfig], plugins: { "astro/jsx-a11y": astroJsxA11yPlugin }, rules: astroJsxA11yRules } : {}, { files: ["**/*.astro"], languageOptions: { parserOptions: { project: customTSConfigPath || true, extraFileExtensions: [".astro"] // this is probably already included in the recommended preset, but we are keeping it for safety. } }, settings: { // 'import/core-modules': [ // 'astro:actions', // 'astro:assets', // 'astro:db', // 'astro:content', // 'astro:container', // 'astro:env', // 'astro:i18n', // 'astro:middleware', // 'astro:transitions', // 'astro:transitions/client', // ], "import/parsers": { "astro-eslint-parser": [".astro"], "@typescript-eslint/parser": [".ts", ".tsx", "mts", "cts"], espree: [".js"] } } } ); }; // src/getBaseConfig.ts import createNoRestrictedProperties from "eslint-no-restricted/properties"; import arrowReturnStyle from "eslint-plugin-arrow-return-style"; import fsecond from "eslint-plugin-fsecond"; import pluginImport from "eslint-plugin-import"; import jsdoc from "eslint-plugin-jsdoc"; import * as regexpPlugin from "eslint-plugin-regexp"; import simpleImportSort from "eslint-plugin-simple-import-sort"; import sonarjs from "eslint-plugin-sonarjs"; import tsdoc from "eslint-plugin-tsdoc"; import unicorn from "eslint-plugin-unicorn"; import globals from "globals"; import tseslint3 from "typescript-eslint"; import eslintJs from "@eslint/js"; import preferEarlyReturn from "@regru/eslint-plugin-prefer-early-return"; import stylistic from "@stylistic/eslint-plugin"; // src/handpickedRules/getBaseEslintHandPickedRules.ts import confusingBrowserGlobals from "confusing-browser-globals"; var getBaseEslintHandPickedRules = () => { return { "func-style": 2, "no-promise-executor-return": 2, "no-unreachable-loop": 2, "no-caller": 2, "no-extend-native": 2, "no-extra-bind": 2, "no-extra-label": 2, "no-implicit-coercion": 2, "no-multi-str": 2, "no-negated-condition": 2, "no-new-wrappers": 2, "no-object-constructor": 2, strict: [2, "never"], "no-octal-escape": 2, "no-proto": 2, "no-sequences": [2, { allowInParentheses: false }], "no-unmodified-loop-condition": 2, "no-void": 2, "no-array-constructor": 2, "no-multi-assign": 2, "no-plusplus": 2, "prefer-destructuring": [ 2, { VariableDeclarator: { array: false, object: true }, AssignmentExpression: { array: false, object: false } }, { enforceForRenamedProperties: false } ], "no-useless-call": 2, "prefer-object-has-own": 2, "no-constant-binary-expression": 2, "no-lone-blocks": 2, "no-eval": 2, "no-return-assign": [2, "always"], "no-else-return": [2, { allowElseIf: false }], "prefer-template": 2, "operator-assignment": [2, "never"], "logical-assignment-operators": [2, "never"], "prefer-object-spread": 2, "prefer-rest-params": 2, "no-param-reassign": 2, "no-redeclare": 2, "no-useless-computed-key": 2, "array-callback-return": [2, { allowImplicit: true, checkForEach: true }], "object-shorthand": 2, "no-unneeded-ternary": [2, { defaultAssignment: false }], "require-atomic-updates": 2, "no-nested-ternary": 2, "no-console": [2, { allow: ["warn", "error", "debug", "info", "table"] }], curly: [2, "all"], eqeqeq: 2, "prefer-arrow-callback": 2, "no-useless-assignment": 2, "no-restricted-imports": [ 2, { paths: [ { name: "prop-types", message: "Dont use prop-types. Use Typescript instead." } ], patterns: [ { group: ["node_modules"], message: "Imports from node_modules are likely a user mistake.", importNamePattern: "^" // this is to allow side-effects imports. See: https://github.com/eslint/eslint/pull/18997. }, { group: ["dist"], message: "Imports from dist are likely a user mistake.", importNamePattern: "^" // this is to allow side-effects imports. See: https://github.com/eslint/eslint/pull/18997. } ] } ], "no-restricted-globals": [2, ...confusingBrowserGlobals], "no-return-await": 0, // we are using the @typescript/eslint version "no-use-before-define": 0, // we are using the @typescript/eslint version "no-unused-expressions": 0, // we are using the @typescript/eslint version "no-empty-function": 0, // we are using the @typescript/eslint version "dot-notation": 0, // we are using the @typescript/eslint version "no-shadow": 0, // we are using the @typescript/eslint version "default-param-last": 0, // we are using the @typescript/eslint version "arrow-body-style": 0 // we are using the eslint-plugin-arrow-return-style version }; }; // src/handpickedRules/importHandPickedRules.ts var importHandPickedRules = { // https://github.com/import-js/eslint-plugin-import/issues/2913 // https://github.com/import-js/eslint-plugin-import/issues/2673 "import/newline-after-import": 2, "import/first": 2, "import/no-default-export": 2, "import/no-anonymous-default-export": 2, "import/no-named-as-default": 2, "import/no-duplicates": [2, { "prefer-inline": true }], "import/no-useless-path-segments": [2, { noUselessIndex: true }] // 'import/consistent-type-specifier-style': [2, 'prefer-inline'], // this is not actually needed when "@typescript-eslint/no-import-type-side-effects is set. Explanation here: https://github.com/import-js/eslint-plugin-import/issues/2676#issuecomment-1407107260 }; // src/handpickedRules/jsdocHandPickedRules.ts var jsdocHandPickedRules = { "jsdoc/require-description": 2, "jsdoc/require-description-complete-sentence": 2, "jsdoc/require-hyphen-before-param-description": 2, "jsdoc/require-returns-description": 2, "jsdoc/require-param-name": 2, "jsdoc/require-param-description": 2, "jsdoc/require-asterisk-prefix": 2, "jsdoc/no-types": 2, "jsdoc/no-multi-asterisks": 2, "jsdoc/no-defaults": 2, "jsdoc/no-blank-block-descriptions": 2, "jsdoc/check-indentation": 2, "jsdoc/check-tag-names": [2, { jsxTags: true }], "jsdoc/check-param-names": [ 2, { checkDestructured: false, enableFixer: false } ], "jsdoc/sort-tags": 2, "jsdoc/tag-lines": [2, "any", { startLines: 1 }], "jsdoc/convert-to-jsdoc-comments": 2 }; // src/handpickedRules/sonarjsHandPickedRules.ts var sonarjsHandPickedRules = { "sonarjs/cognitive-complexity": 0, "sonarjs/prefer-immediate-return": 0 }; // src/handpickedRules/stylisticHandPickedRules.ts var stylisticHandPickedRules = { // Prettier doesn't have strong opinions about emptyLines. See: https://prettier.io/docs/en/rationale.html#empty-lines. "@stylistic/padding-line-between-statements": [ 2, // blank lines after every sequence of variable declarations, like the newline-after-var rule. { blankLine: "always", prev: ["const", "let"], next: "*" }, { blankLine: "any", prev: ["const", "let"], next: ["const", "let"] }, //require blank lines before all return statements, like the newline-before-return rule. { blankLine: "always", prev: "*", next: "return" } ] }; // src/handpickedRules/typescriptHandPickedRules.ts var typescriptHandPickedRules = { "@typescript-eslint/no-unsafe-assignment": 0, "@typescript-eslint/no-array-constructor": 0, "@typescript-eslint/use-unknown-in-catch-callback-variable": 0, "@typescript-eslint/no-require-imports": 0, // eventually we will enable https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-module.md instead "@typescript-eslint/ban-ts-comment": [ 2, { "ts-expect-error": false, "ts-ignore": true, "ts-nocheck": false, "ts-check": false } ], "@typescript-eslint/no-use-before-define": 2, "@typescript-eslint/no-inferrable-types": 2, "@typescript-eslint/no-loop-func": 2, "@typescript-eslint/prefer-function-type": 2, "@typescript-eslint/prefer-string-starts-ends-with": 2, "@typescript-eslint/consistent-type-definitions": 2, "@typescript-eslint/consistent-type-assertions": 2, "@typescript-eslint/consistent-type-imports": [ 2, { fixStyle: "inline-type-imports" } ], "@typescript-eslint/consistent-type-exports": [ 2, { fixMixedExportsWithInlineTypeSpecifier: true } ], "@typescript-eslint/explicit-module-boundary-types": 2, "@typescript-eslint/switch-exhaustiveness-check": 2, "@typescript-eslint/method-signature-style": 2, "@typescript-eslint/prefer-nullish-coalescing": [ 2, { ignorePrimitives: true } ], "@typescript-eslint/no-unused-expressions": [ 2, { allowShortCircuit: true, allowTernary: true, allowTaggedTemplates: true, enforceForJSX: true } ], "@typescript-eslint/array-type": 2, "@typescript-eslint/no-empty-function": 2, "@typescript-eslint/prefer-optional-chain": 2, "@typescript-eslint/dot-notation": 2, "@typescript-eslint/no-import-type-side-effects": 2, "@typescript-eslint/default-param-last": 2, "@typescript-eslint/no-empty-object-type": [ 2, { allowInterfaces: "with-single-extends" } ], "@typescript-eslint/no-shadow": [ 2, { hoist: "all", allow: ["resolve", "reject", "done", "next", "err", "error"], ignoreTypeValueShadow: true, ignoreFunctionTypeParameterNameValueShadow: true } ] }; // src/handpickedRules/unicornHandPickedRules.ts var unicornHandPickedRules = { "unicorn/explicit-length-check": 2, "unicorn/consistent-function-scoping": 2, "unicorn/prefer-default-parameters": 2, "unicorn/no-array-push-push": 2, "unicorn/prefer-array-index-of": 2, "unicorn/prefer-array-flat-map": 2, "unicorn/prefer-array-some": 2, "unicorn/prefer-array-find": 2, "unicorn/prefer-array-flat": 2, "unicorn/prefer-includes": 2, "unicorn/prefer-top-level-await": 2, "unicorn/prefer-spread": 2, "unicorn/no-useless-spread": 2, "unicorn/no-useless-fallback-in-spread": 2, "unicorn/no-for-loop": 2, "unicorn/prefer-set-size": 2, "unicorn/prefer-type-error": 2, "unicorn/prefer-object-from-entries": 2, "unicorn/no-instanceof-array": 2, "unicorn/prefer-native-coercion-functions": 2, "unicorn/prefer-logical-operator-over-ternary": 2, "unicorn/prefer-event-target": 2, "unicorn/no-await-expression-member": 2, "unicorn/no-new-array": 2, "unicorn/throw-new-error": 2, "unicorn/no-array-reduce": 2, "unicorn/no-useless-length-check": 2, "unicorn/prefer-prototype-methods": 2, "unicorn/prefer-date-now": 2, "unicorn/prefer-node-protocol": 2, "unicorn/prefer-export-from": [2, { ignoreUsedVariables: true }], "unicorn/no-new-buffer": 2, "unicorn/prefer-query-selector": 2, "unicorn/prefer-string-replace-all": 2, "unicorn/prefer-switch": [2, { emptyDefaultCase: "do-nothing-comment" }], "unicorn/switch-case-braces": 2, "unicorn/catch-error-name": 2, "unicorn/consistent-destructuring": 2, "unicorn/prefer-string-slice": 2, "unicorn/no-await-in-promise-methods": 2, "unicorn/no-single-promise-in-promise-methods": 2, "unicorn/consistent-empty-array-spread": 2, "unicorn/no-unused-properties": 2 }; // src/utils/getLanguageOptionsTypescript.ts import tseslint2 from "typescript-eslint"; var getLanguageOptionsTypescript = (userChosenTSConfig) => { return { parser: tseslint2.parser, parserOptions: { ecmaFeatures: { modules: true }, project: userChosenTSConfig || true } }; }; // src/utils/getTsNamingConventionRule.ts var getTsNamingConventionRule = ({ isTsx }) => { return { "@typescript-eslint/naming-convention": [ 2, { selector: "default", format: ["camelCase", isTsx && "StrictPascalCase"].filter(Boolean), leadingUnderscore: "forbid", trailingUnderscore: "forbid" }, { selector: "variable", format: ["camelCase", "UPPER_CASE"], modifiers: ["const"], types: ["string", "number"], leadingUnderscore: "forbid", trailingUnderscore: "forbid" }, { selector: "objectLiteralProperty", format: null, leadingUnderscore: "allowSingleOrDouble", trailingUnderscore: "forbid" }, { selector: "typeLike", format: ["PascalCase"], leadingUnderscore: "forbid", trailingUnderscore: "forbid" }, // https://typescript-eslint.io/rules/naming-convention/#enforce-that-boolean-variables-are-prefixed-with-an-allowed-verb { selector: "variable", types: ["boolean"], format: ["PascalCase"], prefix: ["is", "are", "has", "should", "can"], leadingUnderscore: "forbid", trailingUnderscore: "forbid" }, { selector: "variable", modifiers: ["destructured"], format: null }, { selector: "typeProperty", format: null } ] }; }; // src/utils/noRestrictedSyntax.ts import createNoRestrictedSyntax from "eslint-no-restricted/syntax"; var baseNoRestrictedSyntaxRules = [ { selector: "LabeledStatement", name: "noLabels", message: "Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand." }, { selector: "ForInStatement", name: "noForInLoops", message: "for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array." }, { selector: "Identifier[name='Reflect']", name: "noReflect", message: "Avoid the Reflect API. It is a very low-level feature that has only rare and specific use-cases if building complex and hacky libraries. There is no need to use this feature for any kind of normal development." }, { selector: "Identifier[name='Proxy']", name: "noProxy", message: "Avoid Proxy." }, // this needs to be temporarily disabled because of a Typescript limitation with discriminated unions. See: https://github.com/microsoft/TypeScript/issues/44253. // { // selector: "BinaryExpression[operator='in']", // name: 'noInOperator', // message: // "Avoid the 'in' operator. In real-world scenarios there is rarely a need for this operator. For most usecases, basic property access is all you need. For every other case, use the Object.hasOwn() or the Object.prototype.hasOwnProperty() method. In the really niche cases where you actually need to check for the existence of a property both in the object itself AND in it's prototype chain, feel free to disable this rule with the inline eslint-disable syntax.", // }, { selector: [ "PropertyDefinition[accessibility='public']", "PropertyDefinition[accessibility='protected']", "PropertyDefinition[accessibility='private']" ], name: "noAccessModifiers", message: "Avoid access modifiers. In Javascript modules there is no need to limit developer access to properties." }, { selector: ["Identifier[name='PropTypes']", "Identifier[name='propTypes']"], name: "noPropTypes", message: "Avoid PropTypes. Use Typescript instead." }, { selector: "UnaryExpression[operator='delete']", name: "noDeleteOperator", message: 'Avoid the "delete" operator. Use omit() instead.' }, { selector: "TSEnumDeclaration", name: "noEnums", message: "Avoid enums." }, { selector: ["ClassDeclaration", "ClassExpression"], name: "noClasses", message: "Avoid classes. Use functions and objects instead." } ]; var noRestrictedSyntax = createNoRestrictedSyntax( ...baseNoRestrictedSyntaxRules ); // src/getBaseConfig.ts var baseNoRestrictedPropertiesRules = [ { name: "isFinite", message: "Please use Number.isFinite instead", property: [ { object: "global", property: "isFinite" }, { object: "self", property: "isFinite" }, { object: "window", property: "isFinite" } ] }, { name: "isNaN", message: "Please use Number.isNaN instead", property: [ { object: "global", property: "isNaN" }, { object: "self", property: "isNaN" }, { object: "window", property: "isNaN" } ] } ]; var noRestrictedProperties = createNoRestrictedProperties( ...baseNoRestrictedPropertiesRules ); var getBaseConfig = (userConfigChoices) => { const customTSConfigPath = userConfigChoices.pathsOverrides?.tsconfigLocation; return tseslint3.config( { extends: [eslintJs.configs.recommended], files: [supportedFileTypes] }, { files: [supportedFileTypes], rules: getBaseEslintHandPickedRules() }, { extends: tseslint3.configs.strictTypeChecked, files: [supportedFileTypes] }, { files: [`**/*{${allJsExtensions}}`], languageOptions: getLanguageOptionsTypescript(customTSConfigPath) }, { files: [supportedFileTypes], // @ts-expect-error rules: { ...typescriptHandPickedRules, ...getTsNamingConventionRule({ isTsx: false }) } }, { extends: [noRestrictedSyntax.configs.recommended], files: [supportedFileTypes] }, { extends: [noRestrictedProperties.configs.recommended], files: [supportedFileTypes] }, { files: [`**/*.{${tsExtensions},${tsxExtensions},astro}`], plugins: { tsdoc }, rules: { "tsdoc/syntax": 2 } }, { files: [supportedFileTypes], plugins: { "@stylistic": stylistic }, rules: stylisticHandPickedRules }, { files: [supportedFileTypes], plugins: { "@regru/prefer-early-return": preferEarlyReturn }, rules: { "@regru/prefer-early-return/prefer-early-return": [ 2, { maximumStatements: 1 } ] } }, { files: [supportedFileTypes], languageOptions: { globals: globals.builtin }, plugins: { unicorn }, rules: unicornHandPickedRules }, { files: [supportedFileTypes], plugins: { regexp: regexpPlugin }, rules: regexpPlugin.configs["flat/recommended"].rules }, { files: [supportedFileTypes], plugins: { sonarjs }, rules: { ...sonarjs.configs.recommended.rules, ...sonarjsHandPickedRules } }, { files: [supportedFileTypes], plugins: { "arrow-return-style": arrowReturnStyle }, rules: { "arrow-return-style/arrow-return-style": [ 2, { namedExportsAlwaysUseExplicitReturn: false } ], "arrow-return-style/no-export-default-arrow": 2 } }, { files: [supportedFileTypes], plugins: { "simple-import-sort": simpleImportSort }, rules: { "simple-import-sort/imports": [ 2, { groups: [ ["^\\u0000", "^node:", "^", "^@", "^@/", "^#", "^~", "^\\."] ] } ], "simple-import-sort/exports": 2 } }, { files: [supportedFileTypes], plugins: { import: pluginImport }, rules: importHandPickedRules, settings: { "import/parsers": { "@typescript-eslint/parser": [".ts", ".tsx", ".mts", "cts"], espree: [".js"] }, "import/resolver": { typescript: { alwaysTryTypes: true }, node: true } } }, { files: ["**/*.d.ts"], rules: { "import/no-default-export": 0 } }, { files: [supportedFileTypes], plugins: { jsdoc }, rules: jsdocHandPickedRules, settings: { jsdoc: { mode: "typescript" } } }, { files: [supportedFileTypes], plugins: { fsecond }, rules: { "fsecond/prefer-destructured-optionals": 2 } }, { files: [`**/*.config.{${allJsExtensions}}`], rules: { "import/no-default-export": 0, "import/no-anonymous-default-export": 0, "arrow-return-style/no-export-default-arrow": 0 } }, { files: [supportedFileTypes], linterOptions: { reportUnusedDisableDirectives: "error" } } ); }; // src/getJestConfig.ts import jest from "eslint-plugin-jest"; import tseslint4 from "typescript-eslint"; // src/handpickedRules/jestHandPickedRules.ts var jestHandPickedRules = { "jest/no-conditional-expect": 2, "jest/no-conditional-in-test": 2, "jest/no-alias-methods": 2, "jest/no-export": 2, "jest/no-duplicate-hooks": 2, "jest/no-identical-title": 2, "jest/no-focused-tests": 2, "jest/no-jasmine-globals": 2, "jest/no-standalone-expect": 2, "jest/no-test-return-statement": 2, "jest/valid-describe-callback": 2, "jest/no-test-prefixes": 2, "jest/require-top-level-describe": 2, "jest/prefer-comparison-matcher": 2, "jest/prefer-equality-matcher": 2, "jest/prefer-expect-resolves": 2, "jest/prefer-hooks-on-top": 2, "jest/prefer-hooks-in-order": 2, "jest/prefer-strict-equal": 2, "jest/valid-title": 2, "jest/valid-expect-in-promise": 2, "jest/valid-expect": 2, "jest/consistent-test-it": [2, { fn: "test", withinDescribe: "test" }], "jest/unbound-method": 2 // we need to overwrite @typescript-eslint/unbound-method. }; // src/getJestConfig.ts var getJestConfig = (pathsOverrides) => { return tseslint4.config({ files: pathsOverrides ?? [ `**/*.{test,spec}.{${allJsExtensions}}`, `**/tests/**/*.{${allJsExtensions}}`, `**/__tests__/**/*.{${allJsExtensions}}` ], extends: [jest.configs["flat/style"]], rules: { ...jestHandPickedRules, "@typescript-eslint/unbound-method": 0 // see: https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/unbound-method.md } }); }; // src/getReactConfig.ts import fsecond2 from "eslint-plugin-fsecond"; import reactAccessibility from "eslint-plugin-jsx-a11y"; import react from "eslint-plugin-react"; import reactHooks from "eslint-plugin-react-hooks"; import reactRefresh from "eslint-plugin-react-refresh"; import tseslint6 from "typescript-eslint"; import rel1cxReact from "@eslint-react/eslint-plugin"; // src/handpickedRules/reactHandPickedRules.ts var reactHandPickedRules = { "react/prop-types": 0, "react/no-unstable-nested-components": [2, { allowAsProps: false }], "react/jsx-no-useless-fragment": [2, { allowExpressions: true }], "react/function-component-definition": [ 2, { namedComponents: "arrow-function" } ], "react/jsx-boolean-value": 2, "react/jsx-fragments": 2, "react/jsx-key": [ 2, { checkFragmentShorthand: true, warnOnDuplicates: true } ], "react/hook-use-state": 2, // this rule right now isn't really inferring typescript types information about variables being boolean, see more here: https://github.com/jsx-eslint/eslint-plugin-react/issues/3292 // 'react/jsx-no-leaked-render': [2, { validStrategies: ['coerce', 'ternary'] }], "react/destructuring-assignment": 2, "react/jsx-filename-extension": [ 2, { extensions: [".jsx", ".tsx", ".mtsx", ".mjsx"] } ], "react/no-multi-comp": 2, "react/no-array-index-key": 2, "react/no-this-in-sfc": 2, "react/checked-requires-onchange-or-readonly": 2, "react/jsx-sort-props": [ 2, { callbacksLast: true, shorthandFirst: true, shorthandLast: false, ignoreCase: true, noSortAlphabetically: true, multiline: "last", reservedFirst: false } ] }; // src/utils/getLanguageOptionsTypescriptReact.ts import globals2 from "globals"; import tseslint5 from "typescript-eslint"; var getLanguageOptionsTypescriptReact = (userChosenTSConfig) => { return { parser: tseslint5.parser, parserOptions: { ecmaFeatures: { modules: true, jsx: true }, project: userChosenTSConfig || true, jsxPragma: null // useful for typescript x react@17 https://github.com/jsx-eslint/eslint-plugin-react/blob/8cf47a8ac2242ee00ea36eac4b6ae51956ba4411/index.js#L165-L179 }, globals: { ...globals2.browser } }; }; // src/getReactConfig.ts var getReactConfig = (customTSConfigPath) => { return tseslint6.config( { // we are specifically not including .astro files here, to not overwrite astro-eslint-parser. files: [`**/*{${allJsExtensions},${allJsxExtensions}}`], languageOptions: getLanguageOptionsTypescriptReact(customTSConfigPath) }, { files: [supportedFileTypes], // @ts-expect-error plugins: { react }, settings: { react: { version: "detect" } }, rules: { ...react.configs.flat?.recommended.rules, ...react.configs.flat?.["jsx-runtime"].rules, ...reactHandPickedRules } }, { files: [`**/*{${allJsxExtensions}}`], rules: getTsNamingConventionRule({ isTsx: true }) }, { files: [`**/*{${allJsxExtensions}}`], plugins: { "react-refresh": reactRefresh }, rules: { "react-refresh/only-export-components": [ 2, { allowExportNames: ["loader"] } ] } }, { extends: [reactAccessibility.flatConfigs.recommended], files: [`**/*{${allJsxExtensions}}`] }, { files: [supportedFileTypes], plugins: { "react-hooks": reactHooks }, rules: reactHooks.configs.recommended.rules }, { files: [supportedFileTypes], plugins: { "@eslint-react": rel1cxReact.configs.all.plugins["@eslint-react"], "@eslint-react/hooks-extra": rel1cxReact.configs.all.plugins["@eslint-react/hooks-extra"] }, rules: { "@eslint-react/no-leaked-conditional-rendering": 2, "@eslint-react/hooks-extra/ensure-custom-hooks-using-other-hooks": 2 } }, { files: [supportedFileTypes], plugins: { fsecond: fsecond2 }, rules: { "fsecond/valid-event-listener": 2 } }, // Specific overrides for astro { files: ["**/*.astro"], rules: { "react/no-unknown-property": 0, "react/jsx-filename-extension": [ 2, { allow: "always", extensions: [".jsx", ".tsx", ".mtsx", ".mjsx", ".astro"] } ] } } ); }; // src/getStorybookConfig.ts import storybook from "eslint-plugin-storybook"; import tseslint7 from "typescript-eslint"; var getStorybookConfig = () => { return tseslint7.config({ extends: [ storybook.configs["flat/recommended"], storybook.configs["flat/csf"] ], rules: { "import/no-default-export": 0 }, ignores: ["!.storybook"] }); }; // src/getVitestConfig.ts import vitest from "@vitest/eslint-plugin"; // src/handpickedRules/vitestHandPickedRules.ts var vitestHandPickedRules = { "vitest/consistent-test-it": [2, { fn: "test", withinDescribe: "test" }], "vitest/expect-expect": 2, "vitest/no-commented-out-tests": 2, "vitest/no-conditional-expect": 2, "vitest/no-conditional-in-test": 2, "vitest/no-disabled-tests": 2, "vitest/no-duplicate-hooks": 2, "vitest/no-identical-title": 2, "vitest/no-focused-tests": 2, "vitest/no-standalone-expect": 2, "vitest/no-test-prefixes": 2, "vitest/no-test-return-statement": 2, "vitest/prefer-comparison-matcher": 2, "vitest/prefer-each": 2, "vitest/prefer-equality-matcher": 2, "vitest/prefer-expect-resolves": 2, "vitest/prefer-hooks-in-order": 2, "vitest/prefer-hooks-on-top": 2, "vitest/prefer-lowercase-title": 2, "vitest/prefer-mock-promise-shorthand": 2, "vitest/prefer-spy-on": 2, "vitest/prefer-strict-equal": 2, "vitest/prefer-to-be": 2, "vitest/prefer-to-be-falsy": 2, "vitest/prefer-to-be-object": 2, "vitest/prefer-to-be-truthy": 2, "vitest/prefer-to-contain": 2, "vitest/prefer-to-have-length": 2, "vitest/prefer-todo": 2, "vitest/require-hook": 2, "vitest/require-to-throw-message": 2, "vitest/require-top-level-describe": 2, "vitest/valid-describe-callback": 2, "vitest/valid-expect": 2 }; // src/getVitestConfig.ts var getVitestConfig = (pathsOverrides) => { return { files: pathsOverrides ?? [ `**/*.{test,spec}.{${allJsExtensions}}`, `**/tests/**/*.{${allJsExtensions}}`, `**/__tests__/**/*.{${allJsExtensions}}` ], plugins: { vitest }, rules: vitestHandPickedRules }; }; // src/lodashConfig.ts import lodashPlugin from "eslint-plugin-lodash-f"; // src/handpickedRules/lodashHandPickedRules.ts var lodashHandPickedRules = { "lodash-f/import-scope": [2, "member"], "lodash-f/prefer-some": [2, { includeNative: false }], "lodash-f/prefer-includes": [2, { includeNative: false }], "lodash-f/prefer-lodash-method": 0, "lodash-f/prefer-noop": 0, "lodash-f/prefer-matches": 0, "lodash-f/prefer-over-quantifier": 0, "lodash-f/prefer-get": 0, "unicorn/no-instanceof-array": 0 // we are enabling "prefer-lodash-typecheck", therefore we need to disable this. }; // src/lodashConfig.ts var lodashConfig = { files: [supportedFileTypes], plugins: { "lodash-f": lodashPlugin }, rules: { ...lodashPlugin.configs.recommended.rules, ...lodashHandPickedRules } }; // src/nextjsConfig.ts import { fixupPluginRules } from "@eslint/compat"; import nextjs from "@next/eslint-plugin-next"; var nextjsConfig = [ { files: [`**/*.{${allJsExtensions},${allJsxExtensions}}`], plugins: { "@next/next": fixupPluginRules(nextjs) }, rules: { ...nextjs.configs.recommended.rules, ...nextjs.configs["core-web-vitals"].rules, "import/no-default-export": 0, "react/function-component-definition": [ 2, { namedComponents: "function-declaration", unnamedComponents: "function-expression" } ], "@next/next/no-html-link-for-pages": 0 // pages router is legacy at this point. We don't need to support this rule anymore. } }, { files: [ `**/page.{${allJsExtensions},${allJsxExtensions}}`, `**/layout.{${allJsExtensions},${allJsxExtensions}}` ], rules: { "react-refresh/only-export-components": [ 2, { allowExportNames: ["metadata", "generateMetadata"] } ] } } ]; // src/playwrightConfig.ts import playwright from "eslint-plugin-playwright"; // src/handpickedRules/playwrightHandPickedRules.ts var playwrightHandPickedRules = { "playwright/no-force-option": 0, "playwright/prefer-lowercase-title": 2, "playwright/prefer-to-have-length": 2, "playwright/require-top-level-describe": 2, "playwright/prefer-to-be": 2, "playwright/prefer-to-have-count": 2, "playwright/prefer-to-contain": 2, "playwright/prefer-strict-equal": 2 }; // src/playwrightConfig.ts var getPlaywrightConfig = (pathsOverrides) => { return [ { ...playwright.configs["flat/recommended"], files: pathsOverrides ?? [`**/*{${allJsExtensions}}`] }, { files: pathsOverrides ?? [`**/*{${allJsExtensions}}`], rules: playwrightHandPickedRules } ]; }; // src/remedaConfig.ts import remedaPlugin from "eslint-plugin-remeda"; import tseslint8 from "typescript-eslint"; var remedaConfig = tseslint8.config({ files: [supportedFileTypes], plugins: { remeda: remedaPlugin }, //@ts-expect-error rules: remedaPlugin.configs.recommended.rules }); // src/getExportableConfig.ts var getExportableConfig = (userConfigChoices = sheriffStartingOptions, areAllRulesForced) => { let exportableConfig = getBaseConfig(userConfigChoices); const hasReact = Boolean(userConfigChoices.react); if (hasReact || userConfigChoices.next) { exportableConfig.push( ...getReactConfig(userConfigChoices.pathsOverrides?.tsconfigLocation) ); } if (!areAllRulesForced && userConfigChoices.jest && userConfigChoices.vitest) { throw new Error( "Jest and Vitest support cannot be activated at once. Please choose one or the other." ); } if (userConfigChoices.jest) { exportableConfig.push( ...getJestConfig(userConfigChoices.pathsOverrides?.tests) ); } if (userConfigChoices.vitest) { exportableConfig.push( getVitestConfig(userConfigChoices.pathsOverrides?.tests) ); } if (userConfigChoices.next) { exportableConfig.push(...nextjsConfig); } if (userConfigChoices.lodash) { exportableConfig.push(lodashConfig); } if (userConfigChoices.remeda) { exportableConfig.push(...remedaConfig); } if (userConfigChoices.astro) { const customTSConfigPath = userConfigChoices.pathsOverrides?.tsconfigLocation; exportableConfig.push(...getAstroConfig(hasReact, customTSConfigPath)); } if (userConfigChoices.playwright) { exportableConfig.push( ...getPlaywrightConfig(userConfigChoices.pathsOverrides?.playwrightTests) ); } if (userConfigChoices.storybook) { exportableConfig.push(...getStorybookConfig()); } if (userConfigChoices.files) { const allowedPatterns = userConfigChoices.files.map( (globPattern) => `!${globPattern}` ); exportableConfig = exportableConfig.map((configSlice) => { if (configSlice.ignores?.length && !lodash.isEmpty(configSlice.ignores)) { return configSlice; } return { ...configSlice, ignores: ["**/*", ...allowedPatterns] }; }); } const hasIgnoresRecommended = lodash.isBoolean( userConfigChoices.ignores?.recommended ) ? userConfigChoices.ignores?.recommended : true; const hasIgnoresInheritedFromGitignore = lodash.isBoolean( userConfigChoices.ignores?.inheritedFromGitignore ) ? userConfigChoices.ignores?.inheritedFromGitignore : true; exportableConfig.push({ ignores: [ ...hasIgnoresRecommended ? ignores : [], ...hasIgnoresInheritedFromGitignore ? getGitignorePatterns({ strict: false }).ignores : [] ] }); return exportableConfig; }; // src/index.ts import {} from "@typescript-eslint/utils"; import { default as default2 } from "typescript-eslint"; var exportableAllJsExtensions = allJsExtensions; var exportableAllJsxExtensions = allJsxExtensions; var exportableIgnores = ignores; var exportableSheriffStartingOptions = sheriffStartingOptions; var exportableSupportedFileTypes = supportedFileTypes; var exportableTestsFilePatterns = testsFilePatterns; export { exportableAllJsExtensions as allJsExtensions, exportableAllJsxExtensions as allJsxExtensions, getExportableConfig as default, exportableIgnores as ignores, getExportableConfig as sheriff, exportableSheriffStartingOptions as sheriffStartingOptions, exportableSupportedFileTypes as supportedFileTypes, exportableTestsFilePatterns as testsFilePatterns, default2 as tseslint };