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