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 all 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: 3 additions & 5 deletions internal/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -10007,6 +10007,8 @@ type SourceFile struct {
CheckJsDirective *CheckJsDirective
NodeCount int
TextCount int
CommonJSModuleIndicator *Node
ExternalModuleIndicator *Node

// Fields set by binder

Expand All @@ -10029,11 +10031,7 @@ type SourceFile struct {
tokenCacheMu sync.Mutex
tokenCache map[core.TextRange]*Node

// !!!

CommonJSModuleIndicator *Node
ExternalModuleIndicator *Node
JSGlobalAugmentations SymbolTable
JSGlobalAugmentations SymbolTable // !!! remove me
}

func (f *NodeFactory) NewSourceFile(text string, fileName string, path tspath.Path, statements *NodeList) *Node {
Expand Down
7 changes: 3 additions & 4 deletions internal/ast/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -2514,16 +2514,15 @@ func GetImpliedNodeFormatForFile(path string, packageJsonType string) core.Modul
}

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

func GetImpliedNodeFormatForEmitWorker(fileName string, options *core.CompilerOptions, sourceFileMetaData *SourceFileMetaData) core.ResolutionMode {
moduleKind := options.GetEmitModuleKind()
if core.ModuleKindNode16 <= moduleKind && moduleKind <= core.ModuleKindNodeNext {
func GetImpliedNodeFormatForEmitWorker(fileName string, emitModuleKind core.ModuleKind, sourceFileMetaData *SourceFileMetaData) core.ResolutionMode {
if core.ModuleKindNode16 <= emitModuleKind && emitModuleKind <= core.ModuleKindNodeNext {
if sourceFileMetaData == nil {
return core.ModuleKindNone
}
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
10 changes: 5 additions & 5 deletions internal/compiler/fileloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,13 +244,13 @@ func (t *parseTask) start(loader *fileLoader) {
}

loader.wg.Queue(func() {
file := loader.parseSourceFile(t.normalizedFilePath)
t.metadata = loader.loadSourceFileMetaData(t.normalizedFilePath)
file := loader.parseSourceFile(t.normalizedFilePath, t.metadata)
if file == nil {
return
}

t.file = file
t.metadata = loader.loadSourceFileMetaData(file.FileName())

// !!! if noResolve, skip all of this
t.subTasks = make([]*parseTask, 0, len(file.ReferencedFiles)+len(file.Imports())+len(file.ModuleAugmentations))
Expand Down Expand Up @@ -309,9 +309,9 @@ func (p *fileLoader) loadSourceFileMetaData(fileName string) *ast.SourceFileMeta
}
}

func (p *fileLoader) parseSourceFile(fileName string) *ast.SourceFile {
func (p *fileLoader) parseSourceFile(fileName string, metadata *ast.SourceFileMetaData) *ast.SourceFile {
path := tspath.ToPath(fileName, p.opts.Host.GetCurrentDirectory(), p.opts.Host.FS().UseCaseSensitiveFileNames())
sourceFile := p.opts.Host.GetSourceFile(fileName, path, p.opts.Config.CompilerOptions().GetEmitScriptTarget())
sourceFile := p.opts.Host.GetSourceFile(fileName, path, p.opts.Config.CompilerOptions().SourceFileAffecting(), metadata) // TODO(jakebailey): cache :(
return sourceFile
}

Expand Down Expand Up @@ -467,7 +467,7 @@ func getModeForTypeReferenceDirectiveInFile(ref *ast.FileReference, file *ast.So

func getDefaultResolutionModeForFile(fileName string, meta *ast.SourceFileMetaData, options *core.CompilerOptions) core.ResolutionMode {
if importSyntaxAffectsModuleResolution(options) {
return ast.GetImpliedNodeFormatForEmitWorker(fileName, options, meta)
return ast.GetImpliedNodeFormatForEmitWorker(fileName, options.GetEmitModuleKind(), meta)
} else {
return core.ResolutionModeNone
}
Expand Down
6 changes: 3 additions & 3 deletions internal/compiler/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type CompilerHost interface {
GetCurrentDirectory() string
NewLine() string
Trace(msg string)
GetSourceFile(fileName string, path tspath.Path, languageVersion core.ScriptTarget) *ast.SourceFile
GetSourceFile(fileName string, path tspath.Path, options *core.SourceFileAffectingCompilerOptions, metadata *ast.SourceFileMetaData) *ast.SourceFile
}

type FileInfo struct {
Expand Down Expand Up @@ -73,10 +73,10 @@ func (h *compilerHost) Trace(msg string) {
//!!! TODO: implement
}

func (h *compilerHost) GetSourceFile(fileName string, path tspath.Path, languageVersion core.ScriptTarget) *ast.SourceFile {
func (h *compilerHost) GetSourceFile(fileName string, path tspath.Path, options *core.SourceFileAffectingCompilerOptions, metadata *ast.SourceFileMetaData) *ast.SourceFile {
text, _ := h.FS().ReadFile(fileName)
if tspath.FileExtensionIs(fileName, tspath.ExtensionJson) {
return parser.ParseJSONText(fileName, path, text)
}
return parser.ParseSourceFile(fileName, path, text, languageVersion, scanner.JSDocParsingModeParseForTypeErrors)
return parser.ParseSourceFile(fileName, path, text, options, metadata, scanner.JSDocParsingModeParseForTypeErrors)
}
7 changes: 5 additions & 2 deletions internal/compiler/program.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,9 @@ func NewProgram(opts ProgramOptions) *Program {
// In addition to a new program, return a boolean indicating whether the data of the old program was reused.
func (p *Program) UpdateProgram(changedFilePath tspath.Path) (*Program, bool) {
oldFile := p.filesByPath[changedFilePath]
newFile := p.Host().GetSourceFile(oldFile.FileName(), changedFilePath, oldFile.LanguageVersion)
// TODO(jakebailey): is wrong because the new file may have new metadata?
metadata := p.sourceFileMetaDatas[changedFilePath]
newFile := p.Host().GetSourceFile(oldFile.FileName(), changedFilePath, p.Options().SourceFileAffecting(), metadata)
if !canReplaceFileInProgram(oldFile, newFile) {
return NewProgram(p.opts), false
}
Expand Down Expand Up @@ -262,6 +264,7 @@ func (p *Program) initCheckerPool() {
}

func canReplaceFileInProgram(file1 *ast.SourceFile, file2 *ast.SourceFile) bool {
// TODO(jakebailey): metadata??
return file1.FileName() == file2.FileName() &&
file1.Path() == file2.Path() &&
file1.LanguageVersion == file2.LanguageVersion &&
Expand Down Expand Up @@ -696,7 +699,7 @@ func (p *Program) GetEmitSyntaxForUsageLocation(sourceFile ast.HasFileName, loca
}

func (p *Program) GetImpliedNodeFormatForEmit(sourceFile ast.HasFileName) core.ResolutionMode {
return ast.GetImpliedNodeFormatForEmitWorker(sourceFile.FileName(), p.Options(), p.GetSourceFileMetaData(sourceFile.Path()))
return ast.GetImpliedNodeFormatForEmitWorker(sourceFile.FileName(), p.Options().GetEmitModuleKind(), p.GetSourceFileMetaData(sourceFile.Path()))
}

func (p *Program) GetModeForUsageLocation(sourceFile ast.HasFileName, location *ast.StringLiteralLike) core.ResolutionMode {
Expand Down
18 changes: 18 additions & 0 deletions internal/core/compileroptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,18 @@ func (options *CompilerOptions) GetModuleResolutionKind() ModuleResolutionKind {
}
}

func (options *CompilerOptions) GetEmitModuleDetectionKind() ModuleDetectionKind {
if options.ModuleDetection != ModuleDetectionKindNone {
return options.ModuleDetection
}
switch options.GetEmitModuleKind() {
case ModuleKindNode16, ModuleKindNodeNext:
return ModuleDetectionKindForce
default:
return ModuleDetectionKindAuto
}
}

func (options *CompilerOptions) GetResolvePackageJsonExports() bool {
return options.ResolvePackageJsonExports.IsTrueOrUnknown()
}
Expand Down Expand Up @@ -339,7 +351,10 @@ type SourceFileAffectingCompilerOptions struct {
AllowUnreachableCode Tristate
AllowUnusedLabels Tristate
BindInStrictMode bool
EmitModuleDetectionKind ModuleDetectionKind
EmitModuleKind ModuleKind
EmitScriptTarget ScriptTarget
JsxEmit JsxEmit
NoFallthroughCasesInSwitch Tristate
ShouldPreserveConstEnums bool
}
Expand All @@ -349,7 +364,10 @@ func (options *CompilerOptions) SourceFileAffecting() *SourceFileAffectingCompil
AllowUnreachableCode: options.AllowUnreachableCode,
AllowUnusedLabels: options.AllowUnusedLabels,
BindInStrictMode: options.AlwaysStrict.IsTrue() || options.Strict.IsTrue(),
EmitModuleDetectionKind: options.GetEmitModuleDetectionKind(),
EmitModuleKind: options.GetEmitModuleKind(),
EmitScriptTarget: options.GetEmitScriptTarget(),
JsxEmit: options.Jsx,
NoFallthroughCasesInSwitch: options.NoFallthroughCasesInSwitch,
ShouldPreserveConstEnums: options.ShouldPreserveConstEnums(),
}
Expand Down
10 changes: 8 additions & 2 deletions internal/format/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ func applyBulkEdits(text string, edits []core.TextChange) string {
return b.String()
}

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

func TestFormat(t *testing.T) {
t.Parallel()

Expand All @@ -60,7 +64,8 @@ func TestFormat(t *testing.T) {
"/checker.ts",
"/checker.ts",
text,
core.ScriptTargetESNext,
parseCompilerOptions,
nil,
scanner.JSDocParsingModeParseAll,
)
ast.SetParentInChildren(sourceFile.AsNode())
Expand Down Expand Up @@ -93,7 +98,8 @@ func BenchmarkFormat(b *testing.B) {
"/checker.ts",
"/checker.ts",
text,
core.ScriptTargetESNext,
parseCompilerOptions,
nil,
scanner.JSDocParsingModeParseAll,
)
ast.SetParentInChildren(sourceFile.AsNode())
Expand Down
16 changes: 8 additions & 8 deletions internal/fourslash/fourslash.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,15 @@ func (c *parsedFileCache) GetFile(
fileName string,
path tspath.Path,
text string,
scriptTarget core.ScriptTarget,
options core.SourceFileAffectingCompilerOptions,
options *core.SourceFileAffectingCompilerOptions,
metadata *ast.SourceFileMetaData,
) *ast.SourceFile {
key := harnessutil.GetSourceFileCacheKey(
options,
fileName,
path,
scriptTarget,
text,
options,
metadata,
)

cachedFile, ok := sourceFileCache.Load(key)
Expand All @@ -102,16 +102,16 @@ func (c *parsedFileCache) CacheFile(
fileName string,
path tspath.Path,
text string,
scriptTarget core.ScriptTarget,
options core.SourceFileAffectingCompilerOptions,
options *core.SourceFileAffectingCompilerOptions,
metadata *ast.SourceFileMetaData,
sourceFile *ast.SourceFile,
) {
key := harnessutil.GetSourceFileCacheKey(
options,
fileName,
path,
scriptTarget,
text,
options,
metadata,
)
sourceFileCache.Store(key, sourceFile)
}
Expand Down
Loading