-
Notifications
You must be signed in to change notification settings - Fork 1
/
eslint.config.js
283 lines (268 loc) · 12.1 KB
/
eslint.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
import configPrettier from 'eslint-config-prettier';
import configXO from 'eslint-config-xo';
import pluginImport from 'eslint-plugin-import';
import pluginJSDoc from 'eslint-plugin-jsdoc';
import pluginNode from 'eslint-plugin-n';
import pluginPromise from 'eslint-plugin-promise';
import pluginUnicorn from 'eslint-plugin-unicorn';
import globals from 'globals';
import pluginTypeScript from 'typescript-eslint';
import configStandard from './src/eslint-standard-config.js';
import configStylistic from './src/eslint-stylistic-config.js';
import rulePreferEarlyReturn from './src/rules/prefer-early-return/index.js';
// Create a plugin containing our custom rules
const pluginCloudFour = {
rules: {
'prefer-early-return': rulePreferEarlyReturn,
},
};
export default [
// Plugins' recommended configs
pluginNode.configs['flat/recommended'],
pluginJSDoc.configs['flat/recommended-error'],
pluginUnicorn.configs['flat/recommended'],
// "Standards"
...configXO,
configStandard,
// Our settings
{
languageOptions: {
ecmaVersion: 2022,
sourceType: 'module',
parserOptions: {
ecmaFeatures: { jsx: true },
},
globals: {
...globals.es2022,
...globals.node,
document: 'readonly',
navigator: 'readonly',
window: 'readonly',
},
},
plugins: {
import: pluginImport,
promise: pluginPromise,
'@cloudfour': pluginCloudFour,
},
settings: {
jsdoc: {
mode: 'typescript',
tagNamePreference: {
TODO: 'todo',
},
preferredTypes: {
'*': 'any',
Function: '() => void',
function: '() => void',
},
},
},
// Override rules from recommended configs
rules: {
'@cloudfour/prefer-early-return': 'error',
'no-unused-expressions': [
'error',
{
allowShortCircuit: false,
allowTernary: false,
allowTaggedTemplates: false,
},
],
'no-return-assign': ['error'],
'func-names': 'off',
'prefer-const': [
'error',
// If there is a destructuring assignment
// and some of the properties should be const
// but others shouldn't be, let it use let
{ destructuring: 'all' },
],
'no-var': 'error',
'object-shorthand': 'error',
'prefer-object-spread': 'error',
'prefer-spread': 'error',
'prefer-destructuring': ['error', { array: false }],
'prefer-rest-params': 'error',
// We decided that since devs can use blank lines to create logical groupings in code,
// it is best not to have ESLint enforce adding newlines
'padding-line-between-statements': 'off',
'prefer-template': 'error',
'no-param-reassign': 'off', // We don't use `arguments`, and assigning to parameters can be useful
'no-promise-executor-return': 'off', // Allow implicit return in promise executor
'capitalized-comments': [
'error',
'always',
{
ignorePattern: String.raw`pragma|ignore|prettier-ignore|webpack\w+:|c8|return|const|let|var|await|function|console`,
ignoreInlineComments: true,
ignoreConsecutiveComments: true,
},
],
'n/no-unsupported-features/es-syntax': 'off', // Does not account for transpilation
'n/no-unpublished-require': 'off', // Does not account for "build" scripts
'n/shebang': 'off', // Tons of false positives
'n/file-extension-in-import': ['error', 'always'], // Don't allow extension-less relative imports (e.g. use ./foo.js instead of ./foo)
// Used for sorting/grouping import statements
'import/order': [
'error',
{
groups: [
'builtin',
'external',
'internal',
'parent',
'sibling',
'index',
],
'newlines-between': 'always',
alphabetize: { order: 'asc', caseInsensitive: true },
},
],
// Avoid multiple import statements in the same file for the same module
// prefer-inline means it is preferred to use inline `type` imports combined with non-types
// instead of separate imports for types and non-types
// e.g. import { Foo, type Bar } from 'something' is preferred over having separate import statements
'import/no-duplicates': ['error', { 'prefer-inline': true }],
// Used for sorting members within an import statement alphabetically
'sort-imports': ['error', { ignoreDeclarationSort: true }],
'unicorn/import-style': 'off', // It doesn't seem useful to force people to use named, default, or namespace imports
'unicorn/prevent-abbreviations': 'off', // Causes more issues than it's worth
// Null is ok, even though Sindre Sorhus doesn't like it
// It is ok to avoid using null and use undefined instead
// but enforcing it in all code via a lint rule is too annoying
'unicorn/no-null': 'off',
// This rule is meant to avoid the edge case of breaking changes occuring
// due to the `index` parameter being passed unexpectedly into the callback function,
// causing unexpected behavior if the callback expects something that is not the index
// But this is an edge case that can be avoided through careful manual review
// and sometimes through TS
'unicorn/no-array-callback-reference': 'off',
// https://github.com/sindresorhus/eslint-plugin-unicorn/pull/1750
// This rule got removed from the recommended preset, but that hasn't been published yet.
'unicorn/prefer-json-parse-buffer': 'off',
// This rule changes arrays to sets if you call .includes on it
// Converting from array to set has a cost itself, just like .includes has a cost
// We decided to leave the decision of using arrays vs sets to human reviewers
'unicorn/prefer-set-has': 'off',
// Reduce is often useful. Don't need a lint rule to tell us not to use it
'unicorn/no-array-reduce': 'off',
'unicorn/prefer-module': 'off', // A lot of projects still use commonjs by default for non-browser code. We can revisit this rule once commonjs is basically never used.
'unicorn/prefer-switch': 'off', // Switch statements are often longer than if/else chains, and they are still read aloud as "if ... is ... then"
'unicorn/prefer-number-properties': [
'error',
// There isn't a good reason to force use of Number.POSITIVE_INFINITY instead of Infinity
{ checkInfinity: false },
],
// String#replaceAll doesn't quite have enough browser/node support to enable this rule by default.
// TODO [2025-01-01] Reconsider browser/node support for those two methods
'unicorn/prefer-string-replace-all': 'off',
// String#at and Array#at don't quite have enough browser/node support to enable this rule by default.
// TODO [2025-01-01] Reconsider browser/node support for those two methods
'unicorn/prefer-at': 'off',
// This rule suggests incorrect code with the destructured object is modified
// That is a fairly common case, and it is too annoying to always disable the rule on each line
'unicorn/consistent-destructuring': 'off',
// Allow _only_ TODO comments with expirations/conditions
'no-warning-comments': 'off',
'unicorn/expiring-todo-comments': [
'error',
{ allowWarningComments: false },
],
// Disabling jsdoc rules that check the types themselves
// If you want to have type checking on a project, use typescript instead
'jsdoc/newline-after-description': 'off',
'jsdoc/no-undefined-types': 'off',
'jsdoc/valid-types': 'off',
'jsdoc/require-returns': 'off',
'jsdoc/require-param-description': 'off',
'jsdoc/require-property-description': 'off',
'jsdoc/require-returns-description': 'off',
'jsdoc/require-jsdoc': 'off',
'jsdoc/require-returns-check': 'off', // Does not handle @returns with void or undefined
'jsdoc/tag-lines': ['error', 'any', { startLines: 1 }],
},
},
// Load TypeScript ESLint recommended config for TS files only
// @see https://eslint.org/docs/latest/use/configure/combine-configs#apply-a-config-array-to-a-subset-of-files
...pluginTypeScript.configs.recommendedTypeChecked.map((config) => ({
...config,
files: ['**/*.{ts,tsx,mts,cts}'],
})),
// Override recommended rules for TS files only
{
files: ['**/*.{ts,tsx,mts,cts}'],
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
// TS handles checking these
'n/no-missing-import': 'off',
'n/no-missing-require': 'off',
'no-import-assign': 'off', // TS handles this
// With TS, the only reason to have a @param tag
// is if a particular parameter needs a description,
// which is not true for all parameters
'jsdoc/require-param': 'off',
'jsdoc/require-param-type': 'off', // Types should be in type annotations instead
'jsdoc/require-param-description': 'error', // The only reason to have an @param in TS is to add a description
'jsdoc/require-returns-type': 'off', // Return types should be in type annotations instead
'jsdoc/require-returns-description': 'error', // The only reason to have an @returns in TS is to add a description
// Auto-fixes type imports to use the `import type` syntax
// This syntax is preferred because it makes the TS -> JS transformation easier
// because it doesn't require checking which imports are only referenced as types
'@typescript-eslint/consistent-type-imports': [
'error',
// We have set it to allow import('...') for types because that is the only kind of import that is allowed in global type augmentations
{ disallowTypeAnnotations: false },
],
// Don't try to use the result of expression whose type is `void`
'@typescript-eslint/no-confusing-void-expression': [
'error',
{ ignoreArrowShorthand: true },
],
// Don't use the void operator an an expression whose type is already `void`
'@typescript-eslint/no-meaningless-void-operator': 'error',
'@typescript-eslint/no-unnecessary-type-constraint': 'error',
'@typescript-eslint/array-type': ['error', { default: 'array' }], // Require consistency: Use foo[] instead of Array<foo>
'@typescript-eslint/ban-ts-comment': 'error',
'@typescript-eslint/explicit-module-boundary-types': 'off', // Type inference is useful even for public functions
'@typescript-eslint/no-explicit-any': 'off', // Any is an escape hatch, it should be allowed
'@typescript-eslint/no-floating-promises': 'off', // Don't force every promise rejection to be caught. Humans can decide when it makes sense to handle errors and when it doesn't
'@typescript-eslint/no-non-null-assertion': 'error', // Default is warn
'@typescript-eslint/no-unsafe-assignment': 'off', // Any is an escape hatch, let it be an escape hatch
'@typescript-eslint/no-unsafe-call': 'off', // Any is an escape hatch, let it be an escape hatch
'@typescript-eslint/no-unsafe-member-access': 'off', // Any is an escape hatch, let it be an escape hatch
'@typescript-eslint/no-unsafe-return': 'off', // Any is an escape hatch, let it be an escape hatch
'@typescript-eslint/no-unsafe-argument': 'off', // Any is an escape hatch, let it be an escape hatch
'@typescript-eslint/restrict-template-expressions': 'off', // Allow using any-typed-values in template expressions
'@typescript-eslint/no-unnecessary-condition': 'error', // This catches a lot of dead code that TS itself doesn't flag
'@typescript-eslint/no-unnecessary-boolean-literal-compare': 'error',
'@typescript-eslint/prefer-optional-chain': 'error', // More readable syntax
'no-unused-vars': 'off', // TS checks this via noUnusedLocals / noUnusedParameters
'@typescript-eslint/no-unused-vars': 'off', // TS checks this via noUnusedLocals / noUnusedParameters
'@typescript-eslint/no-empty-function': 'off', // Non-TS version of rule is not used either
'@typescript-eslint/unbound-method': 'off', // It is pretty common for this already being handled outside of what TS/ESLint can be aware of
'@typescript-eslint/no-import-type-side-effects': 'error',
'no-unused-expressions': 'off',
'@typescript-eslint/no-unused-expressions': ['error'], // This rule is like the built in ESLint rule but it supports optional chaining
// Replacing the built-in rule with the version that works well with TS
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': [
'error',
{
functions: false,
classes: false,
variables: false,
ignoreTypeReferences: true,
},
],
},
},
// Disable stylistic rules
configStylistic,
configPrettier,
];