1/* 2 Copyright (C) 2015 Yusuke Suzuki <utatane.tea@gmail.com> 3 4 Redistribution and use in source and binary forms, with or without 5 modification, are permitted provided that the following conditions are met: 6 7 * Redistributions of source code must retain the above copyright 8 notice, this list of conditions and the following disclaimer. 9 * Redistributions in binary form must reproduce the above copyright 10 notice, this list of conditions and the following disclaimer in the 11 documentation and/or other materials provided with the distribution. 12 13 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY 17 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23*/ 24"use strict"; 25 26/* eslint-disable no-underscore-dangle */ 27/* eslint-disable no-undefined */ 28 29const Syntax = require("estraverse").Syntax; 30const esrecurse = require("esrecurse"); 31const Reference = require("./reference"); 32const Variable = require("./variable"); 33const PatternVisitor = require("./pattern-visitor"); 34const definition = require("./definition"); 35const assert = require("assert"); 36 37const ParameterDefinition = definition.ParameterDefinition; 38const Definition = definition.Definition; 39 40/** 41 * Traverse identifier in pattern 42 * @param {Object} options - options 43 * @param {pattern} rootPattern - root pattern 44 * @param {Refencer} referencer - referencer 45 * @param {callback} callback - callback 46 * @returns {void} 47 */ 48function traverseIdentifierInPattern(options, rootPattern, referencer, callback) { 49 50 // Call the callback at left hand identifier nodes, and Collect right hand nodes. 51 const visitor = new PatternVisitor(options, rootPattern, callback); 52 53 visitor.visit(rootPattern); 54 55 // Process the right hand nodes recursively. 56 if (referencer !== null && referencer !== undefined) { 57 visitor.rightHandNodes.forEach(referencer.visit, referencer); 58 } 59} 60 61// Importing ImportDeclaration. 62// http://people.mozilla.org/~jorendorff/es6-draft.html#sec-moduledeclarationinstantiation 63// https://github.com/estree/estree/blob/master/es6.md#importdeclaration 64// FIXME: Now, we don't create module environment, because the context is 65// implementation dependent. 66 67class Importer extends esrecurse.Visitor { 68 constructor(declaration, referencer) { 69 super(null, referencer.options); 70 this.declaration = declaration; 71 this.referencer = referencer; 72 } 73 74 visitImport(id, specifier) { 75 this.referencer.visitPattern(id, pattern => { 76 this.referencer.currentScope().__define(pattern, 77 new Definition( 78 Variable.ImportBinding, 79 pattern, 80 specifier, 81 this.declaration, 82 null, 83 null 84 )); 85 }); 86 } 87 88 ImportNamespaceSpecifier(node) { 89 const local = (node.local || node.id); 90 91 if (local) { 92 this.visitImport(local, node); 93 } 94 } 95 96 ImportDefaultSpecifier(node) { 97 const local = (node.local || node.id); 98 99 this.visitImport(local, node); 100 } 101 102 ImportSpecifier(node) { 103 const local = (node.local || node.id); 104 105 if (node.name) { 106 this.visitImport(node.name, node); 107 } else { 108 this.visitImport(local, node); 109 } 110 } 111} 112 113// Referencing variables and creating bindings. 114class Referencer extends esrecurse.Visitor { 115 constructor(options, scopeManager) { 116 super(null, options); 117 this.options = options; 118 this.scopeManager = scopeManager; 119 this.parent = null; 120 this.isInnerMethodDefinition = false; 121 } 122 123 currentScope() { 124 return this.scopeManager.__currentScope; 125 } 126 127 close(node) { 128 while (this.currentScope() && node === this.currentScope().block) { 129 this.scopeManager.__currentScope = this.currentScope().__close(this.scopeManager); 130 } 131 } 132 133 pushInnerMethodDefinition(isInnerMethodDefinition) { 134 const previous = this.isInnerMethodDefinition; 135 136 this.isInnerMethodDefinition = isInnerMethodDefinition; 137 return previous; 138 } 139 140 popInnerMethodDefinition(isInnerMethodDefinition) { 141 this.isInnerMethodDefinition = isInnerMethodDefinition; 142 } 143 144 referencingDefaultValue(pattern, assignments, maybeImplicitGlobal, init) { 145 const scope = this.currentScope(); 146 147 assignments.forEach(assignment => { 148 scope.__referencing( 149 pattern, 150 Reference.WRITE, 151 assignment.right, 152 maybeImplicitGlobal, 153 pattern !== assignment.left, 154 init 155 ); 156 }); 157 } 158 159 visitPattern(node, options, callback) { 160 let visitPatternOptions = options; 161 let visitPatternCallback = callback; 162 163 if (typeof options === "function") { 164 visitPatternCallback = options; 165 visitPatternOptions = { processRightHandNodes: false }; 166 } 167 168 traverseIdentifierInPattern( 169 this.options, 170 node, 171 visitPatternOptions.processRightHandNodes ? this : null, 172 visitPatternCallback 173 ); 174 } 175 176 visitFunction(node) { 177 let i, iz; 178 179 // FunctionDeclaration name is defined in upper scope 180 // NOTE: Not referring variableScope. It is intended. 181 // Since 182 // in ES5, FunctionDeclaration should be in FunctionBody. 183 // in ES6, FunctionDeclaration should be block scoped. 184 185 if (node.type === Syntax.FunctionDeclaration) { 186 187 // id is defined in upper scope 188 this.currentScope().__define(node.id, 189 new Definition( 190 Variable.FunctionName, 191 node.id, 192 node, 193 null, 194 null, 195 null 196 )); 197 } 198 199 // FunctionExpression with name creates its special scope; 200 // FunctionExpressionNameScope. 201 if (node.type === Syntax.FunctionExpression && node.id) { 202 this.scopeManager.__nestFunctionExpressionNameScope(node); 203 } 204 205 // Consider this function is in the MethodDefinition. 206 this.scopeManager.__nestFunctionScope(node, this.isInnerMethodDefinition); 207 208 const that = this; 209 210 /** 211 * Visit pattern callback 212 * @param {pattern} pattern - pattern 213 * @param {Object} info - info 214 * @returns {void} 215 */ 216 function visitPatternCallback(pattern, info) { 217 that.currentScope().__define(pattern, 218 new ParameterDefinition( 219 pattern, 220 node, 221 i, 222 info.rest 223 )); 224 225 that.referencingDefaultValue(pattern, info.assignments, null, true); 226 } 227 228 // Process parameter declarations. 229 for (i = 0, iz = node.params.length; i < iz; ++i) { 230 this.visitPattern(node.params[i], { processRightHandNodes: true }, visitPatternCallback); 231 } 232 233 // if there's a rest argument, add that 234 if (node.rest) { 235 this.visitPattern({ 236 type: "RestElement", 237 argument: node.rest 238 }, pattern => { 239 this.currentScope().__define(pattern, 240 new ParameterDefinition( 241 pattern, 242 node, 243 node.params.length, 244 true 245 )); 246 }); 247 } 248 249 // In TypeScript there are a number of function-like constructs which have no body, 250 // so check it exists before traversing 251 if (node.body) { 252 253 // Skip BlockStatement to prevent creating BlockStatement scope. 254 if (node.body.type === Syntax.BlockStatement) { 255 this.visitChildren(node.body); 256 } else { 257 this.visit(node.body); 258 } 259 } 260 261 this.close(node); 262 } 263 264 visitClass(node) { 265 if (node.type === Syntax.ClassDeclaration) { 266 this.currentScope().__define(node.id, 267 new Definition( 268 Variable.ClassName, 269 node.id, 270 node, 271 null, 272 null, 273 null 274 )); 275 } 276 277 this.visit(node.superClass); 278 279 this.scopeManager.__nestClassScope(node); 280 281 if (node.id) { 282 this.currentScope().__define(node.id, 283 new Definition( 284 Variable.ClassName, 285 node.id, 286 node 287 )); 288 } 289 this.visit(node.body); 290 291 this.close(node); 292 } 293 294 visitProperty(node) { 295 let previous; 296 297 if (node.computed) { 298 this.visit(node.key); 299 } 300 301 const isMethodDefinition = node.type === Syntax.MethodDefinition; 302 303 if (isMethodDefinition) { 304 previous = this.pushInnerMethodDefinition(true); 305 } 306 this.visit(node.value); 307 if (isMethodDefinition) { 308 this.popInnerMethodDefinition(previous); 309 } 310 } 311 312 visitForIn(node) { 313 if (node.left.type === Syntax.VariableDeclaration && node.left.kind !== "var") { 314 this.scopeManager.__nestForScope(node); 315 } 316 317 if (node.left.type === Syntax.VariableDeclaration) { 318 this.visit(node.left); 319 this.visitPattern(node.left.declarations[0].id, pattern => { 320 this.currentScope().__referencing(pattern, Reference.WRITE, node.right, null, true, true); 321 }); 322 } else { 323 this.visitPattern(node.left, { processRightHandNodes: true }, (pattern, info) => { 324 let maybeImplicitGlobal = null; 325 326 if (!this.currentScope().isStrict) { 327 maybeImplicitGlobal = { 328 pattern, 329 node 330 }; 331 } 332 this.referencingDefaultValue(pattern, info.assignments, maybeImplicitGlobal, false); 333 this.currentScope().__referencing(pattern, Reference.WRITE, node.right, maybeImplicitGlobal, true, false); 334 }); 335 } 336 this.visit(node.right); 337 this.visit(node.body); 338 339 this.close(node); 340 } 341 342 visitVariableDeclaration(variableTargetScope, type, node, index) { 343 344 const decl = node.declarations[index]; 345 const init = decl.init; 346 347 this.visitPattern(decl.id, { processRightHandNodes: true }, (pattern, info) => { 348 variableTargetScope.__define( 349 pattern, 350 new Definition( 351 type, 352 pattern, 353 decl, 354 node, 355 index, 356 node.kind 357 ) 358 ); 359 360 this.referencingDefaultValue(pattern, info.assignments, null, true); 361 if (init) { 362 this.currentScope().__referencing(pattern, Reference.WRITE, init, null, !info.topLevel, true); 363 } 364 }); 365 } 366 367 AssignmentExpression(node) { 368 if (PatternVisitor.isPattern(node.left)) { 369 if (node.operator === "=") { 370 this.visitPattern(node.left, { processRightHandNodes: true }, (pattern, info) => { 371 let maybeImplicitGlobal = null; 372 373 if (!this.currentScope().isStrict) { 374 maybeImplicitGlobal = { 375 pattern, 376 node 377 }; 378 } 379 this.referencingDefaultValue(pattern, info.assignments, maybeImplicitGlobal, false); 380 this.currentScope().__referencing(pattern, Reference.WRITE, node.right, maybeImplicitGlobal, !info.topLevel, false); 381 }); 382 } else { 383 this.currentScope().__referencing(node.left, Reference.RW, node.right); 384 } 385 } else { 386 this.visit(node.left); 387 } 388 this.visit(node.right); 389 } 390 391 CatchClause(node) { 392 this.scopeManager.__nestCatchScope(node); 393 394 this.visitPattern(node.param, { processRightHandNodes: true }, (pattern, info) => { 395 this.currentScope().__define(pattern, 396 new Definition( 397 Variable.CatchClause, 398 node.param, 399 node, 400 null, 401 null, 402 null 403 )); 404 this.referencingDefaultValue(pattern, info.assignments, null, true); 405 }); 406 this.visit(node.body); 407 408 this.close(node); 409 } 410 411 Program(node) { 412 this.scopeManager.__nestGlobalScope(node); 413 414 if (this.scopeManager.__isNodejsScope()) { 415 416 // Force strictness of GlobalScope to false when using node.js scope. 417 this.currentScope().isStrict = false; 418 this.scopeManager.__nestFunctionScope(node, false); 419 } 420 421 if (this.scopeManager.__isES6() && this.scopeManager.isModule()) { 422 this.scopeManager.__nestModuleScope(node); 423 } 424 425 if (this.scopeManager.isStrictModeSupported() && this.scopeManager.isImpliedStrict()) { 426 this.currentScope().isStrict = true; 427 } 428 429 this.visitChildren(node); 430 this.close(node); 431 } 432 433 Identifier(node) { 434 this.currentScope().__referencing(node); 435 } 436 437 UpdateExpression(node) { 438 if (PatternVisitor.isPattern(node.argument)) { 439 this.currentScope().__referencing(node.argument, Reference.RW, null); 440 } else { 441 this.visitChildren(node); 442 } 443 } 444 445 MemberExpression(node) { 446 this.visit(node.object); 447 if (node.computed) { 448 this.visit(node.property); 449 } 450 } 451 452 Property(node) { 453 this.visitProperty(node); 454 } 455 456 MethodDefinition(node) { 457 this.visitProperty(node); 458 } 459 460 BreakStatement() {} // eslint-disable-line class-methods-use-this 461 462 ContinueStatement() {} // eslint-disable-line class-methods-use-this 463 464 LabeledStatement(node) { 465 this.visit(node.body); 466 } 467 468 ForStatement(node) { 469 470 // Create ForStatement declaration. 471 // NOTE: In ES6, ForStatement dynamically generates 472 // per iteration environment. However, escope is 473 // a static analyzer, we only generate one scope for ForStatement. 474 if (node.init && node.init.type === Syntax.VariableDeclaration && node.init.kind !== "var") { 475 this.scopeManager.__nestForScope(node); 476 } 477 478 this.visitChildren(node); 479 480 this.close(node); 481 } 482 483 ClassExpression(node) { 484 this.visitClass(node); 485 } 486 487 ClassDeclaration(node) { 488 this.visitClass(node); 489 } 490 491 CallExpression(node) { 492 493 // Check this is direct call to eval 494 if (!this.scopeManager.__ignoreEval() && node.callee.type === Syntax.Identifier && node.callee.name === "eval") { 495 496 // NOTE: This should be `variableScope`. Since direct eval call always creates Lexical environment and 497 // let / const should be enclosed into it. Only VariableDeclaration affects on the caller's environment. 498 this.currentScope().variableScope.__detectEval(); 499 } 500 this.visitChildren(node); 501 } 502 503 BlockStatement(node) { 504 if (this.scopeManager.__isES6()) { 505 this.scopeManager.__nestBlockScope(node); 506 } 507 508 this.visitChildren(node); 509 510 this.close(node); 511 } 512 513 ThisExpression() { 514 this.currentScope().variableScope.__detectThis(); 515 } 516 517 WithStatement(node) { 518 this.visit(node.object); 519 520 // Then nest scope for WithStatement. 521 this.scopeManager.__nestWithScope(node); 522 523 this.visit(node.body); 524 525 this.close(node); 526 } 527 528 VariableDeclaration(node) { 529 const variableTargetScope = (node.kind === "var") ? this.currentScope().variableScope : this.currentScope(); 530 531 for (let i = 0, iz = node.declarations.length; i < iz; ++i) { 532 const decl = node.declarations[i]; 533 534 this.visitVariableDeclaration(variableTargetScope, Variable.Variable, node, i); 535 if (decl.init) { 536 this.visit(decl.init); 537 } 538 } 539 } 540 541 // sec 13.11.8 542 SwitchStatement(node) { 543 this.visit(node.discriminant); 544 545 if (this.scopeManager.__isES6()) { 546 this.scopeManager.__nestSwitchScope(node); 547 } 548 549 for (let i = 0, iz = node.cases.length; i < iz; ++i) { 550 this.visit(node.cases[i]); 551 } 552 553 this.close(node); 554 } 555 556 FunctionDeclaration(node) { 557 this.visitFunction(node); 558 } 559 560 FunctionExpression(node) { 561 this.visitFunction(node); 562 } 563 564 ForOfStatement(node) { 565 this.visitForIn(node); 566 } 567 568 ForInStatement(node) { 569 this.visitForIn(node); 570 } 571 572 ArrowFunctionExpression(node) { 573 this.visitFunction(node); 574 } 575 576 ImportDeclaration(node) { 577 assert(this.scopeManager.__isES6() && this.scopeManager.isModule(), "ImportDeclaration should appear when the mode is ES6 and in the module context."); 578 579 const importer = new Importer(node, this); 580 581 importer.visit(node); 582 } 583 584 visitExportDeclaration(node) { 585 if (node.source) { 586 return; 587 } 588 if (node.declaration) { 589 this.visit(node.declaration); 590 return; 591 } 592 593 this.visitChildren(node); 594 } 595 596 // TODO: ExportDeclaration doesn't exist. for bc? 597 ExportDeclaration(node) { 598 this.visitExportDeclaration(node); 599 } 600 601 ExportAllDeclaration(node) { 602 this.visitExportDeclaration(node); 603 } 604 605 ExportDefaultDeclaration(node) { 606 this.visitExportDeclaration(node); 607 } 608 609 ExportNamedDeclaration(node) { 610 this.visitExportDeclaration(node); 611 } 612 613 ExportSpecifier(node) { 614 615 // TODO: `node.id` doesn't exist. for bc? 616 const local = (node.id || node.local); 617 618 this.visit(local); 619 } 620 621 MetaProperty() { // eslint-disable-line class-methods-use-this 622 623 // do nothing. 624 } 625} 626 627module.exports = Referencer; 628 629/* vim: set sw=4 ts=4 et tw=80 : */ 630