8000 docs: additional checks for rule examples (#19358) · eslint/eslint@0ef8bb8 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0ef8bb8

Browse files
authored
docs: additional checks for rule examples (#19358)
1 parent 58ab2f6 commit 0ef8bb8

File tree

7 files changed

+140
-76
lines changed

7 files changed

+140
-76
lines changed

docs/src/rules/capitalized-comments.md

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,22 @@ Examples of **correct** code for this rule:
4141

4242
// 丈 Non-Latin character at beginning of comment
4343

44-
/* eslint semi:off */
45-
/* eslint-disable */
46-
/* eslint-enable */
4744
/* istanbul ignore next */
4845
/* jscs:enable */
4946
/* jshint asi:true */
5047
/* global foo */
5148
/* globals foo */
5249
/* exported myVar */
53-
// eslint-disable-line
54-
// eslint-disable-next-line
5550
// https://github.com
5651

52+
/* eslint semi:2 */
53+
/* eslint-disable */
54+
foo
55+
/* eslint-enable */
56+
// eslint-disable-next-line
57+
baz
58+
bar // eslint-disable-line
59+
5760
```
5861

5962
:::
@@ -116,19 +119,22 @@ Examples of **correct** code for this rule:
116119

117120
// 丈 Non-Latin character at beginning of comment
118121

119-
/* eslint semi:off */
120-
/* eslint-disable */
121-
/* eslint-enable */
122122
/* istanbul ignore next */
123123
/* jscs:enable */
124124
/* jshint asi:true */
125125
/* global foo */
126126
/* globals foo */
127127
/* exported myVar */
128-
// eslint-disable-line
129-
// eslint-disable-next-line
130128
// https://github.com
131129

130+
/* eslint semi:2 */
131+
/* eslint-disable */
132+
foo
133+
/* eslint-enable */
134+
// eslint-disable-next-line
135+
baz
136+
bar // eslint-disable-line
137+
132138
```
133139

134140
:::

docs/src/rules/comma-dangle.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ var arr = [1,2,];
161161
foo({
162162
bar: "baz",
163163
qux: "quux",
164-
});
164+
},);
165165
```
166166

167167
:::

docs/src/rules/consistent-this.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ foo.bar = this;
6969

7070
Examples of **incorrect** code for this rule with the default `"that"` option, if the variable is not initialized:
7171

72-
::: incorrect
72+
::: incorrect { "sourceType": "script" }
7373

7474
```js
7575
/*eslint consistent-this: ["error", "that"]*/

docs/src/rules/indent-legacy.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ function foo(x) {
316316
})();
317317

318318
if(y) {
319-
console.log('foo');
319+
console.log('foo');
320320
}
321321
```
322322

tests/fixtures/bad-examples.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const foo = "baz";
2828
:::correct
2929

3030
```js
31-
/* eslint another-rule: error */
31+
/* eslint no-undef: error */
3232
```
3333

3434
:::
@@ -80,3 +80,41 @@ const foo = "baz";
8080
```
8181

8282
:::
83+
84+
:::correct { "foo": 6 }
85+
86+
```js
87+
/* eslint no-restricted-syntax: ["error", "ArrayPattern"] */
88+
```
89+
90+
:::
91+
92+
:::correct
93+
94+
```js
95+
/* eslint no-restricted-syntax: ["error", "ArrayPattern"] */
96+
97+
const [foo] = bar;
98+
```
99+
100+
:::
101+
102+
:::incorrect
103+
104+
```js
105+
/* eslint no-restricted-syntax: ["error", "ArrayPattern"] */
106+
107+
const foo = [bar];
108+
```
109+
110+
:::
111+
112+
:::incorrect
113+
114+
```js
115+
/* eslint no-restricted-syntax: ["errorr", "ArrayPattern"] */
116+
117+
const foo = [bar];
118+
```
119+
120+
:::

tests/tools/check-rule-examples.js

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -65,27 +65,38 @@ describe("check-rule-examples", () => {
6565
.replace(/(?<=\x1B\[4m).*(?=bad-examples\.md)/u, "")
6666

6767
// Remove runtime-specific error message part (different in Node.js 18, 20 and 21).
68-
.replace(/(?<='doesn't allow this comment'):.*(?=\x1B\[0m)/u, "");
68+
.replace(/(?<='doesn't allow this comment'):.*(?=\x1B\[0m)/u, "")
69+
70+
// Remove multiple whitespace before rule name in lint errors
71+
.replaceAll(/\s+(?=\S*no-restricted-syntax\S*\n)/gu, " ");
6972

7073
/* eslint-enable no-control-regex -- re-enable rule */
7174

7275
const expectedStderr =
7376
"\x1B[0m\x1B[0m\n" +
7477
"\x1B[0m\x1B[4mbad-examples.md\x1B[24m\x1B[0m\n" +
75-
"\x1B[0m \x1B[2m11:4\x1B[22m \x1B[31merror\x1B[39m Missing language tag: use one of 'javascript', 'js' or 'jsx'\x1B[0m\n" +
76-
"\x1B[0m \x1B[2m12:1\x1B[22m \x1B[31merror\x1B[39m Syntax error: 'import' and 'export' may appear only with 'sourceType: module'\x1B[0m\n" +
77-
"\x1B[0m \x1B[2m20:5\x1B[22m \x1B[31merror\x1B[39m Nonstandard language tag 'ts': use one of 'javascript', 'js' or 'jsx'\x1B[0m\n" +
78-
"\x1B[0m \x1B[2m23:7\x1B[22m \x1B[31merror\x1B[39m Syntax error: Identifier 'foo' has already been declared\x1B[0m\n" +
79-
"\x1B[0m \x1B[2m31:1\x1B[22m \x1B[31merror\x1B[39m Example code should contain a configuration comment like /* eslint no-restricted-syntax: \"error\" */\x1B[0m\n" +
80-
"\x1B[0m \x1B[2m41:1\x1B[22m \x1B[31merror\x1B[39m Failed to parse JSON from 'doesn't allow this comment'\x1B[0m\n" +
81-
"\x1B[0m \x1B[2m51:1\x1B[22m \x1B[31merror\x1B[39m Duplicate /* eslint no-restricted-syntax */ configuration comment. Each example should contain only one. Split this example into multiple examples\x1B[0m\n" +
82-
"\x1B[0m \x1B[2m56:1\x1B[22m \x1B[31merror\x1B[39m Remove unnecessary \"ecmaVersion\":\"latest\"\x1B[0m\n" +
83-
`\x1B[0m \x1B[2m64:1\x1B[22m \x1B[31merror\x1B[39m "ecmaVersion" must be one of ${[3, 5, ...Array.from({ length: LATEST_ECMA_VERSION - 2015 + 1 }, (_, index) => index + 2015)].join(", ")}\x1B[0m\n` +
84-
"\x1B[0m \x1B[2m76:1\x1B[22m \x1B[31merror\x1B[39m /* eslint-env */ comments are no longer supported. Remove the comment\x1B[0m\n" +
85-
"\x1B[0m \x1B[2m78:1\x1B[22m \x1B[31merror\x1B[39m /* eslint-env */ comments are no longer supported. Remove the comment\x1B[0m\n" +
86-
"\x1B[0m \x1B[2m79:1\x1B[22m \x1B[31merror\x1B[39m /* eslint-env */ comments are no longer supported. Remove the comment\x1B[0m\n" +
78+
"\x1B[0m \x1B[2m11:4\x1B[22m \x1B[31merror\x1B[39m Missing language tag: use one of 'javascript', 'js' or 'jsx'\x1B[0m\n" +
79+
"\x1B[0m \x1B[2m12:1\x1B[22m \x1B[31merror\x1B[39m Unexpected lint error found: Parsing error: 'import' and 'export' may appear only with 'sourceType: module'\x1B[0m\n" +
80+
"\x1B[0m \x1B[2m20:5\x1B[22m \x1B[31merror\x1B[39m Nonstandard language tag 'ts': use one of 'javascript', 'js' or 'jsx'\x1B[0m\n" +
81+
"\x1B[0m \x1B[2m23:7\x1B[22m \x1B[31merror\x1B[39m Unexpected lint error found: Parsing error: Identifier 'foo' has already been declared\x1B[0m\n" +
82+
"\x1B[0m \x1B[2m31:1\x1B[22m \x1B[31merror\x1B[39m Example code should contain a configuration comment like /* eslint no-restricted-syntax: \"error\" */\x1B[0m\n" +
83+
"\x1B[0m \x1B[2m41:1\x1B[22m \x1B[31merror\x1B[39m Unexpected lint error found: Failed to parse JSON from 'doesn't allow this comment'\x1B[0m\n" +
84+
"\x1B[0m \x1B[2m51:1\x1B[22m \x1B[31merror\x1B[39m Unexpected lint error found: Rule \"no-restricted-syntax\" is already configured by another configuration comment in the preceding code. This configuration is ignored\x1B[0m\n" +
85+
"\x1B[0m \x1B[2m51:1\x1B[22m \x1B[31merror\x1B[39m Duplicate /* eslint no-restricted-syntax */ configuration comment. Each example should contain only one. Split this example into multiple examples\x1B[0m\n" +
86+
"\x1B[0m \x1B[2m56:1\x1B[22m \x1B[31merror\x1B[39m Remove unnecessary \"ecmaVersion\":\"latest\"\x1B[0m\n" +
87+
`\x1B[0m \x1B[2m64:1\x1B[22m \x1B[31merror\x1B[39m "ecmaVersion" must be one of ${[3, 5, ...Array.from({ length: LATEST_ECMA_VERSION - 2015 + 1 }, (_, index) => index + 2015)].join(", ")}\x1B[0m\n` +
88+
"\x1B[0m \x1B[2m76:1\x1B[22m \x1B[31merror\x1B[39m /* eslint-env */ comments are no longer supported. Remove the comment\x1B[0m\n" +
89+
"\x1B[0m \x1B[2m78:1\x1B[22m \x1B[31merror\x1B[39m /* eslint-env */ comments are no longer supported. Remove the comment\x1B[0m\n" +
90+
"\x1B[0m \x1B[2m79:1\x1B[22m \x1B[31merror\x1B[39m /* eslint-env */ comments are no longer supported. Remove the comment\x1B[0m\n" +
91+
"\x1B[0m \x1B[2m84:1\x1B[22m \x1B[31merror\x1B[39m Configuration error: Key \"languageOptions\": Unexpected key \"foo\" found\x1B[0m\n" +
92+
"\x1B[0m \x1B[2m97:7\x1B[22m \x1B[31merror\x1B[39m Unexpected lint error found: Using 'ArrayPattern' is not allowed \x1B[2mno-restricted-syntax\x1B[22m\x1B[0m\n" +
93+
"\x1B[0m \x1B[2m105:1\x1B[22m \x1B[31merror\x1B[39m Incorrect examples should have at least one error reported by the rule\x1B[0m\n" +
94+
"\x1B[0m \x1B[2m115:1\x1B[22m \x1B[31merror\x1B[39m Incorrect examples should have at least one error reported by the rule\x1B[0m\n" +
95+
"\x1B[0m \x1B[2m115:1\x1B[22m \x1B[31merror\x1B[39m Unexpected lint error found: Inline configuration for rule \"no-restricted-syntax\" is invalid:\x1B[0m\n" +
96+
"\x1B[0m\tExpected severity of \"off\", 0, \"warn\", 1, \"error\", or 2. You passed \"errorr,ArrayPattern\".\x1B[0m\n" +
97+
"\x1B[0m \x1B[2mno-restricted-syntax\x1B[22m\x1B[0m\n" +
8798
"\x1B[0m\x1B[0m\n" +
88-
"\x1B[0m\x1B[31m\x1B[1m✖ 12 problems (12 errors, 0 warnings)\x1B[22m\x1B[39m\x1B[0m\n" +
99+
"\x1B[0m\x1B[31m\x1B[1m✖ 18 problems (18 errors, 0 warnings)\x1B[22m\x1B[39m\x1B[0m\n" +
89100
"\x1B[0m\x1B[31m\x1B[1m\x1B[22m\x1B[39m\x1B[0m\n";
90101

91102
assert.strictEqual(normalizedStderr, expectedStderr);

tools/check-rule-examples.js

Lines changed: 57 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
// Requirements
55
//------------------------------------------------------------------------------
66

7-
const { parse } = require("espree");
87
const { readFile } = require("node:fs").promises;
98
const { glob } = require("glob");
109
const matter = require("gray-matter");
@@ -14,6 +13,7 @@ const markdownItRuleExample = require("../docs/tools/markdown-it-rule-example");
1413
const { ConfigCommentParser } = require("@eslint/plugin-kit");
1514
const rules = require("../lib/rules");
1615
const { LATEST_ECMA_VERSION } = require("../conf/ecma-version");
16+
const { Linter } = require("../lib/linter");
1717

1818
//------------------------------------------------------------------------------
1919
// Typedefs
@@ -37,28 +37,6 @@ const VALID_ECMA_VERSIONS = new Set([
3737

3838
const commentParser = new ConfigCommentParser();
3939

40-
/**
41-
* Tries to parse a specified JavaScript code with Playground presets.
42-
* @param {string} code The JavaScript code to parse.
43-
* @param {LanguageOptions} [languageOptions] Explicitly specified language options.
44-
* @returns {{ ast: ASTNode } | { error: SyntaxError }} An AST with comments, or a `SyntaxError` object if the code cannot be parsed.
45-
*/
46-
function tryParseForPlayground(code, languageOptions) {
47-
try {
48-
const ast = parse(code, {
49-
ecmaVersion: languageOptions?.ecmaVersion ?? "latest",
50-
sourceType: languageOptions?.sourceType ?? "module",
51-
...languageOptions?.parserOptions,
52-
comment: true,
53-
loc: true
54-
});
55-
56-
return { ast };
57-
} catch (error) {
58-
return { error };
59-
}
60-
}
61-
6240
/**
6341
* Checks the example code blocks in a rule documentation file.
6442
* @param {string} filename The file to be checked.
@@ -70,7 +48,7 @@ async function findProblems(filename) {
7048
const isRuleRemoved = !rules.has(title);
7149
const problems = [];
7250
const ruleExampleOptions = markdownItRuleExample({
73-
open({ code, languageOptions, codeBlockToken }) {
51+
open({ code, type, languageOptions = {}, codeBlockToken }) {
7452
const languageTag = codeBlockToken.info;
7553

7654
if (!STANDARD_LANGUAGE_TAGS.has(languageTag)) {
@@ -112,12 +90,64 @@ async function findProblems(filename) {
11290
line: codeBlockToken.map[0] - 1,
11391
column: 1
11492
});
93+
94+
return;
11595
}
11696
}
11797

118-
const { ast, error } = tryParseForPlayground(code, languageOptions);
98+
const linter = new Linter();
99+
let lintMessages;
119100

120-
if (ast) {
101+
try {
102+
lintMessages = linter.verify(code, { languageOptions });
103+
} catch (error) {
104+
problems.push({
105+
fatal: true,
106+
severity: 2,
107+
message: `Configuration error: ${error.message}`,
108+
line: codeBlockToken.map[0] - 1,
109+
column: 1
110+
});
111+
112+
return;
113+
}
114+
115+
// for removed rules, leave only parsing errors
116+
if (isRuleRemoved) {
117+
lintMessages = lintMessages.filter(lintMessage => lintMessage.fatal);
118+
} else {
119+
120+
if (type === "incorrect") {
121+
const { length } = lintMessages;
122+
123+
// filter out errors reported by the rule as they are expected in incorrect examples
124+
lintMessages = lintMessages.filter(lintMessage =>
125+
lintMessage.ruleId !== title ||
126+
lintMessage.fatal ||
127+
lintMessage.message.includes(`Inline configuration for rule "${title}" is invalid`));
128+
129+
if (lintMessages.length === length && !lintMessages.some(lintMessage => lintMessage.fatal)) {
130+
problems.push({
131+
fatal: false,
132+
severity: 2,
133+
message: "Incorrect examples should have at least one error reported by the rule.",
134+
line: codeBlockToken.map[0] + 2,
135+
column: 1
136+
});
137+
}
138+
}
139+
}
140+
141+
problems.push(...lintMessages.map(lintMessage => ({
142+
...lintMessage,
143+
message: `Unexpected lint error found: ${lintMessage.message}`,
144+
line: codeBlockToken.map[0] + 1 + lintMessage.line
145+
})));
146+
147+
const sourceCode = linter.getSourceCode();
148+
149+
if (sourceCode) {
150+
const { ast } = sourceCode;
121151
let hasRuleConfigComment = false;
122152

123153
for (const comment of ast.comments) {
@@ -137,17 +167,8 @@ async function findProblems(filename) {
137167
}
138168
const { value } = commentParser.parseDirective(comment.value);
139169
const parseResult = commentParser.parseJSONLikeConfig(value);
140-
const parseError = parseResult.error;
141170

142-
if (parseError) {
143-
problems.push({
144-
fatal: true,
145-
severity: 2,
146-
message: parseError.message,
147-
line: comment.loc.start.line + codeBlockToken.map[0] + 1,
148-
column: comment.loc.start.column + 1
149-
});
150-
} else if (Object.hasOwn(parseResult.config, title)) {
171+
if (parseResult.ok && Object.hasOwn(parseResult.config, title)) {
151172
if (hasRuleConfigComment) {
152173
problems.push({
153174
fatal: false,
@@ -174,19 +195,7 @@ async function findProblems(filename) {
174195
}
175196
}
176197

177-
if (error) {
178-
const message = `Syntax error: ${error.message}`;
179-
const line = codeBlockToken.map[0] + 1 + error.lineNumber;
180-
const { column } = error;
181198

182-
problems.push({
183-
fatal: false,
184-
severity: 2,
185-
message,
186-
line,
187-
column
188-
});
189-
}
190199
}
191200
});
192201

0 commit comments

Comments
 (0)
0