Skip to content
Open
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
1 change: 1 addition & 0 deletions tsc/internal/execute/incremental/buildInfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,7 @@ type BuildInfo struct {
AffectedFilesPendingEmit []*BuildInfoFilePendingEmit `json:"affectedFilesPendingEmit,omitzero"`
LatestChangedDtsFile BuildInfoPath `json:"latestChangedDtsFile,omitzero"` // Because this is only output file in the program, we dont need fileId to deduplicate name
EmitSignatures []*BuildInfoEmitSignature `json:"emitSignatures,omitzero"`
LinkedPackagesSignature string `json:"linkedPackagesSignature,omitzero"`
ResolvedRoot []*BuildInfoResolvedRoot `json:"resolvedRoot,omitzero"`

// NonIncrementalProgram info
Expand Down
1 change: 1 addition & 0 deletions tsc/internal/execute/incremental/buildinfotosnapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func buildInfoToSnapshot(buildInfo *BuildInfo, config *tsoptions.ParsedCommandLi
if buildInfo.LatestChangedDtsFile != "" {
to.snapshot.latestChangedDtsFile = to.toAbsoluteFileName(buildInfo.LatestChangedDtsFile)
}
to.snapshot.linkedPackagesSignature = buildInfo.LinkedPackagesSignature
to.snapshot.hasErrors = core.IfElse(buildInfo.Errors, core.TSTrue, core.TSFalse)
to.snapshot.hasSemanticErrors = buildInfo.SemanticErrors
to.snapshot.checkPending = buildInfo.CheckPending
Expand Down
73 changes: 73 additions & 0 deletions tsc/internal/execute/incremental/programtosnapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,20 @@ package incremental

import (
"context"
"fmt"
"slices"
"strings"

"github.com/microsoft/TypeScript/tsc/internal/ast"
"github.com/microsoft/TypeScript/tsc/internal/binder"
"github.com/microsoft/TypeScript/tsc/internal/checker"
"github.com/microsoft/TypeScript/tsc/internal/collections"
"github.com/microsoft/TypeScript/tsc/internal/compiler"
"github.com/microsoft/TypeScript/tsc/internal/core"
"github.com/microsoft/TypeScript/tsc/internal/json"
"github.com/microsoft/TypeScript/tsc/internal/outputpaths"
"github.com/microsoft/TypeScript/tsc/internal/packagejson"
"github.com/microsoft/TypeScript/tsc/internal/symlinks"
"github.com/microsoft/TypeScript/tsc/internal/tsoptions"
"github.com/microsoft/TypeScript/tsc/internal/tspath"
)
Expand All @@ -33,6 +40,7 @@ func programToSnapshot(program *compiler.Program, oldProgram *Program, hashWithT
to.computeProgramFileChanges()
to.handleFileDelete()
to.handleGlobalScopeChange()
to.computeLinkedPackagesSignature()
to.handlePendingEmit()
to.handlePendingCheck()
}
Expand Down Expand Up @@ -211,6 +219,9 @@ func (t *toProgramSnapshot) handlePendingEmit() {
} else {
pendingEmitKind = getPendingEmitKindWithOptions(t.snapshot.options, t.oldProgram.snapshot.options)
}
if t.snapshot.linkedPackagesSignature != t.oldProgram.snapshot.linkedPackagesSignature {
pendingEmitKind |= GetFileEmitKind(t.snapshot.options) & FileEmitKindAllDts
}
if pendingEmitKind != FileEmitKindNone {
// Add all files to affectedFilesPendingEmit since emit changed
for _, file := range t.program.GetSourceFiles() {
Expand All @@ -224,6 +235,68 @@ func (t *toProgramSnapshot) handlePendingEmit() {
}
}

func (t *toProgramSnapshot) computeLinkedPackagesSignature() {
if !t.snapshot.options.GetEmitDeclarations() {
return
}
caseSensitivity := t.program.CaseSensitivity()
var baseDirectory string
if buildInfoFileName := outputpaths.GetBuildInfoFileName(t.snapshot.options, caseSensitivity); buildInfoFileName != "" {
baseDirectory = buildInfoFileName.Directory().AsString()
}
toSignaturePath := func(path tspath.PathKey) string {
if baseDirectory == "" {
return path.AsString()
}
return tspath.GetRelativePathFromDirectory(baseDirectory, path.AsString(), caseSensitivity)
}
var links []string
t.program.GetSymlinkCache().Directories().Range(func(symlinkPath tspath.PathKey, link *symlinks.KnownDirectoryLink) bool {
if link == nil {
return true
}
entry := toSignaturePath(symlinkPath) + " -> " + toSignaturePath(link.RealPath)
if info := t.program.GetPackageJsonInfo(link.Real.ResolveFile("package.json")); info != nil {
entry += " " + getPackageEntryFields(info.GetContents())
}
links = append(links, entry)
return true
})
if len(links) == 0 {
return
}
slices.Sort(links)
t.snapshot.linkedPackagesSignature = t.snapshot.computeHash(strings.Join(links, "\n"))
}

func getPackageEntryFields(contents *packagejson.PackageJson) string {
validValue := func(field packagejson.Expected[string]) *string {
if value, ok := field.GetValue(); ok {
return &value
}
return nil
}
fields, err := json.Marshal(struct {
Type string `json:"type,omitzero"`
Main *string `json:"main,omitzero"`
Types *string `json:"types,omitzero"`
Typings *string `json:"typings,omitzero"`
TypesVersions packagejson.JSONValue `json:"typesVersions,omitzero"`
Exports packagejson.ExportsOrImports `json:"exports,omitzero"`
}{
Type: contents.Type.Value,
Main: validValue(contents.Main),
Types: validValue(contents.Types),
Typings: validValue(contents.Typings),
TypesVersions: contents.TypesVersions,
Exports: contents.Exports,
})
if err != nil {
panic(fmt.Sprintf("Failed to marshal package.json fields: %v", err))
}
return string(fields)
}

func (t *toProgramSnapshot) handlePendingCheck() {
if t.oldProgram != nil &&
t.snapshot.semanticDiagnosticsPerFile.Size() != len(t.program.GetSourceFiles()) &&
Expand Down
5 changes: 3 additions & 2 deletions tsc/internal/execute/incremental/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,8 +330,9 @@ type snapshot struct {
// If semantic diagnostic check is pending
checkPending bool
// Looked up package.json files from
packageJsons []tspath.RootedFilePath
missingPackageJsons []tspath.RootedFilePath
packageJsons []tspath.RootedFilePath
missingPackageJsons []tspath.RootedFilePath
linkedPackagesSignature string

// Additional fields that are not serialized but needed to track state

Expand Down
1 change: 1 addition & 0 deletions tsc/internal/execute/incremental/snapshottobuildinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func snapshotToBuildInfo(snapshot *snapshot, program *compiler.Program, buildInf
if snapshot.latestChangedDtsFile != "" {
buildInfo.LatestChangedDtsFile = to.fileNameRelativeToBuildInfo(snapshot.latestChangedDtsFile)
}
buildInfo.LinkedPackagesSignature = snapshot.linkedPackagesSignature
} else {
to.setRootOfNonIncrementalProgram()
}
Expand Down
24 changes: 13 additions & 11 deletions tsc/internal/execute/tsctests/readablebuildinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type readableBuildInfo struct {
AffectedFilesPendingEmit []*readableBuildInfoFilePendingEmit `json:"affectedFilesPendingEmit,omitzero"`
LatestChangedDtsFile string `json:"latestChangedDtsFile,omitzero"` // Because this is only output file in the program, we dont need fileId to deduplicate name
EmitSignatures []*readableBuildInfoEmitSignature `json:"emitSignatures,omitzero"`
LinkedPackagesSignature string `json:"linkedPackagesSignature,omitzero"`
ResolvedRoot []*readableBuildInfoResolvedRoot `json:"resolvedRoot,omitzero"`
Size int `json:"size,omitzero"` // Size of the build info file

Expand Down Expand Up @@ -217,17 +218,18 @@ func (b *readableBuildInfoResolvedRoot) UnmarshalJSON(data []byte) error {

func toReadableBuildInfo(buildInfo *incremental.BuildInfo, buildInfoText string) string {
readable := readableBuildInfo{
buildInfo: buildInfo,
Version: buildInfo.Version,
Errors: buildInfo.Errors,
CheckPending: buildInfo.CheckPending,
FileNames: buildInfoPathsAsStrings(buildInfo.FileNames),
Options: buildInfo.Options,
LatestChangedDtsFile: buildInfo.LatestChangedDtsFile.AsString(),
SemanticErrors: buildInfo.SemanticErrors,
PackageJsons: buildInfoPathsAsStrings(buildInfo.PackageJsons),
MissingPackageJsons: buildInfoPathsAsStrings(buildInfo.MissingPackageJsons),
Size: len(buildInfoText),
buildInfo: buildInfo,
Version: buildInfo.Version,
Errors: buildInfo.Errors,
CheckPending: buildInfo.CheckPending,
FileNames: buildInfoPathsAsStrings(buildInfo.FileNames),
Options: buildInfo.Options,
LatestChangedDtsFile: buildInfo.LatestChangedDtsFile.AsString(),
LinkedPackagesSignature: buildInfo.LinkedPackagesSignature,
SemanticErrors: buildInfo.SemanticErrors,
PackageJsons: buildInfoPathsAsStrings(buildInfo.PackageJsons),
MissingPackageJsons: buildInfoPathsAsStrings(buildInfo.MissingPackageJsons),
Size: len(buildInfoText),
}
readable.setFileInfos()
readable.setRoot()
Expand Down
4 changes: 4 additions & 0 deletions tsc/internal/execute/tsctests/sys.go
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,10 @@ func (s *TestSys) removeNoError(path string) {
}
}

func (s *TestSys) addSymlink(path string, target string) {
s.mapFs().AddSymlink(strings.TrimPrefix(path, "/"), strings.TrimPrefix(target, "/"))
}

func (s *TestSys) readFileNoError(path string) string {
content, ok := s.fsFromFileMap().ReadFile(tspath.ToRootedFilePath(path, s.GetCurrentDirectory()))
if !ok {
Expand Down
65 changes: 65 additions & 0 deletions tsc/internal/execute/tsctests/tsc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2564,11 +2564,76 @@ func TestTscIncremental(t *testing.T) {
},
}

testCases = append(testCases, linkedPackageDeclarationEmitTestInput(
"updates declaration emit when linked dependency is removed and added",
nil,
))

for _, test := range testCases {
test.run(t, "incremental")
}
}

func linkedPackageDeclarationEmitTestInput(subScenario string, commandLineArgs []string) *tscInput {
writeManifestOfA := func(sys *TestSys, dependencies string) {
sys.writeFileNoError("/home/src/workspaces/project/a/package.json", fmt.Sprintf(`{"name":"a","version":"1.0.0","type":"module","dependencies":{%s}}`, dependencies))
}
return &tscInput{
subScenario: subScenario,
files: FileMap{
"/home/src/workspaces/project/a/package.json": `{"name":"a","version":"1.0.0","type":"module","dependencies":{"b":"1.0.0","c":"1.0.0"}}`,
"/home/src/workspaces/project/a/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"incremental": true,
"declaration": true,
"emitDeclarationOnly": true,
"strict": true,
"module": "nodenext",
"types": [],
"rootDir": "src",
"outDir": "dist"
},
"include": ["src"]
}`),
"/home/src/workspaces/project/a/src/index.ts": stringtestutil.Dedent(`
import { currentStage } from "b";
export const stage = currentStage();
`),
"/home/src/workspaces/project/a/node_modules/b": vfstest.Symlink("/home/src/workspaces/project/b"),
"/home/src/workspaces/project/a/node_modules/c": vfstest.Symlink("/home/src/workspaces/project/c"),
"/home/src/workspaces/project/b/package.json": `{"name":"b","version":"1.0.0","type":"module","types":"./index.d.ts","dependencies":{"c":"1.0.0"}}`,
"/home/src/workspaces/project/b/index.d.ts": `export declare const currentStage: () => import("c").Current;`,
"/home/src/workspaces/project/b/node_modules/c": vfstest.Symlink("/home/src/workspaces/project/c"),
"/home/src/workspaces/project/c/package.json": `{"name":"c","version":"1.0.0","type":"module","types":"./index.d.ts"}`,
"/home/src/workspaces/project/c/index.d.ts": stringtestutil.Dedent(`
export interface Current { readonly stage: string }
export declare const current: Current;
`),
},
cwd: "/home/src/workspaces/project/a",
commandLineArgs: commandLineArgs,
edits: []*tscEdit{
{
caption: "remove c from dependencies and unlink it",
edit: func(sys *TestSys) {
writeManifestOfA(sys, `"b":"1.0.0"`)
sys.removeNoError("/home/src/workspaces/project/a/node_modules/c")
},
},
noChange,
{
caption: "add c to dependencies and link it",
edit: func(sys *TestSys) {
writeManifestOfA(sys, `"b":"1.0.0","c":"1.0.0"`)
sys.addSymlink("/home/src/workspaces/project/a/node_modules/c", "/home/src/workspaces/project/c")
},
},
noChange,
},
}
}

func TestTscLibraryResolution(t *testing.T) {
t.Parallel()
getTscLibraryResolutionFileMap := func(libReplacement bool) FileMap {
Expand Down
Loading