Skip to content

Commit 98add1b

Browse files
authored
fix: avoid repeated root scans in no-op builds (#64898)
1 parent b865719 commit 98add1b

4 files changed

Lines changed: 114 additions & 29 deletions

File tree

‎tsc/internal/contentmapper/contentmapper.go‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,12 @@ type Mapper struct {
6161
ContributionID string `json:"-"`
6262
}
6363

64-
var supportedVirtualExtensions = collections.NewSetFromItems(
65-
".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts", ".json",
66-
)
67-
6864
func IsSupportedVirtualExtension(extension string) bool {
69-
return supportedVirtualExtensions.Has(extension)
65+
switch extension {
66+
case ".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts", ".json":
67+
return true
68+
}
69+
return false
7070
}
7171

7272
func IsValidExtension(extension string) bool {

‎tsc/internal/execute/build/buildtask.go‎

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package build
22

33
import (
44
"fmt"
5-
"iter"
65
"slices"
76
"strconv"
87
"strings"
@@ -491,7 +490,7 @@ func (t *BuildTask) getUpToDateStatus(orchestrator *Orchestrator, configPath tsp
491490
if seenRoots.Has(inputPath) || resolvedRoots.Has(inputPath) {
492491
continue
493492
}
494-
if isContentMapperSupplementalBuildInfoPath(inputPath, getBuildInfoRootInfoReader().Roots()) &&
493+
if isContentMapperVirtualFilePath(inputPath, getBuildInfoRootInfoReader()) &&
495494
!orchestrator.host.FS().FileExists(inputFileName) {
496495
continue
497496
}
@@ -620,21 +619,25 @@ func (t *BuildTask) getUpToDateStatus(orchestrator *Orchestrator, configPath tsp
620619
}
621620
}
622621

623-
func isContentMapperSupplementalBuildInfoPath(inputPath tspath.PathKey, roots iter.Seq[tspath.PathKey]) bool {
624-
for root := range roots {
625-
suffix, ok := strings.CutPrefix(string(inputPath), string(root)+".")
626-
if !ok {
627-
continue
628-
}
629-
index, extension, ok := strings.Cut(suffix, ".")
630-
if !ok || extension == "" {
631-
continue
632-
}
633-
if _, err := strconv.Atoi(index); err == nil && contentmapper.IsSupportedVirtualExtension("."+extension) {
634-
return true
635-
}
622+
func isContentMapperVirtualFilePath(inputPath tspath.PathKey, roots *incremental.BuildInfoRootInfoReader) bool {
623+
extension := inputPath.Extension()
624+
if !contentmapper.IsSupportedVirtualExtension(extension) {
625+
return false
636626
}
637-
return false
627+
628+
pathWithoutExtension := tspath.RemoveExtension(inputPath.AsString(), extension)
629+
rootName, index, ok := strings.CutLast(pathWithoutExtension, ".")
630+
if !ok {
631+
return false
632+
}
633+
634+
rootPath, ok := tspath.TryPathKeyFromCanonical(rootName)
635+
if !ok || !roots.HasRoot(rootPath) {
636+
return false
637+
}
638+
639+
_, err := strconv.Atoi(index)
640+
return err == nil
638641
}
639642

640643
func (t *BuildTask) reportUpToDateStatus(orchestrator *Orchestrator) {
Lines changed: 86 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,98 @@
11
package build
22

33
import (
4-
"slices"
4+
"strconv"
55
"testing"
66

7+
"github.com/microsoft/TypeScript/tsc/internal/execute/incremental"
78
"github.com/microsoft/TypeScript/tsc/internal/tspath"
89
"gotest.tools/v3/assert"
910
)
1011

11-
func TestIsContentMapperSupplementalBuildInfoPath(t *testing.T) {
12+
func TestContentMapperVirtualFilePaths(t *testing.T) {
1213
t.Parallel()
13-
roots := []tspath.PathKey{"/src/app.vue", "/src/index.ts"}
14+
buildInfo := &incremental.BuildInfo{
15+
Root: []*incremental.BuildInfoRoot{
16+
{NonIncremental: "/src/a.vue"},
17+
{NonIncremental: "/src/b.ts"},
18+
{NonIncremental: "/src/a.vue.0"},
19+
},
20+
}
21+
roots := buildInfo.GetBuildInfoRootInfoReader("/", tspath.CaseSensitive)
1422

15-
assert.Assert(t, isContentMapperSupplementalBuildInfoPath("/src/app.vue.0.ts", slices.Values(roots)))
16-
assert.Assert(t, isContentMapperSupplementalBuildInfoPath("/src/app.vue.12.mts", slices.Values(roots)))
17-
assert.Assert(t, !isContentMapperSupplementalBuildInfoPath("/src/app.vue.ts", slices.Values(roots)))
18-
assert.Assert(t, !isContentMapperSupplementalBuildInfoPath("/src/app.vue.0.txt", slices.Values(roots)))
19-
assert.Assert(t, !isContentMapperSupplementalBuildInfoPath("/src/other.vue.0.ts", slices.Values(roots)))
23+
assert.Assert(t, isContentMapperVirtualFilePath("/src/a.vue.0.ts", roots))
24+
assert.Assert(t, isContentMapperVirtualFilePath("/src/a.vue.1.tsx", roots))
25+
assert.Assert(t, isContentMapperVirtualFilePath("/src/a.vue.12.mts", roots))
26+
assert.Assert(t, isContentMapperVirtualFilePath("/src/a.vue.2.cts", roots))
27+
assert.Assert(t, isContentMapperVirtualFilePath("/src/a.vue.0.js", roots))
28+
assert.Assert(t, isContentMapperVirtualFilePath("/src/a.vue.0.jsx", roots))
29+
assert.Assert(t, isContentMapperVirtualFilePath("/src/a.vue.0.mjs", roots))
30+
assert.Assert(t, isContentMapperVirtualFilePath("/src/a.vue.0.cjs", roots))
31+
assert.Assert(t, isContentMapperVirtualFilePath("/src/a.vue.0.json", roots))
32+
assert.Assert(t, isContentMapperVirtualFilePath("/src/b.ts.0.ts", roots))
33+
assert.Assert(t, isContentMapperVirtualFilePath("/src/a.vue.0.1.ts", roots))
34+
assert.Assert(t, isContentMapperVirtualFilePath("/src/a.vue.+1.ts", roots))
35+
assert.Assert(t, isContentMapperVirtualFilePath("/src/a.vue.-1.ts", roots))
36+
assert.Assert(t, isContentMapperVirtualFilePath("/src/a.vue.001.ts", roots))
37+
assert.Assert(t, !isContentMapperVirtualFilePath("/src/a.vue.ts", roots))
38+
assert.Assert(t, !isContentMapperVirtualFilePath("/src/a.vue.0.txt", roots))
39+
assert.Assert(t, !isContentMapperVirtualFilePath("/src/a.vue.0.d.ts", roots))
40+
assert.Assert(t, !isContentMapperVirtualFilePath("/src/a.vue.0.d.mts", roots))
41+
assert.Assert(t, !isContentMapperVirtualFilePath("/src/a.vue.0.d.cts", roots))
42+
assert.Assert(t, !isContentMapperVirtualFilePath("/src/a.vue.0", roots))
43+
assert.Assert(t, !isContentMapperVirtualFilePath("/src/a.vue..ts", roots))
44+
assert.Assert(t, !isContentMapperVirtualFilePath("/src/a.vue.x.ts", roots))
45+
assert.Assert(t, !isContentMapperVirtualFilePath("/src/a.vue.99999999999999999999.ts", roots))
46+
assert.Assert(t, !isContentMapperVirtualFilePath("/src/a.vue.1.0.ts", roots))
47+
assert.Assert(t, !isContentMapperVirtualFilePath("/src/b.vue.0.ts", roots))
48+
assert.Assert(t, !isContentMapperVirtualFilePath("/src/a.vuex.0.ts", roots))
49+
assert.Assert(t, !isContentMapperVirtualFilePath("/src/a.ts", roots))
50+
assert.Assert(t, !isContentMapperVirtualFilePath("/src/.0.ts", roots))
51+
assert.Assert(t, !isContentMapperVirtualFilePath("/src/..0.ts", roots))
52+
}
53+
54+
func TestContentMapperVirtualFilePathsUseOriginalRoots(t *testing.T) {
55+
t.Parallel()
56+
buildInfo := &incremental.BuildInfo{
57+
FileNames: []incremental.BuildInfoPath{"./src/a.vue", "./src/b.vue"},
58+
Root: []*incremental.BuildInfoRoot{{Start: 2}},
59+
ResolvedRoot: []*incremental.BuildInfoResolvedRoot{{Resolved: 2, Root: 1}},
60+
}
61+
roots := buildInfo.GetBuildInfoRootInfoReader("/", tspath.CaseSensitive)
62+
63+
assert.Assert(t, isContentMapperVirtualFilePath("/src/a.vue.0.ts", roots))
64+
assert.Assert(t, !isContentMapperVirtualFilePath("/src/b.vue.0.ts", roots))
65+
}
66+
67+
func BenchmarkContentMapperVirtualFilePaths(b *testing.B) {
68+
for _, count := range []int{100, 1000, 10000} {
69+
buildInfo := &incremental.BuildInfo{}
70+
for i := range count {
71+
buildInfo.Root = append(buildInfo.Root, &incremental.BuildInfoRoot{
72+
NonIncremental: incremental.BuildInfoPath("/project/packages/a/src/components/a" + strconv.Itoa(i) + ".vue"),
73+
})
74+
}
75+
roots := buildInfo.GetBuildInfoRootInfoReader("/", tspath.CaseSensitive)
76+
b.Run("roots="+strconv.Itoa(count), func(b *testing.B) {
77+
b.Run("declarationFile", func(b *testing.B) {
78+
b.ReportAllocs()
79+
for b.Loop() {
80+
isContentMapperVirtualFilePath("/project/node_modules/a/b.d.ts", roots)
81+
}
82+
})
83+
b.Run("sourceFile", func(b *testing.B) {
84+
b.ReportAllocs()
85+
for b.Loop() {
86+
isContentMapperVirtualFilePath("/project/node_modules/a/b.js", roots)
87+
}
88+
})
89+
b.Run("virtualFile", func(b *testing.B) {
90+
path := tspath.PathKeyFromCanonical(buildInfo.Root[count-1].NonIncremental.AsString() + ".0.ts")
91+
b.ReportAllocs()
92+
for b.Loop() {
93+
isContentMapperVirtualFilePath(path, roots)
94+
}
95+
})
96+
})
97+
}
2098
}

‎tsc/internal/execute/incremental/buildInfo.go‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,10 @@ func (b *BuildInfoRootInfoReader) Roots() iter.Seq[tspath.PathKey] {
687687
return b.rootToResolved.Keys()
688688
}
689689

690+
func (b *BuildInfoRootInfoReader) HasRoot(path tspath.PathKey) bool {
691+
return b.rootToResolved.Has(path)
692+
}
693+
690694
func (b *BuildInfoRootInfoReader) RootFileName(path tspath.PathKey) tspath.RootedFilePath {
691695
fileName, ok := b.rootFileNames[path]
692696
if !ok {

0 commit comments

Comments
 (0)