Skip to content

Implement externalModuleIndicator #979

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

Merged
merged 16 commits into from
Jun 10, 2025
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions internal/api/encoder/encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ import (
"gotest.tools/v3/assert"
)

var parseCompilerOptions = &core.SourceFileAffectingCompilerOptions{
EmitScriptTarget: core.ScriptTargetLatest,
}

func TestEncodeSourceFile(t *testing.T) {
t.Parallel()
sourceFile := parser.ParseSourceFile("/test.ts", "/test.ts", "import { bar } from \"bar\";\nexport function foo<T, U>(a: string, b: string): any {}\nfoo();", core.ScriptTargetESNext, scanner.JSDocParsingModeParseAll)
sourceFile := parser.ParseSourceFile("/test.ts", "/test.ts", "import { bar } from \"bar\";\nexport function foo<T, U>(a: string, b: string): any {}\nfoo();", parseCompilerOptions, nil, scanner.JSDocParsingModeParseAll)
t.Run("baseline", func(t *testing.T) {
t.Parallel()
buf, err := encoder.EncodeSourceFile(sourceFile, "")
Expand All @@ -42,7 +46,8 @@ func BenchmarkEncodeSourceFile(b *testing.B) {
"/checker.ts",
"/checker.ts",
string(fileContent),
core.ScriptTargetESNext,
parseCompilerOptions,
nil,
scanner.JSDocParsingModeParseAll,
)

Expand Down
8 changes: 5 additions & 3 deletions internal/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -9964,6 +9964,9 @@ type SourceFile struct {
CheckJsDirective *CheckJsDirective
NodeCount int
TextCount int
CommonJSModuleIndicator *Node
Metadata *SourceFileMetaData
ExternalModuleIndicator *Node

// Fields set by binder

Expand Down Expand Up @@ -9992,9 +9995,7 @@ type SourceFile struct {

// !!!

CommonJSModuleIndicator *Node
ExternalModuleIndicator *Node
JSGlobalAugmentations SymbolTable
JSGlobalAugmentations SymbolTable
}

func (f *NodeFactory) NewSourceFile(text string, fileName string, path tspath.Path, statements *NodeList) *Node {
Expand Down Expand Up @@ -10092,6 +10093,7 @@ func (node *SourceFile) copyFrom(other *SourceFile) {
node.ReferencedFiles = other.ReferencedFiles
node.TypeReferenceDirectives = other.TypeReferenceDirectives
node.LibReferenceDirectives = other.LibReferenceDirectives
node.Metadata = other.Metadata
node.CommonJSModuleIndicator = other.CommonJSModuleIndicator
node.ExternalModuleIndicator = other.ExternalModuleIndicator
node.JSGlobalAugmentations = other.JSGlobalAugmentations
Expand Down
18 changes: 11 additions & 7 deletions internal/ast/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -2410,30 +2410,30 @@ func GetImpliedNodeFormatForFile(path string, packageJsonType string) core.Modul
return impliedNodeFormat
}

func GetEmitModuleFormatOfFileWorker(sourceFile *SourceFile, options *core.CompilerOptions, sourceFileMetaData *SourceFileMetaData) core.ModuleKind {
result := GetImpliedNodeFormatForEmitWorker(sourceFile.FileName(), options, sourceFileMetaData)
func GetEmitModuleFormatOfFileWorker(sourceFile *SourceFile, options *core.CompilerOptions) core.ModuleKind {
result := GetImpliedNodeFormatForEmitWorker(sourceFile, options.GetEmitModuleKind())
if result != core.ModuleKindNone {
return result
}
return options.GetEmitModuleKind()
}

func GetImpliedNodeFormatForEmitWorker(fileName string, options *core.CompilerOptions, sourceFileMetaData *SourceFileMetaData) core.ModuleKind {
moduleKind := options.GetEmitModuleKind()
if core.ModuleKindNode16 <= moduleKind && moduleKind <= core.ModuleKindNodeNext {
func GetImpliedNodeFormatForEmitWorker(sourceFile *SourceFile, emitModuleKind core.ModuleKind) core.ModuleKind {
sourceFileMetaData := sourceFile.Metadata
if core.ModuleKindNode16 <= emitModuleKind && emitModuleKind <= core.ModuleKindNodeNext {
if sourceFileMetaData == nil {
return core.ModuleKindNone
}
return sourceFileMetaData.ImpliedNodeFormat
}
if sourceFileMetaData != nil && sourceFileMetaData.ImpliedNodeFormat == core.ModuleKindCommonJS &&
(sourceFileMetaData.PackageJsonType == "commonjs" ||
tspath.FileExtensionIsOneOf(fileName, []string{tspath.ExtensionCjs, tspath.ExtensionCts})) {
tspath.FileExtensionIsOneOf(sourceFile.FileName(), []string{tspath.ExtensionCjs, tspath.ExtensionCts})) {
return core.ModuleKindCommonJS
}
if sourceFileMetaData != nil && sourceFileMetaData.ImpliedNodeFormat == core.ModuleKindESNext &&
(sourceFileMetaData.PackageJsonType == "module" ||
tspath.FileExtensionIsOneOf(fileName, []string{tspath.ExtensionMjs, tspath.ExtensionMts})) {
tspath.FileExtensionIsOneOf(sourceFile.FileName(), []string{tspath.ExtensionMjs, tspath.ExtensionMts})) {
return core.ModuleKindESNext
}
return core.ModuleKindNone
Expand Down Expand Up @@ -2657,6 +2657,10 @@ func GetPragmaArgument(pragma *Pragma, name string) string {
return ""
}

func IsJsxOpeningLikeElement(node *Node) bool {
return IsJsxOpeningElement(node) || IsJsxSelfClosingElement(node)
}

// Of the form: `const x = require("x")` or `const { x } = require("x")` or with `var` or `let`
// The variable must not be exported and must not have a type annotation, even a jsdoc one.
// The initializer must be a call to `require` with a string literal or a string literal-like argument.
Expand Down
10 changes: 7 additions & 3 deletions internal/astnav/tokens_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ var testFiles = []string{
filepath.Join(repo.TypeScriptSubmodulePath, "src/services/mapCode.ts"),
}

var parseCompilerOptions = &core.SourceFileAffectingCompilerOptions{
EmitScriptTarget: core.ScriptTargetLatest,
}

func TestGetTokenAtPosition(t *testing.T) {
t.Parallel()
repo.SkipIfNoTypeScriptSubmodule(t)
Expand Down Expand Up @@ -53,7 +57,7 @@ func TestGetTokenAtPosition(t *testing.T) {
return 0;
}
`
file := parser.ParseSourceFile("/file.ts", "/file.ts", fileText, core.ScriptTargetLatest, scanner.JSDocParsingModeParseAll)
file := parser.ParseSourceFile("/file.ts", "/file.ts", fileText, parseCompilerOptions, nil,scanner.JSDocParsingModeParseAll)
assert.Equal(t, astnav.GetTokenAtPosition(file, 0), astnav.GetTokenAtPosition(file, 0))
})
}
Expand Down Expand Up @@ -88,7 +92,7 @@ func baselineTokens(t *testing.T, testName string, includeEOF bool, getTSTokens
positions[i] = i
}
tsTokens := getTSTokens(string(fileText), positions)
file := parser.ParseSourceFile("/file.ts", "/file.ts", string(fileText), core.ScriptTargetLatest, scanner.JSDocParsingModeParseAll)
file := parser.ParseSourceFile("/file.ts", "/file.ts", string(fileText), parseCompilerOptions, nil,scanner.JSDocParsingModeParseAll)

var output strings.Builder
currentRange := core.NewTextRange(0, 0)
Expand Down Expand Up @@ -421,7 +425,7 @@ export function isAnyDirectorySeparator(charCode: number): boolean {
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()
file := parser.ParseSourceFile("/file.ts", "/file.ts", testCase.fileContent, core.ScriptTargetLatest, scanner.JSDocParsingModeParseAll)
file := parser.ParseSourceFile("/file.ts", "/file.ts", testCase.fileContent, parseCompilerOptions, nil,scanner.JSDocParsingModeParseAll)
token := astnav.FindPrecedingToken(file, testCase.position)
assert.Equal(t, token.Kind, testCase.expectedKind)
})
Expand Down
8 changes: 4 additions & 4 deletions internal/binder/binder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ func BenchmarkBind(b *testing.B) {
path := tspath.ToPath(fileName, "/", osvfs.FS().UseCaseSensitiveFileNames())
sourceText := f.ReadFile(b)

compilerOptions := &core.CompilerOptions{Target: core.ScriptTargetESNext, Module: core.ModuleKindNodeNext}
sourceAffecting := compilerOptions.SourceFileAffecting()

sourceFiles := make([]*ast.SourceFile, b.N)
for i := range b.N {
sourceFiles[i] = parser.ParseSourceFile(fileName, path, sourceText, core.ScriptTargetESNext, scanner.JSDocParsingModeParseAll)
sourceFiles[i] = parser.ParseSourceFile(fileName, path, sourceText, sourceAffecting, nil, scanner.JSDocParsingModeParseAll)
}

compilerOptions := &core.CompilerOptions{Target: core.ScriptTargetESNext, Module: core.ModuleKindNodeNext}
sourceAffecting := compilerOptions.SourceFileAffecting()

// The above parses do a lot of work; ensure GC is finished before we start collecting performance data.
// GC must be called twice to allow things to settle.
runtime.GC()
Expand Down
43 changes: 24 additions & 19 deletions internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -524,10 +524,7 @@ type Program interface {
Options() *core.CompilerOptions
SourceFiles() []*ast.SourceFile
BindSourceFiles()
GetEmitModuleFormatOfFile(sourceFile *ast.SourceFile) core.ModuleKind
GetImpliedNodeFormatForEmit(sourceFile *ast.SourceFile) core.ModuleKind
GetResolvedModule(currentSourceFile *ast.SourceFile, moduleReference string) *ast.SourceFile
GetSourceFileMetaData(path tspath.Path) *ast.SourceFileMetaData
GetJSXRuntimeImportSpecifier(path tspath.Path) (moduleReference string, specifier *ast.Node)
GetImportHelpersImportSpecifier(path tspath.Path) *ast.Node
}
Expand Down Expand Up @@ -4870,7 +4867,7 @@ func (c *Checker) checkModuleDeclaration(node *ast.Node) {
}
}
}
if c.compilerOptions.VerbatimModuleSyntax.IsTrue() && ast.IsSourceFile(node.Parent) && node.ModifierFlags()&ast.ModifierFlagsExport != 0 && c.program.GetEmitModuleFormatOfFile(node.Parent.AsSourceFile()) == core.ModuleKindCommonJS {
if c.compilerOptions.VerbatimModuleSyntax.IsTrue() && ast.IsSourceFile(node.Parent) && node.ModifierFlags()&ast.ModifierFlagsExport != 0 && c.getEmitModuleFormatOfFile(node.Parent.AsSourceFile()) == core.ModuleKindCommonJS {
exportModifier := core.Find(node.ModifierNodes(), func(m *ast.Node) bool { return m.Kind == ast.KindExportKeyword })
c.error(exportModifier, diagnostics.A_top_level_export_modifier_cannot_be_used_on_value_declarations_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled)
}
Expand Down Expand Up @@ -5269,7 +5266,7 @@ func (c *Checker) checkExportAssignment(node *ast.Node) {
if !c.checkGrammarModifiers(node) && ast.IsExportAssignment(node) && node.AsExportAssignment().Modifiers() != nil {
c.grammarErrorOnFirstToken(node, diagnostics.An_export_assignment_cannot_have_modifiers)
}
isIllegalExportDefaultInCJS := !isExportEquals && node.Flags&ast.NodeFlagsAmbient == 0 && c.compilerOptions.VerbatimModuleSyntax.IsTrue() && c.program.GetEmitModuleFormatOfFile(ast.GetSourceFileOfNode(node)) == core.ModuleKindCommonJS
isIllegalExportDefaultInCJS := !isExportEquals && node.Flags&ast.NodeFlagsAmbient == 0 && c.compilerOptions.VerbatimModuleSyntax.IsTrue() && c.getEmitModuleFormatOfFile(ast.GetSourceFileOfNode(node)) == core.ModuleKindCommonJS
if ast.IsIdentifier(node.Expression()) {
id := node.Expression()
sym := c.getExportSymbolOfValueSymbolIfExported(c.resolveEntityName(id, ast.SymbolFlagsAll, true /*ignoreErrors*/, true /*dontResolveAlias*/, node))
Expand Down Expand Up @@ -5329,7 +5326,7 @@ func (c *Checker) checkExportAssignment(node *ast.Node) {
}
if isExportEquals {
// Forbid export= in esm implementation files, and esm mode declaration files
if c.moduleKind >= core.ModuleKindES2015 && c.moduleKind != core.ModuleKindPreserve && ((node.Flags&ast.NodeFlagsAmbient != 0 && c.program.GetImpliedNodeFormatForEmit(ast.GetSourceFileOfNode(node)) == core.ModuleKindESNext) || (node.Flags&ast.NodeFlagsAmbient == 0 && c.program.GetImpliedNodeFormatForEmit(ast.GetSourceFileOfNode(node)) != core.ModuleKindCommonJS)) {
if c.moduleKind >= core.ModuleKindES2015 && c.moduleKind != core.ModuleKindPreserve && ((node.Flags&ast.NodeFlagsAmbient != 0 && c.getImpliedNodeFormatForEmit(ast.GetSourceFileOfNode(node)) == core.ModuleKindESNext) || (node.Flags&ast.NodeFlagsAmbient == 0 && c.getImpliedNodeFormatForEmit(ast.GetSourceFileOfNode(node)) != core.ModuleKindCommonJS)) {
// export assignment is not supported in es6 modules
c.grammarErrorOnNode(node, diagnostics.Export_assignment_cannot_be_used_when_targeting_ECMAScript_modules_Consider_using_export_default_or_another_module_format_instead)
} else if c.moduleKind == core.ModuleKindSystem && node.Flags&ast.NodeFlagsAmbient == 0 {
Expand Down Expand Up @@ -6379,9 +6376,9 @@ func (c *Checker) checkAliasSymbol(node *ast.Node) {
}
}
}
if c.compilerOptions.VerbatimModuleSyntax.IsTrue() && !ast.IsImportEqualsDeclaration(node) && !ast.IsVariableDeclarationInitializedToRequire(node) && c.program.GetEmitModuleFormatOfFile(ast.GetSourceFileOfNode(node)) == core.ModuleKindCommonJS {
if c.compilerOptions.VerbatimModuleSyntax.IsTrue() && !ast.IsImportEqualsDeclaration(node) && !ast.IsVariableDeclarationInitializedToRequire(node) && c.getEmitModuleFormatOfFile(ast.GetSourceFileOfNode(node)) == core.ModuleKindCommonJS {
c.error(node, diagnostics.ESM_syntax_is_not_allowed_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled)
} else if c.moduleKind == core.ModuleKindPreserve && !ast.IsImportEqualsDeclaration(node) && !ast.IsVariableDeclaration(node) && c.program.GetEmitModuleFormatOfFile(ast.GetSourceFileOfNode(node)) == core.ModuleKindCommonJS {
} else if c.moduleKind == core.ModuleKindPreserve && !ast.IsImportEqualsDeclaration(node) && !ast.IsVariableDeclaration(node) && c.getEmitModuleFormatOfFile(ast.GetSourceFileOfNode(node)) == core.ModuleKindCommonJS {
// In `--module preserve`, ESM input syntax emits ESM output syntax, but there will be times
// when we look at the `impliedNodeFormat` of this file and decide it's CommonJS (i.e., currently,
// only if the file extension is .cjs/.cts). To avoid that inconsistency, we disallow ESM syntax
Expand Down Expand Up @@ -8420,7 +8417,7 @@ type CallState struct {
func (c *Checker) resolveCall(node *ast.Node, signatures []*Signature, candidatesOutArray *[]*Signature, checkMode CheckMode, callChainFlags SignatureFlags, headMessage *diagnostics.Message) *Signature {
isTaggedTemplate := node.Kind == ast.KindTaggedTemplateExpression
isDecorator := node.Kind == ast.KindDecorator
isJsxOpeningOrSelfClosingElement := isJsxOpeningLikeElement(node)
isJsxOpeningOrSelfClosingElement := ast.IsJsxOpeningLikeElement(node)
isInstanceof := node.Kind == ast.KindBinaryExpression
reportErrors := !c.isInferencePartiallyBlocked && candidatesOutArray == nil
var s CallState
Expand Down Expand Up @@ -8726,7 +8723,7 @@ func (c *Checker) hasCorrectArity(node *ast.Node, args []*ast.Node, signature *S
argCount = c.getDecoratorArgumentCount(node, signature)
case ast.IsBinaryExpression(node):
argCount = 1
case isJsxOpeningLikeElement(node):
case ast.IsJsxOpeningLikeElement(node):
callIsIncomplete = node.Attributes().End() == node.End()
if callIsIncomplete {
return true
Expand Down Expand Up @@ -8846,7 +8843,7 @@ func (c *Checker) checkTypeArguments(signature *Signature, typeArgumentNodes []*
}

func (c *Checker) isSignatureApplicable(node *ast.Node, args []*ast.Node, signature *Signature, relation *Relation, checkMode CheckMode, reportErrors bool, inferenceContext *InferenceContext, diagnosticOutput *[]*ast.Diagnostic) bool {
if isJsxOpeningLikeElement(node) {
if ast.IsJsxOpeningLikeElement(node) {
return c.checkApplicableSignatureForJsxOpeningLikeElement(node, signature, relation, checkMode, reportErrors, diagnosticOutput)
}
thisType := c.getThisTypeOfSignature(signature)
Expand Down Expand Up @@ -8987,7 +8984,7 @@ func (c *Checker) getEffectiveCheckNode(argument *ast.Node) *ast.Node {
}

func (c *Checker) inferTypeArguments(node *ast.Node, signature *Signature, args []*ast.Node, checkMode CheckMode, context *InferenceContext) []*Type {
if isJsxOpeningLikeElement(node) {
if ast.IsJsxOpeningLikeElement(node) {
return c.inferJsxTypeArguments(node, signature, checkMode, context)
}
// If a contextual type is available, infer from that type to the return type of the call expression. For
Expand Down Expand Up @@ -9969,7 +9966,7 @@ func (c *Checker) checkCollisionsForDeclarationName(node *ast.Node, name *ast.No

func (c *Checker) checkCollisionWithRequireExportsInGeneratedCode(node *ast.Node, name *ast.Node) {
// No need to check for require or exports for ES6 modules and later
if c.program.GetEmitModuleFormatOfFile(ast.GetSourceFileOfNode(node)) >= core.ModuleKindES2015 {
if c.getEmitModuleFormatOfFile(ast.GetSourceFileOfNode(node)) >= core.ModuleKindES2015 {
return
}
if name == nil || !c.needCollisionCheckForIdentifier(node, name, "require") && !c.needCollisionCheckForIdentifier(node, name, "exports") {
Expand Down Expand Up @@ -10180,7 +10177,7 @@ func (c *Checker) checkNewTargetMetaProperty(node *ast.Node) *Type {

func (c *Checker) checkImportMetaProperty(node *ast.Node) *Type {
if c.moduleKind == core.ModuleKindNode16 || c.moduleKind == core.ModuleKindNodeNext {
sourceFileMetaData := c.program.GetSourceFileMetaData(ast.GetSourceFileOfNode(node).Path())
sourceFileMetaData := ast.GetSourceFileOfNode(node).Metadata
if sourceFileMetaData == nil || sourceFileMetaData.ImpliedNodeFormat != core.ModuleKindESNext {
c.error(node, diagnostics.The_import_meta_meta_property_is_not_allowed_in_files_which_will_build_into_CommonJS_output)
}
Expand Down Expand Up @@ -14009,7 +14006,7 @@ func (c *Checker) canHaveSyntheticDefault(file *ast.Node, moduleSymbol *ast.Symb
usageMode = c.getEmitSyntaxForModuleSpecifierExpression(usage)
}
if file != nil && usageMode != core.ModuleKindNone {
targetMode := c.program.GetImpliedNodeFormatForEmit(file.AsSourceFile())
targetMode := c.getImpliedNodeFormatForEmit(file.AsSourceFile())
if usageMode == core.ModuleKindESNext && targetMode == core.ModuleKindCommonJS && core.ModuleKindNode16 <= c.moduleKind && c.moduleKind <= core.ModuleKindNodeNext {
// In Node.js, CommonJS modules always have a synthetic default when imported into ESM
return true
Expand Down Expand Up @@ -26512,7 +26509,7 @@ func (c *Checker) markLinkedReferences(location *ast.Node, hint ReferenceHint, p
c.markExportAssignmentAliasReferenced(location)
return
}
if isJsxOpeningLikeElement(location) || ast.IsJsxOpeningFragment(location) {
if ast.IsJsxOpeningLikeElement(location) || ast.IsJsxOpeningFragment(location) {
c.markJsxAliasReferenced(location)
return
}
Expand Down Expand Up @@ -26677,7 +26674,7 @@ func (c *Checker) markJsxAliasReferenced(node *ast.Node /*JsxOpeningLikeElement
jsxFactoryRefErr := core.IfElse(c.compilerOptions.Jsx == core.JsxEmitReact, diagnostics.This_JSX_tag_requires_0_to_be_in_scope_but_it_could_not_be_found, nil)
jsxFactoryNamespace := c.getJsxNamespace(node)
jsxFactoryLocation := node
if isJsxOpeningLikeElement(node) {
if ast.IsJsxOpeningLikeElement(node) {
jsxFactoryLocation = node.TagName()
}
// allow null as jsxFragmentFactory
Expand Down Expand Up @@ -27722,7 +27719,7 @@ func (c *Checker) getContextualTypeForArgumentAtIndex(callTarget *ast.Node, argI
} else {
signature = c.getResolvedSignature(callTarget, nil, CheckModeNormal)
}
if isJsxOpeningLikeElement(callTarget) && argIndex == 0 {
if ast.IsJsxOpeningLikeElement(callTarget) && argIndex == 0 {
return c.getEffectiveFirstArgumentForJsxSignature(signature, callTarget)
}
restIndex := len(signature.parameters) - 1
Expand Down Expand Up @@ -27976,7 +27973,7 @@ func (c *Checker) getEffectiveCallArguments(node *ast.Node) []*ast.Node {
case ast.IsBinaryExpression(node):
// Handles instanceof operator
return []*ast.Node{node.AsBinaryExpression().Left}
case isJsxOpeningLikeElement(node):
case ast.IsJsxOpeningLikeElement(node):
if len(node.Attributes().AsJsxAttributes().Properties.Nodes) != 0 || (ast.IsJsxOpeningElement(node) && len(node.Parent.Children().Nodes) != 0) {
return []*ast.Node{node.Attributes()}
}
Expand Down Expand Up @@ -30003,3 +30000,11 @@ func (c *Checker) GetEmitResolver(file *ast.SourceFile, skipDiagnostics bool) *e
func (c *Checker) GetAliasedSymbol(symbol *ast.Symbol) *ast.Symbol {
return c.resolveAlias(symbol)
}

func (c *Checker) getEmitModuleFormatOfFile(sourceFile *ast.SourceFile) core.ModuleKind {
return ast.GetEmitModuleFormatOfFileWorker(sourceFile, c.compilerOptions)
}

func (c *Checker) getImpliedNodeFormatForEmit(sourceFile *ast.SourceFile) core.ResolutionMode {
return ast.GetImpliedNodeFormatForEmitWorker(sourceFile, c.compilerOptions.GetEmitModuleKind())
}
Loading
Loading