webpack 5.108.2 → 5.108.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -245,7 +245,10 @@ const computeInterpolatedIdentifier = (
245
245
  * @property {boolean} interpolate whether the value needs interpolation
246
246
  * @property {ExportMode} exportMode export mode
247
247
  * @property {ExportType} exportType export type
248
- * @property {DependencyLocation=} loc per-export source location (for export source maps)
248
+ * @property {number} locStartLine per-export source start line (1-based; flat numbers instead of a nested `DependencyLocation` — entries are per-export hot allocations)
249
+ * @property {number} locStartColumn per-export source start column
250
+ * @property {number} locEndLine per-export source end line
251
+ * @property {number} locEndColumn per-export source end column
249
252
  * @property {string[]=} _conventionNames memoized convention names for `name`
250
253
  * @property {string[]=} _valueConventionNames memoized convention names for `value`
251
254
  */
@@ -412,8 +415,14 @@ class CssIcssExportDependency extends NullDependency {
412
415
  );
413
416
  error.module = module;
414
417
  // Per-entry loc so Compilation attributes the warning precisely
415
- // (it can't use the consolidated dependency's single loc).
416
- if (entry.loc) error.loc = entry.loc;
418
+ // (it can't use the consolidated dependency's single loc). Built
419
+ // on demand entries carry flat numbers, not location objects.
420
+ if (entry.locStartLine > 0) {
421
+ error.loc = {
422
+ start: { line: entry.locStartLine, column: entry.locStartColumn },
423
+ end: { line: entry.locEndLine, column: entry.locEndColumn }
424
+ };
425
+ }
417
426
  if (warnings === null) warnings = [];
418
427
  warnings.push(error);
419
428
  }
@@ -464,7 +473,10 @@ class CssIcssExportDependency extends NullDependency {
464
473
  write(entry.interpolate);
465
474
  write(entry.exportMode);
466
475
  write(entry.exportType);
467
- write(entry.loc);
476
+ write(entry.locStartLine);
477
+ write(entry.locStartColumn);
478
+ write(entry.locEndLine);
479
+ write(entry.locEndColumn);
468
480
  }
469
481
  super.serialize(context);
470
482
  }
@@ -486,7 +498,10 @@ class CssIcssExportDependency extends NullDependency {
486
498
  interpolate: /** @type {boolean} */ (read()),
487
499
  exportMode: /** @type {ExportMode} */ (read()),
488
500
  exportType: /** @type {ExportType} */ (read()),
489
- loc: /** @type {DependencyLocation=} */ (read())
501
+ locStartLine: /** @type {number} */ (read()),
502
+ locStartColumn: /** @type {number} */ (read()),
503
+ locEndLine: /** @type {number} */ (read()),
504
+ locEndColumn: /** @type {number} */ (read())
490
505
  });
491
506
  }
492
507
  this.entries = entries;
@@ -807,23 +822,20 @@ CssIcssExportDependency.Template = class CssIcssExportDependencyTemplate extends
807
822
  templateContext.runtimeTemplate.compilation.compiler.root
808
823
  );
809
824
 
810
- const depLocStart =
811
- entry.loc &&
812
- /** @type {{ start?: { line: number, column: number } }} */ (
813
- entry.loc
814
- ).start;
825
+ const depLocLine = entry.locStartLine;
826
+ const depLocColumn = entry.locStartColumn;
815
827
  for (const used of allNames) {
816
828
  if (entry.exportMode === EXPORT_MODE.ONCE) {
817
829
  if (cssData.exports.has(used)) continue;
818
830
  cssData.exports.set(used, unescaped);
819
831
  if (
820
- depLocStart &&
832
+ depLocLine > 0 &&
821
833
  cssData.exportLocs &&
822
834
  !cssData.exportLocs.has(used)
823
835
  ) {
824
836
  cssData.exportLocs.set(used, {
825
- line: depLocStart.line,
826
- column: depLocStart.column
837
+ line: depLocLine,
838
+ column: depLocColumn
827
839
  });
828
840
  }
829
841
  } else {
@@ -836,13 +848,13 @@ CssIcssExportDependency.Template = class CssIcssExportDependencyTemplate extends
836
848
  `${originalValue ? `${originalValue}${unescaped ? " " : ""}` : ""}${unescaped}`
837
849
  );
838
850
  if (
839
- depLocStart &&
851
+ depLocLine > 0 &&
840
852
  cssData.exportLocs &&
841
853
  !cssData.exportLocs.has(used)
842
854
  ) {
843
855
  cssData.exportLocs.set(used, {
844
- line: depLocStart.line,
845
- column: depLocStart.column
856
+ line: depLocLine,
857
+ column: depLocColumn
846
858
  });
847
859
  }
848
860
  }
@@ -19,8 +19,6 @@ const NullDependency = require("./NullDependency");
19
19
  /** @typedef {import("./HarmonyAcceptImportDependency")} HarmonyAcceptImportDependency */
20
20
  /** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext<[Range, HarmonyAcceptImportDependency[], boolean]>} ObjectDeserializerContext */
21
21
  /** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext<[Range, HarmonyAcceptImportDependency[], boolean]>} ObjectSerializerContext */
22
- /** @typedef {import("../Module")} Module */
23
- /** @typedef {import("../Module").ModuleId} ModuleId */
24
22
 
25
23
  class HarmonyAcceptDependency extends NullDependency {
26
24
  /**
@@ -91,26 +89,20 @@ HarmonyAcceptDependency.Template = class HarmonyAcceptDependencyTemplate extends
91
89
  chunkGraph
92
90
  } = templateContext;
93
91
 
94
- /**
95
- * Gets dependency module id.
96
- * @param {Dependency} dependency the dependency to get module id for
97
- * @returns {ModuleId | null} the module id or null if not found
98
- */
99
- const getDependencyModuleId = (dependency) =>
100
- chunkGraph.getModuleId(
101
- /** @type {Module} */ (moduleGraph.getModule(dependency))
102
- );
103
-
104
92
  /**
105
93
  * Checks whether this harmony accept dependency is related harmony import dependency.
106
94
  * @param {Dependency} a the first dependency
107
95
  * @param {Dependency} b the second dependency
108
96
  * @returns {boolean} true if the dependencies are related
109
97
  */
110
- const isRelatedHarmonyImportDependency = (a, b) =>
111
- a !== b &&
112
- b instanceof HarmonyImportDependency &&
113
- getDependencyModuleId(a) === getDependencyModuleId(b);
98
+ const isRelatedHarmonyImportDependency = (a, b) => {
99
+ if (a === b || !(b instanceof HarmonyImportDependency)) return false;
100
+ // Compare modules by reference: an unresolved import (ignored/failed, or a
101
+ // deferred lazy-barrel re-export) has no module, and a module not in any
102
+ // chunk has a null id — so comparing ids would crash or miss real matches.
103
+ const moduleA = moduleGraph.getModule(a);
104
+ return moduleA !== null && moduleA === moduleGraph.getModule(b);
105
+ };
114
106
 
115
107
  /**
116
108
  * HarmonyAcceptImportDependency lacks a lot of information, such as the defer property.
@@ -12,7 +12,6 @@ const {
12
12
  getImportAttributes
13
13
  } = require("../javascript/JavascriptParser");
14
14
  const { getInnerGraphUtils } = require("../optimize/InnerGraph");
15
- const memoize = require("../util/memoize");
16
15
  const ConstDependency = require("./ConstDependency");
17
16
  const HarmonyAcceptDependency = require("./HarmonyAcceptDependency");
18
17
  const HarmonyAcceptImportDependency = require("./HarmonyAcceptImportDependency");
@@ -22,6 +21,10 @@ const {
22
21
  ExportPresenceModes,
23
22
  getNonOptionalPart
24
23
  } = require("./HarmonyImportDependency");
24
+ const {
25
+ attachDependencyGuards,
26
+ isPresentByGuards
27
+ } = require("./HarmonyImportGuard");
25
28
  const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency");
26
29
  const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDependency");
27
30
  const { ImportPhaseUtils, createGetImportPhase } = require("./ImportPhase");
@@ -46,10 +49,6 @@ const { ImportPhaseUtils, createGetImportPhase } = require("./ImportPhase");
46
49
  /** @typedef {import("./ImportPhase").ImportPhaseType} ImportPhaseType */
47
50
  /** @typedef {import("./HarmonyImportGuard").GuardFrame} GuardFrame */
48
51
 
49
- /** @typedef {Map<string, Set<string>>} Guards Map of import root to guarded member keys */
50
-
51
- const getHarmonyImportGuard = memoize(() => require("./HarmonyImportGuard"));
52
-
53
52
  const harmonySpecifierTag = Symbol("harmony import");
54
53
 
55
54
  // Shared placeholder: plain specifier references have no member ranges, so they
@@ -137,6 +136,12 @@ const findImportSpecifier = (parser, node) => {
137
136
  parser,
138
137
  /** @type {Expression} */ (node.object)
139
138
  );
139
+ case "BinaryExpression":
140
+ // `"x" in ns` presence guard
141
+ return (
142
+ node.operator === "in" &&
143
+ findImportSpecifier(parser, /** @type {Expression} */ (node.right))
144
+ );
140
145
  default:
141
146
  return false;
142
147
  }
@@ -175,21 +180,18 @@ module.exports = class HarmonyImportDependencyParserPlugin {
175
180
  );
176
181
  if (!harmonySettings) return this.exportPresenceMode;
177
182
 
183
+ if (this.exportPresenceMode === ExportPresenceModes.NONE) {
184
+ return this.exportPresenceMode;
185
+ }
186
+
178
187
  const stack = /** @type {GuardFrame[] | undefined} */ (
179
188
  parser.state.guardStack
180
189
  );
181
- if (stack !== undefined) {
182
- const name = harmonySettings.name;
183
- const member = ids[0];
184
- for (let i = stack.length - 1; i >= 0; i--) {
185
- const presence = stack[i].presence;
186
- if (presence !== undefined) {
187
- const members = presence.get(name);
188
- if (members !== undefined && members.has(member)) {
189
- return ExportPresenceModes.NONE;
190
- }
191
- }
192
- }
190
+ if (
191
+ stack !== undefined &&
192
+ isPresentByGuards(parser, stack, harmonySettings.name, ids[0])
193
+ ) {
194
+ return ExportPresenceModes.NONE;
193
195
  }
194
196
 
195
197
  return this.exportPresenceMode;
@@ -354,7 +356,7 @@ module.exports = class HarmonyImportDependencyParserPlugin {
354
356
  dep.loc = /** @type {DependencyLocation} */ (expr.loc);
355
357
  dep.call = parser.scope.inTaggedTemplateTag;
356
358
  parser.state.module.addDependency(dep);
357
- getHarmonyImportGuard().attachDependencyGuards(parser, dep);
359
+ attachDependencyGuards(parser, dep);
358
360
  getInnerGraphUtils(parser.state.compilation).onUsage(
359
361
  parser.state,
360
362
  (e) => (dep.usedByExports = e)
@@ -406,7 +408,7 @@ module.exports = class HarmonyImportDependencyParserPlugin {
406
408
  );
407
409
  dep.loc = /** @type {DependencyLocation} */ (expr.loc);
408
410
  parser.state.module.addDependency(dep);
409
- getHarmonyImportGuard().attachDependencyGuards(parser, dep);
411
+ attachDependencyGuards(parser, dep);
410
412
  getInnerGraphUtils(parser.state.compilation).onUsage(
411
413
  parser.state,
412
414
  (e) => (dep.usedByExports = e)
@@ -463,7 +465,7 @@ module.exports = class HarmonyImportDependencyParserPlugin {
463
465
  /** @type {boolean} */ (this.strictThisContextOnImports);
464
466
  dep.loc = /** @type {DependencyLocation} */ (expr.loc);
465
467
  parser.state.module.addDependency(dep);
466
- getHarmonyImportGuard().attachDependencyGuards(parser, dep);
468
+ attachDependencyGuards(parser, dep);
467
469
  if (args) parser.walkExpressions(args);
468
470
  getInnerGraphUtils(parser.state.compilation).onUsage(
469
471
  parser.state,
@@ -543,90 +545,15 @@ module.exports = class HarmonyImportDependencyParserPlugin {
543
545
  parser.hooks.collectGuards.tap(PLUGIN_NAME, (expression) => {
544
546
  if (parser.scope.isAsmJs) return;
545
547
 
546
- // Presence guards: `"x" in ns` checks that suppress export-presence errors
547
- // (consequent only).
548
- /** @type {Guards | undefined} */
549
- let presence;
550
- if (this.exportPresenceMode !== ExportPresenceModes.NONE) {
551
- /** @type {Guards} */
552
- const guards = new Map();
553
-
554
- /**
555
- * Processes the provided expression.
556
- * @param {Expression} expression expression
557
- * @param {boolean} needTruthy need to be truthy
558
- */
559
- const collect = (expression, needTruthy) => {
560
- if (
561
- expression.type === "UnaryExpression" &&
562
- expression.operator === "!"
563
- ) {
564
- collect(expression.argument, !needTruthy);
565
- return;
566
- } else if (expression.type === "LogicalExpression" && needTruthy) {
567
- if (expression.operator === "&&") {
568
- collect(expression.left, true);
569
- collect(expression.right, true);
570
- } else if (expression.operator === "||") {
571
- const leftEvaluation = parser.evaluateExpression(expression.left);
572
- const leftBool = leftEvaluation.asBool();
573
- if (leftBool === false) {
574
- collect(expression.right, true);
575
- }
576
- } else if (expression.operator === "??") {
577
- const leftEvaluation = parser.evaluateExpression(expression.left);
578
- const leftNullish = leftEvaluation.asNullish();
579
- if (leftNullish === true) {
580
- collect(expression.right, true);
581
- }
582
- }
583
- return;
584
- }
585
- if (!needTruthy) return;
586
-
587
- // Direct `"x" in ns` guards
588
- if (
589
- expression.type === "BinaryExpression" &&
590
- expression.operator === "in"
591
- ) {
592
- if (expression.right.type !== "Identifier") {
593
- return;
594
- }
595
- const info = getInOperatorHarmonyImportInfo(
596
- parser,
597
- expression.left,
598
- expression.right
599
- );
600
- if (!info) return;
601
-
602
- const { settings, leftPart, members } = info;
603
- // Only direct namespace guards
604
- if (members.length > 0) return;
605
- const guarded = guards.get(settings.name);
606
- if (guarded) {
607
- guarded.add(leftPart);
608
- return;
609
- }
610
-
611
- guards.set(settings.name, new Set([leftPart]));
612
- }
613
- };
614
-
615
- collect(expression, true);
616
-
617
- if (guards.size > 0) presence = guards;
618
- }
619
-
620
548
  const hasSpecifier = findImportSpecifier(parser, expression);
621
549
  const depStart = hasSpecifier
622
550
  ? /** @type {Module} */ (parser.state.module).dependencies.length
623
551
  : undefined;
624
552
 
625
- if (presence === undefined && depStart === undefined) return;
553
+ if (depStart === undefined) return;
626
554
 
627
555
  /** @type {GuardFrame} */
628
556
  const consequent = {
629
- presence,
630
557
  test: expression,
631
558
  depStart,
632
559
  condition: true
@@ -15,16 +15,19 @@ const memoize = require("../util/memoize");
15
15
  /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
16
16
  /** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
17
17
  /** @typedef {import("./CommonJsRequireDependency")} CommonJsRequireDependency */
18
+ /** @typedef {import("./HarmonyEvaluatedImportSpecifierDependency")} HarmonyEvaluatedImportSpecifierDependency */
18
19
  /** @typedef {import("./HarmonyImportSpecifierDependency")} HarmonyImportSpecifierDependency */
19
20
  /** @typedef {import("./ImportDependency")} ImportDependency */
20
21
 
21
22
  // Tuple-encoded boolean formula over guard dependencies. Split into per-shape
22
23
  // aliases so the recursive references resolve through array/object types.
24
+ // `v` = dependency-liveness atom, `p` = `"x" in ns` presence atom.
23
25
  /** @typedef {["v", Dependency]} GuardAtom */
26
+ /** @typedef {["p", HarmonyEvaluatedImportSpecifierDependency]} GuardPresenceAtom */
24
27
  /** @typedef {["?"]} GuardUnknown */
25
28
  /** @typedef {["!", GuardFormula]} GuardNot */
26
29
  /** @typedef {["&&" | "||" | "??", GuardFormula, GuardFormula]} GuardLogical */
27
- /** @typedef {GuardAtom | GuardUnknown | GuardNot | GuardLogical} GuardFormula */
30
+ /** @typedef {GuardAtom | GuardPresenceAtom | GuardUnknown | GuardNot | GuardLogical} GuardFormula */
28
31
  /** @typedef {{ formula: GuardFormula, value: boolean }} DependencyGuard branch guard: the dependency is live only when the formula evaluates to `value` */
29
32
  /** @typedef {HarmonyImportSpecifierDependency | CommonJsRequireDependency | ImportDependency} GuardableDependency */
30
33
 
@@ -33,16 +36,19 @@ const memoize = require("../util/memoize");
33
36
  * conditional branch body. Carries the `"x" in ns` presence guards and/or the
34
37
  * dead-branch dependency guard for that branch.
35
38
  * @typedef {object} GuardFrame
36
- * @property {Map<string, Set<string>>=} presence `"x" in ns` presence guards active in this branch
37
39
  * @property {Expression=} test the conditional test (for the lazily built formula)
38
40
  * @property {number=} depStart dependency count before the test was walked
39
41
  * @property {boolean=} condition branch truthiness the dependency guard is live for
40
- * @property {GuardFormula | null=} formula memoized formula (null = no knowable atom)
42
+ * @property {GuardFormula | null=} formula memoized dependency-guard formula (null = no knowable atom)
43
+ * @property {GuardFormula | null=} presenceFormula memoized presence formula (null = no presence atom)
41
44
  */
42
45
 
43
46
  const getHarmonyImportSpecifierDependency = memoize(() =>
44
47
  require("./HarmonyImportSpecifierDependency")
45
48
  );
49
+ const getHarmonyEvaluatedImportSpecifierDependency = memoize(() =>
50
+ require("./HarmonyEvaluatedImportSpecifierDependency")
51
+ );
46
52
 
47
53
  // Tri-state truthiness: UNKNOWN means not statically known.
48
54
  const UNKNOWN = 0;
@@ -62,12 +68,12 @@ const flip = (t) => (t === TRUE ? FALSE : t === FALSE ? TRUE : UNKNOWN);
62
68
  const fromBool = (b) => (b ? TRUE : FALSE);
63
69
 
64
70
  /**
65
- * Build a guard formula from a conditional test AST.
71
+ * Build a liveness formula from a conditional test AST.
66
72
  * @param {Expression} test conditional test expression
67
73
  * @param {Map<number, HarmonyImportSpecifierDependency>} depByRangeStart import-specifier deps in the test, keyed by range start
68
74
  * @returns {GuardFormula | null} formula, or null when it has no knowable atom
69
75
  */
70
- const buildGuardFormula = (test, depByRangeStart) => {
76
+ const buildLivenessFormula = (test, depByRangeStart) => {
71
77
  /**
72
78
  * @param {Expression} node ast node
73
79
  * @returns {GuardFormula} formula node
@@ -104,6 +110,7 @@ const buildGuardFormula = (test, depByRangeStart) => {
104
110
  const hasAtom = (formula) => {
105
111
  switch (formula[0]) {
106
112
  case "v":
113
+ case "p":
107
114
  return true;
108
115
  case "!":
109
116
  return hasAtom(formula[1]);
@@ -122,7 +129,7 @@ const hasAtom = (formula) => {
122
129
  * @param {RuntimeSpec} runtime runtime
123
130
  * @returns {{ t: number, n: number }} truthy `t` and null `n` tri-states
124
131
  */
125
- const evalNode = (formula, moduleGraph, runtime) => {
132
+ const evalLivenessFormula = (formula, moduleGraph, runtime) => {
126
133
  switch (formula[0]) {
127
134
  case "v": {
128
135
  const dep = /** @type {HarmonyImportSpecifierDependency} */ (formula[1]);
@@ -145,12 +152,12 @@ const evalNode = (formula, moduleGraph, runtime) => {
145
152
  case "!":
146
153
  // `!x` always yields a boolean → never nullish.
147
154
  return {
148
- t: flip(evalNode(formula[1], moduleGraph, runtime).t),
155
+ t: flip(evalLivenessFormula(formula[1], moduleGraph, runtime).t),
149
156
  n: FALSE
150
157
  };
151
158
  case "&&": {
152
- const l = evalNode(formula[1], moduleGraph, runtime);
153
- const r = evalNode(formula[2], moduleGraph, runtime);
159
+ const l = evalLivenessFormula(formula[1], moduleGraph, runtime);
160
+ const r = evalLivenessFormula(formula[2], moduleGraph, runtime);
154
161
  const t =
155
162
  l.t === FALSE || r.t === FALSE
156
163
  ? FALSE
@@ -160,8 +167,8 @@ const evalNode = (formula, moduleGraph, runtime) => {
160
167
  return { t, n: UNKNOWN };
161
168
  }
162
169
  case "||": {
163
- const l = evalNode(formula[1], moduleGraph, runtime);
164
- const r = evalNode(formula[2], moduleGraph, runtime);
170
+ const l = evalLivenessFormula(formula[1], moduleGraph, runtime);
171
+ const r = evalLivenessFormula(formula[2], moduleGraph, runtime);
165
172
  const t =
166
173
  l.t === TRUE || r.t === TRUE
167
174
  ? TRUE
@@ -171,8 +178,8 @@ const evalNode = (formula, moduleGraph, runtime) => {
171
178
  return { t, n: UNKNOWN };
172
179
  }
173
180
  case "??": {
174
- const l = evalNode(formula[1], moduleGraph, runtime);
175
- const r = evalNode(formula[2], moduleGraph, runtime);
181
+ const l = evalLivenessFormula(formula[1], moduleGraph, runtime);
182
+ const r = evalLivenessFormula(formula[2], moduleGraph, runtime);
176
183
  // `l ?? r` is l when l non-nullish, else r.
177
184
  const t =
178
185
  l.n === FALSE ? l.t : l.n === TRUE ? r.t : l.t === r.t ? l.t : UNKNOWN;
@@ -192,37 +199,201 @@ const evalNode = (formula, moduleGraph, runtime) => {
192
199
  */
193
200
  const isDeadByGuards = (guards, moduleGraph, runtime) => {
194
201
  for (const guard of guards) {
195
- const t = evalNode(guard.formula, moduleGraph, runtime).t;
202
+ const t = evalLivenessFormula(guard.formula, moduleGraph, runtime).t;
196
203
  if (t !== UNKNOWN && t !== (guard.value ? TRUE : FALSE)) return true;
197
204
  }
198
205
  return false;
199
206
  };
200
207
 
201
208
  /**
202
- * Builds (once) the dependency-guard formula for a frame from the import
203
- * specifier deps created while walking the test.
209
+ * Builds (once) the liveness formula for a frame from the import specifier deps
210
+ * created while walking the test.
204
211
  * @param {JavascriptParser} parser the parser
205
212
  * @param {GuardFrame} frame guard frame
206
213
  * @returns {GuardFormula | null} formula, or null when it has no knowable atom
207
214
  */
208
- const buildFrameFormula = (parser, frame) => {
215
+ const buildFrameLivenessFormula = (parser, frame) => {
209
216
  const HarmonyImportSpecifierDependency =
210
217
  getHarmonyImportSpecifierDependency();
218
+ const HarmonyEvaluatedImportSpecifierDependency =
219
+ getHarmonyEvaluatedImportSpecifierDependency();
211
220
  const deps = /** @type {Module} */ (parser.state.module).dependencies;
212
221
  /** @type {Map<number, HarmonyImportSpecifierDependency>} */
213
222
  const depByRangeStart = new Map();
214
223
  for (let i = /** @type {number} */ (frame.depStart); i < deps.length; i++) {
215
224
  const dep = deps[i];
216
- if (dep instanceof HarmonyImportSpecifierDependency && dep.range) {
225
+ // Exclude `"x" in ns` evaluated deps; those feed the presence formula only.
226
+ if (
227
+ dep instanceof HarmonyImportSpecifierDependency &&
228
+ !(dep instanceof HarmonyEvaluatedImportSpecifierDependency) &&
229
+ dep.range
230
+ ) {
231
+ depByRangeStart.set(dep.range[0], dep);
232
+ }
233
+ }
234
+ return buildLivenessFormula(
235
+ /** @type {Expression} */ (frame.test),
236
+ depByRangeStart
237
+ );
238
+ };
239
+
240
+ /**
241
+ * Build a presence formula from a conditional test AST. Statically-decided
242
+ * `||`/`??` operands are folded away at build time, so the result contains only
243
+ * `"x" in ns` presence atoms (plus `&&`/`!`).
244
+ * @param {JavascriptParser} parser the parser
245
+ * @param {Expression} test conditional test expression
246
+ * @param {Map<number, HarmonyEvaluatedImportSpecifierDependency>} depByRangeStart in-operator deps in the test, keyed by range start
247
+ * @returns {GuardFormula | null} formula, or null when it has no presence atom
248
+ */
249
+ const buildPresenceFormula = (parser, test, depByRangeStart) => {
250
+ /**
251
+ * @param {Expression} node ast node
252
+ * @returns {GuardFormula} formula node
253
+ */
254
+ const build = (node) => {
255
+ if (node.type === "UnaryExpression" && node.operator === "!") {
256
+ return ["!", build(/** @type {Expression} */ (node.argument))];
257
+ }
258
+ if (node.type === "LogicalExpression") {
259
+ if (node.operator === "&&") {
260
+ return [
261
+ "&&",
262
+ build(/** @type {Expression} */ (node.left)),
263
+ build(/** @type {Expression} */ (node.right))
264
+ ];
265
+ }
266
+
267
+ if (node.operator === "||") {
268
+ if (parser.evaluateExpression(node.left).asBool() === false) {
269
+ return build(/** @type {Expression} */ (node.right));
270
+ }
271
+ if (parser.evaluateExpression(node.right).asBool() === false) {
272
+ return build(/** @type {Expression} */ (node.left));
273
+ }
274
+ return ["?"];
275
+ }
276
+
277
+ if (node.operator === "??") {
278
+ const nullish = parser.evaluateExpression(node.left).asNullish();
279
+ if (nullish === true) {
280
+ return build(/** @type {Expression} */ (node.right));
281
+ }
282
+ if (nullish === false) {
283
+ return build(/** @type {Expression} */ (node.left));
284
+ }
285
+ return ["?"];
286
+ }
287
+ }
288
+ const dep =
289
+ node.range && depByRangeStart.get(/** @type {number} */ (node.range[0]));
290
+ return dep ? ["p", dep] : ["?"];
291
+ };
292
+
293
+ const formula = build(test);
294
+ return hasAtom(formula) ? formula : null;
295
+ };
296
+
297
+ /**
298
+ * Whether a presence formula guarantees `ns.member` is present. "Must be truthy"
299
+ * semantics: the member is guaranteed when some `"member" in ns` atom must hold
300
+ * for the branch condition. Statically-dead branches never reach here — the
301
+ * parser eliminates them before the branch body is walked.
302
+ * @param {GuardFormula} formula presence formula
303
+ * @param {string} name namespace binding name
304
+ * @param {string} member member key
305
+ * @param {boolean} needTruthy whether the formula must be truthy
306
+ * @returns {boolean} true when the member is guaranteed present
307
+ */
308
+ const evalPresenceFormula = (formula, name, member, needTruthy) => {
309
+ switch (formula[0]) {
310
+ case "p": {
311
+ const dep = /** @type {HarmonyEvaluatedImportSpecifierDependency} */ (
312
+ formula[1]
313
+ );
314
+ return (
315
+ needTruthy &&
316
+ dep.directImport === true &&
317
+ dep.name === name &&
318
+ dep.ids.length === 1 &&
319
+ dep.ids[0] === member
320
+ );
321
+ }
322
+ case "!":
323
+ return evalPresenceFormula(formula[1], name, member, !needTruthy);
324
+ case "&&":
325
+ return (
326
+ needTruthy &&
327
+ (evalPresenceFormula(formula[1], name, member, true) ||
328
+ evalPresenceFormula(formula[2], name, member, true))
329
+ );
330
+ default:
331
+ return false;
332
+ }
333
+ };
334
+
335
+ /**
336
+ * Builds (once) the presence formula for a frame from the in-operator deps
337
+ * created while walking the test.
338
+ * @param {JavascriptParser} parser the parser
339
+ * @param {GuardFrame} frame guard frame
340
+ * @returns {GuardFormula | null} formula, or null when it has no presence atom
341
+ */
342
+ const buildFramePresenceFormula = (parser, frame) => {
343
+ const HarmonyEvaluatedImportSpecifierDependency =
344
+ getHarmonyEvaluatedImportSpecifierDependency();
345
+ const deps = /** @type {Module} */ (parser.state.module).dependencies;
346
+ /** @type {Map<number, HarmonyEvaluatedImportSpecifierDependency>} */
347
+ const depByRangeStart = new Map();
348
+ for (let i = /** @type {number} */ (frame.depStart); i < deps.length; i++) {
349
+ const dep = deps[i];
350
+ if (dep instanceof HarmonyEvaluatedImportSpecifierDependency && dep.range) {
217
351
  depByRangeStart.set(dep.range[0], dep);
218
352
  }
219
353
  }
220
- return buildGuardFormula(
354
+ return buildPresenceFormula(
355
+ parser,
221
356
  /** @type {Expression} */ (frame.test),
222
357
  depByRangeStart
223
358
  );
224
359
  };
225
360
 
361
+ /**
362
+ * Whether an active presence guard proves `ns.member` present, suppressing
363
+ * export-presence errors. Each frame is evaluated against its branch condition,
364
+ * so the `else` of `if (!("x" in ns))` also guards `ns.x`.
365
+ * @param {JavascriptParser} parser the parser
366
+ * @param {GuardFrame[]} stack the guard stack
367
+ * @param {string} name namespace binding name
368
+ * @param {string} member member key
369
+ * @returns {boolean} true when a guard proves the member present
370
+ */
371
+ const isPresentByGuards = (parser, stack, name, member) => {
372
+ for (let i = stack.length - 1; i >= 0; i--) {
373
+ const frame = stack[i];
374
+ if (frame.depStart === undefined) continue;
375
+ let formula = frame.presenceFormula;
376
+ if (formula === undefined) {
377
+ formula = frame.presenceFormula = buildFramePresenceFormula(
378
+ parser,
379
+ frame
380
+ );
381
+ }
382
+ if (
383
+ formula !== null &&
384
+ evalPresenceFormula(
385
+ formula,
386
+ name,
387
+ member,
388
+ /** @type {boolean} */ (frame.condition)
389
+ )
390
+ ) {
391
+ return true;
392
+ }
393
+ }
394
+ return false;
395
+ };
396
+
226
397
  /**
227
398
  * Tags a freshly created dependency with the active dependency guards.
228
399
  * @param {JavascriptParser} parser the parser
@@ -239,7 +410,7 @@ const attachDependencyGuards = (parser, dep) => {
239
410
  if (frame.depStart === undefined) continue;
240
411
  let formula = frame.formula;
241
412
  if (formula === undefined) {
242
- formula = frame.formula = buildFrameFormula(parser, frame);
413
+ formula = frame.formula = buildFrameLivenessFormula(parser, frame);
243
414
  }
244
415
  if (formula === null) continue;
245
416
  (guards || (guards = [])).push({
@@ -252,3 +423,4 @@ const attachDependencyGuards = (parser, dep) => {
252
423
 
253
424
  module.exports.attachDependencyGuards = attachDependencyGuards;
254
425
  module.exports.isDeadByGuards = isDeadByGuards;
426
+ module.exports.isPresentByGuards = isPresentByGuards;