eslint-config-typed 7.2.0 → 7.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/package.json +4 -4
- package/src/rules/rule-rationale-doc.test.mts +148 -0
package/README.md
CHANGED
|
@@ -43,6 +43,7 @@ A comprehensive ESLint configuration package with strongly-typed rule definition
|
|
|
43
43
|
- [Override Specific Rules](#override-specific-rules)
|
|
44
44
|
- [Use RulesOptions Types](#use-rulesoptions-types)
|
|
45
45
|
- [Target Specific Files](#target-specific-files)
|
|
46
|
+
- [Why Some Rules Are On or Off](#why-some-rules-are-on-or-off)
|
|
46
47
|
- [Troubleshooting](#troubleshooting)
|
|
47
48
|
- [Common Issues](#common-issues)
|
|
48
49
|
- [1. ESLint can't find tsconfig.json](#1-eslint-cant-find-tsconfigjson)
|
|
@@ -900,6 +901,10 @@ export default [
|
|
|
900
901
|
] satisfies readonly FlatConfig[];
|
|
901
902
|
```
|
|
902
903
|
|
|
904
|
+
## Why Some Rules Are On or Off
|
|
905
|
+
|
|
906
|
+
[`docs/rule-rationale.md`](https://github.com/noshiro-pf/mono/blob/main/libs/eslint-config-typed/docs/rule-rationale.md) (in Japanese) explains rules whose setting does not explain itself, such as why loops prefer `for-of` while loop statements stay allowed, and why `reduce` is allowed but only with an initial value. It also says how to fix a report from each rule.
|
|
907
|
+
|
|
903
908
|
## Troubleshooting
|
|
904
909
|
|
|
905
910
|
### Common Issues
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-config-typed",
|
|
3
|
-
"version": "7.2.
|
|
3
|
+
"version": "7.2.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript"
|
|
@@ -90,10 +90,10 @@
|
|
|
90
90
|
"@vitest/ui": "5.0.5",
|
|
91
91
|
"cspell": "10.4.0",
|
|
92
92
|
"dedent": "1.7.2",
|
|
93
|
-
"eslint-config-typed": "7.2.
|
|
94
|
-
"eslint-plugin-ts-data-forge": "0.10.
|
|
93
|
+
"eslint-config-typed": "7.2.1",
|
|
94
|
+
"eslint-plugin-ts-data-forge": "0.10.6",
|
|
95
95
|
"eslint-plugin-ts-fortress": "1.3.11",
|
|
96
|
-
"eslint-plugin-ts-type-forge": "0.8.
|
|
96
|
+
"eslint-plugin-ts-type-forge": "0.8.16",
|
|
97
97
|
"immer": "11.2.7",
|
|
98
98
|
"jiti": "2.7.0",
|
|
99
99
|
"json-schema": "0.4.0",
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { ESLint, type Linter } from 'eslint';
|
|
2
|
+
import * as fs from 'node:fs/promises';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { Arr, isRecord } from 'ts-data-forge';
|
|
5
|
+
import type { ReadonlyRecord } from 'ts-type-forge';
|
|
6
|
+
import { eslintConfigForTypeScript } from '../configs/index.mjs';
|
|
7
|
+
import { eslintTsDataForgeRules } from './eslint-ts-data-forge-rules.mjs';
|
|
8
|
+
|
|
9
|
+
// `docs/rule-rationale.md` explains why a rule is on or off in the preset; a
|
|
10
|
+
// table row, or a setting the prose relies on, that no longer matches what the
|
|
11
|
+
// preset configures says the opposite of what the package does, and nothing
|
|
12
|
+
// else would notice.
|
|
13
|
+
describe('docs/rule-rationale.md', () => {
|
|
14
|
+
const packageRoot = path.resolve(import.meta.dirname, '../..');
|
|
15
|
+
|
|
16
|
+
/** The config the preset resolves to for an ordinary source file. */
|
|
17
|
+
const presetConfig = async (): Promise<
|
|
18
|
+
Readonly<{
|
|
19
|
+
rules: ReadonlyRecord<string, unknown>;
|
|
20
|
+
linterOptions: unknown;
|
|
21
|
+
}>
|
|
22
|
+
> => {
|
|
23
|
+
const eslint = new ESLint({
|
|
24
|
+
cwd: packageRoot,
|
|
25
|
+
overrideConfigFile: true,
|
|
26
|
+
overrideConfig: Array.from(
|
|
27
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion -- the preset's flat config, which ESLint validates
|
|
28
|
+
eslintConfigForTypeScript({
|
|
29
|
+
tsconfigFileName: 'tsconfig.json',
|
|
30
|
+
tsconfigRootDir: packageRoot,
|
|
31
|
+
packageDirs: [packageRoot],
|
|
32
|
+
}) as readonly Linter.Config[],
|
|
33
|
+
),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const config: unknown = await eslint.calculateConfigForFile(
|
|
37
|
+
path.join(packageRoot, 'src/entry-point.mts'),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
assert.isTrue(isRecord(config));
|
|
41
|
+
|
|
42
|
+
const { rules, linterOptions } = config;
|
|
43
|
+
|
|
44
|
+
assert.isTrue(isRecord(rules));
|
|
45
|
+
|
|
46
|
+
return { rules, linterOptions };
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
test('every table row names the severity the preset configures', async () => {
|
|
50
|
+
const [doc, { rules }] = await Promise.all([
|
|
51
|
+
fs.readFile(path.join(packageRoot, 'docs/rule-rationale.md'), 'utf8'),
|
|
52
|
+
presetConfig(),
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
// Every body row of every table is read; one this pattern does not parse
|
|
56
|
+
// fails instead of going unchecked.
|
|
57
|
+
const rows = doc
|
|
58
|
+
.split('\n')
|
|
59
|
+
.filter(
|
|
60
|
+
(line) =>
|
|
61
|
+
line.trimStart().startsWith('|') &&
|
|
62
|
+
!line.includes('---') &&
|
|
63
|
+
!line.includes('ルール'),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
expect(rows.length).toBeGreaterThan(0);
|
|
67
|
+
|
|
68
|
+
const parsed = rows.map((row) => ({
|
|
69
|
+
row,
|
|
70
|
+
groups: rowPattern.exec(row)?.groups,
|
|
71
|
+
}));
|
|
72
|
+
|
|
73
|
+
assert.deepStrictEqual(
|
|
74
|
+
parsed.filter(({ groups }) => groups === undefined).map(({ row }) => row),
|
|
75
|
+
[],
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
for (const { groups } of parsed) {
|
|
79
|
+
const rule = groups?.['rule'];
|
|
80
|
+
|
|
81
|
+
assert.isDefined(rule);
|
|
82
|
+
|
|
83
|
+
const severity = groups?.['severity'];
|
|
84
|
+
|
|
85
|
+
expect(severityOf(rules[rule]), `${rule} in the preset`).toBe(severity);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('the settings the prose relies on', async () => {
|
|
90
|
+
const { rules, linterOptions } = await presetConfig();
|
|
91
|
+
|
|
92
|
+
// A for-loop variable is reported unless it is named `mut_…`.
|
|
93
|
+
const noLet = rules['functional/no-let'];
|
|
94
|
+
|
|
95
|
+
assert.isTrue(Arr.isArray(noLet));
|
|
96
|
+
|
|
97
|
+
const option = noLet[1];
|
|
98
|
+
|
|
99
|
+
assert.isTrue(isRecord(option));
|
|
100
|
+
|
|
101
|
+
expect(option['allowInForLoopInit']).toBe(false);
|
|
102
|
+
|
|
103
|
+
expect(option['ignoreIdentifierPattern']).toContain('^mut_');
|
|
104
|
+
|
|
105
|
+
// A disable comment naming a rule that did not report is itself reported.
|
|
106
|
+
assert.isTrue(isRecord(linterOptions));
|
|
107
|
+
|
|
108
|
+
// ESLint resolves `true` to 'warn' and `false` to 'off'.
|
|
109
|
+
expect(['warn', 'error']).toContain(
|
|
110
|
+
severityOf(linterOptions['reportUnusedDisableDirectives']),
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// `prefer-range-for-loop` is not in the preset; the prose says it has to
|
|
114
|
+
// be added.
|
|
115
|
+
expect(rules['ts-data-forge/prefer-range-for-loop']).toBeUndefined();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('eslintTsDataForgeRules turns on prefer-range-for-loop', () => {
|
|
119
|
+
expect(
|
|
120
|
+
severityOf(eslintTsDataForgeRules['ts-data-forge/prefer-range-for-loop']),
|
|
121
|
+
).toBe('error');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
/** A table body row: `` | `<rule>` | `<severity>` | ``. */
|
|
126
|
+
const rowPattern = /^\| `(?<rule>[^`]+)` +\| `(?<severity>[^`]+)` +\|$/u;
|
|
127
|
+
|
|
128
|
+
/** `'off'`, `'warn'` or `'error'` for a rule entry, whatever its spelling. */
|
|
129
|
+
const severityOf = (entry: unknown): string | undefined => {
|
|
130
|
+
const level = Arr.isArray(entry) ? entry[0] : entry;
|
|
131
|
+
|
|
132
|
+
switch (level) {
|
|
133
|
+
case 0:
|
|
134
|
+
case 'off':
|
|
135
|
+
return 'off';
|
|
136
|
+
|
|
137
|
+
case 1:
|
|
138
|
+
case 'warn':
|
|
139
|
+
return 'warn';
|
|
140
|
+
|
|
141
|
+
case 2:
|
|
142
|
+
case 'error':
|
|
143
|
+
return 'error';
|
|
144
|
+
|
|
145
|
+
default:
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
};
|