diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 000000000..cd1f3503b --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,444 @@ +const { FlatCompat } = require("@eslint/eslintrc"); +const pluginReact = require("eslint-plugin-react"); +const pluginImport = require("eslint-plugin-import"); +const pluginNode = require("eslint-plugin-node"); +const pluginTypeScript = require("@typescript-eslint/eslint-plugin"); + +const compat = new FlatCompat({ + baseDirectory: __dirname, + resolvePluginsRelativeTo: __dirname, +}); + +// Generally, don't change TRANSITION_* severities unless you're LordSputnik ;) +const ERROR = 2; +const TRANSITION_WARNING = 1; // warnings that should be reviewed soon +const WARNING = 1; // warnings that should stay warnings +const TRANSITION_IGNORE = 0; // ignores that should be reviewed soon +const IGNORE = 0; + +// These should not be removed at all. +const possibleErrorsRules = { + 'no-await-in-loop': ERROR, + 'no-console': ERROR, + 'no-template-curly-in-string': ERROR, + 'valid-jsdoc': [ + ERROR, + { + prefer: { + return: 'returns' + }, + requireReturn: false + } + ] +}; + +// These should probably not be removed at all. +const bestPracticesRules = { + 'accessor-pairs': ERROR, + 'array-callback-return': ERROR, + 'block-scoped-var': ERROR, + 'class-methods-use-this': TRANSITION_IGNORE, + complexity: [ERROR, {max: 50}], + 'consistent-return': ERROR, + curly: ERROR, + 'default-case': ERROR, + 'dot-location': [ + ERROR, + 'property' + ], + 'dot-notation': ERROR, + eqeqeq: [ + ERROR, + 'allow-null' + ], + 'guard-for-in': ERROR, + 'no-alert': ERROR, + 'no-caller': ERROR, + 'no-div-regex': ERROR, + 'no-else-return': ERROR, + 'no-empty-function': ERROR, + 'no-eq-null': ERROR, + 'no-eval': ERROR, + 'no-extend-native': ERROR, + 'no-extra-bind': ERROR, + 'no-extra-label': ERROR, + 'no-floating-decimal': ERROR, + 'no-implicit-coercion': ERROR, + 'no-implicit-globals': ERROR, + 'no-implied-eval': ERROR, + 'no-iterator': ERROR, + 'no-labels': ERROR, + 'no-lone-blocks': ERROR, + 'no-loop-func': ERROR, + 'no-magic-numbers': [ + TRANSITION_IGNORE, + { + detectObjects: true, + enforceConst: true, + ignore: [ + 0, + 1, + 2, + 3, + 10 + ], + ignoreArrayIndexes: true + } + ], + 'no-multi-spaces': ERROR, + 'no-multi-str': ERROR, + 'no-new': ERROR, + 'no-new-func': ERROR, + 'no-new-wrappers': ERROR, + 'no-octal-escape': ERROR, + 'no-param-reassign': ERROR, + 'no-proto': ERROR, + 'no-return-assign': ERROR, + 'no-return-await': ERROR, + 'no-script-url': ERROR, + 'no-self-compare': ERROR, + 'no-sequences': ERROR, + 'no-throw-literal': ERROR, + 'no-unmodified-loop-condition': ERROR, + 'no-unused-expressions': TRANSITION_WARNING, + 'no-useless-call': ERROR, + 'no-useless-concat': ERROR, + 'no-useless-return': ERROR, + 'no-void': ERROR, + 'no-warning-comments': WARNING, + 'prefer-promise-reject-errors': ERROR, + radix: ERROR, + 'require-await': ERROR, + 'vars-on-top': ERROR, + 'wrap-iife': [ + ERROR, + 'any' + ], + yoda: ERROR +}; + +const strictModeRules = { + strict: [ERROR, "global"], +}; + +const variablesRules = { + "init-declarations": TRANSITION_IGNORE, + "no-catch-shadow": ERROR, + "no-label-var": ERROR, + "no-undef-init": ERROR, + "no-undefined": ERROR, +}; + +const nodeAndCommonJSRules = { + "node/callback-return": [ERROR, ["callback", "cb", "next", "done"]], + "node/global-require": ERROR, + "node/handle-callback-err": ERROR, + "node/no-missing-import": [ + ERROR, + { tryExtensions: [".js", ".jsx", ".ts", ".tsx"] }, + ], + "node/no-mixed-requires": ERROR, + "node/no-new-require": ERROR, + "node/no-path-concat": ERROR, + "node/no-process-env": TRANSITION_WARNING, + "node/no-process-exit": ERROR, + "node/no-sync": ERROR, + "node/no-unpublished-import": IGNORE, + "node/no-unsupported-features/es-builtins": IGNORE, + "node/no-unsupported-features/es-syntax": IGNORE, +}; + +const stylisticIssuesRules = { + "array-bracket-newline": [ERROR, "consistent"], + "array-bracket-spacing": ERROR, + "block-spacing": ERROR, + "brace-style": [ERROR, "stroustrup", { allowSingleLine: true }], + camelcase: [ERROR, { properties: "always" }], + "comma-dangle": ERROR, + "comma-spacing": ERROR, + "comma-style": ERROR, + "computed-property-spacing": ERROR, + "consistent-this": [ERROR, "self"], + "eol-last": ERROR, + "func-call-spacing": ERROR, + "func-name-matching": ERROR, + "func-names": ERROR, + "func-style": [ERROR, "declaration"], + "function-paren-newline": [TRANSITION_WARNING, "consistent"], + "id-length": [ERROR, { exceptions: ["x", "i", "_", "$", "a", "b", "q"] }], + indent: [ERROR, "tab", { SwitchCase: 1, VariableDeclarator: 1 }], + "jsx-quotes": [ERROR, "prefer-double"], + "key-spacing": ERROR, + "keyword-spacing": ERROR, + "linebreak-style": ERROR, + "lines-around-comment": [ + ERROR, + { allowBlockStart: true, beforeBlockComment: true }, + ], + "lines-between-class-members": ERROR, + "max-depth": [ERROR, 6], + "max-len": [ + WARNING, + { code: 150, ignoreComments: true, ignoreUrls: true, tabWidth: 4 }, + ], + "max-nested-callbacks": [ERROR, 5], + "max-params": [TRANSITION_IGNORE, 4], + "max-statements": [TRANSITION_IGNORE, 15], + "new-cap": [ERROR, { capIsNew: false }], + "new-parens": ERROR, + "no-array-constructor": ERROR, + "no-bitwise": ERROR, + "no-continue": ERROR, + "no-inline-comments": WARNING, + "no-lonely-if": ERROR, + "no-mixed-spaces-and-tabs": [ERROR, "smart-tabs"], + "no-multiple-empty-lines": ERROR, + "no-nested-ternary": ERROR, + "no-new-object": TRANSITION_IGNORE, + "no-trailing-spaces": ERROR, + "no-unneeded-ternary": ERROR, + "no-whitespace-before-property": ERROR, + "object-curly-newline": ERROR, + "object-curly-spacing": ERROR, + "one-var": [ERROR, "never"], + "operator-assignment": ERROR, + "operator-linebreak": [ERROR, "after"], + "padded-blocks": [ERROR, "never"], + "quote-props": [ERROR, "as-needed"], + quotes: [ERROR, "single", "avoid-escape"], + "require-jsdoc": TRANSITION_IGNORE, + "semi-spacing": [ERROR, { after: true, before: false }], + "sort-keys": ERROR, + "sort-vars": ERROR, + "space-before-blocks": ERROR, + "space-before-function-paren": [ERROR, { named: "never" }], + "space-in-parens": ERROR, + "space-infix-ops": ERROR, + "space-unary-ops": ERROR, + "spaced-comment": ERROR, + "unicode-bom": ERROR, + "wrap-regex": ERROR, +}; + +const ecmaScript6Rules = { + "arrow-body-style": ERROR, + "arrow-spacing": ERROR, + "generator-star-spacing": [ERROR, { after: true, before: false }], + "no-confusing-arrow": ERROR, + "no-duplicate-imports": ERROR, + "no-useless-computed-key": ERROR, + "no-useless-constructor": ERROR, + "no-useless-rename": ERROR, + "no-var": ERROR, + "object-shorthand": ERROR, + "prefer-arrow-callback": ERROR, + "prefer-const": WARNING, + "prefer-destructuring": [ERROR, { array: false, object: true }], + "prefer-numeric-literals": ERROR, + "prefer-template": ERROR, + "rest-spread-spacing": ERROR, + "sort-imports": ERROR, + "template-curly-spacing": ERROR, + "yield-star-spacing": ERROR, +}; + +const typescriptRules = { + "@typescript-eslint/ban-types": TRANSITION_WARNING, + "@typescript-eslint/explicit-module-boundary-types": TRANSITION_IGNORE, + "@typescript-eslint/no-explicit-any": TRANSITION_IGNORE, + "@typescript-eslint/no-extra-parens": [ + ERROR, + "all", + { + enforceForArrowConditionals: false, + ignoreJSX: "multi-line", + nestedBinaryExpressions: false, + returnAssign: false, + }, + ], + "@typescript-eslint/no-invalid-this": ERROR, + "@typescript-eslint/no-shadow": ERROR, + "@typescript-eslint/no-unused-vars": TRANSITION_WARNING, + "@typescript-eslint/no-use-before-define": ERROR, + "@typescript-eslint/semi": ERROR, +}; + + +const reactRules = { + "react/boolean-prop-naming": ERROR, + "react/button-has-type": ERROR, + "react/default-props-match-prop-types": ERROR, + "react/forbid-component-props": TRANSITION_IGNORE, + "react/forbid-foreign-prop-types": ERROR, + "react/jsx-boolean-value": ERROR, + "react/jsx-closing-bracket-location": [ERROR, "tag-aligned"], + "react/jsx-closing-tag-location": ERROR, + "react/jsx-curly-brace-presence": ERROR, + "react/jsx-curly-spacing": [ + ERROR, + { + children: true, + }, + ], + "react/jsx-equals-spacing": ERROR, + "react/jsx-first-prop-new-line": ERROR, + "react/jsx-handler-names": ERROR, + "react/jsx-indent-props": [ERROR, "tab"], + "react/jsx-no-bind": [ + ERROR, + { + ignoreRefs: true, + }, + ], + "react/jsx-no-literals": TRANSITION_IGNORE, + "react/jsx-one-expression-per-line": TRANSITION_IGNORE, + "react/jsx-pascal-case": ERROR, + "react/jsx-sort-props": [ + ERROR, + { + callbacksLast: true, + ignoreCase: false, + shorthandFirst: true, + }, + ], + "react/jsx-tag-spacing": [ + ERROR, + { + beforeSelfClosing: "never", + }, + ], + "react/jsx-wrap-multilines": ERROR, + "react/no-access-state-in-setstate": ERROR, + "react/no-array-index-key": TRANSITION_WARNING, + "react/no-danger": TRANSITION_WARNING, + "react/no-did-mount-set-state": ERROR, + "react/no-did-update-set-state": ERROR, + "react/no-direct-mutation-state": ERROR, + "react/no-multi-comp": [ + ERROR, + { + ignoreStateless: true, + }, + ], + "react/no-redundant-should-component-update": ERROR, + "react/no-set-state": TRANSITION_IGNORE, + "react/no-typos": ERROR, + "react/no-unused-prop-types": ERROR, + "react/no-unused-state": TRANSITION_WARNING, + "react/no-will-update-set-state": ERROR, + "react/prefer-es6-class": [ERROR, "always"], + "react/prefer-stateless-function": ERROR, + "react/require-default-props": [ + ERROR, + { + forbidDefaultForRequired: true, + }, + ], + "react/self-closing-comp": ERROR, + "react/sort-comp": ERROR, + "react/sort-prop-types": [ + ERROR, + { + callbacksLast: false, + ignoreCase: false, + requiredFirst: false, + sortShapeProp: true, + }, + ], + "react/style-prop-object": ERROR, + "react/void-dom-elements-no-children": ERROR, +}; + +const es6ImportRules = { + "import/first": ERROR, + "import/newline-after-import": [ + WARNING, + { + count: 2, + }, + ], + "import/no-absolute-path": ERROR, + "import/no-amd": ERROR, + "import/no-commonjs": ERROR, + "import/no-duplicates": ERROR, + "import/no-dynamic-require": TRANSITION_WARNING, + "import/no-extraneous-dependencies": ERROR, + "import/no-internal-modules": [ + ERROR, + { + allow: [ + "**/src/**", + "**/lib/**", + "**/data/**", + "**/config/**", + "**/test/**", + "react-dom/server", + "**/react-select/*", + ], + }, + ], + "import/no-mutable-exports": ERROR, + "import/no-named-as-default": ERROR, + "import/no-named-as-default-member": ERROR, + "import/no-named-default": ERROR, + "import/no-unassigned-import": ERROR, +}; + +module.exports = [ + { + ignores: [ + "lib/**", + "static/**", + "flow-typed/**", + "nyc/**", + "coverage/**", + "out/**", + "!.eslintrc.js", + "webpack.client.js", + ], + }, + { + files: ["**/*.ts", "**/*.tsx"], + languageOptions: { + parser: "@typescript-eslint/parser", + parserOptions: { + sourceType: "module", + ecmaVersion: "latest", + }, + }, + plugins: { + "@typescript-eslint": pluginTypeScript, + }, + rules: typescriptRules, + }, + { + files: ["**/*.js", "**/*.jsx"], + languageOptions: { + ecmaVersion: "latest", + sourceType: "module", + }, + plugins: { + react: pluginReact, + import: pluginImport, + node: pluginNode, + }, + settings: { + "import/resolver": { + node: { + extensions: [".js", ".jsx", ".ts", ".tsx"], + }, + }, + }, + rules: { + ...possibleErrorsRules, + ...bestPracticesRules, + ...strictModeRules, + ...variablesRules, + ...nodeAndCommonJSRules, + ...stylisticIssuesRules, + ...ecmaScript6Rules, + ...reactRules, + ...es6ImportRules, + }, + }, +]; diff --git a/package.json b/package.json index b11c0f2fb..b63f1ccb3 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ }, "browserslist": "> 0.25%, not dead", "overrides": { - "react-select-fast-filter-options":{ + "react-select-fast-filter-options": { "react-select": "$react-select" } }, @@ -106,6 +106,7 @@ "@babel/preset-react": "^7.22.15", "@babel/preset-typescript": "^7.12.1", "@babel/register": "^7.23.7", + "@eslint/eslintrc": "^3.1.0", "@faker-js/faker": "^8.1.0", "@types/express": "^4.17.15", "@types/lodash": "^4.14.202", diff --git a/yarn.lock b/yarn.lock index 5f4ef0ac5..4329159d2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1424,6 +1424,21 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@eslint/eslintrc@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.1.0.tgz#dbd3482bfd91efa663cbe7aa1f506839868207b6" + integrity sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^10.0.1" + globals "^14.0.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + "@eslint/js@^8.47.0": version "8.47.0" resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.47.0.tgz#5478fdf443ff8158f9de171c704ae45308696c7d" @@ -2374,6 +2389,11 @@ acorn@^8.0.4, acorn@^8.2.4, acorn@^8.5.0, acorn@^8.7.1, acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== +acorn@^8.11.3: + version "8.11.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== + adjust-sourcemap-loader@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz#fc4a0fd080f7d10471f30a7320f25560ade28c99" @@ -4171,6 +4191,11 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== +eslint-visitor-keys@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz#e3adc021aa038a2a8e0b2f8b0ce8f66b9483b1fb" + integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw== + eslint-webpack-plugin@^2.4.1: version "2.6.0" resolved "https://registry.yarnpkg.com/eslint-webpack-plugin/-/eslint-webpack-plugin-2.6.0.tgz#3bd4ada4e539cb1f6687d2f619073dbb509361cd" @@ -4287,6 +4312,15 @@ esniff@^2.0.1: event-emitter "^0.3.5" type "^2.7.2" +espree@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.0.1.tgz#600e60404157412751ba4a6f3a2ee1a42433139f" + integrity sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww== + dependencies: + acorn "^8.11.3" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.0.0" + espree@^7.3.0, espree@^7.3.1: version "7.3.1" resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" @@ -4861,6 +4895,11 @@ globals@^13.6.0, globals@^13.9.0: dependencies: type-fest "^0.20.2" +globals@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== + globby@^11.0.3: version "11.0.4" resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5"