Skip to content

Incremental port #1322

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 48 commits into from
Jul 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
ede403a
Refactor
sheetalkamat Jun 20, 2025
ba71811
Add stub for incremental
sheetalkamat Jun 20, 2025
5910a23
Incremental
sheetalkamat Jul 3, 2025
b642e22
Make buildinfo tests portable and readable
sheetalkamat Jun 26, 2025
4ef2a94
Handle casing with compiler options serialization
sheetalkamat Jun 26, 2025
da2a0c4
make common js default impolied file format
sheetalkamat Jun 26, 2025
9123231
More changes
sheetalkamat Jun 27, 2025
6c0f24e
More changes
sheetalkamat Jun 27, 2025
b6cdfce
fix test
sheetalkamat Jun 27, 2025
a2f13d9
more change
sheetalkamat Jun 27, 2025
703cd4b
Parallel!!!
sheetalkamat Jul 3, 2025
4ad58ed
Tests
sheetalkamat Jun 30, 2025
7395f27
Watch changes
sheetalkamat Jun 30, 2025
77c8a12
Verify diagnostic and options serializing
sheetalkamat Jun 30, 2025
31a8c76
Tests for reference map
sheetalkamat Jun 30, 2025
3f2fbfd
Lint
sheetalkamat Jul 1, 2025
a6cdc84
Use fake lib contents to make it deterministic for baselining as well…
sheetalkamat Jul 1, 2025
93c6232
Fix race with latest dts file
sheetalkamat Jul 1, 2025
bdb9076
Fix more tests
sheetalkamat Jul 1, 2025
8c0d4b5
change to test
sheetalkamat Jul 1, 2025
8c62de6
Lint
sheetalkamat Jul 1, 2025
f93f053
Support for edits in tsc tests
sheetalkamat Jul 1, 2025
efd838e
Fix race with default libs
sheetalkamat Jul 1, 2025
1973134
Always collect references for proper program update
sheetalkamat Jul 1, 2025
2ab1229
Add baseline for semantic diagnostics refresh to check for correctness
sheetalkamat Jul 1, 2025
8117de5
No need for scenario name in test name
sheetalkamat Jul 1, 2025
30f10d3
Baseline signature compute
sheetalkamat Jul 1, 2025
3a5e3ac
Test incremental edit
sheetalkamat Jul 1, 2025
e1b3dc7
Fix modified edit baseline
sheetalkamat Jul 1, 2025
e7df40d
Use semantic diagnosics from old snapshot to compare
sheetalkamat Jul 1, 2025
766d372
Fix incremental updates
sheetalkamat Jul 1, 2025
d0cefe6
More tests and updates
sheetalkamat Jul 2, 2025
e048d22
Unify tsc runner even more
sheetalkamat Jul 2, 2025
2f89867
Test fs refactor
sheetalkamat Jul 2, 2025
776ba84
rename test baselines
sheetalkamat Jul 2, 2025
8d8c62c
Write signature and its text for testing
sheetalkamat Jul 21, 2025
f4d5056
Incremental correctness
sheetalkamat Jul 2, 2025
7d7d4f8
Cleanup
sheetalkamat Jul 21, 2025
953be0d
Fix lint
sheetalkamat Jul 21, 2025
405d19f
Feedback
sheetalkamat Jul 22, 2025
3372773
keep mod time and compare it
sheetalkamat Jul 23, 2025
c84c4e1
Use written files for diffing
sheetalkamat Jul 23, 2025
4002609
diffing sorted
sheetalkamat Jul 23, 2025
c87868f
Merge branch 'main' into incremental
sheetalkamat Jul 23, 2025
f8f1040
use fnv.New128a instead
sheetalkamat Jul 23, 2025
ce8668f
lint
sheetalkamat Jul 23, 2025
14d9532
Update internal/incremental/snapshot.go
sheetalkamat Jul 23, 2025
532dad6
Fix error
sheetalkamat Jul 23, 2025
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
3 changes: 2 additions & 1 deletion cmd/tsgo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ func runMain() int {
return runAPI(args[1:])
}
}
return int(execute.CommandLine(newSystem(), nil, args))
result := execute.CommandLine(newSystem(), args, false)
return int(result.Status)
}
29 changes: 29 additions & 0 deletions internal/ast/diagnostic.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type Diagnostic struct {
relatedInformation []*Diagnostic
reportsUnnecessary bool
reportsDeprecated bool
skippedOnNoEmit bool
}

func (d *Diagnostic) File() *SourceFile { return d.file }
Expand All @@ -35,10 +36,12 @@ func (d *Diagnostic) MessageChain() []*Diagnostic { return d.messageChain
func (d *Diagnostic) RelatedInformation() []*Diagnostic { return d.relatedInformation }
func (d *Diagnostic) ReportsUnnecessary() bool { return d.reportsUnnecessary }
func (d *Diagnostic) ReportsDeprecated() bool { return d.reportsDeprecated }
func (d *Diagnostic) SkippedOnNoEmit() bool { return d.skippedOnNoEmit }

func (d *Diagnostic) SetFile(file *SourceFile) { d.file = file }
func (d *Diagnostic) SetLocation(loc core.TextRange) { d.loc = loc }
func (d *Diagnostic) SetCategory(category diagnostics.Category) { d.category = category }
func (d *Diagnostic) SetSkippedOnNoEmit() { d.skippedOnNoEmit = true }

func (d *Diagnostic) SetMessageChain(messageChain []*Diagnostic) *Diagnostic {
d.messageChain = messageChain
Expand Down Expand Up @@ -69,6 +72,32 @@ func (d *Diagnostic) Clone() *Diagnostic {
return &result
}

func NewDiagnosticWith(
file *SourceFile,
loc core.TextRange,
code int32,
category diagnostics.Category,
message string,
messageChain []*Diagnostic,
relatedInformation []*Diagnostic,
reportsUnnecessary bool,
reportsDeprecated bool,
skippedOnNoEmit bool,
) *Diagnostic {
return &Diagnostic{
file: file,
loc: loc,
code: code,
category: category,
message: message,
messageChain: messageChain,
relatedInformation: relatedInformation,
reportsUnnecessary: reportsUnnecessary,
reportsDeprecated: reportsDeprecated,
skippedOnNoEmit: skippedOnNoEmit,
}
}

func NewDiagnostic(file *SourceFile, loc core.TextRange, message *diagnostics.Message, args ...any) *Diagnostic {
return &Diagnostic{
file: file,
Expand Down
4 changes: 4 additions & 0 deletions internal/ast/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -1635,6 +1635,10 @@ func IsModuleAugmentationExternal(node *Node) bool {
return false
}

func IsModuleWithStringLiteralName(node *Node) bool {
return IsModuleDeclaration(node) && node.Name().Kind == KindStringLiteral
}

func GetContainingClass(node *Node) *Node {
return FindAncestor(node.Parent, IsClassLike)
}
Expand Down
23 changes: 21 additions & 2 deletions internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,8 @@ type Checker struct {
packagesMap map[string]bool
activeMappers []*TypeMapper
activeTypeMappersCaches []map[string]*Type
ambientModulesOnce sync.Once
ambientModules []*ast.Symbol
}

func NewChecker(program Program) *Checker {
Expand Down Expand Up @@ -2092,7 +2094,7 @@ func (c *Checker) getSymbol(symbols ast.SymbolTable, name string, meaning ast.Sy
}

func (c *Checker) CheckSourceFile(ctx context.Context, sourceFile *ast.SourceFile) {
if SkipTypeChecking(sourceFile, c.compilerOptions, c.program) {
if SkipTypeChecking(sourceFile, c.compilerOptions, c.program, false) {
return
}
c.checkSourceFile(ctx, sourceFile)
Expand Down Expand Up @@ -10076,7 +10078,7 @@ func (c *Checker) checkCollisionWithRequireExportsInGeneratedCode(node *ast.Node
parent := ast.GetDeclarationContainer(node)
if ast.IsSourceFile(parent) && ast.IsExternalOrCommonJSModule(parent.AsSourceFile()) {
// If the declaration happens to be in external module, report error that require and exports are reserved keywords
c.error(name, diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module, scanner.DeclarationNameToString(name), scanner.DeclarationNameToString(name))
c.errorSkippedOnNoEmit(name, diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module, scanner.DeclarationNameToString(name), scanner.DeclarationNameToString(name))
}
}

Expand Down Expand Up @@ -13330,6 +13332,12 @@ func (c *Checker) error(location *ast.Node, message *diagnostics.Message, args .
return diagnostic
}

func (c *Checker) errorSkippedOnNoEmit(location *ast.Node, message *diagnostics.Message, args ...any) *ast.Diagnostic {
diagnostic := c.error(location, message, args...)
diagnostic.SetSkippedOnNoEmit()
return diagnostic
}

func (c *Checker) errorOrSuggestion(isError bool, location *ast.Node, message *diagnostics.Message, args ...any) {
c.addErrorOrSuggestion(isError, NewDiagnosticForNode(location, message, args...))
}
Expand Down Expand Up @@ -14815,6 +14823,17 @@ func (c *Checker) tryFindAmbientModule(moduleName string, withAugmentations bool
return symbol
}

func (c *Checker) GetAmbientModules() []*ast.Symbol {
c.ambientModulesOnce.Do(func() {
for sym, global := range c.globals {
if strings.HasPrefix(sym, "\"") && strings.HasSuffix(sym, "\"") {
c.ambientModules = append(c.ambientModules, global)
}
}
})
return c.ambientModules
}

func (c *Checker) resolveExternalModuleSymbol(moduleSymbol *ast.Symbol, dontResolveAlias bool) *ast.Symbol {
if moduleSymbol != nil {
exportEquals := c.resolveSymbolEx(moduleSymbol.Exports[ast.InternalSymbolNameExportEquals], dontResolveAlias)
Expand Down
8 changes: 4 additions & 4 deletions internal/checker/checker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ foo.bar;`
fs = bundled.WrapFS(fs)

cd := "/"
host := compiler.NewCompilerHost(cd, fs, bundled.LibPath())
host := compiler.NewCompilerHost(cd, fs, bundled.LibPath(), nil)

parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile("/tsconfig.json", &core.CompilerOptions{}, host, nil)
assert.Equal(t, len(errors), 0, "Expected no errors in parsed command line")
Expand Down Expand Up @@ -70,14 +70,14 @@ func TestCheckSrcCompiler(t *testing.T) {

rootPath := tspath.CombinePaths(tspath.NormalizeSlashes(repo.TypeScriptSubmodulePath), "src", "compiler")

host := compiler.NewCompilerHost(rootPath, fs, bundled.LibPath())
host := compiler.NewCompilerHost(rootPath, fs, bundled.LibPath(), nil)
parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile(tspath.CombinePaths(rootPath, "tsconfig.json"), &core.CompilerOptions{}, host, nil)
assert.Equal(t, len(errors), 0, "Expected no errors in parsed command line")
p := compiler.NewProgram(compiler.ProgramOptions{
Config: parsed,
Host: host,
})
p.CheckSourceFiles(t.Context())
p.CheckSourceFiles(t.Context(), nil)
}

func BenchmarkNewChecker(b *testing.B) {
Expand All @@ -87,7 +87,7 @@ func BenchmarkNewChecker(b *testing.B) {

rootPath := tspath.CombinePaths(tspath.NormalizeSlashes(repo.TypeScriptSubmodulePath), "src", "compiler")

host := compiler.NewCompilerHost(rootPath, fs, bundled.LibPath())
host := compiler.NewCompilerHost(rootPath, fs, bundled.LibPath(), nil)
parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile(tspath.CombinePaths(rootPath, "tsconfig.json"), &core.CompilerOptions{}, host, nil)
assert.Equal(b, len(errors), 0, "Expected no errors in parsed command line")
p := compiler.NewProgram(compiler.ProgramOptions{
Expand Down
13 changes: 12 additions & 1 deletion internal/checker/grammarchecks.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ func (c *Checker) grammarErrorOnNode(node *ast.Node, message *diagnostics.Messag
return false
}

func (c *Checker) grammarErrorOnNodeSkippedOnNoEmit(node *ast.Node, message *diagnostics.Message, args ...any) bool {
sourceFile := ast.GetSourceFileOfNode(node)
if !c.hasParseDiagnostics(sourceFile) {
d := NewDiagnosticForNode(node, message, args...)
d.SetSkippedOnNoEmit()
c.diagnostics.Add(d)
return true
}
return false
}

func (c *Checker) checkGrammarRegularExpressionLiteral(_ *ast.RegularExpressionLiteral) bool {
// !!!
// Unclear if this is needed until regular expression parsing is more thoroughly implemented.
Expand Down Expand Up @@ -1648,7 +1659,7 @@ func (c *Checker) checkGrammarVariableDeclaration(node *ast.VariableDeclaration)
func (c *Checker) checkGrammarForEsModuleMarkerInBindingName(name *ast.Node) bool {
if ast.IsIdentifier(name) {
if name.Text() == "__esModule" {
return c.grammarErrorOnNode(name, diagnostics.Identifier_expected_esModule_is_reserved_as_an_exported_marker_when_transforming_ECMAScript_modules)
return c.grammarErrorOnNodeSkippedOnNoEmit(name, diagnostics.Identifier_expected_esModule_is_reserved_as_an_exported_marker_when_transforming_ECMAScript_modules)
}
} else {
for _, element := range name.AsBindingPattern().Elements.Nodes {
Expand Down
6 changes: 1 addition & 5 deletions internal/checker/symbolaccessibility.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,7 @@ func (ch *Checker) IsAnySymbolAccessible(symbols []*ast.Symbol, enclosingDeclara
}

func hasNonGlobalAugmentationExternalModuleSymbol(declaration *ast.Node) bool {
return isModuleWithStringLiteralName(declaration) || (declaration.Kind == ast.KindSourceFile && ast.IsExternalOrCommonJSModule(declaration.AsSourceFile()))
}

func isModuleWithStringLiteralName(node *ast.Node) bool {
return ast.IsModuleDeclaration(node) && node.Name().Kind == ast.KindStringLiteral
return ast.IsModuleWithStringLiteralName(declaration) || (declaration.Kind == ast.KindSourceFile && ast.IsExternalOrCommonJSModule(declaration.AsSourceFile()))
}

func getQualifiedLeftMeaning(rightMeaning ast.SymbolFlags) ast.SymbolFlags {
Expand Down
4 changes: 2 additions & 2 deletions internal/checker/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -1460,8 +1460,8 @@ func forEachYieldExpression(body *ast.Node, visitor func(expr *ast.Node)) {
traverse(body)
}

func SkipTypeChecking(sourceFile *ast.SourceFile, options *core.CompilerOptions, host Program) bool {
return options.NoCheck.IsTrue() ||
func SkipTypeChecking(sourceFile *ast.SourceFile, options *core.CompilerOptions, host Program, ignoreNoCheck bool) bool {
return (!ignoreNoCheck && options.NoCheck.IsTrue()) ||
options.SkipLibCheck.IsTrue() && sourceFile.IsDeclarationFile ||
options.SkipDefaultLibCheck.IsTrue() && host.IsSourceFileDefaultLibrary(sourceFile.Path()) ||
host.IsSourceFromProjectReference(sourceFile.Path()) ||
Expand Down
49 changes: 49 additions & 0 deletions internal/collections/manytomanyset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package collections

import "fmt"

type ManyToManySet[K comparable, V comparable] struct {
keyToValueSet map[K]*Set[V]
valueToKeySet map[V]*Set[K]
}

func (m *ManyToManySet[K, V]) GetKeys(value V) (*Set[K], bool) {
keys, present := m.valueToKeySet[value]
return keys, present
}

func (m *ManyToManySet[K, V]) GetValues(key K) (*Set[V], bool) {
values, present := m.keyToValueSet[key]
return values, present
}

func (m *ManyToManySet[K, V]) Len() int {
return len(m.keyToValueSet)
}

func (m *ManyToManySet[K, V]) Keys() map[K]*Set[V] {
return m.keyToValueSet
}

func (m *ManyToManySet[K, V]) Set(key K, valueSet *Set[V]) {
_, hasExisting := m.keyToValueSet[key]
if hasExisting {
panic("ManyToManySet.Set: key already exists: " + fmt.Sprintf("%v", key))
}
if m.keyToValueSet == nil {
m.keyToValueSet = make(map[K]*Set[V])
}
m.keyToValueSet[key] = valueSet
for value := range valueSet.Keys() {
// Add to valueToKeySet
keySetForValue, exists := m.valueToKeySet[value]
if !exists {
if m.valueToKeySet == nil {
m.valueToKeySet = make(map[V]*Set[K])
}
keySetForValue = &Set[K]{}
m.valueToKeySet[value] = keySetForValue
}
keySetForValue.Add(key)
}
}
20 changes: 20 additions & 0 deletions internal/collections/set.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package collections

import "maps"

type Set[T comparable] struct {
M map[T]struct{}
}
Expand Down Expand Up @@ -48,6 +50,24 @@ func (s *Set[T]) AddIfAbsent(key T) bool {
return true
}

func (s *Set[T]) Clone() *Set[T] {
if s == nil {
return nil
}
clone := &Set[T]{M: maps.Clone(s.M)}
return clone
}

func (s *Set[T]) Equals(other *Set[T]) bool {
if s == other {
return true
}
if s == nil || other == nil {
return false
}
return maps.Equal(s.M, other.M)
}

func NewSetFromItems[T comparable](items ...T) *Set[T] {
s := &Set[T]{}
for _, item := range items {
Expand Down
16 changes: 16 additions & 0 deletions internal/collections/syncset.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,19 @@ func (s *SyncSet[T]) Add(key T) {
func (s *SyncSet[T]) Delete(key T) {
s.m.Delete(key)
}

func (s *SyncSet[T]) Range(fn func(key T) bool) {
s.m.Range(func(key T, value struct{}) bool {
return fn(key)
})
}

func (s *SyncSet[T]) ToSlice() []T {
var arr []T
arr = make([]T, 0, s.m.Size())
s.m.Range(func(key T, value struct{}) bool {
arr = append(arr, key)
return true
})
return arr
}
2 changes: 1 addition & 1 deletion internal/compiler/checkerpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func (p *checkerPool) GetChecker(ctx context.Context) (*checker.Checker, func())

func (p *checkerPool) createCheckers() {
p.createCheckersOnce.Do(func() {
wg := core.NewWorkGroup(p.program.singleThreaded())
wg := core.NewWorkGroup(p.program.SingleThreaded())
for i := range p.checkerCount {
wg.Queue(func() {
p.checkers[i] = checker.NewChecker(p.program)
Expand Down
10 changes: 1 addition & 9 deletions internal/compiler/emitHost.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,6 @@ import (
"github.com/microsoft/typescript-go/internal/tspath"
)

type WriteFileData struct {
SourceMapUrlPos int
// BuildInfo BuildInfo
Diagnostics []*ast.Diagnostic
DiffersOnlyInMap bool
SkippedDtsWrite bool
}

// NOTE: EmitHost operations must be thread-safe
type EmitHost interface {
printer.EmitHost
Expand Down Expand Up @@ -120,7 +112,7 @@ func (host *emitHost) IsEmitBlocked(file string) bool {
return false
}

func (host *emitHost) WriteFile(fileName string, text string, writeByteOrderMark bool, _ []*ast.SourceFile, _ *printer.WriteFileData) error {
func (host *emitHost) WriteFile(fileName string, text string, writeByteOrderMark bool) error {
return host.program.Host().FS().WriteFile(fileName, text, writeByteOrderMark)
}

Expand Down
Loading
Loading