Skip to content

Commit de328c8

Browse files
committed
feat: add support for language plugins
1 parent fffae29 commit de328c8

12 files changed

+274
-81
lines changed

lib/internal/disabled-area.js

Lines changed: 76 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,7 @@ const utils = require("./utils")
88
const DELIMITER = /[\s,]+/gu
99
const pool = new WeakMap()
1010

11-
module.exports = class DisabledArea {
12-
/**
13-
* Get singleton instance for the given source code.
14-
*
15-
* @param {eslint.SourceCode} sourceCode - The source code to get.
16-
* @returns {DisabledArea} The singleton object for the source code.
17-
*/
18-
static get(sourceCode) {
19-
let retv = pool.get(sourceCode.ast)
20-
21-
if (retv == null) {
22-
retv = new DisabledArea()
23-
retv._scan(sourceCode)
24-
pool.set(sourceCode.ast, retv)
25-
}
26-
27-
return retv
28-
}
29-
11+
class DisabledArea {
3012
/**
3113
* Constructor.
3214
*/
@@ -45,7 +27,7 @@ module.exports = class DisabledArea {
4527
* @param {string[]|null} ruleIds - The ruleId names to disable.
4628
* @param {string} kind - The kind of disable-comments.
4729
* @returns {void}
48-
* @private
30+
* @protected
4931
*/
5032
_disable(comment, location, ruleIds, kind) {
5133
if (ruleIds) {
@@ -85,7 +67,7 @@ module.exports = class DisabledArea {
8567
* @param {string[]|null} ruleIds - The ruleId names to enable.
8668
* @param {string} kind - The kind of disable-comments.
8769
* @returns {void}
88-
* @private
70+
* @protected
8971
*/
9072
_enable(comment, location, ruleIds, kind) {
9173
const relatedDisableDirectives = new Set()
@@ -159,13 +141,60 @@ module.exports = class DisabledArea {
159141

160142
return null
161143
}
144+
}
145+
146+
class DisabledAreaForLanguagePlugin extends DisabledArea {
147+
/**
148+
* Scan the source code and setup disabled area list.
149+
*
150+
* @param {import('@eslint/core').TextSourceCode} sourceCode - The source code to scan.
151+
* @returns {void}
152+
*/
153+
_scan(sourceCode) {
154+
const disableDirectives = sourceCode.getDisableDirectives()
155+
for (const directive of disableDirectives.directives) {
156+
if (
157+
directive.type !== "disable" &&
158+
directive.type !== "enable" &&
159+
directive.type !== "disable-line" &&
160+
directive.type !== "disable-next-line"
161+
) {
162+
continue
163+
}
164+
const ruleIds = directive.value
165+
? directive.value.split(DELIMITER)
166+
: null
167+
168+
const loc = sourceCode.getLoc(directive.node)
169+
if (directive.type === "disable") {
170+
this._disable(directive.node, loc.start, ruleIds, "block")
171+
} else if (directive.type === "enable") {
172+
this._enable(directive.node, loc.start, ruleIds, "block")
173+
} else if (directive.type === "disable-line") {
174+
const line = loc.start.line
175+
const start = { line, column: 0 }
176+
const end = { line: line + 1, column: -1 }
177+
178+
this._disable(directive.node, start, ruleIds, "line")
179+
this._enable(directive.node, end, ruleIds, "line")
180+
} else if (directive.type === "disable-next-line") {
181+
const line = loc.start.line
182+
const start = { line: line + 1, column: 0 }
183+
const end = { line: line + 2, column: -1 }
184+
185+
this._disable(directive.node, start, ruleIds, "line")
186+
this._enable(directive.node, end, ruleIds, "line")
187+
}
188+
}
189+
}
190+
}
162191

192+
class DisabledAreaForLegacy extends DisabledArea {
163193
/**
164194
* Scan the source code and setup disabled area list.
165195
*
166196
* @param {eslint.SourceCode} sourceCode - The source code to scan.
167197
* @returns {void}
168-
* @private
169198
*/
170199
_scan(sourceCode) {
171200
for (const comment of sourceCode.getAllComments()) {
@@ -209,3 +238,28 @@ module.exports = class DisabledArea {
209238
}
210239
}
211240
}
241+
242+
module.exports = {
243+
/**
244+
* Get singleton instance for the given rule context.
245+
*
246+
* @param {import("@eslint/core").RuleContext} context - The rule context code to get.
247+
* @returns {DisabledArea} The singleton object for the rule context.
248+
*/
249+
getDisabledArea(context) {
250+
const sourceCode = context.sourceCode || context.getSourceCode()
251+
let retv = pool.get(sourceCode.ast)
252+
253+
if (retv == null) {
254+
if (typeof sourceCode.getDisableDirectives === "function") {
255+
retv = new DisabledAreaForLanguagePlugin()
256+
} else {
257+
retv = new DisabledAreaForLegacy()
258+
}
259+
retv._scan(sourceCode)
260+
pool.set(sourceCode.ast, retv)
261+
}
262+
263+
return retv
264+
},
265+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
"use strict"
2+
3+
const utils = require("./utils")
4+
5+
/**
6+
* @typedef {object} DirectiveComment
7+
* @property {string} kind The kind of directive comment.
8+
* @property {string} [value] The directive value if it is `eslint-` comment.
9+
* @property {string} description The description of the directive comment.
10+
* @property {object} node The node of the directive comment.
11+
* @property {import("@eslint/core").SourceRange} range The range of the directive comment.
12+
* @property {import("@eslint/core").SourceLocation} loc The location of the directive comment.
13+
*/
14+
15+
const pool = new WeakMap()
16+
17+
/**
18+
* @param {import('eslint').SourceCode} sourceCode - The source code to scan.
19+
* @returns {DirectiveComment[]} The directive comments.
20+
*/
21+
function getAllDirectiveCommentsFromAllComments(sourceCode) {
22+
/** @type {DirectiveComment[]} */
23+
const result = []
24+
for (const comment of sourceCode.getAllComments()) {
25+
const directiveComment = utils.parseDirectiveComment(comment)
26+
if (directiveComment != null) {
27+
result.push({
28+
kind: directiveComment.kind,
29+
value: directiveComment.value,
30+
description: directiveComment.description,
31+
node: comment,
32+
range: comment.range,
33+
loc: comment.loc,
34+
})
35+
}
36+
}
37+
return result
38+
}
39+
40+
/**
41+
* @param {import('@eslint/core').TextSourceCode} sourceCode - The source code to scan.
42+
* @returns {DirectiveComment[]} The directive comments.
43+
*/
44+
function getAllDirectiveCommentsFromInlineConfigNodes(sourceCode) {
45+
/** @type {DirectiveComment[]} */
46+
const result = []
47+
const disableDirectives = sourceCode.getDisableDirectives()
48+
for (const directive of disableDirectives.directives) {
49+
result.push({
50+
kind: `eslint-${directive.type}`,
51+
value: directive.value,
52+
description: directive.justification,
53+
node: directive.node,
54+
range: sourceCode.getRange(directive.node),
55+
get loc() {
56+
return sourceCode.getLoc(directive.node)
57+
},
58+
})
59+
}
60+
for (const node of sourceCode.getInlineConfigNodes()) {
61+
const range = sourceCode.getRange(node)
62+
// The node has intersection of the directive comment.
63+
// So, we need to skip it.
64+
if (
65+
result.some(
66+
(comment) =>
67+
comment.range[0] > range[1] && range[0] < comment.range[1]
68+
)
69+
) {
70+
continue
71+
}
72+
const nodeText = sourceCode.text.slice(range[0], range[1])
73+
// Extract comment content from the comment text.
74+
// The comment format was based on the language comment definition in vscode-eslint.
75+
// See https://github.com/microsoft/vscode-eslint/blob/c0e753713ea9935667e849d91e549adbff213e7e/server/src/languageDefaults.ts#L14
76+
const commentValue =
77+
nodeText.startsWith("/*") && nodeText.startsWith("*/")
78+
? nodeText.slice(2, -2)
79+
: nodeText.startsWith("//")
80+
? nodeText.slice(2)
81+
: nodeText.startsWith("<!--") && nodeText.endsWith("-->")
82+
? nodeText.slice(4, -3)
83+
: nodeText.startsWith("###") && nodeText.endsWith("###")
84+
? nodeText.slice(1)
85+
: nodeText.startsWith("#")
86+
? nodeText.slice(1)
87+
: nodeText
88+
const directiveComment = utils.parseDirectiveText(commentValue)
89+
if (directiveComment != null) {
90+
result.push({
91+
kind: directiveComment.kind,
92+
value: directiveComment.value,
93+
description: directiveComment.description,
94+
node,
95+
range,
96+
get loc() {
97+
return sourceCode.getLoc(node)
98+
},
99+
})
100+
}
101+
}
102+
return result
103+
}
104+
105+
module.exports = {
106+
/**
107+
* Get all directive comments for the given rule context.
108+
*
109+
* @param {import("@eslint/core").RuleContext} context - The rule context to get.
110+
* @returns {DirectiveComment[]} The all directive comments object for the rule context.
111+
*/
112+
getAllDirectiveComments(context) {
113+
const sourceCode = context.sourceCode || context.getSourceCode()
114+
let result = pool.get(sourceCode.ast)
115+
116+
if (result == null) {
117+
if (
118+
typeof sourceCode.getInlineConfigNodes === "function" &&
119+
typeof sourceCode.getDisableDirectives === "function"
120+
) {
121+
result =
122+
getAllDirectiveCommentsFromInlineConfigNodes(sourceCode)
123+
} else {
124+
result = getAllDirectiveCommentsFromAllComments(sourceCode)
125+
}
126+
pool.set(sourceCode.ast, result)
127+
}
128+
129+
return result
130+
},
131+
}

lib/internal/utils.js

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,12 @@ module.exports = {
107107
* @returns {{kind: string, value: string, description: string | null}|null} The parsed data of the given comment. If `null`, it is not a directive comment.
108108
*/
109109
parseDirectiveComment(comment) {
110-
const { text, description } = divideDirectiveComment(comment.value)
111-
const match = DIRECTIVE_PATTERN.exec(text)
112-
113-
if (!match) {
110+
const parsed = parseDirectiveText(comment.value)
111+
if (!parsed) {
114112
return null
115113
}
116-
const directiveText = match[1]
117-
const lineCommentSupported = LINE_COMMENT_PATTERN.test(directiveText)
114+
115+
const lineCommentSupported = LINE_COMMENT_PATTERN.test(parsed.kind)
118116

119117
if (comment.type === "Line" && !lineCommentSupported) {
120118
return null
@@ -128,14 +126,33 @@ module.exports = {
128126
return null
129127
}
130128

131-
const directiveValue = text.slice(match.index + directiveText.length)
132-
133-
return {
134-
kind: directiveText,
135-
value: directiveValue.trim(),
136-
description,
137-
}
129+
return parsed
138130
},
131+
parseDirectiveText,
132+
}
133+
134+
/**
135+
* Parse the given text as a directive comment.
136+
*
137+
* @param {string} textToParse - The text to parse.
138+
* @returns {{kind: string, value: string, description: string | null}|null} The parsed data of the given comment. If `null`, it is not a directive comment.
139+
*/
140+
function parseDirectiveText(textToParse) {
141+
const { text, description } = divideDirectiveComment(textToParse)
142+
const match = DIRECTIVE_PATTERN.exec(text)
143+
144+
if (!match) {
145+
return null
146+
}
147+
const directiveText = match[1]
148+
149+
const directiveValue = text.slice(match.index + directiveText.length)
150+
151+
return {
152+
kind: directiveText,
153+
value: directiveValue.trim(),
154+
description,
155+
}
139156
}
140157

141158
/**

lib/rules/disable-enable-pair.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55
"use strict"
66

7-
const DisabledArea = require("../internal/disabled-area")
7+
const { getDisabledArea } = require("../internal/disabled-area")
88
const utils = require("../internal/utils")
99

1010
module.exports = {
@@ -39,8 +39,7 @@ module.exports = {
3939
create(context) {
4040
const allowWholeFile =
4141
context.options[0] && context.options[0].allowWholeFile
42-
const sourceCode = context.getSourceCode()
43-
const disabledArea = DisabledArea.get(sourceCode)
42+
const disabledArea = getDisabledArea(context)
4443

4544
return {
4645
Program(node) {

lib/rules/no-aggregating-enable.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55
"use strict"
66

7-
const DisabledArea = require("../internal/disabled-area")
7+
const { getDisabledArea } = require("../internal/disabled-area")
88
const utils = require("../internal/utils")
99

1010
module.exports = {
@@ -26,8 +26,7 @@ module.exports = {
2626
},
2727

2828
create(context) {
29-
const sourceCode = context.getSourceCode()
30-
const disabledArea = DisabledArea.get(sourceCode)
29+
const disabledArea = getDisabledArea(context)
3130

3231
return {
3332
Program() {

lib/rules/no-duplicate-disable.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55
"use strict"
66

7-
const DisabledArea = require("../internal/disabled-area")
7+
const { getDisabledArea } = require("../internal/disabled-area")
88
const utils = require("../internal/utils")
99

1010
module.exports = {
@@ -25,8 +25,7 @@ module.exports = {
2525
},
2626

2727
create(context) {
28-
const sourceCode = context.getSourceCode()
29-
const disabledArea = DisabledArea.get(sourceCode)
28+
const disabledArea = getDisabledArea(context)
3029

3130
return {
3231
Program() {

0 commit comments

Comments
 (0)