Skip to content

Commit cbfba9a

Browse files
authored
fix: distinguish free variable and tagged variable (#19795)
1 parent 61a15a6 commit cbfba9a

File tree

12 files changed

+204
-30
lines changed

12 files changed

+204
-30
lines changed

lib/JavascriptMetaInfoPlugin.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,7 @@ class JavascriptMetaInfoPlugin {
5454
topLevelDeclarations = buildInfo.topLevelDeclarations = new Set();
5555
}
5656
for (const name of parser.scope.definitions.asSet()) {
57-
const freeInfo = parser.getFreeInfoFromVariable(name);
58-
if (freeInfo === undefined) {
57+
if (parser.isVariableDefined(name)) {
5958
topLevelDeclarations.add(name);
6059
}
6160
}

lib/javascript/JavascriptParser.js

Lines changed: 83 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -258,17 +258,42 @@ const getImportAttributes = (node) => {
258258
return result;
259259
};
260260

261+
/** @typedef {typeof VariableInfoFlags.Evaluated | typeof VariableInfoFlags.Free | typeof VariableInfoFlags.Normal | typeof VariableInfoFlags.Tagged} VariableInfoFlagsType */
262+
263+
const VariableInfoFlags = Object.freeze({
264+
Evaluated: 0b000,
265+
Free: 0b001,
266+
Normal: 0b010,
267+
Tagged: 0b100
268+
});
269+
261270
class VariableInfo {
262271
/**
263272
* @param {ScopeInfo} declaredScope scope in which the variable is declared
264-
* @param {string | true | undefined} freeName which free name the variable aliases, or true when none
273+
* @param {string | undefined} name which name the variable use, defined name or free name or tagged name
274+
* @param {VariableInfoFlagsType} flags how the variable is created
265275
* @param {TagInfo | undefined} tagInfo info about tags
266276
*/
267-
constructor(declaredScope, freeName, tagInfo) {
277+
constructor(declaredScope, name, flags, tagInfo) {
268278
this.declaredScope = declaredScope;
269-
this.freeName = freeName;
279+
this.name = name;
280+
this.flags = flags;
270281
this.tagInfo = tagInfo;
271282
}
283+
284+
/**
285+
* @returns {boolean} the variable is free or not
286+
*/
287+
isFree() {
288+
return (this.flags & VariableInfoFlags.Free) > 0;
289+
}
290+
291+
/**
292+
* @returns {boolean} the variable is tagged by tagVariable or not
293+
*/
294+
isTagged() {
295+
return (this.flags & VariableInfoFlags.Tagged) > 0;
296+
}
272297
}
273298

274299
/** @typedef {string | ScopeInfo | VariableInfo} ExportedVariableInfo */
@@ -1426,7 +1451,7 @@ class JavascriptParser extends Parser {
14261451
const info = this.getVariableInfo(/** @type {Identifier} */ (expr).name);
14271452
if (
14281453
typeof info === "string" ||
1429-
(info instanceof VariableInfo && typeof info.freeName === "string")
1454+
(info instanceof VariableInfo && (info.isFree() || info.isTagged()))
14301455
) {
14311456
return {
14321457
name: info,
@@ -1441,7 +1466,7 @@ class JavascriptParser extends Parser {
14411466
const info = this.getVariableInfo("this");
14421467
if (
14431468
typeof info === "string" ||
1444-
(info instanceof VariableInfo && typeof info.freeName === "string")
1469+
(info instanceof VariableInfo && (info.isFree() || info.isTagged()))
14451470
) {
14461471
return {
14471472
name: info,
@@ -4053,13 +4078,13 @@ class JavascriptParser extends Parser {
40534078
}
40544079
tagInfo = tagInfo.next;
40554080
}
4056-
if (info.freeName === true) {
4081+
if (!info.isFree() && !info.isTagged()) {
40574082
if (defined !== undefined) {
40584083
return defined();
40594084
}
40604085
return;
40614086
}
4062-
name = info.freeName;
4087+
name = info.name;
40634088
}
40644089
const hook = hookMap.get(name);
40654090
if (hook !== undefined) {
@@ -4818,25 +4843,31 @@ class JavascriptParser extends Parser {
48184843
* @param {string} name name
48194844
* @param {Tag} tag tag info
48204845
* @param {TagData=} data data
4846+
* @param {VariableInfoFlagsType=} flags flags
48214847
*/
4822-
tagVariable(name, tag, data) {
4848+
tagVariable(name, tag, data, flags = VariableInfoFlags.Tagged) {
48234849
const oldInfo = this.scope.definitions.get(name);
48244850
/** @type {VariableInfo} */
48254851
let newInfo;
48264852
if (oldInfo === undefined) {
4827-
newInfo = new VariableInfo(this.scope, name, {
4853+
newInfo = new VariableInfo(this.scope, name, flags, {
48284854
tag,
48294855
data,
48304856
next: undefined
48314857
});
48324858
} else if (oldInfo instanceof VariableInfo) {
4833-
newInfo = new VariableInfo(oldInfo.declaredScope, oldInfo.freeName, {
4834-
tag,
4835-
data,
4836-
next: oldInfo.tagInfo
4837-
});
4859+
newInfo = new VariableInfo(
4860+
oldInfo.declaredScope,
4861+
oldInfo.name,
4862+
/** @type {VariableInfoFlagsType} */ (oldInfo.flags | flags),
4863+
{
4864+
tag,
4865+
data,
4866+
next: oldInfo.tagInfo
4867+
}
4868+
);
48384869
} else {
4839-
newInfo = new VariableInfo(oldInfo, true, {
4870+
newInfo = new VariableInfo(oldInfo, name, flags, {
48404871
tag,
48414872
data,
48424873
next: undefined
@@ -4875,7 +4906,7 @@ class JavascriptParser extends Parser {
48754906
const info = this.scope.definitions.get(name);
48764907
if (info === undefined) return false;
48774908
if (info instanceof VariableInfo) {
4878-
return info.freeName === true;
4909+
return !info.isFree();
48794910
}
48804911
return true;
48814912
}
@@ -4904,7 +4935,12 @@ class JavascriptParser extends Parser {
49044935
} else {
49054936
this.scope.definitions.set(
49064937
name,
4907-
new VariableInfo(this.scope, variableInfo, undefined)
4938+
new VariableInfo(
4939+
this.scope,
4940+
variableInfo,
4941+
VariableInfoFlags.Free,
4942+
undefined
4943+
)
49084944
);
49094945
}
49104946
} else {
@@ -4917,7 +4953,12 @@ class JavascriptParser extends Parser {
49174953
* @returns {VariableInfo} variable info
49184954
*/
49194955
evaluatedVariable(tagInfo) {
4920-
return new VariableInfo(this.scope, undefined, tagInfo);
4956+
return new VariableInfo(
4957+
this.scope,
4958+
undefined,
4959+
VariableInfoFlags.Evaluated,
4960+
tagInfo
4961+
);
49214962
}
49224963

49234964
/**
@@ -5002,9 +5043,27 @@ class JavascriptParser extends Parser {
50025043
getFreeInfoFromVariable(varName) {
50035044
const info = this.getVariableInfo(varName);
50045045
let name;
5005-
if (info instanceof VariableInfo) {
5006-
name = info.freeName;
5007-
if (typeof name !== "string") return;
5046+
if (info instanceof VariableInfo && info.name) {
5047+
if (!info.isFree()) return;
5048+
name = info.name;
5049+
} else if (typeof info !== "string") {
5050+
return;
5051+
} else {
5052+
name = info;
5053+
}
5054+
return { info, name };
5055+
}
5056+
5057+
/**
5058+
* @param {string} varName variable name
5059+
* @returns {{name: string, info: VariableInfo | string} | undefined} name of the free variable and variable info for that
5060+
*/
5061+
getNameInfoFromVariable(varName) {
5062+
const info = this.getVariableInfo(varName);
5063+
let name;
5064+
if (info instanceof VariableInfo && info.name) {
5065+
if (!info.isFree() && !info.isTagged()) return;
5066+
name = info.name;
50085067
} else if (typeof info !== "string") {
50095068
return;
50105069
} else {
@@ -5035,7 +5094,7 @@ class JavascriptParser extends Parser {
50355094
}
50365095
const rootName = getRootName(callee);
50375096
if (!rootName) return;
5038-
const result = this.getFreeInfoFromVariable(rootName);
5097+
const result = this.getNameInfoFromVariable(rootName);
50395098
if (!result) return;
50405099
const { info: rootInfo, name: resolvedRoot } = result;
50415100
const calleeName = objectAndMembersToName(resolvedRoot, rootMembers);
@@ -5058,7 +5117,7 @@ class JavascriptParser extends Parser {
50585117
const rootName = getRootName(object);
50595118
if (!rootName) return;
50605119

5061-
const result = this.getFreeInfoFromVariable(rootName);
5120+
const result = this.getNameInfoFromVariable(rootName);
50625121
if (!result) return;
50635122
const { info: rootInfo, name: resolvedRoot } = result;
50645123
return {
@@ -5151,4 +5210,5 @@ module.exports.ALLOWED_MEMBER_TYPES_CALL_EXPRESSION =
51515210
module.exports.ALLOWED_MEMBER_TYPES_EXPRESSION =
51525211
ALLOWED_MEMBER_TYPES_EXPRESSION;
51535212
module.exports.VariableInfo = VariableInfo;
5213+
module.exports.VariableInfoFlags = VariableInfoFlags;
51545214
module.exports.getImportAttributes = getImportAttributes;

lib/optimize/InnerGraph.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"use strict";
77

88
const { UsageState } = require("../ExportsInfo");
9+
const JavascriptParser = require("../javascript/JavascriptParser");
910

1011
/** @typedef {import("estree").Node} AnyNode */
1112
/** @typedef {import("../Dependency")} Dependency */
@@ -15,7 +16,6 @@ const { UsageState } = require("../ExportsInfo");
1516
/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */
1617
/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */
1718
/** @typedef {import("../Parser").ParserState} ParserState */
18-
/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
1919
/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
2020

2121
/** @typedef {Map<TopLevelSymbol | null, Set<string | TopLevelSymbol> | true | undefined>} InnerGraph */
@@ -348,7 +348,12 @@ module.exports.tagTopLevelSymbol = (parser, name) => {
348348
}
349349

350350
const fn = new TopLevelSymbol(name);
351-
parser.tagVariable(name, topLevelSymbolTag, fn);
351+
parser.tagVariable(
352+
name,
353+
topLevelSymbolTag,
354+
fn,
355+
JavascriptParser.VariableInfoFlags.Normal
356+
);
352357
return fn;
353358
};
354359

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const a = 1;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
exports.b = 2;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const c = 3;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const d = 4;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { a } from "./a";
2+
import { createRequire } from "module";
3+
4+
const myRequire = createRequire(import.meta.url);
5+
const { b } = myRequire("./b");
6+
const c = new URL("./c.js", import.meta.url);
7+
const audioContext = new AudioContext();
8+
const d = audioContext.audioWorklet.addModule(new URL("./d.js", import.meta.url));
9+
10+
it("should have correct top level declarations", async () => {
11+
await d;
12+
expect(a).toBe(1);
13+
expect(b).toBe(2);
14+
expect(c.pathname.endsWith(".js")).toBe(true);
15+
})
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"use strict";
2+
3+
let outputDirectory;
4+
5+
module.exports = {
6+
moduleScope(scope) {
7+
const FakeWorker = require("../../../helpers/createFakeWorker")({
8+
outputDirectory
9+
});
10+
11+
scope.AudioContext = class AudioContext {
12+
constructor() {
13+
this.audioWorklet = {
14+
addModule: (url) => Promise.resolve(FakeWorker.bind(null, url))
15+
};
16+
}
17+
};
18+
},
19+
findBundle(i, options) {
20+
outputDirectory = options.output.path;
21+
return ["main.js"];
22+
}
23+
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"use strict";
2+
3+
const supportsWorker = require("../../../helpers/supportsWorker");
4+
5+
module.exports = () => supportsWorker();

0 commit comments

Comments
 (0)