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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy