mirror of https://github.com/webpack/webpack.git
fix: distinguish free variable and tagged variable (#19795)
This commit is contained in:
parent
61a15a672e
commit
cbfba9a150
|
@ -54,8 +54,7 @@ class JavascriptMetaInfoPlugin {
|
||||||
topLevelDeclarations = buildInfo.topLevelDeclarations = new Set();
|
topLevelDeclarations = buildInfo.topLevelDeclarations = new Set();
|
||||||
}
|
}
|
||||||
for (const name of parser.scope.definitions.asSet()) {
|
for (const name of parser.scope.definitions.asSet()) {
|
||||||
const freeInfo = parser.getFreeInfoFromVariable(name);
|
if (parser.isVariableDefined(name)) {
|
||||||
if (freeInfo === undefined) {
|
|
||||||
topLevelDeclarations.add(name);
|
topLevelDeclarations.add(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -258,17 +258,42 @@ const getImportAttributes = (node) => {
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @typedef {typeof VariableInfoFlags.Evaluated | typeof VariableInfoFlags.Free | typeof VariableInfoFlags.Normal | typeof VariableInfoFlags.Tagged} VariableInfoFlagsType */
|
||||||
|
|
||||||
|
const VariableInfoFlags = Object.freeze({
|
||||||
|
Evaluated: 0b000,
|
||||||
|
Free: 0b001,
|
||||||
|
Normal: 0b010,
|
||||||
|
Tagged: 0b100
|
||||||
|
});
|
||||||
|
|
||||||
class VariableInfo {
|
class VariableInfo {
|
||||||
/**
|
/**
|
||||||
* @param {ScopeInfo} declaredScope scope in which the variable is declared
|
* @param {ScopeInfo} declaredScope scope in which the variable is declared
|
||||||
* @param {string | true | undefined} freeName which free name the variable aliases, or true when none
|
* @param {string | undefined} name which name the variable use, defined name or free name or tagged name
|
||||||
|
* @param {VariableInfoFlagsType} flags how the variable is created
|
||||||
* @param {TagInfo | undefined} tagInfo info about tags
|
* @param {TagInfo | undefined} tagInfo info about tags
|
||||||
*/
|
*/
|
||||||
constructor(declaredScope, freeName, tagInfo) {
|
constructor(declaredScope, name, flags, tagInfo) {
|
||||||
this.declaredScope = declaredScope;
|
this.declaredScope = declaredScope;
|
||||||
this.freeName = freeName;
|
this.name = name;
|
||||||
|
this.flags = flags;
|
||||||
this.tagInfo = tagInfo;
|
this.tagInfo = tagInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {boolean} the variable is free or not
|
||||||
|
*/
|
||||||
|
isFree() {
|
||||||
|
return (this.flags & VariableInfoFlags.Free) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {boolean} the variable is tagged by tagVariable or not
|
||||||
|
*/
|
||||||
|
isTagged() {
|
||||||
|
return (this.flags & VariableInfoFlags.Tagged) > 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @typedef {string | ScopeInfo | VariableInfo} ExportedVariableInfo */
|
/** @typedef {string | ScopeInfo | VariableInfo} ExportedVariableInfo */
|
||||||
|
@ -1426,7 +1451,7 @@ class JavascriptParser extends Parser {
|
||||||
const info = this.getVariableInfo(/** @type {Identifier} */ (expr).name);
|
const info = this.getVariableInfo(/** @type {Identifier} */ (expr).name);
|
||||||
if (
|
if (
|
||||||
typeof info === "string" ||
|
typeof info === "string" ||
|
||||||
(info instanceof VariableInfo && typeof info.freeName === "string")
|
(info instanceof VariableInfo && (info.isFree() || info.isTagged()))
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
name: info,
|
name: info,
|
||||||
|
@ -1441,7 +1466,7 @@ class JavascriptParser extends Parser {
|
||||||
const info = this.getVariableInfo("this");
|
const info = this.getVariableInfo("this");
|
||||||
if (
|
if (
|
||||||
typeof info === "string" ||
|
typeof info === "string" ||
|
||||||
(info instanceof VariableInfo && typeof info.freeName === "string")
|
(info instanceof VariableInfo && (info.isFree() || info.isTagged()))
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
name: info,
|
name: info,
|
||||||
|
@ -4053,13 +4078,13 @@ class JavascriptParser extends Parser {
|
||||||
}
|
}
|
||||||
tagInfo = tagInfo.next;
|
tagInfo = tagInfo.next;
|
||||||
}
|
}
|
||||||
if (info.freeName === true) {
|
if (!info.isFree() && !info.isTagged()) {
|
||||||
if (defined !== undefined) {
|
if (defined !== undefined) {
|
||||||
return defined();
|
return defined();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
name = info.freeName;
|
name = info.name;
|
||||||
}
|
}
|
||||||
const hook = hookMap.get(name);
|
const hook = hookMap.get(name);
|
||||||
if (hook !== undefined) {
|
if (hook !== undefined) {
|
||||||
|
@ -4818,25 +4843,31 @@ class JavascriptParser extends Parser {
|
||||||
* @param {string} name name
|
* @param {string} name name
|
||||||
* @param {Tag} tag tag info
|
* @param {Tag} tag tag info
|
||||||
* @param {TagData=} data data
|
* @param {TagData=} data data
|
||||||
|
* @param {VariableInfoFlagsType=} flags flags
|
||||||
*/
|
*/
|
||||||
tagVariable(name, tag, data) {
|
tagVariable(name, tag, data, flags = VariableInfoFlags.Tagged) {
|
||||||
const oldInfo = this.scope.definitions.get(name);
|
const oldInfo = this.scope.definitions.get(name);
|
||||||
/** @type {VariableInfo} */
|
/** @type {VariableInfo} */
|
||||||
let newInfo;
|
let newInfo;
|
||||||
if (oldInfo === undefined) {
|
if (oldInfo === undefined) {
|
||||||
newInfo = new VariableInfo(this.scope, name, {
|
newInfo = new VariableInfo(this.scope, name, flags, {
|
||||||
tag,
|
tag,
|
||||||
data,
|
data,
|
||||||
next: undefined
|
next: undefined
|
||||||
});
|
});
|
||||||
} else if (oldInfo instanceof VariableInfo) {
|
} else if (oldInfo instanceof VariableInfo) {
|
||||||
newInfo = new VariableInfo(oldInfo.declaredScope, oldInfo.freeName, {
|
newInfo = new VariableInfo(
|
||||||
tag,
|
oldInfo.declaredScope,
|
||||||
data,
|
oldInfo.name,
|
||||||
next: oldInfo.tagInfo
|
/** @type {VariableInfoFlagsType} */ (oldInfo.flags | flags),
|
||||||
});
|
{
|
||||||
|
tag,
|
||||||
|
data,
|
||||||
|
next: oldInfo.tagInfo
|
||||||
|
}
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
newInfo = new VariableInfo(oldInfo, true, {
|
newInfo = new VariableInfo(oldInfo, name, flags, {
|
||||||
tag,
|
tag,
|
||||||
data,
|
data,
|
||||||
next: undefined
|
next: undefined
|
||||||
|
@ -4875,7 +4906,7 @@ class JavascriptParser extends Parser {
|
||||||
const info = this.scope.definitions.get(name);
|
const info = this.scope.definitions.get(name);
|
||||||
if (info === undefined) return false;
|
if (info === undefined) return false;
|
||||||
if (info instanceof VariableInfo) {
|
if (info instanceof VariableInfo) {
|
||||||
return info.freeName === true;
|
return !info.isFree();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -4904,7 +4935,12 @@ class JavascriptParser extends Parser {
|
||||||
} else {
|
} else {
|
||||||
this.scope.definitions.set(
|
this.scope.definitions.set(
|
||||||
name,
|
name,
|
||||||
new VariableInfo(this.scope, variableInfo, undefined)
|
new VariableInfo(
|
||||||
|
this.scope,
|
||||||
|
variableInfo,
|
||||||
|
VariableInfoFlags.Free,
|
||||||
|
undefined
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -4917,7 +4953,12 @@ class JavascriptParser extends Parser {
|
||||||
* @returns {VariableInfo} variable info
|
* @returns {VariableInfo} variable info
|
||||||
*/
|
*/
|
||||||
evaluatedVariable(tagInfo) {
|
evaluatedVariable(tagInfo) {
|
||||||
return new VariableInfo(this.scope, undefined, tagInfo);
|
return new VariableInfo(
|
||||||
|
this.scope,
|
||||||
|
undefined,
|
||||||
|
VariableInfoFlags.Evaluated,
|
||||||
|
tagInfo
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -5002,9 +5043,27 @@ class JavascriptParser extends Parser {
|
||||||
getFreeInfoFromVariable(varName) {
|
getFreeInfoFromVariable(varName) {
|
||||||
const info = this.getVariableInfo(varName);
|
const info = this.getVariableInfo(varName);
|
||||||
let name;
|
let name;
|
||||||
if (info instanceof VariableInfo) {
|
if (info instanceof VariableInfo && info.name) {
|
||||||
name = info.freeName;
|
if (!info.isFree()) return;
|
||||||
if (typeof name !== "string") return;
|
name = info.name;
|
||||||
|
} else if (typeof info !== "string") {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
name = info;
|
||||||
|
}
|
||||||
|
return { info, name };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} varName variable name
|
||||||
|
* @returns {{name: string, info: VariableInfo | string} | undefined} name of the free variable and variable info for that
|
||||||
|
*/
|
||||||
|
getNameInfoFromVariable(varName) {
|
||||||
|
const info = this.getVariableInfo(varName);
|
||||||
|
let name;
|
||||||
|
if (info instanceof VariableInfo && info.name) {
|
||||||
|
if (!info.isFree() && !info.isTagged()) return;
|
||||||
|
name = info.name;
|
||||||
} else if (typeof info !== "string") {
|
} else if (typeof info !== "string") {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
@ -5035,7 +5094,7 @@ class JavascriptParser extends Parser {
|
||||||
}
|
}
|
||||||
const rootName = getRootName(callee);
|
const rootName = getRootName(callee);
|
||||||
if (!rootName) return;
|
if (!rootName) return;
|
||||||
const result = this.getFreeInfoFromVariable(rootName);
|
const result = this.getNameInfoFromVariable(rootName);
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
const { info: rootInfo, name: resolvedRoot } = result;
|
const { info: rootInfo, name: resolvedRoot } = result;
|
||||||
const calleeName = objectAndMembersToName(resolvedRoot, rootMembers);
|
const calleeName = objectAndMembersToName(resolvedRoot, rootMembers);
|
||||||
|
@ -5058,7 +5117,7 @@ class JavascriptParser extends Parser {
|
||||||
const rootName = getRootName(object);
|
const rootName = getRootName(object);
|
||||||
if (!rootName) return;
|
if (!rootName) return;
|
||||||
|
|
||||||
const result = this.getFreeInfoFromVariable(rootName);
|
const result = this.getNameInfoFromVariable(rootName);
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
const { info: rootInfo, name: resolvedRoot } = result;
|
const { info: rootInfo, name: resolvedRoot } = result;
|
||||||
return {
|
return {
|
||||||
|
@ -5151,4 +5210,5 @@ module.exports.ALLOWED_MEMBER_TYPES_CALL_EXPRESSION =
|
||||||
module.exports.ALLOWED_MEMBER_TYPES_EXPRESSION =
|
module.exports.ALLOWED_MEMBER_TYPES_EXPRESSION =
|
||||||
ALLOWED_MEMBER_TYPES_EXPRESSION;
|
ALLOWED_MEMBER_TYPES_EXPRESSION;
|
||||||
module.exports.VariableInfo = VariableInfo;
|
module.exports.VariableInfo = VariableInfo;
|
||||||
|
module.exports.VariableInfoFlags = VariableInfoFlags;
|
||||||
module.exports.getImportAttributes = getImportAttributes;
|
module.exports.getImportAttributes = getImportAttributes;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const { UsageState } = require("../ExportsInfo");
|
const { UsageState } = require("../ExportsInfo");
|
||||||
|
const JavascriptParser = require("../javascript/JavascriptParser");
|
||||||
|
|
||||||
/** @typedef {import("estree").Node} AnyNode */
|
/** @typedef {import("estree").Node} AnyNode */
|
||||||
/** @typedef {import("../Dependency")} Dependency */
|
/** @typedef {import("../Dependency")} Dependency */
|
||||||
|
@ -15,7 +16,6 @@ const { UsageState } = require("../ExportsInfo");
|
||||||
/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */
|
/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */
|
||||||
/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */
|
/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */
|
||||||
/** @typedef {import("../Parser").ParserState} ParserState */
|
/** @typedef {import("../Parser").ParserState} ParserState */
|
||||||
/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
|
|
||||||
/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
|
/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
|
||||||
|
|
||||||
/** @typedef {Map<TopLevelSymbol | null, Set<string | TopLevelSymbol> | true | undefined>} InnerGraph */
|
/** @typedef {Map<TopLevelSymbol | null, Set<string | TopLevelSymbol> | true | undefined>} InnerGraph */
|
||||||
|
@ -348,7 +348,12 @@ module.exports.tagTopLevelSymbol = (parser, name) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn = new TopLevelSymbol(name);
|
const fn = new TopLevelSymbol(name);
|
||||||
parser.tagVariable(name, topLevelSymbolTag, fn);
|
parser.tagVariable(
|
||||||
|
name,
|
||||||
|
topLevelSymbolTag,
|
||||||
|
fn,
|
||||||
|
JavascriptParser.VariableInfoFlags.Normal
|
||||||
|
);
|
||||||
return fn;
|
return fn;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export const a = 1;
|
|
@ -0,0 +1 @@
|
||||||
|
exports.b = 2;
|
|
@ -0,0 +1 @@
|
||||||
|
export const c = 3;
|
|
@ -0,0 +1 @@
|
||||||
|
export const d = 4;
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { a } from "./a";
|
||||||
|
import { createRequire } from "module";
|
||||||
|
|
||||||
|
const myRequire = createRequire(import.meta.url);
|
||||||
|
const { b } = myRequire("./b");
|
||||||
|
const c = new URL("./c.js", import.meta.url);
|
||||||
|
const audioContext = new AudioContext();
|
||||||
|
const d = audioContext.audioWorklet.addModule(new URL("./d.js", import.meta.url));
|
||||||
|
|
||||||
|
it("should have correct top level declarations", async () => {
|
||||||
|
await d;
|
||||||
|
expect(a).toBe(1);
|
||||||
|
expect(b).toBe(2);
|
||||||
|
expect(c.pathname.endsWith(".js")).toBe(true);
|
||||||
|
})
|
|
@ -0,0 +1,23 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
let outputDirectory;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
moduleScope(scope) {
|
||||||
|
const FakeWorker = require("../../../helpers/createFakeWorker")({
|
||||||
|
outputDirectory
|
||||||
|
});
|
||||||
|
|
||||||
|
scope.AudioContext = class AudioContext {
|
||||||
|
constructor() {
|
||||||
|
this.audioWorklet = {
|
||||||
|
addModule: (url) => Promise.resolve(FakeWorker.bind(null, url))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
findBundle(i, options) {
|
||||||
|
outputDirectory = options.output.path;
|
||||||
|
return ["main.js"];
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,5 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const supportsWorker = require("../../../helpers/supportsWorker");
|
||||||
|
|
||||||
|
module.exports = () => supportsWorker();
|
|
@ -0,0 +1,44 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/** @type {import("../../../../").Configuration} */
|
||||||
|
module.exports = {
|
||||||
|
target: "web",
|
||||||
|
output: {
|
||||||
|
filename: "[name].js"
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
parser: {
|
||||||
|
javascript: {
|
||||||
|
createRequire: true,
|
||||||
|
worker: ["*audioContext.audioWorklet.addModule()"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
function testPlugin(compiler) {
|
||||||
|
compiler.hooks.finishMake.tap("test", (compilation) => {
|
||||||
|
for (const module of compilation.modules) {
|
||||||
|
const name = module.nameForCondition();
|
||||||
|
const topLevelDeclarations =
|
||||||
|
module.buildInfo && module.buildInfo.topLevelDeclarations;
|
||||||
|
if (
|
||||||
|
name &&
|
||||||
|
name.includes("top-level-declarations/index.js") &&
|
||||||
|
topLevelDeclarations
|
||||||
|
) {
|
||||||
|
const expectedTopLevelDeclarations = new Set([
|
||||||
|
"a",
|
||||||
|
"createRequire",
|
||||||
|
"myRequire",
|
||||||
|
"b",
|
||||||
|
"c",
|
||||||
|
"audioContext",
|
||||||
|
"d"
|
||||||
|
]);
|
||||||
|
expect(topLevelDeclarations).toEqual(expectedTopLevelDeclarations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
|
@ -7716,7 +7716,12 @@ declare class JavascriptParser extends ParserClass {
|
||||||
unsetAsiPosition(pos: number): void;
|
unsetAsiPosition(pos: number): void;
|
||||||
isStatementLevelExpression(expr: Expression): boolean;
|
isStatementLevelExpression(expr: Expression): boolean;
|
||||||
getTagData(name: string, tag: symbol): undefined | TagData;
|
getTagData(name: string, tag: symbol): undefined | TagData;
|
||||||
tagVariable(name: string, tag: symbol, data?: TagData): void;
|
tagVariable(
|
||||||
|
name: string,
|
||||||
|
tag: symbol,
|
||||||
|
data?: TagData,
|
||||||
|
flags?: 0 | 1 | 2 | 4
|
||||||
|
): void;
|
||||||
defineVariable(name: string): void;
|
defineVariable(name: string): void;
|
||||||
undefineVariable(name: string): void;
|
undefineVariable(name: string): void;
|
||||||
isVariableDefined(name: string): boolean;
|
isVariableDefined(name: string): boolean;
|
||||||
|
@ -7794,6 +7799,9 @@ declare class JavascriptParser extends ParserClass {
|
||||||
getFreeInfoFromVariable(
|
getFreeInfoFromVariable(
|
||||||
varName: string
|
varName: string
|
||||||
): undefined | { name: string; info: string | VariableInfo };
|
): undefined | { name: string; info: string | VariableInfo };
|
||||||
|
getNameInfoFromVariable(
|
||||||
|
varName: string
|
||||||
|
): undefined | { name: string; info: string | VariableInfo };
|
||||||
getMemberExpressionInfo(
|
getMemberExpressionInfo(
|
||||||
expression:
|
expression:
|
||||||
| ImportExpressionImport
|
| ImportExpressionImport
|
||||||
|
@ -7842,6 +7850,12 @@ declare class JavascriptParser extends ParserClass {
|
||||||
static ALLOWED_MEMBER_TYPES_CALL_EXPRESSION: 1;
|
static ALLOWED_MEMBER_TYPES_CALL_EXPRESSION: 1;
|
||||||
static ALLOWED_MEMBER_TYPES_EXPRESSION: 2;
|
static ALLOWED_MEMBER_TYPES_EXPRESSION: 2;
|
||||||
static VariableInfo: typeof VariableInfo;
|
static VariableInfo: typeof VariableInfo;
|
||||||
|
static VariableInfoFlags: Readonly<{
|
||||||
|
Evaluated: 0;
|
||||||
|
Free: 1;
|
||||||
|
Normal: 2;
|
||||||
|
Tagged: 4;
|
||||||
|
}>;
|
||||||
static getImportAttributes: (
|
static getImportAttributes: (
|
||||||
node:
|
node:
|
||||||
| ImportDeclarationJavascriptParser
|
| ImportDeclarationJavascriptParser
|
||||||
|
@ -16949,13 +16963,18 @@ declare interface Values {
|
||||||
declare class VariableInfo {
|
declare class VariableInfo {
|
||||||
constructor(
|
constructor(
|
||||||
declaredScope: ScopeInfo,
|
declaredScope: ScopeInfo,
|
||||||
freeName?: string | true,
|
name: undefined | string,
|
||||||
|
flags: VariableInfoFlagsType,
|
||||||
tagInfo?: TagInfo
|
tagInfo?: TagInfo
|
||||||
);
|
);
|
||||||
declaredScope: ScopeInfo;
|
declaredScope: ScopeInfo;
|
||||||
freeName?: string | true;
|
name?: string;
|
||||||
|
flags: VariableInfoFlagsType;
|
||||||
tagInfo?: TagInfo;
|
tagInfo?: TagInfo;
|
||||||
|
isFree(): boolean;
|
||||||
|
isTagged(): boolean;
|
||||||
}
|
}
|
||||||
|
type VariableInfoFlagsType = 0 | 1 | 2 | 4;
|
||||||
declare interface VirtualModuleConfig {
|
declare interface VirtualModuleConfig {
|
||||||
/**
|
/**
|
||||||
* - The module type
|
* - The module type
|
||||||
|
|
Loading…
Reference in New Issue