@@ -859,27 +859,84 @@ func (v *View) shouldUseRequire() bool {
859859 return shouldUseRequire
860860}
861861
862+ // fileSyntaxKind represents the detected module syntax of a source file.
863+ type fileSyntaxKind int
864+
865+ const (
866+ fileSyntaxKindAmbiguous fileSyntaxKind = iota
867+ fileSyntaxKindESM
868+ fileSyntaxKindCJS
869+ )
870+
871+ // detectSyntax returns whether a source file has unambiguous ESM or CJS syntax.
872+ // When moduleDetection is "force", ExternalModuleIndicator may be set to the
873+ // source file node itself rather than a genuine syntax indicator, so we fall back
874+ // to inspecting the file's Imports() to find actual import/export declarations.
875+ func detectSyntax (file * ast.SourceFile , options * core.CompilerOptions ) fileSyntaxKind {
876+ hasESM , hasCJS := detectSyntaxIndicators (file , options )
877+ switch {
878+ case hasCJS && ! hasESM :
879+ return fileSyntaxKindCJS
880+ case hasESM && ! hasCJS :
881+ return fileSyntaxKindESM
882+ default :
883+ return fileSyntaxKindAmbiguous
884+ }
885+ }
886+
887+ // detectSyntaxIndicators checks whether a source file contains genuine ESM
888+ // and/or CJS syntax. Under moduleDetection "force", the cached
889+ // ExternalModuleIndicator may be the source file itself rather than a real
890+ // statement, so we look at Imports() for actual import/export declarations.
891+ func detectSyntaxIndicators (file * ast.SourceFile , options * core.CompilerOptions ) (hasESM bool , hasCJS bool ) {
892+ hasCJS = file .CommonJSModuleIndicator != nil
893+ if options .GetEmitModuleDetectionKind () != core .ModuleDetectionKindForce {
894+ // ExternalModuleIndicator is reliable when moduleDetection is not "force"
895+ hasESM = file .ExternalModuleIndicator != nil
896+ return hasESM , hasCJS
897+ }
898+ // Under moduleDetection "force", ExternalModuleIndicator is set to
899+ // file.AsNode() when there is no genuine ESM syntax, so only trust it
900+ // when it points to a real statement node.
901+ if file .ExternalModuleIndicator != nil && file .ExternalModuleIndicator != file .AsNode () {
902+ return true , hasCJS
903+ }
904+ // Fall back to scanning Imports() for actual import/export declarations
905+ // (not require() calls or dynamic imports).
906+ for _ , imp := range file .Imports () {
907+ if imp .Flags & ast .NodeFlagsSynthesized != 0 {
908+ continue
909+ }
910+ parent := imp .Parent
911+ if parent == nil {
912+ continue
913+ }
914+ switch parent .Kind {
915+ case ast .KindImportDeclaration , ast .KindJSImportDeclaration , ast .KindExportDeclaration :
916+ return true , hasCJS
917+ case ast .KindExternalModuleReference :
918+ // import x = require("...") — this is ESM-ish syntax
919+ return true , hasCJS
920+ }
921+ }
922+ return hasESM , hasCJS
923+ }
924+
862925func (v * View ) computeShouldUseRequire () bool {
863926 // 1. TypeScript files don't use require variable declarations
864927 if ! tspath .HasJSFileExtension (v .importingFile .FileName ()) {
865928 return false
866929 }
867930
868931 // 2. If the current source file is unambiguously CJS or ESM, go with that
869- switch {
870- case v . importingFile . CommonJSModuleIndicator != nil && v . importingFile . ExternalModuleIndicator == nil :
932+ switch detectSyntax ( v . importingFile , v . program . Options ()) {
933+ case fileSyntaxKindCJS :
871934 return true
872- case v . importingFile . ExternalModuleIndicator != nil && v . importingFile . CommonJSModuleIndicator == nil :
935+ case fileSyntaxKindESM :
873936 return false
874937 }
875938
876- // 3. If there's a tsconfig/jsconfig, use its module setting
877- if v .program .Options ().ConfigFilePath != "" {
878- return v .program .Options ().GetEmitModuleKind () < core .ModuleKindES2015
879- }
880-
881- // 4. In --module nodenext, assume we're not emitting JS -> JS, so use
882- // whatever syntax Node expects based on the detected module kind
939+ // 3. Use the implied node format to determine CJS vs ESM
883940 // TODO: consider removing `impliedNodeFormatForEmit`
884941 switch v .program .GetImpliedNodeFormatForEmit (v .importingFile ) {
885942 case core .ModuleKindCommonJS :
@@ -888,14 +945,21 @@ func (v *View) computeShouldUseRequire() bool {
888945 return false
889946 }
890947
948+ // 4. If there's a tsconfig/jsconfig, use its module setting
949+ if v .program .Options ().ConfigFilePath != "" {
950+ return v .program .Options ().GetEmitModuleKind () < core .ModuleKindES2015
951+ }
952+
891953 // 5. Match the first other JS file in the program that's unambiguously CJS or ESM
892954 for _ , otherFile := range v .program .GetSourceFiles () {
893955 switch {
894956 case otherFile == v .importingFile , ! ast .IsSourceFileJS (otherFile ), v .program .IsSourceFileFromExternalLibrary (otherFile ):
895957 continue
896- case otherFile .CommonJSModuleIndicator != nil && otherFile .ExternalModuleIndicator == nil :
958+ }
959+ switch detectSyntax (otherFile , v .program .Options ()) {
960+ case fileSyntaxKindCJS :
897961 return true
898- case otherFile . ExternalModuleIndicator != nil && otherFile . CommonJSModuleIndicator == nil :
962+ case fileSyntaxKindESM :
899963 return false
900964 }
901965 }
0 commit comments