UNPKG

eslint-config-sheriff

Version:

A comprehensive and opinionated TypeScript-first ESLint configuration.

1,040 lines (1,010 loc) 31.4 kB
import { globalIgnores } from "eslint/config"; import getGitignorePatterns from "eslint-config-flat-gitignore"; import lodash from "lodash"; import astro from "eslint-plugin-astro"; import tseslint, { default as tseslint$1 } from "typescript-eslint"; 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 eslintJs from "@eslint/js"; import preferEarlyReturn from "@regru/eslint-plugin-prefer-early-return"; import stylistic from "@stylistic/eslint-plugin"; import confusingBrowserGlobals from "confusing-browser-globals"; import createNoRestrictedSyntax from "eslint-no-restricted/syntax"; import jest from "eslint-plugin-jest"; 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 reactYouMightNotNeedAnEffect from "eslint-plugin-react-you-might-not-need-an-effect"; import rel1cxReact from "@eslint-react/eslint-plugin"; import storybook from "eslint-plugin-storybook"; import vitest from "@vitest/eslint-plugin"; import lodashPlugin from "eslint-plugin-lodash-f"; import { fixupPluginRules } from "@eslint/compat"; import nextjs from "@next/eslint-plugin-next"; import playwright from "eslint-plugin-playwright"; import remedaPlugin from "eslint-plugin-remeda"; //#region ../sheriff-constants/src/index.ts const sheriffStartingOptions = { react: false, lodash: false, remeda: false, next: false, astro: false, playwright: false, storybook: true, jest: false, vitest: false }; const jsExtensions = "js,mjs,cjs"; const jsxExtensions = "jsx,mjsx"; const tsExtensions = "ts,mts,cts"; const tsxExtensions = "tsx,mtsx"; const allJsExtensions = `${jsExtensions},${tsExtensions}`; const allJsxExtensions = `${jsxExtensions},${tsxExtensions}`; const supportedFileTypes = `**/*{${allJsExtensions},${allJsxExtensions},astro}`; const testsFilePatterns = [ `**/*.{test,spec}.{${allJsExtensions}}`, `**/tests/**/*.{${allJsExtensions}}`, `**/__tests__/**/*.{${allJsExtensions}}` ]; const ignores = [ "**/node_modules/**", "**/dist/**", "**/build/**", "**/artifacts/**", "**/coverage/**", "eslint.config.{js,mjs,cjs}" ]; //#endregion //#region src/utils/getTsNamingConventionRule.ts const getTsNamingConventionRule = ({ isTsx, isAstroEndpoint }) => { const options = [ { 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" }, { 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 } ]; if (isAstroEndpoint) options.push({ selector: ["variable", "function"], types: ["function"], modifiers: ["exported"], format: null, filter: "^(GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS|CONNECT|TRACE|ALL)$" }); return { "@typescript-eslint/naming-convention": [2, ...options] }; }; //#endregion //#region src/getAstroConfig.ts const baseAstroJsxA11yConfig = astro.configs["flat/jsx-a11y-strict"].slice(0, -1); const astroJsxA11yConfig = astro.configs["flat/jsx-a11y-strict"].pop(); const astroJsxA11yPlugin = astroJsxA11yConfig?.plugins?.["jsx-a11y"] ?? {}; const astroJsxA11yRules = astroJsxA11yConfig?.rules ?? {}; const getAstroConfig = (hasReact, customTSConfigPath) => { return tseslint$1.config(astro.configs["flat/recommended"], hasReact ? { extends: [baseAstroJsxA11yConfig], plugins: { "astro/jsx-a11y": astroJsxA11yPlugin }, rules: astroJsxA11yRules } : {}, { files: ["**/*.astro"], languageOptions: { parserOptions: { parser: tseslint$1.parser, project: customTSConfigPath || true, extraFileExtensions: [".astro"] } }, settings: { "import/parsers": { "astro-eslint-parser": [".astro"], "@typescript-eslint/parser": [ ".ts", ".tsx", "mts", "cts" ], espree: [".js"] } }, rules: getTsNamingConventionRule({ isTsx: true }) }, { files: ["**/*.astro/*.ts"], languageOptions: { parserOptions: { parser: tseslint$1.parser, project: customTSConfigPath || true } } }, { files: [`**/src/pages/**/*.{${allJsExtensions}}`], rules: getTsNamingConventionRule({ isTsx: false, isAstroEndpoint: true }) }); }; //#endregion //#region src/handpickedRules/getBaseEslintHandPickedRules.ts const 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-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: "^" }, { group: ["dist"], message: "Imports from dist are likely a user mistake.", importNamePattern: "^" }] }], "no-restricted-globals": [2, ...confusingBrowserGlobals], "no-return-await": 0, "no-use-before-define": 0, "no-unused-expressions": 0, "no-empty-function": 0, "dot-notation": 0, "no-shadow": 0, "default-param-last": 0, "arrow-body-style": 0 }; }; //#endregion //#region src/handpickedRules/importHandPickedRules.ts const importHandPickedRules = { "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 }] }; //#endregion //#region src/handpickedRules/jsdocHandPickedRules.ts const 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 }; //#endregion //#region src/handpickedRules/sonarjsHandPickedRules.ts const sonarjsHandPickedRules = { "sonarjs/cognitive-complexity": 0, "sonarjs/prefer-immediate-return": 0, "sonarjs/no-duplicate-string": 0 }; //#endregion //#region src/handpickedRules/stylisticHandPickedRules.ts const stylisticHandPickedRules = { "@stylistic/padding-line-between-statements": [ 2, { blankLine: "always", prev: ["const", "let"], next: "*" }, { blankLine: "any", prev: ["const", "let"], next: ["const", "let"] }, { blankLine: "always", prev: "*", next: "return" } ] }; //#endregion //#region src/handpickedRules/typescriptHandPickedRules.ts const 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, "@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 }] }; //#endregion //#region src/handpickedRules/unicornHandPickedRules.ts const 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 }; //#endregion //#region src/utils/getLanguageOptionsTypescript.ts const getLanguageOptionsTypescript = (userChosenTSConfig) => { return { parser: tseslint$1.parser, parserOptions: { ecmaFeatures: { modules: true }, project: userChosenTSConfig || true } }; }; //#endregion //#region src/utils/noRestrictedSyntax.ts const 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." }, { 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." } ]; const noRestrictedSyntax = createNoRestrictedSyntax(...baseNoRestrictedSyntaxRules); //#endregion //#region src/getBaseConfig.ts const noRestrictedProperties = createNoRestrictedProperties(...[{ 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" } ] }]); const getBaseConfig = (userConfigChoices) => { const customTSConfigPath = userConfigChoices.pathsOverrides?.tsconfigLocation; return tseslint$1.config({ extends: [eslintJs.configs.recommended], files: [supportedFileTypes] }, { files: [supportedFileTypes], rules: getBaseEslintHandPickedRules() }, { extends: tseslint$1.configs.strictTypeChecked, files: [supportedFileTypes] }, { files: [`**/*{${allJsExtensions}}`], languageOptions: getLanguageOptionsTypescript(customTSConfigPath) }, { files: [supportedFileTypes], 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" } } }, { extends: [fsecond.configs.recommended], files: [supportedFileTypes], rules: { "fsecond/valid-event-listener": 0 } }, { files: [allJsxExtensions], plugins: { fsecond }, rules: { "fsecond/valid-event-listener": [2, { requireUseEventListenerHook: true }] } }, { 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" } }); }; //#endregion //#region src/handpickedRules/jestHandPickedRules.ts const 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 }; //#endregion //#region src/getJestConfig.ts const getJestConfig = (pathsOverrides) => { return tseslint$1.config({ files: pathsOverrides ?? [ `**/*.{test,spec}.{${allJsExtensions}}`, `**/tests/**/*.{${allJsExtensions}}`, `**/__tests__/**/*.{${allJsExtensions}}` ], extends: [jest.configs["flat/style"]], rules: { ...jestHandPickedRules, "@typescript-eslint/unbound-method": 0 } }); }; //#endregion //#region src/handpickedRules/reactHandPickedRules.ts const 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, "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 }] }; //#endregion //#region src/utils/getLanguageOptionsTypescriptReact.ts const getLanguageOptionsTypescriptReact = (userChosenTSConfig) => { return { parser: tseslint$1.parser, parserOptions: { ecmaFeatures: { modules: true, jsx: true }, project: userChosenTSConfig || true, jsxPragma: null }, globals: { ...globals.browser } }; }; //#endregion //#region src/getReactConfig.ts const getReactConfig = (customTSConfigPath) => { return tseslint$1.config({ files: [`**/*{${allJsExtensions},${allJsxExtensions}}`], languageOptions: getLanguageOptionsTypescriptReact(customTSConfigPath) }, { files: [supportedFileTypes], 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-latest"].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: { "react-you-might-not-need-an-effect": reactYouMightNotNeedAnEffect }, rules: reactYouMightNotNeedAnEffect.configs.recommended.rules }, { files: [supportedFileTypes], plugins: { fsecond }, rules: { "fsecond/valid-event-listener": 2 } }, { files: ["**/*.astro"], rules: { "react/no-unknown-property": 0, "react/jsx-filename-extension": [2, { allow: "always", extensions: [ ".jsx", ".tsx", ".mtsx", ".mjsx", ".astro" ] }] } }); }; //#endregion //#region src/getStorybookConfig.ts const getStorybookConfig = () => { return tseslint$1.config({ extends: [storybook.configs["flat/recommended"], storybook.configs["flat/csf"]], rules: { "import/no-default-export": 0 }, ignores: ["!.storybook"] }); }; //#endregion //#region src/handpickedRules/vitestHandPickedRules.ts const 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 }; //#endregion //#region src/getVitestConfig.ts const getVitestConfig = (pathsOverrides) => { return { files: pathsOverrides ?? [ `**/*.{test,spec}.{${allJsExtensions}}`, `**/tests/**/*.{${allJsExtensions}}`, `**/__tests__/**/*.{${allJsExtensions}}` ], plugins: { vitest }, rules: vitestHandPickedRules }; }; //#endregion //#region src/handpickedRules/lodashHandPickedRules.ts const 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 }; //#endregion //#region src/lodashConfig.ts const lodashConfig = { files: [supportedFileTypes], plugins: { "lodash-f": lodashPlugin }, rules: { ...lodashPlugin.configs.recommended.rules, ...lodashHandPickedRules } }; //#endregion //#region src/nextjsConfig.ts const 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 } }, { files: [`**/page.{${allJsExtensions},${allJsxExtensions}}`, `**/layout.{${allJsExtensions},${allJsxExtensions}}`], rules: { "react-refresh/only-export-components": [2, { allowExportNames: [ "metadata", "generateMetadata", "generateStaticParams" ] }] } }]; //#endregion //#region src/handpickedRules/playwrightHandPickedRules.ts const 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, "playwright/prefer-native-locators": 2 }; //#endregion //#region src/playwrightConfig.ts const getPlaywrightConfig = (pathsOverrides) => { return [{ ...playwright.configs["flat/recommended"], files: pathsOverrides ?? [`**/*{${allJsExtensions}}`], rules: { ...playwright.configs["flat/recommended"].rules, ...playwrightHandPickedRules } }]; }; //#endregion //#region src/remedaConfig.ts const remedaConfig = tseslint$1.config({ files: [supportedFileTypes], plugins: { remeda: remedaPlugin }, rules: remedaPlugin.configs.recommended.rules }); //#endregion //#region src/getExportableConfig.ts const 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 !== false) 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(globalIgnores([...hasIgnoresRecommended ? ignores : [], ...hasIgnoresInheritedFromGitignore ? getGitignorePatterns({ strict: false }).ignores : []])); return exportableConfig; }; //#endregion //#region src/index.ts const exportableAllJsExtensions = allJsExtensions; const exportableAllJsxExtensions = allJsxExtensions; const exportableIgnores = ignores; const exportableSheriffStartingOptions = sheriffStartingOptions; const exportableSupportedFileTypes = supportedFileTypes; const exportableTestsFilePatterns = testsFilePatterns; //#endregion export { exportableAllJsExtensions as allJsExtensions, exportableAllJsxExtensions as allJsxExtensions, getExportableConfig as default, getExportableConfig as sheriff, exportableIgnores as ignores, exportableSheriffStartingOptions as sheriffStartingOptions, exportableSupportedFileTypes as supportedFileTypes, exportableTestsFilePatterns as testsFilePatterns, tseslint }; //# sourceMappingURL=index.js.map