Skip to content

fix: report semantic strict-mode hazards in ES module output - #21434

Merged
alexander-akait merged 7 commits into
mainfrom
fix/esm-strict-mode-semantic-warnings
Jul 16, 2026
Merged

fix: report semantic strict-mode hazards in ES module output#21434
alexander-akait merged 7 commits into
mainfrom
fix/esm-strict-mode-semantic-warnings

Conversation

@alexander-akait

@alexander-akait alexander-akait commented Jul 16, 2026

Copy link
Copy Markdown
Member

Summary

#21387 reports the strict-mode-only syntax a loose module carries into strict ES module output, but semantic hazards still ship silently: arguments.callee/arguments.caller throw a TypeError at runtime, and assigning to the read-only globals undefined/NaN/Infinity throws a TypeError instead of silently doing nothing. This reports them through the same mechanism, skipping already-strict sources and shadowed bindings, and adds a strictModeViolations parser option ("error" | "warn" | false, default "warn", "error" under experiments.futureDefaults) so the diagnostics can be tuned or disabled globally or per rule, mirroring exportsPresence. An undeclared-variable assignment warning was prototyped but dropped: dependency plugins (require.ensure, AMD, import().then) walk callback bodies inline without registering their vars/params, so declaration tracking is structurally incomplete there — reporting it safely first needs those walk paths fixed (possible follow-up). Refs #17121; covers the semantic scope left open by #19628.

What kind of change does this PR introduce?

fix

Did you add tests for your changes?

Yes — test/configCases/parsing/strict-mode-module-output-semantics (warnings, bundle still runs), strict-mode-module-output-semantics-future-defaults (errors under experiments.futureDefaults), and strict-mode-module-output-severity/-error (per-rule strictModeViolations: false suppression and explicit "error" severity).

Does this PR introduce a breaking change?

No — the hazards are reported as warnings by default, only become errors under experiments.futureDefaults, and can be disabled via strictModeViolations: false.

If relevant, what needs to be documented once your changes are merged or what have you already documented?

Document the new module.parser.javascript.strictModeViolations option and extend the strict-mode diagnostics note for output.module builds with the new semantic warnings.

Use of AI

Yes — the change and tests were implemented with Claude Code under my direction; I reviewed the diff and verified the test runs (TestCases/ConfigTestCases/StatsTestCases suites, tsc, lint).


Generated by Claude Code

Sloppy modules emitted as strict ESM can break at runtime with no
build-time signal: accessing arguments.callee / arguments.caller throws
a TypeError, and assigning to an undeclared variable throws a
ReferenceError (a TypeError for read-only globals) instead of creating
a global. Report these as warnings, or errors under
experiments.futureDefaults, skipping already-strict sources and targets
a plugin rewrites.

Claude-Session: https://claude.ai/code/session_01TjQJMStkM69Gomi4G5XV5W
Copilot AI review requested due to automatic review settings July 16, 2026 11:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

This PR is packaged and the instant preview is available (1bec5b6).

Install it locally:

  • npm
npm i -D webpack@https://pkg.pr.new/webpack@1bec5b6
  • yarn
yarn add -D webpack@https://pkg.pr.new/webpack@1bec5b6
  • pnpm
pnpm add -D webpack@https://pkg.pr.new/webpack@1bec5b6

@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.18%. Comparing base (8dd081a) to head (9015db5).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #21434      +/-   ##
==========================================
+ Coverage   93.17%   93.18%   +0.01%     
==========================================
  Files         610      610              
  Lines       70785    70836      +51     
  Branches    20135    20156      +21     
==========================================
+ Hits        65951    66010      +59     
+ Misses       4834     4826       -8     
Flag Coverage Δ
css-parsing 25.78% <4.76%> (-0.02%) ⬇️
html5lib 27.41% <4.76%> (-0.02%) ⬇️
integration 89.30% <100.00%> (+0.01%) ⬆️
test262 42.90% <47.61%> (-0.06%) ⬇️
unit 46.13% <23.80%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread test/configCases/parsing/strict-mode-module-output-semantics/mod.js Fixed
Comment thread test/configCases/parsing/strict-mode-module-output-semantics/mod.js Fixed
}

// The rest keeps its behavior in strict mode — no diagnostics.
class PrivateAccess {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional: PrivateAccess only needs to be parsed, not used — arguments.#p exercises the non-Identifier (private field) property guard in _checkStrictModeArgumentsMember, asserting no diagnostic is emitted. This fixture never executes (the compilation intentionally errors under futureDefaults), so exporting it would add nothing.


Generated by Claude Code

The undeclared-variable warning relied on the parser's definition
tracking, which is incomplete inside callback bodies that dependency
plugins walk inline without registering params or vars (require.ensure,
AMD require/define, import().then) — e.g. a for(var i) loop inside a
require.ensure callback was reported as undeclared. Keep only the
scope-independent semantic checks: arguments.callee/caller and
assignments to the read-only globals undefined/NaN/Infinity.

Claude-Session: https://claude.ai/code/session_01TjQJMStkM69Gomi4G5XV5W
Copilot AI review requested due to automatic review settings July 16, 2026 12:10
@changeset-bot

changeset-bot Bot commented Jul 16, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 9015db5

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes changesets to release 1 package
Name Type
webpack Minor

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@codspeed-hq

codspeed-hq Bot commented Jul 16, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by ×2

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
✅ 161 untouched benchmarks

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Memory benchmark "react", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 314 KB 156.9 KB ×2

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing fix/esm-strict-mode-semantic-warnings (9015db5) with main (7f319a9)

Open in CodSpeed


// Already-strict source keeps its behavior in ESM output — no diagnostics.
function strictCallee() {
return arguments.callee;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional (same as the previous round): this fixture asserts already-strict source gets no diagnostic (scope.isStrict branch); the function is never called. Replacing arguments.callee deletes the tested case.


Generated by Claude Code


// Already-strict source keeps its behavior in ESM output — no diagnostics.
function strictCallee() {
return arguments.callee;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional (same as the previous round): asserts already-strict source is skipped by the diagnostics; never called at runtime.


Generated by Claude Code

}

function shadowsUndefined() {
var undefined = 1;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional: undefined = 2; is the tested statement — a shadowed undefined binding must suppress the read-only-global diagnostic (the isVariableDefined branch). Collapsing to a single initialization would delete the assignment being asserted.


Generated by Claude Code

}

function shadowsUndefined() {
var undefined = 1;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional: undefined = 2; is the tested statement — a shadowed undefined binding must suppress the read-only-global diagnostic (the isVariableDefined branch), and the runnable test asserts the function returns 2. Collapsing the initialization would delete the assignment being asserted.


Generated by Claude Code

Copilot AI review requested due to automatic review settings July 16, 2026 12:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Move the detection into StrictModeWarningsPlugin tapping
expressionMemberChain/callMemberChain for("arguments") — the keyed
dispatch already happens, so other expressions pay nothing and the
per-member-expression branches in the walker are gone. Shadowed
bindings never dispatch free-name hooks, replacing the manual check.
Also drop the module-output flag for programs that are already strict
(ESM sources, "use strict"), so the remaining inline checks cost zero
there, and report computed literal access (arguments["callee"]) too.

Claude-Session: https://claude.ai/code/session_01TjQJMStkM69Gomi4G5XV5W
Copilot AI review requested due to automatic review settings July 16, 2026 12:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

}

function shadowsArguments() {
var arguments = { callee: null };

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional (re-flagged from the previous round): the arguments binding is the negative case — a shadowed arguments must suppress the arguments.callee diagnostic (a defined binding never dispatches the free-variable member-chain hook). Renaming it would delete the case being tested; the fixture is parse-only and never runs.


Generated by Claude Code

Since the syntactic checks have no hook points, a separate plugin split
the feature across files — register the arguments.callee/caller
member-chain taps in the parser itself instead (like its own evaluate
taps), keeping the keyed-hook dispatch cost model and one home for all
checks.

Claude-Session: https://claude.ai/code/session_01TjQJMStkM69Gomi4G5XV5W
Copilot AI review requested due to automatic review settings July 16, 2026 12:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…rict-mode diagnostics

Allows configuring the severity of strict-mode violation diagnostics in
ES module output via module.parser.javascript.strictModeViolations
("error" | "warn" | false), globally or per rule. Defaults to "warn",
or "error" under experiments.futureDefaults.
Copilot AI review requested due to automatic review settings July 16, 2026 13:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings July 16, 2026 13:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

Copy link
Copy Markdown
Contributor

Types Coverage

Coverage after merging fix/esm-strict-mode-semantic-warnings into main will be
99.34%
Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
bin
   webpack.js98.77%100%100%98.77%91
examples
   build-common.js100%100%100%100%
   buildAll.js100%100%100%100%
   examples.js100%100%100%100%
   template-common.js98.21%100%100%98.21%72
examples/custom-javascript-parser
   test.filter.js100%100%100%100%
examples/custom-javascript-parser/internals
   acorn-parse.js100%100%100%100%
   meriyah-parse.js100%100%100%100%
   oxc-parse.js91.30%100%100%91.30%140, 142–143, 145, 147, 153–154, 161, 168, 90
examples/markdown
   webpack.config.mjs100%100%100%100%
examples/module-federation
   test.filter.js100%100%100%100%
examples/reexport-components
   test.filter.js100%100%100%100%
examples/typescript
   test.filter.js100%100%100%100%
examples/typescript-non-erasable
   test.filter.js50%100%100%50%5
examples/virtual-modules
   test.filter.js100%100%100%100%
examples/wasm-bindgen-esm
   test.filter.js100%100%100%100%
examples/wasm-complex
   test.filter.js100%100%100%100%
examples/wasm-emscripten
   test.filter.js100%100%100%100%
examples/wasm-simple
   test.filter.js100%100%100%100%
examples/wasm-simple-source-phase
   test.filter.js100%100%100%100%
lib
   APIPlugin.js100%100%100%100%
   AsyncDependenciesBlock.js100%100%100%100%
   AutomaticPrefetchPlugin.js100%100%100%100%
   BannerPlugin.js100%100%100%100%
   Cache.js98.21%100%100%98.21%101
   CacheFacade.js100%100%100%100%
   Chunk.js99.72%100%100%99.72%39
   ChunkGraph.js100%100%100%100%
   ChunkGroup.js100%100%100%100%
   ChunkTemplate.js100%100%100%100%
   CircularModulesPlugin.js98.81%100%100%98.81%136
   CleanPlugin.js99.12%100%100%99.12%207, 227
   CodeGenerationResults.js100%100%100%100%
   CompatibilityPlugin.js100%100%100%100%
   Compilation.js98.42%100%100%98.42%1639, 1958, 1965, 1973, 1995, 1998, 2937, 3416–3417, 3449, 4149, 4179, 4232–4233, 4237, 4242, 4258–4259, 4273–4274, 4279–4280, 4757, 4783, 527, 532, 5591, 5623, 5640, 5656, 5672, 5687, 5712–5713, 5715, 6045, 6050, 6056, 6059, 6066, 6078, 6080, 6084, 6100, 6115, 6147, 6201, 6225, 6340, 778–779
   Compiler.js99.56%100%100%99.56%1147–1148, 1156
   ConcatenationScope.js98.65%100%100%98.65%195
   ConditionalInitFragment.js100%100%100%100%
   ConstPlugin.js100%100%100%100%
   ContextExclusionPlugin.js100%100%100%100%
   ContextModule.js99.88%100%100%99.88%1461
   ContextModuleFactory.js97.20%100%100%97.20%266, 435, 456, 461, 501, 512, 514, 518, 527–528
   ContextReplacementPlugin.js100%100%100%100%
   DefinePlugin.js99.07%100%100%99.07%1048, 176–177, 193, 212, 286
   DependenciesBlock.js100%100%100%100%
   Dependency.js98.51%100%100%98.51%479, 525
   DependencyTemplate.js100%100%100%100%
   DependencyTemplates.js100%100%100%100%
   DotenvPlugin.js98.41%100%100%98.41%378, 391–392
   DynamicEntryPlugin.js100%100%100%100%
   EntryOptionPlugin.js100%100%100%100%
   EntryPlugin.js100%100%100%100%
   Entrypoint.js100%100%100%100%
   EnvironmentPlugin.js97.14%100%100%97.14%49
   ErrorHelpers.js100%100%100%100%
   EvalDevToolModulePlugin.js100%100%100%100%
   EvalSourceMapDevToolPlugin.js100%100%100%100%
   ExportsInfo.js100%100%100%100%
   ExportsInfoApiPlugin.js100%100%100%100%
   ExternalModule.js98.65%100%100%98.65%1196, 1199, 514–518, 520, 666
   ExternalModuleFactoryPlugin.js100%100%100%100%
   ExternalsPlugin.js100%100%100%100%
   FileSystemInfo.js99.52%100%100%99.52%182, 2402–2403, 2406, 2417, 2428, 2439, 280, 3876, 3891, 3915
   FlagAllModulesAsUsedPlugin.js100%100%100%100%
   FlagDependencyExportsPlugin.js98.21%100%100%98.21%448, 457, 460, 464, 476
   FlagDependencyUsagePlugin.js100%100%100%100%
   FlagEntryExportAsUsedPlugin.js100%100%100%100%
   Generator.js100%100%100%100%
   HotModuleReplacementPlugin.js100%100%100%100%
   HotUpdateChunk.js100%100%100%100%
   IgnorePlugin.js100%100%100%100%
   IgnoreWarningsPlugin.js100%100%100%100%
   InitFragment.js100%100%100%100%
   JavascriptMetaInfoPlugin.js100%100%100%100%
   LazyBarrel.js100%100%100%100%
   LibraryTemplatePlugin.js100%100%100%100%
   LoaderOptionsPlugin.js100%100%100%100%
   LoaderTargetPlugin.js100%100%100%100%
   MainTemplate.js100%100%100%100%
   ManifestPlugin.js100%100%100%100%
   Module.js98.50%100%100%98.50%1288, 1293, 1353, 1367, 1429, 1438
   ModuleFactory.js100%100%100%100%
   ModuleFilenameHelpers.js98.85%100%100%98.85%106, 108
   ModuleGraph.js99.73%100%100%99.73%1005
   ModuleGraphConnection.js100%100%100%100%
   ModuleInfoHeaderPlugin.js100%100%100%100%
   ModuleNotFoundError.js100%100%100%100%
   ModuleProfile.js100%100%100%100%
   ModuleSourceTypeConstants.js100%100%100%100%
   ModuleTemplate.js100%100%100%100%
   ModuleTypeConstants.js100%100%100%100%
   MultiCompiler.js99.69%100%100%99.69%661
   MultiStats.js100%100%100%100%
   MultiWatching.js100%100%100%100%
   NoEmitOnErrorsPlugin.js100%100%100%100%
   NodeStuffPlugin.js100%100%100%100%
   NormalModule.js97.97%100%100%97.97%1008, 1025, 1273, 1307, 1323, 1770, 2067, 2072–2082, 34, 988, 991
   NormalModuleFactory.js98.72%100%100%98.72%1117, 1385, 1396, 1406, 1457–1459, 1466, 520, 532
   NormalModuleReplacementPlugin.js100%100%100%100%
   NullFactory.js100%100%100%100%
   OptimizationStages.js100%100%100%100%
   OptionsApply.js100%100%100%100%
   Parser.js100%100%100%100%
   PlatformPlugin.js100%100%100%100%
   PrefetchPlugin.js100%100%100%100%
   ProgressPlugin.js99.80%100%100%99.80%688
   ProvidePlugin.js100%100%100%100%
   RawModule.js100%100%100%100%
   RecordIdsPlugin.js100%100%100%100%
   RequestShortener.js100%100%100%100%
   ResolverFactory.js100%100%100%100%
   RuntimeGlobals.js100%100%100%100%
   RuntimeModule.js100%100%100%100%
   RuntimePlugin.js100%100%100%100%
   RuntimeTemplate.js100%100%100%100%
   SelfModuleFactory.js100%100%100%100%
   SingleEntryPlugin.js100%100%100%100%
   SourceMapDevToolModuleOptionsPlugin.js100%100%100%100%
   SourceMapDevToolPlugin.js98.62%100%100%98.62%220, 224, 226, 419, 430, 889
   Stats.js100%100%100%100%
   Template.js100%100%100%100%
   TemplatedPathPlugin.js99.43%100%100%99.43%308–309
   UseStrictPlugin.js100%100%100%100%
   WarnCaseSensitiveModulesPlugin.js100%100%100%100%
   WarnDeprecatedOptionPlugin.js100%100%100%100%
   WarnNoModeSetPlugin.js100%100%100%100%
   WatchIgnorePlugin.js100%100%100%100%
   Watching.js100%100%100%100%
   WebpackError.js100%100%100%100%
   WebpackIsIncludedPlugin.js100%100%100%100%
   WebpackOptionsApply.js100%100%100%100%
   WebpackOptionsDefaulter.js100%100%100%100%
   buildChunkGraph.js99.87%100%100%99.87%371
   cli.js98.63%100%100%98.63%10, 119, 549, 581, 631, 905
   index.js99.72%100%100%99.72%184
   validateSchema.js94.67%100%100%94.67%100, 87, 89, 98
   webpack.js97.10%100%100%97.10%10, 263, 285, 287
lib/asset
   AssetBytesGenerator.js100%100%100%100%
   AssetBytesParser.js100%100%100%100%
   AssetGenerator.js100%100%100%100%
   AssetModule.js100%100%100%100%
   AssetModulesPlugin.js97.94%100%100%97.94%294, 318, 321, 42, 451, 47
   AssetParser.js100%100%100%100%
   AssetSourceGenerator.js100%100%100%100%
   AssetSourceParser.js100%100%100%100%
   RawDataUrlModule.js100%100%100%100%
   WebManifestGenerator.js100%100%100%100%
   WebManifestParser.js100%100%100%100%
lib/async-modules
   AsyncModuleHelpers.js100%100%100%100%
   AwaitDependenciesInitFragment.js100%100%100%100%
   InferAsyncModulesPlugin.js100%100%100%100%
   isGeneratorLowered.js100%100%100%100%
lib/bun
   BunTargetPlugin.js100%100%100%100%
lib/cache
   AddBuildDependenciesPlugin.js100%100%100%100%
   AddManagedPathsPlugin.js100%100%100%100%
   IdleFileCachePlugin.js97.92%100%100%97.92%75, 87, 95
   MemoryCachePlugin.js95.83%100%100%95.83%33
   MemoryWithGcCachePlugin.js93.15%100%100%93.15%107, 114–115, 123, 90
   PackFileCacheStrategy.js96.40%100%100%96.40%1251, 1351, 1355, 1417, 628, 647, 657–659, 661, 677–678, 683, 686, 688, 693, 698, 723, 729, 763, 769, 775, 780, 791, 800, 805–806, 808, 825, 831–832, 834
   ResolverCachePlugin.js100%100%100

@alexander-akait
alexander-akait merged commit 1bec5b6 into main Jul 16, 2026
77 of 79 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Sponsor
SponsoredKunjungi sekarang
Promo