Skip to content
This repository was archived by the owner on Jan 19, 2019. It is now read-only.

Commit 4e83b52

Browse files
committed
Update: add proper scope analysis (fixes #535)
1 parent 5d49243 commit 4e83b52

16 files changed

+2720
-5
lines changed

analyze-scope.js

+323
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
"use strict";
2+
3+
/* eslint-disable new-cap, no-underscore-dangle */
4+
5+
const escope = require("eslint-scope");
6+
const { Definition, ParameterDefinition } = require("eslint-scope/lib/definition");
7+
const OriginalPatternVisitor = require("eslint-scope/lib/pattern-visitor");
8+
const Reference = require("eslint-scope/lib/reference");
9+
const OriginalReferencer = require("eslint-scope/lib/referencer");
10+
const Scope = require("eslint-scope/lib/scope").Scope;
11+
const fallback = require("eslint-visitor-keys").getKeys;
12+
const childVisitorKeys = require("./visitor-keys");
13+
14+
/** The scope class for enum. */
15+
class EnumScope extends Scope {
16+
constructor(scopeManager, upperScope, block) {
17+
super(scopeManager, "enum", upperScope, block, false);
18+
}
19+
}
20+
21+
class PatternVisitor extends OriginalPatternVisitor {
22+
Identifier(node) {
23+
super.Identifier(node);
24+
if (node.typeAnnotation) {
25+
this.rightHandNodes.push(node.typeAnnotation);
26+
}
27+
}
28+
29+
ArrayPattern(node) {
30+
node.elements.forEach(this.visit, this);
31+
if (node.typeAnnotation) {
32+
this.rightHandNodes.push(node.typeAnnotation);
33+
}
34+
}
35+
36+
ObjectPattern(node) {
37+
node.properties.forEach(this.visit, this);
38+
if (node.typeAnnotation) {
39+
this.rightHandNodes.push(node.typeAnnotation);
40+
}
41+
}
42+
}
43+
44+
class Referencer extends OriginalReferencer {
45+
constructor(...args) {
46+
super(...args);
47+
this.typeMode = false;
48+
}
49+
50+
/**
51+
* Override to use PatternVisitor we overrode.
52+
* @param {Identifier} node The Identifier node to visit.
53+
* @param {Object} [options] The flag to visit right-hand side nodes.
54+
* @param {Function} callback The callback function for left-hand side nodes.
55+
* @returns {void}
56+
*/
57+
visitPattern(node, options, callback) {
58+
if (!node) {
59+
return;
60+
}
61+
62+
if (typeof options === "function") {
63+
callback = options;
64+
options = { processRightHandNodes: false };
65+
}
66+
67+
const visitor = new PatternVisitor(this.options, node, callback);
68+
visitor.visit(node);
69+
70+
if (options.processRightHandNodes) {
71+
visitor.rightHandNodes.forEach(this.visit, this);
72+
}
73+
}
74+
75+
/**
76+
* Override.
77+
* Visit `node.typeParameters` and `node.returnType` additionally to find `typeof` expressions.
78+
* @param {FunctionDeclaration|FunctionExpression|ArrowFunctionExpression} node The function node to visit.
79+
* @returns {void}
80+
*/
81+
visitFunction(node) {
82+
const { type, id, typeParameters, params, returnType, body } = node;
83+
const scopeManager = this.scopeManager;
84+
const upperScope = this.currentScope();
85+
86+
// Process the name.
87+
if (type === "FunctionDeclaration") {
88+
upperScope.__define(
89+
id,
90+
new Definition("FunctionName", id, node, null, null, null)
91+
);
92+
} else if (type === "FunctionExpression" && id) {
93+
scopeManager.__nestFunctionExpressionNameScope(node);
94+
}
95+
96+
// Process the type parameters
97+
if (typeParameters) {
98+
this.visit(typeParameters);
99+
}
100+
101+
// Open the function scope.
102+
scopeManager.__nestFunctionScope(node, this.isInnerMethodDefinition);
103+
const innerScope = this.currentScope();
104+
105+
// Process parameter declarations.
106+
for (let i = 0; i < params.length; ++i) {
107+
this.visitPattern(
108+
params[i],
109+
{ processRightHandNodes: true },
110+
(pattern, info) => {
111+
innerScope.__define(
112+
pattern,
113+
new ParameterDefinition(
114+
pattern,
115+
node,
116+
i,
117+
info.rest
118+
)
119+
);
120+
this.referencingDefaultValue(
121+
pattern,
122+
info.assignments,
123+
null,
124+
true
125+
);
126+
}
127+
);
128+
}
129+
130+
// Process the return type.
131+
if (returnType) {
132+
this.visit(returnType);
133+
}
134+
135+
// Process the body.
136+
if (body) {
137+
if (body.type === "BlockStatement") {
138+
this.visitChildren(body);
139+
} else {
140+
this.visit(body);
141+
}
142+
}
143+
144+
// Close the function scope.
145+
this.close(node);
146+
}
147+
148+
/**
149+
* Override.
150+
* Ignore it in the type mode.
151+
* @param {Identifier} node The Identifier node to visit.
152+
* @returns {void}
153+
*/
154+
Identifier(node) {
155+
if (this.typeMode) {
156+
return;
157+
}
158+
super.Identifier(node);
159+
}
160+
161+
/**
162+
* Override.
163+
* Don't make variable if `kind === "type"`.
164+
* It doesn't declare variables but declare types.
165+
* @param {VariableDeclaration} node The VariableDeclaration node to visit.
166+
* @returns {void}
167+
*/
168+
VariableDeclaration(node) {
169+
if (node.kind !== "type") {
170+
super.VariableDeclaration(node);
171+
return;
172+
}
173+
174+
// To detect typeof.
175+
this.typeMode = true;
176+
this.visitChildren(node);
177+
this.typeMode = false;
178+
}
179+
180+
/**
181+
* Don't make variable because it declares only types.
182+
* Switch to the type mode and visit child nodes to find `typeof x` expression in type declarations.
183+
* @param {TSInterfaceDeclaration} node The TSInterfaceDeclaration node to visit.
184+
* @returns {void}
185+
*/
186+
TSInterfaceDeclaration(node) {
187+
if (this.typeMode) {
188+
this.visitChildren(node);
189+
} else {
190+
this.typeMode = true;
191+
this.visitChildren(node);
192+
this.typeMode = false;
193+
}
194+
}
195+
196+
/**
197+
* Switch to the type mode and visit child nodes to find `typeof x` expression in type declarations.
198+
* @param {TSTypeAnnotation} node The TSTypeAnnotation node to visit.
199+
* @returns {void}
200+
*/
201+
TSTypeAnnotation(node) {
202+
if (this.typeMode) {
203+
this.visitChildren(node);
204+
} else {
205+
this.typeMode = true;
206+
this.visitChildren(node);
207+
this.typeMode = false;
208+
}
209+
}
210+
211+
/**
212+
* Switch to the type mode and visit child nodes to find `typeof x` expression in type declarations.
213+
* @param {TSTypeParameterDeclaration} node The TSTypeParameterDeclaration node to visit.
214+
* @returns {void}
215+
*/
216+
TSTypeParameterDeclaration(node) {
217+
if (this.typeMode) {
218+
this.visitChildren(node);
219+
} else {
220+
this.typeMode = true;
221+
this.visitChildren(node);
222+
this.typeMode = false;
223+
}
224+
}
225+
226+
/**
227+
* Create reference objects for the references are in `typeof` expression.
228+
* @param {TSTypeQuery} node The TSTypeQuery node to visit.
229+
* @returns {void}
230+
*/
231+
TSTypeQuery(node) {
232+
if (this.typeMode) {
233+
this.typeMode = false;
234+
this.visitChildren(node);
235+
this.typeMode = true;
236+
} else {
237+
this.visitChildren(node);
238+
}
239+
}
240+
241+
/**
242+
* Create variable object for the enum.
243+
* The enum declaration creates a scope for the enum members.
244+
*
245+
* enum E {
246+
* A,
247+
* B,
248+
* C = A + B // A and B are references to the enum member.
249+
* }
250+
*
251+
* const a = 0
252+
* enum E {
253+
* A = a // a is above constant.
254+
* }
255+
*
256+
* @param {TSEnumDeclaration} node The TSEnumDeclaration node to visit.
257+
* @returns {void}
258+
*/
259+
TSEnumDeclaration(node) {
260+
const { id, members } = node;
261+
const scopeManager = this.scopeManager;
262+
const scope = this.currentScope();
263+
264+
if (id) {
265+
scope.__define(id, new Definition("EnumName", id, node));
266+
}
267+
268+
scopeManager.__nestScope(new EnumScope(scopeManager, scope, node));
269+
for (const member of members) {
270+
this.visit(member);
271+
}
272+
this.close(node);
273+
}
274+
275+
/**
276+
* Create variable object for the enum member and create reference object for the initializer.
277+
* And visit the initializer.
278+
*
279+
* @param {TSEnumMember} node The TSEnumMember node to visit.
280+
* @returns {void}
281+
*/
282+
TSEnumMember(node) {
283+
const { id, initializer } = node;
284+
const scope = this.currentScope();
285+
286+
scope.__define(id, new Definition("EnumMemberName", id, node));
287+
if (initializer) {
288+
scope.__referencing(
289+
id,
290+
Reference.WRITE,
291+
initializer,
292+
null,
293+
false,
294+
true
295+
);
296+
this.visit(initializer);
297+
}
298+
}
299+
}
300+
301+
module.exports = function(ast, parserOptions, extraOptions) {
302+
const options = {
303+
ignoreEval: true,
304+
optimistic: false,
305+
directive: false,
306+
nodejsScope:
307+
ast.sourceType === "script" &&
308+
(parserOptions.ecmaFeatures &&
309+
parserOptions.ecmaFeatures.globalReturn) === true,
310+
impliedStrict: false,
311+
sourceType: extraOptions.sourceType,
312+
ecmaVersion: parserOptions.ecmaVersion || 2018,
313+
childVisitorKeys,
314+
fallback
315+
};
316+
317+
const scopeManager = new escope.ScopeManager(options);
318+
const referencer = new Referencer(options, scopeManager);
319+
320+
referencer.visit(ast);
321+
322+
return scopeManager;
323+
};

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"version": "20.1.1",
88
"files": [
99
"parser.js",
10+
"analyze-scope.js",
1011
"visitor-keys.js"
1112
],
1213
"engines": {
@@ -50,6 +51,7 @@
5051
},
5152
"dependencies": {
5253
"eslint": "4.19.1",
54+
"eslint-scope": "^4.0.0",
5355
"eslint-visitor-keys": "^1.0.0",
5456
"typescript-estree": "2.1.0"
5557
},

parser.js

+35-5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
const parse = require("typescript-estree").parse;
1212
const astNodeTypes = require("typescript-estree").AST_NODE_TYPES;
1313
const traverser = require("eslint/lib/util/traverser");
14+
const analyzeScope = require("./analyze-scope");
1415
const visitorKeys = require("./visitor-keys");
1516

1617
//------------------------------------------------------------------------------
@@ -21,16 +22,45 @@ exports.version = require("./package.json").version;
2122

2223
exports.parseForESLint = function parseForESLint(code, options) {
2324
const ast = parse(code, options);
25+
const extraOptions = {
26+
sourceType: ast.sourceType
27+
};
28+
2429
traverser.traverse(ast, {
2530
enter: node => {
26-
if (node.type === "DeclareFunction" || node.type === "FunctionExpression" || node.type === "FunctionDeclaration") {
27-
if (!node.body) {
28-
node.type = `TSEmptyBody${node.type}`;
29-
}
31+
switch (node.type) {
32+
// Just for backword compatibility.
33+
case "DeclareFunction":
34+
if (!node.body) {
35+
node.type = `TSEmptyBody${node.type}`;
36+
}
37+
break;
38+
39+
// Function#body cannot be null in ESTree spec.
40+
case "FunctionExpression":
41+
case "FunctionDeclaration":
42+
if (!node.body) {
43+
node.type = `TSEmptyBody${node.type}`;
44+
}
45+
break;
46+
47+
// Import/Export declarations cannot appear in script.
48+
// But if those appear only in namespace/module blocks, `ast.sourceType` was `"script"`.
49+
// This doesn't modify `ast.sourceType` directly for backrard compatibility.
50+
case "ImportDeclaration":
51+
case "ExportAllDeclaration":
52+
case "ExportDefaultDeclaration":
53+
case "ExportNamedDeclaration":
54+
extraOptions.sourceType = "module";
55+
break;
56+
57+
// no default
3058
}
3159
}
3260
});
33-
return { ast, visitorKeys };
61+
62+
const scopeManager = analyzeScope(ast, options, extraOptions);
63+
return { ast, scopeManager, visitorKeys };
3464
};
3565

3666
exports.parse = function(code, options) {

0 commit comments

Comments
 (0)