mirror of https://github.com/webpack/webpack.git
fix: should avoid through variables in inlined module
This commit is contained in:
parent
65bce6826b
commit
bb10e4b5f0
|
@ -5,14 +5,16 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
DEFAULT_EXPORT,
|
||||
NAMESPACE_OBJECT_EXPORT
|
||||
} = require("./util/concatenate");
|
||||
|
||||
/** @typedef {import("./Module")} Module */
|
||||
|
||||
const MODULE_REFERENCE_REGEXP =
|
||||
/^__WEBPACK_MODULE_REFERENCE__(\d+)_([\da-f]+|ns)(_call)?(_directImport)?(?:_asiSafe(\d))?__$/;
|
||||
|
||||
const DEFAULT_EXPORT = "__WEBPACK_DEFAULT_EXPORT__";
|
||||
const NAMESPACE_OBJECT_EXPORT = "__WEBPACK_NAMESPACE_OBJECT__";
|
||||
|
||||
/**
|
||||
* @typedef {object} ExternalModuleInfo
|
||||
* @property {number} index
|
||||
|
|
|
@ -31,13 +31,21 @@ const Template = require("../Template");
|
|||
const { last, someInIterable } = require("../util/IterableHelpers");
|
||||
const StringXor = require("../util/StringXor");
|
||||
const { compareModulesByIdentifier } = require("../util/comparators");
|
||||
const {
|
||||
getPathInAst,
|
||||
getAllReferences,
|
||||
RESERVED_NAMES,
|
||||
findNewName,
|
||||
addScopeSymbols,
|
||||
getUsedNamesInScopeInfo
|
||||
} = require("../util/concatenate");
|
||||
const createHash = require("../util/createHash");
|
||||
const { getPathInAst, getAllReferences } = require("../util/mergeScope");
|
||||
const nonNumericOnlyHash = require("../util/nonNumericOnlyHash");
|
||||
const { intersectRuntime } = require("../util/runtime");
|
||||
const JavascriptGenerator = require("./JavascriptGenerator");
|
||||
const JavascriptParser = require("./JavascriptParser");
|
||||
|
||||
/** @typedef {import("eslint-scope").Reference} Reference */
|
||||
/** @typedef {import("eslint-scope").Scope} Scope */
|
||||
/** @typedef {import("eslint-scope").Variable} Variable */
|
||||
/** @typedef {import("webpack-sources").Source} Source */
|
||||
|
@ -1458,8 +1466,7 @@ class JavascriptModulesPlugin {
|
|||
const renamedInlinedModules = new Map();
|
||||
const { runtimeTemplate } = renderContext;
|
||||
|
||||
/** @typedef {{ source: Source, ast: any, variables: Set<Variable>, usedInNonInlined: Set<Variable>}} InlinedModulesInfo */
|
||||
/** @type {Map<Module, InlinedModulesInfo>} */
|
||||
/** @type {Map<Module, { source: Source, module: Module, ast: any, variables: Set<Variable>, through: Set<Reference>, usedInNonInlined: Set<Variable>, moduleScope: Scope }>} */
|
||||
const inlinedModulesToInfo = new Map();
|
||||
/** @type {Set<string>} */
|
||||
const nonInlinedModuleThroughIdentifiers = new Set();
|
||||
|
@ -1487,14 +1494,17 @@ class JavascriptModulesPlugin {
|
|||
ignoreEval: true
|
||||
});
|
||||
|
||||
const globalScope = /** @type {Scope} */ (scopeManager.acquire(ast));
|
||||
const globalScope = scopeManager.acquire(ast);
|
||||
if (inlinedModules && inlinedModules.has(m)) {
|
||||
const moduleScope = globalScope.childScopes[0];
|
||||
inlinedModulesToInfo.set(m, {
|
||||
source: moduleSource,
|
||||
ast,
|
||||
module: m,
|
||||
variables: new Set(moduleScope.variables),
|
||||
usedInNonInlined: new Set()
|
||||
through: new Set(moduleScope.through),
|
||||
usedInNonInlined: new Set(),
|
||||
moduleScope
|
||||
});
|
||||
} else {
|
||||
for (const ref of globalScope.through) {
|
||||
|
@ -1505,7 +1515,10 @@ class JavascriptModulesPlugin {
|
|||
|
||||
for (const [, { variables, usedInNonInlined }] of inlinedModulesToInfo) {
|
||||
for (const variable of variables) {
|
||||
if (nonInlinedModuleThroughIdentifiers.has(variable.name)) {
|
||||
if (
|
||||
nonInlinedModuleThroughIdentifiers.has(variable.name) ||
|
||||
RESERVED_NAMES.has(variable.name)
|
||||
) {
|
||||
usedInNonInlined.add(variable);
|
||||
}
|
||||
}
|
||||
|
@ -1519,40 +1532,71 @@ class JavascriptModulesPlugin {
|
|||
continue;
|
||||
}
|
||||
|
||||
const usedNames = new Set(
|
||||
Array.from(
|
||||
/** @type {InlinedModulesInfo} */
|
||||
(inlinedModulesToInfo.get(m)).variables
|
||||
).map(v => v.name)
|
||||
const info = inlinedModulesToInfo.get(m);
|
||||
const allUsedNames = new Set(
|
||||
Array.from(info.through, v => v.identifier.name)
|
||||
);
|
||||
|
||||
for (const variable of usedInNonInlined) {
|
||||
allUsedNames.add(variable.name);
|
||||
}
|
||||
|
||||
for (const variable of info.variables) {
|
||||
allUsedNames.add(variable.name);
|
||||
const references = getAllReferences(variable);
|
||||
const allIdentifiers = new Set(
|
||||
references.map(r => r.identifier).concat(variable.identifiers)
|
||||
);
|
||||
|
||||
const newName = this.findNewName(
|
||||
const usedNamesInScopeInfo = new Map();
|
||||
const ignoredScopes = new Set();
|
||||
|
||||
const name = variable.name;
|
||||
const { usedNames, alreadyCheckedScopes } = getUsedNamesInScopeInfo(
|
||||
usedNamesInScopeInfo,
|
||||
info.module.identifier(),
|
||||
name
|
||||
);
|
||||
|
||||
if (allUsedNames.has(name) || usedNames.has(name)) {
|
||||
const references = getAllReferences(variable);
|
||||
for (const ref of references) {
|
||||
addScopeSymbols(
|
||||
ref.from,
|
||||
usedNames,
|
||||
alreadyCheckedScopes,
|
||||
ignoredScopes
|
||||
);
|
||||
}
|
||||
|
||||
const newName = findNewName(
|
||||
variable.name,
|
||||
allUsedNames,
|
||||
usedNames,
|
||||
m.readableIdentifier(runtimeTemplate.requestShortener)
|
||||
);
|
||||
usedNames.add(newName);
|
||||
allUsedNames.add(newName);
|
||||
for (const identifier of allIdentifiers) {
|
||||
const r = /** @type {Range} */ (identifier.range);
|
||||
const r = identifier.range;
|
||||
const path = getPathInAst(ast, identifier);
|
||||
if (path && path.length > 1) {
|
||||
const maybeProperty =
|
||||
path[1].type === "AssignmentPattern" && path[1].left === path[0]
|
||||
? path[2]
|
||||
: path[1];
|
||||
if (maybeProperty.type === "Property" && maybeProperty.shorthand) {
|
||||
if (
|
||||
maybeProperty.type === "Property" &&
|
||||
maybeProperty.shorthand
|
||||
) {
|
||||
source.insert(r[1], `: ${newName}`);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
source.replace(r[0], r[1] - 1, newName);
|
||||
}
|
||||
} else {
|
||||
allUsedNames.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
renamedInlinedModules.set(m, source);
|
||||
|
@ -1560,38 +1604,6 @@ class JavascriptModulesPlugin {
|
|||
|
||||
return renamedInlinedModules;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} oldName oldName
|
||||
* @param {Set<string>} usedName usedName
|
||||
* @param {string} extraInfo extraInfo
|
||||
* @returns {string} extraInfo
|
||||
*/
|
||||
findNewName(oldName, usedName, extraInfo) {
|
||||
let name = oldName;
|
||||
|
||||
// Remove uncool stuff
|
||||
extraInfo = extraInfo.replace(
|
||||
/\.+\/|(\/index)?\.([a-zA-Z0-9]{1,4})($|\s|\?)|\s*\+\s*\d+\s*modules/g,
|
||||
""
|
||||
);
|
||||
const splittedInfo = extraInfo.split("/");
|
||||
while (splittedInfo.length) {
|
||||
name = splittedInfo.pop() + (name ? `_${name}` : "");
|
||||
const nameIdent = Template.toIdentifier(name);
|
||||
if (!usedName.has(nameIdent)) {
|
||||
return nameIdent;
|
||||
}
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
let nameWithNumber = Template.toIdentifier(`${name}_${i}`);
|
||||
while (usedName.has(nameWithNumber)) {
|
||||
i++;
|
||||
nameWithNumber = Template.toIdentifier(`${name}_${i}`);
|
||||
}
|
||||
return nameWithNumber;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = JavascriptModulesPlugin;
|
||||
|
|
|
@ -24,10 +24,17 @@ const JavascriptParser = require("../javascript/JavascriptParser");
|
|||
const { equals } = require("../util/ArrayHelpers");
|
||||
const LazySet = require("../util/LazySet");
|
||||
const { concatComparators } = require("../util/comparators");
|
||||
const {
|
||||
RESERVED_NAMES,
|
||||
findNewName,
|
||||
addScopeSymbols,
|
||||
getAllReferences,
|
||||
getPathInAst,
|
||||
getUsedNamesInScopeInfo
|
||||
} = require("../util/concatenate");
|
||||
const createHash = require("../util/createHash");
|
||||
const { makePathsRelative } = require("../util/identifier");
|
||||
const makeSerializable = require("../util/makeSerializable");
|
||||
const { getAllReferences, getPathInAst } = require("../util/mergeScope");
|
||||
const propertyAccess = require("../util/propertyAccess");
|
||||
const { propertyName } = require("../util/propertyName");
|
||||
const {
|
||||
|
@ -74,6 +81,7 @@ const {
|
|||
/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
|
||||
/** @typedef {import("../util/Hash")} Hash */
|
||||
/** @typedef {typeof import("../util/Hash")} HashConstructor */
|
||||
/** @typedef {import("../util/concatenate").UsedNames} UsedNames */
|
||||
/** @typedef {import("../util/fs").InputFileSystem} InputFileSystem */
|
||||
/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
|
||||
|
||||
|
@ -171,52 +179,13 @@ if (!ReferencerClass.prototype.PropertyDefinition) {
|
|||
* @property {ConcatenatedModuleInfo | ExternalModuleInfo} target
|
||||
*/
|
||||
|
||||
/** @typedef {Set<string>} UsedNames */
|
||||
|
||||
const RESERVED_NAMES = new Set(
|
||||
[
|
||||
// internal names (should always be renamed)
|
||||
ConcatenationScope.DEFAULT_EXPORT,
|
||||
ConcatenationScope.NAMESPACE_OBJECT_EXPORT,
|
||||
|
||||
// keywords
|
||||
"abstract,arguments,async,await,boolean,break,byte,case,catch,char,class,const,continue",
|
||||
"debugger,default,delete,do,double,else,enum,eval,export,extends,false,final,finally,float",
|
||||
"for,function,goto,if,implements,import,in,instanceof,int,interface,let,long,native,new,null",
|
||||
"package,private,protected,public,return,short,static,super,switch,synchronized,this,throw",
|
||||
"throws,transient,true,try,typeof,var,void,volatile,while,with,yield",
|
||||
|
||||
// commonjs/amd
|
||||
"module,__dirname,__filename,exports,require,define",
|
||||
|
||||
// js globals
|
||||
"Array,Date,eval,function,hasOwnProperty,Infinity,isFinite,isNaN,isPrototypeOf,length,Math",
|
||||
"NaN,name,Number,Object,prototype,String,toString,undefined,valueOf",
|
||||
|
||||
// browser globals
|
||||
"alert,all,anchor,anchors,area,assign,blur,button,checkbox,clearInterval,clearTimeout",
|
||||
"clientInformation,close,closed,confirm,constructor,crypto,decodeURI,decodeURIComponent",
|
||||
"defaultStatus,document,element,elements,embed,embeds,encodeURI,encodeURIComponent,escape",
|
||||
"event,fileUpload,focus,form,forms,frame,innerHeight,innerWidth,layer,layers,link,location",
|
||||
"mimeTypes,navigate,navigator,frames,frameRate,hidden,history,image,images,offscreenBuffering",
|
||||
"open,opener,option,outerHeight,outerWidth,packages,pageXOffset,pageYOffset,parent,parseFloat",
|
||||
"parseInt,password,pkcs11,plugin,prompt,propertyIsEnum,radio,reset,screenX,screenY,scroll",
|
||||
"secure,select,self,setInterval,setTimeout,status,submit,taint,text,textarea,top,unescape",
|
||||
"untaint,window",
|
||||
|
||||
// window events
|
||||
"onblur,onclick,onerror,onfocus,onkeydown,onkeypress,onkeyup,onmouseover,onload,onmouseup,onmousedown,onsubmit"
|
||||
]
|
||||
.join(",")
|
||||
.split(",")
|
||||
);
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {string} property property
|
||||
* @param {function(T[keyof T], T[keyof T]): 0 | 1 | -1} comparator comparator
|
||||
* @returns {Comparator<T>} comparator
|
||||
*/
|
||||
|
||||
const createComparator = (property, comparator) => (a, b) =>
|
||||
comparator(
|
||||
a[/** @type {keyof T} */ (property)],
|
||||
|
@ -628,25 +597,6 @@ const getFinalName = (
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Scope | null} s scope
|
||||
* @param {UsedNames} nameSet name set
|
||||
* @param {TODO} scopeSet1 scope set 1
|
||||
* @param {TODO} scopeSet2 scope set 2
|
||||
*/
|
||||
const addScopeSymbols = (s, nameSet, scopeSet1, scopeSet2) => {
|
||||
let scope = s;
|
||||
while (scope) {
|
||||
if (scopeSet1.has(scope)) break;
|
||||
if (scopeSet2.has(scope)) break;
|
||||
scopeSet1.add(scope);
|
||||
for (const variable of scope.variables) {
|
||||
nameSet.add(variable.name);
|
||||
}
|
||||
scope = scope.upper;
|
||||
}
|
||||
};
|
||||
|
||||
const TYPES = new Set(["javascript"]);
|
||||
|
||||
/**
|
||||
|
@ -1193,18 +1143,6 @@ class ConcatenatedModule extends Module {
|
|||
* @param {string} id export id
|
||||
* @returns {{ usedNames: UsedNames, alreadyCheckedScopes: Set<TODO> }} info
|
||||
*/
|
||||
const getUsedNamesInScopeInfo = (module, id) => {
|
||||
const key = `${module}-${id}`;
|
||||
let info = usedNamesInScopeInfo.get(key);
|
||||
if (info === undefined) {
|
||||
info = {
|
||||
usedNames: new Set(),
|
||||
alreadyCheckedScopes: new Set()
|
||||
};
|
||||
usedNamesInScopeInfo.set(key, info);
|
||||
}
|
||||
return info;
|
||||
};
|
||||
|
||||
// Set of already checked scopes
|
||||
const ignoredScopes = new Set();
|
||||
|
@ -1274,6 +1212,7 @@ class ConcatenatedModule extends Module {
|
|||
if (!binding.ids) continue;
|
||||
const { usedNames, alreadyCheckedScopes } =
|
||||
getUsedNamesInScopeInfo(
|
||||
usedNamesInScopeInfo,
|
||||
binding.info.module.identifier(),
|
||||
"name" in binding ? binding.name : ""
|
||||
);
|
||||
|
@ -1306,6 +1245,7 @@ class ConcatenatedModule extends Module {
|
|||
// generate names for symbols
|
||||
for (const info of moduleToInfoMap.values()) {
|
||||
const { usedNames: namespaceObjectUsedNames } = getUsedNamesInScopeInfo(
|
||||
usedNamesInScopeInfo,
|
||||
info.module.identifier(),
|
||||
""
|
||||
);
|
||||
|
@ -1314,6 +1254,7 @@ class ConcatenatedModule extends Module {
|
|||
for (const variable of info.moduleScope.variables) {
|
||||
const name = variable.name;
|
||||
const { usedNames, alreadyCheckedScopes } = getUsedNamesInScopeInfo(
|
||||
usedNamesInScopeInfo,
|
||||
info.module.identifier(),
|
||||
name
|
||||
);
|
||||
|
@ -1327,7 +1268,7 @@ class ConcatenatedModule extends Module {
|
|||
ignoredScopes
|
||||
);
|
||||
}
|
||||
const newName = this.findNewName(
|
||||
const newName = findNewName(
|
||||
name,
|
||||
allUsedNames,
|
||||
usedNames,
|
||||
|
@ -1375,7 +1316,7 @@ class ConcatenatedModule extends Module {
|
|||
info.namespaceExportSymbol
|
||||
);
|
||||
} else {
|
||||
namespaceObjectName = this.findNewName(
|
||||
namespaceObjectName = findNewName(
|
||||
"namespaceObject",
|
||||
allUsedNames,
|
||||
namespaceObjectUsedNames,
|
||||
|
@ -1390,7 +1331,7 @@ class ConcatenatedModule extends Module {
|
|||
break;
|
||||
}
|
||||
case "external": {
|
||||
const externalName = this.findNewName(
|
||||
const externalName = findNewName(
|
||||
"",
|
||||
allUsedNames,
|
||||
namespaceObjectUsedNames,
|
||||
|
@ -1404,7 +1345,7 @@ class ConcatenatedModule extends Module {
|
|||
}
|
||||
const buildMeta = /** @type {BuildMeta} */ (info.module.buildMeta);
|
||||
if (buildMeta.exportsType !== "namespace") {
|
||||
const externalNameInterop = this.findNewName(
|
||||
const externalNameInterop = findNewName(
|
||||
"namespaceObject",
|
||||
allUsedNames,
|
||||
namespaceObjectUsedNames,
|
||||
|
@ -1418,7 +1359,7 @@ class ConcatenatedModule extends Module {
|
|||
buildMeta.exportsType === "default" &&
|
||||
buildMeta.defaultObject !== "redirect"
|
||||
) {
|
||||
const externalNameInterop = this.findNewName(
|
||||
const externalNameInterop = findNewName(
|
||||
"namespaceObject2",
|
||||
allUsedNames,
|
||||
namespaceObjectUsedNames,
|
||||
|
@ -1429,7 +1370,7 @@ class ConcatenatedModule extends Module {
|
|||
topLevelDeclarations.add(externalNameInterop);
|
||||
}
|
||||
if (buildMeta.exportsType === "dynamic" || !buildMeta.exportsType) {
|
||||
const externalNameInterop = this.findNewName(
|
||||
const externalNameInterop = findNewName(
|
||||
"default",
|
||||
allUsedNames,
|
||||
namespaceObjectUsedNames,
|
||||
|
@ -1915,53 +1856,6 @@ ${defineGetters}`
|
|||
return [list, map];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} oldName old name
|
||||
* @param {UsedNames} usedNamed1 used named 1
|
||||
* @param {UsedNames} usedNamed2 used named 2
|
||||
* @param {string} extraInfo extra info
|
||||
* @returns {string} found new name
|
||||
*/
|
||||
findNewName(oldName, usedNamed1, usedNamed2, extraInfo) {
|
||||
let name = oldName;
|
||||
|
||||
if (name === ConcatenationScope.DEFAULT_EXPORT) {
|
||||
name = "";
|
||||
}
|
||||
if (name === ConcatenationScope.NAMESPACE_OBJECT_EXPORT) {
|
||||
name = "namespaceObject";
|
||||
}
|
||||
|
||||
// Remove uncool stuff
|
||||
extraInfo = extraInfo.replace(
|
||||
/\.+\/|(\/index)?\.([a-zA-Z0-9]{1,4})($|\s|\?)|\s*\+\s*\d+\s*modules/g,
|
||||
""
|
||||
);
|
||||
|
||||
const splittedInfo = extraInfo.split("/");
|
||||
while (splittedInfo.length) {
|
||||
name = splittedInfo.pop() + (name ? `_${name}` : "");
|
||||
const nameIdent = Template.toIdentifier(name);
|
||||
if (
|
||||
!usedNamed1.has(nameIdent) &&
|
||||
(!usedNamed2 || !usedNamed2.has(nameIdent))
|
||||
)
|
||||
return nameIdent;
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
let nameWithNumber = Template.toIdentifier(`${name}_${i}`);
|
||||
while (
|
||||
usedNamed1.has(nameWithNumber) ||
|
||||
// eslint-disable-next-line no-unmodified-loop-condition
|
||||
(usedNamed2 && usedNamed2.has(nameWithNumber))
|
||||
) {
|
||||
i++;
|
||||
nameWithNumber = Template.toIdentifier(`${name}_${i}`);
|
||||
}
|
||||
return nameWithNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Hash} hash the hash used to track dependencies
|
||||
* @param {UpdateHashContext} context context
|
||||
|
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
MIT License http://www.opensource.org/licenses/mit-license.php
|
||||
Author Tobias Koppers @sokra
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const Template = require("../Template");
|
||||
|
||||
/** @typedef {import("eslint-scope").Scope} Scope */
|
||||
/** @typedef {import("eslint-scope").Reference} Reference */
|
||||
/** @typedef {import("eslint-scope").Variable} Variable */
|
||||
/** @typedef {import("estree").Node} Node */
|
||||
/** @typedef {import("../javascript/JavascriptParser").Range} Range */
|
||||
/** @typedef {import("../javascript/JavascriptParser").Program} Program */
|
||||
/** @typedef {Set<string>} UsedNames */
|
||||
|
||||
const DEFAULT_EXPORT = "__WEBPACK_DEFAULT_EXPORT__";
|
||||
const NAMESPACE_OBJECT_EXPORT = "__WEBPACK_NAMESPACE_OBJECT__";
|
||||
|
||||
/**
|
||||
* @param {Variable} variable variable
|
||||
* @returns {Reference[]} references
|
||||
*/
|
||||
const getAllReferences = variable => {
|
||||
let set = variable.references;
|
||||
// Look for inner scope variables too (like in class Foo { t() { Foo } })
|
||||
const identifiers = new Set(variable.identifiers);
|
||||
for (const scope of variable.scope.childScopes) {
|
||||
for (const innerVar of scope.variables) {
|
||||
if (innerVar.identifiers.some(id => identifiers.has(id))) {
|
||||
set = set.concat(innerVar.references);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return set;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Node | Node[]} ast ast
|
||||
* @param {Node} node node
|
||||
* @returns {undefined | Node[]} result
|
||||
*/
|
||||
const getPathInAst = (ast, node) => {
|
||||
if (ast === node) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const nr = /** @type {Range} */ (node.range);
|
||||
|
||||
/**
|
||||
* @param {Node} n node
|
||||
* @returns {Node[] | undefined} result
|
||||
*/
|
||||
const enterNode = n => {
|
||||
if (!n) return;
|
||||
const r = n.range;
|
||||
if (r && r[0] <= nr[0] && r[1] >= nr[1]) {
|
||||
const path = getPathInAst(n, node);
|
||||
if (path) {
|
||||
path.push(n);
|
||||
return path;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (Array.isArray(ast)) {
|
||||
for (let i = 0; i < ast.length; i++) {
|
||||
const enterResult = enterNode(ast[i]);
|
||||
if (enterResult !== undefined) return enterResult;
|
||||
}
|
||||
} else if (ast && typeof ast === "object") {
|
||||
const keys =
|
||||
/** @type {Array<keyof Node>} */
|
||||
(Object.keys(ast));
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
// We are making the faster check in `enterNode` using `n.range`
|
||||
const value =
|
||||
ast[
|
||||
/** @type {Exclude<keyof Node, "range" | "loc" | "leadingComments" | "trailingComments">} */
|
||||
(keys[i])
|
||||
];
|
||||
if (Array.isArray(value)) {
|
||||
const pathResult = getPathInAst(value, node);
|
||||
if (pathResult !== undefined) return pathResult;
|
||||
} else if (value && typeof value === "object") {
|
||||
const enterResult = enterNode(value);
|
||||
if (enterResult !== undefined) return enterResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} oldName old name
|
||||
* @param {UsedNames} usedNamed1 used named 1
|
||||
* @param {UsedNames} usedNamed2 used named 2
|
||||
* @param {string} extraInfo extra info
|
||||
* @returns {string} found new name
|
||||
*/
|
||||
function findNewName(oldName, usedNamed1, usedNamed2, extraInfo) {
|
||||
let name = oldName;
|
||||
|
||||
if (name === DEFAULT_EXPORT) {
|
||||
name = "";
|
||||
}
|
||||
if (name === NAMESPACE_OBJECT_EXPORT) {
|
||||
name = "namespaceObject";
|
||||
}
|
||||
|
||||
// Remove uncool stuff
|
||||
extraInfo = extraInfo.replace(
|
||||
/\.+\/|(\/index)?\.([a-zA-Z0-9]{1,4})($|\s|\?)|\s*\+\s*\d+\s*modules/g,
|
||||
""
|
||||
);
|
||||
|
||||
const splittedInfo = extraInfo.split("/");
|
||||
while (splittedInfo.length) {
|
||||
name = splittedInfo.pop() + (name ? `_${name}` : "");
|
||||
const nameIdent = Template.toIdentifier(name);
|
||||
if (
|
||||
!usedNamed1.has(nameIdent) &&
|
||||
(!usedNamed2 || !usedNamed2.has(nameIdent))
|
||||
)
|
||||
return nameIdent;
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
let nameWithNumber = Template.toIdentifier(`${name}_${i}`);
|
||||
while (
|
||||
usedNamed1.has(nameWithNumber) ||
|
||||
// eslint-disable-next-line no-unmodified-loop-condition
|
||||
(usedNamed2 && usedNamed2.has(nameWithNumber))
|
||||
) {
|
||||
i++;
|
||||
nameWithNumber = Template.toIdentifier(`${name}_${i}`);
|
||||
}
|
||||
return nameWithNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Scope | null} s scope
|
||||
* @param {UsedNames} nameSet name set
|
||||
* @param {TODO} scopeSet1 scope set 1
|
||||
* @param {TODO} scopeSet2 scope set 2
|
||||
*/
|
||||
const addScopeSymbols = (s, nameSet, scopeSet1, scopeSet2) => {
|
||||
let scope = s;
|
||||
while (scope) {
|
||||
if (scopeSet1.has(scope)) break;
|
||||
if (scopeSet2.has(scope)) break;
|
||||
scopeSet1.add(scope);
|
||||
for (const variable of scope.variables) {
|
||||
nameSet.add(variable.name);
|
||||
}
|
||||
scope = scope.upper;
|
||||
}
|
||||
};
|
||||
|
||||
const RESERVED_NAMES = new Set(
|
||||
[
|
||||
// internal names (should always be renamed)
|
||||
DEFAULT_EXPORT,
|
||||
NAMESPACE_OBJECT_EXPORT,
|
||||
|
||||
// keywords
|
||||
"abstract,arguments,async,await,boolean,break,byte,case,catch,char,class,const,continue",
|
||||
"debugger,default,delete,do,double,else,enum,eval,export,extends,false,final,finally,float",
|
||||
"for,function,goto,if,implements,import,in,instanceof,int,interface,let,long,native,new,null",
|
||||
"package,private,protected,public,return,short,static,super,switch,synchronized,this,throw",
|
||||
"throws,transient,true,try,typeof,var,void,volatile,while,with,yield",
|
||||
|
||||
// commonjs/amd
|
||||
"module,__dirname,__filename,exports,require,define",
|
||||
|
||||
// js globals
|
||||
"Array,Date,eval,function,hasOwnProperty,Infinity,isFinite,isNaN,isPrototypeOf,length,Math",
|
||||
"NaN,name,Number,Object,prototype,String,Symbol,toString,undefined,valueOf",
|
||||
|
||||
// browser globals
|
||||
"alert,all,anchor,anchors,area,assign,blur,button,checkbox,clearInterval,clearTimeout",
|
||||
"clientInformation,close,closed,confirm,constructor,crypto,decodeURI,decodeURIComponent",
|
||||
"defaultStatus,document,element,elements,embed,embeds,encodeURI,encodeURIComponent,escape",
|
||||
"event,fileUpload,focus,form,forms,frame,innerHeight,innerWidth,layer,layers,link,location",
|
||||
"mimeTypes,navigate,navigator,frames,frameRate,hidden,history,image,images,offscreenBuffering",
|
||||
"open,opener,option,outerHeight,outerWidth,packages,pageXOffset,pageYOffset,parent,parseFloat",
|
||||
"parseInt,password,pkcs11,plugin,prompt,propertyIsEnum,radio,reset,screenX,screenY,scroll",
|
||||
"secure,select,self,setInterval,setTimeout,status,submit,taint,text,textarea,top,unescape",
|
||||
"untaint,window",
|
||||
|
||||
// window events
|
||||
"onblur,onclick,onerror,onfocus,onkeydown,onkeypress,onkeyup,onmouseover,onload,onmouseup,onmousedown,onsubmit"
|
||||
]
|
||||
.join(",")
|
||||
.split(",")
|
||||
);
|
||||
|
||||
/**
|
||||
* @param {Map<string, { usedNames: UsedNames, alreadyCheckedScopes: Set<TODO> }>} usedNamesInScopeInfo used names in scope info
|
||||
* @param {string} module module identifier
|
||||
* @param {string} id export id
|
||||
* @returns {{ usedNames: UsedNames, alreadyCheckedScopes: Set<TODO> }} info
|
||||
*/
|
||||
const getUsedNamesInScopeInfo = (usedNamesInScopeInfo, module, id) => {
|
||||
const key = `${module}-${id}`;
|
||||
let info = usedNamesInScopeInfo.get(key);
|
||||
if (info === undefined) {
|
||||
info = {
|
||||
usedNames: new Set(),
|
||||
alreadyCheckedScopes: new Set()
|
||||
};
|
||||
usedNamesInScopeInfo.set(key, info);
|
||||
}
|
||||
return info;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getUsedNamesInScopeInfo,
|
||||
findNewName,
|
||||
getAllReferences,
|
||||
getPathInAst,
|
||||
NAMESPACE_OBJECT_EXPORT,
|
||||
DEFAULT_EXPORT,
|
||||
RESERVED_NAMES,
|
||||
addScopeSymbols
|
||||
};
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
MIT License http://www.opensource.org/licenses/mit-license.php
|
||||
Author Tobias Koppers @sokra
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/** @typedef {import("eslint-scope").Reference} Reference */
|
||||
/** @typedef {import("eslint-scope").Variable} Variable */
|
||||
/** @typedef {import("estree").Node} Node */
|
||||
/** @typedef {import("estree").Program} Program */
|
||||
/** @typedef {import("../javascript/JavascriptParser").Range} Range */
|
||||
|
||||
/**
|
||||
* @param {Variable} variable variable
|
||||
* @returns {Reference[]} references
|
||||
*/
|
||||
const getAllReferences = variable => {
|
||||
let set = variable.references;
|
||||
// Look for inner scope variables too (like in class Foo { t() { Foo } })
|
||||
const identifiers = new Set(variable.identifiers);
|
||||
for (const scope of variable.scope.childScopes) {
|
||||
for (const innerVar of scope.variables) {
|
||||
if (innerVar.identifiers.some(id => identifiers.has(id))) {
|
||||
set = set.concat(innerVar.references);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return set;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Node | Node[]} ast ast
|
||||
* @param {Node} node node
|
||||
* @returns {undefined | Node[]} result
|
||||
*/
|
||||
const getPathInAst = (ast, node) => {
|
||||
if (ast === node) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const nr = /** @type {Range} */ (node.range);
|
||||
|
||||
/**
|
||||
* @param {Node} n node
|
||||
* @returns {Node[] | undefined} result
|
||||
*/
|
||||
const enterNode = n => {
|
||||
if (!n) return;
|
||||
const r = n.range;
|
||||
if (r && r[0] <= nr[0] && r[1] >= nr[1]) {
|
||||
const path = getPathInAst(n, node);
|
||||
if (path) {
|
||||
path.push(n);
|
||||
return path;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (Array.isArray(ast)) {
|
||||
for (let i = 0; i < ast.length; i++) {
|
||||
const enterResult = enterNode(ast[i]);
|
||||
if (enterResult !== undefined) return enterResult;
|
||||
}
|
||||
} else if (ast && typeof ast === "object") {
|
||||
const keys =
|
||||
/** @type {Array<keyof Node>} */
|
||||
(Object.keys(ast));
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
// We are making the faster check in `enterNode` using `n.range`
|
||||
const value =
|
||||
ast[
|
||||
/** @type {Exclude<keyof Node, "range" | "loc" | "leadingComments" | "trailingComments">} */
|
||||
(keys[i])
|
||||
];
|
||||
if (Array.isArray(value)) {
|
||||
const pathResult = getPathInAst(value, node);
|
||||
if (pathResult !== undefined) return pathResult;
|
||||
} else if (value && typeof value === "object") {
|
||||
const enterResult = enterNode(value);
|
||||
if (enterResult !== undefined) return enterResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { getAllReferences, getPathInAst };
|
|
@ -1,16 +1,25 @@
|
|||
import { value as value1 } from './module1'
|
||||
const value2 = require('./module2')
|
||||
const value3 = require('./module3')
|
||||
const value4 = require('./module4')
|
||||
|
||||
let value = 42
|
||||
let src_value = 43
|
||||
let src_src_value = 44
|
||||
let Symbol = 'Symbol'
|
||||
|
||||
it('inlined module should not leak to non-inlined modules', () => {
|
||||
// The two variables are in nested scope and could be the candidate names for inline module during renaming.
|
||||
// The renaming logic should detect them and bypass to avoid the collisions.
|
||||
const index_src_value = -1
|
||||
const index_src_value_0 = -1
|
||||
|
||||
expect(value1).toBe(undefined)
|
||||
expect(value).toBe(42)
|
||||
expect(src_value).toBe(43)
|
||||
expect(src_src_value).toBe(44)
|
||||
expect(value2).toBe("undefined") // should not touch leaked `value` variable
|
||||
expect(value3).toBe("undefined") // should not touch leaked `src_value` variable
|
||||
expect(Symbol).toBe('Symbol')
|
||||
expect(value2).toBe("undefined") // Should not touch `value` variable in inline module.
|
||||
expect(value3).toBe("undefined") // Should not touch src_value` in inline module.
|
||||
expect(value4).toBe("function") // Module variables in inline module should not shadowling global variables.
|
||||
})
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
module.exports = typeof Symbol
|
|
@ -5628,11 +5628,6 @@ declare class JavascriptModulesPlugin {
|
|||
allStrict: boolean,
|
||||
hasChunkModules: boolean
|
||||
): false | Map<Module, Source>;
|
||||
findNewName(
|
||||
oldName: string,
|
||||
usedName: Set<string>,
|
||||
extraInfo: string
|
||||
): string;
|
||||
static getCompilationHooks(
|
||||
compilation: Compilation
|
||||
): CompilationHooksJavascriptModulesPlugin;
|
||||
|
|
Loading…
Reference in New Issue