diff --git a/change/@rightcapital-eslint-config-base-f6574db9-989f-40c2-b82d-3cc997bcdc65.json b/change/@rightcapital-eslint-config-base-f6574db9-989f-40c2-b82d-3cc997bcdc65.json new file mode 100644 index 00000000..a0b71e60 --- /dev/null +++ b/change/@rightcapital-eslint-config-base-f6574db9-989f-40c2-b82d-3cc997bcdc65.json @@ -0,0 +1,7 @@ +{ + "comment": "refactor: extract eslint-config-airbnb rules", + "type": "patch", + "packageName": "@rightcapital/eslint-config-base", + "email": "im@pyonpyon.today", + "dependentChangeType": "patch" +} diff --git a/change/@rightcapital-eslint-config-typescript-react-60f30222-672b-4e3a-af00-1b6bc10bdae6.json b/change/@rightcapital-eslint-config-typescript-react-60f30222-672b-4e3a-af00-1b6bc10bdae6.json new file mode 100644 index 00000000..e3e47cbf --- /dev/null +++ b/change/@rightcapital-eslint-config-typescript-react-60f30222-672b-4e3a-af00-1b6bc10bdae6.json @@ -0,0 +1,7 @@ +{ + "comment": "refactor: extract eslint-config-airbnb rules", + "type": "patch", + "packageName": "@rightcapital/eslint-config-typescript-react", + "email": "im@pyonpyon.today", + "dependentChangeType": "patch" +} diff --git a/change/@rightcapital-eslint-plugin-df99b850-51f8-451f-94f0-aa59c9db13ad.json b/change/@rightcapital-eslint-plugin-df99b850-51f8-451f-94f0-aa59c9db13ad.json new file mode 100644 index 00000000..b78b38f8 --- /dev/null +++ b/change/@rightcapital-eslint-plugin-df99b850-51f8-451f-94f0-aa59c9db13ad.json @@ -0,0 +1,7 @@ +{ + "comment": "refactor: extract eslint-config-airbnb rules", + "type": "patch", + "packageName": "@rightcapital/eslint-plugin", + "email": "im@pyonpyon.today", + "dependentChangeType": "patch" +} diff --git a/package.json b/package.json index 972c28ba..021059a9 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "change": "beachball change --no-commit", "check": "beachball check", "commit": "cz", + "dev": "tsc --build --watch", "fix": "concurrently 'pnpm:fix:*' \"pnpm --filter './specs/*' fix\"", "fix:eslint": "eslint --fix .", "fix:prettier": "prettier --write --list-different .", diff --git a/packages/eslint-config-base/package.json b/packages/eslint-config-base/package.json index 96948c84..29023145 100644 --- a/packages/eslint-config-base/package.json +++ b/packages/eslint-config-base/package.json @@ -24,14 +24,17 @@ }, "dependencies": { "@rushstack/eslint-patch": "1.10.3", - "eslint-config-airbnb-base": "15.0.0", + "confusing-browser-globals": "1.0.11", "eslint-import-resolver-typescript": "3.6.1", "eslint-plugin-import": "npm:eslint-plugin-i@2.29.1", "eslint-plugin-simple-import-sort": "12.1.0", - "eslint-plugin-unicorn": "54.0.0" + "eslint-plugin-unicorn": "54.0.0", + "semver": "7.6.2" }, "devDependencies": { - "@rightcapital/tsconfig": "workspace:*" + "@rightcapital/tsconfig": "workspace:*", + "@types/confusing-browser-globals": "1.0.3", + "@types/semver": "7.5.8" }, "peerDependencies": { "eslint": "^8.23.1" diff --git a/packages/eslint-config-base/src/index.ts b/packages/eslint-config-base/src/index.ts index 73937f28..7243ff09 100644 --- a/packages/eslint-config-base/src/index.ts +++ b/packages/eslint-config-base/src/index.ts @@ -5,10 +5,18 @@ require('@rushstack/eslint-patch/modern-module-resolution'); const config: Linter.Config = { extends: [ - require.resolve('eslint-config-airbnb-base'), + require.resolve('./rules/best-practices'), + require.resolve('./rules/errors'), + require.resolve('./rules/node'), + require.resolve('./rules/style'), + require.resolve('./rules/variables'), + require.resolve('./rules/es6'), + require.resolve('./rules/imports'), + require.resolve('./rules/strict'), require.resolve('eslint-config-prettier'), ], plugins: ['simple-import-sort', 'unicorn'], + env: { es6: true }, parserOptions: { ecmaVersion: 'latest', ecmaFeatures: { jsx: true }, diff --git a/packages/eslint-config-base/src/rules/best-practices.ts b/packages/eslint-config-base/src/rules/best-practices.ts new file mode 100644 index 00000000..bdffa013 --- /dev/null +++ b/packages/eslint-config-base/src/rules/best-practices.ts @@ -0,0 +1,452 @@ +import type { Linter } from 'eslint'; + +// extracted from eslint-config-airbnb-base@15.0.0 +// https://github.com/airbnb/javascript/blob/eslint-config-airbnb-base-v15.0.0/packages/eslint-config-airbnb-base/rules/best-practices.js +const config: Linter.Config = { + rules: { + // enforces getter/setter pairs in objects + // https://eslint.org/docs/rules/accessor-pairs + 'accessor-pairs': 'off', + + // enforces return statements in callbacks of array's methods + // https://eslint.org/docs/rules/array-callback-return + 'array-callback-return': ['error', { allowImplicit: true }], + + // treat var statements as if they were block scoped + // https://eslint.org/docs/rules/block-scoped-var + 'block-scoped-var': 'error', + + // specify the maximum cyclomatic complexity allowed in a program + // https://eslint.org/docs/rules/complexity + complexity: ['off', 20], + + // enforce that class methods use "this" + // https://eslint.org/docs/rules/class-methods-use-this + 'class-methods-use-this': [ + 'error', + { + exceptMethods: [], + }, + ], + + // require return statements to either always or never specify values + // https://eslint.org/docs/rules/consistent-return + 'consistent-return': 'error', + + // specify curly brace conventions for all control statements + // https://eslint.org/docs/rules/curly + curly: ['error', 'multi-line'], // multiline + + // require default case in switch statements + // https://eslint.org/docs/rules/default-case + 'default-case': ['error', { commentPattern: '^no default$' }], + + // Enforce default clauses in switch statements to be last + // https://eslint.org/docs/rules/default-case-last + 'default-case-last': 'error', + + // https://eslint.org/docs/rules/default-param-last + 'default-param-last': 'error', + + // encourages use of dot notation whenever possible + // https://eslint.org/docs/rules/dot-notation + 'dot-notation': ['error', { allowKeywords: true }], + + // enforces consistent newlines before or after dots + // https://eslint.org/docs/rules/dot-location + 'dot-location': ['error', 'property'], + + // require the use of === and !== + // https://eslint.org/docs/rules/eqeqeq + eqeqeq: ['error', 'always', { null: 'ignore' }], + + // Require grouped accessor pairs in object literals and classes + // https://eslint.org/docs/rules/grouped-accessor-pairs + 'grouped-accessor-pairs': 'error', + + // make sure for-in loops have an if statement + // https://eslint.org/docs/rules/guard-for-in + 'guard-for-in': 'error', + + // enforce a maximum number of classes per file + // https://eslint.org/docs/rules/max-classes-per-file + 'max-classes-per-file': ['error', 1], + + // disallow the use of alert, confirm, and prompt + // https://eslint.org/docs/rules/no-alert + 'no-alert': 'warn', + + // disallow use of arguments.caller or arguments.callee + // https://eslint.org/docs/rules/no-caller + 'no-caller': 'error', + + // disallow lexical declarations in case/default clauses + // https://eslint.org/docs/rules/no-case-declarations + 'no-case-declarations': 'error', + + // Disallow returning value in constructor + // https://eslint.org/docs/rules/no-constructor-return + 'no-constructor-return': 'error', + + // disallow division operators explicitly at beginning of regular expression + // https://eslint.org/docs/rules/no-div-regex + 'no-div-regex': 'off', + + // disallow else after a return in an if + // https://eslint.org/docs/rules/no-else-return + 'no-else-return': ['error', { allowElseIf: false }], + + // disallow empty functions, except for standalone funcs/arrows + // https://eslint.org/docs/rules/no-empty-function + 'no-empty-function': [ + 'error', + { + allow: ['arrowFunctions', 'functions', 'methods'], + }, + ], + + // disallow empty destructuring patterns + // https://eslint.org/docs/rules/no-empty-pattern + 'no-empty-pattern': 'error', + + // disallow comparisons to null without a type-checking operator + // https://eslint.org/docs/rules/no-eq-null + 'no-eq-null': 'off', + + // disallow use of eval() + // https://eslint.org/docs/rules/no-eval + 'no-eval': 'error', + + // disallow adding to native types + // https://eslint.org/docs/rules/no-extend-native + 'no-extend-native': 'error', + + // disallow unnecessary function binding + // https://eslint.org/docs/rules/no-extra-bind + 'no-extra-bind': 'error', + + // disallow Unnecessary Labels + // https://eslint.org/docs/rules/no-extra-label + 'no-extra-label': 'error', + + // disallow fallthrough of case statements + // https://eslint.org/docs/rules/no-fallthrough + 'no-fallthrough': 'error', + + // disallow the use of leading or trailing decimal points in numeric literals + // https://eslint.org/docs/rules/no-floating-decimal + 'no-floating-decimal': 'error', + + // disallow reassignments of native objects or read-only globals + // https://eslint.org/docs/rules/no-global-assign + 'no-global-assign': ['error', { exceptions: [] }], + + // deprecated in favor of no-global-assign + // https://eslint.org/docs/rules/no-native-reassign + 'no-native-reassign': 'off', + + // disallow implicit type conversions + // https://eslint.org/docs/rules/no-implicit-coercion + 'no-implicit-coercion': [ + 'off', + { + boolean: false, + number: true, + string: true, + allow: [], + }, + ], + + // disallow var and named functions in global scope + // https://eslint.org/docs/rules/no-implicit-globals + 'no-implicit-globals': 'off', + + // disallow use of eval()-like methods + // https://eslint.org/docs/rules/no-implied-eval + 'no-implied-eval': 'error', + + // disallow this keywords outside of classes or class-like objects + // https://eslint.org/docs/rules/no-invalid-this + 'no-invalid-this': 'off', + + // disallow usage of __iterator__ property + // https://eslint.org/docs/rules/no-iterator + 'no-iterator': 'error', + + // disallow use of labels for anything other than loops and switches + // https://eslint.org/docs/rules/no-labels + 'no-labels': ['error', { allowLoop: false, allowSwitch: false }], + + // disallow unnecessary nested blocks + // https://eslint.org/docs/rules/no-lone-blocks + 'no-lone-blocks': 'error', + + // disallow creation of functions within loops + // https://eslint.org/docs/rules/no-loop-func + 'no-loop-func': 'error', + + // disallow magic numbers + // https://eslint.org/docs/rules/no-magic-numbers + 'no-magic-numbers': [ + 'off', + { + ignore: [], + ignoreArrayIndexes: true, + enforceConst: true, + detectObjects: false, + }, + ], + + // disallow use of multiple spaces + // https://eslint.org/docs/rules/no-multi-spaces + 'no-multi-spaces': [ + 'error', + { + ignoreEOLComments: false, + }, + ], + + // disallow use of multiline strings + // https://eslint.org/docs/rules/no-multi-str + 'no-multi-str': 'error', + + // disallow use of new operator when not part of the assignment or comparison + // https://eslint.org/docs/rules/no-new + 'no-new': 'error', + + // disallow use of new operator for Function object + // https://eslint.org/docs/rules/no-new-func + 'no-new-func': 'error', + + // disallows creating new instances of String, Number, and Boolean + // https://eslint.org/docs/rules/no-new-wrappers + 'no-new-wrappers': 'error', + + // Disallow \8 and \9 escape sequences in string literals + // https://eslint.org/docs/rules/no-nonoctal-decimal-escape + 'no-nonoctal-decimal-escape': 'error', + + // disallow use of (old style) octal literals + // https://eslint.org/docs/rules/no-octal + 'no-octal': 'error', + + // disallow use of octal escape sequences in string literals, such as + // var foo = 'Copyright \251'; + // https://eslint.org/docs/rules/no-octal-escape + 'no-octal-escape': 'error', + + // disallow reassignment of function parameters + // disallow parameter object manipulation except for specific exclusions + // rule: https://eslint.org/docs/rules/no-param-reassign.html + 'no-param-reassign': [ + 'error', + { + props: true, + ignorePropertyModificationsFor: [ + 'acc', // for reduce accumulators + 'accumulator', // for reduce accumulators + 'e', // for e.returnvalue + 'ctx', // for Koa routing + 'context', // for Koa routing + 'req', // for Express requests + 'request', // for Express requests + 'res', // for Express responses + 'response', // for Express responses + '$scope', // for Angular 1 scopes + 'staticContext', // for ReactRouter context + ], + }, + ], + + // disallow usage of __proto__ property + // https://eslint.org/docs/rules/no-proto + 'no-proto': 'error', + + // disallow declaring the same variable more than once + // https://eslint.org/docs/rules/no-redeclare + 'no-redeclare': 'error', + + // disallow certain object properties + // https://eslint.org/docs/rules/no-restricted-properties + 'no-restricted-properties': [ + 'error', + { + object: 'arguments', + property: 'callee', + message: 'arguments.callee is deprecated', + }, + { + object: 'global', + property: 'isFinite', + message: 'Please use Number.isFinite instead', + }, + { + object: 'self', + property: 'isFinite', + message: 'Please use Number.isFinite instead', + }, + { + object: 'window', + property: 'isFinite', + message: 'Please use Number.isFinite instead', + }, + { + object: 'global', + property: 'isNaN', + message: 'Please use Number.isNaN instead', + }, + { + object: 'self', + property: 'isNaN', + message: 'Please use Number.isNaN instead', + }, + { + object: 'window', + property: 'isNaN', + message: 'Please use Number.isNaN instead', + }, + { + property: '__defineGetter__', + message: 'Please use Object.defineProperty instead.', + }, + { + property: '__defineSetter__', + message: 'Please use Object.defineProperty instead.', + }, + { + object: 'Math', + property: 'pow', + message: 'Use the exponentiation operator (**) instead.', + }, + ], + + // disallow use of assignment in return statement + // https://eslint.org/docs/rules/no-return-assign + 'no-return-assign': ['error', 'always'], + + // disallow redundant `return await` + // https://eslint.org/docs/rules/no-return-await + 'no-return-await': 'error', + + // disallow use of `javascript:` urls. + // https://eslint.org/docs/rules/no-script-url + 'no-script-url': 'error', + + // disallow self assignment + // https://eslint.org/docs/rules/no-self-assign + 'no-self-assign': [ + 'error', + { + props: true, + }, + ], + + // disallow comparisons where both sides are exactly the same + // https://eslint.org/docs/rules/no-self-compare + 'no-self-compare': 'error', + + // disallow use of comma operator + // https://eslint.org/docs/rules/no-sequences + 'no-sequences': 'error', + + // restrict what can be thrown as an exception + // https://eslint.org/docs/rules/no-throw-literal + 'no-throw-literal': 'error', + + // disallow unmodified conditions of loops + // https://eslint.org/docs/rules/no-unmodified-loop-condition + 'no-unmodified-loop-condition': 'off', + + // disallow usage of expressions in statement position + // https://eslint.org/docs/rules/no-unused-expressions + 'no-unused-expressions': [ + 'error', + { + allowShortCircuit: false, + allowTernary: false, + allowTaggedTemplates: false, + }, + ], + + // disallow unused labels + // https://eslint.org/docs/rules/no-unused-labels + 'no-unused-labels': 'error', + + // disallow unnecessary .call() and .apply() + // https://eslint.org/docs/rules/no-useless-call + 'no-useless-call': 'off', + + // Disallow unnecessary catch clauses + // https://eslint.org/docs/rules/no-useless-catch + 'no-useless-catch': 'error', + + // disallow useless string concatenation + // https://eslint.org/docs/rules/no-useless-concat + 'no-useless-concat': 'error', + + // disallow unnecessary string escaping + // https://eslint.org/docs/rules/no-useless-escape + 'no-useless-escape': 'error', + + // disallow redundant return; keywords + // https://eslint.org/docs/rules/no-useless-return + 'no-useless-return': 'error', + + // disallow use of void operator + // https://eslint.org/docs/rules/no-void + 'no-void': 'error', + + // disallow usage of configurable warning terms in comments: e.g. todo + // https://eslint.org/docs/rules/no-warning-comments + 'no-warning-comments': [ + 'off', + { terms: ['todo', 'fixme', 'xxx'], location: 'start' }, + ], + + // disallow use of the with statement + // https://eslint.org/docs/rules/no-with + 'no-with': 'error', + + // require using Error objects as Promise rejection reasons + // https://eslint.org/docs/rules/prefer-promise-reject-errors + 'prefer-promise-reject-errors': ['error', { allowEmptyReject: true }], + + // Suggest using named capture group in regular expression + // https://eslint.org/docs/rules/prefer-named-capture-group + 'prefer-named-capture-group': 'off', + + // https://eslint.org/docs/rules/prefer-regex-literals + 'prefer-regex-literals': [ + 'error', + { + disallowRedundantWrapping: true, + }, + ], + + // require use of the second argument for parseInt() + // https://eslint.org/docs/rules/radix + radix: 'error', + + // require `await` in `async function` (note: this is a horrible rule that should never be used) + // https://eslint.org/docs/rules/require-await + 'require-await': 'off', + + // Enforce the use of u flag on RegExp + // https://eslint.org/docs/rules/require-unicode-regexp + 'require-unicode-regexp': 'off', + + // requires to declare all vars on top of their containing scope + // https://eslint.org/docs/rules/vars-on-top + 'vars-on-top': 'error', + + // require immediate function invocation to be wrapped in parentheses + // https://eslint.org/docs/rules/wrap-iife.html + 'wrap-iife': ['error', 'outside', { functionPrototypeMethods: false }], + + // require or disallow Yoda conditions + // https://eslint.org/docs/rules/yoda + yoda: 'error', + }, +}; + +export = config; diff --git a/packages/eslint-config-base/src/rules/errors.ts b/packages/eslint-config-base/src/rules/errors.ts new file mode 100644 index 00000000..ee12bf5b --- /dev/null +++ b/packages/eslint-config-base/src/rules/errors.ts @@ -0,0 +1,195 @@ +import type { Linter } from 'eslint'; + +// extracted from eslint-config-airbnb-base@15.0.0 +// https://github.com/airbnb/javascript/blob/eslint-config-airbnb-base-v15.0.0/packages/eslint-config-airbnb-base/rules/errors.js +const config: Linter.Config = { + rules: { + // Enforce “for” loop update clause moving the counter in the right direction + // https://eslint.org/docs/rules/for-direction + 'for-direction': 'error', + + // Enforces that a return statement is present in property getters + // https://eslint.org/docs/rules/getter-return + 'getter-return': ['error', { allowImplicit: true }], + + // disallow using an async function as a Promise executor + // https://eslint.org/docs/rules/no-async-promise-executor + 'no-async-promise-executor': 'error', + + // Disallow await inside of loops + // https://eslint.org/docs/rules/no-await-in-loop + 'no-await-in-loop': 'error', + + // Disallow comparisons to negative zero + // https://eslint.org/docs/rules/no-compare-neg-zero + 'no-compare-neg-zero': 'error', + + // disallow assignment in conditional expressions + 'no-cond-assign': ['error', 'always'], + + // disallow use of console + 'no-console': 'warn', + + // disallow use of constant expressions in conditions + 'no-constant-condition': 'warn', + + // disallow control characters in regular expressions + 'no-control-regex': 'error', + + // disallow use of debugger + 'no-debugger': 'error', + + // disallow duplicate arguments in functions + 'no-dupe-args': 'error', + + // Disallow duplicate conditions in if-else-if chains + // https://eslint.org/docs/rules/no-dupe-else-if + 'no-dupe-else-if': 'error', + + // disallow duplicate keys when creating object literals + 'no-dupe-keys': 'error', + + // disallow a duplicate case label. + 'no-duplicate-case': 'error', + + // disallow empty statements + 'no-empty': 'error', + + // disallow the use of empty character classes in regular expressions + 'no-empty-character-class': 'error', + + // disallow assigning to the exception in a catch block + 'no-ex-assign': 'error', + + // disallow double-negation boolean casts in a boolean context + // https://eslint.org/docs/rules/no-extra-boolean-cast + 'no-extra-boolean-cast': 'error', + + // disallow unnecessary parentheses + // https://eslint.org/docs/rules/no-extra-parens + 'no-extra-parens': [ + 'off', + 'all', + { + conditionalAssign: true, + nestedBinaryExpressions: false, + returnAssign: false, + ignoreJSX: 'all', // delegate to eslint-plugin-react + enforceForArrowConditionals: false, + }, + ], + + // disallow unnecessary semicolons + 'no-extra-semi': 'error', + + // disallow overwriting functions written as function declarations + 'no-func-assign': 'error', + + // https://eslint.org/docs/rules/no-import-assign + 'no-import-assign': 'error', + + // disallow function or variable declarations in nested blocks + 'no-inner-declarations': 'error', + + // disallow invalid regular expression strings in the RegExp constructor + 'no-invalid-regexp': 'error', + + // disallow irregular whitespace outside of strings and comments + 'no-irregular-whitespace': 'error', + + // Disallow Number Literals That Lose Precision + // https://eslint.org/docs/rules/no-loss-of-precision + 'no-loss-of-precision': 'error', + + // Disallow characters which are made with multiple code points in character class syntax + // https://eslint.org/docs/rules/no-misleading-character-class + 'no-misleading-character-class': 'error', + + // disallow the use of object properties of the global object (Math and JSON) as functions + 'no-obj-calls': 'error', + + // Disallow returning values from Promise executor functions + // https://eslint.org/docs/rules/no-promise-executor-return + 'no-promise-executor-return': 'error', + + // disallow use of Object.prototypes builtins directly + // https://eslint.org/docs/rules/no-prototype-builtins + 'no-prototype-builtins': 'error', + + // disallow multiple spaces in a regular expression literal + 'no-regex-spaces': 'error', + + // Disallow returning values from setters + // https://eslint.org/docs/rules/no-setter-return + 'no-setter-return': 'error', + + // disallow sparse arrays + 'no-sparse-arrays': 'error', + + // Disallow template literal placeholder syntax in regular strings + // https://eslint.org/docs/rules/no-template-curly-in-string + 'no-template-curly-in-string': 'error', + + // Avoid code that looks like two expressions but is actually one + // https://eslint.org/docs/rules/no-unexpected-multiline + 'no-unexpected-multiline': 'error', + + // disallow unreachable statements after a return, throw, continue, or break statement + 'no-unreachable': 'error', + + // Disallow loops with a body that allows only one iteration + // https://eslint.org/docs/rules/no-unreachable-loop + 'no-unreachable-loop': [ + 'error', + { + ignore: [], // WhileStatement, DoWhileStatement, ForStatement, ForInStatement, ForOfStatement + }, + ], + + // disallow return/throw/break/continue inside finally blocks + // https://eslint.org/docs/rules/no-unsafe-finally + 'no-unsafe-finally': 'error', + + // disallow negating the left operand of relational operators + // https://eslint.org/docs/rules/no-unsafe-negation + 'no-unsafe-negation': 'error', + + // disallow use of optional chaining in contexts where the undefined value is not allowed + // https://eslint.org/docs/rules/no-unsafe-optional-chaining + 'no-unsafe-optional-chaining': [ + 'error', + { disallowArithmeticOperators: true }, + ], + + // Disallow Unused Private Class Members + // https://eslint.org/docs/rules/no-unused-private-class-members + // TODO: enable once eslint 7 is dropped (which is semver-major) + 'no-unused-private-class-members': 'off', + + // Disallow useless backreferences in regular expressions + // https://eslint.org/docs/rules/no-useless-backreference + 'no-useless-backreference': 'error', + + // disallow negation of the left operand of an in expression + // deprecated in favor of no-unsafe-negation + 'no-negated-in-lhs': 'off', + + // Disallow assignments that can lead to race conditions due to usage of await or yield + // https://eslint.org/docs/rules/require-atomic-updates + // note: not enabled because it is very buggy + 'require-atomic-updates': 'off', + + // disallow comparisons with the value NaN + 'use-isnan': 'error', + + // ensure JSDoc comments are valid + // https://eslint.org/docs/rules/valid-jsdoc + 'valid-jsdoc': 'off', + + // ensure that the results of typeof are compared against a valid string + // https://eslint.org/docs/rules/valid-typeof + 'valid-typeof': ['error', { requireStringLiterals: true }], + }, +}; + +export = config; diff --git a/packages/eslint-config-base/src/rules/es6.ts b/packages/eslint-config-base/src/rules/es6.ts new file mode 100644 index 00000000..53fa0f9f --- /dev/null +++ b/packages/eslint-config-base/src/rules/es6.ts @@ -0,0 +1,224 @@ +import type { Linter } from 'eslint'; + +// extracted from eslint-config-airbnb-base@15.0.0 +// https://github.com/airbnb/javascript/blob/eslint-config-airbnb-base-v15.0.0/packages/eslint-config-airbnb-base/rules/es6.js +const config: Linter.Config = { + env: { + es6: true, + }, + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + ecmaFeatures: { + generators: false, + objectLiteralDuplicateProperties: false, + }, + }, + + rules: { + // enforces no braces where they can be omitted + // https://eslint.org/docs/rules/arrow-body-style + // TODO: enable requireReturnForObjectLiteral? + 'arrow-body-style': [ + 'error', + 'as-needed', + { + requireReturnForObjectLiteral: false, + }, + ], + + // require parens in arrow function arguments + // https://eslint.org/docs/rules/arrow-parens + 'arrow-parens': ['error', 'always'], + + // require space before/after arrow function's arrow + // https://eslint.org/docs/rules/arrow-spacing + 'arrow-spacing': ['error', { before: true, after: true }], + + // verify super() callings in constructors + 'constructor-super': 'error', + + // enforce the spacing around the * in generator functions + // https://eslint.org/docs/rules/generator-star-spacing + 'generator-star-spacing': ['error', { before: false, after: true }], + + // disallow modifying variables of class declarations + // https://eslint.org/docs/rules/no-class-assign + 'no-class-assign': 'error', + + // disallow arrow functions where they could be confused with comparisons + // https://eslint.org/docs/rules/no-confusing-arrow + 'no-confusing-arrow': [ + 'error', + { + allowParens: true, + }, + ], + + // disallow modifying variables that are declared using const + 'no-const-assign': 'error', + + // disallow duplicate class members + // https://eslint.org/docs/rules/no-dupe-class-members + 'no-dupe-class-members': 'error', + + // disallow importing from the same path more than once + // https://eslint.org/docs/rules/no-duplicate-imports + // replaced by https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-duplicates.md + 'no-duplicate-imports': 'off', + + // disallow symbol constructor + // https://eslint.org/docs/rules/no-new-symbol + 'no-new-symbol': 'error', + + // Disallow specified names in exports + // https://eslint.org/docs/rules/no-restricted-exports + 'no-restricted-exports': [ + 'error', + { + restrictedNamedExports: [ + 'default', // use `export default` to provide a default export + 'then', // this will cause tons of confusion when your module is dynamically `import()`ed, and will break in most node ESM versions + ], + }, + ], + + // disallow specific imports + // https://eslint.org/docs/rules/no-restricted-imports + 'no-restricted-imports': [ + 'off', + { + paths: [], + patterns: [], + }, + ], + + // disallow to use this/super before super() calling in constructors. + // https://eslint.org/docs/rules/no-this-before-super + 'no-this-before-super': 'error', + + // disallow useless computed property keys + // https://eslint.org/docs/rules/no-useless-computed-key + 'no-useless-computed-key': 'error', + + // disallow unnecessary constructor + // https://eslint.org/docs/rules/no-useless-constructor + 'no-useless-constructor': 'error', + + // disallow renaming import, export, and destructured assignments to the same name + // https://eslint.org/docs/rules/no-useless-rename + 'no-useless-rename': [ + 'error', + { + ignoreDestructuring: false, + ignoreImport: false, + ignoreExport: false, + }, + ], + + // require let or const instead of var + 'no-var': 'error', + + // require method and property shorthand syntax for object literals + // https://eslint.org/docs/rules/object-shorthand + 'object-shorthand': [ + 'error', + 'always', + { + ignoreConstructors: false, + avoidQuotes: true, + }, + ], + + // suggest using arrow functions as callbacks + 'prefer-arrow-callback': [ + 'error', + { + allowNamedFunctions: false, + allowUnboundThis: true, + }, + ], + + // suggest using of const declaration for variables that are never modified after declared + 'prefer-const': [ + 'error', + { + destructuring: 'any', + ignoreReadBeforeAssign: true, + }, + ], + + // Prefer destructuring from arrays and objects + // https://eslint.org/docs/rules/prefer-destructuring + 'prefer-destructuring': [ + 'error', + { + VariableDeclarator: { + array: false, + object: true, + }, + AssignmentExpression: { + array: true, + object: false, + }, + }, + { + enforceForRenamedProperties: false, + }, + ], + + // disallow parseInt() in favor of binary, octal, and hexadecimal literals + // https://eslint.org/docs/rules/prefer-numeric-literals + 'prefer-numeric-literals': 'error', + + // suggest using Reflect methods where applicable + // https://eslint.org/docs/rules/prefer-reflect + 'prefer-reflect': 'off', + + // use rest parameters instead of arguments + // https://eslint.org/docs/rules/prefer-rest-params + 'prefer-rest-params': 'error', + + // suggest using the spread syntax instead of .apply() + // https://eslint.org/docs/rules/prefer-spread + 'prefer-spread': 'error', + + // suggest using template literals instead of string concatenation + // https://eslint.org/docs/rules/prefer-template + 'prefer-template': 'error', + + // disallow generator functions that do not have yield + // https://eslint.org/docs/rules/require-yield + 'require-yield': 'error', + + // enforce spacing between object rest-spread + // https://eslint.org/docs/rules/rest-spread-spacing + 'rest-spread-spacing': ['error', 'never'], + + // import sorting + // https://eslint.org/docs/rules/sort-imports + 'sort-imports': [ + 'off', + { + ignoreCase: false, + ignoreDeclarationSort: false, + ignoreMemberSort: false, + memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'], + }, + ], + + // require a Symbol description + // https://eslint.org/docs/rules/symbol-description + 'symbol-description': 'error', + + // enforce usage of spacing in template strings + // https://eslint.org/docs/rules/template-curly-spacing + 'template-curly-spacing': 'error', + + // enforce spacing around the * in yield* expressions + // https://eslint.org/docs/rules/yield-star-spacing + 'yield-star-spacing': ['error', 'after'], + }, +}; + +export = config; diff --git a/packages/eslint-config-base/src/rules/imports.ts b/packages/eslint-config-base/src/rules/imports.ts new file mode 100644 index 00000000..ca0e998d --- /dev/null +++ b/packages/eslint-config-base/src/rules/imports.ts @@ -0,0 +1,297 @@ +import type { Linter } from 'eslint'; + +// extracted from eslint-config-airbnb-base@15.0.0 +// https://github.com/airbnb/javascript/blob/eslint-config-airbnb-base-v15.0.0/packages/eslint-config-airbnb-base/rules/imports.js +const config: Linter.Config = { + env: { + es6: true, + }, + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + }, + plugins: ['import'], + + settings: { + 'import/resolver': { + node: { + extensions: ['.mjs', '.js', '.json'], + }, + }, + 'import/extensions': ['.js', '.mjs', '.jsx'], + 'import/core-modules': [], + 'import/ignore': [ + 'node_modules', + '\\.(coffee|scss|css|less|hbs|svg|json)$', + ], + }, + + rules: { + // Static analysis: + + // ensure imports point to files/modules that can be resolved + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-unresolved.md + 'import/no-unresolved': ['error', { commonjs: true, caseSensitive: true }], + + // ensure named imports coupled with named exports + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/named.md#when-not-to-use-it + 'import/named': 'error', + + // ensure default import coupled with default export + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/default.md#when-not-to-use-it + 'import/default': 'off', + + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/namespace.md + 'import/namespace': 'off', + + // Helpful warnings: + + // disallow invalid exports, e.g. multiple defaults + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/export.md + 'import/export': 'error', + + // do not allow a default import name to match a named export + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-named-as-default.md + 'import/no-named-as-default': 'error', + + // warn on accessing default export property names that are also named exports + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-named-as-default-member.md + 'import/no-named-as-default-member': 'error', + + // disallow use of jsdoc-marked-deprecated imports + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-deprecated.md + 'import/no-deprecated': 'off', + + // Forbid the use of extraneous packages + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-extraneous-dependencies.md + // paths are treated both as absolute paths, and relative to process.cwd() + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: [ + 'test/**', // tape, common npm pattern + 'tests/**', // also common npm pattern + 'spec/**', // mocha, rspec-like pattern + '**/__tests__/**', // jest pattern + '**/__mocks__/**', // jest pattern + 'test.{js,jsx}', // repos with a single test file + 'test-*.{js,jsx}', // repos with multiple top-level test files + '**/*{.,_}{test,spec}.{js,jsx}', // tests where the extension or filename suffix denotes that it is a test + '**/jest.config.js', // jest config + '**/jest.setup.js', // jest setup + '**/vue.config.js', // vue-cli config + '**/webpack.config.js', // webpack config + '**/webpack.config.*.js', // webpack config + '**/rollup.config.js', // rollup config + '**/rollup.config.*.js', // rollup config + '**/gulpfile.js', // gulp config + '**/gulpfile.*.js', // gulp config + '**/Gruntfile{,.js}', // grunt config + '**/protractor.conf.js', // protractor config + '**/protractor.conf.*.js', // protractor config + '**/karma.conf.js', // karma config + '**/.eslintrc.js', // eslint config + ], + optionalDependencies: false, + }, + ], + + // Forbid mutable exports + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-mutable-exports.md + 'import/no-mutable-exports': 'error', + + // Module systems: + + // disallow require() + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-commonjs.md + 'import/no-commonjs': 'off', + + // disallow AMD require/define + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-amd.md + 'import/no-amd': 'error', + + // No Node.js builtin modules + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-nodejs-modules.md + // TODO: enable? + 'import/no-nodejs-modules': 'off', + + // Style guide: + + // disallow non-import statements appearing before import statements + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/first.md + 'import/first': 'error', + + // disallow non-import statements appearing before import statements + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/imports-first.md + // deprecated: use `import/first` + 'import/imports-first': 'off', + + // disallow duplicate imports + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-duplicates.md + 'import/no-duplicates': 'error', + + // disallow namespace imports + // TODO: enable? + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-namespace.md + 'import/no-namespace': 'off', + + // Ensure consistent use of file extension within the import path + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/extensions.md + 'import/extensions': [ + 'error', + 'ignorePackages', + { + js: 'never', + mjs: 'never', + jsx: 'never', + }, + ], + + // ensure absolute imports are above relative imports and that unassigned imports are ignored + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/order.md + // TODO: enforce a stricter convention in module import order? + 'import/order': [ + 'error', + { groups: [['builtin', 'external', 'internal']] }, + ], + + // Require a newline after the last import/require in a group + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/newline-after-import.md + 'import/newline-after-import': 'error', + + // Require modules with a single export to use a default export + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/prefer-default-export.md + 'import/prefer-default-export': 'error', + + // Restrict which files can be imported in a given folder + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-restricted-paths.md + 'import/no-restricted-paths': 'off', + + // Forbid modules to have too many dependencies + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/max-dependencies.md + 'import/max-dependencies': ['off', { max: 10 }], + + // Forbid import of modules using absolute paths + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-absolute-path.md + 'import/no-absolute-path': 'error', + + // Forbid require() calls with expressions + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-dynamic-require.md + 'import/no-dynamic-require': 'error', + + // prevent importing the submodules of other modules + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-internal-modules.md + 'import/no-internal-modules': [ + 'off', + { + allow: [], + }, + ], + + // Warn if a module could be mistakenly parsed as a script by a consumer + // leveraging Unambiguous JavaScript Grammar + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/unambiguous.md + // this should not be enabled until this proposal has at least been *presented* to TC39. + // At the moment, it's not a thing. + 'import/unambiguous': 'off', + + // Forbid Webpack loader syntax in imports + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-webpack-loader-syntax.md + 'import/no-webpack-loader-syntax': 'error', + + // Prevent unassigned imports + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-unassigned-import.md + // importing for side effects is perfectly acceptable, if you need side effects. + 'import/no-unassigned-import': 'off', + + // Prevent importing the default as if it were named + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-named-default.md + 'import/no-named-default': 'error', + + // Reports if a module's default export is unnamed + // https://github.com/benmosher/eslint-plugin-import/blob/d9b712ac7fd1fddc391f7b234827925c160d956f/docs/rules/no-anonymous-default-export.md + 'import/no-anonymous-default-export': [ + 'off', + { + allowArray: false, + allowArrowFunction: false, + allowAnonymousClass: false, + allowAnonymousFunction: false, + allowLiteral: false, + allowObject: false, + }, + ], + + // This rule enforces that all exports are declared at the bottom of the file. + // https://github.com/benmosher/eslint-plugin-import/blob/98acd6afd04dcb6920b81330114e146dc8532ea4/docs/rules/exports-last.md + // TODO: enable? + 'import/exports-last': 'off', + + // Reports when named exports are not grouped together in a single export declaration + // or when multiple assignments to CommonJS module.exports or exports object are present + // in a single file. + // https://github.com/benmosher/eslint-plugin-import/blob/44a038c06487964394b1e15b64f3bd34e5d40cde/docs/rules/group-exports.md + 'import/group-exports': 'off', + + // forbid default exports. this is a terrible rule, do not use it. + // https://github.com/benmosher/eslint-plugin-import/blob/44a038c06487964394b1e15b64f3bd34e5d40cde/docs/rules/no-default-export.md + 'import/no-default-export': 'off', + + // Prohibit named exports. this is a terrible rule, do not use it. + // https://github.com/benmosher/eslint-plugin-import/blob/1ec80fa35fa1819e2d35a70e68fb6a149fb57c5e/docs/rules/no-named-export.md + 'import/no-named-export': 'off', + + // Forbid a module from importing itself + // https://github.com/benmosher/eslint-plugin-import/blob/44a038c06487964394b1e15b64f3bd34e5d40cde/docs/rules/no-self-import.md + 'import/no-self-import': 'error', + + // Forbid cyclical dependencies between modules + // https://github.com/benmosher/eslint-plugin-import/blob/d81f48a2506182738409805f5272eff4d77c9348/docs/rules/no-cycle.md + 'import/no-cycle': ['error', { maxDepth: '∞' }], + + // Ensures that there are no useless path segments + // https://github.com/benmosher/eslint-plugin-import/blob/ebafcbf59ec9f653b2ac2a0156ca3bcba0a7cf57/docs/rules/no-useless-path-segments.md + 'import/no-useless-path-segments': ['error', { commonjs: true }], + + // dynamic imports require a leading comment with a webpackChunkName + // https://github.com/benmosher/eslint-plugin-import/blob/ebafcbf59ec9f653b2ac2a0156ca3bcba0a7cf57/docs/rules/dynamic-import-chunkname.md + 'import/dynamic-import-chunkname': [ + 'off', + { + importFunctions: [], + webpackChunknameFormat: '[0-9a-zA-Z-_/.]+', + }, + ], + + // Use this rule to prevent imports to folders in relative parent paths. + // https://github.com/benmosher/eslint-plugin-import/blob/c34f14f67f077acd5a61b3da9c0b0de298d20059/docs/rules/no-relative-parent-imports.md + 'import/no-relative-parent-imports': 'off', + + // Reports modules without any exports, or with unused exports + // https://github.com/benmosher/eslint-plugin-import/blob/f63dd261809de6883b13b6b5b960e6d7f42a7813/docs/rules/no-unused-modules.md + // TODO: enable once it supports CJS + 'import/no-unused-modules': [ + 'off', + { + ignoreExports: [], + missingExports: true, + unusedExports: true, + }, + ], + + // Reports the use of import declarations with CommonJS exports in any module except for the main module. + // https://github.com/benmosher/eslint-plugin-import/blob/1012eb951767279ce3b540a4ec4f29236104bb5b/docs/rules/no-import-module-exports.md + 'import/no-import-module-exports': [ + 'error', + { + exceptions: [], + }, + ], + + // Use this rule to prevent importing packages through relative paths. + // https://github.com/benmosher/eslint-plugin-import/blob/1012eb951767279ce3b540a4ec4f29236104bb5b/docs/rules/no-relative-packages.md + 'import/no-relative-packages': 'error', + }, +}; + +export = config; diff --git a/packages/eslint-config-base/src/rules/node.ts b/packages/eslint-config-base/src/rules/node.ts new file mode 100644 index 00000000..90ebe31a --- /dev/null +++ b/packages/eslint-config-base/src/rules/node.ts @@ -0,0 +1,49 @@ +import type { Linter } from 'eslint'; + +// extracted from eslint-config-airbnb-base@15.0.0 +// https://github.com/airbnb/javascript/blob/eslint-config-airbnb-base-v15.0.0/packages/eslint-config-airbnb-base/rules/node.js +const config: Linter.Config = { + env: { + node: true, + }, + + rules: { + // enforce return after a callback + 'callback-return': 'off', + + // require all requires be top-level + // https://eslint.org/docs/rules/global-require + 'global-require': 'error', + + // enforces error handling in callbacks (node environment) + 'handle-callback-err': 'off', + + // disallow use of the Buffer() constructor + // https://eslint.org/docs/rules/no-buffer-constructor + 'no-buffer-constructor': 'error', + + // disallow mixing regular variable and require declarations + 'no-mixed-requires': ['off', false], + + // disallow use of new operator with the require function + 'no-new-require': 'error', + + // disallow string concatenation with __dirname and __filename + // https://eslint.org/docs/rules/no-path-concat + 'no-path-concat': 'error', + + // disallow use of process.env + 'no-process-env': 'off', + + // disallow process.exit() + 'no-process-exit': 'off', + + // restrict usage of specified node modules + 'no-restricted-modules': 'off', + + // disallow use of synchronous methods (off by default) + 'no-sync': 'off', + }, +}; + +export = config; diff --git a/packages/eslint-config-base/src/rules/strict.ts b/packages/eslint-config-base/src/rules/strict.ts new file mode 100644 index 00000000..1baa79dc --- /dev/null +++ b/packages/eslint-config-base/src/rules/strict.ts @@ -0,0 +1,12 @@ +import type { Linter } from 'eslint'; + +// extracted from eslint-config-airbnb-base@15.0.0 +// https://github.com/airbnb/javascript/blob/eslint-config-airbnb-base-v15.0.0/packages/eslint-config-airbnb-base/rules/strict.js +const config: Linter.Config = { + rules: { + // babel inserts `'use strict';` for us + strict: ['error', 'never'], + }, +}; + +export = config; diff --git a/packages/eslint-config-base/src/rules/style.ts b/packages/eslint-config-base/src/rules/style.ts new file mode 100644 index 00000000..28e5d71f --- /dev/null +++ b/packages/eslint-config-base/src/rules/style.ts @@ -0,0 +1,655 @@ +import type { Linter } from 'eslint'; + +import semver = require('semver'); +import eslintPkg = require('eslint/package.json'); + +// extracted from eslint-config-airbnb-base@15.0.0 +// https://github.com/airbnb/javascript/blob/eslint-config-airbnb-base-v15.0.0/packages/eslint-config-airbnb-base/rules/style.js +const config: Linter.Config = { + rules: { + // enforce line breaks after opening and before closing array brackets + // https://eslint.org/docs/rules/array-bracket-newline + // TODO: enable? semver-major + 'array-bracket-newline': ['off', 'consistent'], // object option alternative: { multiline: true, minItems: 3 } + + // enforce line breaks between array elements + // https://eslint.org/docs/rules/array-element-newline + // TODO: enable? semver-major + 'array-element-newline': ['off', { multiline: true, minItems: 3 }], + + // enforce spacing inside array brackets + 'array-bracket-spacing': ['error', 'never'], + + // enforce spacing inside single-line blocks + // https://eslint.org/docs/rules/block-spacing + 'block-spacing': ['error', 'always'], + + // enforce one true brace style + 'brace-style': ['error', '1tbs', { allowSingleLine: true }], + + // require camel case names + camelcase: ['error', { properties: 'never', ignoreDestructuring: false }], + + // enforce or disallow capitalization of the first letter of a comment + // https://eslint.org/docs/rules/capitalized-comments + 'capitalized-comments': [ + 'off', + 'never', + { + line: { + ignorePattern: '.*', + ignoreInlineComments: true, + ignoreConsecutiveComments: true, + }, + block: { + ignorePattern: '.*', + ignoreInlineComments: true, + ignoreConsecutiveComments: true, + }, + }, + ], + + // require trailing commas in multiline object literals + 'comma-dangle': [ + 'error', + { + arrays: 'always-multiline', + objects: 'always-multiline', + imports: 'always-multiline', + exports: 'always-multiline', + functions: 'always-multiline', + }, + ], + + // enforce spacing before and after comma + 'comma-spacing': ['error', { before: false, after: true }], + + // enforce one true comma style + 'comma-style': [ + 'error', + 'last', + { + exceptions: { + ArrayExpression: false, + ArrayPattern: false, + ArrowFunctionExpression: false, + CallExpression: false, + FunctionDeclaration: false, + FunctionExpression: false, + ImportDeclaration: false, + ObjectExpression: false, + ObjectPattern: false, + VariableDeclaration: false, + NewExpression: false, + }, + }, + ], + + // disallow padding inside computed properties + 'computed-property-spacing': ['error', 'never'], + + // enforces consistent naming when capturing the current execution context + 'consistent-this': 'off', + + // enforce newline at the end of file, with no multiple empty lines + 'eol-last': ['error', 'always'], + + // https://eslint.org/docs/rules/function-call-argument-newline + 'function-call-argument-newline': ['error', 'consistent'], + + // enforce spacing between functions and their invocations + // https://eslint.org/docs/rules/func-call-spacing + 'func-call-spacing': ['error', 'never'], + + // requires function names to match the name of the variable or property to which they are + // assigned + // https://eslint.org/docs/rules/func-name-matching + 'func-name-matching': [ + 'off', + 'always', + { + includeCommonJSModuleExports: false, + considerPropertyDescriptor: true, + }, + ], + + // require function expressions to have a name + // https://eslint.org/docs/rules/func-names + 'func-names': 'warn', + + // enforces use of function declarations or expressions + // https://eslint.org/docs/rules/func-style + // TODO: enable + 'func-style': ['off', 'expression'], + + // require line breaks inside function parentheses if there are line breaks between parameters + // https://eslint.org/docs/rules/function-paren-newline + 'function-paren-newline': [ + 'error', + semver.satisfies(eslintPkg.version, '>= 6') + ? 'multiline-arguments' + : 'consistent', + ], + + // disallow specified identifiers + // https://eslint.org/docs/rules/id-denylist + 'id-denylist': 'off', + + // this option enforces minimum and maximum identifier lengths + // (variable names, property names etc.) + 'id-length': 'off', + + // require identifiers to match the provided regular expression + 'id-match': 'off', + + // Enforce the location of arrow function bodies with implicit returns + // https://eslint.org/docs/rules/implicit-arrow-linebreak + 'implicit-arrow-linebreak': ['error', 'beside'], + + // this option sets a specific tab width for your code + // https://eslint.org/docs/rules/indent + indent: [ + 'error', + 2, + { + SwitchCase: 1, + VariableDeclarator: 1, + outerIIFEBody: 1, + // MemberExpression: null, + FunctionDeclaration: { + parameters: 1, + body: 1, + }, + FunctionExpression: { + parameters: 1, + body: 1, + }, + CallExpression: { + arguments: 1, + }, + ArrayExpression: 1, + ObjectExpression: 1, + ImportDeclaration: 1, + flatTernaryExpressions: false, + // list derived from https://github.com/benjamn/ast-types/blob/HEAD/def/jsx.js + ignoredNodes: [ + 'JSXElement', + 'JSXElement > *', + 'JSXAttribute', + 'JSXIdentifier', + 'JSXNamespacedName', + 'JSXMemberExpression', + 'JSXSpreadAttribute', + 'JSXExpressionContainer', + 'JSXOpeningElement', + 'JSXClosingElement', + 'JSXFragment', + 'JSXOpeningFragment', + 'JSXClosingFragment', + 'JSXText', + 'JSXEmptyExpression', + 'JSXSpreadChild', + ], + ignoreComments: false, + }, + ], + + // specify whether double or single quotes should be used in JSX attributes + // https://eslint.org/docs/rules/jsx-quotes + 'jsx-quotes': ['off', 'prefer-double'], + + // enforces spacing between keys and values in object literal properties + 'key-spacing': ['error', { beforeColon: false, afterColon: true }], + + // require a space before & after certain keywords + 'keyword-spacing': [ + 'error', + { + before: true, + after: true, + overrides: { + return: { after: true }, + throw: { after: true }, + case: { after: true }, + }, + }, + ], + + // enforce position of line comments + // https://eslint.org/docs/rules/line-comment-position + // TODO: enable? + 'line-comment-position': [ + 'off', + { + position: 'above', + ignorePattern: '', + applyDefaultPatterns: true, + }, + ], + + // disallow mixed 'LF' and 'CRLF' as linebreaks + // https://eslint.org/docs/rules/linebreak-style + 'linebreak-style': ['error', 'unix'], + + // require or disallow an empty line between class members + // https://eslint.org/docs/rules/lines-between-class-members + 'lines-between-class-members': [ + 'error', + 'always', + { exceptAfterSingleLine: false }, + ], + + // enforces empty lines around comments + 'lines-around-comment': 'off', + + // require or disallow newlines around directives + // https://eslint.org/docs/rules/lines-around-directive + 'lines-around-directive': [ + 'error', + { + before: 'always', + after: 'always', + }, + ], + + // specify the maximum depth that blocks can be nested + 'max-depth': ['off', 4], + + // specify the maximum length of a line in your program + // https://eslint.org/docs/rules/max-len + 'max-len': [ + 'error', + 100, + 2, + { + ignoreUrls: true, + ignoreComments: false, + ignoreRegExpLiterals: true, + ignoreStrings: true, + ignoreTemplateLiterals: true, + }, + ], + + // specify the max number of lines in a file + // https://eslint.org/docs/rules/max-lines + 'max-lines': [ + 'off', + { + max: 300, + skipBlankLines: true, + skipComments: true, + }, + ], + + // enforce a maximum function length + // https://eslint.org/docs/rules/max-lines-per-function + 'max-lines-per-function': [ + 'off', + { + max: 50, + skipBlankLines: true, + skipComments: true, + IIFEs: true, + }, + ], + + // specify the maximum depth callbacks can be nested + 'max-nested-callbacks': 'off', + + // limits the number of parameters that can be used in the function declaration. + 'max-params': ['off', 3], + + // specify the maximum number of statement allowed in a function + 'max-statements': ['off', 10], + + // restrict the number of statements per line + // https://eslint.org/docs/rules/max-statements-per-line + 'max-statements-per-line': ['off', { max: 1 }], + + // enforce a particular style for multiline comments + // https://eslint.org/docs/rules/multiline-comment-style + 'multiline-comment-style': ['off', 'starred-block'], + + // require multiline ternary + // https://eslint.org/docs/rules/multiline-ternary + // TODO: enable? + 'multiline-ternary': ['off', 'never'], + + // require a capital letter for constructors + 'new-cap': [ + 'error', + { + newIsCap: true, + newIsCapExceptions: [], + capIsNew: false, + capIsNewExceptions: [ + 'Immutable.Map', + 'Immutable.Set', + 'Immutable.List', + ], + }, + ], + + // disallow the omission of parentheses when invoking a constructor with no arguments + // https://eslint.org/docs/rules/new-parens + 'new-parens': 'error', + + // allow/disallow an empty newline after var statement + 'newline-after-var': 'off', + + // https://eslint.org/docs/rules/newline-before-return + 'newline-before-return': 'off', + + // enforces new line after each method call in the chain to make it + // more readable and easy to maintain + // https://eslint.org/docs/rules/newline-per-chained-call + 'newline-per-chained-call': ['error', { ignoreChainWithDepth: 4 }], + + // disallow use of the Array constructor + 'no-array-constructor': 'error', + + // disallow use of bitwise operators + // https://eslint.org/docs/rules/no-bitwise + 'no-bitwise': 'error', + + // disallow use of the continue statement + // https://eslint.org/docs/rules/no-continue + 'no-continue': 'error', + + // disallow comments inline after code + 'no-inline-comments': 'off', + + // disallow if as the only statement in an else block + // https://eslint.org/docs/rules/no-lonely-if + 'no-lonely-if': 'error', + + // disallow un-paren'd mixes of different operators + // https://eslint.org/docs/rules/no-mixed-operators + 'no-mixed-operators': [ + 'error', + { + // the list of arithmetic groups disallows mixing `%` and `**` + // with other arithmetic operators. + groups: [ + ['%', '**'], + ['%', '+'], + ['%', '-'], + ['%', '*'], + ['%', '/'], + ['/', '*'], + ['&', '|', '<<', '>>', '>>>'], + ['==', '!=', '===', '!=='], + ['&&', '||'], + ], + allowSamePrecedence: false, + }, + ], + + // disallow mixed spaces and tabs for indentation + 'no-mixed-spaces-and-tabs': 'error', + + // disallow use of chained assignment expressions + // https://eslint.org/docs/rules/no-multi-assign + 'no-multi-assign': ['error'], + + // disallow multiple empty lines, only one newline at the end, and no new lines at the beginning + // https://eslint.org/docs/rules/no-multiple-empty-lines + 'no-multiple-empty-lines': ['error', { max: 1, maxBOF: 0, maxEOF: 0 }], + + // disallow negated conditions + // https://eslint.org/docs/rules/no-negated-condition + 'no-negated-condition': 'off', + + // disallow nested ternary expressions + 'no-nested-ternary': 'error', + + // disallow use of the Object constructor + 'no-new-object': 'error', + + // disallow use of unary operators, ++ and -- + // https://eslint.org/docs/rules/no-plusplus + 'no-plusplus': 'error', + + // disallow certain syntax forms + // https://eslint.org/docs/rules/no-restricted-syntax + 'no-restricted-syntax': [ + 'error', + { + selector: 'ForInStatement', + 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: 'ForOfStatement', + message: + 'iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations.', + }, + { + selector: 'LabeledStatement', + message: + 'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.', + }, + { + selector: 'WithStatement', + message: + '`with` is disallowed in strict mode because it makes code impossible to predict and optimize.', + }, + ], + + // disallow space between function identifier and application + 'no-spaced-func': 'error', + + // disallow tab characters entirely + 'no-tabs': 'error', + + // disallow the use of ternary operators + 'no-ternary': 'off', + + // disallow trailing whitespace at the end of lines + 'no-trailing-spaces': [ + 'error', + { + skipBlankLines: false, + ignoreComments: false, + }, + ], + + // disallow dangling underscores in identifiers + // https://eslint.org/docs/rules/no-underscore-dangle + 'no-underscore-dangle': [ + 'error', + { + allow: [], + allowAfterThis: false, + allowAfterSuper: false, + enforceInMethodNames: true, + }, + ], + + // disallow the use of Boolean literals in conditional expressions + // also, prefer `a || b` over `a ? a : b` + // https://eslint.org/docs/rules/no-unneeded-ternary + 'no-unneeded-ternary': ['error', { defaultAssignment: false }], + + // disallow whitespace before properties + // https://eslint.org/docs/rules/no-whitespace-before-property + 'no-whitespace-before-property': 'error', + + // enforce the location of single-line statements + // https://eslint.org/docs/rules/nonblock-statement-body-position + 'nonblock-statement-body-position': ['error', 'beside', { overrides: {} }], + + // require padding inside curly braces + 'object-curly-spacing': ['error', 'always'], + + // enforce line breaks between braces + // https://eslint.org/docs/rules/object-curly-newline + 'object-curly-newline': [ + 'error', + { + ObjectExpression: { + minProperties: 4, + multiline: true, + consistent: true, + }, + ObjectPattern: { minProperties: 4, multiline: true, consistent: true }, + ImportDeclaration: { + minProperties: 4, + multiline: true, + consistent: true, + }, + ExportDeclaration: { + minProperties: 4, + multiline: true, + consistent: true, + }, + }, + ], + + // enforce "same line" or "multiple line" on object properties. + // https://eslint.org/docs/rules/object-property-newline + 'object-property-newline': [ + 'error', + { + allowAllPropertiesOnSameLine: true, + }, + ], + + // allow just one var statement per function + 'one-var': ['error', 'never'], + + // require a newline around variable declaration + // https://eslint.org/docs/rules/one-var-declaration-per-line + 'one-var-declaration-per-line': ['error', 'always'], + + // require assignment operator shorthand where possible or prohibit it entirely + // https://eslint.org/docs/rules/operator-assignment + 'operator-assignment': ['error', 'always'], + + // Requires operator at the beginning of the line in multiline statements + // https://eslint.org/docs/rules/operator-linebreak + 'operator-linebreak': ['error', 'before', { overrides: { '=': 'none' } }], + + // disallow padding within blocks + 'padded-blocks': [ + 'error', + { + blocks: 'never', + classes: 'never', + switches: 'never', + }, + { + allowSingleLineBlocks: true, + }, + ], + + // Require or disallow padding lines between statements + // https://eslint.org/docs/rules/padding-line-between-statements + 'padding-line-between-statements': 'off', + + // Disallow the use of Math.pow in favor of the ** operator + // https://eslint.org/docs/rules/prefer-exponentiation-operator + 'prefer-exponentiation-operator': 'error', + + // Prefer use of an object spread over Object.assign + // https://eslint.org/docs/rules/prefer-object-spread + 'prefer-object-spread': 'error', + + // require quotes around object literal property names + // https://eslint.org/docs/rules/quote-props.html + 'quote-props': [ + 'error', + 'as-needed', + { keywords: false, unnecessary: true, numbers: false }, + ], + + // specify whether double or single quotes should be used + quotes: ['error', 'single', { avoidEscape: true }], + + // do not require jsdoc + // https://eslint.org/docs/rules/require-jsdoc + 'require-jsdoc': 'off', + + // require or disallow use of semicolons instead of ASI + semi: ['error', 'always'], + + // enforce spacing before and after semicolons + 'semi-spacing': ['error', { before: false, after: true }], + + // Enforce location of semicolons + // https://eslint.org/docs/rules/semi-style + 'semi-style': ['error', 'last'], + + // requires object keys to be sorted + 'sort-keys': ['off', 'asc', { caseSensitive: false, natural: true }], + + // sort variables within the same declaration block + 'sort-vars': 'off', + + // require or disallow space before blocks + 'space-before-blocks': 'error', + + // require or disallow space before function opening parenthesis + // https://eslint.org/docs/rules/space-before-function-paren + 'space-before-function-paren': [ + 'error', + { + anonymous: 'always', + named: 'never', + asyncArrow: 'always', + }, + ], + + // require or disallow spaces inside parentheses + 'space-in-parens': ['error', 'never'], + + // require spaces around operators + 'space-infix-ops': 'error', + + // Require or disallow spaces before/after unary operators + // https://eslint.org/docs/rules/space-unary-ops + 'space-unary-ops': [ + 'error', + { + words: true, + nonwords: false, + overrides: {}, + }, + ], + + // require or disallow a space immediately following the // or /* in a comment + // https://eslint.org/docs/rules/spaced-comment + 'spaced-comment': [ + 'error', + 'always', + { + line: { + exceptions: ['-', '+'], + markers: ['=', '!', '/'], // space here to support sprockets directives, slash for TS /// comments + }, + block: { + exceptions: ['-', '+'], + markers: ['=', '!', ':', '::'], // space here to support sprockets directives and flow comment types + balanced: true, + }, + }, + ], + + // Enforce spacing around colons of switch statements + // https://eslint.org/docs/rules/switch-colon-spacing + 'switch-colon-spacing': ['error', { after: true, before: false }], + + // Require or disallow spacing between template tags and their literals + // https://eslint.org/docs/rules/template-tag-spacing + 'template-tag-spacing': ['error', 'never'], + + // require or disallow the Unicode Byte Order Mark + // https://eslint.org/docs/rules/unicode-bom + 'unicode-bom': ['error', 'never'], + + // require regex literals to be wrapped in parentheses + 'wrap-regex': 'off', + }, +}; + +export = config; diff --git a/packages/eslint-config-base/src/rules/variables.ts b/packages/eslint-config-base/src/rules/variables.ts new file mode 100644 index 00000000..fb2057c6 --- /dev/null +++ b/packages/eslint-config-base/src/rules/variables.ts @@ -0,0 +1,69 @@ +import type { Linter } from 'eslint'; + +import confusingBrowserGlobals = require('confusing-browser-globals'); + +// extracted from eslint-config-airbnb-base@15.0.0 +// https://github.com/airbnb/javascript/blob/eslint-config-airbnb-base-v15.0.0/packages/eslint-config-airbnb-base/rules/variables.js +const config: Linter.Config = { + rules: { + // enforce or disallow variable initializations at definition + 'init-declarations': 'off', + + // disallow the catch clause parameter name being the same as a variable in the outer scope + 'no-catch-shadow': 'off', + + // disallow deletion of variables + 'no-delete-var': 'error', + + // disallow labels that share a name with a variable + // https://eslint.org/docs/rules/no-label-var + 'no-label-var': 'error', + + // disallow specific globals + 'no-restricted-globals': [ + 'error', + { + name: 'isFinite', + message: + 'Use Number.isFinite instead https://github.com/airbnb/javascript#standard-library--isfinite', + }, + { + name: 'isNaN', + message: + 'Use Number.isNaN instead https://github.com/airbnb/javascript#standard-library--isnan', + }, + ...confusingBrowserGlobals, + ], + + // disallow declaration of variables already declared in the outer scope + 'no-shadow': 'error', + + // disallow shadowing of names such as arguments + 'no-shadow-restricted-names': 'error', + + // disallow use of undeclared variables unless mentioned in a /*global */ block + 'no-undef': 'error', + + // disallow use of undefined when initializing variables + 'no-undef-init': 'error', + + // disallow use of undefined variable + // https://eslint.org/docs/rules/no-undefined + // TODO: enable? + 'no-undefined': 'off', + + // disallow declaration of variables that are not used in the code + 'no-unused-vars': [ + 'error', + { vars: 'all', args: 'after-used', ignoreRestSiblings: true }, + ], + + // disallow use of variables before they are defined + 'no-use-before-define': [ + 'error', + { functions: true, classes: true, variables: true }, + ], + }, +}; + +export = config; diff --git a/packages/eslint-config-typescript-react/package.json b/packages/eslint-config-typescript-react/package.json index 78544fd6..8ac2837d 100644 --- a/packages/eslint-config-typescript-react/package.json +++ b/packages/eslint-config-typescript-react/package.json @@ -27,9 +27,9 @@ "@rightcapital/eslint-config-typescript": "workspace:*", "@rightcapital/eslint-plugin": "workspace:*", "@rushstack/eslint-patch": "1.10.3", - "eslint-config-airbnb": "19.0.4", "eslint-config-prettier": "9.1.0", "eslint-plugin-jsx-a11y": "6.7.1", + "eslint-plugin-react": "7.34.3", "eslint-plugin-react-hooks": "4.6.2" }, "devDependencies": { diff --git a/packages/eslint-config-typescript-react/src/index.ts b/packages/eslint-config-typescript-react/src/index.ts index 3d6aec5d..4f9a91ad 100644 --- a/packages/eslint-config-typescript-react/src/index.ts +++ b/packages/eslint-config-typescript-react/src/index.ts @@ -5,8 +5,8 @@ require('@rushstack/eslint-patch/modern-module-resolution'); const config: Linter.Config = { extends: [ require.resolve('@rightcapital/eslint-config-typescript'), - require.resolve('eslint-config-airbnb/hooks'), - require.resolve('eslint-config-airbnb/rules/react-a11y'), + require.resolve('./rules/hooks'), + require.resolve('./rules/react-a11y'), 'plugin:@rightcapital/recommended-react', 'plugin:@eslint-react/recommended-legacy', ], diff --git a/packages/eslint-config-typescript-react/src/rules/hooks.ts b/packages/eslint-config-typescript-react/src/rules/hooks.ts new file mode 100644 index 00000000..6ed33a71 --- /dev/null +++ b/packages/eslint-config-typescript-react/src/rules/hooks.ts @@ -0,0 +1,25 @@ +import type { Linter } from 'eslint'; + +// extracted from eslint-config-airbnb@19.0.4 +// https://github.com/airbnb/javascript/blob/eslint-config-airbnb-v19.0.4/packages/eslint-config-airbnb/rules/react-hooks.js +const config: Linter.Config = { + plugins: ['react-hooks'], + + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + + rules: { + // Enforce Rules of Hooks + // https://github.com/facebook/react/blob/c11015ff4f610ac2924d1fc6d569a17657a404fd/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js + 'react-hooks/rules-of-hooks': 'error', + + // Verify the list of the dependencies for Hooks like useEffect and similar + // https://github.com/facebook/react/blob/1204c789776cb01fbaf3e9f032e7e2ba85a44137/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js + 'react-hooks/exhaustive-deps': 'error', + }, +}; + +export = config; diff --git a/packages/eslint-config-typescript-react/src/rules/react-a11y.ts b/packages/eslint-config-typescript-react/src/rules/react-a11y.ts new file mode 100644 index 00000000..0ab05df1 --- /dev/null +++ b/packages/eslint-config-typescript-react/src/rules/react-a11y.ts @@ -0,0 +1,309 @@ +import type { Linter } from 'eslint'; + +// extracted from eslint-config-airbnb@19.0.4 +// https://github.com/airbnb/javascript/blob/eslint-config-airbnb-v19.0.4/packages/eslint-config-airbnb/rules/react-a11y.js +const config: Linter.Config = { + plugins: ['jsx-a11y', 'react'], + + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + + rules: { + // ensure emoji are accessible + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/accessible-emoji.md + // disabled: rule is deprecated + 'jsx-a11y/accessible-emoji': 'off', + + // Enforce that all elements that require alternative text have meaningful information + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/alt-text.md + 'jsx-a11y/alt-text': [ + 'error', + { + elements: ['img', 'object', 'area', 'input[type="image"]'], + img: [], + object: [], + area: [], + 'input[type="image"]': [], + }, + ], + + // Enforce that anchors have content + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/anchor-has-content.md + 'jsx-a11y/anchor-has-content': ['error', { components: [] }], + + // ensure tags are valid + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/0745af376cdc8686d85a361ce36952b1fb1ccf6e/docs/rules/anchor-is-valid.md + 'jsx-a11y/anchor-is-valid': [ + 'error', + { + components: ['Link'], + specialLink: ['to'], + aspects: ['noHref', 'invalidHref', 'preferButton'], + }, + ], + + // elements with aria-activedescendant must be tabbable + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/aria-activedescendant-has-tabindex.md + 'jsx-a11y/aria-activedescendant-has-tabindex': 'error', + + // Enforce all aria-* props are valid. + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/aria-props.md + 'jsx-a11y/aria-props': 'error', + + // Enforce ARIA state and property values are valid. + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/aria-proptypes.md + 'jsx-a11y/aria-proptypes': 'error', + + // Require ARIA roles to be valid and non-abstract + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/aria-role.md + 'jsx-a11y/aria-role': ['error', { ignoreNonDOM: false }], + + // Enforce that elements that do not support ARIA roles, states, and + // properties do not have those attributes. + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/aria-unsupported-elements.md + 'jsx-a11y/aria-unsupported-elements': 'error', + + // Ensure the autocomplete attribute is correct and suitable for the form field it is used with + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/29c68596b15c4ff0a40daae6d4a2670e36e37d35/docs/rules/autocomplete-valid.md + 'jsx-a11y/autocomplete-valid': [ + 'off', + { + inputComponents: [], + }, + ], + + // require onClick be accompanied by onKeyUp/onKeyDown/onKeyPress + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/click-events-have-key-events.md + 'jsx-a11y/click-events-have-key-events': 'error', + + // Enforce that a control (an interactive element) has a text label. + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/control-has-associated-label.md + 'jsx-a11y/control-has-associated-label': [ + 'error', + { + labelAttributes: ['label'], + controlComponents: [], + ignoreElements: [ + 'audio', + 'canvas', + 'embed', + 'input', + 'textarea', + 'tr', + 'video', + ], + ignoreRoles: [ + 'grid', + 'listbox', + 'menu', + 'menubar', + 'radiogroup', + 'row', + 'tablist', + 'toolbar', + 'tree', + 'treegrid', + ], + depth: 5, + }, + ], + + // ensure tags have content and are not aria-hidden + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/heading-has-content.md + 'jsx-a11y/heading-has-content': ['error', { components: [''] }], + + // require HTML elements to have a "lang" prop + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/html-has-lang.md + 'jsx-a11y/html-has-lang': 'error', + + // ensure iframe elements have a unique title + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/iframe-has-title.md + 'jsx-a11y/iframe-has-title': 'error', + + // Prevent img alt text from containing redundant words like "image", "picture", or "photo" + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/img-redundant-alt.md + 'jsx-a11y/img-redundant-alt': 'error', + + // Elements with an interactive role and interaction handlers must be focusable + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/interactive-supports-focus.md + 'jsx-a11y/interactive-supports-focus': 'error', + + // Enforce that a label tag has a text label and an associated control. + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/b800f40a2a69ad48015ae9226fbe879f946757ed/docs/rules/label-has-associated-control.md + 'jsx-a11y/label-has-associated-control': [ + 'error', + { + labelComponents: [], + labelAttributes: [], + controlComponents: [], + assert: 'both', + depth: 25, + }, + ], + + // require HTML element's lang prop to be valid + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/lang.md + 'jsx-a11y/lang': 'error', + + // media elements must have captions + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/media-has-caption.md + 'jsx-a11y/media-has-caption': [ + 'error', + { + audio: [], + video: [], + track: [], + }, + ], + + // require that mouseover/out come with focus/blur, for keyboard-only users + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/mouse-events-have-key-events.md + 'jsx-a11y/mouse-events-have-key-events': 'error', + + // Prevent use of `accessKey` + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-access-key.md + 'jsx-a11y/no-access-key': 'error', + + // prohibit autoFocus prop + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-autofocus.md + 'jsx-a11y/no-autofocus': ['error', { ignoreNonDOM: true }], + + // prevent distracting elements, like and + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-distracting-elements.md + 'jsx-a11y/no-distracting-elements': [ + 'error', + { + elements: ['marquee', 'blink'], + }, + ], + + // WAI-ARIA roles should not be used to convert an interactive element to non-interactive + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-interactive-element-to-noninteractive-role.md + 'jsx-a11y/no-interactive-element-to-noninteractive-role': [ + 'error', + { + tr: ['none', 'presentation'], + }, + ], + + // A non-interactive element does not support event handlers (mouse and key handlers) + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-noninteractive-element-interactions.md + 'jsx-a11y/no-noninteractive-element-interactions': [ + 'error', + { + handlers: [ + 'onClick', + 'onMouseDown', + 'onMouseUp', + 'onKeyPress', + 'onKeyDown', + 'onKeyUp', + ], + }, + ], + + // WAI-ARIA roles should not be used to convert a non-interactive element to interactive + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-noninteractive-element-to-interactive-role.md + 'jsx-a11y/no-noninteractive-element-to-interactive-role': [ + 'error', + { + ul: [ + 'listbox', + 'menu', + 'menubar', + 'radiogroup', + 'tablist', + 'tree', + 'treegrid', + ], + ol: [ + 'listbox', + 'menu', + 'menubar', + 'radiogroup', + 'tablist', + 'tree', + 'treegrid', + ], + li: ['menuitem', 'option', 'row', 'tab', 'treeitem'], + table: ['grid'], + td: ['gridcell'], + }, + ], + + // Tab key navigation should be limited to elements on the page that can be interacted with. + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-noninteractive-tabindex.md + 'jsx-a11y/no-noninteractive-tabindex': [ + 'error', + { + tags: [], + roles: ['tabpanel'], + }, + ], + + // require onBlur instead of onChange + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-onchange.md + 'jsx-a11y/no-onchange': 'off', + + // ensure HTML elements do not specify redundant ARIA roles + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-redundant-roles.md + 'jsx-a11y/no-redundant-roles': 'error', + + // Enforce that DOM elements without semantic behavior not have interaction handlers + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-static-element-interactions.md + 'jsx-a11y/no-static-element-interactions': [ + 'error', + { + handlers: [ + 'onClick', + 'onMouseDown', + 'onMouseUp', + 'onKeyPress', + 'onKeyDown', + 'onKeyUp', + ], + }, + ], + + // Enforce that elements with ARIA roles must have all required attributes + // for that role. + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/role-has-required-aria-props.md + 'jsx-a11y/role-has-required-aria-props': 'error', + + // Enforce that elements with explicit or implicit roles defined contain + // only aria-* properties supported by that role. + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/role-supports-aria-props.md + 'jsx-a11y/role-supports-aria-props': 'error', + + // only allow to have the "scope" attr + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/scope.md + 'jsx-a11y/scope': 'error', + + // Enforce tabIndex value is not greater than zero. + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/tabindex-no-positive.md + 'jsx-a11y/tabindex-no-positive': 'error', + + // ---------------------------------------------------- + // Rules that no longer exist in eslint-plugin-jsx-a11y + // ---------------------------------------------------- + + // require that JSX labels use "htmlFor" + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/label-has-for.md + // deprecated: replaced by `label-has-associated-control` rule + 'jsx-a11y/label-has-for': [ + 'off', + { + components: [], + required: { + every: ['nesting', 'id'], + }, + allowChildren: false, + }, + ], + }, +}; + +export = config; diff --git a/packages/eslint-plugin/tsconfig.scripts.json b/packages/eslint-plugin/tsconfig.scripts.json index 7d472721..b3b3bf7e 100644 --- a/packages/eslint-plugin/tsconfig.scripts.json +++ b/packages/eslint-plugin/tsconfig.scripts.json @@ -1,7 +1,8 @@ { "extends": "@rightcapital/tsconfig", "compilerOptions": { - "verbatimModuleSyntax": false + "verbatimModuleSyntax": false, + "outDir": "./node_modules/.cache/rightcapital/eslint-plugin-scripts" }, "include": ["*.ts", "*.mts", "*.cts"], "exclude": ["src"] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc875f3e..1b93c94c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -68,12 +68,12 @@ importers: '@rushstack/eslint-patch': specifier: 1.10.3 version: 1.10.3 + confusing-browser-globals: + specifier: 1.0.11 + version: 1.0.11 eslint: specifier: ^8.23.1 version: 8.57.0 - eslint-config-airbnb-base: - specifier: 15.0.0 - version: 15.0.0(eslint-plugin-i@2.29.1(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0))(eslint@8.57.0) eslint-import-resolver-typescript: specifier: 3.6.1 version: 3.6.1(eslint-plugin-i@2.29.1)(eslint@8.57.0) @@ -86,10 +86,19 @@ importers: eslint-plugin-unicorn: specifier: 54.0.0 version: 54.0.0(eslint@8.57.0) + semver: + specifier: 7.6.2 + version: 7.6.2 devDependencies: '@rightcapital/tsconfig': specifier: workspace:* version: link:../tsconfig + '@types/confusing-browser-globals': + specifier: 1.0.3 + version: 1.0.3 + '@types/semver': + specifier: 7.5.8 + version: 7.5.8 packages/eslint-config-javascript: dependencies: @@ -158,15 +167,15 @@ importers: eslint: specifier: ^8.57.0 version: 8.57.0 - eslint-config-airbnb: - specifier: 19.0.4 - version: 19.0.4(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint-plugin-jsx-a11y@6.7.1(eslint@8.57.0))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.0))(eslint-plugin-react@7.34.3(eslint@8.57.0))(eslint@8.57.0) eslint-config-prettier: specifier: 9.1.0 version: 9.1.0(eslint@8.57.0) eslint-plugin-jsx-a11y: specifier: 6.7.1 version: 6.7.1(eslint@8.57.0) + eslint-plugin-react: + specifier: 7.34.3 + version: 7.34.3(eslint@8.57.0) eslint-plugin-react-hooks: specifier: 4.6.2 version: 4.6.2(eslint@8.57.0) @@ -764,6 +773,9 @@ packages: resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} + '@types/confusing-browser-globals@1.0.3': + resolution: {integrity: sha512-q+6axdE3RyjrSsy2ONE4UpF89rwOfpoMBP3lqJ+OzLuOeYHwP+o2GITzuleKb1UT3FSYybO8QmeACgyHleu2CA==} + '@types/conventional-commits-parser@5.0.0': resolution: {integrity: sha512-loB369iXNmAZglwWATL+WRe+CRMmmBPtpolYzIebFaX4YA3x+BEfLqhUAV9WanycKI3TG1IMr5bMJDajDKLlUQ==} @@ -776,9 +788,6 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/json5@0.0.29': - resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/node@20.14.10': resolution: {integrity: sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==} @@ -1047,10 +1056,6 @@ packages: resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} engines: {node: '>= 0.4'} - array.prototype.findlastindex@1.2.5: - resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==} - engines: {node: '>= 0.4'} - array.prototype.flat@1.3.2: resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} engines: {node: '>= 0.4'} @@ -1479,23 +1484,6 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-config-airbnb-base@15.0.0: - resolution: {integrity: sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==} - engines: {node: ^10.12.0 || >=12.0.0} - peerDependencies: - eslint: ^7.32.0 || ^8.2.0 - eslint-plugin-import: ^2.25.2 - - eslint-config-airbnb@19.0.4: - resolution: {integrity: sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==} - engines: {node: ^10.12.0 || ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^7.32.0 || ^8.2.0 - eslint-plugin-import: ^2.25.3 - eslint-plugin-jsx-a11y: ^6.5.1 - eslint-plugin-react: ^7.28.0 - eslint-plugin-react-hooks: ^4.3.0 - eslint-config-prettier@9.1.0: resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} hasBin: true @@ -1552,16 +1540,6 @@ packages: peerDependencies: eslint: ^7.2.0 || ^8 - eslint-plugin-import@2.29.1: - resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint-plugin-jsx-a11y@6.7.1: resolution: {integrity: sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==} engines: {node: '>=4.0'} @@ -2238,10 +2216,6 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - json5@1.0.2: - resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} - hasBin: true - jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} @@ -2464,10 +2438,6 @@ packages: resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} engines: {node: '>= 0.4'} - object.groupby@1.0.3: - resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} - engines: {node: '>= 0.4'} - object.hasown@1.1.4: resolution: {integrity: sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg==} engines: {node: '>= 0.4'} @@ -2894,10 +2864,6 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - strip-bom@4.0.0: resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} engines: {node: '>=8'} @@ -3006,9 +2972,6 @@ packages: ts-pattern@5.2.0: resolution: {integrity: sha512-aGaSpOlDcns7ZoeG/OMftWyQG1KqPVhgplhJxNCvyIXqWrumM5uIoOSarw/hmmi/T1PnuQ/uD8NaFHvLpHicDg==} - tsconfig-paths@3.15.0: - resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} - tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} @@ -3780,6 +3743,8 @@ snapshots: '@sindresorhus/merge-streams@4.0.0': {} + '@types/confusing-browser-globals@1.0.3': {} + '@types/conventional-commits-parser@5.0.0': dependencies: '@types/node': 20.14.10 @@ -3793,8 +3758,6 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/json5@0.0.29': {} - '@types/node@20.14.10': dependencies: undici-types: 5.26.5 @@ -4210,15 +4173,6 @@ snapshots: es-object-atoms: 1.0.0 es-shim-unscopables: 1.0.2 - array.prototype.findlastindex@1.2.5: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-errors: 1.3.0 - es-object-atoms: 1.0.0 - es-shim-unscopables: 1.0.2 - array.prototype.flat@1.3.2: dependencies: call-bind: 1.0.7 @@ -4784,35 +4738,6 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-airbnb-base@15.0.0(eslint-plugin-i@2.29.1(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0))(eslint@8.57.0): - dependencies: - confusing-browser-globals: 1.0.11 - eslint: 8.57.0 - eslint-plugin-import: eslint-plugin-i@2.29.1(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) - object.assign: 4.1.5 - object.entries: 1.1.8 - semver: 6.3.1 - - eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0): - dependencies: - confusing-browser-globals: 1.0.11 - eslint: 8.57.0 - eslint-plugin-import: 2.29.1(eslint@8.57.0) - object.assign: 4.1.5 - object.entries: 1.1.8 - semver: 6.3.1 - - eslint-config-airbnb@19.0.4(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint-plugin-jsx-a11y@6.7.1(eslint@8.57.0))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.0))(eslint-plugin-react@7.34.3(eslint@8.57.0))(eslint@8.57.0): - dependencies: - eslint: 8.57.0 - eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(eslint@8.57.0) - eslint-plugin-jsx-a11y: 6.7.1(eslint@8.57.0) - eslint-plugin-react: 7.34.3(eslint@8.57.0) - eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) - object.assign: 4.1.5 - object.entries: 1.1.8 - eslint-config-prettier@9.1.0(eslint@8.57.0): dependencies: eslint: 8.57.0 @@ -4894,31 +4819,6 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.29.1(eslint@8.57.0): - dependencies: - array-includes: 3.1.8 - array.prototype.findlastindex: 1.2.5 - array.prototype.flat: 1.3.2 - array.prototype.flatmap: 1.3.2 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 8.57.0 - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(eslint-plugin-i@2.29.1)(eslint@8.57.0))(eslint@8.57.0) - hasown: 2.0.2 - is-core-module: 2.14.0 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.0 - semver: 6.3.1 - tsconfig-paths: 3.15.0 - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - eslint-plugin-jsx-a11y@6.7.1(eslint@8.57.0): dependencies: '@babel/runtime': 7.24.7 @@ -5732,10 +5632,6 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} - json5@1.0.2: - dependencies: - minimist: 1.2.8 - jsonfile@6.1.0: dependencies: universalify: 2.0.1 @@ -5944,12 +5840,6 @@ snapshots: es-abstract: 1.23.3 es-object-atoms: 1.0.0 - object.groupby@1.0.3: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - object.hasown@1.1.4: dependencies: define-properties: 1.2.1 @@ -6409,8 +6299,6 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-bom@3.0.0: {} - strip-bom@4.0.0: {} strip-final-newline@2.0.0: {} @@ -6494,13 +6382,6 @@ snapshots: ts-pattern@5.2.0: {} - tsconfig-paths@3.15.0: - dependencies: - '@types/json5': 0.0.29 - json5: 1.0.2 - minimist: 1.2.8 - strip-bom: 3.0.0 - tslib@1.14.1: {} tslib@2.6.3: {} diff --git a/specs/eslint/README.md b/specs/eslint/README.md index 916bbe40..a8c8abce 100644 --- a/specs/eslint/README.md +++ b/specs/eslint/README.md @@ -8,7 +8,7 @@ We made snapshot tests for the `@rightcapital/eslint-config-*` packages to make ## Dealing with dependency upgrades -When upgrading dependencies, snapshot tests may fail because of the changes of the configs. e.g. `eslint-config-airbnb-base` changed some rules in a new version. +When upgrading dependencies, snapshot tests may fail because of the changes of the configs. e.g. `plugin:@typescript-eslint/recommended-type-checked` changed some rules in a new version. In this case: @@ -16,7 +16,7 @@ In this case: 2. Check if the changes are expected: - 1. If yes, update the snapshot by running(the `-u` flag is Jest's [updateSnapshot](https://jestjs.io/docs/cli#--updatesnapshot) flag): + 1. If yes, update the snapshot by running(the `-u` flag is Vitests's [snapshot updating](https://vitest.dev/guide/snapshot.html#updating-snapshots) flag): ```sh pnpm test -- -u diff --git a/tsconfig.scripts.json b/tsconfig.scripts.json index 04dcc61f..7320d84c 100644 --- a/tsconfig.scripts.json +++ b/tsconfig.scripts.json @@ -10,7 +10,9 @@ "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, - "moduleResolution": "node16" + "moduleResolution": "node16", + + "outDir": "./node_modules/.cache/rightcapital/frontend-style-guide-scripts" }, "include": ["beachball.config.ts"] }