add guards support in JavascriptParser
parent
86a8bd9618
commit
9925ea24e5
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
MIT License http://www.opensource.org/licenses/mit-license.php
|
||||
Author Ivan Kopeykin @vankop
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const makeSerializable = require("../util/makeSerializable");
|
||||
const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDependency");
|
||||
const NullDependency = require("./NullDependency");
|
||||
|
||||
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
|
||||
/** @typedef {import("../ChunkGraph")} ChunkGraph */
|
||||
/** @typedef {import("../Dependency")} Dependency */
|
||||
/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
|
||||
|
||||
/**
|
||||
* Dependency just for export presence import specifier.
|
||||
*/
|
||||
class HarmonyExportPresenceImportSpecifierDependency extends HarmonyImportSpecifierDependency {
|
||||
get type() {
|
||||
return "export presence harmony import specifier";
|
||||
}
|
||||
}
|
||||
|
||||
makeSerializable(
|
||||
HarmonyExportPresenceImportSpecifierDependency,
|
||||
"webpack/lib/dependencies/HarmonyExportPresenceImportSpecifierDependency"
|
||||
);
|
||||
|
||||
HarmonyExportPresenceImportSpecifierDependency.Template =
|
||||
/** @type {any} can't cast to HarmonyImportSpecifierDependency.Template */ (
|
||||
NullDependency.Template
|
||||
);
|
||||
|
||||
module.exports = HarmonyExportPresenceImportSpecifierDependency;
|
|
@ -11,6 +11,7 @@ const ConstDependency = require("./ConstDependency");
|
|||
const HarmonyAcceptDependency = require("./HarmonyAcceptDependency");
|
||||
const HarmonyAcceptImportDependency = require("./HarmonyAcceptImportDependency");
|
||||
const HarmonyEvaluatedImportSpecifierDependency = require("./HarmonyEvaluatedImportSpecifierDependency");
|
||||
const HarmonyExportPresenceImportSpecifierDependency = require("./HarmonyExportPresenceImportSpecifierDependency");
|
||||
const HarmonyExports = require("./HarmonyExports");
|
||||
const { ExportPresenceModes } = require("./HarmonyImportDependency");
|
||||
const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency");
|
||||
|
@ -96,6 +97,34 @@ module.exports = class HarmonyImportDependencyParserPlugin {
|
|||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} ids ids
|
||||
* @returns {string} guard name
|
||||
*/
|
||||
const createGuard = ids => ids.join(".");
|
||||
/**
|
||||
* @param {string} guard guard
|
||||
* @param {number} idsLength ids length
|
||||
* @returns {number} mode
|
||||
*/
|
||||
const detectExportPresenceMode = (guard, idsLength) => {
|
||||
if (
|
||||
exportPresenceMode === ExportPresenceModes.NONE ||
|
||||
// namespace objects are safe to use
|
||||
idsLength === 0 ||
|
||||
parser.scope.guards.has(guard) ||
|
||||
// if possible guard is in guard position,
|
||||
// it is guarded by member chain minus one element. e.g.
|
||||
// if (a && a.b) {} or if (a && "c" in a.b) {}
|
||||
// a.b is guarded by a
|
||||
(parser.scope.inGuardPosition &&
|
||||
(idsLength === 1 ||
|
||||
parser.scope.guards.has(guard.slice(0, guard.lastIndexOf(".")))))
|
||||
)
|
||||
return ExportPresenceModes.NONE;
|
||||
|
||||
return exportPresenceMode;
|
||||
};
|
||||
parser.hooks.isPure
|
||||
.for("Identifier")
|
||||
.tap("HarmonyImportDependencyParserPlugin", expression => {
|
||||
|
@ -147,53 +176,137 @@ module.exports = class HarmonyImportDependencyParserPlugin {
|
|||
parser.hooks.binaryExpression.tap(
|
||||
"HarmonyImportDependencyParserPlugin",
|
||||
expression => {
|
||||
if (expression.operator !== "in") return;
|
||||
if (expression.operator === "in") {
|
||||
const leftPartEvaluated = parser.evaluateExpression(expression.left);
|
||||
if (!leftPartEvaluated || leftPartEvaluated.couldHaveSideEffects())
|
||||
return;
|
||||
const leftPart = leftPartEvaluated.asString();
|
||||
if (!leftPart) return;
|
||||
|
||||
const leftPartEvaluated = parser.evaluateExpression(expression.left);
|
||||
if (leftPartEvaluated.couldHaveSideEffects()) return;
|
||||
const leftPart = leftPartEvaluated.asString();
|
||||
if (!leftPart) return;
|
||||
const rightPart = parser.evaluateExpression(expression.right);
|
||||
if (!rightPart || !rightPart.isIdentifier()) return;
|
||||
|
||||
const rightPart = parser.evaluateExpression(expression.right);
|
||||
if (!rightPart.isIdentifier()) return;
|
||||
const rootInfo = rightPart.rootInfo;
|
||||
if (
|
||||
!rootInfo ||
|
||||
!rootInfo.tagInfo ||
|
||||
rootInfo.tagInfo.tag !== harmonySpecifierTag
|
||||
)
|
||||
return;
|
||||
const settings = rootInfo.tagInfo.data;
|
||||
const members = rightPart.getMembers();
|
||||
const baseIds = settings.ids.concat(members);
|
||||
const ids = baseIds.concat([leftPart]);
|
||||
const dep = new HarmonyEvaluatedImportSpecifierDependency(
|
||||
settings.source,
|
||||
settings.sourceOrder,
|
||||
ids,
|
||||
settings.name,
|
||||
expression.range,
|
||||
settings.assertions,
|
||||
"in"
|
||||
);
|
||||
dep.directImport = members.length === 0;
|
||||
dep.asiSafe = !parser.isAsiPosition(expression.range[0]);
|
||||
dep.loc = expression.loc;
|
||||
parser.state.module.addDependency(dep);
|
||||
InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e));
|
||||
|
||||
const rootInfo = rightPart.rootInfo;
|
||||
if (
|
||||
!rootInfo ||
|
||||
!rootInfo.tagInfo ||
|
||||
rootInfo.tagInfo.tag !== harmonySpecifierTag
|
||||
)
|
||||
return;
|
||||
const settings = rootInfo.tagInfo.data;
|
||||
const members = rightPart.getMembers();
|
||||
const dep = new HarmonyEvaluatedImportSpecifierDependency(
|
||||
settings.source,
|
||||
settings.sourceOrder,
|
||||
settings.ids.concat(members).concat([leftPart]),
|
||||
settings.name,
|
||||
expression.range,
|
||||
settings.assertions,
|
||||
"in"
|
||||
);
|
||||
dep.directImport = members.length === 0;
|
||||
dep.asiSafe = !parser.isAsiPosition(expression.range[0]);
|
||||
dep.loc = expression.loc;
|
||||
parser.state.module.addDependency(dep);
|
||||
InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e));
|
||||
return true;
|
||||
if (parser.scope.inGuardPosition) {
|
||||
parser.scope.guards.add(createGuard(ids));
|
||||
// namespace objects and identifiers are safe to use
|
||||
if (baseIds.length > 1) {
|
||||
// check for export presence for right side expression
|
||||
const mode = detectExportPresenceMode(
|
||||
createGuard(baseIds),
|
||||
baseIds.length
|
||||
);
|
||||
// e.g if ("a" in b.c) {}
|
||||
// here "b.c" is not guarded by "b"
|
||||
if (mode !== ExportPresenceModes.NONE) {
|
||||
const dep = new HarmonyExportPresenceImportSpecifierDependency(
|
||||
settings.source,
|
||||
settings.sourceOrder,
|
||||
baseIds,
|
||||
settings.name,
|
||||
rightPart.expression.range,
|
||||
mode,
|
||||
settings.assertions
|
||||
);
|
||||
dep.loc = rightPart.expression.loc;
|
||||
parser.state.module.addDependency(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (expression.operator === "!=") {
|
||||
if (!parser.scope.inGuardPosition) return;
|
||||
let identifierEvaluated;
|
||||
const leftPartEvaluated = parser.evaluateExpression(expression.left);
|
||||
if (!leftPartEvaluated) {
|
||||
return;
|
||||
} else if (leftPartEvaluated.isIdentifier()) {
|
||||
const rootInfo = leftPartEvaluated.rootInfo;
|
||||
if (
|
||||
!rootInfo ||
|
||||
!rootInfo.tagInfo ||
|
||||
rootInfo.tagInfo.tag !== harmonySpecifierTag
|
||||
)
|
||||
return;
|
||||
identifierEvaluated = leftPartEvaluated;
|
||||
} else if (leftPartEvaluated.isFalsy() !== true) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rightPartEvaluated = parser.evaluateExpression(
|
||||
expression.right
|
||||
);
|
||||
if (!rightPartEvaluated) {
|
||||
return;
|
||||
} else if (
|
||||
!identifierEvaluated &&
|
||||
rightPartEvaluated.isIdentifier()
|
||||
) {
|
||||
const rootInfo = rightPartEvaluated.rootInfo;
|
||||
if (
|
||||
!rootInfo ||
|
||||
!rootInfo.tagInfo ||
|
||||
rootInfo.tagInfo.tag !== harmonySpecifierTag
|
||||
)
|
||||
return;
|
||||
identifierEvaluated = rightPartEvaluated;
|
||||
} else if (rightPartEvaluated.isFalsy() !== true) {
|
||||
return;
|
||||
}
|
||||
|
||||
// other hooks will add guards and dependencies
|
||||
parser.walkExpression(identifierEvaluated.expression);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
);
|
||||
parser.hooks.expression
|
||||
.for(harmonySpecifierTag)
|
||||
.tap("HarmonyImportDependencyParserPlugin", expr => {
|
||||
const settings = /** @type {HarmonySettings} */ (parser.currentTagData);
|
||||
let exportPresenceModeComputed;
|
||||
// namespace object is safe to use
|
||||
if (settings.ids.length) {
|
||||
const guard = createGuard(settings.ids);
|
||||
exportPresenceModeComputed = detectExportPresenceMode(
|
||||
guard,
|
||||
settings.ids.length
|
||||
);
|
||||
if (parser.scope.inGuardPosition) parser.scope.guards.add(guard);
|
||||
}
|
||||
const dep = new HarmonyImportSpecifierDependency(
|
||||
settings.source,
|
||||
settings.sourceOrder,
|
||||
settings.ids,
|
||||
settings.name,
|
||||
expr.range,
|
||||
exportPresenceMode,
|
||||
exportPresenceModeComputed || ExportPresenceModes.NONE,
|
||||
settings.assertions
|
||||
);
|
||||
dep.shorthand = parser.scope.inShorthand;
|
||||
|
@ -224,6 +337,12 @@ module.exports = class HarmonyImportDependencyParserPlugin {
|
|||
)
|
||||
: expression;
|
||||
const ids = settings.ids.concat(nonOptionalMembers);
|
||||
const guard = createGuard(ids);
|
||||
const exportPresenceMode = detectExportPresenceMode(
|
||||
guard,
|
||||
ids.length
|
||||
);
|
||||
if (parser.scope.inGuardPosition) parser.scope.guards.add(guard);
|
||||
const dep = new HarmonyImportSpecifierDependency(
|
||||
settings.source,
|
||||
settings.sourceOrder,
|
||||
|
@ -267,7 +386,7 @@ module.exports = class HarmonyImportDependencyParserPlugin {
|
|||
ids,
|
||||
settings.name,
|
||||
expr.range,
|
||||
exportPresenceMode,
|
||||
detectExportPresenceMode(createGuard(ids), ids.length),
|
||||
settings.assertions
|
||||
);
|
||||
dep.directImport = members.length === 0;
|
||||
|
|
|
@ -12,6 +12,7 @@ const HarmonyEvaluatedImportSpecifierDependency = require("./HarmonyEvaluatedImp
|
|||
const HarmonyExportExpressionDependency = require("./HarmonyExportExpressionDependency");
|
||||
const HarmonyExportHeaderDependency = require("./HarmonyExportHeaderDependency");
|
||||
const HarmonyExportImportedSpecifierDependency = require("./HarmonyExportImportedSpecifierDependency");
|
||||
const HarmonyExportPresenceImportSpecifierDependency = require("./HarmonyExportPresenceImportSpecifierDependency");
|
||||
const HarmonyExportSpecifierDependency = require("./HarmonyExportSpecifierDependency");
|
||||
const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency");
|
||||
const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDependency");
|
||||
|
@ -69,6 +70,15 @@ class HarmonyModulesPlugin {
|
|||
new HarmonyEvaluatedImportSpecifierDependency.Template()
|
||||
);
|
||||
|
||||
compilation.dependencyFactories.set(
|
||||
HarmonyExportPresenceImportSpecifierDependency,
|
||||
normalModuleFactory
|
||||
);
|
||||
compilation.dependencyTemplates.set(
|
||||
HarmonyExportPresenceImportSpecifierDependency,
|
||||
new HarmonyExportPresenceImportSpecifierDependency.Template()
|
||||
);
|
||||
|
||||
compilation.dependencyTemplates.set(
|
||||
HarmonyExportHeaderDependency,
|
||||
new HarmonyExportHeaderDependency.Template()
|
||||
|
|
|
@ -301,18 +301,22 @@ class BasicEvaluatedExpression {
|
|||
this.type = TypeString;
|
||||
this.string = string;
|
||||
this.sideEffects = false;
|
||||
if (string === "") this.falsy = true;
|
||||
else this.truthy = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setUndefined() {
|
||||
this.type = TypeUndefined;
|
||||
this.sideEffects = false;
|
||||
this.falsy = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
setNull() {
|
||||
this.type = TypeNull;
|
||||
this.sideEffects = false;
|
||||
this.falsy = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -320,6 +324,8 @@ class BasicEvaluatedExpression {
|
|||
this.type = TypeNumber;
|
||||
this.number = number;
|
||||
this.sideEffects = false;
|
||||
if (number === 0) this.falsy = true;
|
||||
else this.truthy = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -327,6 +333,8 @@ class BasicEvaluatedExpression {
|
|||
this.type = TypeBigInt;
|
||||
this.bigint = bigint;
|
||||
this.sideEffects = false;
|
||||
if (bigint === BigInt(0)) this.falsy = true;
|
||||
else this.truthy = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -334,6 +342,8 @@ class BasicEvaluatedExpression {
|
|||
this.type = TypeBoolean;
|
||||
this.bool = bool;
|
||||
this.sideEffects = false;
|
||||
if (bool === false) this.falsy = true;
|
||||
else this.truthy = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -341,6 +351,7 @@ class BasicEvaluatedExpression {
|
|||
this.type = TypeRegExp;
|
||||
this.regExp = regExp;
|
||||
this.sideEffects = false;
|
||||
this.truthy = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ const { SyncBailHook, HookMap } = require("tapable");
|
|||
const vm = require("vm");
|
||||
const Parser = require("../Parser");
|
||||
const StackedMap = require("../util/StackedMap");
|
||||
const WriteOnlyStackedSet = require("../util/WriteOnlyStackedSet");
|
||||
const binarySearchBounds = require("../util/binarySearchBounds");
|
||||
const memoize = require("../util/memoize");
|
||||
const BasicEvaluatedExpression = require("./BasicEvaluatedExpression");
|
||||
|
@ -95,11 +96,13 @@ class VariableInfo {
|
|||
/**
|
||||
* @typedef {Object} ScopeInfo
|
||||
* @property {StackedMap<string, VariableInfo | ScopeInfo>} definitions
|
||||
* @property {WriteOnlyStackedSet<string>} guards
|
||||
* @property {boolean | "arrow"} topLevelScope
|
||||
* @property {boolean} inShorthand
|
||||
* @property {boolean} isStrict
|
||||
* @property {boolean} isAsmJs
|
||||
* @property {boolean} inTry
|
||||
* @property {boolean} inGuardPosition
|
||||
*/
|
||||
|
||||
const joinRanges = (startRange, endRange) => {
|
||||
|
@ -1648,8 +1651,11 @@ class JavascriptParser extends Parser {
|
|||
walkIfStatement(statement) {
|
||||
const result = this.hooks.statementIf.call(statement);
|
||||
if (result === undefined) {
|
||||
this.walkExpression(statement.test);
|
||||
const oldGuard = this.scope.guards;
|
||||
this.scope.guards = oldGuard.createChild();
|
||||
this.inGuardPosition(() => this.walkExpression(statement.test), true);
|
||||
this.walkNestedStatement(statement.consequent);
|
||||
this.scope.guards = oldGuard;
|
||||
if (statement.alternate) {
|
||||
this.walkNestedStatement(statement.alternate);
|
||||
}
|
||||
|
@ -2318,12 +2324,15 @@ class JavascriptParser extends Parser {
|
|||
walkAwaitExpression(expression) {
|
||||
if (this.scope.topLevelScope === true)
|
||||
this.hooks.topLevelAwait.call(expression);
|
||||
this.walkExpression(expression.argument);
|
||||
this.inGuardPosition(() => this.walkExpression(expression.argument), false);
|
||||
}
|
||||
|
||||
walkArrayExpression(expression) {
|
||||
if (expression.elements) {
|
||||
this.walkExpressions(expression.elements);
|
||||
this.inGuardPosition(
|
||||
() => this.walkExpressions(expression.elements),
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2340,7 +2349,7 @@ class JavascriptParser extends Parser {
|
|||
propIndex++
|
||||
) {
|
||||
const prop = expression.properties[propIndex];
|
||||
this.walkProperty(prop);
|
||||
this.inGuardPosition(() => this.walkProperty(prop), false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2424,17 +2433,20 @@ class JavascriptParser extends Parser {
|
|||
const old = this.statementPath.pop();
|
||||
for (const expr of expression.expressions) {
|
||||
this.statementPath.push(expr);
|
||||
this.walkExpression(expr);
|
||||
this.inGuardPosition(() => this.walkExpression(expr), false);
|
||||
this.statementPath.pop();
|
||||
}
|
||||
this.statementPath.push(old);
|
||||
} else {
|
||||
this.walkExpressions(expression.expressions);
|
||||
this.inGuardPosition(
|
||||
() => this.walkExpressions(expression.expressions),
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
walkUpdateExpression(expression) {
|
||||
this.walkExpression(expression.argument);
|
||||
this.inGuardPosition(() => this.walkExpression(expression.argument), false);
|
||||
}
|
||||
|
||||
walkUnaryExpression(expression) {
|
||||
|
@ -2454,7 +2466,7 @@ class JavascriptParser extends Parser {
|
|||
if (result === true) return;
|
||||
}
|
||||
}
|
||||
this.walkExpression(expression.argument);
|
||||
this.inGuardPosition(() => this.walkExpression(expression.argument), false);
|
||||
}
|
||||
|
||||
walkLeftRightExpression(expression) {
|
||||
|
@ -2464,14 +2476,27 @@ class JavascriptParser extends Parser {
|
|||
|
||||
walkBinaryExpression(expression) {
|
||||
if (this.hooks.binaryExpression.call(expression) === undefined) {
|
||||
this.walkLeftRightExpression(expression);
|
||||
this.inGuardPosition(
|
||||
() => this.walkLeftRightExpression(expression),
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {LogicalExpressionNode} expression expression
|
||||
*/
|
||||
walkLogicalExpression(expression) {
|
||||
const result = this.hooks.expressionLogicalOperator.call(expression);
|
||||
if (result === undefined) {
|
||||
this.walkLeftRightExpression(expression);
|
||||
if (expression.operator === "&&") {
|
||||
this.walkLeftRightExpression(expression);
|
||||
} else {
|
||||
this.inGuardPosition(
|
||||
() => this.walkLeftRightExpression(expression),
|
||||
false
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (result) {
|
||||
this.walkExpression(expression.right);
|
||||
|
@ -2552,8 +2577,11 @@ class JavascriptParser extends Parser {
|
|||
walkConditionalExpression(expression) {
|
||||
const result = this.hooks.expressionConditionalOperator.call(expression);
|
||||
if (result === undefined) {
|
||||
this.walkExpression(expression.test);
|
||||
const oldGuard = this.scope.guards;
|
||||
this.scope.guards = oldGuard.createChild();
|
||||
this.inGuardPosition(() => this.walkExpression(expression.test), true);
|
||||
this.walkExpression(expression.consequent);
|
||||
this.scope.guards = oldGuard;
|
||||
if (expression.alternate) {
|
||||
this.walkExpression(expression.alternate);
|
||||
}
|
||||
|
@ -2573,31 +2601,41 @@ class JavascriptParser extends Parser {
|
|||
expression
|
||||
);
|
||||
if (result === true) return;
|
||||
this.walkExpression(expression.callee);
|
||||
if (expression.arguments) {
|
||||
this.walkExpressions(expression.arguments);
|
||||
}
|
||||
this.inGuardPosition(() => {
|
||||
this.walkExpression(expression.callee);
|
||||
if (expression.arguments) {
|
||||
this.walkExpressions(expression.arguments);
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
|
||||
walkYieldExpression(expression) {
|
||||
if (expression.argument) {
|
||||
this.walkExpression(expression.argument);
|
||||
this.inGuardPosition(
|
||||
() => this.walkExpression(expression.argument),
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
walkTemplateLiteral(expression) {
|
||||
if (expression.expressions) {
|
||||
this.walkExpressions(expression.expressions);
|
||||
this.inGuardPosition(
|
||||
() => this.walkExpressions(expression.expressions),
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
walkTaggedTemplateExpression(expression) {
|
||||
if (expression.tag) {
|
||||
this.walkExpression(expression.tag);
|
||||
}
|
||||
if (expression.quasi && expression.quasi.expressions) {
|
||||
this.walkExpressions(expression.quasi.expressions);
|
||||
}
|
||||
this.inGuardPosition(() => {
|
||||
if (expression.tag) {
|
||||
this.walkExpression(expression.tag);
|
||||
}
|
||||
if (expression.quasi && expression.quasi.expressions) {
|
||||
this.walkExpressions(expression.quasi.expressions);
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
|
||||
walkClassExpression(expression) {
|
||||
|
@ -2687,7 +2725,7 @@ class JavascriptParser extends Parser {
|
|||
let result = this.hooks.importCall.call(expression);
|
||||
if (result === true) return;
|
||||
|
||||
this.walkExpression(expression.source);
|
||||
this.inGuardPosition(() => this.walkExpression(expression.source), false);
|
||||
}
|
||||
|
||||
walkCallExpression(expression) {
|
||||
|
@ -2753,17 +2791,19 @@ class JavascriptParser extends Parser {
|
|||
if (result2 === true) return;
|
||||
}
|
||||
|
||||
if (expression.callee) {
|
||||
if (expression.callee.type === "MemberExpression") {
|
||||
// because of call context we need to walk the call context as expression
|
||||
this.walkExpression(expression.callee.object);
|
||||
if (expression.callee.computed === true)
|
||||
this.walkExpression(expression.callee.property);
|
||||
} else {
|
||||
this.walkExpression(expression.callee);
|
||||
this.inGuardPosition(() => {
|
||||
if (expression.callee) {
|
||||
if (expression.callee.type === "MemberExpression") {
|
||||
// because of call context we need to walk the call context as expression
|
||||
this.walkExpression(expression.callee.object);
|
||||
if (expression.callee.computed === true)
|
||||
this.walkExpression(expression.callee.property);
|
||||
} else {
|
||||
this.walkExpression(expression.callee);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (expression.arguments) this.walkExpressions(expression.arguments);
|
||||
if (expression.arguments) this.walkExpressions(expression.arguments);
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3025,6 +3065,17 @@ class JavascriptParser extends Parser {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Function} fn function
|
||||
* @param {boolean} state guard position state
|
||||
*/
|
||||
inGuardPosition(fn, state) {
|
||||
const old = this.scope.inGuardPosition;
|
||||
this.scope.inGuardPosition = state;
|
||||
fn();
|
||||
this.scope.inGuardPosition = old;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @param {any} params scope params
|
||||
|
@ -3036,10 +3087,12 @@ class JavascriptParser extends Parser {
|
|||
this.scope = {
|
||||
topLevelScope: oldScope.topLevelScope,
|
||||
inTry: false,
|
||||
inGuardPosition: false,
|
||||
inShorthand: false,
|
||||
isStrict: oldScope.isStrict,
|
||||
isAsmJs: oldScope.isAsmJs,
|
||||
definitions: oldScope.definitions.createChild()
|
||||
definitions: oldScope.definitions.createChild(),
|
||||
guards: oldScope.guards.createChild()
|
||||
};
|
||||
|
||||
this.undefineVariable("this");
|
||||
|
@ -3058,10 +3111,12 @@ class JavascriptParser extends Parser {
|
|||
this.scope = {
|
||||
topLevelScope: oldScope.topLevelScope,
|
||||
inTry: false,
|
||||
inGuardPosition: false,
|
||||
inShorthand: false,
|
||||
isStrict: oldScope.isStrict,
|
||||
isAsmJs: oldScope.isAsmJs,
|
||||
definitions: oldScope.definitions.createChild()
|
||||
definitions: oldScope.definitions.createChild(),
|
||||
guards: oldScope.guards.createChild()
|
||||
};
|
||||
|
||||
if (hasThis) {
|
||||
|
@ -3082,10 +3137,12 @@ class JavascriptParser extends Parser {
|
|||
this.scope = {
|
||||
topLevelScope: oldScope.topLevelScope,
|
||||
inTry: oldScope.inTry,
|
||||
inGuardPosition: false,
|
||||
inShorthand: false,
|
||||
isStrict: oldScope.isStrict,
|
||||
isAsmJs: oldScope.isAsmJs,
|
||||
definitions: oldScope.definitions.createChild()
|
||||
definitions: oldScope.definitions.createChild(),
|
||||
guards: oldScope.guards.createChild()
|
||||
};
|
||||
|
||||
fn();
|
||||
|
@ -3333,10 +3390,12 @@ class JavascriptParser extends Parser {
|
|||
this.scope = {
|
||||
topLevelScope: true,
|
||||
inTry: false,
|
||||
inGuardPosition: false,
|
||||
inShorthand: false,
|
||||
isStrict: false,
|
||||
isAsmJs: false,
|
||||
definitions: new StackedMap()
|
||||
definitions: new StackedMap(),
|
||||
guards: new WriteOnlyStackedSet()
|
||||
};
|
||||
/** @type {ParserState} */
|
||||
this.state = state;
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
MIT License http://www.opensource.org/licenses/mit-license.php
|
||||
Author Ivan Kopeykin @vankop
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
class WriteOnlyStackedSet {
|
||||
constructor(sets = []) {
|
||||
this._sets = sets;
|
||||
this._current = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {T} el element
|
||||
*/
|
||||
add(el) {
|
||||
if (!this._current) {
|
||||
this._current = new Set();
|
||||
this._sets.push(this._current);
|
||||
}
|
||||
this._current.add(el);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {T} el element
|
||||
* @returns {boolean} result
|
||||
*/
|
||||
has(el) {
|
||||
for (const set of this._sets) {
|
||||
if (set.has(el)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._sets = [];
|
||||
if (this._current) this._current.clear();
|
||||
}
|
||||
|
||||
createChild() {
|
||||
return new WriteOnlyStackedSet(this._sets.length ? this._sets.slice() : []);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WriteOnlyStackedSet;
|
|
@ -105,6 +105,8 @@ module.exports = {
|
|||
require("../dependencies/HarmonyImportSpecifierDependency"),
|
||||
"dependencies/HarmonyEvaluatedImportSpecifierDependency": () =>
|
||||
require("../dependencies/HarmonyEvaluatedImportSpecifierDependency"),
|
||||
"dependencies/HarmonyExportPresenceImportSpecifierDependency": () =>
|
||||
require("../dependencies/HarmonyExportPresenceImportSpecifierDependency"),
|
||||
"dependencies/ImportContextDependency": () =>
|
||||
require("../dependencies/ImportContextDependency"),
|
||||
"dependencies/ImportDependency": () =>
|
||||
|
|
|
@ -1,7 +1,93 @@
|
|||
import { NotHere as aaa } from "./aaa/index.js";
|
||||
import { NotHere as bbb } from "./bbb/index.js";
|
||||
import { NotHere as ccc } from "./ccc/index.js";
|
||||
import { NotHere as ddd } from "./ddd/index.js";
|
||||
import { NotHere as aaa, /* not here */ a } from "./aaa/index.js";
|
||||
import { NotHere as bbb, /* not here */ b } from "./bbb/index.js";
|
||||
import { NotHere as ccc, /* not here */ c } from "./ccc/index.js";
|
||||
import { NotHere as ddd, /* not here */ d } from "./ddd/index.js";
|
||||
import * as m from "./module";
|
||||
|
||||
const val1 = Math.random();
|
||||
|
||||
function throw_() {
|
||||
throw new Error();
|
||||
}
|
||||
function justFunction() {}
|
||||
|
||||
describe("should not add additional warnings/errors", () => {
|
||||
it("simple cases", () => {
|
||||
if (b) {
|
||||
if (d) d();
|
||||
b();
|
||||
if (c) {
|
||||
b();
|
||||
}
|
||||
}
|
||||
(false && d);
|
||||
(d ? d() : throw_());
|
||||
// should add 2 warnings
|
||||
if (a && val1 || true) {
|
||||
a();
|
||||
}
|
||||
if (a && a.b && a.b.c) {
|
||||
a();
|
||||
}
|
||||
// only one warning
|
||||
if (a.b.c) {
|
||||
a.b.c();
|
||||
}
|
||||
});
|
||||
|
||||
it("different expressions", () => {
|
||||
if (a && a.b.c) {}
|
||||
// should add warning (function scope)
|
||||
if ((() => a())()) {}
|
||||
// should add warning (unary expression)
|
||||
if (!a && b) {}
|
||||
// should add warning (binary expression)
|
||||
if (a & true) {}
|
||||
|
||||
function *foo() {
|
||||
// should add warning (yield expression)
|
||||
if (yield a && true) {}
|
||||
}
|
||||
async function foo1() {
|
||||
// should add warning (yield expression)
|
||||
if (await a && true) {}
|
||||
}
|
||||
let var1;
|
||||
if (var1 = b) {}
|
||||
if ((var1 = b) && c && c.a) {}
|
||||
// should add warning
|
||||
if (justFunction`${a}`) {}
|
||||
if (`${a}`) {}
|
||||
});
|
||||
|
||||
it("in operator", () => {
|
||||
if ("a" in m) { justFunction(m.a); }
|
||||
if ("b" in m && "c" in m.b) { justFunction(m.b.c); }
|
||||
if ("c" in m) { justFunction(m.c); }
|
||||
// should add one warning
|
||||
if ("a"in d.c) { justFunction(d.c.a()); }
|
||||
});
|
||||
|
||||
it("identifier != falsy", () => {
|
||||
if (c != false) {}
|
||||
// should add warning since value could be undefined !== false
|
||||
if (c !== false) {}
|
||||
if (c != null && c.a != undefined && c.a.b != false && 0 != c.a.b.c && "" != c.a.b.c.d) {
|
||||
c();
|
||||
c.a();
|
||||
c().a;
|
||||
{
|
||||
c.a.b();
|
||||
const a = () => c.a.b.c.d();
|
||||
const b = function () {
|
||||
c.a.b.c.d();
|
||||
}
|
||||
}
|
||||
}
|
||||
// should add 2 warnings
|
||||
if (c != undefined ?? undefined != c) {}
|
||||
});
|
||||
});
|
||||
|
||||
it("should do nothing", () => {
|
||||
expect(aaa).toBe(undefined);
|
||||
|
|
|
@ -10,5 +10,65 @@ module.exports = [
|
|||
{
|
||||
moduleName: /ddd/,
|
||||
message: /NoNo.+not found/
|
||||
},
|
||||
{
|
||||
moduleName: /index/,
|
||||
message: /a.+not found/
|
||||
},
|
||||
{
|
||||
moduleName: /index/,
|
||||
message: /a.+not found/
|
||||
},
|
||||
{
|
||||
moduleName: /index/,
|
||||
message: /a.+not found/
|
||||
},
|
||||
{
|
||||
moduleName: /index/,
|
||||
message: /a.+not found/
|
||||
},
|
||||
{
|
||||
moduleName: /index/,
|
||||
message: /a.+not found/
|
||||
},
|
||||
{
|
||||
moduleName: /index/,
|
||||
message: /a.+not found/
|
||||
},
|
||||
{
|
||||
moduleName: /index/,
|
||||
message: /a.+not found/
|
||||
},
|
||||
{
|
||||
moduleName: /index/,
|
||||
message: /a.+not found/
|
||||
},
|
||||
{
|
||||
moduleName: /index/,
|
||||
message: /a.+not found/
|
||||
},
|
||||
{
|
||||
moduleName: /index/,
|
||||
message: /a.+not found/
|
||||
},
|
||||
{
|
||||
moduleName: /index/,
|
||||
message: /a.+not found/
|
||||
},
|
||||
{
|
||||
moduleName: /index/,
|
||||
message: /d.+not found/
|
||||
},
|
||||
{
|
||||
moduleName: /index/,
|
||||
message: /c.+not found/
|
||||
},
|
||||
{
|
||||
moduleName: /index/,
|
||||
message: /c.+not found/
|
||||
},
|
||||
{
|
||||
moduleName: /index/,
|
||||
message: /c.+not found/
|
||||
}
|
||||
];
|
||||
|
|
|
@ -5271,7 +5271,7 @@ declare class JavascriptParser extends Parser {
|
|||
walkUnaryExpression(expression?: any): void;
|
||||
walkLeftRightExpression(expression?: any): void;
|
||||
walkBinaryExpression(expression?: any): void;
|
||||
walkLogicalExpression(expression?: any): void;
|
||||
walkLogicalExpression(expression: LogicalExpression): void;
|
||||
walkAssignmentExpression(expression?: any): void;
|
||||
walkConditionalExpression(expression?: any): void;
|
||||
walkNewExpression(expression?: any): void;
|
||||
|
@ -5329,6 +5329,7 @@ declare class JavascriptParser extends Parser {
|
|||
defined: () => any,
|
||||
...args: AsArray<T>
|
||||
): R;
|
||||
inGuardPosition(fn: Function, state: boolean): void;
|
||||
inScope(params: any, fn: () => void): void;
|
||||
inFunctionScope(hasThis?: any, params?: any, fn?: any): void;
|
||||
inBlockScope(fn?: any): void;
|
||||
|
@ -10729,11 +10730,13 @@ declare interface RuntimeValueOptions {
|
|||
}
|
||||
declare interface ScopeInfo {
|
||||
definitions: StackedMap<string, ScopeInfo | VariableInfo>;
|
||||
guards: WriteOnlyStackedSet<string>;
|
||||
topLevelScope: boolean | "arrow";
|
||||
inShorthand: boolean;
|
||||
isStrict: boolean;
|
||||
isAsmJs: boolean;
|
||||
inTry: boolean;
|
||||
inGuardPosition: boolean;
|
||||
}
|
||||
declare interface Selector<A, B> {
|
||||
(input: A): B;
|
||||
|
@ -12478,6 +12481,12 @@ declare interface WithOptions {
|
|||
declare interface WriteOnlySet<T> {
|
||||
add: (T?: any) => void;
|
||||
}
|
||||
declare abstract class WriteOnlyStackedSet<T> {
|
||||
add(el: T): void;
|
||||
has(el: T): boolean;
|
||||
clear(): void;
|
||||
createChild(): WriteOnlyStackedSet<any>;
|
||||
}
|
||||
type __TypeWebpackOptions = (data: object) =>
|
||||
| string
|
||||
| {
|
||||
|
|
Loading…
Reference in New Issue