Skip to content

Commit 64d8b6c

Browse files
committed
Breaking: remove parserServices.registerTemplateBodyVisitor
That was using an internal API of ESLint. The internal API was deleted in v4.6.0. So this commit changes the strategy to traverse.
1 parent 426a9e3 commit 64d8b6c

22 files changed

+1673
-76
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"debug": "^3.0.0",
1919
"eslint-scope": "^3.7.1",
2020
"espree": "^3.3.2",
21+
"esquery": "^1.0.0",
2122
"lodash": "^4.17.4"
2223
},
2324
"devDependencies": {

rollup.config.js

-3
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@ const pkg = require("./package.json")
1010
const external = [
1111
"assert",
1212
"events",
13-
"fs", //TODO: remove fs
1413
"path",
15-
"eslint/lib/util/node-event-generator",
16-
"eslint/lib/token-store",
1714
].concat(Object.keys(pkg.dependencies))
1815

1916
export default {

src/external/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
The code in this directory is copied from `eslint` repo (then I added types).
2+
Those are internal API, but I want to use those.
3+
4+
This repo does not test those additionally.

src/external/node-event-generator.ts

+314
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
/**
2+
* This file is copied from `eslint/lib/util/node-event-generator.js`
3+
*/
4+
import EventEmitter from "events"
5+
import * as esquery from "esquery"
6+
import * as lodash from "lodash"
7+
import {Node} from "../ast"
8+
9+
interface Selector {
10+
rawSelector: string
11+
isExit: boolean
12+
parsedSelector: esquery.Selector
13+
listenerTypes: string[] | null
14+
attributeCount: number
15+
identifierCount: number
16+
}
17+
18+
/**
19+
* Gets the possible types of a selector
20+
* @param parsedSelector An object (from esquery) describing the matching behavior of the selector
21+
* @returns The node types that could possibly trigger this selector, or `null` if all node types could trigger it
22+
*/
23+
function getPossibleTypes(parsedSelector: esquery.Selector): string[] | null {
24+
switch (parsedSelector.type) {
25+
case "identifier":
26+
return [parsedSelector.value]
27+
28+
case "matches": {
29+
const typesForComponents = parsedSelector.selectors.map(getPossibleTypes)
30+
31+
if (typesForComponents.every(Boolean)) {
32+
return lodash.union.apply(null, typesForComponents)
33+
}
34+
return null
35+
}
36+
37+
case "compound": {
38+
const typesForComponents = parsedSelector.selectors.map(getPossibleTypes).filter(typesForComponent => typesForComponent)
39+
40+
// If all of the components could match any type, then the compound could also match any type.
41+
if (!typesForComponents.length) {
42+
return null
43+
}
44+
45+
/*
46+
* If at least one of the components could only match a particular type, the compound could only match
47+
* the intersection of those types.
48+
*/
49+
return lodash.intersection.apply(null, typesForComponents)
50+
}
51+
52+
case "child":
53+
case "descendant":
54+
case "sibling":
55+
case "adjacent":
56+
return getPossibleTypes(parsedSelector.right)
57+
58+
default:
59+
return null
60+
}
61+
}
62+
63+
/**
64+
* Counts the number of class, pseudo-class, and attribute queries in this selector
65+
* @param parsedSelector An object (from esquery) describing the selector's matching behavior
66+
* @returns The number of class, pseudo-class, and attribute queries in this selector
67+
*/
68+
function countClassAttributes(parsedSelector: esquery.Selector): number {
69+
switch (parsedSelector.type) {
70+
case "child":
71+
case "descendant":
72+
case "sibling":
73+
case "adjacent":
74+
return countClassAttributes(parsedSelector.left) + countClassAttributes(parsedSelector.right)
75+
76+
case "compound":
77+
case "not":
78+
case "matches":
79+
return parsedSelector.selectors.reduce((sum, childSelector) => sum + countClassAttributes(childSelector), 0)
80+
81+
case "attribute":
82+
case "field":
83+
case "nth-child":
84+
case "nth-last-child":
85+
return 1
86+
87+
default:
88+
return 0
89+
}
90+
}
91+
92+
/**
93+
* Counts the number of identifier queries in this selector
94+
* @param parsedSelector An object (from esquery) describing the selector's matching behavior
95+
* @returns The number of identifier queries
96+
*/
97+
function countIdentifiers(parsedSelector: esquery.Selector): number {
98+
switch (parsedSelector.type) {
99+
case "child":
100+
case "descendant":
101+
case "sibling":
102+
case "adjacent":
103+
return countIdentifiers(parsedSelector.left) + countIdentifiers(parsedSelector.right)
104+
105+
case "compound":
106+
case "not":
107+
case "matches":
108+
return parsedSelector.selectors.reduce((sum, childSelector) => sum + countIdentifiers(childSelector), 0)
109+
110+
case "identifier":
111+
return 1
112+
113+
default:
114+
return 0
115+
}
116+
}
117+
118+
/**
119+
* Compares the specificity of two selector objects, with CSS-like rules.
120+
* @param selectorA An AST selector descriptor
121+
* @param selectorB Another AST selector descriptor
122+
* @returns
123+
* a value less than 0 if selectorA is less specific than selectorB
124+
* a value greater than 0 if selectorA is more specific than selectorB
125+
* a value less than 0 if selectorA and selectorB have the same specificity, and selectorA <= selectorB alphabetically
126+
* a value greater than 0 if selectorA and selectorB have the same specificity, and selectorA > selectorB alphabetically
127+
*/
128+
function compareSpecificity(selectorA: Selector, selectorB: Selector): number {
129+
return selectorA.attributeCount - selectorB.attributeCount ||
130+
selectorA.identifierCount - selectorB.identifierCount ||
131+
(selectorA.rawSelector <= selectorB.rawSelector ? -1 : 1)
132+
}
133+
134+
/**
135+
* Parses a raw selector string, and throws a useful error if parsing fails.
136+
* @param rawSelector A raw AST selector
137+
* @returns An object (from esquery) describing the matching behavior of this selector
138+
* @throws An error if the selector is invalid
139+
*/
140+
function tryParseSelector(rawSelector: string): esquery.Selector {
141+
try {
142+
return esquery.parse(rawSelector.replace(/:exit$/, ""))
143+
}
144+
catch (err) {
145+
if (typeof err.offset === "number") {
146+
throw new Error(`Syntax error in selector "${rawSelector}" at position ${err.offset}: ${err.message}`)
147+
}
148+
throw err
149+
}
150+
}
151+
152+
/**
153+
* Parses a raw selector string, and returns the parsed selector along with specificity and type information.
154+
* @param {string} rawSelector A raw AST selector
155+
* @returns {ASTSelector} A selector descriptor
156+
*/
157+
const parseSelector = lodash.memoize<(rawSelector: string) => Selector>(rawSelector => {
158+
const parsedSelector = tryParseSelector(rawSelector)
159+
160+
return {
161+
rawSelector,
162+
isExit: rawSelector.endsWith(":exit"),
163+
parsedSelector,
164+
listenerTypes: getPossibleTypes(parsedSelector),
165+
attributeCount: countClassAttributes(parsedSelector),
166+
identifierCount: countIdentifiers(parsedSelector),
167+
}
168+
})
169+
170+
//------------------------------------------------------------------------------
171+
// Public Interface
172+
//------------------------------------------------------------------------------
173+
174+
/**
175+
* The event generator for AST nodes.
176+
* This implements below interface.
177+
*
178+
* ```ts
179+
* interface EventGenerator {
180+
* emitter: EventEmitter
181+
* enterNode(node: ASTNode): void
182+
* leaveNode(node: ASTNode): void
183+
* }
184+
* ```
185+
*/
186+
export default class NodeEventGenerator {
187+
emitter: EventEmitter
188+
189+
private currentAncestry: Node[]
190+
private enterSelectorsByNodeType: Map<string, Selector[]>
191+
private exitSelectorsByNodeType: Map<string, Selector[]>
192+
private anyTypeEnterSelectors: Selector[]
193+
private anyTypeExitSelectors: Selector[]
194+
195+
/**
196+
* @param emitter - An event emitter which is the destination of events. This emitter must already
197+
* have registered listeners for all of the events that it needs to listen for.
198+
*/
199+
constructor(emitter: EventEmitter) {
200+
this.emitter = emitter
201+
this.currentAncestry = []
202+
this.enterSelectorsByNodeType = new Map()
203+
this.exitSelectorsByNodeType = new Map()
204+
this.anyTypeEnterSelectors = []
205+
this.anyTypeExitSelectors = []
206+
207+
const eventNames = typeof emitter.eventNames === "function"
208+
209+
// Use the built-in eventNames() function if available (Node 6+)
210+
? emitter.eventNames()
211+
212+
/*
213+
* Otherwise, use the private _events property.
214+
* Using a private property isn't ideal here, but this seems to
215+
* be the best way to get a list of event names without overriding
216+
* addEventListener, which would hurt performance. This property
217+
* is widely used and unlikely to be removed in a future version
218+
* (see https://github.com/nodejs/node/issues/1817). Also, future
219+
* node versions will have eventNames() anyway.
220+
*/
221+
: Object.keys((emitter as any)._events)
222+
223+
for (const rawSelector of eventNames) {
224+
if (typeof rawSelector === "symbol") {
225+
continue
226+
}
227+
const selector = parseSelector(rawSelector)
228+
229+
if (selector.listenerTypes) {
230+
for (const nodeType of selector.listenerTypes) {
231+
const typeMap = selector.isExit ? this.exitSelectorsByNodeType : this.enterSelectorsByNodeType
232+
233+
let selectors = typeMap.get(nodeType)
234+
if (selectors == null) {
235+
typeMap.set(nodeType, (selectors = []))
236+
}
237+
selectors.push(selector)
238+
}
239+
}
240+
else {
241+
(selector.isExit ? this.anyTypeExitSelectors : this.anyTypeEnterSelectors).push(selector)
242+
}
243+
}
244+
245+
this.anyTypeEnterSelectors.sort(compareSpecificity)
246+
this.anyTypeExitSelectors.sort(compareSpecificity)
247+
for (const selectorList of this.enterSelectorsByNodeType.values()) {
248+
selectorList.sort(compareSpecificity)
249+
}
250+
for (const selectorList of this.exitSelectorsByNodeType.values()) {
251+
selectorList.sort(compareSpecificity)
252+
}
253+
}
254+
255+
/**
256+
* Checks a selector against a node, and emits it if it matches
257+
* @param node The node to check
258+
* @param selector An AST selector descriptor
259+
*/
260+
private applySelector(node: Node, selector: Selector): void {
261+
if (esquery.matches(node, selector.parsedSelector, this.currentAncestry)) {
262+
this.emitter.emit(selector.rawSelector, node)
263+
}
264+
}
265+
266+
/**
267+
* Applies all appropriate selectors to a node, in specificity order
268+
* @param node The node to check
269+
* @param isExit `false` if the node is currently being entered, `true` if it's currently being exited
270+
*/
271+
private applySelectors(node: Node, isExit: boolean): void {
272+
const selectorsByNodeType = (isExit ? this.exitSelectorsByNodeType : this.enterSelectorsByNodeType).get(node.type) || []
273+
const anyTypeSelectors = isExit ? this.anyTypeExitSelectors : this.anyTypeEnterSelectors
274+
275+
/*
276+
* selectorsByNodeType and anyTypeSelectors were already sorted by specificity in the constructor.
277+
* Iterate through each of them, applying selectors in the right order.
278+
*/
279+
let selectorsByTypeIndex = 0
280+
let anyTypeSelectorsIndex = 0
281+
282+
while (selectorsByTypeIndex < selectorsByNodeType.length || anyTypeSelectorsIndex < anyTypeSelectors.length) {
283+
if (
284+
selectorsByTypeIndex >= selectorsByNodeType.length ||
285+
(anyTypeSelectorsIndex < anyTypeSelectors.length && compareSpecificity(anyTypeSelectors[anyTypeSelectorsIndex], selectorsByNodeType[selectorsByTypeIndex]) < 0)
286+
) {
287+
this.applySelector(node, anyTypeSelectors[anyTypeSelectorsIndex++])
288+
}
289+
else {
290+
this.applySelector(node, selectorsByNodeType[selectorsByTypeIndex++])
291+
}
292+
}
293+
}
294+
295+
/**
296+
* Emits an event of entering AST node.
297+
* @param node - A node which was entered.
298+
*/
299+
enterNode(node: Node): void {
300+
if (node.parent) {
301+
this.currentAncestry.unshift(node.parent)
302+
}
303+
this.applySelectors(node, false)
304+
}
305+
306+
/**
307+
* Emits an event of leaving AST node.
308+
* @param node - A node which was left.
309+
*/
310+
leaveNode(node: Node): void {
311+
this.applySelectors(node, true)
312+
this.currentAncestry.shift()
313+
}
314+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* @fileoverview Define the cursor which iterates tokens and comments in reverse.
3+
* @author Toru Nagashima
4+
*/
5+
import {Token} from "../../../ast"
6+
import {getLastIndex, search} from "../utils"
7+
import Cursor from "./cursor"
8+
9+
/**
10+
* The cursor which iterates tokens and comments in reverse.
11+
*/
12+
export default class BackwardTokenCommentCursor extends Cursor {
13+
private tokens: Token[]
14+
private comments: Token[]
15+
private tokenIndex: number
16+
private commentIndex: number
17+
private border: number
18+
19+
/**
20+
* Initializes this cursor.
21+
* @param tokens - The array of tokens.
22+
* @param comments - The array of comments.
23+
* @param indexMap - The map from locations to indices in `tokens`.
24+
* @param startLoc - The start location of the iteration range.
25+
* @param endLoc - The end location of the iteration range.
26+
*/
27+
constructor(tokens: Token[], comments: Token[], indexMap: { [key: number]: number }, startLoc: number, endLoc: number) {
28+
super()
29+
this.tokens = tokens
30+
this.comments = comments
31+
this.tokenIndex = getLastIndex(tokens, indexMap, endLoc)
32+
this.commentIndex = search(comments, endLoc) - 1
33+
this.border = startLoc
34+
}
35+
36+
/** @inheritdoc */
37+
moveNext(): boolean {
38+
const token = (this.tokenIndex >= 0) ? this.tokens[this.tokenIndex] : null
39+
const comment = (this.commentIndex >= 0) ? this.comments[this.commentIndex] : null
40+
41+
if (token && (!comment || token.range[1] > comment.range[1])) {
42+
this.current = token
43+
this.tokenIndex -= 1
44+
}
45+
else if (comment) {
46+
this.current = comment
47+
this.commentIndex -= 1
48+
}
49+
else {
50+
this.current = null
51+
}
52+
53+
return this.current != null && (this.border === -1 || this.current.range[0] >= this.border)
54+
}
55+
}

0 commit comments

Comments
 (0)