From fce61e8b7f8f483f1f77663334fb0782a05721de Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Fri, 30 Oct 2026 23:02:40 -0700 Subject: [PATCH 1/3] Compute whether a file is CommonJS in the parser CommonJSModuleIndicator was only set by the binder, so any reader of an unbound file saw nil and treated a CommonJS JS file as a script. In particular, canReplaceFileInProgram compares module-ness of the old (usually bound) file against the freshly parsed new file, so editing any CommonJS file always rebuilt the whole program, while a script turning into CommonJS was wrongly reused. The binder also wrote this field on files shared between programs via the parse cache. Whether a file is CommonJS is purely syntactic, so the parser now records it as SourceFile.IsCommonJSModule. Nothing read the indicator node itself, so a bool is enough. Reparsed JSDoc nodes count too, since the binder visits them. The binder no longer writes the field, but still creates the file symbol at the first CommonJS construct to keep symbol creation order, and asserts that it agrees with the parser. --- tsc/internal/ast/ast.go | 4 +- tsc/internal/ast/utilities.go | 4 +- tsc/internal/binder/binder.go | 82 +++++++++++-------- tsc/internal/binder/nameresolver.go | 2 +- tsc/internal/checker/checker.go | 4 +- tsc/internal/checker/emitresolver.go | 2 +- tsc/internal/compiler/program_test.go | 26 ++++++ tsc/internal/ls/autoimport/fix.go | 2 +- tsc/internal/parser/parser.go | 58 ++++++++++++- tsc/internal/parser/parser_test.go | 40 +++++++++ tsc/internal/parser/reparser.go | 2 + tsc/internal/project/refcountcache_test.go | 2 +- .../transformers/declarations/transform.go | 10 +-- .../moduletransforms/commonjsmodule.go | 2 +- 14 files changed, 187 insertions(+), 53 deletions(-) diff --git a/tsc/internal/ast/ast.go b/tsc/internal/ast/ast.go index 80089ab333bf3..a821de49b8d80 100644 --- a/tsc/internal/ast/ast.go +++ b/tsc/internal/ast/ast.go @@ -2417,7 +2417,7 @@ type SourceFile struct { CheckJsDirective *CheckJsDirective NodeCount int TextCount int - CommonJSModuleIndicator *Node + IsCommonJSModule bool // If this is the SourceFile itself, then this module was "forced" // to be an external module (previously "true"). ExternalModuleIndicator *Node @@ -2738,7 +2738,7 @@ func (node *SourceFile) copyFrom(other *SourceFile) { node.ReferencedFiles = other.ReferencedFiles node.TypeReferenceDirectives = other.TypeReferenceDirectives node.LibReferenceDirectives = other.LibReferenceDirectives - node.CommonJSModuleIndicator = other.CommonJSModuleIndicator + node.IsCommonJSModule = other.IsCommonJSModule node.ExternalModuleIndicator = other.ExternalModuleIndicator node.Flags |= other.Flags } diff --git a/tsc/internal/ast/utilities.go b/tsc/internal/ast/utilities.go index f178903372dc2..345acda3b9c2b 100644 --- a/tsc/internal/ast/utilities.go +++ b/tsc/internal/ast/utilities.go @@ -1737,12 +1737,12 @@ func IsExternalModule(file *SourceFile) bool { } func IsExternalOrCommonJSModule(file *SourceFile) bool { - return file.ExternalModuleIndicator != nil || file.CommonJSModuleIndicator != nil + return file.ExternalModuleIndicator != nil || file.IsCommonJSModule } // TODO: Should we deprecate `IsExternalOrCommonJSModule` in favor of this function? func IsEffectiveExternalModule(node *SourceFile, compilerOptions *core.CompilerOptions) bool { - return IsExternalModule(node) || (isCommonJSContainingModuleKind(compilerOptions.GetEmitModuleKind()) && node.CommonJSModuleIndicator != nil) + return IsExternalModule(node) || (isCommonJSContainingModuleKind(compilerOptions.GetEmitModuleKind()) && node.IsCommonJSModule) } func isCommonJSContainingModuleKind(kind core.ModuleKind) bool { diff --git a/tsc/internal/binder/binder.go b/tsc/internal/binder/binder.go index bc462e49a7f95..0383f9530ed0a 100644 --- a/tsc/internal/binder/binder.go +++ b/tsc/internal/binder/binder.go @@ -52,33 +52,34 @@ type Binder struct { bindFunc func(*ast.Node) bool unreachableFlow *ast.FlowNode - container *ast.Node - thisContainer *ast.Node - blockScopeContainer *ast.Node - lastContainer *ast.Node - currentFlow *ast.FlowNode - currentBreakTarget *ast.FlowLabel - currentContinueTarget *ast.FlowLabel - currentReturnTarget *ast.FlowLabel - currentTrueTarget *ast.FlowLabel - currentFalseTarget *ast.FlowLabel - currentExceptionTarget *ast.FlowLabel - preSwitchCaseFlow *ast.FlowNode - activeLabelList *ActiveLabel - emitFlags ast.NodeFlags - seenThisKeyword bool - hasExplicitReturn bool - hasFlowEffects bool - inAssignmentPattern bool - seenParseError bool - symbolCount int - notConstEnumOnlyModules collections.Set[*ast.Symbol] - symbolWithDataArena core.Arena[ast.SymbolWithData] - symbolExtraArena ast.SymbolExtraArena - flowNodeArena core.Arena[ast.FlowNode] - flowListArena core.Arena[ast.FlowList] - singleDeclarationsArena core.Arena[*ast.Node] - expandoAssignments []ExpandoAssignmentInfo + container *ast.Node + thisContainer *ast.Node + blockScopeContainer *ast.Node + lastContainer *ast.Node + currentFlow *ast.FlowNode + currentBreakTarget *ast.FlowLabel + currentContinueTarget *ast.FlowLabel + currentReturnTarget *ast.FlowLabel + currentTrueTarget *ast.FlowLabel + currentFalseTarget *ast.FlowLabel + currentExceptionTarget *ast.FlowLabel + preSwitchCaseFlow *ast.FlowNode + activeLabelList *ActiveLabel + emitFlags ast.NodeFlags + seenThisKeyword bool + hasExplicitReturn bool + hasFlowEffects bool + inAssignmentPattern bool + seenParseError bool + seenCommonJSModuleIndicator bool + symbolCount int + notConstEnumOnlyModules collections.Set[*ast.Symbol] + symbolWithDataArena core.Arena[ast.SymbolWithData] + symbolExtraArena ast.SymbolExtraArena + flowNodeArena core.Arena[ast.FlowNode] + flowListArena core.Arena[ast.FlowList] + singleDeclarationsArena core.Arena[*ast.Node] + expandoAssignments []ExpandoAssignmentInfo } type ActiveLabel struct { @@ -758,7 +759,7 @@ func (b *Binder) bindPropertyWorker(node *ast.Node) { func (b *Binder) bindSourceFileIfExternalModule() { b.setExportContextFlag(b.file.AsNode()) - if ast.IsExternalOrCommonJSModule(b.file) { + if b.isExternalOrCommonJSModule() { b.bindSourceFileAsExternalModule() } else if ast.IsJsonSourceFile(b.file) { b.bindSourceFileAsExternalModule() @@ -927,17 +928,21 @@ func (b *Binder) bindFunctionExpression(node *ast.Node) { func (b *Binder) bindCallExpression(node *ast.Node) { // We're only inspecting call expressions to detect CommonJS modules, so we can skip // this check if we've already seen the module indicator - if b.file.CommonJSModuleIndicator == nil && ast.IsRequireCall(node, false /*requireStringLiteralLikeArgument*/) { + if !b.seenCommonJSModuleIndicator && ast.IsRequireCall(node, false /*requireStringLiteralLikeArgument*/) { b.setCommonJSModuleIndicator(node) } } +// setCommonJSModuleIndicator reports whether node's file is a CommonJS module, which the +// parser has already determined. The file symbol is created at the first CommonJS construct +// in the walk, rather than up front, to preserve symbol creation order. func (b *Binder) setCommonJSModuleIndicator(node *ast.Node) bool { - if b.file.ExternalModuleIndicator != nil && b.file.ExternalModuleIndicator != b.file.AsNode() { + if !b.file.IsCommonJSModule { + debug.Assert(b.file.ExternalModuleIndicator != nil && b.file.ExternalModuleIndicator != b.file.AsNode(), "parser missed a CommonJS module indicator") return false } - if b.file.CommonJSModuleIndicator == nil { - b.file.CommonJSModuleIndicator = node + if !b.seenCommonJSModuleIndicator { + b.seenCommonJSModuleIndicator = true if b.file.ExternalModuleIndicator == nil { b.bindSourceFileAsExternalModule() } @@ -945,6 +950,12 @@ func (b *Binder) setCommonJSModuleIndicator(node *ast.Node) bool { return true } +// isExternalOrCommonJSModule is like ast.IsExternalOrCommonJSModule, but a CommonJS file only +// counts as a module once the walk has reached its first CommonJS construct. +func (b *Binder) isExternalOrCommonJSModule() bool { + return b.file.ExternalModuleIndicator != nil || b.seenCommonJSModuleIndicator +} + func (b *Binder) bindClassLikeDeclaration(node *ast.Node) { name := node.Name() switch node.Kind { @@ -1248,7 +1259,7 @@ func (b *Binder) bindBlockScopedDeclaration(node *ast.Node, symbolFlags ast.Symb case ast.KindModuleDeclaration: b.declareModuleMember(node, symbolFlags, symbolExcludes) case ast.KindSourceFile: - if ast.IsExternalOrCommonJSModule(b.container.AsSourceFile()) { + if b.isExternalOrCommonJSModule() { b.declareModuleMember(node, symbolFlags, symbolExcludes) break } @@ -1612,12 +1623,13 @@ func (b *Binder) bindContainer(node *ast.Node, containerFlags ContainerFlags) { b.bindBlockScopedDeclaration(statement, ast.SymbolFlagsTypeAlias, ast.SymbolFlagsTypeAliasExcludes) } } - if b.file.CommonJSModuleIndicator != nil { + debug.Assert(b.seenCommonJSModuleIndicator == b.file.IsCommonJSModule, "binder missed a CommonJS module indicator") + if b.seenCommonJSModuleIndicator { b.declareCommonJSVariable("module") b.declareCommonJSVariable("exports") } } - if ast.IsSourceFile(node) && ast.IsExternalOrCommonJSModule(node.AsSourceFile()) || ast.IsAmbientModule(node) { + if ast.IsSourceFile(node) && b.isExternalOrCommonJSModule() || ast.IsAmbientModule(node) { b.bindCommonJSTypeExports(node.Symbol()) } b.container = saveContainer diff --git a/tsc/internal/binder/nameresolver.go b/tsc/internal/binder/nameresolver.go index 4d0db524946cb..2046b42800170 100644 --- a/tsc/internal/binder/nameresolver.go +++ b/tsc/internal/binder/nameresolver.go @@ -144,7 +144,7 @@ loop: } if name != ast.InternalSymbolNameDefault { if result = r.lookup(moduleExports, name, meaning&ast.SymbolFlagsModuleMember); result != nil { - if ast.IsSourceFile(location) && location.AsSourceFile().CommonJSModuleIndicator != nil && result.Flags()&ast.SymbolFlagsType == 0 { + if ast.IsSourceFile(location) && location.AsSourceFile().IsCommonJSModule && result.Flags()&ast.SymbolFlagsType == 0 { result = nil } else { break loop diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 5a5a155797c24..f256585c2a675 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -12309,7 +12309,7 @@ func (c *Checker) getContextualThisParameterType(fn *ast.Node) *Type { // Don't contextually type `this` as `exports` in `exports.Point = function(x, y) { this.x = x; this.y = y; }` if inJs && ast.IsIdentifier(expression) { sourceFile := ast.GetSourceFileOfNode(parent) - if sourceFile.CommonJSModuleIndicator != nil && c.getResolvedSymbol(expression).Flags()&ast.SymbolFlagsModuleExports != 0 { + if sourceFile.IsCommonJSModule && c.getResolvedSymbol(expression).Flags()&ast.SymbolFlagsModuleExports != 0 { return nil } } @@ -17341,7 +17341,7 @@ func (c *Checker) getTypeOfFuncClassEnumModuleWorker(symbol *ast.Symbol) *Type { if symbol.Flags()&ast.SymbolFlagsModule != 0 && isShorthandAmbientModuleSymbol(symbol) { return c.anyType } else if symbol.Flags()&ast.SymbolFlagsValueModule != 0 && symbol.ValueDeclaration() != nil && - ast.IsSourceFile(symbol.ValueDeclaration()) && symbol.ValueDeclaration().AsSourceFile().CommonJSModuleIndicator != nil { + ast.IsSourceFile(symbol.ValueDeclaration()) && symbol.ValueDeclaration().AsSourceFile().IsCommonJSModule { resolvedModule := c.resolveExternalModuleSymbol(symbol, false /*dontResolveAlias*/) if resolvedModule != symbol { return c.getTypeOfSymbol(resolvedModule) diff --git a/tsc/internal/checker/emitresolver.go b/tsc/internal/checker/emitresolver.go index e4fad5c3f5b1f..118e7dbef6b5f 100644 --- a/tsc/internal/checker/emitresolver.go +++ b/tsc/internal/checker/emitresolver.go @@ -141,7 +141,7 @@ func (r *EmitResolver) PrecalculateDeclarationEmitVisibility(file *ast.SourceFil func isCommonJSModuleExports(node *ast.Node) bool { if ast.IsBinaryExpression(node) && ast.IsExpressionStatement(node.Parent) && ast.IsSourceFile(node.Parent.Parent) && - node.Parent.Parent.AsSourceFile().CommonJSModuleIndicator != nil { + node.Parent.Parent.AsSourceFile().IsCommonJSModule { switch ast.GetAssignmentDeclarationKind(node) { case ast.JSDeclarationKindModuleExports, ast.JSDeclarationKindExportsProperty: return true diff --git a/tsc/internal/compiler/program_test.go b/tsc/internal/compiler/program_test.go index 4ef0537403903..df16384ac5576 100644 --- a/tsc/internal/compiler/program_test.go +++ b/tsc/internal/compiler/program_test.go @@ -621,3 +621,29 @@ func BenchmarkNewProgram(b *testing.B) { } }) } + +func TestUpdateProgramCommonJSFile(t *testing.T) { + t.Parallel() + files := map[string]any{ + "/src/tsconfig.json": `{"compilerOptions":{"allowJs":true,"noLib":true},"files":["index.js"]}`, + "/src/index.js": "exports.a = 1;", + } + fs := vfstest.FromMap(files, tspath.CaseSensitive) + config, diagnostics := tsoptions.GetParsedCommandLineOfConfigFile("/src/tsconfig.json", nil, nil, tsoptions.NewDefaultParseConfigHost(fs), nil) + assert.Equal(t, len(diagnostics), 0) + program := NewProgram(ProgramOptions{Config: config, Host: NewCompilerHost(fs, "/", nil, nil, nil)}) + program.BindSourceFiles() + + update := func(content string) bool { + files["/src/index.js"] = content + host := NewCompilerHost(vfstest.FromMap(files, tspath.CaseSensitive), "/", nil, nil, nil) + var reused bool + program, _, reused = program.UpdateProgram("/src/index.js", host, nil, nil) + program.BindSourceFiles() + return reused + } + + assert.Assert(t, update("exports.a = 2;"), "editing a CommonJS file should reuse the program") + assert.Assert(t, update("var a = 2;") == false, "a CommonJS file becoming a script should not reuse the program") + assert.Assert(t, update("exports.a = 2;") == false, "a script becoming a CommonJS file should not reuse the program") +} diff --git a/tsc/internal/ls/autoimport/fix.go b/tsc/internal/ls/autoimport/fix.go index 1333ef59cc134..d5708d66406b0 100644 --- a/tsc/internal/ls/autoimport/fix.go +++ b/tsc/internal/ls/autoimport/fix.go @@ -922,7 +922,7 @@ func detectSyntax(file *ast.SourceFile, options *core.CompilerOptions) fileSynta // ExternalModuleIndicator may be the source file itself rather than a real // statement, so we look at Imports() for actual import/export declarations. func detectSyntaxIndicators(file *ast.SourceFile, options *core.CompilerOptions) (hasESM bool, hasCJS bool) { - hasCJS = file.CommonJSModuleIndicator != nil + hasCJS = file.IsCommonJSModule if options.GetEmitModuleDetectionKind() != core.ModuleDetectionKindForce { // ExternalModuleIndicator is reliable when moduleDetection is not "force" hasESM = file.ExternalModuleIndicator != nil diff --git a/tsc/internal/parser/parser.go b/tsc/internal/parser/parser.go index c670d64ad21c2..24055f132c5b8 100644 --- a/tsc/internal/parser/parser.go +++ b/tsc/internal/parser/parser.go @@ -98,6 +98,9 @@ type Parser struct { currentParent *ast.Node setParentFromContext ast.Visitor reparsedClones []*ast.Node + + hasCommonJSConstruct bool + jsdocHasCommonJSConstruct bool } func newParser() *Parser { @@ -346,6 +349,8 @@ type ParserState struct { reparsedClonesLen int statementHasAwaitIdentifier bool hasParseError bool + hasCommonJSConstruct bool + jsdocHasCommonJSConstruct bool } func (p *Parser) mark() ParserState { @@ -358,6 +363,8 @@ func (p *Parser) mark() ParserState { reparsedClonesLen: len(p.reparsedClones), statementHasAwaitIdentifier: p.statementHasAwaitIdentifier, hasParseError: p.hasParseError, + hasCommonJSConstruct: p.hasCommonJSConstruct, + jsdocHasCommonJSConstruct: p.jsdocHasCommonJSConstruct, } } @@ -371,6 +378,8 @@ func (p *Parser) rewind(state ParserState) { p.reparsedClones = p.reparsedClones[0:state.reparsedClonesLen] p.statementHasAwaitIdentifier = state.statementHasAwaitIdentifier p.hasParseError = state.hasParseError + p.hasCommonJSConstruct = state.hasCommonJSConstruct + p.jsdocHasCommonJSConstruct = state.jsdocHasCommonJSConstruct } func (p *Parser) lookAhead(callback func(p *Parser) bool) bool { @@ -451,6 +460,10 @@ func (p *Parser) parseSourceFileWorker() *ast.SourceFile { if !result.IsDeclarationFile && result.ExternalModuleIndicator != nil && len(p.possibleAwaitSpans) > 0 { reparse := p.finishNode(p.reparseTopLevelAwait(result), pos) if node != reparse { + // Reparsing can change which CommonJS constructs exist, so recompute from the final tree. + if p.contextFlags&ast.NodeFlagsJavaScriptFile != 0 { + p.hasCommonJSConstruct = containsCommonJSModuleIndicator(reparse) + } result = reparse.AsSourceFile() p.finishSourceFile(result, isDeclarationFile) } @@ -483,6 +496,47 @@ func (p *Parser) finishSourceFile(result *ast.SourceFile, isDeclarationFile bool slices.SortFunc(p.reparsedClones, ast.CompareNodePositions) result.ReparsedClones = slices.Clone(p.reparsedClones) ast.SetExternalModuleIndicator(result, p.opts.ExternalModuleIndicatorOptions) + // CommonJS constructs only make a file a module when it has no ES module syntax. + if result.ExternalModuleIndicator == nil || result.ExternalModuleIndicator == result.AsNode() { + if !p.hasCommonJSConstruct && p.jsdocHasCommonJSConstruct { + // Only reparsed JSDoc clones that survive into the tree are bound. + p.hasCommonJSConstruct = containsCommonJSModuleIndicator(result.AsNode()) + } + result.IsCommonJSModule = p.hasCommonJSConstruct + } else { + result.IsCommonJSModule = false + } +} + +// checkCommonJSModuleIndicator records whether a JavaScript file contains a CommonJS construct +// (a require call, or an assignment or Object.defineProperty on exports or module.exports). +// The binder binds the same constructs as CommonJS exports. Nodes parsed within JSDoc are +// only bound if reparsed, so for those we just note that the final tree needs checking. +func (p *Parser) checkCommonJSModuleIndicator(node *ast.Node) *ast.Node { + if !p.hasCommonJSConstruct && node.Flags&ast.NodeFlagsJavaScriptFile != 0 && isCommonJSModuleIndicator(node) { + if node.Flags&ast.NodeFlagsJSDoc == 0 { + p.hasCommonJSConstruct = true + } else { + p.jsdocHasCommonJSConstruct = true + } + } + return node +} + +func containsCommonJSModuleIndicator(node *ast.Node) bool { + var visit func(*ast.Node) bool + visit = func(n *ast.Node) bool { + return isCommonJSModuleIndicator(n) || n.ForEachChild(visit) + } + return visit(node) +} + +func isCommonJSModuleIndicator(node *ast.Node) bool { + switch ast.GetAssignmentDeclarationKind(node) { + case ast.JSDeclarationKindModuleExports, ast.JSDeclarationKindExportsProperty, ast.JSDeclarationKindObjectDefinePropertyExports: + return true + } + return ast.IsRequireCall(node, false /*requireStringLiteralLikeArgument*/) } func (p *Parser) createJSDocCache() map[*ast.Node][]*ast.Node { @@ -4740,7 +4794,7 @@ func (p *Parser) makeAsExpression(left *ast.Expression, right *ast.TypeNode) *as } func (p *Parser) makeBinaryExpression(left *ast.Expression, operatorToken *ast.Node, right *ast.Expression, pos int) *ast.Node { - return p.finishNode(p.factory.NewBinaryExpression(nil /*modifiers*/, left, nil /*typeNode*/, operatorToken, right), pos) + return p.checkCommonJSModuleIndicator(p.finishNode(p.factory.NewBinaryExpression(nil /*modifiers*/, left, nil /*typeNode*/, operatorToken, right), pos)) } func (p *Parser) parseUnaryExpressionOrHigher() *ast.Expression { @@ -5556,7 +5610,7 @@ func (p *Parser) parseCallExpressionRest(pos int, expression *ast.Expression) *a inner := expression argumentList := p.parseArgumentList() isOptionalChain := questionDotToken != nil || p.tryReparseOptionalChain(expression) - expression = p.checkJSSyntax(p.finishNode(p.factory.NewCallExpression(expression, questionDotToken, typeArguments, argumentList, core.IfElse(isOptionalChain, ast.NodeFlagsOptionalChain, ast.NodeFlagsNone)), pos)) + expression = p.checkCommonJSModuleIndicator(p.checkJSSyntax(p.finishNode(p.factory.NewCallExpression(expression, questionDotToken, typeArguments, argumentList, core.IfElse(isOptionalChain, ast.NodeFlagsOptionalChain, ast.NodeFlagsNone)), pos))) p.unparseExpressionWithTypeArguments(inner, typeArguments, expression) continue } diff --git a/tsc/internal/parser/parser_test.go b/tsc/internal/parser/parser_test.go index ffa96713eb444..a6aaff87ff5d7 100644 --- a/tsc/internal/parser/parser_test.go +++ b/tsc/internal/parser/parser_test.go @@ -571,3 +571,43 @@ namespace N { assert.Equal(t, positionMap.UTF8ToUTF16(afterBoxDrawingCharacter), afterBoxDrawingCharacter-2) assert.Equal(t, positionMap.UTF8ToUTF16(len(sourceText)), len(sourceText)-2) } + +func TestCommonJSModuleIndicator(t *testing.T) { + t.Parallel() + tests := []struct { + fileName tspath.RootedFilePath + source string + expected bool + }{ + {"/a.js", `const x = require("x");`, true}, + {"/a.js", `module.exports = {};`, true}, + {"/a.js", `exports.x = 1;`, true}, + {"/a.js", `module.exports.x = 1;`, true}, + {"/a.js", `Object.defineProperty(exports, "x", { value: 1 });`, true}, + {"/a.js", `function f() { exports.x = 1; }`, true}, + {"/a.js", `var x = 1; foo(x);`, false}, + {"/a.js", `/** @type {{ [require("x")]: number }} */ var x;`, true}, + {"/a.js", `/** @param {number} [x=require("x")] */ function f(x) {}`, false}, + {"/a.js", "/**\n * @type {{ [require('x')]: () => void }}\n * @type {() => void}\n */\nfunction f() {}", false}, + {"/a.js", "/**\n * @type {{ [require('x')]: number }}\n * @type {number}\n */\nthis.x = 1;", false}, + {"/a.js", "/**\n * @template {{ [require('x')]: number }} T\n * @typedef\n */\nfunction f() {}", false}, + {"/a.js", `exports.x = 1; export {};`, false}, + {"/a.js", `export const y = require("x");`, false}, + {"/a.js", `/** @satisfies {any} */ module.exports = exports;`, true}, + {"/a.js", "await / import.meta / 1;\nawait / require('x') / 1;", false}, + {"/a.js", `await / 1 ; export {}; / ; require('x');`, true}, + {"/a.cjs", `exports.x = 1;`, true}, + {"/a.cjs", `await / 1 ; export {}; / ; require('x');`, true}, + {"/a.mjs", `await /require("x")/;`, false}, + {"/a.ts", `exports.x = 1; const y = require("x");`, false}, + } + for _, test := range tests { + t.Run(test.fileName.AsString()+" "+test.source, func(t *testing.T) { + t.Parallel() + opts := ast.SourceFileParseOptions{FileName: test.fileName, PathKey: tspath.CaseSensitive.PathKey(test.fileName.AsPath())} + opts.ExternalModuleIndicatorOptions.Force = test.fileName.ExtensionIsOneOf([]string{tspath.ExtensionCjs, tspath.ExtensionMjs}) + file := parser.ParseSourceFile(opts, test.source, core.GetScriptKindFromFileName(test.fileName)) + assert.Equal(t, file.IsCommonJSModule, test.expected) + }) + } +} diff --git a/tsc/internal/parser/reparser.go b/tsc/internal/parser/reparser.go index bd0e5e4d04588..ef420f7e19971 100644 --- a/tsc/internal/parser/reparser.go +++ b/tsc/internal/parser/reparser.go @@ -18,6 +18,8 @@ func (p *Parser) finishReparsedNode(node *ast.Node, locationNode *ast.Node) { func (p *Parser) finishMutatedNode(node *ast.Node) { p.overrideParentInImmediateChildren(node) + // Wrapping an assignment's right side can make it a CommonJS export assignment. + p.checkCommonJSModuleIndicator(node) } // Deep-clone the given node and add the clone to the reparsed clone list. The list is used by ast.GetReparsedNodeForNode diff --git a/tsc/internal/project/refcountcache_test.go b/tsc/internal/project/refcountcache_test.go index 396a1a3921878..87aeebc9afb80 100644 --- a/tsc/internal/project/refcountcache_test.go +++ b/tsc/internal/project/refcountcache_test.go @@ -86,7 +86,7 @@ func TestParseCacheBindsBeforePublishing(t *testing.T) { defer cache.Deref(key) assert.Assert(t, file.IsBound()) - assert.Assert(t, file.CommonJSModuleIndicator != nil) + assert.Assert(t, file.IsCommonJSModule) } func TestParseCacheAcquireExistingUsesFullKey(t *testing.T) { diff --git a/tsc/internal/transformers/declarations/transform.go b/tsc/internal/transformers/declarations/transform.go index 8b5b449ec87f5..9a71b68e351a4 100644 --- a/tsc/internal/transformers/declarations/transform.go +++ b/tsc/internal/transformers/declarations/transform.go @@ -834,7 +834,7 @@ func (tx *DeclarationTransformer) transformTypeParameterDeclaration(input *ast.T } func (tx *DeclarationTransformer) transformVariableDeclaration(input *ast.VariableDeclaration) *ast.Node { - if tx.state.currentSourceFile.CommonJSModuleIndicator != nil && ast.IsVariableDeclarationInitializedToRequire(input.AsNode()) { + if tx.state.currentSourceFile.IsCommonJSModule && ast.IsVariableDeclarationInitializedToRequire(input.AsNode()) { return tx.transformCjsRequireVariableDeclaration(input) } if ast.IsBindingPattern(input.Name()) && hasAnyBindingInitializers(input.Name().AsBindingPattern()) { @@ -2253,7 +2253,7 @@ func (tx *DeclarationTransformer) transformVariableStatement(input *ast.Variable inputNodes := input.DeclarationList.AsVariableDeclarationList().Declarations.Nodes var extraImports []*ast.Node - if tx.state.currentSourceFile.CommonJSModuleIndicator != nil { + if tx.state.currentSourceFile.IsCommonJSModule { var normalDeclarations []*ast.Node var imports []*ast.Node for _, n := range inputNodes { @@ -2710,7 +2710,7 @@ func (tx *DeclarationTransformer) visitCJSExportAssignments(expression *ast.Node defer cleanupDiagnosticContext() switch ast.GetAssignmentDeclarationKind(expression) { case ast.JSDeclarationKindModuleExports: - if tx.state.currentSourceFile.CommonJSModuleIndicator != nil { + if tx.state.currentSourceFile.IsCommonJSModule { result := tx.transformExportAssignment(expression.Parent, expression, expression.AsBinaryExpression().Right, true /*isExportEquals*/) if result != nil { tx.cjsExportAssignment = result @@ -2732,14 +2732,14 @@ func (tx *DeclarationTransformer) visitNestedExpression(expression *ast.Node) *a case ast.JSDeclarationKindProperty: tx.transformExpandoAssignment(expression.AsBinaryExpression()) case ast.JSDeclarationKindExportsProperty: - if tx.state.currentSourceFile.CommonJSModuleIndicator != nil { + if tx.state.currentSourceFile.IsCommonJSModule { result := tx.transformCommonJSExport(expression, tx.getNameExpressionPreferringIdentifier(ast.GetElementOrPropertyAccessName(expression.AsBinaryExpression().Left))) if result != nil { tx.cjsExportMembers = append(tx.cjsExportMembers, result) } } case ast.JSDeclarationKindObjectDefinePropertyExports: - if tx.state.currentSourceFile.CommonJSModuleIndicator != nil { + if tx.state.currentSourceFile.IsCommonJSModule { result := tx.transformCommonJSExport(expression, tx.getNameExpressionPreferringIdentifier(expression.Arguments()[1])) if result != nil { tx.cjsExportMembers = append(tx.cjsExportMembers, result) diff --git a/tsc/internal/transformers/moduletransforms/commonjsmodule.go b/tsc/internal/transformers/moduletransforms/commonjsmodule.go index 8ee78bbea8543..ab58a42ae574d 100644 --- a/tsc/internal/transformers/moduletransforms/commonjsmodule.go +++ b/tsc/internal/transformers/moduletransforms/commonjsmodule.go @@ -244,7 +244,7 @@ func (tx *CommonJSModuleTransformer) visitSourceFile(node *ast.SourceFile) *ast. func (tx *CommonJSModuleTransformer) shouldEmitUnderscoreUnderscoreESModule() bool { if tx.currentSourceFile.FileName().ExtensionIsOneOf(tspath.SupportedJSExtensionsFlat) && - tx.currentSourceFile.CommonJSModuleIndicator != nil && + tx.currentSourceFile.IsCommonJSModule && (tx.currentSourceFile.ExternalModuleIndicator == nil || tx.currentSourceFile.ExternalModuleIndicator.Kind == ast.KindSourceFile) { return false } From 7dced3650707af0c31988d5ab7e4869c674f56d0 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Sat, 31 Oct 2026 13:23:38 -0700 Subject: [PATCH 2/3] Avoid reflowing Binder struct fields --- tsc/internal/binder/binder.go | 55 ++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/tsc/internal/binder/binder.go b/tsc/internal/binder/binder.go index 0383f9530ed0a..210a78907718f 100644 --- a/tsc/internal/binder/binder.go +++ b/tsc/internal/binder/binder.go @@ -52,34 +52,35 @@ type Binder struct { bindFunc func(*ast.Node) bool unreachableFlow *ast.FlowNode - container *ast.Node - thisContainer *ast.Node - blockScopeContainer *ast.Node - lastContainer *ast.Node - currentFlow *ast.FlowNode - currentBreakTarget *ast.FlowLabel - currentContinueTarget *ast.FlowLabel - currentReturnTarget *ast.FlowLabel - currentTrueTarget *ast.FlowLabel - currentFalseTarget *ast.FlowLabel - currentExceptionTarget *ast.FlowLabel - preSwitchCaseFlow *ast.FlowNode - activeLabelList *ActiveLabel - emitFlags ast.NodeFlags - seenThisKeyword bool - hasExplicitReturn bool - hasFlowEffects bool - inAssignmentPattern bool - seenParseError bool + container *ast.Node + thisContainer *ast.Node + blockScopeContainer *ast.Node + lastContainer *ast.Node + currentFlow *ast.FlowNode + currentBreakTarget *ast.FlowLabel + currentContinueTarget *ast.FlowLabel + currentReturnTarget *ast.FlowLabel + currentTrueTarget *ast.FlowLabel + currentFalseTarget *ast.FlowLabel + currentExceptionTarget *ast.FlowLabel + preSwitchCaseFlow *ast.FlowNode + activeLabelList *ActiveLabel + emitFlags ast.NodeFlags + seenThisKeyword bool + hasExplicitReturn bool + hasFlowEffects bool + inAssignmentPattern bool + seenParseError bool + symbolCount int + notConstEnumOnlyModules collections.Set[*ast.Symbol] + symbolWithDataArena core.Arena[ast.SymbolWithData] + symbolExtraArena ast.SymbolExtraArena + flowNodeArena core.Arena[ast.FlowNode] + flowListArena core.Arena[ast.FlowList] + singleDeclarationsArena core.Arena[*ast.Node] + expandoAssignments []ExpandoAssignmentInfo + seenCommonJSModuleIndicator bool - symbolCount int - notConstEnumOnlyModules collections.Set[*ast.Symbol] - symbolWithDataArena core.Arena[ast.SymbolWithData] - symbolExtraArena ast.SymbolExtraArena - flowNodeArena core.Arena[ast.FlowNode] - flowListArena core.Arena[ast.FlowList] - singleDeclarationsArena core.Arena[*ast.Node] - expandoAssignments []ExpandoAssignmentInfo } type ActiveLabel struct { From 120b4650bd5210bcb97d5cc77df4e57859009803 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Sat, 31 Oct 2026 13:31:56 -0700 Subject: [PATCH 3/3] Keep CommonJSModuleIndicator as a node Go back to storing the indicator node rather than a bool. TypeScript 6.0 reported the "File is a CommonJS module; it may be converted to an ES module" suggestion at this node, and keeping it avoids having to find the construct again if that suggestion is ported. --- tsc/internal/ast/ast.go | 4 +-- tsc/internal/ast/utilities.go | 4 +-- tsc/internal/binder/binder.go | 4 +-- tsc/internal/binder/nameresolver.go | 2 +- tsc/internal/checker/checker.go | 4 +-- tsc/internal/checker/emitresolver.go | 2 +- tsc/internal/ls/autoimport/fix.go | 2 +- tsc/internal/parser/parser.go | 36 +++++++++++-------- tsc/internal/parser/parser_test.go | 2 +- tsc/internal/project/refcountcache_test.go | 2 +- .../transformers/declarations/transform.go | 10 +++--- .../moduletransforms/commonjsmodule.go | 2 +- 12 files changed, 40 insertions(+), 34 deletions(-) diff --git a/tsc/internal/ast/ast.go b/tsc/internal/ast/ast.go index a821de49b8d80..80089ab333bf3 100644 --- a/tsc/internal/ast/ast.go +++ b/tsc/internal/ast/ast.go @@ -2417,7 +2417,7 @@ type SourceFile struct { CheckJsDirective *CheckJsDirective NodeCount int TextCount int - IsCommonJSModule bool + CommonJSModuleIndicator *Node // If this is the SourceFile itself, then this module was "forced" // to be an external module (previously "true"). ExternalModuleIndicator *Node @@ -2738,7 +2738,7 @@ func (node *SourceFile) copyFrom(other *SourceFile) { node.ReferencedFiles = other.ReferencedFiles node.TypeReferenceDirectives = other.TypeReferenceDirectives node.LibReferenceDirectives = other.LibReferenceDirectives - node.IsCommonJSModule = other.IsCommonJSModule + node.CommonJSModuleIndicator = other.CommonJSModuleIndicator node.ExternalModuleIndicator = other.ExternalModuleIndicator node.Flags |= other.Flags } diff --git a/tsc/internal/ast/utilities.go b/tsc/internal/ast/utilities.go index 345acda3b9c2b..f178903372dc2 100644 --- a/tsc/internal/ast/utilities.go +++ b/tsc/internal/ast/utilities.go @@ -1737,12 +1737,12 @@ func IsExternalModule(file *SourceFile) bool { } func IsExternalOrCommonJSModule(file *SourceFile) bool { - return file.ExternalModuleIndicator != nil || file.IsCommonJSModule + return file.ExternalModuleIndicator != nil || file.CommonJSModuleIndicator != nil } // TODO: Should we deprecate `IsExternalOrCommonJSModule` in favor of this function? func IsEffectiveExternalModule(node *SourceFile, compilerOptions *core.CompilerOptions) bool { - return IsExternalModule(node) || (isCommonJSContainingModuleKind(compilerOptions.GetEmitModuleKind()) && node.IsCommonJSModule) + return IsExternalModule(node) || (isCommonJSContainingModuleKind(compilerOptions.GetEmitModuleKind()) && node.CommonJSModuleIndicator != nil) } func isCommonJSContainingModuleKind(kind core.ModuleKind) bool { diff --git a/tsc/internal/binder/binder.go b/tsc/internal/binder/binder.go index 210a78907718f..eb03bab1fd282 100644 --- a/tsc/internal/binder/binder.go +++ b/tsc/internal/binder/binder.go @@ -938,7 +938,7 @@ func (b *Binder) bindCallExpression(node *ast.Node) { // parser has already determined. The file symbol is created at the first CommonJS construct // in the walk, rather than up front, to preserve symbol creation order. func (b *Binder) setCommonJSModuleIndicator(node *ast.Node) bool { - if !b.file.IsCommonJSModule { + if b.file.CommonJSModuleIndicator == nil { debug.Assert(b.file.ExternalModuleIndicator != nil && b.file.ExternalModuleIndicator != b.file.AsNode(), "parser missed a CommonJS module indicator") return false } @@ -1624,7 +1624,7 @@ func (b *Binder) bindContainer(node *ast.Node, containerFlags ContainerFlags) { b.bindBlockScopedDeclaration(statement, ast.SymbolFlagsTypeAlias, ast.SymbolFlagsTypeAliasExcludes) } } - debug.Assert(b.seenCommonJSModuleIndicator == b.file.IsCommonJSModule, "binder missed a CommonJS module indicator") + debug.Assert(b.seenCommonJSModuleIndicator == (b.file.CommonJSModuleIndicator != nil), "binder missed a CommonJS module indicator") if b.seenCommonJSModuleIndicator { b.declareCommonJSVariable("module") b.declareCommonJSVariable("exports") diff --git a/tsc/internal/binder/nameresolver.go b/tsc/internal/binder/nameresolver.go index 2046b42800170..4d0db524946cb 100644 --- a/tsc/internal/binder/nameresolver.go +++ b/tsc/internal/binder/nameresolver.go @@ -144,7 +144,7 @@ loop: } if name != ast.InternalSymbolNameDefault { if result = r.lookup(moduleExports, name, meaning&ast.SymbolFlagsModuleMember); result != nil { - if ast.IsSourceFile(location) && location.AsSourceFile().IsCommonJSModule && result.Flags()&ast.SymbolFlagsType == 0 { + if ast.IsSourceFile(location) && location.AsSourceFile().CommonJSModuleIndicator != nil && result.Flags()&ast.SymbolFlagsType == 0 { result = nil } else { break loop diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index f256585c2a675..5a5a155797c24 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -12309,7 +12309,7 @@ func (c *Checker) getContextualThisParameterType(fn *ast.Node) *Type { // Don't contextually type `this` as `exports` in `exports.Point = function(x, y) { this.x = x; this.y = y; }` if inJs && ast.IsIdentifier(expression) { sourceFile := ast.GetSourceFileOfNode(parent) - if sourceFile.IsCommonJSModule && c.getResolvedSymbol(expression).Flags()&ast.SymbolFlagsModuleExports != 0 { + if sourceFile.CommonJSModuleIndicator != nil && c.getResolvedSymbol(expression).Flags()&ast.SymbolFlagsModuleExports != 0 { return nil } } @@ -17341,7 +17341,7 @@ func (c *Checker) getTypeOfFuncClassEnumModuleWorker(symbol *ast.Symbol) *Type { if symbol.Flags()&ast.SymbolFlagsModule != 0 && isShorthandAmbientModuleSymbol(symbol) { return c.anyType } else if symbol.Flags()&ast.SymbolFlagsValueModule != 0 && symbol.ValueDeclaration() != nil && - ast.IsSourceFile(symbol.ValueDeclaration()) && symbol.ValueDeclaration().AsSourceFile().IsCommonJSModule { + ast.IsSourceFile(symbol.ValueDeclaration()) && symbol.ValueDeclaration().AsSourceFile().CommonJSModuleIndicator != nil { resolvedModule := c.resolveExternalModuleSymbol(symbol, false /*dontResolveAlias*/) if resolvedModule != symbol { return c.getTypeOfSymbol(resolvedModule) diff --git a/tsc/internal/checker/emitresolver.go b/tsc/internal/checker/emitresolver.go index 118e7dbef6b5f..e4fad5c3f5b1f 100644 --- a/tsc/internal/checker/emitresolver.go +++ b/tsc/internal/checker/emitresolver.go @@ -141,7 +141,7 @@ func (r *EmitResolver) PrecalculateDeclarationEmitVisibility(file *ast.SourceFil func isCommonJSModuleExports(node *ast.Node) bool { if ast.IsBinaryExpression(node) && ast.IsExpressionStatement(node.Parent) && ast.IsSourceFile(node.Parent.Parent) && - node.Parent.Parent.AsSourceFile().IsCommonJSModule { + node.Parent.Parent.AsSourceFile().CommonJSModuleIndicator != nil { switch ast.GetAssignmentDeclarationKind(node) { case ast.JSDeclarationKindModuleExports, ast.JSDeclarationKindExportsProperty: return true diff --git a/tsc/internal/ls/autoimport/fix.go b/tsc/internal/ls/autoimport/fix.go index d5708d66406b0..1333ef59cc134 100644 --- a/tsc/internal/ls/autoimport/fix.go +++ b/tsc/internal/ls/autoimport/fix.go @@ -922,7 +922,7 @@ func detectSyntax(file *ast.SourceFile, options *core.CompilerOptions) fileSynta // ExternalModuleIndicator may be the source file itself rather than a real // statement, so we look at Imports() for actual import/export declarations. func detectSyntaxIndicators(file *ast.SourceFile, options *core.CompilerOptions) (hasESM bool, hasCJS bool) { - hasCJS = file.IsCommonJSModule + hasCJS = file.CommonJSModuleIndicator != nil if options.GetEmitModuleDetectionKind() != core.ModuleDetectionKindForce { // ExternalModuleIndicator is reliable when moduleDetection is not "force" hasESM = file.ExternalModuleIndicator != nil diff --git a/tsc/internal/parser/parser.go b/tsc/internal/parser/parser.go index 24055f132c5b8..82c36656ddd50 100644 --- a/tsc/internal/parser/parser.go +++ b/tsc/internal/parser/parser.go @@ -99,7 +99,7 @@ type Parser struct { setParentFromContext ast.Visitor reparsedClones []*ast.Node - hasCommonJSConstruct bool + commonJSModuleIndicator *ast.Node jsdocHasCommonJSConstruct bool } @@ -349,7 +349,7 @@ type ParserState struct { reparsedClonesLen int statementHasAwaitIdentifier bool hasParseError bool - hasCommonJSConstruct bool + commonJSModuleIndicator *ast.Node jsdocHasCommonJSConstruct bool } @@ -363,7 +363,7 @@ func (p *Parser) mark() ParserState { reparsedClonesLen: len(p.reparsedClones), statementHasAwaitIdentifier: p.statementHasAwaitIdentifier, hasParseError: p.hasParseError, - hasCommonJSConstruct: p.hasCommonJSConstruct, + commonJSModuleIndicator: p.commonJSModuleIndicator, jsdocHasCommonJSConstruct: p.jsdocHasCommonJSConstruct, } } @@ -378,7 +378,7 @@ func (p *Parser) rewind(state ParserState) { p.reparsedClones = p.reparsedClones[0:state.reparsedClonesLen] p.statementHasAwaitIdentifier = state.statementHasAwaitIdentifier p.hasParseError = state.hasParseError - p.hasCommonJSConstruct = state.hasCommonJSConstruct + p.commonJSModuleIndicator = state.commonJSModuleIndicator p.jsdocHasCommonJSConstruct = state.jsdocHasCommonJSConstruct } @@ -462,7 +462,7 @@ func (p *Parser) parseSourceFileWorker() *ast.SourceFile { if node != reparse { // Reparsing can change which CommonJS constructs exist, so recompute from the final tree. if p.contextFlags&ast.NodeFlagsJavaScriptFile != 0 { - p.hasCommonJSConstruct = containsCommonJSModuleIndicator(reparse) + p.commonJSModuleIndicator = findCommonJSModuleIndicator(reparse) } result = reparse.AsSourceFile() p.finishSourceFile(result, isDeclarationFile) @@ -498,24 +498,24 @@ func (p *Parser) finishSourceFile(result *ast.SourceFile, isDeclarationFile bool ast.SetExternalModuleIndicator(result, p.opts.ExternalModuleIndicatorOptions) // CommonJS constructs only make a file a module when it has no ES module syntax. if result.ExternalModuleIndicator == nil || result.ExternalModuleIndicator == result.AsNode() { - if !p.hasCommonJSConstruct && p.jsdocHasCommonJSConstruct { + if p.commonJSModuleIndicator == nil && p.jsdocHasCommonJSConstruct { // Only reparsed JSDoc clones that survive into the tree are bound. - p.hasCommonJSConstruct = containsCommonJSModuleIndicator(result.AsNode()) + p.commonJSModuleIndicator = findCommonJSModuleIndicator(result.AsNode()) } - result.IsCommonJSModule = p.hasCommonJSConstruct + result.CommonJSModuleIndicator = p.commonJSModuleIndicator } else { - result.IsCommonJSModule = false + result.CommonJSModuleIndicator = nil } } -// checkCommonJSModuleIndicator records whether a JavaScript file contains a CommonJS construct +// checkCommonJSModuleIndicator records the first CommonJS construct in a JavaScript file // (a require call, or an assignment or Object.defineProperty on exports or module.exports). // The binder binds the same constructs as CommonJS exports. Nodes parsed within JSDoc are // only bound if reparsed, so for those we just note that the final tree needs checking. func (p *Parser) checkCommonJSModuleIndicator(node *ast.Node) *ast.Node { - if !p.hasCommonJSConstruct && node.Flags&ast.NodeFlagsJavaScriptFile != 0 && isCommonJSModuleIndicator(node) { + if p.commonJSModuleIndicator == nil && node.Flags&ast.NodeFlagsJavaScriptFile != 0 && isCommonJSModuleIndicator(node) { if node.Flags&ast.NodeFlagsJSDoc == 0 { - p.hasCommonJSConstruct = true + p.commonJSModuleIndicator = node } else { p.jsdocHasCommonJSConstruct = true } @@ -523,12 +523,18 @@ func (p *Parser) checkCommonJSModuleIndicator(node *ast.Node) *ast.Node { return node } -func containsCommonJSModuleIndicator(node *ast.Node) bool { +func findCommonJSModuleIndicator(node *ast.Node) *ast.Node { + var found *ast.Node var visit func(*ast.Node) bool visit = func(n *ast.Node) bool { - return isCommonJSModuleIndicator(n) || n.ForEachChild(visit) + if isCommonJSModuleIndicator(n) { + found = n + return true + } + return n.ForEachChild(visit) } - return visit(node) + visit(node) + return found } func isCommonJSModuleIndicator(node *ast.Node) bool { diff --git a/tsc/internal/parser/parser_test.go b/tsc/internal/parser/parser_test.go index a6aaff87ff5d7..839e9fc52f377 100644 --- a/tsc/internal/parser/parser_test.go +++ b/tsc/internal/parser/parser_test.go @@ -607,7 +607,7 @@ func TestCommonJSModuleIndicator(t *testing.T) { opts := ast.SourceFileParseOptions{FileName: test.fileName, PathKey: tspath.CaseSensitive.PathKey(test.fileName.AsPath())} opts.ExternalModuleIndicatorOptions.Force = test.fileName.ExtensionIsOneOf([]string{tspath.ExtensionCjs, tspath.ExtensionMjs}) file := parser.ParseSourceFile(opts, test.source, core.GetScriptKindFromFileName(test.fileName)) - assert.Equal(t, file.IsCommonJSModule, test.expected) + assert.Equal(t, file.CommonJSModuleIndicator != nil, test.expected) }) } } diff --git a/tsc/internal/project/refcountcache_test.go b/tsc/internal/project/refcountcache_test.go index 87aeebc9afb80..396a1a3921878 100644 --- a/tsc/internal/project/refcountcache_test.go +++ b/tsc/internal/project/refcountcache_test.go @@ -86,7 +86,7 @@ func TestParseCacheBindsBeforePublishing(t *testing.T) { defer cache.Deref(key) assert.Assert(t, file.IsBound()) - assert.Assert(t, file.IsCommonJSModule) + assert.Assert(t, file.CommonJSModuleIndicator != nil) } func TestParseCacheAcquireExistingUsesFullKey(t *testing.T) { diff --git a/tsc/internal/transformers/declarations/transform.go b/tsc/internal/transformers/declarations/transform.go index 9a71b68e351a4..8b5b449ec87f5 100644 --- a/tsc/internal/transformers/declarations/transform.go +++ b/tsc/internal/transformers/declarations/transform.go @@ -834,7 +834,7 @@ func (tx *DeclarationTransformer) transformTypeParameterDeclaration(input *ast.T } func (tx *DeclarationTransformer) transformVariableDeclaration(input *ast.VariableDeclaration) *ast.Node { - if tx.state.currentSourceFile.IsCommonJSModule && ast.IsVariableDeclarationInitializedToRequire(input.AsNode()) { + if tx.state.currentSourceFile.CommonJSModuleIndicator != nil && ast.IsVariableDeclarationInitializedToRequire(input.AsNode()) { return tx.transformCjsRequireVariableDeclaration(input) } if ast.IsBindingPattern(input.Name()) && hasAnyBindingInitializers(input.Name().AsBindingPattern()) { @@ -2253,7 +2253,7 @@ func (tx *DeclarationTransformer) transformVariableStatement(input *ast.Variable inputNodes := input.DeclarationList.AsVariableDeclarationList().Declarations.Nodes var extraImports []*ast.Node - if tx.state.currentSourceFile.IsCommonJSModule { + if tx.state.currentSourceFile.CommonJSModuleIndicator != nil { var normalDeclarations []*ast.Node var imports []*ast.Node for _, n := range inputNodes { @@ -2710,7 +2710,7 @@ func (tx *DeclarationTransformer) visitCJSExportAssignments(expression *ast.Node defer cleanupDiagnosticContext() switch ast.GetAssignmentDeclarationKind(expression) { case ast.JSDeclarationKindModuleExports: - if tx.state.currentSourceFile.IsCommonJSModule { + if tx.state.currentSourceFile.CommonJSModuleIndicator != nil { result := tx.transformExportAssignment(expression.Parent, expression, expression.AsBinaryExpression().Right, true /*isExportEquals*/) if result != nil { tx.cjsExportAssignment = result @@ -2732,14 +2732,14 @@ func (tx *DeclarationTransformer) visitNestedExpression(expression *ast.Node) *a case ast.JSDeclarationKindProperty: tx.transformExpandoAssignment(expression.AsBinaryExpression()) case ast.JSDeclarationKindExportsProperty: - if tx.state.currentSourceFile.IsCommonJSModule { + if tx.state.currentSourceFile.CommonJSModuleIndicator != nil { result := tx.transformCommonJSExport(expression, tx.getNameExpressionPreferringIdentifier(ast.GetElementOrPropertyAccessName(expression.AsBinaryExpression().Left))) if result != nil { tx.cjsExportMembers = append(tx.cjsExportMembers, result) } } case ast.JSDeclarationKindObjectDefinePropertyExports: - if tx.state.currentSourceFile.IsCommonJSModule { + if tx.state.currentSourceFile.CommonJSModuleIndicator != nil { result := tx.transformCommonJSExport(expression, tx.getNameExpressionPreferringIdentifier(expression.Arguments()[1])) if result != nil { tx.cjsExportMembers = append(tx.cjsExportMembers, result) diff --git a/tsc/internal/transformers/moduletransforms/commonjsmodule.go b/tsc/internal/transformers/moduletransforms/commonjsmodule.go index ab58a42ae574d..8ee78bbea8543 100644 --- a/tsc/internal/transformers/moduletransforms/commonjsmodule.go +++ b/tsc/internal/transformers/moduletransforms/commonjsmodule.go @@ -244,7 +244,7 @@ func (tx *CommonJSModuleTransformer) visitSourceFile(node *ast.SourceFile) *ast. func (tx *CommonJSModuleTransformer) shouldEmitUnderscoreUnderscoreESModule() bool { if tx.currentSourceFile.FileName().ExtensionIsOneOf(tspath.SupportedJSExtensionsFlat) && - tx.currentSourceFile.IsCommonJSModule && + tx.currentSourceFile.CommonJSModuleIndicator != nil && (tx.currentSourceFile.ExternalModuleIndicator == nil || tx.currentSourceFile.ExternalModuleIndicator.Kind == ast.KindSourceFile) { return false }