Skip to content

Commit dd79abc

Browse files
ota-meshimdjermanovicnzakas
authored
fix: eslint-disable to be able to parse quoted rule names (#17612)
* fix: `eslint-disable` to be able to parse literal rule names * fix: id casing * Apply suggestions from code review Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com> Co-authored-by: Nicholas C. Zakas <nicholas@humanwhocodes.com> * test: update test cases * test: add eslint-env test case * test: add test case and fix test case --------- Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com> Co-authored-by: Nicholas C. Zakas <nicholas@humanwhocodes.com>
1 parent d2f6801 commit dd79abc

File tree

4 files changed

+290
-2
lines changed

4 files changed

+290
-2
lines changed

lib/linter/apply-disable-directives.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ function createIndividualDirectivesRemoval(directives, commentToken) {
8787
return directives.map(directive => {
8888
const { ruleId } = directive;
8989

90-
const regex = new RegExp(String.raw`(?:^|\s*,\s*)${escapeRegExp(ruleId)}(?:\s*,\s*|$)`, "u");
90+
const regex = new RegExp(String.raw`(?:^|\s*,\s*)(?<quote>['"]?)${escapeRegExp(ruleId)}\k<quote>(?:\s*,\s*|$)`, "u");
9191
const match = regex.exec(listText);
9292
const matchedText = match[0];
9393
const matchStartOffset = listStartOffset + match.index;

lib/linter/config-comment-parser.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ module.exports = class ConfigCommentParser {
139139
const items = {};
140140

141141
string.split(",").forEach(name => {
142-
const trimmedName = name.trim();
142+
const trimmedName = name.trim().replace(/^(?<quote>['"]?)(?<ruleId>.*)\k<quote>$/us, "$<ruleId>");
143143

144144
if (trimmedName) {
145145
items[trimmedName] = true;

tests/lib/linter/config-comment-parser.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,28 @@ describe("ConfigCommentParser", () => {
224224
b: true
225225
});
226226
});
227+
228+
it("should parse list config with quoted items", () => {
229+
const code = "'a', \"b\", 'c\", \"d'";
230+
const result = commentParser.parseListConfig(code);
231+
232+
assert.deepStrictEqual(result, {
233+
a: true,
234+
b: true,
235+
"\"d'": true, // This result is correct because used mismatched quotes.
236+
"'c\"": true // This result is correct because used mismatched quotes.
237+
});
238+
});
239+
it("should parse list config with spaced items", () => {
240+
const code = " a b , 'c d' , \"e f\" ";
241+
const result = commentParser.parseListConfig(code);
242+
243+
assert.deepStrictEqual(result, {
244+
"a b": true,
245+
"c d": true,
246+
"e f": true
247+
});
248+
});
227249
});
228250

229251
});

tests/lib/linter/linter.js

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1467,6 +1467,32 @@ describe("Linter", () => {
14671467
linter.verify(code, config);
14681468
assert(spy && spy.calledOnce);
14691469
});
1470+
1471+
it("variables should be available in global scope with quoted items", () => {
1472+
const code = `/*${ESLINT_ENV} 'node'*/ function f() {} /*${ESLINT_ENV} "browser", "mocha"*/`;
1473+
const config = { rules: { checker: "error" } };
1474+
let spy;
1475+
1476+
linter.defineRule("checker", {
1477+
create(context) {
1478+
spy = sinon.spy(() => {
1479+
const scope = context.getScope(),
1480+
exports = getVariable(scope, "exports"),
1481+
window = getVariable(scope, "window"),
1482+
it = getVariable(scope, "it");
1483+
1484+
assert.strictEqual(exports.writeable, true);
1485+
assert.strictEqual(window.writeable, false);
1486+
assert.strictEqual(it.writeable, false);
1487+
});
1488+
1489+
return { Program: spy };
1490+
}
1491+
});
1492+
1493+
linter.verify(code, config);
1494+
assert(spy && spy.calledOnce);
1495+
});
14701496
});
14711497

14721498
describe("when evaluating code containing /*eslint-env */ block with sloppy whitespace", () => {
@@ -2604,6 +2630,33 @@ describe("Linter", () => {
26042630
assert.strictEqual(suppressedMessages.length, 2);
26052631
assert.strictEqual(suppressedMessages[0].ruleId, "no-alert");
26062632
});
2633+
2634+
it("should report a violation with quoted rule names in eslint-disable-line", () => {
2635+
const code = [
2636+
"alert('test'); // eslint-disable-line 'no-alert'",
2637+
"console.log('test');", // here
2638+
"alert('test'); // eslint-disable-line \"no-alert\""
2639+
].join("\n");
2640+
const config = {
2641+
rules: {
2642+
"no-alert": 1,
2643+
"no-console": 1
2644+
}
2645+
};
2646+
2647+
const messages = linter.verify(code, config, filename);
2648+
const suppressedMessages = linter.getSuppressedMessages();
2649+
2650+
assert.strictEqual(messages.length, 1);
2651+
assert.strictEqual(messages[0].ruleId, "no-console");
2652+
assert.strictEqual(messages[0].line, 2);
2653+
2654+
assert.strictEqual(suppressedMessages.length, 2);
2655+
assert.strictEqual(suppressedMessages[0].ruleId, "no-alert");
2656+
assert.strictEqual(suppressedMessages[0].line, 1);
2657+
assert.strictEqual(suppressedMessages[1].ruleId, "no-alert");
2658+
assert.strictEqual(suppressedMessages[1].line, 3);
2659+
});
26072660
});
26082661

26092662
describe("eslint-disable-next-line", () => {
@@ -2957,6 +3010,31 @@ describe("Linter", () => {
29573010

29583011
assert.strictEqual(suppressedMessages.length, 0);
29593012
});
3013+
3014+
it("should ignore violation of specified rule on next line with quoted rule names", () => {
3015+
const code = [
3016+
"// eslint-disable-next-line 'no-alert'",
3017+
"alert('test');",
3018+
"// eslint-disable-next-line \"no-alert\"",
3019+
"alert('test');",
3020+
"console.log('test');"
3021+
].join("\n");
3022+
const config = {
3023+
rules: {
3024+
"no-alert": 1,
3025+
"no-console": 1
3026+
}
3027+
};
3028+
const messages = linter.verify(code, config, filename);
3029+
const suppressedMessages = linter.getSuppressedMessages();
3030+
3031+
assert.strictEqual(messages.length, 1);
3032+
assert.strictEqual(messages[0].ruleId, "no-console");
3033+
3034+
assert.strictEqual(suppressedMessages.length, 2);
3035+
assert.strictEqual(suppressedMessages[0].ruleId, "no-alert");
3036+
assert.strictEqual(suppressedMessages[1].ruleId, "no-alert");
3037+
});
29603038
});
29613039
});
29623040

@@ -3233,6 +3311,61 @@ describe("Linter", () => {
32333311
assert.strictEqual(suppressedMessages[2].ruleId, "no-console");
32343312
assert.strictEqual(suppressedMessages[2].line, 6);
32353313
});
3314+
3315+
it("should report a violation with quoted rule names in eslint-disable", () => {
3316+
const code = [
3317+
"/*eslint-disable 'no-alert' */",
3318+
"alert('test');",
3319+
"console.log('test');", // here
3320+
"/*eslint-enable */",
3321+
"/*eslint-disable \"no-console\" */",
3322+
"alert('test');", // here
3323+
"console.log('test');"
3324+
].join("\n");
3325+
const config = { rules: { "no-alert": 1, "no-console": 1 } };
3326+
3327+
const messages = linter.verify(code, config, filename);
3328+
const suppressedMessages = linter.getSuppressedMessages();
3329+
3330+
assert.strictEqual(messages.length, 2);
3331+
assert.strictEqual(messages[0].ruleId, "no-console");
3332+
assert.strictEqual(messages[1].ruleId, "no-alert");
3333+
3334+
assert.strictEqual(suppressedMessages.length, 2);
3335+
assert.strictEqual(suppressedMessages[0].ruleId, "no-alert");
3336+
assert.strictEqual(suppressedMessages[1].ruleId, "no-console");
3337+
});
3338+
3339+
it("should report a violation with quoted rule names in eslint-enable", () => {
3340+
const code = [
3341+
"/*eslint-disable no-alert, no-console */",
3342+
"alert('test');",
3343+
"console.log('test');",
3344+
"/*eslint-enable 'no-alert'*/",
3345+
"alert('test');", // here
3346+
"console.log('test');",
3347+
"/*eslint-enable \"no-console\"*/",
3348+
"console.log('test');" // here
3349+
].join("\n");
3350+
const config = { rules: { "no-alert": 1, "no-console": 1 } };
3351+
3352+
const messages = linter.verify(code, config, filename);
3353+
const suppressedMessages = linter.getSuppressedMessages();
3354+
3355+
assert.strictEqual(messages.length, 2);
3356+
assert.strictEqual(messages[0].ruleId, "no-alert");
3357+
assert.strictEqual(messages[0].line, 5);
3358+
assert.strictEqual(messages[1].ruleId, "no-console");
3359+
assert.strictEqual(messages[1].line, 8);
3360+
3361+
assert.strictEqual(suppressedMessages.length, 3);
3362+
assert.strictEqual(suppressedMessages[0].ruleId, "no-alert");
3363+
assert.strictEqual(suppressedMessages[0].line, 2);
3364+
assert.strictEqual(suppressedMessages[1].ruleId, "no-console");
3365+
assert.strictEqual(suppressedMessages[1].line, 3);
3366+
assert.strictEqual(suppressedMessages[2].ruleId, "no-console");
3367+
assert.strictEqual(suppressedMessages[2].line, 6);
3368+
});
32363369
});
32373370

32383371
describe("when evaluating code with comments to enable and disable multiple comma separated rules", () => {
@@ -4813,6 +4946,20 @@ var a = "test2";
48134946
output
48144947
);
48154948
});
4949+
4950+
// Test for quoted rule names
4951+
for (const testcaseForLiteral of [
4952+
{ code: code.replace(/((?:un)?used[\w-]*)/gu, '"$1"'), output: output.replace(/((?:un)?used[\w-]*)/gu, '"$1"') },
4953+
{ code: code.replace(/((?:un)?used[\w-]*)/gu, "'$1'"), output: output.replace(/((?:un)?used[\w-]*)/gu, "'$1'") }
4954+
]) {
4955+
// eslint-disable-next-line no-loop-func -- `linter` is getting updated in beforeEach()
4956+
it(testcaseForLiteral.code, () => {
4957+
assert.strictEqual(
4958+
linter.verifyAndFix(testcaseForLiteral.code, config).output,
4959+
testcaseForLiteral.output
4960+
);
4961+
});
4962+
}
48164963
}
48174964
});
48184965
});
@@ -13060,6 +13207,60 @@ var a = "test2";
1306013207
assert.strictEqual(suppressedMessages[1].line, 3);
1306113208
});
1306213209

13210+
it("should report a violation with quoted rule names in eslint-disable", () => {
13211+
const code = [
13212+
"/*eslint-disable 'no-alert' */",
13213+
"alert('test');",
13214+
"console.log('test');", // here
13215+
"/*eslint-enable */",
13216+
"/*eslint-disable \"no-console\" */",
13217+
"alert('test');", // here
13218+
"console.log('test');"
13219+
].join("\n");
13220+
const config = { rules: { "no-alert": 1, "no-console": 1 } };
13221+
13222+
const messages = linter.verify(code, config, filename);
13223+
const suppressedMessages = linter.getSuppressedMessages();
13224+
13225+
assert.strictEqual(messages.length, 2);
13226+
assert.strictEqual(messages[0].ruleId, "no-console");
13227+
assert.strictEqual(messages[1].ruleId, "no-alert");
13228+
13229+
assert.strictEqual(suppressedMessages.length, 2);
13230+
assert.strictEqual(suppressedMessages[0].ruleId, "no-alert");
13231+
assert.strictEqual(suppressedMessages[1].ruleId, "no-console");
13232+
});
13233+
13234+
it("should report a violation with quoted rule names in eslint-enable", () => {
13235+
const code = [
13236+
"/*eslint-disable no-alert, no-console */",
13237+
"alert('test');",
13238+
"console.log('test');",
13239+
"/*eslint-enable 'no-alert'*/",
13240+
"alert('test');", // here
13241+
"console.log('test');",
13242+
"/*eslint-enable \"no-console\"*/",
13243+
"console.log('test');" // here
13244+
].join("\n");
13245+
const config = { rules: { "no-alert": 1, "no-console": 1 } };
13246+
13247+
const messages = linter.verify(code, config, filename);
13248+
const suppressedMessages = linter.getSuppressedMessages();
13249+
13250+
assert.strictEqual(messages.length, 2);
13251+
assert.strictEqual(messages[0].ruleId, "no-alert");
13252+
assert.strictEqual(messages[0].line, 5);
13253+
assert.strictEqual(messages[1].ruleId, "no-console");
13254+
assert.strictEqual(messages[1].line, 8);
13255+
13256+
assert.strictEqual(suppressedMessages.length, 3);
13257+
assert.strictEqual(suppressedMessages[0].ruleId, "no-alert");
13258+
assert.strictEqual(suppressedMessages[0].line, 2);
13259+
assert.strictEqual(suppressedMessages[1].ruleId, "no-console");
13260+
assert.strictEqual(suppressedMessages[1].line, 3);
13261+
assert.strictEqual(suppressedMessages[2].ruleId, "no-console");
13262+
assert.strictEqual(suppressedMessages[2].line, 6);
13263+
});
1306313264
});
1306413265

1306513266
describe("/*eslint-disable-line*/", () => {
@@ -13293,6 +13494,32 @@ var a = "test2";
1329313494
assert.strictEqual(suppressedMessages.length, 5);
1329413495
});
1329513496

13497+
it("should report a violation with quoted rule names in eslint-disable-line", () => {
13498+
const code = [
13499+
"alert('test'); // eslint-disable-line 'no-alert'",
13500+
"console.log('test');", // here
13501+
"alert('test'); // eslint-disable-line \"no-alert\""
13502+
].join("\n");
13503+
const config = {
13504+
rules: {
13505+
"no-alert": 1,
13506+
"no-console": 1
13507+
}
13508+
};
13509+
13510+
const messages = linter.verify(code, config, filename);
13511+
const suppressedMessages = linter.getSuppressedMessages();
13512+
13513+
assert.strictEqual(messages.length, 1);
13514+
assert.strictEqual(messages[0].ruleId, "no-console");
13515+
assert.strictEqual(messages[0].line, 2);
13516+
13517+
assert.strictEqual(suppressedMessages.length, 2);
13518+
assert.strictEqual(suppressedMessages[0].ruleId, "no-alert");
13519+
assert.strictEqual(suppressedMessages[0].line, 1);
13520+
assert.strictEqual(suppressedMessages[1].ruleId, "no-alert");
13521+
assert.strictEqual(suppressedMessages[1].line, 3);
13522+
});
1329613523
});
1329713524

1329813525
describe("/*eslint-disable-next-line*/", () => {
@@ -13689,6 +13916,31 @@ var a = "test2";
1368913916

1369013917
assert.strictEqual(suppressedMessages.length, 0);
1369113918
});
13919+
13920+
it("should ignore violation of specified rule on next line with quoted rule names", () => {
13921+
const code = [
13922+
"// eslint-disable-next-line 'no-alert'",
13923+
"alert('test');",
13924+
"// eslint-disable-next-line \"no-alert\"",
13925+
"alert('test');",
13926+
"console.log('test');"
13927+
].join("\n");
13928+
const config = {
13929+
rules: {
13930+
"no-alert": 1,
13931+
"no-console": 1
13932+
}
13933+
};
13934+
const messages = linter.verify(code, config, filename);
13935+
const suppressedMessages = linter.getSuppressedMessages();
13936+
13937+
assert.strictEqual(messages.length, 1);
13938+
assert.strictEqual(messages[0].ruleId, "no-console");
13939+
13940+
assert.strictEqual(suppressedMessages.length, 2);
13941+
assert.strictEqual(suppressedMessages[0].ruleId, "no-alert");
13942+
assert.strictEqual(suppressedMessages[1].ruleId, "no-alert");
13943+
});
1369213944
});
1369313945

1369413946
describe("descriptions in directive comments", () => {
@@ -15366,6 +15618,20 @@ var a = "test2";
1536615618
output
1536715619
);
1536815620
});
15621+
15622+
// Test for quoted rule names
15623+
for (const testcaseForLiteral of [
15624+
{ code: code.replace(/(test\/[\w-]+)/gu, '"$1"'), output: output.replace(/(test\/[\w-]+)/gu, '"$1"') },
15625+
{ code: code.replace(/(test\/[\w-]+)/gu, "'$1'"), output: output.replace(/(test\/[\w-]+)/gu, "'$1'") }
15626+
]) {
15627+
// eslint-disable-next-line no-loop-func -- `linter` is getting updated in beforeEach()
15628+
it(testcaseForLiteral.code, () => {
15629+
assert.strictEqual(
15630+
linter.verifyAndFix(testcaseForLiteral.code, config).output,
15631+
testcaseForLiteral.output
15632+
);
15633+
});
15634+
}
1536915635
}
1537015636
});
1537115637
});

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