From 52892a29a32839f12170d32eb765bfd02be970a6 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Wed, 9 Apr 2025 17:41:51 -0700 Subject: [PATCH 1/2] Compute SourceFileAffectingCompilerOptions and use in binder --- internal/binder/binder.go | 22 +++++----- internal/binder/binder_test.go | 3 +- internal/compiler/program.go | 14 ++++++- internal/core/compileroptions.go | 41 +++++++----------- internal/printer/namegenerator_test.go | 44 ++++++++++---------- internal/project/documentregistry.go | 2 +- internal/project/service_test.go | 7 +++- internal/testutil/harnessutil/harnessutil.go | 2 +- internal/transformers/commonjsmodule_test.go | 5 ++- internal/transformers/esmodule_test.go | 5 ++- internal/transformers/importelision_test.go | 2 +- internal/transformers/runtimesyntax_test.go | 6 +-- 12 files changed, 81 insertions(+), 72 deletions(-) diff --git a/internal/binder/binder.go b/internal/binder/binder.go index 8b591b7d8f..a7b58bcc49 100644 --- a/internal/binder/binder.go +++ b/internal/binder/binder.go @@ -40,7 +40,7 @@ const ( type Binder struct { file *ast.SourceFile - options *core.CompilerOptions + options *core.SourceFileAffectingCompilerOptions languageVersion core.ScriptTarget bindFunc func(*ast.Node) bool unreachableFlow *ast.FlowNode @@ -86,7 +86,7 @@ type ActiveLabel struct { func (label *ActiveLabel) BreakTarget() *ast.FlowNode { return label.breakTarget } func (label *ActiveLabel) ContinueTarget() *ast.FlowNode { return label.continueTarget } -func BindSourceFile(file *ast.SourceFile, options *core.CompilerOptions) { +func BindSourceFile(file *ast.SourceFile, options *core.SourceFileAffectingCompilerOptions) { // This is constructed this way to make the compiler "out-line" the function, // avoiding most work in the common case where the file has already been bound. if !file.IsBound() { @@ -111,14 +111,14 @@ func putBinder(b *Binder) { binderPool.Put(b) } -func bindSourceFile(file *ast.SourceFile, options *core.CompilerOptions) { +func bindSourceFile(file *ast.SourceFile, options *core.SourceFileAffectingCompilerOptions) { file.BindOnce(func() { b := getBinder() defer putBinder(b) b.file = file b.options = options - b.languageVersion = options.GetEmitScriptTarget() - b.inStrictMode = (options.AlwaysStrict.IsTrue() || options.Strict.IsTrue()) && !file.IsDeclarationFile || ast.IsExternalModule(file) + b.languageVersion = options.EmitScriptTarget + b.inStrictMode = options.BindInStrictMode && !file.IsDeclarationFile || ast.IsExternalModule(file) b.unreachableFlow = b.newFlowNode(ast.FlowFlagsUnreachable) b.reportedUnreachableFlow = b.newFlowNode(ast.FlowFlagsUnreachable) b.bind(file.AsNode()) @@ -1331,7 +1331,7 @@ func (b *Binder) checkStrictModeWithStatement(node *ast.Node) { func (b *Binder) checkStrictModeLabeledStatement(node *ast.Node) { // Grammar checking for labeledStatement - if b.inStrictMode && b.options.Target >= core.ScriptTargetES2015 { + if b.inStrictMode && b.options.EmitScriptTarget >= core.ScriptTargetES2015 { data := node.AsLabeledStatement() if ast.IsDeclarationStatement(data.Statement) || ast.IsVariableStatement(data.Statement) { b.errorOnFirstToken(data.Label, diagnostics.A_label_is_not_allowed_here) @@ -1665,7 +1665,7 @@ func (b *Binder) checkUnreachable(node *ast.Node) bool { func (b *Binder) shouldReportErrorOnModuleDeclaration(node *ast.Node) bool { instanceState := ast.GetModuleInstanceState(node) - return instanceState == ast.ModuleInstanceStateInstantiated || (instanceState == ast.ModuleInstanceStateConstEnumOnly && b.options.ShouldPreserveConstEnums()) + return instanceState == ast.ModuleInstanceStateInstantiated || (instanceState == ast.ModuleInstanceStateConstEnumOnly && b.options.ShouldPreserveConstEnums) } func (b *Binder) errorOnEachUnreachableRange(node *ast.Node, isError bool) { @@ -2421,8 +2421,8 @@ func (b *Binder) bindInitializer(node *ast.Node) { b.currentFlow = b.finishFlowLabel(exitFlow) } -func isEnumDeclarationWithPreservedEmit(node *ast.Node, options *core.CompilerOptions) bool { - return node.Kind == ast.KindEnumDeclaration && (!ast.IsEnumConst(node) || options.ShouldPreserveConstEnums()) +func isEnumDeclarationWithPreservedEmit(node *ast.Node, options *core.SourceFileAffectingCompilerOptions) bool { + return node.Kind == ast.KindEnumDeclaration && (!ast.IsEnumConst(node) || options.ShouldPreserveConstEnums) } func setFlowNode(node *ast.Node, flowNode *ast.FlowNode) { @@ -2746,11 +2746,11 @@ func isFunctionSymbol(symbol *ast.Symbol) bool { return false } -func unreachableCodeIsError(options *core.CompilerOptions) bool { +func unreachableCodeIsError(options *core.SourceFileAffectingCompilerOptions) bool { return options.AllowUnreachableCode == core.TSFalse } -func unusedLabelIsError(options *core.CompilerOptions) bool { +func unusedLabelIsError(options *core.SourceFileAffectingCompilerOptions) bool { return options.AllowUnusedLabels == core.TSFalse } diff --git a/internal/binder/binder_test.go b/internal/binder/binder_test.go index 8518509df3..4dea2edfeb 100644 --- a/internal/binder/binder_test.go +++ b/internal/binder/binder_test.go @@ -28,6 +28,7 @@ func BenchmarkBind(b *testing.B) { } compilerOptions := &core.CompilerOptions{Target: core.ScriptTargetESNext, ModuleKind: 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. @@ -36,7 +37,7 @@ func BenchmarkBind(b *testing.B) { b.ResetTimer() for i := range b.N { - BindSourceFile(sourceFiles[i], compilerOptions) + BindSourceFile(sourceFiles[i], sourceAffecting) } }) } diff --git a/internal/compiler/program.go b/internal/compiler/program.go index 1ecce83020..f5fdb1b523 100644 --- a/internal/compiler/program.go +++ b/internal/compiler/program.go @@ -41,6 +41,9 @@ type Program struct { currentDirectory string configFileParsingDiagnostics []*ast.Diagnostic + sourceAffectingCompilerOptionsOnce sync.Once + sourceAffectingCompilerOptions *core.SourceFileAffectingCompilerOptions + resolver *module.Resolver comparePathsOptions tspath.ComparePathsOptions @@ -184,12 +187,19 @@ func (p *Program) GetConfigFileParsingDiagnostics() []*ast.Diagnostic { return slices.Clip(p.configFileParsingDiagnostics) } +func (p *Program) getSourceAffectingCompilerOptions() *core.SourceFileAffectingCompilerOptions { + p.sourceAffectingCompilerOptionsOnce.Do(func() { + p.sourceAffectingCompilerOptions = p.compilerOptions.SourceFileAffecting() + }) + return p.sourceAffectingCompilerOptions +} + func (p *Program) BindSourceFiles() { wg := core.NewWorkGroup(p.programOptions.SingleThreaded) for _, file := range p.files { if !file.IsBound() { wg.Queue(func() { - binder.BindSourceFile(file, p.compilerOptions) + binder.BindSourceFile(file, p.getSourceAffectingCompilerOptions()) }) } } @@ -388,7 +398,7 @@ func SortAndDeduplicateDiagnostics(diagnostics []*ast.Diagnostic) []*ast.Diagnos func (p *Program) getDiagnosticsHelper(sourceFile *ast.SourceFile, ensureBound bool, ensureChecked bool, getDiagnostics func(*ast.SourceFile) []*ast.Diagnostic) []*ast.Diagnostic { if sourceFile != nil { if ensureBound { - binder.BindSourceFile(sourceFile, p.compilerOptions) + binder.BindSourceFile(sourceFile, p.getSourceAffectingCompilerOptions()) } return SortAndDeduplicateDiagnostics(getDiagnostics(sourceFile)) } diff --git a/internal/core/compileroptions.go b/internal/core/compileroptions.go index 51c76a35a3..b35bf50f09 100644 --- a/internal/core/compileroptions.go +++ b/internal/core/compileroptions.go @@ -269,34 +269,25 @@ func (options *CompilerOptions) HasJsonModuleEmitEnabled() bool { return true } -// SourceFileAffectingCompilerOptions are the CompilerOptions values that when -// changed require a new SourceFile be created. +// SourceFileAffectingCompilerOptions are the precomputed CompilerOptions values which +// affect the parse and bind of a source file. type SourceFileAffectingCompilerOptions struct { - // !!! generate this - Target ScriptTarget - Jsx JsxEmit - JsxImportSource string - ImportHelpers Tristate - AlwaysStrict Tristate - ModuleDetection ModuleDetectionKind - AllowUnreachableCode Tristate - AllowUnusedLabels Tristate - PreserveConstEnums Tristate - IsolatedModules Tristate + AllowUnreachableCode Tristate + AllowUnusedLabels Tristate + BindInStrictMode bool + EmitScriptTarget ScriptTarget + NoFallthroughCasesInSwitch Tristate + ShouldPreserveConstEnums bool } -func (options *CompilerOptions) SourceFileAffecting() SourceFileAffectingCompilerOptions { - return SourceFileAffectingCompilerOptions{ - Target: options.Target, - Jsx: options.Jsx, - JsxImportSource: options.JsxImportSource, - ImportHelpers: options.ImportHelpers, - AlwaysStrict: options.AlwaysStrict, - ModuleDetection: options.ModuleDetection, - AllowUnreachableCode: options.AllowUnreachableCode, - AllowUnusedLabels: options.AllowUnusedLabels, - PreserveConstEnums: options.PreserveConstEnums, - IsolatedModules: options.IsolatedModules, +func (options *CompilerOptions) SourceFileAffecting() *SourceFileAffectingCompilerOptions { + return &SourceFileAffectingCompilerOptions{ + AllowUnreachableCode: options.AllowUnreachableCode, + AllowUnusedLabels: options.AllowUnusedLabels, + BindInStrictMode: options.AlwaysStrict.IsTrue() || options.Strict.IsTrue(), + EmitScriptTarget: options.GetEmitScriptTarget(), + NoFallthroughCasesInSwitch: options.NoFallthroughCasesInSwitch, + ShouldPreserveConstEnums: options.ShouldPreserveConstEnums(), } } diff --git a/internal/printer/namegenerator_test.go b/internal/printer/namegenerator_test.go index 7279fe7904..b07994b3ec 100644 --- a/internal/printer/namegenerator_test.go +++ b/internal/printer/namegenerator_test.go @@ -11,6 +11,8 @@ import ( "gotest.tools/v3/assert" ) +var defaultSourceFileAffectingOptions = (&core.CompilerOptions{}).SourceFileAffecting() + func TestTempVariable1(t *testing.T) { t.Parallel() @@ -257,7 +259,7 @@ func TestGeneratedNameForIdentifier1(t *testing.T) { ec := printer.NewEmitContext() file := parsetestutil.ParseTypeScript("function f() {}", false /*jsx*/) - binder.BindSourceFile(file, &core.CompilerOptions{}) + binder.BindSourceFile(file, defaultSourceFileAffectingOptions) n := file.Statements.Nodes[0].Name() name1 := ec.NewGeneratedNameForNode(n, printer.AutoGenerateOptions{}) @@ -274,7 +276,7 @@ func TestGeneratedNameForIdentifier2(t *testing.T) { ec := printer.NewEmitContext() file := parsetestutil.ParseTypeScript("function f() {}", false /*jsx*/) - binder.BindSourceFile(file, &core.CompilerOptions{}) + binder.BindSourceFile(file, defaultSourceFileAffectingOptions) n := file.Statements.Nodes[0].Name() name1 := ec.NewGeneratedNameForNode(n, printer.AutoGenerateOptions{ @@ -294,7 +296,7 @@ func TestGeneratedNameForIdentifier3(t *testing.T) { ec := printer.NewEmitContext() file := parsetestutil.ParseTypeScript("function f() {}", false /*jsx*/) - binder.BindSourceFile(file, &core.CompilerOptions{}) + binder.BindSourceFile(file, defaultSourceFileAffectingOptions) n := file.Statements.Nodes[0].Name() name1 := ec.NewGeneratedNameForNode(n, printer.AutoGenerateOptions{ @@ -316,7 +318,7 @@ func TestGeneratedNameForNamespace1(t *testing.T) { ec := printer.NewEmitContext() file := parsetestutil.ParseTypeScript("namespace foo { }", false /*jsx*/) - binder.BindSourceFile(file, &core.CompilerOptions{}) + binder.BindSourceFile(file, defaultSourceFileAffectingOptions) ns1 := file.Statements.Nodes[0] name1 := ec.NewGeneratedNameForNode(ns1, printer.AutoGenerateOptions{}) @@ -334,7 +336,7 @@ func TestGeneratedNameForNamespace2(t *testing.T) { ec := printer.NewEmitContext() file := parsetestutil.ParseTypeScript("namespace foo { var foo; }", false /*jsx*/) - binder.BindSourceFile(file, &core.CompilerOptions{}) + binder.BindSourceFile(file, defaultSourceFileAffectingOptions) ns1 := file.Statements.Nodes[0] name1 := ec.NewGeneratedNameForNode(ns1, printer.AutoGenerateOptions{}) @@ -352,7 +354,7 @@ func TestGeneratedNameForNamespace3(t *testing.T) { ec := printer.NewEmitContext() file := parsetestutil.ParseTypeScript("namespace ns1 { namespace foo { var foo; } } namespace ns2 { namespace foo { var foo; } }", false /*jsx*/) - binder.BindSourceFile(file, &core.CompilerOptions{}) + binder.BindSourceFile(file, defaultSourceFileAffectingOptions) ns1 := file.Statements.Nodes[0].AsModuleDeclaration().Body.AsModuleBlock().Statements.Nodes[0] ns2 := file.Statements.Nodes[1].AsModuleDeclaration().Body.AsModuleBlock().Statements.Nodes[0] @@ -374,7 +376,7 @@ func TestGeneratedNameForNamespace4(t *testing.T) { ec := printer.NewEmitContext() file := parsetestutil.ParseTypeScript("namespace ns1 { namespace foo { var foo; } } namespace ns2 { namespace foo { var foo; } }", false /*jsx*/) - binder.BindSourceFile(file, &core.CompilerOptions{}) + binder.BindSourceFile(file, defaultSourceFileAffectingOptions) ns1 := file.Statements.Nodes[0].AsModuleDeclaration().Body.AsModuleBlock().Statements.Nodes[0] ns2 := file.Statements.Nodes[1].AsModuleDeclaration().Body.AsModuleBlock().Statements.Nodes[0] @@ -400,7 +402,7 @@ func TestGeneratedNameForNodeCached(t *testing.T) { ec := printer.NewEmitContext() file := parsetestutil.ParseTypeScript("namespace foo { var foo; }", false /*jsx*/) - binder.BindSourceFile(file, &core.CompilerOptions{}) + binder.BindSourceFile(file, defaultSourceFileAffectingOptions) ns1 := file.Statements.Nodes[0] name1 := ec.NewGeneratedNameForNode(ns1, printer.AutoGenerateOptions{}) @@ -420,7 +422,7 @@ func TestGeneratedNameForImport(t *testing.T) { ec := printer.NewEmitContext() file := parsetestutil.ParseTypeScript("import * as foo from 'foo'", false /*jsx*/) - binder.BindSourceFile(file, &core.CompilerOptions{}) + binder.BindSourceFile(file, defaultSourceFileAffectingOptions) n := file.Statements.Nodes[0] name1 := ec.NewGeneratedNameForNode(n, printer.AutoGenerateOptions{}) @@ -437,7 +439,7 @@ func TestGeneratedNameForExport(t *testing.T) { ec := printer.NewEmitContext() file := parsetestutil.ParseTypeScript("export * as foo from 'foo'", false /*jsx*/) - binder.BindSourceFile(file, &core.CompilerOptions{}) + binder.BindSourceFile(file, defaultSourceFileAffectingOptions) n := file.Statements.Nodes[0] name1 := ec.NewGeneratedNameForNode(n, printer.AutoGenerateOptions{}) @@ -454,7 +456,7 @@ func TestGeneratedNameForFunctionDeclaration1(t *testing.T) { ec := printer.NewEmitContext() file := parsetestutil.ParseTypeScript("export function f() {}", false /*jsx*/) - binder.BindSourceFile(file, &core.CompilerOptions{}) + binder.BindSourceFile(file, defaultSourceFileAffectingOptions) n := file.Statements.Nodes[0] name1 := ec.NewGeneratedNameForNode(n, printer.AutoGenerateOptions{}) @@ -471,7 +473,7 @@ func TestGeneratedNameForFunctionDeclaration2(t *testing.T) { ec := printer.NewEmitContext() file := parsetestutil.ParseTypeScript("export default function () {}", false /*jsx*/) - binder.BindSourceFile(file, &core.CompilerOptions{}) + binder.BindSourceFile(file, defaultSourceFileAffectingOptions) n := file.Statements.Nodes[0] name1 := ec.NewGeneratedNameForNode(n, printer.AutoGenerateOptions{}) @@ -488,7 +490,7 @@ func TestGeneratedNameForClassDeclaration1(t *testing.T) { ec := printer.NewEmitContext() file := parsetestutil.ParseTypeScript("export class C {}", false /*jsx*/) - binder.BindSourceFile(file, &core.CompilerOptions{}) + binder.BindSourceFile(file, defaultSourceFileAffectingOptions) n := file.Statements.Nodes[0] name1 := ec.NewGeneratedNameForNode(n, printer.AutoGenerateOptions{}) @@ -505,7 +507,7 @@ func TestGeneratedNameForClassDeclaration2(t *testing.T) { ec := printer.NewEmitContext() file := parsetestutil.ParseTypeScript("export default class {}", false /*jsx*/) - binder.BindSourceFile(file, &core.CompilerOptions{}) + binder.BindSourceFile(file, defaultSourceFileAffectingOptions) n := file.Statements.Nodes[0] name1 := ec.NewGeneratedNameForNode(n, printer.AutoGenerateOptions{}) @@ -522,7 +524,7 @@ func TestGeneratedNameForExportAssignment(t *testing.T) { ec := printer.NewEmitContext() file := parsetestutil.ParseTypeScript("export default 0", false /*jsx*/) - binder.BindSourceFile(file, &core.CompilerOptions{}) + binder.BindSourceFile(file, defaultSourceFileAffectingOptions) n := file.Statements.Nodes[0] name1 := ec.NewGeneratedNameForNode(n, printer.AutoGenerateOptions{}) @@ -539,7 +541,7 @@ func TestGeneratedNameForClassExpression(t *testing.T) { ec := printer.NewEmitContext() file := parsetestutil.ParseTypeScript("(class {})", false /*jsx*/) - binder.BindSourceFile(file, &core.CompilerOptions{}) + binder.BindSourceFile(file, defaultSourceFileAffectingOptions) n := file.Statements.Nodes[0].AsExpressionStatement().Expression.AsParenthesizedExpression().Expression name1 := ec.NewGeneratedNameForNode(n, printer.AutoGenerateOptions{}) @@ -556,7 +558,7 @@ func TestGeneratedNameForMethod1(t *testing.T) { ec := printer.NewEmitContext() file := parsetestutil.ParseTypeScript("class C { m() {} }", false /*jsx*/) - binder.BindSourceFile(file, &core.CompilerOptions{}) + binder.BindSourceFile(file, defaultSourceFileAffectingOptions) n := file.Statements.Nodes[0].AsClassDeclaration().Members.Nodes[0] name1 := ec.NewGeneratedNameForNode(n, printer.AutoGenerateOptions{}) @@ -573,7 +575,7 @@ func TestGeneratedNameForMethod2(t *testing.T) { ec := printer.NewEmitContext() file := parsetestutil.ParseTypeScript("class C { 0() {} }", false /*jsx*/) - binder.BindSourceFile(file, &core.CompilerOptions{}) + binder.BindSourceFile(file, defaultSourceFileAffectingOptions) n := file.Statements.Nodes[0].AsClassDeclaration().Members.Nodes[0] name1 := ec.NewGeneratedNameForNode(n, printer.AutoGenerateOptions{}) @@ -590,7 +592,7 @@ func TestGeneratedPrivateNameForMethod(t *testing.T) { ec := printer.NewEmitContext() file := parsetestutil.ParseTypeScript("class C { m() {} }", false /*jsx*/) - binder.BindSourceFile(file, &core.CompilerOptions{}) + binder.BindSourceFile(file, defaultSourceFileAffectingOptions) n := file.Statements.Nodes[0].AsClassDeclaration().Members.Nodes[0] name1 := ec.NewGeneratedPrivateNameForNode(n, printer.AutoGenerateOptions{}) @@ -607,7 +609,7 @@ func TestGeneratedNameForComputedPropertyName(t *testing.T) { ec := printer.NewEmitContext() file := parsetestutil.ParseTypeScript("class C { [x] }", false /*jsx*/) - binder.BindSourceFile(file, &core.CompilerOptions{}) + binder.BindSourceFile(file, defaultSourceFileAffectingOptions) n := file.Statements.Nodes[0].AsClassDeclaration().Members.Nodes[0].Name() name1 := ec.NewGeneratedNameForNode(n, printer.AutoGenerateOptions{}) @@ -624,7 +626,7 @@ func TestGeneratedNameForOther(t *testing.T) { ec := printer.NewEmitContext() file := parsetestutil.ParseTypeScript("class C { [x] }", false /*jsx*/) - binder.BindSourceFile(file, &core.CompilerOptions{}) + binder.BindSourceFile(file, defaultSourceFileAffectingOptions) n := ec.Factory.NewObjectLiteralExpression( ec.Factory.NewNodeList([]*ast.Node{}), diff --git a/internal/project/documentregistry.go b/internal/project/documentregistry.go index fe4460ccc7..f8d86693d8 100644 --- a/internal/project/documentregistry.go +++ b/internal/project/documentregistry.go @@ -19,7 +19,7 @@ type registryKey struct { func newRegistryKey(options *core.CompilerOptions, path tspath.Path, scriptKind core.ScriptKind) registryKey { return registryKey{ - SourceFileAffectingCompilerOptions: options.SourceFileAffecting(), + SourceFileAffectingCompilerOptions: *options.SourceFileAffecting(), path: path, scriptKind: scriptKind, } diff --git a/internal/project/service_test.go b/internal/project/service_test.go index a0164f746a..2d1b14dc8a 100644 --- a/internal/project/service_test.go +++ b/internal/project/service_test.go @@ -179,8 +179,11 @@ func TestService(t *testing.T) { filesCopy := maps.Clone(files) filesCopy["/home/projects/TS/p2/tsconfig.json"] = `{ "compilerOptions": { - "module": "nodenext" - } + "noLib": true, + "module": "nodenext", + "strict": true, + "noCheck" true // Added + }, }` filesCopy["/home/projects/TS/p2/src/index.ts"] = `import { x } from "../../p1/src/x";` service, _ := setup(filesCopy) diff --git a/internal/testutil/harnessutil/harnessutil.go b/internal/testutil/harnessutil/harnessutil.go index e80f345bb5..f3ac5522d3 100644 --- a/internal/testutil/harnessutil/harnessutil.go +++ b/internal/testutil/harnessutil/harnessutil.go @@ -444,7 +444,7 @@ func (h *cachedCompilerHost) GetSourceFile(fileName string, path tspath.Path, la text, _ := h.FS().ReadFile(fileName) key := sourceFileCacheKey{ - SourceFileAffectingCompilerOptions: h.options.SourceFileAffecting(), + SourceFileAffectingCompilerOptions: *h.options.SourceFileAffecting(), fileName: fileName, path: path, languageVersion: languageVersion, diff --git a/internal/transformers/commonjsmodule_test.go b/internal/transformers/commonjsmodule_test.go index cf12798064..caeac780de 100644 --- a/internal/transformers/commonjsmodule_test.go +++ b/internal/transformers/commonjsmodule_test.go @@ -1020,16 +1020,17 @@ exports.a = a;`, compilerOptions := rec.options compilerOptions.ModuleKind = core.ModuleKindCommonJS + sourceFileAffecting := compilerOptions.SourceFileAffecting() file := parsetestutil.ParseTypeScript(rec.input, rec.jsx) parsetestutil.CheckDiagnostics(t, file) - binder.BindSourceFile(file, &compilerOptions) + binder.BindSourceFile(file, sourceFileAffecting) var other *ast.SourceFile if len(rec.other) > 0 { other = parsetestutil.ParseTypeScript(rec.other, rec.jsx) parsetestutil.CheckDiagnostics(t, other) - binder.BindSourceFile(other, &compilerOptions) + binder.BindSourceFile(other, sourceFileAffecting) } emitContext := printer.NewEmitContext() diff --git a/internal/transformers/esmodule_test.go b/internal/transformers/esmodule_test.go index ff1eef01e1..65aadc1088 100644 --- a/internal/transformers/esmodule_test.go +++ b/internal/transformers/esmodule_test.go @@ -220,15 +220,16 @@ var __rewriteRelativeImportExtension;`, t.Parallel() compilerOptions := rec.options + sourceFileAffecting := compilerOptions.SourceFileAffecting() file := parsetestutil.ParseTypeScript(rec.input, rec.jsx) parsetestutil.CheckDiagnostics(t, file) - binder.BindSourceFile(file, &compilerOptions) + binder.BindSourceFile(file, sourceFileAffecting) var other *ast.SourceFile if len(rec.other) > 0 { other = parsetestutil.ParseTypeScript(rec.other, rec.jsx) parsetestutil.CheckDiagnostics(t, other) - binder.BindSourceFile(other, &compilerOptions) + binder.BindSourceFile(other, sourceFileAffecting) } emitContext := printer.NewEmitContext() diff --git a/internal/transformers/importelision_test.go b/internal/transformers/importelision_test.go index 5d4034246c..63179e2192 100644 --- a/internal/transformers/importelision_test.go +++ b/internal/transformers/importelision_test.go @@ -35,7 +35,7 @@ func (p *fakeProgram) BindSourceFiles() { for _, file := range p.files { if !file.IsBound() { wg.Queue(func() { - binder.BindSourceFile(file, p.compilerOptions) + binder.BindSourceFile(file, p.compilerOptions.SourceFileAffecting()) }) } } diff --git a/internal/transformers/runtimesyntax_test.go b/internal/transformers/runtimesyntax_test.go index ab15428f7d..f102b5aec5 100644 --- a/internal/transformers/runtimesyntax_test.go +++ b/internal/transformers/runtimesyntax_test.go @@ -233,7 +233,7 @@ var E; options := &core.CompilerOptions{} file := parsetestutil.ParseTypeScript(rec.input, false /*jsx*/) parsetestutil.CheckDiagnostics(t, file) - binder.BindSourceFile(file, options) + binder.BindSourceFile(file, options.SourceFileAffecting()) emitContext := printer.NewEmitContext() resolver := binder.NewReferenceResolver(options, binder.ReferenceResolverHooks{}) emittestutil.CheckEmit(t, emitContext, NewRuntimeSyntaxTransformer(emitContext, options, resolver).TransformSourceFile(file), rec.output) @@ -411,7 +411,7 @@ func TestNamespaceTransformer(t *testing.T) { options := &core.CompilerOptions{} file := parsetestutil.ParseTypeScript(rec.input, false /*jsx*/) parsetestutil.CheckDiagnostics(t, file) - binder.BindSourceFile(file, options) + binder.BindSourceFile(file, options.SourceFileAffecting()) emitContext := printer.NewEmitContext() resolver := binder.NewReferenceResolver(options, binder.ReferenceResolverHooks{}) emittestutil.CheckEmit(t, emitContext, NewRuntimeSyntaxTransformer(emitContext, options, resolver).TransformSourceFile(file), rec.output) @@ -447,7 +447,7 @@ func TestParameterPropertyTransformer(t *testing.T) { options := &core.CompilerOptions{} file := parsetestutil.ParseTypeScript(rec.input, false /*jsx*/) parsetestutil.CheckDiagnostics(t, file) - binder.BindSourceFile(file, options) + binder.BindSourceFile(file, options.SourceFileAffecting()) emitContext := printer.NewEmitContext() resolver := binder.NewReferenceResolver(options, binder.ReferenceResolverHooks{}) file = NewTypeEraserTransformer(emitContext, options).TransformSourceFile(file) From bab97ade84e078a137f9d29bf8040d51234d9381 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 10 Apr 2025 08:59:08 -0700 Subject: [PATCH 2/2] wip --- internal/ast/ast.go | 6 ++- internal/ast/utilities.go | 5 +++ internal/checker/checker.go | 16 ++++---- internal/checker/jsx.go | 2 +- internal/checker/relater.go | 2 +- internal/checker/utilities.go | 6 +-- internal/core/compileroptions.go | 16 ++++++++ internal/parser/parser.go | 68 +++++++++++++++++++++++++++++++- 8 files changed, 104 insertions(+), 17 deletions(-) diff --git a/internal/ast/ast.go b/internal/ast/ast.go index 7d77dbff0b..2c6efe72c1 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -8184,6 +8184,10 @@ func (node *JsxFragment) computeSubtreeFacts() SubtreeFacts { SubtreeContainsJsx } +func IsJsxFragment(node *Node) bool { + return node.Kind == KindJsxFragment +} + /// The opening element of a <>... JsxFragment type JsxOpeningFragment struct { @@ -9711,6 +9715,7 @@ type SourceFile struct { TypeReferenceDirectives []*FileReference LibReferenceDirectives []*FileReference CheckJsDirective *CheckJsDirective + ExternalModuleIndicator *Node // Fields set by binder @@ -9740,7 +9745,6 @@ type SourceFile struct { // !!! CommonJSModuleIndicator *Node - ExternalModuleIndicator *Node JSGlobalAugmentations SymbolTable } diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go index 30a4d350b2..049050ccd7 100644 --- a/internal/ast/utilities.go +++ b/internal/ast/utilities.go @@ -2639,3 +2639,8 @@ func getPragmaArgument(pragma *Pragma, name string) string { } return "" } + + +func IsJsxOpeningLikeElement(node *Node) bool { + return IsJsxOpeningElement(node) || IsJsxSelfClosingElement(node) +} diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 9b60982ccd..dddc978ca8 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -8330,7 +8330,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 @@ -8636,7 +8636,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 @@ -8756,7 +8756,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) @@ -8897,7 +8897,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 @@ -26310,7 +26310,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 } @@ -26475,7 +26475,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 @@ -27520,7 +27520,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 @@ -27741,7 +27741,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()} } diff --git a/internal/checker/jsx.go b/internal/checker/jsx.go index eacb7b71fc..3ae1e0203d 100644 --- a/internal/checker/jsx.go +++ b/internal/checker/jsx.go @@ -118,7 +118,7 @@ func (c *Checker) checkJsxAttributes(node *ast.Node, checkMode CheckMode) *Type } func (c *Checker) checkJsxOpeningLikeElementOrOpeningFragment(node *ast.Node) { - isNodeOpeningLikeElement := isJsxOpeningLikeElement(node) + isNodeOpeningLikeElement := ast.IsJsxOpeningLikeElement(node) if isNodeOpeningLikeElement { c.checkGrammarJsxElement(node) } diff --git a/internal/checker/relater.go b/internal/checker/relater.go index 4cca82aa2c..3c45467d95 100644 --- a/internal/checker/relater.go +++ b/internal/checker/relater.go @@ -2693,7 +2693,7 @@ func (r *Relater) hasExcessProperties(source *Type, target *Type, reportErrors b if r.errorNode == nil { panic("No errorNode in hasExcessProperties") } - if ast.IsJsxAttributes(r.errorNode) || isJsxOpeningLikeElement(r.errorNode) || isJsxOpeningLikeElement(r.errorNode.Parent) { + if ast.IsJsxAttributes(r.errorNode) || ast.IsJsxOpeningLikeElement(r.errorNode) || ast.IsJsxOpeningLikeElement(r.errorNode.Parent) { // JsxAttributes has an object-literal flag and undergo same type-assignablity check as normal object-literal. // However, using an object-literal error message will be very confusing to the users so we give different a message. if prop.ValueDeclaration != nil && ast.IsJsxAttribute(prop.ValueDeclaration) && ast.GetSourceFileOfNode(r.errorNode) == ast.GetSourceFileOfNode(prop.ValueDeclaration.Name()) { diff --git a/internal/checker/utilities.go b/internal/checker/utilities.go index e2231126f5..c654ec17c8 100644 --- a/internal/checker/utilities.go +++ b/internal/checker/utilities.go @@ -1406,10 +1406,6 @@ func reverseAccessKind(a AccessKind) AccessKind { panic("Unhandled case in reverseAccessKind") } -func isJsxOpeningLikeElement(node *ast.Node) bool { - return ast.IsJsxOpeningElement(node) || ast.IsJsxSelfClosingElement(node) -} - // Deprecated in favor of `ast.IsObjectLiteralElement` func isObjectLiteralElementLike(node *ast.Node) bool { return ast.IsObjectLiteralElement(node) @@ -1532,7 +1528,7 @@ func isCallChain(node *ast.Node) bool { } func (c *Checker) callLikeExpressionMayHaveTypeArguments(node *ast.Node) bool { - return isCallOrNewExpression(node) || ast.IsTaggedTemplateExpression(node) || isJsxOpeningLikeElement(node) + return isCallOrNewExpression(node) || ast.IsTaggedTemplateExpression(node) || ast.IsJsxOpeningLikeElement(node) } func isSuperCall(n *ast.Node) bool { diff --git a/internal/core/compileroptions.go b/internal/core/compileroptions.go index b35bf50f09..5e31a15114 100644 --- a/internal/core/compileroptions.go +++ b/internal/core/compileroptions.go @@ -176,6 +176,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) GetESModuleInterop() bool { if options.ESModuleInterop != TSUnknown { return options.ESModuleInterop == TSTrue @@ -275,7 +287,9 @@ type SourceFileAffectingCompilerOptions struct { AllowUnreachableCode Tristate AllowUnusedLabels Tristate BindInStrictMode bool + EmitModuleDetectionKind ModuleDetectionKind EmitScriptTarget ScriptTarget + JsxEmit JsxEmit NoFallthroughCasesInSwitch Tristate ShouldPreserveConstEnums bool } @@ -285,7 +299,9 @@ func (options *CompilerOptions) SourceFileAffecting() *SourceFileAffectingCompil AllowUnreachableCode: options.AllowUnreachableCode, AllowUnusedLabels: options.AllowUnusedLabels, BindInStrictMode: options.AlwaysStrict.IsTrue() || options.Strict.IsTrue(), + EmitModuleDetectionKind: options.GetEmitModuleDetectionKind(), EmitScriptTarget: options.GetEmitScriptTarget(), + JsxEmit: options.Jsx, NoFallthroughCasesInSwitch: options.NoFallthroughCasesInSwitch, ShouldPreserveConstEnums: options.ShouldPreserveConstEnums(), } diff --git a/internal/parser/parser.go b/internal/parser/parser.go index 9445881b46..2e53e4f5e8 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -345,7 +345,6 @@ func (p *Parser) finishSourceFile(result *ast.SourceFile, isDeclarationFile bool result.Pragmas = getCommentPragmas(&p.factory, p.sourceText) processPragmasIntoFields(result) result.SetDiagnostics(attachFileToDiagnostics(p.diagnostics, result)) - result.ExternalModuleIndicator = isFileProbablyExternalModule(result) // !!! result.IsDeclarationFile = isDeclarationFile result.LanguageVersion = p.languageVersion result.LanguageVariant = p.languageVariant @@ -354,10 +353,77 @@ func (p *Parser) finishSourceFile(result *ast.SourceFile, isDeclarationFile bool result.Identifiers = p.identifiers result.IdentifierCount = p.identifierCount result.SetJSDocCache(p.jsdocCache) + result.ExternalModuleIndicator = p.getExternalModuleIndicator(result, &core.SourceFileAffectingCompilerOptions{}) // TODO(jakebailey) p.jsdocCache = nil p.identifiers = nil } +func (p *Parser) getExternalModuleIndicator(file *ast.SourceFile, options *core.SourceFileAffectingCompilerOptions) *ast.Node { + // All detection kinds start by checking this. + if node := isFileProbablyExternalModule(file); node != nil { + return node + } + + switch options.EmitModuleDetectionKind { + case core.ModuleDetectionKindForce: + // All non-declaration files are modules, declaration files still do the usual isFileProbablyExternalModule + if !file.IsDeclarationFile { + return file.AsNode() + } + return nil + case core.ModuleDetectionKindLegacy: + // Files are modules if they have imports, exports, or import.meta + return nil + case core.ModuleDetectionKindAuto: + // If module is nodenext or node16, all esm format files are modules + // If jsx is react-jsx or react-jsxdev then jsx tags force module-ness + // otherwise, the presence of import or export statments (or import.meta) implies module-ness + if options.JsxEmit == core.JsxEmitReactJSX || options.JsxEmit == core.JsxEmitReactJSXDev { + if node := isFileModuleFromUsingJSXTag(file); node != nil { + return node + } + } + return isFileForcedToBeModuleByFormat(file, options) + default: + panic("Unhandled case in getExternalModuleIndicator") + } +} + +func isFileModuleFromUsingJSXTag(file *ast.SourceFile) *ast.Node { + if file.IsDeclarationFile { + return nil + } + return walkTreeForJSXTags(file.AsNode()) +} + +func walkTreeForJSXTags(node *ast.Node) *ast.Node { + var found *ast.Node + + var visitor func(n *ast.Node) bool + visitor = func(n *ast.Node) bool { + if found != nil { + return true + } + if node.SubtreeFacts()&ast.SubtreeContainsJsx == 0 { + return true + } + if ast.IsJsxOpeningElement(node) || ast.IsJsxFragment(node) { + found = node + return true + } + return node.ForEachChild(visitor) + } + visitor(node) + + return found +} + +func isFileForcedToBeModuleByFormat(file *ast.SourceFile, options *core.SourceFileAffectingCompilerOptions) *ast.Node { + panic("TODO") + + // GetImpliedNodeFormatForEmitWorker but we need the metadata???? +} + func (p *Parser) parseToplevelStatement(i int) *ast.Node { p.statementHasAwaitIdentifier = false statement := p.parseStatement()