-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy patheslint.config.mjs
More file actions
306 lines (297 loc) · 15.3 KB
/
eslint.config.mjs
File metadata and controls
306 lines (297 loc) · 15.3 KB
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
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
import { fixupPluginRules } from "@eslint/compat";
import { defineConfig } from "eslint/config";
import perfectionist from "eslint-plugin-perfectionist";
import { Alphabet } from "eslint-plugin-perfectionist/alphabet";
import jsdoc from "eslint-plugin-jsdoc";
import sortKeys from "eslint-plugin-sort-keys";
import unicorn from "eslint-plugin-unicorn";
import tseslint from "typescript-eslint";
const config = {
files: ["**/*.{js,jsx,mjs,ts,tsx}"],
languageOptions: { parser: tseslint.parser },
linterOptions: {
// Individual repos may have their own additional ESLint setups that enable
// more rules than we do here, which means that we can't reliably determine
// which directives are actually unused
reportUnusedDisableDirectives: "off",
},
// TODO: Add more plugins: react, react-hooks. Ideally we could also add
// prefer-arrow, but it only autofixes single-line functions :/ Most popular
// plugins: https://www.npmjs.com/search?q=keywords%3Aeslint-plugin
plugins: {
"@typescript-eslint": tseslint.plugin,
jsdoc,
perfectionist,
"sort-keys": fixupPluginRules(sortKeys),
unicorn,
},
//
// PLEASE READ BEFORE EDITING THESE RULES!
//
// We must keep these rules compatible with Duolingo's internal ESLint config
// (it could be nice to someday update our internal config to inherit from
// this one). Every rule that we enable here must be autofixable and worth
// its performance cost, e.g. we should disable complex rules that protect
// against rare problems in our codebase.
//
// Unless stated otherwise, we include disabled rules (commented out) in this
// file to record that we're willfully disabling them, a practice that
// simplifies future ESLint upgrades: we can more easily identify and
// evaluate only newly available rules instead of needing to also revisit
// existing rules that were omitted from this file.
//
// The rule declarations below are organized into sections based on the
// plugins that provide them. These sections are sorted alphabetically by
// plugin name.
rules: {
/* eslint-disable -- Group each plugin's rules together */
// Native ESLint rules. Get the list of autofixable rules by running the
// snippet below in the browser console at
// https://eslint.org/docs/latest/rules/
//
// copy([...$$("p.rule__categories__type:nth-child(3):not([aria-hidden=true])")].map(p=>p.closest("article.rule").querySelector("a.rule__name")?.textContent).filter(x=>x).sort().join("\n"))
"arrow-body-style": ["error", "as-needed"],
// "capitalized-comments": "error",
curly: ["error", "all"],
// "dot-notation": "error",
eqeqeq: ["error", "always"],
// "logical-assignment-operators": "error",
// "no-div-regex": "error",
"no-else-return": "error",
// "no-extra-bind": "error",
"no-extra-boolean-cast": ["error", { enforceForInnerExpressions: false }],
"no-extra-label": "error",
// "no-implicit-coercion": "error",
"no-lonely-if": "error",
"no-regex-spaces": "error",
"no-undef-init": "error",
// "no-unneeded-ternary": "error", // We defensively prefer ternary with null for JSX nodes. Also, this rule creates `||` which conflicts with `@typescript-eslint/prefer-nullish-coalescing` which we currently can't/don't autofix because it needs type info
"no-unused-labels": "error",
"no-useless-computed-key": ["error", { enforceForClassMembers: true }],
"no-useless-rename": "error",
// "no-useless-return": "error", // Doesn't play well with `no-empty` rule
"no-var": "error",
"object-shorthand": ["error", "always"],
"one-var": ["error", "never"],
"operator-assignment": ["error", "always"],
"prefer-arrow-callback": "error",
"prefer-const": "error",
// "prefer-destructuring": "error",
"prefer-exponentiation-operator": "error",
"prefer-numeric-literals": "error",
// "prefer-object-has-own": "error",
// "prefer-object-spread": "error",
"prefer-template": "error",
// This only sorts members within an individual import statement, not import
// statements ("declarations") themselves. We disable that because this
// rule sorts in a weird way: by first member rather than by module name.
// The `import/order` rule provided by eslint-plugin-import does sort
// declarations by module name, but we forgo that too because it groups
// declarations based on environmental factors (e.g. node_modules, Node
// version) that we can't easily determine or reproduce here in a
// repo-agnostic way. One compromise might be to use `import/order` and
// simply disable its regrouping feature in favor of whatever groups are
// found in the source code to be formatted, but no such option exists :/
// "sort-imports": ["error", { ignoreDeclarationSort: true }], // Replaced by perfectionist/sort-named-imports
"sort-vars": "error",
// "strict": "error",
// "unicode-bom": "error",
yoda: ["error", "never"],
// JSDoc rules. All autofixable rules:
// https://github.com/gajus/eslint-plugin-jsdoc/tree/main?tab=readme-ov-file#user-content-eslint-plugin-jsdoc-rules
"jsdoc/check-alignment": "error",
// "jsdoc/check-line-alignment": "error",
"jsdoc/check-param-names": "error",
"jsdoc/check-property-names": "error",
"jsdoc/check-tag-names": "error",
// "jsdoc/check-types": "error",
"jsdoc/empty-tags": "error",
// "jsdoc/match-name": "error",
"jsdoc/multiline-blocks": [
"error",
{
// This should be long enough to account for all but the most deeply
// nested/indented JSDoc blocks while minimizing false negatives that
// could fit on a single line without exceeding the line length limit
minimumLengthForMultiline: 60,
noMultilineBlocks: true,
},
],
// "jsdoc/no-bad-blocks": "error",
// "jsdoc/no-blank-block-descriptions": "error",
// "jsdoc/no-blank-blocks": "error",
// "jsdoc/no-defaults": "error",
// "jsdoc/no-multi-asterisks": "error", // Bug: fixer deletes Markdown bullets
"jsdoc/no-types": "error",
"jsdoc/require-asterisk-prefix": "error",
// "jsdoc/require-description-complete-sentence": "error",
// "jsdoc/require-example": "error",
// "jsdoc/require-hyphen-before-param-description": "error",
// "jsdoc/require-jsdoc": "error",
// "jsdoc/require-param": "error",
// "jsdoc/require-property": "error",
// "jsdoc/tag-lines": "error",
// "jsdoc/text-escaping": "error",
// perfectionist rules. https://perfectionist.dev/rules
// "perfectionist/sort-enums" // Reordering can change numeric enum values
// "perfectionist/sort-heritage-clauses" // Not worth the churn when interfaces are involved
// "perfectionist/sort-imports" // TODO: Enable once grouping is more configurable
"perfectionist/sort-interfaces": "error",
// "perfectionist/sort-intersection-types" // Not worth the churn when interfaces are involved
"perfectionist/sort-named-exports": "error",
"perfectionist/sort-named-imports": "error",
"perfectionist/sort-object-types": "error",
// "perfectionist/sort-objects // Prefer sort-keys because it leaves computed properties alone
// "perfectionist/sort-switch-case" // TODO: Enable once it supports partitionByNewLine
// "perfectionist/sort-union-types" // Not worth the churn when interfaces are involved
// sort-keys rules. https://github.com/namnm/eslint-plugin-sort-keys
"sort-keys/sort-keys-fix": ["error", "asc", { natural: true }],
// typescript-eslint rules. We exclude rules that require type info because
// they're prohibitively slow. Get the list of autofixable rules by running
// the snippet below in the browser console at
// https://typescript-eslint.io/rules/?=xdeprecated-fixable-xtypeInformation
//
// copy([...$$("table td:first-child a code")].map(c=>c.textContent).sort().join("\n"))
"@typescript-eslint/array-type": ["error", { default: "array" }],
"@typescript-eslint/ban-tslint-comment": "error",
// "@typescript-eslint/consistent-generic-constructors": "error",
"@typescript-eslint/consistent-indexed-object-style": ["error", "record"],
"@typescript-eslint/consistent-type-assertions": [
"error",
{
arrayLiteralTypeAssertions: "allow-as-parameter",
assertionStyle: "as",
objectLiteralTypeAssertions: "allow-as-parameter",
},
],
// "@typescript-eslint/consistent-type-definitions": ["error", "interface"], // Can cause `Index signature for type 'string' is missing in type`
// "@typescript-eslint/consistent-type-imports": ["error", { disallowTypeAnnotations: false, prefer: "type-imports" }], // TODO: Enable once all our code is on TS 3.8+
// "@typescript-eslint/explicit-member-accessibility" // Buggy? Doesn't actually add `public` when absent
// "@typescript-eslint/method-signature-style": "error",
"@typescript-eslint/no-array-constructor": "error",
// "@typescript-eslint/no-dynamic-delete": "error",
// "@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-extra-non-null-assertion": "error",
// "@typescript-eslint/no-import-type-side-effects": "error",
"@typescript-eslint/no-inferrable-types": "error",
// "@typescript-eslint/no-restricted-types": "error",
"@typescript-eslint/no-useless-empty-export": "error",
// "@typescript-eslint/no-wrapper-object-types": "error",
"@typescript-eslint/prefer-as-const": "error",
"@typescript-eslint/prefer-function-type": "error",
"@typescript-eslint/prefer-namespace-keyword": "error",
// eslint-plugin-unicorn rules. https://github.com/sindresorhus/eslint-plugin-unicorn?tab=readme-ov-file#rules
// "unicorn/better-regex": "error",
"unicorn/catch-error-name": ["error", { name: "ex" }],
"unicorn/consistent-destructuring": "error",
"unicorn/consistent-empty-array-spread": "error",
// "unicorn/consistent-existence-index-check": "error",
// "unicorn/consistent-template-literal-escape": "error",
// "unicorn/custom-error-definition": "error",
// "unicorn/empty-brace-spaces": "error",
// "unicorn/escape-case": "error", // Implemented by Prettier
// "unicorn/explicit-length-check": "error",
// "unicorn/new-for-builtins": "error",
// "unicorn/no-array-for-each": "error", // Bug: fixer deletes comments
// "unicorn/no-array-method-this-argument": "error",
// "unicorn/no-await-expression-member": "error",
"unicorn/no-console-spaces": "error",
"unicorn/no-for-loop": "error",
// "unicorn/no-hex-escape": "error",
// "unicorn/no-lonely-if": "error", // Bug: Moves comments around
"unicorn/no-negated-condition": "error",
// "unicorn/no-nested-ternary": "error",
// "unicorn/no-new-array": "error",
"unicorn/no-new-buffer": "error",
// "unicorn/no-null": "error",
"unicorn/no-single-promise-in-promise-methods": "error",
// "unicorn/no-static-only-class": "error",
"unicorn/no-typeof-undefined": "error",
// "unicorn/no-unnecessary-array-splice-count": "error",
"unicorn/no-unnecessary-await": "error",
"unicorn/no-unreadable-array-destructuring": "error",
"unicorn/no-useless-fallback-in-spread": "error",
// "unicorn/no-useless-iterator-to-array": "error",
// "unicorn/no-useless-length-check": "error",
// "unicorn/no-useless-promise-resolve-reject": "error", // Conflicts with @typescript-eslint/no-throw-literal
"unicorn/no-useless-spread": "error",
// "unicorn/no-useless-undefined": "error", // Doesn't play well with TS
"unicorn/no-zero-fractions": "error",
// "unicorn/number-literal-case": "error", // Implemented by Prettier
// "unicorn/numeric-separators-style": "error",
// "unicorn/prefer-add-event-listener": "error",
// "unicorn/prefer-array-find": "error", // Doesn't play well with TS, which types `filter(...)[0]` as non-undefined
// "unicorn/prefer-array-flat": "error",
// "unicorn/prefer-array-flat-map": "error", // Doesn't play well with TS
"unicorn/prefer-array-index-of": "error",
"unicorn/prefer-array-some": "error",
// "unicorn/prefer-at": "error",
"unicorn/prefer-date-now": "error",
// "unicorn/prefer-default-parameters": "error",
// "unicorn/prefer-dom-node-append": "error",
"unicorn/prefer-dom-node-dataset": "error",
// "unicorn/prefer-dom-node-remove": "error",
"unicorn/prefer-export-from": "error",
// "unicorn/prefer-global-this": "error",
// "unicorn/prefer-import-meta-properties": "error",
// "unicorn/prefer-includes": "error", // Bug: `includes` fails on `as const` strings
// "unicorn/prefer-json-parse-buffer": "error",
// "unicorn/prefer-keyboard-event-key": "error",
"unicorn/prefer-math-min-max": "error",
// "unicorn/prefer-math-trunc": "error",
// "unicorn/prefer-modern-dom-apis": "error",
// "unicorn/prefer-modern-math-apis": "error",
// "unicorn/prefer-module": "error",
// "unicorn/prefer-native-coercion-functions": "error",
"unicorn/prefer-negative-index": "error",
// "unicorn/prefer-node-protocol": "error",
// "unicorn/prefer-number-properties": "error",
// "unicorn/prefer-object-from-entries": "error", // Bug: Doesn't update TS generic from reduce<Record<a,b>> to map<[a,b]>
"unicorn/prefer-optional-catch-binding": "error",
// "unicorn/prefer-prototype-methods": "error",
"unicorn/prefer-query-selector": "error",
// "unicorn/prefer-reflect-apply": "error",
"unicorn/prefer-regexp-test": "error",
// "unicorn/prefer-set-has": "error",
"unicorn/prefer-set-size": "error",
// "unicorn/prefer-simple-condition-first": "error",
// "unicorn/prefer-single-call": "error", // Bug: fixer deletes comments
// "unicorn/prefer-spread": "error", // Bug: https://github.com/sindresorhus/eslint-plugin-unicorn/issues/2041
// "unicorn/prefer-string-raw": "error",
// "unicorn/prefer-string-replace-all": "error",
"unicorn/prefer-string-slice": "error",
"unicorn/prefer-string-starts-ends-with": "error",
"unicorn/prefer-string-trim-start-end": "error",
// "unicorn/prefer-switch": "error",
// "unicorn/prefer-ternary": "error", // Usually requires manual conversion of let to const
// "unicorn/prefer-type-error": "error",
// "unicorn/prevent-abbreviations": "error",
// "unicorn/relative-url-style": "error",
// "unicorn/require-array-join-separator": "error",
// "unicorn/require-number-to-fixed-digits-argument": "error",
// "unicorn/string-content": "error",
// "unicorn/switch-case-braces": "error",
// "unicorn/switch-case-break-position": "error",
// "unicorn/template-indent": "error",
"unicorn/text-encoding-identifier-case": "error",
// "unicorn/throw-new-error": "error",
/* eslint-enable */
},
settings: {
perfectionist: {
// By default, perfectionist sorts lowercase before uppercase. We reverse
// that behavior here to match our existing pre-2026 convention, which is
// also arguably more sensible from a coding perspective because it
// prioritizes SCREAMING_SNAKE_CASE constants, similar to how such
// constants are typically defined first in a source file
alphabet: Alphabet.generateRecommendedAlphabet()
.placeAllWithCaseBeforeAllWithOtherCase("uppercase")
.getCharacters(),
ignoreCase: false,
order: "asc",
partitionByNewLine: true,
type: "custom",
},
},
};
export default defineConfig([config]);