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
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
@@ -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, "")
@@ -42,7 +46,8 @@ func BenchmarkEncodeSourceFile(b *testing.B) {
"/checker.ts",
"/checker.ts",
string(fileContent),
core.ScriptTargetESNext,
parseCompilerOptions,
nil,
scanner.JSDocParsingModeParseAll,
)

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

// Fields set by binder

@@ -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 {
7 changes: 3 additions & 4 deletions internal/ast/utilities.go
Original file line number Diff line number Diff line change
@@ -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
}
10 changes: 7 additions & 3 deletions internal/astnav/tokens_test.go
Original file line number Diff line number Diff line change
@@ -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)
@@ -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))
})
}
@@ -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)
@@ -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)
})
8 changes: 4 additions & 4 deletions internal/binder/binder_test.go
Original file line number Diff line number Diff line change
@@ -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()
10 changes: 5 additions & 5 deletions internal/compiler/fileloader.go
Original file line number Diff line number Diff line change
@@ -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))
@@ -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
}

@@ -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
}
6 changes: 3 additions & 3 deletions internal/compiler/host.go
Original file line number Diff line number Diff line change
@@ -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 {
@@ -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
@@ -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
}
@@ -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 &&
@@ -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 {
18 changes: 18 additions & 0 deletions internal/core/compileroptions.go
Original file line number Diff line number Diff line change
@@ -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()
}
@@ -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
}
@@ -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(),
}
10 changes: 8 additions & 2 deletions internal/format/api_test.go
Original file line number Diff line number Diff line change
@@ -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()

@@ -60,7 +64,8 @@ func TestFormat(t *testing.T) {
"/checker.ts",
"/checker.ts",
text,
core.ScriptTargetESNext,
parseCompilerOptions,
nil,
scanner.JSDocParsingModeParseAll,
)
ast.SetParentInChildren(sourceFile.AsNode())
@@ -93,7 +98,8 @@ func BenchmarkFormat(b *testing.B) {
"/checker.ts",
"/checker.ts",
text,
core.ScriptTargetESNext,
parseCompilerOptions,
nil,
scanner.JSDocParsingModeParseAll,
)
ast.SetParentInChildren(sourceFile.AsNode())
16 changes: 8 additions & 8 deletions internal/fourslash/fourslash.go
Original file line number Diff line number Diff line change
@@ -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)
@@ -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)
}
Loading
Oops, something went wrong.