Skip to content
This repository was archived by the owner on Aug 18, 2021. It is now read-only.

Commit dbc6546

Browse files
mysticateanot-an-aardvark
authored andcommitted
Use new scopeManager/visitorKeys APIs (#542)
(fixes eslint/eslint#9762)
1 parent 1f220c2 commit dbc6546

16 files changed

+592
-158
lines changed

lib/analyze-scope.js

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
"use strict";
2+
3+
const t = require("@babel/types");
4+
const escope = require("eslint-scope");
5+
const Definition = require("eslint-scope/lib/definition").Definition;
6+
const OriginalPatternVisitor = require("eslint-scope/lib/pattern-visitor");
7+
const OriginalReferencer = require("eslint-scope/lib/referencer");
8+
const fallback = require("eslint-visitor-keys").getKeys;
9+
const childVisitorKeys = require("./visitor-keys");
10+
11+
const flowFlippedAliasKeys = t.FLIPPED_ALIAS_KEYS.Flow.concat([
12+
"ArrayPattern",
13+
"ClassDeclaration",
14+
"ClassExpression",
15+
"FunctionDeclaration",
16+
"FunctionExpression",
17+
"Identifier",
18+
"ObjectPattern",
19+
"RestElement",
20+
]);
21+
const visitorKeysMap = Object.keys(t.VISITOR_KEYS).reduce(function(acc, key) {
22+
const value = t.VISITOR_KEYS[key];
23+
if (flowFlippedAliasKeys.indexOf(value) === -1) {
24+
acc[key] = value;
25+
}
26+
return acc;
27+
}, {});
28+
29+
const propertyTypes = {
30+
// loops
31+
callProperties: { type: "loop", values: ["value"] },
32+
indexers: { type: "loop", values: ["key", "value"] },
33+
properties: { type: "loop", values: ["argument", "value"] },
34+
types: { type: "loop" },
35+
params: { type: "loop" },
36+
// single property
37+
argument: { type: "single" },
38+
elementType: { type: "single" },
39+
qualification: { type: "single" },
40+
rest: { type: "single" },
41+
returnType: { type: "single" },
42+
// others
43+
typeAnnotation: { type: "typeAnnotation" },
44+
typeParameters: { type: "typeParameters" },
45+
id: { type: "id" },
46+
};
47+
48+
class PatternVisitor extends OriginalPatternVisitor {
49+
ArrayPattern(node) {
50+
node.elements.forEach(this.visit, this);
51+
}
52+
53+
ObjectPattern(node) {
54+
node.properties.forEach(this.visit, this);
55+
}
56+
}
57+
58+
class Referencer extends OriginalReferencer {
59+
// inherits.
60+
visitPattern(node, options, callback) {
61+
if (!node) {
62+
return;
63+
}
64+
65+
// Visit type annotations.
66+
this._checkIdentifierOrVisit(node.typeAnnotation);
67+
if (t.isAssignmentPattern(node)) {
68+
this._checkIdentifierOrVisit(node.left.typeAnnotation);
69+
}
70+
71+
// Overwrite `super.visitPattern(node, options, callback)` in order to not visit `ArrayPattern#typeAnnotation` and `ObjectPattern#typeAnnotation`.
72+
if (typeof options === "function") {
73+
callback = options;
74+
options = { processRightHandNodes: false };
75+
}
76+
77+
const visitor = new PatternVisitor(this.options, node, callback);
78+
visitor.visit(node);
79+
80+
// Process the right hand nodes recursively.
81+
if (options.processRightHandNodes) {
82+
visitor.rightHandNodes.forEach(this.visit, this);
83+
}
84+
}
85+
86+
// inherits.
87+
visitClass(node) {
88+
// Decorators.
89+
this._visitArray(node.decorators);
90+
91+
// Flow type parameters.
92+
const typeParamScope = this._nestTypeParamScope(node);
93+
94+
// Flow super types.
95+
this._visitTypeAnnotation(node.implements);
96+
this._visitTypeAnnotation(
97+
node.superTypeParameters && node.superTypeParameters.params
98+
);
99+
100+
// Basic.
101+
super.visitClass(node);
102+
103+
// Close the type parameter scope.
104+
if (typeParamScope) {
105+
this.close(node);
106+
}
107+
}
108+
109+
// inherits.
110+
visitFunction(node) {
111+
const typeParamScope = this._nestTypeParamScope(node);
112+
113+
// Flow return types.
114+
this._checkIdentifierOrVisit(node.returnType);
115+
116+
// Basic.
117+
super.visitFunction(node);
118+
119+
// Close the type parameter scope.
120+
if (typeParamScope) {
121+
this.close(node);
122+
}
123+
}
124+
125+
// inherits.
126+
visitProperty(node) {
127+
if (node.value && node.value.type === "TypeCastExpression") {
128+
this._visitTypeAnnotation(node.value);
129+
}
130+
this._visitArray(node.decorators);
131+
super.visitProperty(node);
132+
}
133+
134+
InterfaceDeclaration(node) {
135+
this._createScopeVariable(node, node.id);
136+
137+
const typeParamScope = this._nestTypeParamScope(node);
138+
139+
// TODO: Handle mixins
140+
this._visitArray(node.extends);
141+
this.visit(node.body);
142+
143+
if (typeParamScope) {
144+
this.close(node);
145+
}
146+
}
147+
148+
TypeAlias(node) {
149+
this._createScopeVariable(node, node.id);
150+
151+
const typeParamScope = this._nestTypeParamScope(node);
152+
153+
this.visit(node.right);
154+
155+
if (typeParamScope) {
156+
this.close(node);
157+
}
158+
}
159+
160+
ClassProperty(node) {
161+
this._visitClassProperty(node);
162+
}
163+
164+
ClassPrivateProperty(node) {
165+
this._visitClassProperty(node);
166+
}
167+
168+
DeclareModule(node) {
169+
this._visitDeclareX(node);
170+
}
171+
172+
DeclareFunction(node) {
173+
this._visitDeclareX(node);
174+
}
175+
176+
DeclareVariable(node) {
177+
this._visitDeclareX(node);
178+
}
179+
180+
DeclareClass(node) {
181+
this._visitDeclareX(node);
182+
}
183+
184+
_visitClassProperty(node) {
185+
this._visitTypeAnnotation(node.typeAnnotation);
186+
this.visitProperty(node);
187+
}
188+
189+
_visitDeclareX(node) {
190+
if (node.id) {
191+
this._createScopeVariable(node, node.id);
192+
}
193+
194+
const typeParamScope = this._nestTypeParamScope(node);
195+
if (typeParamScope) {
196+
this.close(node);
197+
}
198+
}
199+
200+
_createScopeVariable(node, name) {
201+
this.currentScope().variableScope.__define(
202+
name,
203+
new Definition("Variable", name, node, null, null, null)
204+
);
205+
}
206+
207+
_nestTypeParamScope(node) {
208+
if (!node.typeParameters) {
209+
return null;
210+
}
211+
212+
const parentScope = this.scopeManager.__currentScope;
213+
const scope = new escope.Scope(
214+
this.scopeManager,
215+
"type-parameters",
216+
parentScope,
217+
node,
218+
false
219+
);
220+
221+
this.scopeManager.__nestScope(scope);
222+
for (let j = 0; j < node.typeParameters.params.length; j++) {
223+
const name = node.typeParameters.params[j];
224+
scope.__define(name, new Definition("TypeParameter", name, name));
225+
if (name.typeAnnotation) {
226+
this._checkIdentifierOrVisit(name);
227+
}
228+
}
229+
scope.__define = function() {
230+
return parentScope.__define.apply(parentScope, arguments);
231+
};
232+
233+
return scope;
234+
}
235+
236+
_visitTypeAnnotation(node) {
237+
if (!node) {
238+
return;
239+
}
240+
if (Array.isArray(node)) {
241+
node.forEach(this._visitTypeAnnotation, this);
242+
return;
243+
}
244+
245+
// get property to check (params, id, etc...)
246+
const visitorValues = visitorKeysMap[node.type];
247+
if (!visitorValues) {
248+
return;
249+
}
250+
251+
// can have multiple properties
252+
for (let i = 0; i < visitorValues.length; i++) {
253+
const visitorValue = visitorValues[i];
254+
const propertyType = propertyTypes[visitorValue];
255+
const nodeProperty = node[visitorValue];
256+
// check if property or type is defined
257+
if (propertyType == null || nodeProperty == null) {
258+
continue;
259+
}
260+
if (propertyType.type === "loop") {
261+
for (let j = 0; j < nodeProperty.length; j++) {
262+
if (Array.isArray(propertyType.values)) {
263+
for (let k = 0; k < propertyType.values.length; k++) {
264+
const loopPropertyNode = nodeProperty[j][propertyType.values[k]];
265+
if (loopPropertyNode) {
266+
this._checkIdentifierOrVisit(loopPropertyNode);
267+
}
268+
}
269+
} else {
270+
this._checkIdentifierOrVisit(nodeProperty[j]);
271+
}
272+
}
273+
} else if (propertyType.type === "single") {
274+
this._checkIdentifierOrVisit(nodeProperty);
275+
} else if (propertyType.type === "typeAnnotation") {
276+
this._visitTypeAnnotation(node.typeAnnotation);
277+
} else if (propertyType.type === "typeParameters") {
278+
for (let l = 0; l < node.typeParameters.params.length; l++) {
279+
this._checkIdentifierOrVisit(node.typeParameters.params[l]);
280+
}
281+
} else if (propertyType.type === "id") {
282+
if (node.id.type === "Identifier") {
283+
this._checkIdentifierOrVisit(node.id);
284+
} else {
285+
this._visitTypeAnnotation(node.id);
286+
}
287+
}
288+
}
289+
}
290+
291+
_checkIdentifierOrVisit(node) {
292+
if (node && node.typeAnnotation) {
293+
this._visitTypeAnnotation(node.typeAnnotation);
294+
} else if (node && node.type === "Identifier") {
295+
this.visit(node);
296+
} else {
297+
this._visitTypeAnnotation(node);
298+
}
299+
}
300+
301+
_visitArray(nodeList) {
302+
if (nodeList) {
303+
for (const node of nodeList) {
304+
this.visit(node);
305+
}
306+
}
307+
}
308+
}
309+
310+
module.exports = function(ast, parserOptions) {
311+
const options = {
312+
optimistic: false,
313+
directive: false,
314+
nodejsScope:
315+
ast.sourceType === "script" &&
316+
(parserOptions.ecmaFeatures &&
317+
parserOptions.ecmaFeatures.globalReturn) === true,
318+
impliedStrict: false,
319+
sourceType: ast.sourceType,
320+
ecmaVersion: parserOptions.ecmaVersion || 6,
321+
childVisitorKeys,
322+
fallback,
323+
};
324+
const scopeManager = new escope.ScopeManager(options);
325+
const referencer = new Referencer(options, scopeManager);
326+
327+
referencer.visit(ast);
328+
329+
return scopeManager;
330+
};
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

lib/index.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"use strict";
2+
3+
let patched = false;
4+
5+
exports.parse = function(code, options) {
6+
patched = true;
7+
return require("./parse-with-patch")(code, options);
8+
};
9+
10+
exports.parseForESLint = function(code, options) {
11+
if (!patched && options.eslintVisitorKeys && options.eslintScopeManager) {
12+
return require("./parse-with-scope")(code, options);
13+
}
14+
15+
patched = true;
16+
return { ast: require("./parse-with-patch")(code, options) };
17+
};

0 commit comments

Comments
 (0)