-
Notifications
You must be signed in to change notification settings - Fork 893
Reparse CJS export assignments only when not shadowed by locals #3345
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c04b6a1
a3034a4
6415aaf
ba1e5fd
f7c5801
6b23368
a71867d
a25f65e
e87090d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -61,6 +61,13 @@ const ( | |||||||||
| jsdocScannerInfoHasSeeOrLink | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| type ShadowFlags int32 | ||||||||||
|
|
||||||||||
| const ( | ||||||||||
| ShadowFlagsModule ShadowFlags = 1 << iota // `module` identifier is shadowed | ||||||||||
| ShadowFlagsExports // `exports` identifier is shadowed | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| type Parser struct { | ||||||||||
| scanner *scanner.Scanner | ||||||||||
| factory ast.NodeFactory | ||||||||||
|
|
@@ -78,6 +85,7 @@ type Parser struct { | |||||||||
| sourceFlags ast.NodeFlags | ||||||||||
| contextFlags ast.NodeFlags | ||||||||||
| parsingContexts ParsingContexts | ||||||||||
| shadowFlags ShadowFlags | ||||||||||
| statementHasAwaitIdentifier bool | ||||||||||
| hasDeprecatedTag bool | ||||||||||
| hasParseError bool | ||||||||||
|
|
@@ -338,6 +346,7 @@ func (p *Parser) parseErrorAtRange(loc core.TextRange, message *diagnostics.Mess | |||||||||
| type ParserState struct { | ||||||||||
| scannerState scanner.ScannerState | ||||||||||
| contextFlags ast.NodeFlags | ||||||||||
| shadowFlags ShadowFlags | ||||||||||
| diagnosticsLen int | ||||||||||
| jsDiagnosticsLen int | ||||||||||
| jsdocInfosLen int | ||||||||||
|
|
@@ -350,6 +359,7 @@ func (p *Parser) mark() ParserState { | |||||||||
| return ParserState{ | ||||||||||
| scannerState: p.scanner.Mark(), | ||||||||||
| contextFlags: p.contextFlags, | ||||||||||
| shadowFlags: p.shadowFlags, | ||||||||||
| diagnosticsLen: len(p.diagnostics), | ||||||||||
| jsDiagnosticsLen: len(p.jsDiagnostics), | ||||||||||
| jsdocInfosLen: len(p.jsdocInfos), | ||||||||||
|
|
@@ -363,6 +373,7 @@ func (p *Parser) rewind(state ParserState) { | |||||||||
| p.scanner.Rewind(state.scannerState) | ||||||||||
| p.token = p.scanner.Token() | ||||||||||
| p.contextFlags = state.contextFlags | ||||||||||
| p.shadowFlags = state.shadowFlags | ||||||||||
| p.diagnostics = p.diagnostics[0:state.diagnosticsLen] | ||||||||||
| p.jsDiagnostics = p.jsDiagnostics[0:state.jsDiagnosticsLen] | ||||||||||
| p.jsdocInfos = p.jsdocInfos[0:state.jsdocInfosLen] | ||||||||||
|
|
@@ -1209,7 +1220,9 @@ func (p *Parser) parseBlock(ignoreMissingOpenBrace bool, diagnosticMessage *diag | |||||||||
| multiline := false | ||||||||||
| if openBraceParsed || ignoreMissingOpenBrace { | ||||||||||
| multiline = p.hasPrecedingLineBreak() | ||||||||||
| saveShadowFlags := p.shadowFlags | ||||||||||
| statements := p.parseList(PCBlockStatements, (*Parser).parseStatement) | ||||||||||
| p.shadowFlags = saveShadowFlags | ||||||||||
|
Comment on lines
+1223
to
+1225
|
||||||||||
| saveShadowFlags := p.shadowFlags | |
| statements := p.parseList(PCBlockStatements, (*Parser).parseStatement) | |
| p.shadowFlags = saveShadowFlags | |
| statements := p.parseList(PCBlockStatements, (*Parser).parseStatement) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a known limitation. Tracking in the parser can't fully handle hoisting because usages may precede a declaration.
Copilot
AI
Apr 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Restoring shadowFlags at the end of parsing a for (...) ... statement will drop shadowing introduced by var in the initializer, even though var bindings are visible after the loop.
Example: for (var module of xs) {} module.exports = 1; should treat module as shadowed in the subsequent statement, but the restore here will clear it and may incorrectly reparse module.exports.
Suggestion: only unwind block-scoped shadowing here, while preserving function-scoped shadowing (e.g., var bindings) so it remains in effect after the loop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, this is a known limitation.
Copilot
AI
Apr 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shadowFlags is parser-global state, so it needs to be saved/restored for all constructs that introduce a new binding scope. The current saves/restores cover blocks, for, catch, and some function/class forms, but arrow functions and switch case blocks don’t appear to reset shadowFlags when exiting their scope.
That can cause bindings like (module) => {} or switch { case 0: let exports; } to permanently set ShadowFlagsModule/Exports and suppress later CommonJS export reparsing at the outer scope.
Suggestion: apply the same save/restore pattern around arrow function parameter+body parsing and around parseCaseBlock (or otherwise centralize scope management for shadow tracking).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Arrow functions are now handled. Case blocks don't have their own scope so need no additional handing.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| //// [tests/cases/compiler/esModuleWithShadowedModuleIdentifier.ts] //// | ||
|
|
||
| === dep.js === | ||
| function something(module) { | ||
| >something : Symbol(something, Decl(dep.js, 0, 0)) | ||
| >module : Symbol(module, Decl(dep.js, 0, 19)) | ||
|
|
||
| module.exports = 8; | ||
| >module : Symbol(module, Decl(dep.js, 0, 19)) | ||
| } | ||
|
|
||
| export default 7; | ||
|
|
||
| === main.ts === | ||
| import DefaultExport from "./dep.js"; | ||
| >DefaultExport : Symbol(DefaultExport, Decl(main.ts, 0, 6)) | ||
|
|
||
| export default 3 + DefaultExport; | ||
| >DefaultExport : Symbol(DefaultExport, Decl(main.ts, 0, 6)) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type doesn't need to be exported either, technically