Skip to content
Draft
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
6 changes: 6 additions & 0 deletions internal/compiler/program.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/microsoft/typescript-go/internal/module"
"github.com/microsoft/typescript-go/internal/modulespecifiers"
"github.com/microsoft/typescript-go/internal/outputpaths"
"github.com/microsoft/typescript-go/internal/packagejson"
"github.com/microsoft/typescript-go/internal/parser"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/scanner"
Expand Down Expand Up @@ -101,6 +102,11 @@ func (p *Program) GetPackageJsonInfo(pkgJsonPath string) modulespecifiers.Packag
return nil
}

// Iterates on all package json cache entries
func (p *Program) PackageJsonCacheEntries(f func(key tspath.Path, value *packagejson.InfoCacheEntry) bool) {
p.resolver.PackageJsonCacheEntries(f)
}

// GetRedirectTargets implements checker.Program.
func (p *Program) GetRedirectTargets(path tspath.Path) []string {
return nil // !!! TODO: project references support
Expand Down
49 changes: 31 additions & 18 deletions internal/execute/build/buildtask.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type buildTask struct {
configTime time.Time
extendedConfigTimes []time.Time
inputFiles []time.Time
packageJsonTimes []time.Time

buildInfoEntry *buildInfoEntry
buildInfoEntryMu sync.Mutex
Expand Down Expand Up @@ -346,6 +347,9 @@ func (t *buildTask) getUpToDateStatus(orchestrator *Orchestrator, configPath tsp

// Check the build info
buildInfoPath := t.resolved.GetBuildInfoFileName()
getBuildInfoDirectory := core.Memoize(func() string {
return tspath.GetDirectoryPath(tspath.GetNormalizedAbsolutePath(buildInfoPath, orchestrator.comparePathsOptions.CurrentDirectory))
})
buildInfo, buildInfoTime := t.loadOrStoreBuildInfo(orchestrator, configPath, buildInfoPath)
if buildInfo == nil {
return &upToDateStatus{kind: upToDateStatusTypeOutputMissing, data: buildInfoPath}
Expand Down Expand Up @@ -382,15 +386,17 @@ func (t *buildTask) getUpToDateStatus(orchestrator *Orchestrator, configPath tsp
}

// Some of the emit files like source map or dts etc are not yet done
if buildInfo.IsEmitPending(t.resolved, tspath.GetDirectoryPath(tspath.GetNormalizedAbsolutePath(buildInfoPath, orchestrator.comparePathsOptions.CurrentDirectory))) {
if buildInfo.IsEmitPending(t.resolved, getBuildInfoDirectory()) {
return &upToDateStatus{kind: upToDateStatusTypeOutOfDateOptions, data: buildInfoPath}
}
}
var inputTextUnchanged bool
oldestOutputFileAndTime := fileAndTime{buildInfoPath, buildInfoTime}
var newestInputFileAndTime fileAndTime
var seenRoots collections.Set[tspath.Path]
var buildInfoRootInfoReader *incremental.BuildInfoRootInfoReader
getBuildInfoRootInfoReader := core.Memoize(func() *incremental.BuildInfoRootInfoReader {
return buildInfo.GetBuildInfoRootInfoReader(getBuildInfoDirectory(), orchestrator.comparePathsOptions)
})
for _, inputFile := range t.resolved.FileNames() {
inputTime := orchestrator.host.GetMTime(inputFile)
if inputTime.IsZero() {
Expand All @@ -401,10 +407,7 @@ func (t *buildTask) getUpToDateStatus(orchestrator *Orchestrator, configPath tsp
var version string
var currentVersion string
if buildInfo.IsIncremental() {
if buildInfoRootInfoReader == nil {
buildInfoRootInfoReader = buildInfo.GetBuildInfoRootInfoReader(tspath.GetDirectoryPath(tspath.GetNormalizedAbsolutePath(buildInfoPath, orchestrator.comparePathsOptions.CurrentDirectory)), orchestrator.comparePathsOptions)
}
buildInfoFileInfo, resolvedInputPath := buildInfoRootInfoReader.GetBuildInfoFileInfo(inputPath)
buildInfoFileInfo, resolvedInputPath := getBuildInfoRootInfoReader().GetBuildInfoFileInfo(inputPath)
if fileInfo := buildInfoFileInfo.GetFileInfo(); fileInfo != nil && fileInfo.Version() != "" {
version = fileInfo.Version()
if text, ok := orchestrator.host.FS().ReadFile(string(resolvedInputPath)); ok {
Expand All @@ -426,10 +429,7 @@ func (t *buildTask) getUpToDateStatus(orchestrator *Orchestrator, configPath tsp
seenRoots.Add(inputPath)
}

if buildInfoRootInfoReader == nil {
buildInfoRootInfoReader = buildInfo.GetBuildInfoRootInfoReader(tspath.GetDirectoryPath(tspath.GetNormalizedAbsolutePath(buildInfoPath, orchestrator.comparePathsOptions.CurrentDirectory)), orchestrator.comparePathsOptions)
}
for root := range buildInfoRootInfoReader.Roots() {
for root := range getBuildInfoRootInfoReader().Roots() {
if !seenRoots.Has(root) {
// File was root file when project was built but its not any more
return &upToDateStatus{kind: upToDateStatusTypeOutOfDateRoots, data: &inputOutputName{string(root), buildInfoPath}}
Expand Down Expand Up @@ -512,14 +512,12 @@ func (t *buildTask) getUpToDateStatus(orchestrator *Orchestrator, configPath tsp
}
}

// !!! sheetal TODO : watch??
// // Check package file time
// const packageJsonLookups = state.lastCachedPackageJsonLookups.get(resolvedPath);
// const dependentPackageFileStatus = packageJsonLookups && forEachKey(
// packageJsonLookups,
// path => checkConfigFileUpToDateStatus(state, path, oldestOutputFileTime, oldestOutputFileName),
// );
// if (dependentPackageFileStatus) return dependentPackageFileStatus;
for packageJson := range buildInfo.GetPackageJsons(getBuildInfoDirectory()) {
packageJsonStatus := checkInputFileTime(packageJson)
if packageJsonStatus != nil {
return packageJsonStatus
}
}

return &upToDateStatus{
kind: core.IfElse(
Expand Down Expand Up @@ -729,6 +727,11 @@ func (t *buildTask) updateWatch(orchestrator *Orchestrator, oldCache *collection
}
}
}
if t.buildInfoEntry != nil && t.buildInfoEntry.buildInfo != nil {
for file := range t.buildInfoEntry.buildInfo.GetPackageJsons(tspath.GetDirectoryPath(string(t.buildInfoEntry.path))) {
t.packageJsonTimes = append(t.packageJsonTimes, orchestrator.host.loadOrStoreMTime(file, oldCache, false))
}
}
}

func (t *buildTask) resetStatus() {
Expand Down Expand Up @@ -778,6 +781,16 @@ func (t *buildTask) hasUpdate(orchestrator *Orchestrator, path tspath.Path) upda
}
}
}
if t.buildInfoEntry != nil && t.buildInfoEntry.buildInfo != nil {
index := 0
for file := range t.buildInfoEntry.buildInfo.GetPackageJsons(tspath.GetDirectoryPath(string(t.buildInfoEntry.path))) {
if orchestrator.host.GetMTime(file) != t.packageJsonTimes[index] {
t.resetStatus()
needsUpdate = true
}
index++
}
}
return core.IfElse(needsConfigUpdate, updateKindConfig, core.IfElse(needsUpdate, updateKindUpdate, updateKindNone))
}

Expand Down
11 changes: 11 additions & 0 deletions internal/execute/incremental/buildInfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@ type BuildInfo struct {
Errors bool `json:"errors,omitzero"`
CheckPending bool `json:"checkPending,omitzero"`
Root []*BuildInfoRoot `json:"root,omitzero"`
PackageJsons []string `json:"packageJsons,omitzero"`

// IncrementalProgram info
FileNames []string `json:"fileNames,omitzero"`
Expand Down Expand Up @@ -520,6 +521,16 @@ func (b *BuildInfo) IsEmitPending(resolved *tsoptions.ParsedCommandLine, buildIn
return false
}

func (b *BuildInfo) GetPackageJsons(buildInfoDirectory string) iter.Seq[string] {
return func(yield func(string) bool) {
for _, packageJson := range b.PackageJsons {
if !yield(tspath.GetNormalizedAbsolutePath(packageJson, buildInfoDirectory)) {
return
}
}
}
}

func (b *BuildInfo) GetBuildInfoRootInfoReader(buildInfoDirectory string, comparePathOptions tspath.ComparePathsOptions) *BuildInfoRootInfoReader {
resolvedRootFileInfos := make(map[tspath.Path]*BuildInfoFileInfo, len(b.FileNames))
// Roots of the File
Expand Down
9 changes: 9 additions & 0 deletions internal/execute/incremental/buildinfotosnapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func buildInfoToSnapshot(buildInfo *BuildInfo, config *tsoptions.ParsedCommandLi
to.snapshot.hasErrors = core.IfElse(buildInfo.Errors, core.TSTrue, core.TSFalse)
to.snapshot.hasSemanticErrors = buildInfo.SemanticErrors
to.snapshot.checkPending = buildInfo.CheckPending
to.setPackageJsons()
return &to.snapshot
}

Expand Down Expand Up @@ -169,3 +170,11 @@ func (t *toSnapshot) setAffectedFilesPendingEmit() {
t.snapshot.affectedFilesPendingEmit.Store(t.toFilePath(pendingEmit.FileId), core.IfElse(pendingEmit.EmitKind == 0, ownOptionsEmitKind, pendingEmit.EmitKind))
}
}

func (t *toSnapshot) setPackageJsons() {
if t.buildInfo.PackageJsons != nil {
t.snapshot.packageJsons = core.Map(t.buildInfo.PackageJsons, t.toAbsolutePath)
} else {
t.snapshot.packageJsons = make([]string, 0)
}
}
14 changes: 10 additions & 4 deletions internal/execute/incremental/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import (
"time"

"github.com/microsoft/typescript-go/internal/compiler"
"github.com/microsoft/typescript-go/internal/vfs"
)

type Host interface {
FS() vfs.FS
GetMTime(fileName string) time.Time
SetMTime(fileName string, mTime time.Time) error
}
Expand All @@ -17,12 +19,16 @@ type host struct {

var _ Host = (*host)(nil)

func (b *host) GetMTime(fileName string) time.Time {
return GetMTime(b.host, fileName)
func (h *host) FS() vfs.FS {
return h.host.FS()
}

func (b *host) SetMTime(fileName string, mTime time.Time) error {
return b.host.FS().Chtimes(fileName, time.Time{}, mTime)
func (h *host) GetMTime(fileName string) time.Time {
return GetMTime(h.host, fileName)
}

func (h *host) SetMTime(fileName string, mTime time.Time) error {
return h.host.FS().Chtimes(fileName, time.Time{}, mTime)
}

func CreateHost(compilerHost compiler.CompilerHost) Host {
Expand Down
45 changes: 35 additions & 10 deletions internal/execute/incremental/program.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/diagnostics"
"github.com/microsoft/typescript-go/internal/outputpaths"
"github.com/microsoft/typescript-go/internal/packagejson"
"github.com/microsoft/typescript-go/internal/tspath"
)

Expand Down Expand Up @@ -260,11 +261,17 @@ func (p *Program) emitBuildInfo(ctx context.Context, options compiler.EmitOption
return nil
}
if p.snapshot.hasErrors == core.TSUnknown {
p.ensureHasErrorsForState(ctx, p.program)
p.ensureHasErrorsForState(ctx)
if p.snapshot.hasErrors != p.snapshot.hasErrorsFromOldState || p.snapshot.hasSemanticErrors != p.snapshot.hasSemanticErrorsFromOldState {
p.snapshot.buildInfoEmitPending.Store(true)
}
}
if p.snapshot.packageJsons == nil {
p.ensurePackageJsonsForState()
if !slices.Equal(p.snapshot.packageJsons, p.snapshot.packageJsonsFromOldState) {
p.snapshot.buildInfoEmitPending.Store(true)
}
}
if !p.snapshot.buildInfoEmitPending.Load() {
return nil
}
Expand Down Expand Up @@ -298,9 +305,9 @@ func (p *Program) emitBuildInfo(ctx context.Context, options compiler.EmitOption
}
}

func (p *Program) ensureHasErrorsForState(ctx context.Context, program *compiler.Program) {
func (p *Program) ensureHasErrorsForState(ctx context.Context) {
var hasIncludeProcessingDiagnostics bool
if slices.ContainsFunc(program.GetSourceFiles(), func(file *ast.SourceFile) bool {
if slices.ContainsFunc(p.program.GetSourceFiles(), func(file *ast.SourceFile) bool {
if _, ok := p.snapshot.emitDiagnosticsPerFile.Load(file.Path()); ok {
// emit diagnostics will be encoded in buildInfo;
return true
Expand All @@ -317,12 +324,12 @@ func (p *Program) ensureHasErrorsForState(ctx context.Context, program *compiler
return
}
if hasIncludeProcessingDiagnostics ||
len(program.GetConfigFileParsingDiagnostics()) > 0 ||
len(program.GetSyntacticDiagnostics(ctx, nil)) > 0 ||
len(program.GetProgramDiagnostics()) > 0 ||
len(program.GetBindDiagnostics(ctx, nil)) > 0 ||
len(program.GetOptionsDiagnostics(ctx)) > 0 ||
len(program.GetGlobalDiagnostics(ctx)) > 0 {
len(p.program.GetConfigFileParsingDiagnostics()) > 0 ||
len(p.program.GetSyntacticDiagnostics(ctx, nil)) > 0 ||
len(p.program.GetProgramDiagnostics()) > 0 ||
len(p.program.GetBindDiagnostics(ctx, nil)) > 0 ||
len(p.program.GetOptionsDiagnostics(ctx)) > 0 ||
len(p.program.GetGlobalDiagnostics(ctx)) > 0 {
p.snapshot.hasErrors = core.TSTrue
// Dont need to encode semantic errors state since the syntax and program diagnostics are encoded as present
p.snapshot.hasSemanticErrors = false
Expand All @@ -331,7 +338,7 @@ func (p *Program) ensureHasErrorsForState(ctx context.Context, program *compiler

p.snapshot.hasErrors = core.TSFalse
// Check semantic and emit diagnostics first as we dont need to ask program about it
if slices.ContainsFunc(program.GetSourceFiles(), func(file *ast.SourceFile) bool {
if slices.ContainsFunc(p.program.GetSourceFiles(), func(file *ast.SourceFile) bool {
semanticDiagnostics, ok := p.snapshot.semanticDiagnosticsPerFile.Load(file.Path())
if !ok {
// Missing semantic diagnostics in cache will be encoded in incremental buildInfo
Expand All @@ -348,3 +355,21 @@ func (p *Program) ensureHasErrorsForState(ctx context.Context, program *compiler
p.snapshot.hasSemanticErrors = !p.snapshot.options.IsIncremental()
}
}

func (p *Program) ensurePackageJsonsForState() {
config := tspath.GetDirectoryPath(p.program.CommandLine().ConfigName())
if config != "" {
p.program.PackageJsonCacheEntries(func(key tspath.Path, value *packagejson.InfoCacheEntry) bool {
if value.Exists() {
p.snapshot.packageJsons = append(p.snapshot.packageJsons, p.host.FS().Realpath(tspath.CombinePaths(value.PackageDirectory, "package.json")))
}
return true
})
}
if p.snapshot.packageJsons == nil {
p.snapshot.packageJsons = make([]string, 0)
} else {
slices.Sort(p.snapshot.packageJsons)
p.snapshot.packageJsons = core.Deduplicate(p.snapshot.packageJsons)
}
}
1 change: 1 addition & 0 deletions internal/execute/incremental/programtosnapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func (t *toProgramSnapshot) reuseFromOldProgram() {
t.snapshot.buildInfoEmitPending.Store(t.oldProgram.snapshot.buildInfoEmitPending.Load())
t.snapshot.hasErrorsFromOldState = t.oldProgram.snapshot.hasErrors
t.snapshot.hasSemanticErrorsFromOldState = t.oldProgram.snapshot.hasSemanticErrors
t.snapshot.packageJsonsFromOldState = t.oldProgram.snapshot.packageJsons
} else {
t.snapshot.buildInfoEmitPending.Store(t.snapshot.options.IsIncremental())
}
Expand Down
3 changes: 3 additions & 0 deletions internal/execute/incremental/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ type snapshot struct {
hasSemanticErrors bool
// If semantic diagnostic check is pending
checkPending bool
// Looked up package.json files from
packageJsons []string

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

Expand All @@ -224,6 +226,7 @@ type snapshot struct {
hasErrorsFromOldState core.Tristate
hasSemanticErrorsFromOldState bool
allFilesExcludingDefaultLibraryFileOnce sync.Once
packageJsonsFromOldState []string
// Cache of all files excluding default library file for the current program
allFilesExcludingDefaultLibraryFile []*ast.SourceFile
hasChangedDtsFile bool
Expand Down
7 changes: 7 additions & 0 deletions internal/execute/incremental/snapshottobuildinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func snapshotToBuildInfo(snapshot *snapshot, program *compiler.Program, buildInf
to.buildInfo.Errors = snapshot.hasErrors.IsTrue()
to.buildInfo.SemanticErrors = snapshot.hasSemanticErrors
to.buildInfo.CheckPending = snapshot.checkPending
to.setPackageJsons()
return &to.buildInfo
}

Expand Down Expand Up @@ -350,3 +351,9 @@ func (t *toBuildInfo) setRootOfNonIncrementalProgram() {
}
})
}

func (t *toBuildInfo) setPackageJsons() {
if len(t.snapshot.packageJsons) > 0 {
t.buildInfo.PackageJsons = core.Map(t.snapshot.packageJsons, t.relativeToBuildInfo)
}
}
2 changes: 2 additions & 0 deletions internal/execute/tsctests/readablebuildinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type readableBuildInfo struct {
Errors bool `json:"errors,omitzero"`
CheckPending bool `json:"checkPending,omitzero"`
Root []*readableBuildInfoRoot `json:"root,omitzero"`
PackageJsons []string `json:"packageJsons,omitzero"`

// IncrementalProgram info
FileNames []string `json:"fileNames,omitzero"`
Expand Down Expand Up @@ -214,6 +215,7 @@ func toReadableBuildInfo(buildInfo *incremental.BuildInfo, buildInfoText string)
Options: buildInfo.Options,
LatestChangedDtsFile: buildInfo.LatestChangedDtsFile,
SemanticErrors: buildInfo.SemanticErrors,
PackageJsons: buildInfo.PackageJsons,
Size: len(buildInfoText),
}
readable.setFileInfos()
Expand Down
4 changes: 0 additions & 4 deletions internal/execute/tsctests/tsc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2395,7 +2395,6 @@ func TestTscModuleResolution(t *testing.T) {
},
},
{
// !!! sheetal package.json watches not yet implemented
subScenario: `resolves specifier in output declaration file from referenced project correctly with cts and mts extensions`,
files: FileMap{
`/user/username/projects/myproject/packages/pkg1/package.json`: stringtestutil.Dedent(`
Expand Down Expand Up @@ -2443,7 +2442,6 @@ func TestTscModuleResolution(t *testing.T) {
edit: func(sys *testSys) {
sys.replaceFileText(`/user/username/projects/myproject/packages/pkg1/package.json`, `"module"`, `"commonjs"`)
},
expectedDiff: "Package.json watch pending, so no change detected yet",
},
{
caption: "removes those errors when a package file is changed back",
Expand All @@ -2456,7 +2454,6 @@ func TestTscModuleResolution(t *testing.T) {
edit: func(sys *testSys) {
sys.replaceFileText(`/user/username/projects/myproject/packages/pkg1/package.json`, `"module"`, `"commonjs"`)
},
expectedDiff: "Package.json watch pending, so no change detected yet",
},
{
caption: "removes those errors when a package file is changed to cjs extensions",
Expand Down Expand Up @@ -2512,7 +2509,6 @@ func TestTscModuleResolution(t *testing.T) {
edit: func(sys *testSys) {
sys.replaceFileText(`/user/username/projects/myproject/packages/pkg2/package.json`, `index.js`, `other.js`)
},
expectedDiff: "Package.json watch pending, so no change detected yet",
},
{
caption: "removes those errors when a package file is changed back",
Expand Down
Loading
Loading