-
Notifications
You must be signed in to change notification settings - Fork 1
/
eslint.config.js
280 lines (252 loc) · 8.52 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
import globals from 'globals'
const ERROR = 'error'
const WARN = 'warn'
const has = pkg => {
try {
import.meta.resolve(pkg, import.meta.url)
return true
} catch {
return false
}
}
const hasTypeScript = has('typescript')
const hasReact = has('react')
const hasTestingLibrary = has('@testing-library/dom')
const hasJestDom = has('@testing-library/jest-dom')
const hasVitest = has('vitest')
const vitestFiles = ['**/__tests__/**/*', '**/*.test.*', '**/*.spec.*']
const testFiles = ['**/tests/**', '**/#tests/**', ...vitestFiles]
const playwrightFiles = ['**/e2e/**']
export const config = [
{
ignores: [
'**/.cache/**',
'**/node_modules/**',
'**/build/**',
'**/public/build/**',
'**/playwright-report/**',
'**/server-build/**',
'**/dist/**',
'**/coverage/**',
'.test.env'
]
},
// all files
{
plugins: {
import: (await import('eslint-plugin-import-x')).default
},
languageOptions: {
globals: {
...globals.browser,
...globals.node
}
},
rules: {
'no-unexpected-multiline': ERROR,
'no-warning-comments': [ERROR, {terms: ['FIXME'], location: 'anywhere'}],
'import/no-duplicates': [WARN, {'prefer-inline': true}]
}
},
// JSX/TSX files
hasReact
? {
files: ['**/*.tsx', '**/*.jsx'],
plugins: {
react: (await import('eslint-plugin-react')).default
},
languageOptions: {
parser: (await import('typescript-eslint')).parser,
parserOptions: {
jsx: true
}
},
rules: {
'react/jsx-key': WARN
}
}
: null,
// react-hook rules are applicable in ts/js/tsx/jsx, but only with React as a
// dep
hasReact
? {
files: ['**/*.ts?(x)', '**/*.js?(x)'],
plugins: {
'react-hooks': (await import('eslint-plugin-react-hooks')).default
},
rules: {
'react-hooks/rules-of-hooks': ERROR,
'react-hooks/exhaustive-deps': WARN
}
}
: null,
// JS and JSX files
{
files: ['**/*.js?(x)'],
rules: {
// most of these rules are useful for JS but not TS because TS handles these better
// if it weren't for https://github.com/import-js/eslint-plugin-import/issues/2132
// we could enable this :(
// 'import/no-unresolved': ERROR,
'no-unused-vars': [
WARN,
{
args: 'after-used',
argsIgnorePattern: '^_',
ignoreRestSiblings: true,
varsIgnorePattern: '^ignored'
}
]
}
},
// TS and TSX files
hasTypeScript
? {
files: ['**/*.ts?(x)'],
languageOptions: {
parser: (await import('typescript-eslint')).parser,
parserOptions: {
projectService: true
}
},
plugins: {
'@typescript-eslint': (await import('typescript-eslint')).plugin
},
rules: {
'@typescript-eslint/no-unused-vars': [
WARN,
{
args: 'after-used',
argsIgnorePattern: '^_',
ignoreRestSiblings: true,
varsIgnorePattern: '^ignored'
}
],
'import/consistent-type-specifier-style': [WARN, 'prefer-inline'],
'@typescript-eslint/consistent-type-imports': [
WARN,
{
prefer: 'type-imports',
disallowTypeAnnotations: true,
fixStyle: 'inline-type-imports'
}
],
'@typescript-eslint/no-misused-promises': [
'error',
{checksVoidReturn: false}
],
'@typescript-eslint/no-floating-promises': 'error'
// here are rules we've decided to not enable. Commented out rather
// than setting them to disabled to avoid them being referenced at all
// when config resolution happens.
// @typescript-eslint/require-await - sometimes you really do want
// async without await to make a function async. TypeScript will ensure
// it's treated as an async function by consumers and that's enough for me.
// @typescript-eslint/prefer-promise-reject-errors - sometimes you
// aren't the one creating the error, and you just want to propagate an
// error object with an unknown type.
// @typescript-eslint/only-throw-error - same reason as above.
// However, this rule supports options to allow you to throw `any` and
// `unknown`. Unfortunately, in Remix you can throw Response objects,
// and we don't want to enable this rule for those cases.
// @typescript-eslint/no-unsafe-declaration-merging - this is a rare
// enough problem (especially if you focus on types over interfaces)
// that it's not worth enabling.
// @typescript-eslint/no-unsafe-enum-comparison - enums are not
// recommended or used in epic projects, so it's not worth enabling.
// @typescript-eslint/no-unsafe-unary-minus - this is a rare enough
// problem that it's not worth enabling.
// @typescript-eslint/no-base-to-string - this doesn't handle when
// your object actually does implement toString unless you do so with
// a class which is not 100% of the time. For example, the timings
// object in the epic stack uses defineProperty to implement toString.
// It's not high enough risk/impact to enable.
// @typescript-eslint/no-non-null-assertion - normally you should not
// use ! to tell TS to ignore the null case, but you're a responsible
// adult and if you're going to do that, the linter shouldn't yell at
// you about it.
// @typescript-eslint/restrict-template-expressions - toString is a
// feature of many built-in objects and custom ones. It's not worth
// enabling.
// @typescript-eslint/no-confusing-void-expression - what's confusing
// to one person isn't necessarily confusing to others. Arrow
// functions that call something that returns void is not confusing
// and the types will make sure you don't mess something up.
// these each protect you from `any` and while it's best to avoid
// using `any`, it's not worth having a lint rule yell at you when you
// do:
// - @typescript-eslint/no-unsafe-argument
// - @typescript-eslint/no-unsafe-call
// - @typescript-eslint/no-unsafe-member-access
// - @typescript-eslint/no-unsafe-return
// - @typescript-eslint/no-unsafe-assignment
}
}
: null,
// This assumes test files are those which are in the test directory or have
// *.test.* or *.spec.* in the filename. If a file doesn't match this assumption,
// then it will not be allowed to import test files.
{
files: ['**/*.ts?(x)', '**/*.js?(x)'],
ignores: testFiles,
rules: {
'no-restricted-imports': [
ERROR,
{
patterns: [
{
group: testFiles,
message: 'Do not import test files in source files'
}
]
}
]
}
},
hasTestingLibrary
? {
files: testFiles,
ignores: [...playwrightFiles],
plugins: {
'testing-library': (await import('eslint-plugin-testing-library'))
.default
},
rules: {
'testing-library/no-unnecessary-act': [ERROR, {isStrict: false}],
'testing-library/no-wait-for-side-effects': ERROR,
'testing-library/prefer-find-by': ERROR
}
}
: null,
hasJestDom
? {
files: testFiles,
ignores: [...playwrightFiles],
plugins: {
'jest-dom': (await import('eslint-plugin-jest-dom')).default
},
rules: {
'jest-dom/prefer-checked': ERROR,
'jest-dom/prefer-enabled-disabled': ERROR,
'jest-dom/prefer-focus': ERROR,
'jest-dom/prefer-required': ERROR
}
}
: null,
hasVitest
? {
files: testFiles,
ignores: [...playwrightFiles],
plugins: {
vitest: (await import('@vitest/eslint-plugin')).default
},
rules: {
// you don't want the editor to autofix this, but we do want to be
// made aware of it
'vitest/no-focused-tests': [WARN, {fixable: false}]
}
}
: null
].filter(Boolean)
// this is for backward compatibility
export default config