| /* |
| MIT License http://www.opensource.org/licenses/mit-license.php |
| Author Tobias Koppers @sokra |
| */ |
| "use strict"; |
| |
| // Syntax: https://developer.mozilla.org/en/SpiderMonkey/Parser_API |
| |
| const acorn = require("acorn"); |
| const { Tapable, SyncBailHook, HookMap } = require("tapable"); |
| const util = require("util"); |
| const vm = require("vm"); |
| const BasicEvaluatedExpression = require("./BasicEvaluatedExpression"); |
| const StackedSetMap = require("./util/StackedSetMap"); |
| |
| const acornParser = acorn.Parser; |
| |
| const joinRanges = (startRange, endRange) => { |
| if (!endRange) return startRange; |
| if (!startRange) return endRange; |
| return [startRange[0], endRange[1]]; |
| }; |
| |
| const defaultParserOptions = { |
| ranges: true, |
| locations: true, |
| ecmaVersion: 11, |
| sourceType: "module", |
| onComment: null |
| }; |
| |
| // regexp to match at lease one "magic comment" |
| const webpackCommentRegExp = new RegExp(/(^|\W)webpack[A-Z]{1,}[A-Za-z]{1,}:/); |
| |
| const EMPTY_COMMENT_OPTIONS = { |
| options: null, |
| errors: null |
| }; |
| |
| class Parser extends Tapable { |
| constructor(options, sourceType = "auto") { |
| super(); |
| this.hooks = { |
| evaluateTypeof: new HookMap(() => new SyncBailHook(["expression"])), |
| evaluate: new HookMap(() => new SyncBailHook(["expression"])), |
| evaluateIdentifier: new HookMap(() => new SyncBailHook(["expression"])), |
| evaluateDefinedIdentifier: new HookMap( |
| () => new SyncBailHook(["expression"]) |
| ), |
| evaluateCallExpressionMember: new HookMap( |
| () => new SyncBailHook(["expression", "param"]) |
| ), |
| statement: new SyncBailHook(["statement"]), |
| statementIf: new SyncBailHook(["statement"]), |
| label: new HookMap(() => new SyncBailHook(["statement"])), |
| import: new SyncBailHook(["statement", "source"]), |
| importSpecifier: new SyncBailHook([ |
| "statement", |
| "source", |
| "exportName", |
| "identifierName" |
| ]), |
| export: new SyncBailHook(["statement"]), |
| exportImport: new SyncBailHook(["statement", "source"]), |
| exportDeclaration: new SyncBailHook(["statement", "declaration"]), |
| exportExpression: new SyncBailHook(["statement", "declaration"]), |
| exportSpecifier: new SyncBailHook([ |
| "statement", |
| "identifierName", |
| "exportName", |
| "index" |
| ]), |
| exportImportSpecifier: new SyncBailHook([ |
| "statement", |
| "source", |
| "identifierName", |
| "exportName", |
| "index" |
| ]), |
| varDeclaration: new HookMap(() => new SyncBailHook(["declaration"])), |
| varDeclarationLet: new HookMap(() => new SyncBailHook(["declaration"])), |
| varDeclarationConst: new HookMap(() => new SyncBailHook(["declaration"])), |
| varDeclarationVar: new HookMap(() => new SyncBailHook(["declaration"])), |
| canRename: new HookMap(() => new SyncBailHook(["initExpression"])), |
| rename: new HookMap(() => new SyncBailHook(["initExpression"])), |
| assigned: new HookMap(() => new SyncBailHook(["expression"])), |
| assign: new HookMap(() => new SyncBailHook(["expression"])), |
| typeof: new HookMap(() => new SyncBailHook(["expression"])), |
| importCall: new SyncBailHook(["expression"]), |
| call: new HookMap(() => new SyncBailHook(["expression"])), |
| callAnyMember: new HookMap(() => new SyncBailHook(["expression"])), |
| new: new HookMap(() => new SyncBailHook(["expression"])), |
| expression: new HookMap(() => new SyncBailHook(["expression"])), |
| expressionAnyMember: new HookMap(() => new SyncBailHook(["expression"])), |
| expressionConditionalOperator: new SyncBailHook(["expression"]), |
| expressionLogicalOperator: new SyncBailHook(["expression"]), |
| program: new SyncBailHook(["ast", "comments"]) |
| }; |
| const HOOK_MAP_COMPAT_CONFIG = { |
| evaluateTypeof: /^evaluate typeof (.+)$/, |
| evaluateIdentifier: /^evaluate Identifier (.+)$/, |
| evaluateDefinedIdentifier: /^evaluate defined Identifier (.+)$/, |
| evaluateCallExpressionMember: /^evaluate CallExpression .(.+)$/, |
| evaluate: /^evaluate (.+)$/, |
| label: /^label (.+)$/, |
| varDeclarationLet: /^var-let (.+)$/, |
| varDeclarationConst: /^var-const (.+)$/, |
| varDeclarationVar: /^var-var (.+)$/, |
| varDeclaration: /^var (.+)$/, |
| canRename: /^can-rename (.+)$/, |
| rename: /^rename (.+)$/, |
| typeof: /^typeof (.+)$/, |
| assigned: /^assigned (.+)$/, |
| assign: /^assign (.+)$/, |
| callAnyMember: /^call (.+)\.\*$/, |
| call: /^call (.+)$/, |
| new: /^new (.+)$/, |
| expressionConditionalOperator: /^expression \?:$/, |
| expressionAnyMember: /^expression (.+)\.\*$/, |
| expression: /^expression (.+)$/ |
| }; |
| this._pluginCompat.tap("Parser", options => { |
| for (const name of Object.keys(HOOK_MAP_COMPAT_CONFIG)) { |
| const regexp = HOOK_MAP_COMPAT_CONFIG[name]; |
| const match = regexp.exec(options.name); |
| if (match) { |
| if (match[1]) { |
| this.hooks[name].tap( |
| match[1], |
| options.fn.name || "unnamed compat plugin", |
| options.fn.bind(this) |
| ); |
| } else { |
| this.hooks[name].tap( |
| options.fn.name || "unnamed compat plugin", |
| options.fn.bind(this) |
| ); |
| } |
| return true; |
| } |
| } |
| }); |
| this.options = options; |
| this.sourceType = sourceType; |
| this.scope = undefined; |
| this.state = undefined; |
| this.comments = undefined; |
| this.initializeEvaluating(); |
| } |
| |
| initializeEvaluating() { |
| this.hooks.evaluate.for("Literal").tap("Parser", expr => { |
| switch (typeof expr.value) { |
| case "number": |
| return new BasicEvaluatedExpression() |
| .setNumber(expr.value) |
| .setRange(expr.range); |
| case "string": |
| return new BasicEvaluatedExpression() |
| .setString(expr.value) |
| .setRange(expr.range); |
| case "boolean": |
| return new BasicEvaluatedExpression() |
| .setBoolean(expr.value) |
| .setRange(expr.range); |
| } |
| if (expr.value === null) { |
| return new BasicEvaluatedExpression().setNull().setRange(expr.range); |
| } |
| if (expr.value instanceof RegExp) { |
| return new BasicEvaluatedExpression() |
| .setRegExp(expr.value) |
| .setRange(expr.range); |
| } |
| }); |
| this.hooks.evaluate.for("LogicalExpression").tap("Parser", expr => { |
| let left; |
| let leftAsBool; |
| let right; |
| if (expr.operator === "&&") { |
| left = this.evaluateExpression(expr.left); |
| leftAsBool = left && left.asBool(); |
| if (leftAsBool === false) return left.setRange(expr.range); |
| if (leftAsBool !== true) return; |
| right = this.evaluateExpression(expr.right); |
| return right.setRange(expr.range); |
| } else if (expr.operator === "||") { |
| left = this.evaluateExpression(expr.left); |
| leftAsBool = left && left.asBool(); |
| if (leftAsBool === true) return left.setRange(expr.range); |
| if (leftAsBool !== false) return; |
| right = this.evaluateExpression(expr.right); |
| return right.setRange(expr.range); |
| } |
| }); |
| this.hooks.evaluate.for("BinaryExpression").tap("Parser", expr => { |
| let left; |
| let right; |
| let res; |
| if (expr.operator === "+") { |
| left = this.evaluateExpression(expr.left); |
| right = this.evaluateExpression(expr.right); |
| if (!left || !right) return; |
| res = new BasicEvaluatedExpression(); |
| if (left.isString()) { |
| if (right.isString()) { |
| res.setString(left.string + right.string); |
| } else if (right.isNumber()) { |
| res.setString(left.string + right.number); |
| } else if ( |
| right.isWrapped() && |
| right.prefix && |
| right.prefix.isString() |
| ) { |
| // "left" + ("prefix" + inner + "postfix") |
| // => ("leftprefix" + inner + "postfix") |
| res.setWrapped( |
| new BasicEvaluatedExpression() |
| .setString(left.string + right.prefix.string) |
| .setRange(joinRanges(left.range, right.prefix.range)), |
| right.postfix, |
| right.wrappedInnerExpressions |
| ); |
| } else if (right.isWrapped()) { |
| // "left" + ([null] + inner + "postfix") |
| // => ("left" + inner + "postfix") |
| res.setWrapped(left, right.postfix, right.wrappedInnerExpressions); |
| } else { |
| // "left" + expr |
| // => ("left" + expr + "") |
| res.setWrapped(left, null, [right]); |
| } |
| } else if (left.isNumber()) { |
| if (right.isString()) { |
| res.setString(left.number + right.string); |
| } else if (right.isNumber()) { |
| res.setNumber(left.number + right.number); |
| } else { |
| return; |
| } |
| } else if (left.isWrapped()) { |
| if (left.postfix && left.postfix.isString() && right.isString()) { |
| // ("prefix" + inner + "postfix") + "right" |
| // => ("prefix" + inner + "postfixright") |
| res.setWrapped( |
| left.prefix, |
| new BasicEvaluatedExpression() |
| .setString(left.postfix.string + right.string) |
| .setRange(joinRanges(left.postfix.range, right.range)), |
| left.wrappedInnerExpressions |
| ); |
| } else if ( |
| left.postfix && |
| left.postfix.isString() && |
| right.isNumber() |
| ) { |
| // ("prefix" + inner + "postfix") + 123 |
| // => ("prefix" + inner + "postfix123") |
| res.setWrapped( |
| left.prefix, |
| new BasicEvaluatedExpression() |
| .setString(left.postfix.string + right.number) |
| .setRange(joinRanges(left.postfix.range, right.range)), |
| left.wrappedInnerExpressions |
| ); |
| } else if (right.isString()) { |
| // ("prefix" + inner + [null]) + "right" |
| // => ("prefix" + inner + "right") |
| res.setWrapped(left.prefix, right, left.wrappedInnerExpressions); |
| } else if (right.isNumber()) { |
| // ("prefix" + inner + [null]) + 123 |
| // => ("prefix" + inner + "123") |
| res.setWrapped( |
| left.prefix, |
| new BasicEvaluatedExpression() |
| .setString(right.number + "") |
| .setRange(right.range), |
| left.wrappedInnerExpressions |
| ); |
| } else if (right.isWrapped()) { |
| // ("prefix1" + inner1 + "postfix1") + ("prefix2" + inner2 + "postfix2") |
| // ("prefix1" + inner1 + "postfix1" + "prefix2" + inner2 + "postfix2") |
| res.setWrapped( |
| left.prefix, |
| right.postfix, |
| left.wrappedInnerExpressions && |
| right.wrappedInnerExpressions && |
| left.wrappedInnerExpressions |
| .concat(left.postfix ? [left.postfix] : []) |
| .concat(right.prefix ? [right.prefix] : []) |
| .concat(right.wrappedInnerExpressions) |
| ); |
| } else { |
| // ("prefix" + inner + postfix) + expr |
| // => ("prefix" + inner + postfix + expr + [null]) |
| res.setWrapped( |
| left.prefix, |
| null, |
| left.wrappedInnerExpressions && |
| left.wrappedInnerExpressions.concat( |
| left.postfix ? [left.postfix, right] : [right] |
| ) |
| ); |
| } |
| } else { |
| if (right.isString()) { |
| // left + "right" |
| // => ([null] + left + "right") |
| res.setWrapped(null, right, [left]); |
| } else if (right.isWrapped()) { |
| // left + (prefix + inner + "postfix") |
| // => ([null] + left + prefix + inner + "postfix") |
| res.setWrapped( |
| null, |
| right.postfix, |
| right.wrappedInnerExpressions && |
| (right.prefix ? [left, right.prefix] : [left]).concat( |
| right.wrappedInnerExpressions |
| ) |
| ); |
| } else { |
| return; |
| } |
| } |
| res.setRange(expr.range); |
| return res; |
| } else if (expr.operator === "-") { |
| left = this.evaluateExpression(expr.left); |
| right = this.evaluateExpression(expr.right); |
| if (!left || !right) return; |
| if (!left.isNumber() || !right.isNumber()) return; |
| res = new BasicEvaluatedExpression(); |
| res.setNumber(left.number - right.number); |
| res.setRange(expr.range); |
| return res; |
| } else if (expr.operator === "*") { |
| left = this.evaluateExpression(expr.left); |
| right = this.evaluateExpression(expr.right); |
| if (!left || !right) return; |
| if (!left.isNumber() || !right.isNumber()) return; |
| res = new BasicEvaluatedExpression(); |
| res.setNumber(left.number * right.number); |
| res.setRange(expr.range); |
| return res; |
| } else if (expr.operator === "/") { |
| left = this.evaluateExpression(expr.left); |
| right = this.evaluateExpression(expr.right); |
| if (!left || !right) return; |
| if (!left.isNumber() || !right.isNumber()) return; |
| res = new BasicEvaluatedExpression(); |
| res.setNumber(left.number / right.number); |
| res.setRange(expr.range); |
| return res; |
| } else if (expr.operator === "**") { |
| left = this.evaluateExpression(expr.left); |
| right = this.evaluateExpression(expr.right); |
| if (!left || !right) return; |
| if (!left.isNumber() || !right.isNumber()) return; |
| res = new BasicEvaluatedExpression(); |
| res.setNumber(Math.pow(left.number, right.number)); |
| res.setRange(expr.range); |
| return res; |
| } else if (expr.operator === "==" || expr.operator === "===") { |
| left = this.evaluateExpression(expr.left); |
| right = this.evaluateExpression(expr.right); |
| if (!left || !right) return; |
| res = new BasicEvaluatedExpression(); |
| res.setRange(expr.range); |
| if (left.isString() && right.isString()) { |
| return res.setBoolean(left.string === right.string); |
| } else if (left.isNumber() && right.isNumber()) { |
| return res.setBoolean(left.number === right.number); |
| } else if (left.isBoolean() && right.isBoolean()) { |
| return res.setBoolean(left.bool === right.bool); |
| } |
| } else if (expr.operator === "!=" || expr.operator === "!==") { |
| left = this.evaluateExpression(expr.left); |
| right = this.evaluateExpression(expr.right); |
| if (!left || !right) return; |
| res = new BasicEvaluatedExpression(); |
| res.setRange(expr.range); |
| if (left.isString() && right.isString()) { |
| return res.setBoolean(left.string !== right.string); |
| } else if (left.isNumber() && right.isNumber()) { |
| return res.setBoolean(left.number !== right.number); |
| } else if (left.isBoolean() && right.isBoolean()) { |
| return res.setBoolean(left.bool !== right.bool); |
| } |
| } else if (expr.operator === "&") { |
| left = this.evaluateExpression(expr.left); |
| right = this.evaluateExpression(expr.right); |
| if (!left || !right) return; |
| if (!left.isNumber() || !right.isNumber()) return; |
| res = new BasicEvaluatedExpression(); |
| res.setNumber(left.number & right.number); |
| res.setRange(expr.range); |
| return res; |
| } else if (expr.operator === "|") { |
| left = this.evaluateExpression(expr.left); |
| right = this.evaluateExpression(expr.right); |
| if (!left || !right) return; |
| if (!left.isNumber() || !right.isNumber()) return; |
| res = new BasicEvaluatedExpression(); |
| res.setNumber(left.number | right.number); |
| res.setRange(expr.range); |
| return res; |
| } else if (expr.operator === "^") { |
| left = this.evaluateExpression(expr.left); |
| right = this.evaluateExpression(expr.right); |
| if (!left || !right) return; |
| if (!left.isNumber() || !right.isNumber()) return; |
| res = new BasicEvaluatedExpression(); |
| res.setNumber(left.number ^ right.number); |
| res.setRange(expr.range); |
| return res; |
| } else if (expr.operator === ">>>") { |
| left = this.evaluateExpression(expr.left); |
| right = this.evaluateExpression(expr.right); |
| if (!left || !right) return; |
| if (!left.isNumber() || !right.isNumber()) return; |
| res = new BasicEvaluatedExpression(); |
| res.setNumber(left.number >>> right.number); |
| res.setRange(expr.range); |
| return res; |
| } else if (expr.operator === ">>") { |
| left = this.evaluateExpression(expr.left); |
| right = this.evaluateExpression(expr.right); |
| if (!left || !right) return; |
| if (!left.isNumber() || !right.isNumber()) return; |
| res = new BasicEvaluatedExpression(); |
| res.setNumber(left.number >> right.number); |
| res.setRange(expr.range); |
| return res; |
| } else if (expr.operator === "<<") { |
| left = this.evaluateExpression(expr.left); |
| right = this.evaluateExpression(expr.right); |
| if (!left || !right) return; |
| if (!left.isNumber() || !right.isNumber()) return; |
| res = new BasicEvaluatedExpression(); |
| res.setNumber(left.number << right.number); |
| res.setRange(expr.range); |
| return res; |
| } |
| }); |
| this.hooks.evaluate.for("UnaryExpression").tap("Parser", expr => { |
| if (expr.operator === "typeof") { |
| let res; |
| let name; |
| if (expr.argument.type === "Identifier") { |
| name = |
| this.scope.renames.get(expr.argument.name) || expr.argument.name; |
| if (!this.scope.definitions.has(name)) { |
| const hook = this.hooks.evaluateTypeof.get(name); |
| if (hook !== undefined) { |
| res = hook.call(expr); |
| if (res !== undefined) return res; |
| } |
| } |
| } |
| if (expr.argument.type === "MemberExpression") { |
| const exprName = this.getNameForExpression(expr.argument); |
| if (exprName && exprName.free) { |
| const hook = this.hooks.evaluateTypeof.get(exprName.name); |
| if (hook !== undefined) { |
| res = hook.call(expr); |
| if (res !== undefined) return res; |
| } |
| } |
| } |
| if (expr.argument.type === "FunctionExpression") { |
| return new BasicEvaluatedExpression() |
| .setString("function") |
| .setRange(expr.range); |
| } |
| const arg = this.evaluateExpression(expr.argument); |
| if (arg.isString() || arg.isWrapped()) { |
| return new BasicEvaluatedExpression() |
| .setString("string") |
| .setRange(expr.range); |
| } |
| if (arg.isNumber()) { |
| return new BasicEvaluatedExpression() |
| .setString("number") |
| .setRange(expr.range); |
| } |
| if (arg.isBoolean()) { |
| return new BasicEvaluatedExpression() |
| .setString("boolean") |
| .setRange(expr.range); |
| } |
| if (arg.isArray() || arg.isConstArray() || arg.isRegExp()) { |
| return new BasicEvaluatedExpression() |
| .setString("object") |
| .setRange(expr.range); |
| } |
| } else if (expr.operator === "!") { |
| const argument = this.evaluateExpression(expr.argument); |
| if (!argument) return; |
| if (argument.isBoolean()) { |
| return new BasicEvaluatedExpression() |
| .setBoolean(!argument.bool) |
| .setRange(expr.range); |
| } |
| if (argument.isTruthy()) { |
| return new BasicEvaluatedExpression() |
| .setBoolean(false) |
| .setRange(expr.range); |
| } |
| if (argument.isFalsy()) { |
| return new BasicEvaluatedExpression() |
| .setBoolean(true) |
| .setRange(expr.range); |
| } |
| if (argument.isString()) { |
| return new BasicEvaluatedExpression() |
| .setBoolean(!argument.string) |
| .setRange(expr.range); |
| } |
| if (argument.isNumber()) { |
| return new BasicEvaluatedExpression() |
| .setBoolean(!argument.number) |
| .setRange(expr.range); |
| } |
| } else if (expr.operator === "~") { |
| const argument = this.evaluateExpression(expr.argument); |
| if (!argument) return; |
| if (!argument.isNumber()) return; |
| const res = new BasicEvaluatedExpression(); |
| res.setNumber(~argument.number); |
| res.setRange(expr.range); |
| return res; |
| } |
| }); |
| this.hooks.evaluateTypeof.for("undefined").tap("Parser", expr => { |
| return new BasicEvaluatedExpression() |
| .setString("undefined") |
| .setRange(expr.range); |
| }); |
| this.hooks.evaluate.for("Identifier").tap("Parser", expr => { |
| const name = this.scope.renames.get(expr.name) || expr.name; |
| if (!this.scope.definitions.has(expr.name)) { |
| const hook = this.hooks.evaluateIdentifier.get(name); |
| if (hook !== undefined) { |
| const result = hook.call(expr); |
| if (result) return result; |
| } |
| return new BasicEvaluatedExpression() |
| .setIdentifier(name) |
| .setRange(expr.range); |
| } else { |
| const hook = this.hooks.evaluateDefinedIdentifier.get(name); |
| if (hook !== undefined) { |
| return hook.call(expr); |
| } |
| } |
| }); |
| this.hooks.evaluate.for("ThisExpression").tap("Parser", expr => { |
| const name = this.scope.renames.get("this"); |
| if (name) { |
| const hook = this.hooks.evaluateIdentifier.get(name); |
| if (hook !== undefined) { |
| const result = hook.call(expr); |
| if (result) return result; |
| } |
| return new BasicEvaluatedExpression() |
| .setIdentifier(name) |
| .setRange(expr.range); |
| } |
| }); |
| this.hooks.evaluate.for("MemberExpression").tap("Parser", expression => { |
| let exprName = this.getNameForExpression(expression); |
| if (exprName) { |
| if (exprName.free) { |
| const hook = this.hooks.evaluateIdentifier.get(exprName.name); |
| if (hook !== undefined) { |
| const result = hook.call(expression); |
| if (result) return result; |
| } |
| return new BasicEvaluatedExpression() |
| .setIdentifier(exprName.name) |
| .setRange(expression.range); |
| } else { |
| const hook = this.hooks.evaluateDefinedIdentifier.get(exprName.name); |
| if (hook !== undefined) { |
| return hook.call(expression); |
| } |
| } |
| } |
| }); |
| this.hooks.evaluate.for("CallExpression").tap("Parser", expr => { |
| if (expr.callee.type !== "MemberExpression") return; |
| if ( |
| expr.callee.property.type !== |
| (expr.callee.computed ? "Literal" : "Identifier") |
| ) |
| return; |
| const param = this.evaluateExpression(expr.callee.object); |
| if (!param) return; |
| const property = expr.callee.property.name || expr.callee.property.value; |
| const hook = this.hooks.evaluateCallExpressionMember.get(property); |
| if (hook !== undefined) { |
| return hook.call(expr, param); |
| } |
| }); |
| this.hooks.evaluateCallExpressionMember |
| .for("replace") |
| .tap("Parser", (expr, param) => { |
| if (!param.isString()) return; |
| if (expr.arguments.length !== 2) return; |
| let arg1 = this.evaluateExpression(expr.arguments[0]); |
| let arg2 = this.evaluateExpression(expr.arguments[1]); |
| if (!arg1.isString() && !arg1.isRegExp()) return; |
| arg1 = arg1.regExp || arg1.string; |
| if (!arg2.isString()) return; |
| arg2 = arg2.string; |
| return new BasicEvaluatedExpression() |
| .setString(param.string.replace(arg1, arg2)) |
| .setRange(expr.range); |
| }); |
| ["substr", "substring"].forEach(fn => { |
| this.hooks.evaluateCallExpressionMember |
| .for(fn) |
| .tap("Parser", (expr, param) => { |
| if (!param.isString()) return; |
| let arg1; |
| let result, |
| str = param.string; |
| switch (expr.arguments.length) { |
| case 1: |
| arg1 = this.evaluateExpression(expr.arguments[0]); |
| if (!arg1.isNumber()) return; |
| result = str[fn](arg1.number); |
| break; |
| case 2: { |
| arg1 = this.evaluateExpression(expr.arguments[0]); |
| const arg2 = this.evaluateExpression(expr.arguments[1]); |
| if (!arg1.isNumber()) return; |
| if (!arg2.isNumber()) return; |
| result = str[fn](arg1.number, arg2.number); |
| break; |
| } |
| default: |
| return; |
| } |
| return new BasicEvaluatedExpression() |
| .setString(result) |
| .setRange(expr.range); |
| }); |
| }); |
| |
| /** |
| * @param {string} kind "cooked" | "raw" |
| * @param {TODO} templateLiteralExpr TemplateLiteral expr |
| * @returns {{quasis: BasicEvaluatedExpression[], parts: BasicEvaluatedExpression[]}} Simplified template |
| */ |
| const getSimplifiedTemplateResult = (kind, templateLiteralExpr) => { |
| const quasis = []; |
| const parts = []; |
| |
| for (let i = 0; i < templateLiteralExpr.quasis.length; i++) { |
| const quasiExpr = templateLiteralExpr.quasis[i]; |
| const quasi = quasiExpr.value[kind]; |
| |
| if (i > 0) { |
| const prevExpr = parts[parts.length - 1]; |
| const expr = this.evaluateExpression( |
| templateLiteralExpr.expressions[i - 1] |
| ); |
| const exprAsString = expr.asString(); |
| if (typeof exprAsString === "string") { |
| // We can merge quasi + expr + quasi when expr |
| // is a const string |
| |
| prevExpr.setString(prevExpr.string + exprAsString + quasi); |
| prevExpr.setRange([prevExpr.range[0], quasiExpr.range[1]]); |
| // We unset the expression as it doesn't match to a single expression |
| prevExpr.setExpression(undefined); |
| continue; |
| } |
| parts.push(expr); |
| } |
| |
| const part = new BasicEvaluatedExpression() |
| .setString(quasi) |
| .setRange(quasiExpr.range) |
| .setExpression(quasiExpr); |
| quasis.push(part); |
| parts.push(part); |
| } |
| return { |
| quasis, |
| parts |
| }; |
| }; |
| |
| this.hooks.evaluate.for("TemplateLiteral").tap("Parser", node => { |
| const { quasis, parts } = getSimplifiedTemplateResult("cooked", node); |
| if (parts.length === 1) { |
| return parts[0].setRange(node.range); |
| } |
| return new BasicEvaluatedExpression() |
| .setTemplateString(quasis, parts, "cooked") |
| .setRange(node.range); |
| }); |
| this.hooks.evaluate.for("TaggedTemplateExpression").tap("Parser", node => { |
| if (this.evaluateExpression(node.tag).identifier !== "String.raw") return; |
| const { quasis, parts } = getSimplifiedTemplateResult("raw", node.quasi); |
| if (parts.length === 1) { |
| return parts[0].setRange(node.range); |
| } |
| return new BasicEvaluatedExpression() |
| .setTemplateString(quasis, parts, "raw") |
| .setRange(node.range); |
| }); |
| |
| this.hooks.evaluateCallExpressionMember |
| .for("concat") |
| .tap("Parser", (expr, param) => { |
| if (!param.isString() && !param.isWrapped()) return; |
| |
| let stringSuffix = null; |
| let hasUnknownParams = false; |
| for (let i = expr.arguments.length - 1; i >= 0; i--) { |
| const argExpr = this.evaluateExpression(expr.arguments[i]); |
| if (!argExpr.isString() && !argExpr.isNumber()) { |
| hasUnknownParams = true; |
| break; |
| } |
| |
| const value = argExpr.isString() |
| ? argExpr.string |
| : "" + argExpr.number; |
| |
| const newString = value + (stringSuffix ? stringSuffix.string : ""); |
| const newRange = [ |
| argExpr.range[0], |
| (stringSuffix || argExpr).range[1] |
| ]; |
| stringSuffix = new BasicEvaluatedExpression() |
| .setString(newString) |
| .setRange(newRange); |
| } |
| |
| if (hasUnknownParams) { |
| const prefix = param.isString() ? param : param.prefix; |
| return new BasicEvaluatedExpression() |
| .setWrapped(prefix, stringSuffix) |
| .setRange(expr.range); |
| } else if (param.isWrapped()) { |
| const postfix = stringSuffix || param.postfix; |
| return new BasicEvaluatedExpression() |
| .setWrapped(param.prefix, postfix) |
| .setRange(expr.range); |
| } else { |
| const newString = |
| param.string + (stringSuffix ? stringSuffix.string : ""); |
| return new BasicEvaluatedExpression() |
| .setString(newString) |
| .setRange(expr.range); |
| } |
| }); |
| this.hooks.evaluateCallExpressionMember |
| .for("split") |
| .tap("Parser", (expr, param) => { |
| if (!param.isString()) return; |
| if (expr.arguments.length !== 1) return; |
| let result; |
| const arg = this.evaluateExpression(expr.arguments[0]); |
| if (arg.isString()) { |
| result = param.string.split(arg.string); |
| } else if (arg.isRegExp()) { |
| result = param.string.split(arg.regExp); |
| } else { |
| return; |
| } |
| return new BasicEvaluatedExpression() |
| .setArray(result) |
| .setRange(expr.range); |
| }); |
| this.hooks.evaluate.for("ConditionalExpression").tap("Parser", expr => { |
| const condition = this.evaluateExpression(expr.test); |
| const conditionValue = condition.asBool(); |
| let res; |
| if (conditionValue === undefined) { |
| const consequent = this.evaluateExpression(expr.consequent); |
| const alternate = this.evaluateExpression(expr.alternate); |
| if (!consequent || !alternate) return; |
| res = new BasicEvaluatedExpression(); |
| if (consequent.isConditional()) { |
| res.setOptions(consequent.options); |
| } else { |
| res.setOptions([consequent]); |
| } |
| if (alternate.isConditional()) { |
| res.addOptions(alternate.options); |
| } else { |
| res.addOptions([alternate]); |
| } |
| } else { |
| res = this.evaluateExpression( |
| conditionValue ? expr.consequent : expr.alternate |
| ); |
| } |
| res.setRange(expr.range); |
| return res; |
| }); |
| this.hooks.evaluate.for("ArrayExpression").tap("Parser", expr => { |
| const items = expr.elements.map(element => { |
| return element !== null && this.evaluateExpression(element); |
| }); |
| if (!items.every(Boolean)) return; |
| return new BasicEvaluatedExpression() |
| .setItems(items) |
| .setRange(expr.range); |
| }); |
| } |
| |
| getRenameIdentifier(expr) { |
| const result = this.evaluateExpression(expr); |
| if (result && result.isIdentifier()) { |
| return result.identifier; |
| } |
| } |
| |
| walkClass(classy) { |
| if (classy.superClass) this.walkExpression(classy.superClass); |
| if (classy.body && classy.body.type === "ClassBody") { |
| const wasTopLevel = this.scope.topLevelScope; |
| this.scope.topLevelScope = false; |
| for (const methodDefinition of classy.body.body) { |
| if (methodDefinition.type === "MethodDefinition") { |
| this.walkMethodDefinition(methodDefinition); |
| } |
| } |
| this.scope.topLevelScope = wasTopLevel; |
| } |
| } |
| |
| walkMethodDefinition(methodDefinition) { |
| if (methodDefinition.computed && methodDefinition.key) { |
| this.walkExpression(methodDefinition.key); |
| } |
| if (methodDefinition.value) { |
| this.walkExpression(methodDefinition.value); |
| } |
| } |
| |
| // Prewalking iterates the scope for variable declarations |
| prewalkStatements(statements) { |
| for (let index = 0, len = statements.length; index < len; index++) { |
| const statement = statements[index]; |
| this.prewalkStatement(statement); |
| } |
| } |
| |
| // Block-Prewalking iterates the scope for block variable declarations |
| blockPrewalkStatements(statements) { |
| for (let index = 0, len = statements.length; index < len; index++) { |
| const statement = statements[index]; |
| this.blockPrewalkStatement(statement); |
| } |
| } |
| |
| // Walking iterates the statements and expressions and processes them |
| walkStatements(statements) { |
| for (let index = 0, len = statements.length; index < len; index++) { |
| const statement = statements[index]; |
| this.walkStatement(statement); |
| } |
| } |
| |
| prewalkStatement(statement) { |
| switch (statement.type) { |
| case "BlockStatement": |
| this.prewalkBlockStatement(statement); |
| break; |
| case "DoWhileStatement": |
| this.prewalkDoWhileStatement(statement); |
| break; |
| case "ExportAllDeclaration": |
| this.prewalkExportAllDeclaration(statement); |
| break; |
| case "ExportDefaultDeclaration": |
| this.prewalkExportDefaultDeclaration(statement); |
| break; |
| case "ExportNamedDeclaration": |
| this.prewalkExportNamedDeclaration(statement); |
| break; |
| case "ForInStatement": |
| this.prewalkForInStatement(statement); |
| break; |
| case "ForOfStatement": |
| this.prewalkForOfStatement(statement); |
| break; |
| case "ForStatement": |
| this.prewalkForStatement(statement); |
| break; |
| case "FunctionDeclaration": |
| this.prewalkFunctionDeclaration(statement); |
| break; |
| case "IfStatement": |
| this.prewalkIfStatement(statement); |
| break; |
| case "ImportDeclaration": |
| this.prewalkImportDeclaration(statement); |
| break; |
| case "LabeledStatement": |
| this.prewalkLabeledStatement(statement); |
| break; |
| case "SwitchStatement": |
| this.prewalkSwitchStatement(statement); |
| break; |
| case "TryStatement": |
| this.prewalkTryStatement(statement); |
| break; |
| case "VariableDeclaration": |
| this.prewalkVariableDeclaration(statement); |
| break; |
| case "WhileStatement": |
| this.prewalkWhileStatement(statement); |
| break; |
| case "WithStatement": |
| this.prewalkWithStatement(statement); |
| break; |
| } |
| } |
| |
| blockPrewalkStatement(statement) { |
| switch (statement.type) { |
| case "VariableDeclaration": |
| this.blockPrewalkVariableDeclaration(statement); |
| break; |
| case "ExportDefaultDeclaration": |
| this.blockPrewalkExportDefaultDeclaration(statement); |
| break; |
| case "ExportNamedDeclaration": |
| this.blockPrewalkExportNamedDeclaration(statement); |
| break; |
| case "ClassDeclaration": |
| this.blockPrewalkClassDeclaration(statement); |
| break; |
| } |
| } |
| |
| walkStatement(statement) { |
| if (this.hooks.statement.call(statement) !== undefined) return; |
| switch (statement.type) { |
| case "BlockStatement": |
| this.walkBlockStatement(statement); |
| break; |
| case "ClassDeclaration": |
| this.walkClassDeclaration(statement); |
| break; |
| case "DoWhileStatement": |
| this.walkDoWhileStatement(statement); |
| break; |
| case "ExportDefaultDeclaration": |
| this.walkExportDefaultDeclaration(statement); |
| break; |
| case "ExportNamedDeclaration": |
| this.walkExportNamedDeclaration(statement); |
| break; |
| case "ExpressionStatement": |
| this.walkExpressionStatement(statement); |
| break; |
| case "ForInStatement": |
| this.walkForInStatement(statement); |
| break; |
| case "ForOfStatement": |
| this.walkForOfStatement(statement); |
| break; |
| case "ForStatement": |
| this.walkForStatement(statement); |
| break; |
| case "FunctionDeclaration": |
| this.walkFunctionDeclaration(statement); |
| break; |
| case "IfStatement": |
| this.walkIfStatement(statement); |
| break; |
| case "LabeledStatement": |
| this.walkLabeledStatement(statement); |
| break; |
| case "ReturnStatement": |
| this.walkReturnStatement(statement); |
| break; |
| case "SwitchStatement": |
| this.walkSwitchStatement(statement); |
| break; |
| case "ThrowStatement": |
| this.walkThrowStatement(statement); |
| break; |
| case "TryStatement": |
| this.walkTryStatement(statement); |
| break; |
| case "VariableDeclaration": |
| this.walkVariableDeclaration(statement); |
| break; |
| case "WhileStatement": |
| this.walkWhileStatement(statement); |
| break; |
| case "WithStatement": |
| this.walkWithStatement(statement); |
| break; |
| } |
| } |
| |
| // Real Statements |
| prewalkBlockStatement(statement) { |
| this.prewalkStatements(statement.body); |
| } |
| |
| walkBlockStatement(statement) { |
| this.inBlockScope(() => { |
| const body = statement.body; |
| this.blockPrewalkStatements(body); |
| this.walkStatements(body); |
| }); |
| } |
| |
| walkExpressionStatement(statement) { |
| this.walkExpression(statement.expression); |
| } |
| |
| prewalkIfStatement(statement) { |
| this.prewalkStatement(statement.consequent); |
| if (statement.alternate) { |
| this.prewalkStatement(statement.alternate); |
| } |
| } |
| |
| walkIfStatement(statement) { |
| const result = this.hooks.statementIf.call(statement); |
| if (result === undefined) { |
| this.walkExpression(statement.test); |
| this.walkStatement(statement.consequent); |
| if (statement.alternate) { |
| this.walkStatement(statement.alternate); |
| } |
| } else { |
| if (result) { |
| this.walkStatement(statement.consequent); |
| } else if (statement.alternate) { |
| this.walkStatement(statement.alternate); |
| } |
| } |
| } |
| |
| prewalkLabeledStatement(statement) { |
| this.prewalkStatement(statement.body); |
| } |
| |
| walkLabeledStatement(statement) { |
| const hook = this.hooks.label.get(statement.label.name); |
| if (hook !== undefined) { |
| const result = hook.call(statement); |
| if (result === true) return; |
| } |
| this.walkStatement(statement.body); |
| } |
| |
| prewalkWithStatement(statement) { |
| this.prewalkStatement(statement.body); |
| } |
| |
| walkWithStatement(statement) { |
| this.walkExpression(statement.object); |
| this.walkStatement(statement.body); |
| } |
| |
| prewalkSwitchStatement(statement) { |
| this.prewalkSwitchCases(statement.cases); |
| } |
| |
| walkSwitchStatement(statement) { |
| this.walkExpression(statement.discriminant); |
| this.walkSwitchCases(statement.cases); |
| } |
| |
| walkTerminatingStatement(statement) { |
| if (statement.argument) this.walkExpression(statement.argument); |
| } |
| |
| walkReturnStatement(statement) { |
| this.walkTerminatingStatement(statement); |
| } |
| |
| walkThrowStatement(statement) { |
| this.walkTerminatingStatement(statement); |
| } |
| |
| prewalkTryStatement(statement) { |
| this.prewalkStatement(statement.block); |
| } |
| |
| walkTryStatement(statement) { |
| if (this.scope.inTry) { |
| this.walkStatement(statement.block); |
| } else { |
| this.scope.inTry = true; |
| this.walkStatement(statement.block); |
| this.scope.inTry = false; |
| } |
| if (statement.handler) this.walkCatchClause(statement.handler); |
| if (statement.finalizer) this.walkStatement(statement.finalizer); |
| } |
| |
| prewalkWhileStatement(statement) { |
| this.prewalkStatement(statement.body); |
| } |
| |
| walkWhileStatement(statement) { |
| this.walkExpression(statement.test); |
| this.walkStatement(statement.body); |
| } |
| |
| prewalkDoWhileStatement(statement) { |
| this.prewalkStatement(statement.body); |
| } |
| |
| walkDoWhileStatement(statement) { |
| this.walkStatement(statement.body); |
| this.walkExpression(statement.test); |
| } |
| |
| prewalkForStatement(statement) { |
| if (statement.init) { |
| if (statement.init.type === "VariableDeclaration") { |
| this.prewalkStatement(statement.init); |
| } |
| } |
| this.prewalkStatement(statement.body); |
| } |
| |
| walkForStatement(statement) { |
| this.inBlockScope(() => { |
| if (statement.init) { |
| if (statement.init.type === "VariableDeclaration") { |
| this.blockPrewalkVariableDeclaration(statement.init); |
| this.walkStatement(statement.init); |
| } else { |
| this.walkExpression(statement.init); |
| } |
| } |
| if (statement.test) { |
| this.walkExpression(statement.test); |
| } |
| if (statement.update) { |
| this.walkExpression(statement.update); |
| } |
| const body = statement.body; |
| if (body.type === "BlockStatement") { |
| // no need to add additional scope |
| this.blockPrewalkStatements(body.body); |
| this.walkStatements(body.body); |
| } else { |
| this.walkStatement(body); |
| } |
| }); |
| } |
| |
| prewalkForInStatement(statement) { |
| if (statement.left.type === "VariableDeclaration") { |
| this.prewalkVariableDeclaration(statement.left); |
| } |
| this.prewalkStatement(statement.body); |
| } |
| |
| walkForInStatement(statement) { |
| this.inBlockScope(() => { |
| if (statement.left.type === "VariableDeclaration") { |
| this.blockPrewalkVariableDeclaration(statement.left); |
| this.walkVariableDeclaration(statement.left); |
| } else { |
| this.walkPattern(statement.left); |
| } |
| this.walkExpression(statement.right); |
| const body = statement.body; |
| if (body.type === "BlockStatement") { |
| // no need to add additional scope |
| this.blockPrewalkStatements(body.body); |
| this.walkStatements(body.body); |
| } else { |
| this.walkStatement(body); |
| } |
| }); |
| } |
| |
| prewalkForOfStatement(statement) { |
| if (statement.left.type === "VariableDeclaration") { |
| this.prewalkVariableDeclaration(statement.left); |
| } |
| this.prewalkStatement(statement.body); |
| } |
| |
| walkForOfStatement(statement) { |
| this.inBlockScope(() => { |
| if (statement.left.type === "VariableDeclaration") { |
| this.blockPrewalkVariableDeclaration(statement.left); |
| this.walkVariableDeclaration(statement.left); |
| } else { |
| this.walkPattern(statement.left); |
| } |
| this.walkExpression(statement.right); |
| const body = statement.body; |
| if (body.type === "BlockStatement") { |
| // no need to add additional scope |
| this.blockPrewalkStatements(body.body); |
| this.walkStatements(body.body); |
| } else { |
| this.walkStatement(body); |
| } |
| }); |
| } |
| |
| // Declarations |
| prewalkFunctionDeclaration(statement) { |
| if (statement.id) { |
| this.scope.renames.set(statement.id.name, null); |
| this.scope.definitions.add(statement.id.name); |
| } |
| } |
| |
| walkFunctionDeclaration(statement) { |
| const wasTopLevel = this.scope.topLevelScope; |
| this.scope.topLevelScope = false; |
| this.inFunctionScope(true, statement.params, () => { |
| for (const param of statement.params) { |
| this.walkPattern(param); |
| } |
| if (statement.body.type === "BlockStatement") { |
| this.detectStrictMode(statement.body.body); |
| this.prewalkStatement(statement.body); |
| this.walkStatement(statement.body); |
| } else { |
| this.walkExpression(statement.body); |
| } |
| }); |
| this.scope.topLevelScope = wasTopLevel; |
| } |
| |
| prewalkImportDeclaration(statement) { |
| const source = statement.source.value; |
| this.hooks.import.call(statement, source); |
| for (const specifier of statement.specifiers) { |
| const name = specifier.local.name; |
| this.scope.renames.set(name, null); |
| this.scope.definitions.add(name); |
| switch (specifier.type) { |
| case "ImportDefaultSpecifier": |
| this.hooks.importSpecifier.call(statement, source, "default", name); |
| break; |
| case "ImportSpecifier": |
| this.hooks.importSpecifier.call( |
| statement, |
| source, |
| specifier.imported.name, |
| name |
| ); |
| break; |
| case "ImportNamespaceSpecifier": |
| this.hooks.importSpecifier.call(statement, source, null, name); |
| break; |
| } |
| } |
| } |
| |
| enterDeclaration(declaration, onIdent) { |
| switch (declaration.type) { |
| case "VariableDeclaration": |
| for (const declarator of declaration.declarations) { |
| switch (declarator.type) { |
| case "VariableDeclarator": { |
| this.enterPattern(declarator.id, onIdent); |
| break; |
| } |
| } |
| } |
| break; |
| case "FunctionDeclaration": |
| this.enterPattern(declaration.id, onIdent); |
| break; |
| case "ClassDeclaration": |
| this.enterPattern(declaration.id, onIdent); |
| break; |
| } |
| } |
| |
| blockPrewalkExportNamedDeclaration(statement) { |
| if (statement.declaration) { |
| this.blockPrewalkStatement(statement.declaration); |
| } |
| } |
| |
| prewalkExportNamedDeclaration(statement) { |
| let source; |
| if (statement.source) { |
| source = statement.source.value; |
| this.hooks.exportImport.call(statement, source); |
| } else { |
| this.hooks.export.call(statement); |
| } |
| if (statement.declaration) { |
| if ( |
| !this.hooks.exportDeclaration.call(statement, statement.declaration) |
| ) { |
| this.prewalkStatement(statement.declaration); |
| let index = 0; |
| this.enterDeclaration(statement.declaration, def => { |
| this.hooks.exportSpecifier.call(statement, def, def, index++); |
| }); |
| } |
| } |
| if (statement.specifiers) { |
| for ( |
| let specifierIndex = 0; |
| specifierIndex < statement.specifiers.length; |
| specifierIndex++ |
| ) { |
| const specifier = statement.specifiers[specifierIndex]; |
| switch (specifier.type) { |
| case "ExportSpecifier": { |
| const name = specifier.exported.name; |
| if (source) { |
| this.hooks.exportImportSpecifier.call( |
| statement, |
| source, |
| specifier.local.name, |
| name, |
| specifierIndex |
| ); |
| } else { |
| this.hooks.exportSpecifier.call( |
| statement, |
| specifier.local.name, |
| name, |
| specifierIndex |
| ); |
| } |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| walkExportNamedDeclaration(statement) { |
| if (statement.declaration) { |
| this.walkStatement(statement.declaration); |
| } |
| } |
| |
| blockPrewalkExportDefaultDeclaration(statement) { |
| if (statement.declaration.type === "ClassDeclaration") { |
| this.blockPrewalkClassDeclaration(statement.declaration); |
| } |
| } |
| |
| prewalkExportDefaultDeclaration(statement) { |
| this.prewalkStatement(statement.declaration); |
| if ( |
| statement.declaration.id && |
| statement.declaration.type !== "FunctionExpression" && |
| statement.declaration.type !== "ClassExpression" |
| ) { |
| this.hooks.exportSpecifier.call( |
| statement, |
| statement.declaration.id.name, |
| "default" |
| ); |
| } |
| } |
| |
| walkExportDefaultDeclaration(statement) { |
| this.hooks.export.call(statement); |
| if ( |
| statement.declaration.id && |
| statement.declaration.type !== "FunctionExpression" && |
| statement.declaration.type !== "ClassExpression" |
| ) { |
| if ( |
| !this.hooks.exportDeclaration.call(statement, statement.declaration) |
| ) { |
| this.walkStatement(statement.declaration); |
| } |
| } else { |
| // Acorn parses `export default function() {}` as `FunctionDeclaration` and |
| // `export default class {}` as `ClassDeclaration`, both with `id = null`. |
| // These nodes must be treated as expressions. |
| if (statement.declaration.type === "FunctionDeclaration") { |
| this.walkFunctionDeclaration(statement.declaration); |
| } else if (statement.declaration.type === "ClassDeclaration") { |
| this.walkClassDeclaration(statement.declaration); |
| } else { |
| this.walkExpression(statement.declaration); |
| } |
| if (!this.hooks.exportExpression.call(statement, statement.declaration)) { |
| this.hooks.exportSpecifier.call( |
| statement, |
| statement.declaration, |
| "default" |
| ); |
| } |
| } |
| } |
| |
| prewalkExportAllDeclaration(statement) { |
| const source = statement.source.value; |
| this.hooks.exportImport.call(statement, source); |
| this.hooks.exportImportSpecifier.call(statement, source, null, null, 0); |
| } |
| |
| prewalkVariableDeclaration(statement) { |
| if (statement.kind !== "var") return; |
| this._prewalkVariableDeclaration(statement, this.hooks.varDeclarationVar); |
| } |
| |
| blockPrewalkVariableDeclaration(statement) { |
| if (statement.kind === "var") return; |
| const hookMap = |
| statement.kind === "const" |
| ? this.hooks.varDeclarationConst |
| : this.hooks.varDeclarationLet; |
| this._prewalkVariableDeclaration(statement, hookMap); |
| } |
| |
| _prewalkVariableDeclaration(statement, hookMap) { |
| for (const declarator of statement.declarations) { |
| switch (declarator.type) { |
| case "VariableDeclarator": { |
| this.enterPattern(declarator.id, (name, decl) => { |
| let hook = hookMap.get(name); |
| if (hook === undefined || !hook.call(decl)) { |
| hook = this.hooks.varDeclaration.get(name); |
| if (hook === undefined || !hook.call(decl)) { |
| this.scope.renames.set(name, null); |
| this.scope.definitions.add(name); |
| } |
| } |
| }); |
| break; |
| } |
| } |
| } |
| } |
| |
| walkVariableDeclaration(statement) { |
| for (const declarator of statement.declarations) { |
| switch (declarator.type) { |
| case "VariableDeclarator": { |
| const renameIdentifier = |
| declarator.init && this.getRenameIdentifier(declarator.init); |
| if (renameIdentifier && declarator.id.type === "Identifier") { |
| const hook = this.hooks.canRename.get(renameIdentifier); |
| if (hook !== undefined && hook.call(declarator.init)) { |
| // renaming with "var a = b;" |
| const hook = this.hooks.rename.get(renameIdentifier); |
| if (hook === undefined || !hook.call(declarator.init)) { |
| this.scope.renames.set( |
| declarator.id.name, |
| this.scope.renames.get(renameIdentifier) || renameIdentifier |
| ); |
| this.scope.definitions.delete(declarator.id.name); |
| } |
| break; |
| } |
| } |
| this.walkPattern(declarator.id); |
| if (declarator.init) this.walkExpression(declarator.init); |
| break; |
| } |
| } |
| } |
| } |
| |
| blockPrewalkClassDeclaration(statement) { |
| if (statement.id) { |
| this.scope.renames.set(statement.id.name, null); |
| this.scope.definitions.add(statement.id.name); |
| } |
| } |
| |
| walkClassDeclaration(statement) { |
| this.walkClass(statement); |
| } |
| |
| prewalkSwitchCases(switchCases) { |
| for (let index = 0, len = switchCases.length; index < len; index++) { |
| const switchCase = switchCases[index]; |
| this.prewalkStatements(switchCase.consequent); |
| } |
| } |
| |
| walkSwitchCases(switchCases) { |
| for (let index = 0, len = switchCases.length; index < len; index++) { |
| const switchCase = switchCases[index]; |
| |
| if (switchCase.test) { |
| this.walkExpression(switchCase.test); |
| } |
| this.walkStatements(switchCase.consequent); |
| } |
| } |
| |
| walkCatchClause(catchClause) { |
| this.inBlockScope(() => { |
| // Error binding is optional in catch clause since ECMAScript 2019 |
| if (catchClause.param !== null) { |
| this.enterPattern(catchClause.param, ident => { |
| this.scope.renames.set(ident, null); |
| this.scope.definitions.add(ident); |
| }); |
| this.walkPattern(catchClause.param); |
| } |
| this.prewalkStatement(catchClause.body); |
| this.walkStatement(catchClause.body); |
| }); |
| } |
| |
| walkPattern(pattern) { |
| switch (pattern.type) { |
| case "ArrayPattern": |
| this.walkArrayPattern(pattern); |
| break; |
| case "AssignmentPattern": |
| this.walkAssignmentPattern(pattern); |
| break; |
| case "MemberExpression": |
| this.walkMemberExpression(pattern); |
| break; |
| case "ObjectPattern": |
| this.walkObjectPattern(pattern); |
| break; |
| case "RestElement": |
| this.walkRestElement(pattern); |
| break; |
| } |
| } |
| |
| walkAssignmentPattern(pattern) { |
| this.walkExpression(pattern.right); |
| this.walkPattern(pattern.left); |
| } |
| |
| walkObjectPattern(pattern) { |
| for (let i = 0, len = pattern.properties.length; i < len; i++) { |
| const prop = pattern.properties[i]; |
| if (prop) { |
| if (prop.computed) this.walkExpression(prop.key); |
| if (prop.value) this.walkPattern(prop.value); |
| } |
| } |
| } |
| |
| walkArrayPattern(pattern) { |
| for (let i = 0, len = pattern.elements.length; i < len; i++) { |
| const element = pattern.elements[i]; |
| if (element) this.walkPattern(element); |
| } |
| } |
| |
| walkRestElement(pattern) { |
| this.walkPattern(pattern.argument); |
| } |
| |
| walkExpressions(expressions) { |
| for (const expression of expressions) { |
| if (expression) { |
| this.walkExpression(expression); |
| } |
| } |
| } |
| |
| walkExpression(expression) { |
| switch (expression.type) { |
| case "ArrayExpression": |
| this.walkArrayExpression(expression); |
| break; |
| case "ArrowFunctionExpression": |
| this.walkArrowFunctionExpression(expression); |
| break; |
| case "AssignmentExpression": |
| this.walkAssignmentExpression(expression); |
| break; |
| case "AwaitExpression": |
| this.walkAwaitExpression(expression); |
| break; |
| case "BinaryExpression": |
| this.walkBinaryExpression(expression); |
| break; |
| case "CallExpression": |
| this.walkCallExpression(expression); |
| break; |
| case "ClassExpression": |
| this.walkClassExpression(expression); |
| break; |
| case "ConditionalExpression": |
| this.walkConditionalExpression(expression); |
| break; |
| case "FunctionExpression": |
| this.walkFunctionExpression(expression); |
| break; |
| case "Identifier": |
| this.walkIdentifier(expression); |
| break; |
| case "LogicalExpression": |
| this.walkLogicalExpression(expression); |
| break; |
| case "MemberExpression": |
| this.walkMemberExpression(expression); |
| break; |
| case "NewExpression": |
| this.walkNewExpression(expression); |
| break; |
| case "ObjectExpression": |
| this.walkObjectExpression(expression); |
| break; |
| case "SequenceExpression": |
| this.walkSequenceExpression(expression); |
| break; |
| case "SpreadElement": |
| this.walkSpreadElement(expression); |
| break; |
| case "TaggedTemplateExpression": |
| this.walkTaggedTemplateExpression(expression); |
| break; |
| case "TemplateLiteral": |
| this.walkTemplateLiteral(expression); |
| break; |
| case "ThisExpression": |
| this.walkThisExpression(expression); |
| break; |
| case "UnaryExpression": |
| this.walkUnaryExpression(expression); |
| break; |
| case "UpdateExpression": |
| this.walkUpdateExpression(expression); |
| break; |
| case "YieldExpression": |
| this.walkYieldExpression(expression); |
| break; |
| } |
| } |
| |
| walkAwaitExpression(expression) { |
| this.walkExpression(expression.argument); |
| } |
| |
| walkArrayExpression(expression) { |
| if (expression.elements) { |
| this.walkExpressions(expression.elements); |
| } |
| } |
| |
| walkSpreadElement(expression) { |
| if (expression.argument) { |
| this.walkExpression(expression.argument); |
| } |
| } |
| |
| walkObjectExpression(expression) { |
| for ( |
| let propIndex = 0, len = expression.properties.length; |
| propIndex < len; |
| propIndex++ |
| ) { |
| const prop = expression.properties[propIndex]; |
| if (prop.type === "SpreadElement") { |
| this.walkExpression(prop.argument); |
| continue; |
| } |
| if (prop.computed) { |
| this.walkExpression(prop.key); |
| } |
| if (prop.shorthand) { |
| this.scope.inShorthand = true; |
| } |
| this.walkExpression(prop.value); |
| if (prop.shorthand) { |
| this.scope.inShorthand = false; |
| } |
| } |
| } |
| |
| walkFunctionExpression(expression) { |
| const wasTopLevel = this.scope.topLevelScope; |
| this.scope.topLevelScope = false; |
| const scopeParams = expression.params; |
| |
| // Add function name in scope for recursive calls |
| if (expression.id) { |
| scopeParams.push(expression.id.name); |
| } |
| |
| this.inFunctionScope(true, scopeParams, () => { |
| for (const param of expression.params) { |
| this.walkPattern(param); |
| } |
| if (expression.body.type === "BlockStatement") { |
| this.detectStrictMode(expression.body.body); |
| this.prewalkStatement(expression.body); |
| this.walkStatement(expression.body); |
| } else { |
| this.walkExpression(expression.body); |
| } |
| }); |
| this.scope.topLevelScope = wasTopLevel; |
| } |
| |
| walkArrowFunctionExpression(expression) { |
| this.inFunctionScope(false, expression.params, () => { |
| for (const param of expression.params) { |
| this.walkPattern(param); |
| } |
| if (expression.body.type === "BlockStatement") { |
| this.detectStrictMode(expression.body.body); |
| this.prewalkStatement(expression.body); |
| this.walkStatement(expression.body); |
| } else { |
| this.walkExpression(expression.body); |
| } |
| }); |
| } |
| |
| walkSequenceExpression(expression) { |
| if (expression.expressions) this.walkExpressions(expression.expressions); |
| } |
| |
| walkUpdateExpression(expression) { |
| this.walkExpression(expression.argument); |
| } |
| |
| walkUnaryExpression(expression) { |
| if (expression.operator === "typeof") { |
| const exprName = this.getNameForExpression(expression.argument); |
| if (exprName && exprName.free) { |
| const hook = this.hooks.typeof.get(exprName.name); |
| if (hook !== undefined) { |
| const result = hook.call(expression); |
| if (result === true) return; |
| } |
| } |
| } |
| this.walkExpression(expression.argument); |
| } |
| |
| walkLeftRightExpression(expression) { |
| this.walkExpression(expression.left); |
| this.walkExpression(expression.right); |
| } |
| |
| walkBinaryExpression(expression) { |
| this.walkLeftRightExpression(expression); |
| } |
| |
| walkLogicalExpression(expression) { |
| const result = this.hooks.expressionLogicalOperator.call(expression); |
| if (result === undefined) { |
| this.walkLeftRightExpression(expression); |
| } else { |
| if (result) { |
| this.walkExpression(expression.right); |
| } |
| } |
| } |
| |
| walkAssignmentExpression(expression) { |
| const renameIdentifier = this.getRenameIdentifier(expression.right); |
| if (expression.left.type === "Identifier" && renameIdentifier) { |
| const hook = this.hooks.canRename.get(renameIdentifier); |
| if (hook !== undefined && hook.call(expression.right)) { |
| // renaming "a = b;" |
| const hook = this.hooks.rename.get(renameIdentifier); |
| if (hook === undefined || !hook.call(expression.right)) { |
| this.scope.renames.set(expression.left.name, renameIdentifier); |
| this.scope.definitions.delete(expression.left.name); |
| } |
| return; |
| } |
| } |
| if (expression.left.type === "Identifier") { |
| const assignedHook = this.hooks.assigned.get(expression.left.name); |
| if (assignedHook === undefined || !assignedHook.call(expression)) { |
| this.walkExpression(expression.right); |
| } |
| this.scope.renames.set(expression.left.name, null); |
| const assignHook = this.hooks.assign.get(expression.left.name); |
| if (assignHook === undefined || !assignHook.call(expression)) { |
| this.walkExpression(expression.left); |
| } |
| return; |
| } |
| this.walkExpression(expression.right); |
| this.walkPattern(expression.left); |
| this.enterPattern(expression.left, (name, decl) => { |
| this.scope.renames.set(name, null); |
| }); |
| } |
| |
| walkConditionalExpression(expression) { |
| const result = this.hooks.expressionConditionalOperator.call(expression); |
| if (result === undefined) { |
| this.walkExpression(expression.test); |
| this.walkExpression(expression.consequent); |
| if (expression.alternate) { |
| this.walkExpression(expression.alternate); |
| } |
| } else { |
| if (result) { |
| this.walkExpression(expression.consequent); |
| } else if (expression.alternate) { |
| this.walkExpression(expression.alternate); |
| } |
| } |
| } |
| |
| walkNewExpression(expression) { |
| const callee = this.evaluateExpression(expression.callee); |
| if (callee.isIdentifier()) { |
| const hook = this.hooks.new.get(callee.identifier); |
| if (hook !== undefined) { |
| const result = hook.call(expression); |
| if (result === true) { |
| return; |
| } |
| } |
| } |
| |
| this.walkExpression(expression.callee); |
| if (expression.arguments) { |
| this.walkExpressions(expression.arguments); |
| } |
| } |
| |
| walkYieldExpression(expression) { |
| if (expression.argument) { |
| this.walkExpression(expression.argument); |
| } |
| } |
| |
| walkTemplateLiteral(expression) { |
| if (expression.expressions) { |
| this.walkExpressions(expression.expressions); |
| } |
| } |
| |
| walkTaggedTemplateExpression(expression) { |
| if (expression.tag) { |
| this.walkExpression(expression.tag); |
| } |
| if (expression.quasi && expression.quasi.expressions) { |
| this.walkExpressions(expression.quasi.expressions); |
| } |
| } |
| |
| walkClassExpression(expression) { |
| this.walkClass(expression); |
| } |
| |
| _walkIIFE(functionExpression, options, currentThis) { |
| const renameArgOrThis = argOrThis => { |
| const renameIdentifier = this.getRenameIdentifier(argOrThis); |
| if (renameIdentifier) { |
| const hook = this.hooks.canRename.get(renameIdentifier); |
| if (hook !== undefined && hook.call(argOrThis)) { |
| const hook = this.hooks.rename.get(renameIdentifier); |
| if (hook === undefined || !hook.call(argOrThis)) { |
| return renameIdentifier; |
| } |
| } |
| } |
| this.walkExpression(argOrThis); |
| }; |
| const params = functionExpression.params; |
| const renameThis = currentThis ? renameArgOrThis(currentThis) : null; |
| const args = options.map(renameArgOrThis); |
| const wasTopLevel = this.scope.topLevelScope; |
| this.scope.topLevelScope = false; |
| const scopeParams = params.filter((identifier, idx) => !args[idx]); |
| |
| // Add function name in scope for recursive calls |
| if (functionExpression.id) { |
| scopeParams.push(functionExpression.id.name); |
| } |
| |
| this.inFunctionScope(true, scopeParams, () => { |
| if (renameThis) { |
| this.scope.renames.set("this", renameThis); |
| } |
| for (let i = 0; i < args.length; i++) { |
| const param = args[i]; |
| if (!param) continue; |
| if (!params[i] || params[i].type !== "Identifier") continue; |
| this.scope.renames.set(params[i].name, param); |
| } |
| if (functionExpression.body.type === "BlockStatement") { |
| this.prewalkStatement(functionExpression.body); |
| this.walkStatement(functionExpression.body); |
| } else { |
| this.walkExpression(functionExpression.body); |
| } |
| }); |
| this.scope.topLevelScope = wasTopLevel; |
| } |
| |
| walkCallExpression(expression) { |
| if ( |
| expression.callee.type === "MemberExpression" && |
| expression.callee.object.type === "FunctionExpression" && |
| !expression.callee.computed && |
| (expression.callee.property.name === "call" || |
| expression.callee.property.name === "bind") && |
| expression.arguments.length > 0 |
| ) { |
| // (function(…) { }.call/bind(?, …)) |
| this._walkIIFE( |
| expression.callee.object, |
| expression.arguments.slice(1), |
| expression.arguments[0] |
| ); |
| } else if (expression.callee.type === "FunctionExpression") { |
| // (function(…) { }(…)) |
| this._walkIIFE(expression.callee, expression.arguments, null); |
| } else if (expression.callee.type === "Import") { |
| let result = this.hooks.importCall.call(expression); |
| if (result === true) return; |
| |
| if (expression.arguments) this.walkExpressions(expression.arguments); |
| } else { |
| const callee = this.evaluateExpression(expression.callee); |
| if (callee.isIdentifier()) { |
| const callHook = this.hooks.call.get(callee.identifier); |
| if (callHook !== undefined) { |
| let result = callHook.call(expression); |
| if (result === true) return; |
| } |
| let identifier = callee.identifier.replace(/\.[^.]+$/, ""); |
| if (identifier !== callee.identifier) { |
| const callAnyHook = this.hooks.callAnyMember.get(identifier); |
| if (callAnyHook !== undefined) { |
| let result = callAnyHook.call(expression); |
| if (result === true) return; |
| } |
| } |
| } |
| |
| if (expression.callee) this.walkExpression(expression.callee); |
| if (expression.arguments) this.walkExpressions(expression.arguments); |
| } |
| } |
| |
| walkMemberExpression(expression) { |
| const exprName = this.getNameForExpression(expression); |
| if (exprName && exprName.free) { |
| const expressionHook = this.hooks.expression.get(exprName.name); |
| if (expressionHook !== undefined) { |
| const result = expressionHook.call(expression); |
| if (result === true) return; |
| } |
| const expressionAnyMemberHook = this.hooks.expressionAnyMember.get( |
| exprName.nameGeneral |
| ); |
| if (expressionAnyMemberHook !== undefined) { |
| const result = expressionAnyMemberHook.call(expression); |
| if (result === true) return; |
| } |
| } |
| this.walkExpression(expression.object); |
| if (expression.computed === true) this.walkExpression(expression.property); |
| } |
| |
| walkThisExpression(expression) { |
| const expressionHook = this.hooks.expression.get("this"); |
| if (expressionHook !== undefined) { |
| expressionHook.call(expression); |
| } |
| } |
| |
| walkIdentifier(expression) { |
| if (!this.scope.definitions.has(expression.name)) { |
| const hook = this.hooks.expression.get( |
| this.scope.renames.get(expression.name) || expression.name |
| ); |
| if (hook !== undefined) { |
| const result = hook.call(expression); |
| if (result === true) return; |
| } |
| } |
| } |
| |
| /** |
| * @deprecated |
| * @param {any} params scope params |
| * @param {function(): void} fn inner function |
| * @returns {void} |
| */ |
| inScope(params, fn) { |
| const oldScope = this.scope; |
| this.scope = { |
| topLevelScope: oldScope.topLevelScope, |
| inTry: false, |
| inShorthand: false, |
| isStrict: oldScope.isStrict, |
| definitions: oldScope.definitions.createChild(), |
| renames: oldScope.renames.createChild() |
| }; |
| |
| this.scope.renames.set("this", null); |
| |
| this.enterPatterns(params, ident => { |
| this.scope.renames.set(ident, null); |
| this.scope.definitions.add(ident); |
| }); |
| |
| fn(); |
| |
| this.scope = oldScope; |
| } |
| |
| inFunctionScope(hasThis, params, fn) { |
| const oldScope = this.scope; |
| this.scope = { |
| topLevelScope: oldScope.topLevelScope, |
| inTry: false, |
| inShorthand: false, |
| isStrict: oldScope.isStrict, |
| definitions: oldScope.definitions.createChild(), |
| renames: oldScope.renames.createChild() |
| }; |
| |
| if (hasThis) { |
| this.scope.renames.set("this", null); |
| } |
| |
| this.enterPatterns(params, ident => { |
| this.scope.renames.set(ident, null); |
| this.scope.definitions.add(ident); |
| }); |
| |
| fn(); |
| |
| this.scope = oldScope; |
| } |
| |
| inBlockScope(fn) { |
| const oldScope = this.scope; |
| this.scope = { |
| topLevelScope: oldScope.topLevelScope, |
| inTry: oldScope.inTry, |
| inShorthand: false, |
| isStrict: oldScope.isStrict, |
| definitions: oldScope.definitions.createChild(), |
| renames: oldScope.renames.createChild() |
| }; |
| |
| fn(); |
| |
| this.scope = oldScope; |
| } |
| |
| detectStrictMode(statements) { |
| const isStrict = |
| statements.length >= 1 && |
| statements[0].type === "ExpressionStatement" && |
| statements[0].expression.type === "Literal" && |
| statements[0].expression.value === "use strict"; |
| if (isStrict) { |
| this.scope.isStrict = true; |
| } |
| } |
| |
| enterPatterns(patterns, onIdent) { |
| for (const pattern of patterns) { |
| if (typeof pattern !== "string") { |
| this.enterPattern(pattern, onIdent); |
| } else if (pattern) { |
| onIdent(pattern); |
| } |
| } |
| } |
| |
| enterPattern(pattern, onIdent) { |
| if (!pattern) return; |
| switch (pattern.type) { |
| case "ArrayPattern": |
| this.enterArrayPattern(pattern, onIdent); |
| break; |
| case "AssignmentPattern": |
| this.enterAssignmentPattern(pattern, onIdent); |
| break; |
| case "Identifier": |
| this.enterIdentifier(pattern, onIdent); |
| break; |
| case "ObjectPattern": |
| this.enterObjectPattern(pattern, onIdent); |
| break; |
| case "RestElement": |
| this.enterRestElement(pattern, onIdent); |
| break; |
| case "Property": |
| this.enterPattern(pattern.value, onIdent); |
| break; |
| } |
| } |
| |
| enterIdentifier(pattern, onIdent) { |
| onIdent(pattern.name, pattern); |
| } |
| |
| enterObjectPattern(pattern, onIdent) { |
| for ( |
| let propIndex = 0, len = pattern.properties.length; |
| propIndex < len; |
| propIndex++ |
| ) { |
| const prop = pattern.properties[propIndex]; |
| this.enterPattern(prop, onIdent); |
| } |
| } |
| |
| enterArrayPattern(pattern, onIdent) { |
| for ( |
| let elementIndex = 0, len = pattern.elements.length; |
| elementIndex < len; |
| elementIndex++ |
| ) { |
| const element = pattern.elements[elementIndex]; |
| this.enterPattern(element, onIdent); |
| } |
| } |
| |
| enterRestElement(pattern, onIdent) { |
| this.enterPattern(pattern.argument, onIdent); |
| } |
| |
| enterAssignmentPattern(pattern, onIdent) { |
| this.enterPattern(pattern.left, onIdent); |
| } |
| |
| evaluateExpression(expression) { |
| try { |
| const hook = this.hooks.evaluate.get(expression.type); |
| if (hook !== undefined) { |
| const result = hook.call(expression); |
| if (result !== undefined) { |
| if (result) { |
| result.setExpression(expression); |
| } |
| return result; |
| } |
| } |
| } catch (e) { |
| console.warn(e); |
| // ignore error |
| } |
| return new BasicEvaluatedExpression() |
| .setRange(expression.range) |
| .setExpression(expression); |
| } |
| |
| parseString(expression) { |
| switch (expression.type) { |
| case "BinaryExpression": |
| if (expression.operator === "+") { |
| return ( |
| this.parseString(expression.left) + |
| this.parseString(expression.right) |
| ); |
| } |
| break; |
| case "Literal": |
| return expression.value + ""; |
| } |
| throw new Error( |
| expression.type + " is not supported as parameter for require" |
| ); |
| } |
| |
| parseCalculatedString(expression) { |
| switch (expression.type) { |
| case "BinaryExpression": |
| if (expression.operator === "+") { |
| const left = this.parseCalculatedString(expression.left); |
| const right = this.parseCalculatedString(expression.right); |
| if (left.code) { |
| return { |
| range: left.range, |
| value: left.value, |
| code: true, |
| conditional: false |
| }; |
| } else if (right.code) { |
| return { |
| range: [ |
| left.range[0], |
| right.range ? right.range[1] : left.range[1] |
| ], |
| value: left.value + right.value, |
| code: true, |
| conditional: false |
| }; |
| } else { |
| return { |
| range: [left.range[0], right.range[1]], |
| value: left.value + right.value, |
| code: false, |
| conditional: false |
| }; |
| } |
| } |
| break; |
| case "ConditionalExpression": { |
| const consequent = this.parseCalculatedString(expression.consequent); |
| const alternate = this.parseCalculatedString(expression.alternate); |
| const items = []; |
| if (consequent.conditional) { |
| items.push(...consequent.conditional); |
| } else if (!consequent.code) { |
| items.push(consequent); |
| } else { |
| break; |
| } |
| if (alternate.conditional) { |
| items.push(...alternate.conditional); |
| } else if (!alternate.code) { |
| items.push(alternate); |
| } else { |
| break; |
| } |
| return { |
| range: undefined, |
| value: "", |
| code: true, |
| conditional: items |
| }; |
| } |
| case "Literal": |
| return { |
| range: expression.range, |
| value: expression.value + "", |
| code: false, |
| conditional: false |
| }; |
| } |
| return { |
| range: undefined, |
| value: "", |
| code: true, |
| conditional: false |
| }; |
| } |
| |
| parse(source, initialState) { |
| let ast; |
| let comments; |
| if (typeof source === "object" && source !== null) { |
| ast = source; |
| comments = source.comments; |
| } else { |
| comments = []; |
| ast = Parser.parse(source, { |
| sourceType: this.sourceType, |
| onComment: comments |
| }); |
| } |
| |
| const oldScope = this.scope; |
| const oldState = this.state; |
| const oldComments = this.comments; |
| this.scope = { |
| topLevelScope: true, |
| inTry: false, |
| inShorthand: false, |
| isStrict: false, |
| definitions: new StackedSetMap(), |
| renames: new StackedSetMap() |
| }; |
| const state = (this.state = initialState || {}); |
| this.comments = comments; |
| if (this.hooks.program.call(ast, comments) === undefined) { |
| this.detectStrictMode(ast.body); |
| this.prewalkStatements(ast.body); |
| this.blockPrewalkStatements(ast.body); |
| this.walkStatements(ast.body); |
| } |
| this.scope = oldScope; |
| this.state = oldState; |
| this.comments = oldComments; |
| return state; |
| } |
| |
| evaluate(source) { |
| const ast = Parser.parse("(" + source + ")", { |
| sourceType: this.sourceType, |
| locations: false |
| }); |
| // TODO(https://github.com/acornjs/acorn/issues/741) |
| // @ts-ignore |
| if (ast.body.length !== 1 || ast.body[0].type !== "ExpressionStatement") { |
| throw new Error("evaluate: Source is not a expression"); |
| } |
| // TODO(https://github.com/acornjs/acorn/issues/741) |
| // @ts-ignore |
| return this.evaluateExpression(ast.body[0].expression); |
| } |
| |
| getComments(range) { |
| return this.comments.filter( |
| comment => comment.range[0] >= range[0] && comment.range[1] <= range[1] |
| ); |
| } |
| |
| parseCommentOptions(range) { |
| const comments = this.getComments(range); |
| if (comments.length === 0) { |
| return EMPTY_COMMENT_OPTIONS; |
| } |
| let options = {}; |
| let errors = []; |
| for (const comment of comments) { |
| const { value } = comment; |
| if (value && webpackCommentRegExp.test(value)) { |
| // try compile only if webpack options comment is present |
| try { |
| const val = vm.runInNewContext(`(function(){return {${value}};})()`); |
| Object.assign(options, val); |
| } catch (e) { |
| e.comment = comment; |
| errors.push(e); |
| } |
| } |
| } |
| return { options, errors }; |
| } |
| |
| getNameForExpression(expression) { |
| let expr = expression; |
| const exprName = []; |
| while ( |
| expr.type === "MemberExpression" && |
| expr.property.type === (expr.computed ? "Literal" : "Identifier") |
| ) { |
| exprName.push(expr.computed ? expr.property.value : expr.property.name); |
| expr = expr.object; |
| } |
| let free; |
| if (expr.type === "Identifier") { |
| free = !this.scope.definitions.has(expr.name); |
| exprName.push(this.scope.renames.get(expr.name) || expr.name); |
| } else if ( |
| expr.type === "ThisExpression" && |
| this.scope.renames.get("this") |
| ) { |
| free = true; |
| exprName.push(this.scope.renames.get("this")); |
| } else if (expr.type === "ThisExpression") { |
| free = this.scope.topLevelScope; |
| exprName.push("this"); |
| } else { |
| return null; |
| } |
| let prefix = ""; |
| for (let i = exprName.length - 1; i >= 2; i--) { |
| prefix += exprName[i] + "."; |
| } |
| if (exprName.length > 1) { |
| prefix += exprName[1]; |
| } |
| const name = prefix ? prefix + "." + exprName[0] : exprName[0]; |
| const nameGeneral = prefix; |
| return { |
| name, |
| nameGeneral, |
| free |
| }; |
| } |
| |
| static parse(code, options) { |
| const type = options ? options.sourceType : "module"; |
| const parserOptions = Object.assign( |
| Object.create(null), |
| defaultParserOptions, |
| options |
| ); |
| |
| if (type === "auto") { |
| parserOptions.sourceType = "module"; |
| } else if (parserOptions.sourceType === "script") { |
| parserOptions.allowReturnOutsideFunction = true; |
| } |
| |
| let ast; |
| let error; |
| let threw = false; |
| try { |
| ast = acornParser.parse(code, parserOptions); |
| } catch (e) { |
| error = e; |
| threw = true; |
| } |
| |
| if (threw && type === "auto") { |
| parserOptions.sourceType = "script"; |
| parserOptions.allowReturnOutsideFunction = true; |
| if (Array.isArray(parserOptions.onComment)) { |
| parserOptions.onComment.length = 0; |
| } |
| try { |
| ast = acornParser.parse(code, parserOptions); |
| threw = false; |
| } catch (e) { |
| threw = true; |
| } |
| } |
| |
| if (threw) { |
| throw error; |
| } |
| |
| return ast; |
| } |
| } |
| |
| // TODO remove in webpack 5 |
| Object.defineProperty(Parser.prototype, "getCommentOptions", { |
| configurable: false, |
| value: util.deprecate( |
| /** |
| * @deprecated |
| * @param {TODO} range Range |
| * @returns {void} |
| * @this {Parser} |
| */ |
| function(range) { |
| return this.parseCommentOptions(range).options; |
| }, |
| "Parser.getCommentOptions: Use Parser.parseCommentOptions(range) instead" |
| ) |
| }); |
| |
| module.exports = Parser; |