Skip to content

Commit d010853

Browse files
committed
fix: be more careful about ext => rule mapping
See comments about `no-undef`
1 parent a7dda10 commit d010853

File tree

1 file changed

+161
-62
lines changed

1 file changed

+161
-62
lines changed

lib/eslint.mjs

+161-62
Original file line numberDiff line numberDiff line change
@@ -23,71 +23,99 @@ const legacy = {
2323
react: legacyReact,
2424
}
2525

26-
// See https://www.npmjs.com/package/eslint-plugin-html#user-content-settings
27-
const htmlExtensions = htmlSettings.getSettings({}).htmlExtensions
26+
// WARNING: eslint rules from `typescript-eslint`, even the "untyped" rules, assume that your code will be run through
27+
// `tsc` or equivalent for type checking. Using any rule set from `typescript-eslint` will, for example, turn off the
28+
// `no-undef` rule. That makes sense if you'll use TypeScript to catch undefined globals, but it could be dangerous
29+
// for plain JavaScript.
30+
// More information here: https://github.com/typescript-eslint/typescript-eslint/issues/8825#issuecomment-2033315610
2831

29-
// '.html' => '**/*.html'
30-
const htmlGlobs = htmlExtensions.map(ext => `**/*${ext}`)
32+
/**
33+
* Convert an array of file extensions to an array of globs
34+
* @param {string[]} extArray - an array of file extensions, like `.foo`
35+
* @returns {string[]} an array of globs, like `** /*.foo` (without the space)
36+
*/
37+
const extArrayToGlobArray = extArray => extArray.map(ext => `**/*${ext}`)
3138

32-
const typeScriptExtensions = ['.ts', '.tsx', '.mts', '.cts']
39+
// See https://www.npmjs.com/package/eslint-plugin-html#user-content-settings
40+
const htmlSettingsDefault = htmlSettings.getSettings({})
41+
42+
const fileExtensions = (x => {
43+
x.allScript = [...x.javaScript, ...x.typeScript]
44+
return x
45+
})({
46+
html: /** @type {string[]} */ (htmlSettingsDefault.htmlExtensions),
47+
javaScript: ['.js', '.jsx', '.mjs', '.cjs'],
48+
markdown: ['.md'],
49+
typeScript: ['.ts', '.tsx', '.mts', '.cts'],
50+
react: ['.jsx', '.tsx'],
51+
xml: /** @type {string[]} */ (htmlSettingsDefault.xmlExtensions),
52+
})
53+
54+
// This explicitly lists each entry so that we can get unused warnings
55+
const fileGlobs = {
56+
allScript: extArrayToGlobArray(fileExtensions.allScript),
57+
html: extArrayToGlobArray(fileExtensions.html),
58+
javaScript: extArrayToGlobArray(fileExtensions.javaScript),
59+
markdown: extArrayToGlobArray(fileExtensions.markdown),
60+
react: extArrayToGlobArray(fileExtensions.react),
61+
typeScript: extArrayToGlobArray(fileExtensions.typeScript),
62+
xml: extArrayToGlobArray(fileExtensions.xml),
63+
}
3364

3465
/**
35-
* Base rules recommended when type information is not available.
36-
* These rules are also safe to use when type information is available.
66+
* Rules for specific file types outside of the core JS/TS rule sets.
3767
*/
38-
const typeFreeRules = tseslint.config(
39-
eslint.configs.recommended,
40-
tseslint.configs.recommended,
41-
tseslint.configs.stylistic,
68+
const miscFileRules = tseslint.config([
69+
// eslint-plugin-html
70+
{
71+
name: 'scratch/miscFileRules[eslint-plugin-html]',
72+
files: [...fileGlobs.html, ...fileGlobs.xml],
73+
plugins: { html },
74+
settings: {
75+
'html/html-extensions': fileExtensions.html,
76+
'xml/xml-extensions': fileExtensions.xml,
77+
},
78+
},
79+
// eslint-plugin-markdown
80+
{
81+
name: 'scratch/miscFileRules[eslint-plugin-markdown]',
82+
files: fileGlobs.markdown,
83+
extends: [markdown.configs.recommended],
84+
},
85+
])
4286

87+
/**
88+
* Rules recommended for all script files, whether or not type information is available or checked.
89+
*/
90+
const allScriptRules = tseslint.config([
4391
// eslint-plugin-formatjs
4492
{
93+
name: 'scratch/allScriptRules[eslint-plugin-formatjs]',
4594
plugins: {
4695
formatjs,
4796
},
4897
rules: {
4998
'formatjs/no-offset': ['error'],
5099
},
51100
},
52-
// eslint-plugin-html
53-
{
54-
files: htmlGlobs,
55-
plugins: { html },
56-
settings: {
57-
'html/html-extensions': htmlExtensions,
58-
},
59-
},
60101
// eslint-plugin-import
61102
{
103+
name: 'scratch/allScriptRules[eslint-plugin-import]',
62104
plugins: importPlugin.flatConfigs.recommended.plugins,
63105
rules: {
64106
'import/no-duplicates': 'error', // Forbid duplicate imports
65107
},
66108
},
67-
// eslint-plugin-jsdoc
68-
jsdoc.configs['flat/recommended-error'],
69-
{
70-
files: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'],
71-
extends: [jsdoc.configs['flat/recommended-typescript-error']],
72-
},
109+
// eslint-plugin-jsx-a11y
73110
{
74-
rules: {
75-
// If JSDoc comments are present, they must be informative (non-trivial).
76-
// For example, the description "The foo." on a variable called "foo" is not informative.
77-
// https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/informative-docs.md
78-
'jsdoc/informative-docs': ['error'],
79-
80-
// Don't require JSDoc comments. Library authors should consider turning this on for external interfaces.
81-
// https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-jsdoc.md
82-
'jsdoc/require-jsdoc': ['off'],
83-
},
111+
name: 'scratch/allScriptRules[eslint-plugin-jsx-a11y]',
112+
files: fileGlobs.react,
113+
extends: [jsxA11y.flatConfigs.recommended],
84114
},
85-
// eslint-plugin-jsx-a11y
86-
jsxA11y.flatConfigs.recommended,
87-
// eslint-plugin-markdown
88-
markdown.configs.recommended,
89115
// eslint-plugin-react
90116
{
117+
name: 'scratch/allScriptRules[eslint-plugin-react]',
118+
files: fileGlobs.react,
91119
plugins: {
92120
react,
93121
},
@@ -142,27 +170,17 @@ const typeFreeRules = tseslint.config(
142170
},
143171
// eslint-plugin-react-hooks
144172
{
173+
name: 'scratch/allScriptRules[eslint-plugin-react-hooks]',
174+
files: fileGlobs.react,
145175
extends: [reactHooks.configs['recommended-latest']],
146176
rules: {
147177
// https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/README.md#advanced-configuration
148178
'react-hooks/exhaustive-deps': ['error', { additionalHooks: '^useAsync$' }],
149179
},
150180
},
151-
// typescript-eslint
152-
{
153-
rules: {
154-
// https://typescript-eslint.io/rules/no-non-null-asserted-nullish-coalescing/
155-
'@typescript-eslint/no-non-null-asserted-nullish-coalescing': ['error'],
156-
157-
// https://typescript-eslint.io/rules/no-useless-constructor/
158-
'@typescript-eslint/no-useless-constructor': ['error'],
159-
160-
// https://typescript-eslint.io/rules/no-non-null-assertion
161-
'@typescript-eslint/no-non-null-assertion': ['error'],
162-
},
163-
},
164181
// @eslint-community/eslint-plugin-eslint-comments
165182
{
183+
name: 'scratch/allScriptRules[eslint-plugin-eslint-comments]',
166184
extends: [
167185
// @ts-expect-error This plugin's recommended rules don't quite match the type `tseslint.config` expects.
168186
eslintComments.recommended,
@@ -174,6 +192,7 @@ const typeFreeRules = tseslint.config(
174192
},
175193
// @eslint/js
176194
{
195+
name: 'scratch/allScriptRules[@eslint/js]',
177196
rules: {
178197
// https://eslint.org/docs/latest/rules/arrow-body-style
179198
'arrow-body-style': ['error', 'as-needed'],
@@ -206,17 +225,48 @@ const typeFreeRules = tseslint.config(
206225
'symbol-description': ['error'],
207226
},
208227
},
209-
)
228+
])
229+
230+
/**
231+
* Additional rules recommended when type information is not available or checked.
232+
*/
233+
const typeFreeRules = tseslint.config([
234+
{
235+
name: 'scratch/typeFreeRules[base]',
236+
extends: [eslint.configs.recommended],
237+
},
238+
...allScriptRules,
239+
{
240+
name: 'scratch/typeFreeRules[eslint-plugin-jsdoc]',
241+
extends: [jsdoc.configs['flat/recommended-error']],
242+
rules: {
243+
// If JSDoc comments are present, they must be informative (non-trivial).
244+
// For example, the description "The foo." on a variable called "foo" is not informative.
245+
// https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/informative-docs.md
246+
'jsdoc/informative-docs': ['error'],
247+
248+
// Don't require JSDoc comments. Library authors should consider turning this on for external interfaces.
249+
// https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-jsdoc.md
250+
'jsdoc/require-jsdoc': ['off'],
251+
},
252+
},
253+
])
210254

211255
/**
212-
* Additional rules recommended when information is available.
256+
* Rules recommended when type information is available and checked. This configuration turns off some rules with the
257+
* assumption that other software, such as TypeScript, will flag those problems. For example, the `no-undef` rule is
258+
* disabled in this configuration. These rules include `allScriptRules`.
213259
* These rules require additional configuration.
214260
* @see https://typescript-eslint.io/getting-started/typed-linting/
215261
*/
216-
const typeCheckedRules = tseslint.config(
217-
tseslint.configs.recommendedTypeChecked,
218-
tseslint.configs.stylisticTypeChecked,
262+
const typeCheckedRules = tseslint.config([
219263
{
264+
name: 'scratch/typeCheckedRules[base]',
265+
extends: [
266+
eslint.configs.recommended,
267+
tseslint.configs.recommendedTypeChecked,
268+
tseslint.configs.stylisticTypeChecked,
269+
],
220270
rules: {
221271
// https://typescript-eslint.io/rules/no-unnecessary-condition/
222272
'@typescript-eslint/no-unnecessary-condition': ['error'],
@@ -225,7 +275,43 @@ const typeCheckedRules = tseslint.config(
225275
'@typescript-eslint/require-await': ['error'],
226276
},
227277
},
228-
)
278+
...allScriptRules,
279+
{
280+
name: 'scratch/typeCheckedRules[eslint-plugin-jsdoc][1]',
281+
extends: [jsdoc.configs['flat/recommended-error']],
282+
},
283+
{
284+
name: 'scratch/typeCheckedRules[eslint-plugin-jsdoc][2]',
285+
extends: [jsdoc.configs['flat/recommended-typescript-error']],
286+
},
287+
{
288+
name: 'scratch/typeCheckedRules[eslint-plugin-jsdoc][3]',
289+
rules: {
290+
// If JSDoc comments are present, they must be informative (non-trivial).
291+
// For example, the description "The foo." on a variable called "foo" is not informative.
292+
// https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/informative-docs.md
293+
'jsdoc/informative-docs': ['error'],
294+
295+
// Don't require JSDoc comments. Library authors should consider turning this on for external interfaces.
296+
// https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-jsdoc.md
297+
'jsdoc/require-jsdoc': ['off'],
298+
},
299+
},
300+
// typescript-eslint
301+
{
302+
name: 'scratch/typeCheckedRules[typescript-eslint]',
303+
rules: {
304+
// https://typescript-eslint.io/rules/no-non-null-asserted-nullish-coalescing/
305+
'@typescript-eslint/no-non-null-asserted-nullish-coalescing': ['error'],
306+
307+
// https://typescript-eslint.io/rules/no-useless-constructor/
308+
'@typescript-eslint/no-useless-constructor': ['error'],
309+
310+
// https://typescript-eslint.io/rules/no-non-null-assertion
311+
'@typescript-eslint/no-non-null-assertion': ['error'],
312+
},
313+
},
314+
])
229315

230316
/**
231317
* Scratch's recommended configuration when type information is not available.
@@ -235,9 +321,10 @@ const recommendedTypeFree = tseslint.config(typeFreeRules, eslintConfigPrettier)
235321
/**
236322
* Scratch's recommended configuration when type information is available.
237323
* These rules require additional configuration.
324+
* WARNING: These rules do not specify the `files` property.
238325
* @see https://typescript-eslint.io/getting-started/typed-linting/
239326
*/
240-
const recommendedTypeChecked = tseslint.config(typeFreeRules, typeCheckedRules, eslintConfigPrettier)
327+
const recommendedTypeChecked = tseslint.config(typeCheckedRules, eslintConfigPrettier)
241328

242329
/**
243330
* Scratch's recommended configuration for general use.
@@ -246,10 +333,22 @@ const recommendedTypeChecked = tseslint.config(typeFreeRules, typeCheckedRules,
246333
* @see https://typescript-eslint.io/getting-started/typed-linting/
247334
*/
248335
const recommended = tseslint.config(
249-
typeFreeRules,
250336
{
251-
files: typeScriptExtensions,
337+
name: 'scratch/recommended',
338+
},
339+
miscFileRules,
340+
{
341+
files: fileGlobs.allScript,
342+
extends: [typeFreeRules],
343+
},
344+
{
345+
files: fileGlobs.typeScript,
252346
extends: [typeCheckedRules],
347+
languageOptions: {
348+
parserOptions: {
349+
projectService: true,
350+
},
351+
},
253352
},
254353
eslintConfigPrettier,
255354
)
@@ -258,4 +357,4 @@ const recommended = tseslint.config(
258357
export { config } from 'typescript-eslint'
259358

260359
// Our exported configurations
261-
export { recommended, recommendedTypeChecked, recommendedTypeFree, legacy }
360+
export { recommended, recommendedTypeChecked, recommendedTypeFree, miscFileRules, legacy }

0 commit comments

Comments
 (0)