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