"use strict"; const t = require("@babel/types"); const escope = require("eslint-scope"); const Definition = require("eslint-scope/lib/definition").Definition; const OriginalPatternVisitor = require("eslint-scope/lib/pattern-visitor"); const OriginalReferencer = require("eslint-scope/lib/referencer"); const fallback = require("eslint-visitor-keys").getKeys; const childVisitorKeys = require("./visitor-keys"); const flowFlippedAliasKeys = t.FLIPPED_ALIAS_KEYS.Flow.concat([ "ArrayPattern", "ClassDeclaration", "ClassExpression", "FunctionDeclaration", "FunctionExpression", "Identifier", "ObjectPattern", "RestElement", ]); const visitorKeysMap = Object.keys(t.VISITOR_KEYS).reduce(function(acc, key) { const value = t.VISITOR_KEYS[key]; if (flowFlippedAliasKeys.indexOf(value) === -1) { acc[key] = value; } return acc; }, {}); const propertyTypes = { // loops callProperties: { type: "loop", values: ["value"] }, indexers: { type: "loop", values: ["key", "value"] }, properties: { type: "loop", values: ["argument", "value"] }, types: { type: "loop" }, params: { type: "loop" }, // single property argument: { type: "single" }, elementType: { type: "single" }, qualification: { type: "single" }, rest: { type: "single" }, returnType: { type: "single" }, // others typeAnnotation: { type: "typeAnnotation" }, typeParameters: { type: "typeParameters" }, id: { type: "id" }, }; class PatternVisitor extends OriginalPatternVisitor { ArrayPattern(node) { node.elements.forEach(this.visit, this); } ObjectPattern(node) { node.properties.forEach(this.visit, this); } } class Referencer extends OriginalReferencer { // inherits. visitPattern(node, options, callback) { if (!node) { return; } // Visit type annotations. this._checkIdentifierOrVisit(node.typeAnnotation); if (t.isAssignmentPattern(node)) { this._checkIdentifierOrVisit(node.left.typeAnnotation); } // Overwrite `super.visitPattern(node, options, callback)` in order to not visit `ArrayPattern#typeAnnotation` and `ObjectPattern#typeAnnotation`. if (typeof options === "function") { callback = options; options = { processRightHandNodes: false }; } const visitor = new PatternVisitor(this.options, node, callback); visitor.visit(node); // Process the right hand nodes recursively. if (options.processRightHandNodes) { visitor.rightHandNodes.forEach(this.visit, this); } } // inherits. visitClass(node) { // Decorators. this._visitArray(node.decorators); // Flow type parameters. const typeParamScope = this._nestTypeParamScope(node); // Flow super types. this._visitTypeAnnotation(node.implements); this._visitTypeAnnotation( node.superTypeParameters && node.superTypeParameters.params ); // Basic. super.visitClass(node); // Close the type parameter scope. if (typeParamScope) { this.close(node); } } // inherits. visitFunction(node) { const typeParamScope = this._nestTypeParamScope(node); // Flow return types. this._checkIdentifierOrVisit(node.returnType); // Basic. super.visitFunction(node); // Close the type parameter scope. if (typeParamScope) { this.close(node); } } // inherits. visitProperty(node) { if (node.value && node.value.type === "TypeCastExpression") { this._visitTypeAnnotation(node.value); } this._visitArray(node.decorators); super.visitProperty(node); } InterfaceDeclaration(node) { this._createScopeVariable(node, node.id); const typeParamScope = this._nestTypeParamScope(node); // TODO: Handle mixins this._visitArray(node.extends); this.visit(node.body); if (typeParamScope) { this.close(node); } } TypeAlias(node) { this._createScopeVariable(node, node.id); const typeParamScope = this._nestTypeParamScope(node); this.visit(node.right); if (typeParamScope) { this.close(node); } } ClassProperty(node) { this._visitClassProperty(node); } ClassPrivateProperty(node) { this._visitClassProperty(node); } DeclareModule(node) { this._visitDeclareX(node); } DeclareFunction(node) { this._visitDeclareX(node); } DeclareVariable(node) { this._visitDeclareX(node); } DeclareClass(node) { this._visitDeclareX(node); } // visit OptionalMemberExpression as a MemberExpression. OptionalMemberExpression(node) { super.MemberExpression(node); } _visitClassProperty(node) { this._visitTypeAnnotation(node.typeAnnotation); this.visitProperty(node); } _visitDeclareX(node) { if (node.id) { this._createScopeVariable(node, node.id); } const typeParamScope = this._nestTypeParamScope(node); if (typeParamScope) { this.close(node); } } _createScopeVariable(node, name) { this.currentScope().variableScope.__define( name, new Definition("Variable", name, node, null, null, null) ); } _nestTypeParamScope(node) { if (!node.typeParameters) { return null; } const parentScope = this.scopeManager.__currentScope; const scope = new escope.Scope( this.scopeManager, "type-parameters", parentScope, node, false ); this.scopeManager.__nestScope(scope); for (let j = 0; j < node.typeParameters.params.length; j++) { const name = node.typeParameters.params[j]; scope.__define(name, new Definition("TypeParameter", name, name)); if (name.typeAnnotation) { this._checkIdentifierOrVisit(name); } } scope.__define = function() { return parentScope.__define.apply(parentScope, arguments); }; return scope; } _visitTypeAnnotation(node) { if (!node) { return; } if (Array.isArray(node)) { node.forEach(this._visitTypeAnnotation, this); return; } // get property to check (params, id, etc...) const visitorValues = visitorKeysMap[node.type]; if (!visitorValues) { return; } // can have multiple properties for (let i = 0; i < visitorValues.length; i++) { const visitorValue = visitorValues[i]; const propertyType = propertyTypes[visitorValue]; const nodeProperty = node[visitorValue]; // check if property or type is defined if (propertyType == null || nodeProperty == null) { continue; } if (propertyType.type === "loop") { for (let j = 0; j < nodeProperty.length; j++) { if (Array.isArray(propertyType.values)) { for (let k = 0; k < propertyType.values.length; k++) { const loopPropertyNode = nodeProperty[j][propertyType.values[k]]; if (loopPropertyNode) { this._checkIdentifierOrVisit(loopPropertyNode); } } } else { this._checkIdentifierOrVisit(nodeProperty[j]); } } } else if (propertyType.type === "single") { this._checkIdentifierOrVisit(nodeProperty); } else if (propertyType.type === "typeAnnotation") { this._visitTypeAnnotation(node.typeAnnotation); } else if (propertyType.type === "typeParameters") { for (let l = 0; l < node.typeParameters.params.length; l++) { this._checkIdentifierOrVisit(node.typeParameters.params[l]); } } else if (propertyType.type === "id") { if (node.id.type === "Identifier") { this._checkIdentifierOrVisit(node.id); } else { this._visitTypeAnnotation(node.id); } } } } _checkIdentifierOrVisit(node) { if (node && node.typeAnnotation) { this._visitTypeAnnotation(node.typeAnnotation); } else if (node && node.type === "Identifier") { this.visit(node); } else { this._visitTypeAnnotation(node); } } _visitArray(nodeList) { if (nodeList) { for (const node of nodeList) { this.visit(node); } } } } module.exports = function(ast, parserOptions) { const options = { ignoreEval: true, optimistic: false, directive: false, nodejsScope: ast.sourceType === "script" && (parserOptions.ecmaFeatures && parserOptions.ecmaFeatures.globalReturn) === true, impliedStrict: false, sourceType: ast.sourceType, ecmaVersion: parserOptions.ecmaVersion || 2018, fallback, }; if (OriginalReferencer._babelEslintPatched) { require("./patch-eslint-scope")(parserOptions); return escope.analyze(ast, options); } options.childVisitorKeys = childVisitorKeys; const scopeManager = new escope.ScopeManager(options); const referencer = new Referencer(options, scopeManager); referencer.visit(ast); return scopeManager; };