Skip to content

Commit dbe7edb

Browse files
authored
feat: add support for language plugins (#257)
1 parent fffae29 commit dbe7edb

21 files changed

+646
-210
lines changed

lib/internal/disabled-area.js

+83-26
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,62 @@ module.exports = class DisabledArea {
159141

160142
return null
161143
}
144+
}
162145

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+
![
158+
"disable",
159+
"enable",
160+
"disable-line",
161+
"disable-next-line",
162+
].includes(directive.type)
163+
) {
164+
continue
165+
}
166+
const ruleIds = directive.value
167+
? directive.value.split(DELIMITER)
168+
: null
169+
170+
const loc = sourceCode.getLoc(directive.node)
171+
if (directive.type === "disable") {
172+
this._disable(directive.node, loc.start, ruleIds, "block")
173+
} else if (directive.type === "enable") {
174+
this._enable(directive.node, loc.start, ruleIds, "block")
175+
} else if (directive.type === "disable-line") {
176+
const line = loc.start.line
177+
const start = { line, column: 0 }
178+
const end = { line: line + 1, column: -1 }
179+
180+
this._disable(directive.node, start, ruleIds, "line")
181+
this._enable(directive.node, end, ruleIds, "line")
182+
} else if (directive.type === "disable-next-line") {
183+
const line = loc.start.line
184+
const start = { line: line + 1, column: 0 }
185+
const end = { line: line + 2, column: -1 }
186+
187+
this._disable(directive.node, start, ruleIds, "line")
188+
this._enable(directive.node, end, ruleIds, "line")
189+
}
190+
}
191+
}
192+
}
193+
194+
class DisabledAreaForLegacy extends DisabledArea {
163195
/**
164196
* Scan the source code and setup disabled area list.
165197
*
166198
* @param {eslint.SourceCode} sourceCode - The source code to scan.
167199
* @returns {void}
168-
* @private
169200
*/
170201
_scan(sourceCode) {
171202
for (const comment of sourceCode.getAllComments()) {
@@ -176,10 +207,12 @@ module.exports = class DisabledArea {
176207

177208
const kind = directiveComment.kind
178209
if (
179-
kind !== "eslint-disable" &&
180-
kind !== "eslint-enable" &&
181-
kind !== "eslint-disable-line" &&
182-
kind !== "eslint-disable-next-line"
210+
![
211+
"eslint-disable",
212+
"eslint-enable",
213+
"eslint-disable-line",
214+
"eslint-disable-next-line",
215+
].includes(kind)
183216
) {
184217
continue
185218
}
@@ -209,3 +242,27 @@ module.exports = class DisabledArea {
209242
}
210243
}
211244
}
245+
246+
module.exports = {
247+
/**
248+
* Get singleton instance for the given rule context.
249+
*
250+
* @param {import("@eslint/core").RuleContext} context - The rule context code to get.
251+
* @returns {DisabledArea} The singleton object for the rule context.
252+
*/
253+
getDisabledArea(context) {
254+
const sourceCode = context.sourceCode || context.getSourceCode()
255+
let retv = pool.get(sourceCode.ast)
256+
257+
if (retv == null) {
258+
retv =
259+
typeof sourceCode.getDisableDirectives === "function"
260+
? new DisabledAreaForLanguagePlugin()
261+
: new DisabledAreaForLegacy()
262+
retv._scan(sourceCode)
263+
pool.set(sourceCode.ast, retv)
264+
}
265+
266+
return retv
267+
},
268+
}
+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
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+
return sourceCode
23+
.getAllComments()
24+
.map((comment) => ({
25+
comment,
26+
directiveComment: utils.parseDirectiveComment(comment),
27+
}))
28+
.filter(({ directiveComment }) => Boolean(directiveComment))
29+
.map(
30+
({ comment, directiveComment }) =>
31+
/** @type {DirectiveComment} */ ({
32+
kind: directiveComment.kind,
33+
value: directiveComment.value,
34+
description: directiveComment.description,
35+
node: comment,
36+
range: comment.range,
37+
loc: comment.loc,
38+
})
39+
)
40+
}
41+
42+
/**
43+
* @param {import('@eslint/core').TextSourceCode} sourceCode - The source code to scan.
44+
* @returns {DirectiveComment[]} The directive comments.
45+
*/
46+
function getAllDirectiveCommentsFromInlineConfigNodes(sourceCode) {
47+
const result = sourceCode.getDisableDirectives().directives.map(
48+
(directive) =>
49+
/** @type {DirectiveComment} */ ({
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+
61+
return result.concat(
62+
sourceCode
63+
.getInlineConfigNodes()
64+
.map((node) => ({
65+
node,
66+
range: sourceCode.getRange(node),
67+
}))
68+
.filter(
69+
({ range }) =>
70+
// The node has intersection of the directive comment.
71+
// So, we need to skip it.
72+
!result.some(
73+
(comment) =>
74+
comment.range[0] <= range[1] &&
75+
range[0] <= comment.range[1]
76+
)
77+
)
78+
.map(({ node, range }) => {
79+
const nodeText = sourceCode.text.slice(range[0], range[1])
80+
const commentValue = extractCommentContent(nodeText)
81+
const directiveComment = utils.parseDirectiveText(commentValue)
82+
83+
return {
84+
directiveComment,
85+
node,
86+
range,
87+
}
88+
})
89+
.filter(
90+
({ directiveComment }) =>
91+
directiveComment != null &&
92+
![
93+
"eslint-disable",
94+
"eslint-disable-line",
95+
"eslint-disable-next-line",
96+
"eslint-enable",
97+
].includes(directiveComment.kind)
98+
)
99+
.map(
100+
({ directiveComment, node, range }) =>
101+
/** @type {DirectiveComment} */ ({
102+
kind: directiveComment.kind,
103+
value: directiveComment.value,
104+
description: directiveComment.description,
105+
node,
106+
range,
107+
get loc() {
108+
return sourceCode.getLoc(node)
109+
},
110+
})
111+
)
112+
)
113+
}
114+
115+
function extractCommentContent(text) {
116+
// Extract comment content from the comment text.
117+
// The comment format was based on the language comment definition in vscode-eslint.
118+
// See https://github.com/microsoft/vscode-eslint/blob/c0e753713ea9935667e849d91e549adbff213e7e/server/src/languageDefaults.ts#L14
119+
return text.startsWith("/*") && text.endsWith("*/")
120+
? text.slice(2, -2)
121+
: text.startsWith("//")
122+
? text.slice(2)
123+
: text.startsWith("<!--") && text.endsWith("-->")
124+
? text.slice(4, -3)
125+
: text.startsWith("###") && text.endsWith("###")
126+
? text.slice(3, -3)
127+
: text.startsWith("#")
128+
? text.slice(1)
129+
: text
130+
}
131+
132+
module.exports = {
133+
/**
134+
* Get all directive comments for the given rule context.
135+
*
136+
* @param {import("@eslint/core").RuleContext} context - The rule context to get.
137+
* @returns {DirectiveComment[]} The all directive comments object for the rule context.
138+
*/
139+
getAllDirectiveComments(context) {
140+
const sourceCode = context.sourceCode || context.getSourceCode()
141+
let result = pool.get(sourceCode.ast)
142+
143+
if (result == null) {
144+
result =
145+
typeof sourceCode.getInlineConfigNodes === "function" &&
146+
typeof sourceCode.getDisableDirectives === "function"
147+
? getAllDirectiveCommentsFromInlineConfigNodes(sourceCode)
148+
: getAllDirectiveCommentsFromAllComments(sourceCode)
149+
pool.set(sourceCode.ast, result)
150+
}
151+
152+
return result
153+
},
154+
}

0 commit comments

Comments
 (0)