You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
459 lines
12 KiB
459 lines
12 KiB
"use strict"; |
|
|
|
Object.defineProperty(exports, "__esModule", { |
|
value: true |
|
}); |
|
exports._guessExecutionStatusRelativeTo = _guessExecutionStatusRelativeTo; |
|
exports._resolve = _resolve; |
|
exports.canHaveVariableDeclarationOrExpression = canHaveVariableDeclarationOrExpression; |
|
exports.canSwapBetweenExpressionAndStatement = canSwapBetweenExpressionAndStatement; |
|
exports.equals = equals; |
|
exports.getSource = getSource; |
|
exports.has = has; |
|
exports.is = void 0; |
|
exports.isCompletionRecord = isCompletionRecord; |
|
exports.isConstantExpression = isConstantExpression; |
|
exports.isInStrictMode = isInStrictMode; |
|
exports.isNodeType = isNodeType; |
|
exports.isStatementOrBlock = isStatementOrBlock; |
|
exports.isStatic = isStatic; |
|
exports.isnt = isnt; |
|
exports.matchesPattern = matchesPattern; |
|
exports.referencesImport = referencesImport; |
|
exports.resolve = resolve; |
|
exports.willIMaybeExecuteBefore = willIMaybeExecuteBefore; |
|
|
|
var _t = require("@babel/types"); |
|
|
|
const { |
|
STATEMENT_OR_BLOCK_KEYS, |
|
VISITOR_KEYS, |
|
isBlockStatement, |
|
isExpression, |
|
isIdentifier, |
|
isLiteral, |
|
isStringLiteral, |
|
isType, |
|
matchesPattern: _matchesPattern |
|
} = _t; |
|
|
|
function matchesPattern(pattern, allowPartial) { |
|
return _matchesPattern(this.node, pattern, allowPartial); |
|
} |
|
|
|
function has(key) { |
|
const val = this.node && this.node[key]; |
|
|
|
if (val && Array.isArray(val)) { |
|
return !!val.length; |
|
} else { |
|
return !!val; |
|
} |
|
} |
|
|
|
function isStatic() { |
|
return this.scope.isStatic(this.node); |
|
} |
|
|
|
const is = has; |
|
exports.is = is; |
|
|
|
function isnt(key) { |
|
return !this.has(key); |
|
} |
|
|
|
function equals(key, value) { |
|
return this.node[key] === value; |
|
} |
|
|
|
function isNodeType(type) { |
|
return isType(this.type, type); |
|
} |
|
|
|
function canHaveVariableDeclarationOrExpression() { |
|
return (this.key === "init" || this.key === "left") && this.parentPath.isFor(); |
|
} |
|
|
|
function canSwapBetweenExpressionAndStatement(replacement) { |
|
if (this.key !== "body" || !this.parentPath.isArrowFunctionExpression()) { |
|
return false; |
|
} |
|
|
|
if (this.isExpression()) { |
|
return isBlockStatement(replacement); |
|
} else if (this.isBlockStatement()) { |
|
return isExpression(replacement); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
function isCompletionRecord(allowInsideFunction) { |
|
let path = this; |
|
let first = true; |
|
|
|
do { |
|
const { |
|
type, |
|
container |
|
} = path; |
|
|
|
if (!first && (path.isFunction() || type === "StaticBlock")) { |
|
return !!allowInsideFunction; |
|
} |
|
|
|
first = false; |
|
|
|
if (Array.isArray(container) && path.key !== container.length - 1) { |
|
return false; |
|
} |
|
} while ((path = path.parentPath) && !path.isProgram() && !path.isDoExpression()); |
|
|
|
return true; |
|
} |
|
|
|
function isStatementOrBlock() { |
|
if (this.parentPath.isLabeledStatement() || isBlockStatement(this.container)) { |
|
return false; |
|
} else { |
|
return STATEMENT_OR_BLOCK_KEYS.includes(this.key); |
|
} |
|
} |
|
|
|
function referencesImport(moduleSource, importName) { |
|
if (!this.isReferencedIdentifier()) { |
|
if (this.isJSXMemberExpression() && this.node.property.name === importName || (this.isMemberExpression() || this.isOptionalMemberExpression()) && (this.node.computed ? isStringLiteral(this.node.property, { |
|
value: importName |
|
}) : this.node.property.name === importName)) { |
|
const object = this.get("object"); |
|
return object.isReferencedIdentifier() && object.referencesImport(moduleSource, "*"); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
const binding = this.scope.getBinding(this.node.name); |
|
if (!binding || binding.kind !== "module") return false; |
|
const path = binding.path; |
|
const parent = path.parentPath; |
|
if (!parent.isImportDeclaration()) return false; |
|
|
|
if (parent.node.source.value === moduleSource) { |
|
if (!importName) return true; |
|
} else { |
|
return false; |
|
} |
|
|
|
if (path.isImportDefaultSpecifier() && importName === "default") { |
|
return true; |
|
} |
|
|
|
if (path.isImportNamespaceSpecifier() && importName === "*") { |
|
return true; |
|
} |
|
|
|
if (path.isImportSpecifier() && isIdentifier(path.node.imported, { |
|
name: importName |
|
})) { |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
function getSource() { |
|
const node = this.node; |
|
|
|
if (node.end) { |
|
const code = this.hub.getCode(); |
|
if (code) return code.slice(node.start, node.end); |
|
} |
|
|
|
return ""; |
|
} |
|
|
|
function willIMaybeExecuteBefore(target) { |
|
return this._guessExecutionStatusRelativeTo(target) !== "after"; |
|
} |
|
|
|
function getOuterFunction(path) { |
|
return (path.scope.getFunctionParent() || path.scope.getProgramParent()).path; |
|
} |
|
|
|
function isExecutionUncertain(type, key) { |
|
switch (type) { |
|
case "LogicalExpression": |
|
return key === "right"; |
|
|
|
case "ConditionalExpression": |
|
case "IfStatement": |
|
return key === "consequent" || key === "alternate"; |
|
|
|
case "WhileStatement": |
|
case "DoWhileStatement": |
|
case "ForInStatement": |
|
case "ForOfStatement": |
|
return key === "body"; |
|
|
|
case "ForStatement": |
|
return key === "body" || key === "update"; |
|
|
|
case "SwitchStatement": |
|
return key === "cases"; |
|
|
|
case "TryStatement": |
|
return key === "handler"; |
|
|
|
case "AssignmentPattern": |
|
return key === "right"; |
|
|
|
case "OptionalMemberExpression": |
|
return key === "property"; |
|
|
|
case "OptionalCallExpression": |
|
return key === "arguments"; |
|
|
|
default: |
|
return false; |
|
} |
|
} |
|
|
|
function isExecutionUncertainInList(paths, maxIndex) { |
|
for (let i = 0; i < maxIndex; i++) { |
|
const path = paths[i]; |
|
|
|
if (isExecutionUncertain(path.parent.type, path.parentKey)) { |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
function _guessExecutionStatusRelativeTo(target) { |
|
return _guessExecutionStatusRelativeToCached(this, target, new Map()); |
|
} |
|
|
|
function _guessExecutionStatusRelativeToCached(base, target, cache) { |
|
const funcParent = { |
|
this: getOuterFunction(base), |
|
target: getOuterFunction(target) |
|
}; |
|
|
|
if (funcParent.target.node !== funcParent.this.node) { |
|
return _guessExecutionStatusRelativeToDifferentFunctionsCached(base, funcParent.target, cache); |
|
} |
|
|
|
const paths = { |
|
target: target.getAncestry(), |
|
this: base.getAncestry() |
|
}; |
|
if (paths.target.indexOf(base) >= 0) return "after"; |
|
if (paths.this.indexOf(target) >= 0) return "before"; |
|
let commonPath; |
|
const commonIndex = { |
|
target: 0, |
|
this: 0 |
|
}; |
|
|
|
while (!commonPath && commonIndex.this < paths.this.length) { |
|
const path = paths.this[commonIndex.this]; |
|
commonIndex.target = paths.target.indexOf(path); |
|
|
|
if (commonIndex.target >= 0) { |
|
commonPath = path; |
|
} else { |
|
commonIndex.this++; |
|
} |
|
} |
|
|
|
if (!commonPath) { |
|
throw new Error("Internal Babel error - The two compared nodes" + " don't appear to belong to the same program."); |
|
} |
|
|
|
if (isExecutionUncertainInList(paths.this, commonIndex.this - 1) || isExecutionUncertainInList(paths.target, commonIndex.target - 1)) { |
|
return "unknown"; |
|
} |
|
|
|
const divergence = { |
|
this: paths.this[commonIndex.this - 1], |
|
target: paths.target[commonIndex.target - 1] |
|
}; |
|
|
|
if (divergence.target.listKey && divergence.this.listKey && divergence.target.container === divergence.this.container) { |
|
return divergence.target.key > divergence.this.key ? "before" : "after"; |
|
} |
|
|
|
const keys = VISITOR_KEYS[commonPath.type]; |
|
const keyPosition = { |
|
this: keys.indexOf(divergence.this.parentKey), |
|
target: keys.indexOf(divergence.target.parentKey) |
|
}; |
|
return keyPosition.target > keyPosition.this ? "before" : "after"; |
|
} |
|
|
|
const executionOrderCheckedNodes = new Set(); |
|
|
|
function _guessExecutionStatusRelativeToDifferentFunctionsInternal(base, target, cache) { |
|
if (!target.isFunctionDeclaration() || target.parentPath.isExportDeclaration()) { |
|
return "unknown"; |
|
} |
|
|
|
const binding = target.scope.getBinding(target.node.id.name); |
|
if (!binding.references) return "before"; |
|
const referencePaths = binding.referencePaths; |
|
let allStatus; |
|
|
|
for (const path of referencePaths) { |
|
const childOfFunction = !!path.find(path => path.node === target.node); |
|
if (childOfFunction) continue; |
|
|
|
if (path.key !== "callee" || !path.parentPath.isCallExpression()) { |
|
return "unknown"; |
|
} |
|
|
|
if (executionOrderCheckedNodes.has(path.node)) continue; |
|
executionOrderCheckedNodes.add(path.node); |
|
|
|
try { |
|
const status = _guessExecutionStatusRelativeToCached(base, path, cache); |
|
|
|
if (allStatus && allStatus !== status) { |
|
return "unknown"; |
|
} else { |
|
allStatus = status; |
|
} |
|
} finally { |
|
executionOrderCheckedNodes.delete(path.node); |
|
} |
|
} |
|
|
|
return allStatus; |
|
} |
|
|
|
function _guessExecutionStatusRelativeToDifferentFunctionsCached(base, target, cache) { |
|
let nodeMap = cache.get(base.node); |
|
|
|
if (!nodeMap) { |
|
cache.set(base.node, nodeMap = new Map()); |
|
} else if (nodeMap.has(target.node)) { |
|
return nodeMap.get(target.node); |
|
} |
|
|
|
const result = _guessExecutionStatusRelativeToDifferentFunctionsInternal(base, target, cache); |
|
|
|
nodeMap.set(target.node, result); |
|
return result; |
|
} |
|
|
|
function resolve(dangerous, resolved) { |
|
return this._resolve(dangerous, resolved) || this; |
|
} |
|
|
|
function _resolve(dangerous, resolved) { |
|
if (resolved && resolved.indexOf(this) >= 0) return; |
|
resolved = resolved || []; |
|
resolved.push(this); |
|
|
|
if (this.isVariableDeclarator()) { |
|
if (this.get("id").isIdentifier()) { |
|
return this.get("init").resolve(dangerous, resolved); |
|
} else {} |
|
} else if (this.isReferencedIdentifier()) { |
|
const binding = this.scope.getBinding(this.node.name); |
|
if (!binding) return; |
|
if (!binding.constant) return; |
|
if (binding.kind === "module") return; |
|
|
|
if (binding.path !== this) { |
|
const ret = binding.path.resolve(dangerous, resolved); |
|
if (this.find(parent => parent.node === ret.node)) return; |
|
return ret; |
|
} |
|
} else if (this.isTypeCastExpression()) { |
|
return this.get("expression").resolve(dangerous, resolved); |
|
} else if (dangerous && this.isMemberExpression()) { |
|
const targetKey = this.toComputedKey(); |
|
if (!isLiteral(targetKey)) return; |
|
const targetName = targetKey.value; |
|
const target = this.get("object").resolve(dangerous, resolved); |
|
|
|
if (target.isObjectExpression()) { |
|
const props = target.get("properties"); |
|
|
|
for (const prop of props) { |
|
if (!prop.isProperty()) continue; |
|
const key = prop.get("key"); |
|
let match = prop.isnt("computed") && key.isIdentifier({ |
|
name: targetName |
|
}); |
|
match = match || key.isLiteral({ |
|
value: targetName |
|
}); |
|
if (match) return prop.get("value").resolve(dangerous, resolved); |
|
} |
|
} else if (target.isArrayExpression() && !isNaN(+targetName)) { |
|
const elems = target.get("elements"); |
|
const elem = elems[targetName]; |
|
if (elem) return elem.resolve(dangerous, resolved); |
|
} |
|
} |
|
} |
|
|
|
function isConstantExpression() { |
|
if (this.isIdentifier()) { |
|
const binding = this.scope.getBinding(this.node.name); |
|
if (!binding) return false; |
|
return binding.constant; |
|
} |
|
|
|
if (this.isLiteral()) { |
|
if (this.isRegExpLiteral()) { |
|
return false; |
|
} |
|
|
|
if (this.isTemplateLiteral()) { |
|
return this.get("expressions").every(expression => expression.isConstantExpression()); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
if (this.isUnaryExpression()) { |
|
if (this.node.operator !== "void") { |
|
return false; |
|
} |
|
|
|
return this.get("argument").isConstantExpression(); |
|
} |
|
|
|
if (this.isBinaryExpression()) { |
|
return this.get("left").isConstantExpression() && this.get("right").isConstantExpression(); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
function isInStrictMode() { |
|
const start = this.isProgram() ? this : this.parentPath; |
|
const strictParent = start.find(path => { |
|
if (path.isProgram({ |
|
sourceType: "module" |
|
})) return true; |
|
if (path.isClass()) return true; |
|
if (!path.isProgram() && !path.isFunction()) return false; |
|
|
|
if (path.isArrowFunctionExpression() && !path.get("body").isBlockStatement()) { |
|
return false; |
|
} |
|
|
|
const body = path.isFunction() ? path.node.body : path.node; |
|
|
|
for (const directive of body.directives) { |
|
if (directive.value.value === "use strict") { |
|
return true; |
|
} |
|
} |
|
}); |
|
return !!strictParent; |
|
} |