vendor tsgo
This commit is contained in:
393
tools/tsgo/internal/execute/incremental/affectedfileshandler.go
Normal file
393
tools/tsgo/internal/execute/incremental/affectedfileshandler.go
Normal file
@@ -0,0 +1,393 @@
|
||||
package incremental
|
||||
|
||||
import (
|
||||
"context"
|
||||
"maps"
|
||||
"slices"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/checker"
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/compiler"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
)
|
||||
|
||||
type dtsMayChange map[tspath.Path]FileEmitKind
|
||||
|
||||
func (c dtsMayChange) addFileToAffectedFilesPendingEmit(filePath tspath.Path, emitKind FileEmitKind) {
|
||||
c[filePath] = emitKind
|
||||
}
|
||||
|
||||
type updatedSignature struct {
|
||||
mu sync.Mutex
|
||||
signature string
|
||||
kind SignatureUpdateKind
|
||||
}
|
||||
|
||||
type affectedFilesHandler struct {
|
||||
ctx context.Context
|
||||
program *Program
|
||||
hasAllFilesExcludingDefaultLibraryFile atomic.Bool
|
||||
updatedSignatures collections.SyncMap[tspath.Path, *updatedSignature]
|
||||
dtsMayChange []dtsMayChange
|
||||
filesToRemoveDiagnostics collections.SyncSet[tspath.Path]
|
||||
cleanedDiagnosticsOfLibFiles sync.Once
|
||||
seenFileAndReferences collections.SyncMap[tspath.Path, bool]
|
||||
}
|
||||
|
||||
func (h *affectedFilesHandler) getDtsMayChange(affectedFilePath tspath.Path, affectedFileEmitKind FileEmitKind) dtsMayChange {
|
||||
result := dtsMayChange(map[tspath.Path]FileEmitKind{affectedFilePath: affectedFileEmitKind})
|
||||
h.dtsMayChange = append(h.dtsMayChange, result)
|
||||
return result
|
||||
}
|
||||
|
||||
func (h *affectedFilesHandler) isChangedSignature(path tspath.Path) bool {
|
||||
newSignature, _ := h.updatedSignatures.Load(path)
|
||||
// This method is called after updating signatures of that path, so signature is present in updatedSignatures
|
||||
// And is already calculated, so no need to lock and unlock mutex on the entry
|
||||
oldInfo, _ := h.program.snapshot.fileInfos.Load(path)
|
||||
return newSignature.signature != oldInfo.signature
|
||||
}
|
||||
|
||||
func (h *affectedFilesHandler) removeSemanticDiagnosticsOf(path tspath.Path) {
|
||||
h.filesToRemoveDiagnostics.Add(path)
|
||||
}
|
||||
|
||||
func (h *affectedFilesHandler) removeDiagnosticsOfLibraryFiles() {
|
||||
h.cleanedDiagnosticsOfLibFiles.Do(func() {
|
||||
for _, file := range h.program.GetSourceFiles() {
|
||||
if h.program.program.IsSourceFileDefaultLibrary(file.Path()) && !h.program.program.SkipTypeChecking(file, true) {
|
||||
h.removeSemanticDiagnosticsOf(file.Path())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (h *affectedFilesHandler) computeDtsSignature(file *ast.SourceFile) string {
|
||||
var signature string
|
||||
h.program.program.Emit(h.ctx, compiler.EmitOptions{
|
||||
TargetSourceFile: file,
|
||||
EmitOnly: compiler.EmitOnlyForcedDts,
|
||||
WriteFile: func(fileName string, text string, data *compiler.WriteFileData) error {
|
||||
if !tspath.IsDeclarationFileName(fileName) {
|
||||
panic("File extension for signature expected to be dts, got : " + fileName)
|
||||
}
|
||||
signature = h.program.snapshot.computeSignatureWithDiagnostics(file, text, data)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
return signature
|
||||
}
|
||||
|
||||
func (h *affectedFilesHandler) updateShapeSignature(file *ast.SourceFile, useFileVersionAsSignature bool) bool {
|
||||
update := &updatedSignature{}
|
||||
update.mu.Lock()
|
||||
defer update.mu.Unlock()
|
||||
// If we have cached the result for this file, that means hence forth we should assume file shape is uptodate
|
||||
if existing, ok := h.updatedSignatures.LoadOrStore(file.Path(), update); ok {
|
||||
// Ensure calculations for existing ones are complete before using the value
|
||||
existing.mu.Lock()
|
||||
defer existing.mu.Unlock()
|
||||
return false
|
||||
}
|
||||
|
||||
info, _ := h.program.snapshot.fileInfos.Load(file.Path())
|
||||
prevSignature := info.signature
|
||||
if !file.IsDeclarationFile && !useFileVersionAsSignature {
|
||||
update.signature = h.computeDtsSignature(file)
|
||||
}
|
||||
// Default is to use file version as signature
|
||||
if update.signature == "" {
|
||||
update.signature = info.version
|
||||
update.kind = SignatureUpdateKindUsedVersion
|
||||
}
|
||||
return update.signature != prevSignature
|
||||
}
|
||||
|
||||
func (h *affectedFilesHandler) getFilesAffectedBy(path tspath.Path) []*ast.SourceFile {
|
||||
file := h.program.program.GetSourceFileByPath(path)
|
||||
if file == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !h.updateShapeSignature(file, false) {
|
||||
return []*ast.SourceFile{file}
|
||||
}
|
||||
|
||||
if info, _ := h.program.snapshot.fileInfos.Load(file.Path()); info.affectsGlobalScope {
|
||||
h.hasAllFilesExcludingDefaultLibraryFile.Store(true)
|
||||
h.program.snapshot.getAllFilesExcludingDefaultLibraryFile(h.program.program, file)
|
||||
}
|
||||
|
||||
if h.program.snapshot.options.IsolatedModules.IsTrue() {
|
||||
return []*ast.SourceFile{file}
|
||||
}
|
||||
|
||||
// Now we need to if each file in the referencedBy list has a shape change as well.
|
||||
// Because if so, its own referencedBy files need to be saved as well to make the
|
||||
// emitting result consistent with files on disk.
|
||||
seenFileNamesMap := h.forEachFileReferencedBy(
|
||||
file,
|
||||
func(currentFile *ast.SourceFile, currentPath tspath.Path) (queueForFile bool, fastReturn bool) {
|
||||
// If the current file is not nil and has a shape change, we need to queue it for processing
|
||||
if currentFile != nil && h.updateShapeSignature(currentFile, false) {
|
||||
return true, false
|
||||
}
|
||||
return false, false
|
||||
},
|
||||
)
|
||||
// Return array of values that needs emit
|
||||
return core.Filter(slices.Collect(maps.Values(seenFileNamesMap)), func(file *ast.SourceFile) bool {
|
||||
return file != nil
|
||||
})
|
||||
}
|
||||
|
||||
func (h *affectedFilesHandler) forEachFileReferencedBy(file *ast.SourceFile, fn func(currentFile *ast.SourceFile, currentPath tspath.Path) (queueForFile bool, fastReturn bool)) map[tspath.Path]*ast.SourceFile {
|
||||
// Now we need to if each file in the referencedBy list has a shape change as well.
|
||||
// Because if so, its own referencedBy files need to be saved as well to make the
|
||||
// emitting result consistent with files on disk.
|
||||
seenFileNamesMap := map[tspath.Path]*ast.SourceFile{}
|
||||
// Start with the paths this file was referenced by
|
||||
seenFileNamesMap[file.Path()] = file
|
||||
queue := slices.Collect(h.program.snapshot.referencedMap.getReferencedBy(file.Path()))
|
||||
for len(queue) > 0 {
|
||||
currentPath := queue[len(queue)-1]
|
||||
queue = queue[:len(queue)-1]
|
||||
if _, ok := seenFileNamesMap[currentPath]; !ok {
|
||||
currentFile := h.program.program.GetSourceFileByPath(currentPath)
|
||||
seenFileNamesMap[currentPath] = currentFile
|
||||
queueForFile, fastReturn := fn(currentFile, currentPath)
|
||||
if fastReturn {
|
||||
return seenFileNamesMap
|
||||
}
|
||||
if queueForFile {
|
||||
for ref := range h.program.snapshot.referencedMap.getReferencedBy(currentFile.Path()) {
|
||||
queue = append(queue, ref)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return seenFileNamesMap
|
||||
}
|
||||
|
||||
// Handles semantic diagnostics and dts emit for affectedFile and files, that are referencing modules that export entities from affected file
|
||||
// This is because even though js emit doesnt change, dts emit / type used can change resulting in need for dts emit and js change
|
||||
func (h *affectedFilesHandler) handleDtsMayChangeOfAffectedFile(dtsMayChange dtsMayChange, affectedFile *ast.SourceFile) {
|
||||
h.removeSemanticDiagnosticsOf(affectedFile.Path())
|
||||
|
||||
// If affected files is everything except default library, then nothing more to do
|
||||
if h.hasAllFilesExcludingDefaultLibraryFile.Load() {
|
||||
h.removeDiagnosticsOfLibraryFiles()
|
||||
// When a change affects the global scope, all files are considered to be affected without updating their signature
|
||||
// That means when affected file is handled, its signature can be out of date
|
||||
// To avoid this, ensure that we update the signature for any affected file in this scenario.
|
||||
h.updateShapeSignature(affectedFile, false)
|
||||
return
|
||||
}
|
||||
|
||||
if h.program.snapshot.options.AssumeChangesOnlyAffectDirectDependencies.IsTrue() {
|
||||
return
|
||||
}
|
||||
|
||||
// Iterate on referencing modules that export entities from affected file and delete diagnostics and add pending emit
|
||||
// If there was change in signature (dts output) for the changed file,
|
||||
// then only we need to handle pending file emit
|
||||
if !h.program.snapshot.changedFilesSet.Has(affectedFile.Path()) ||
|
||||
!h.isChangedSignature(affectedFile.Path()) {
|
||||
return
|
||||
}
|
||||
|
||||
// At this point affectedFile is actually one of the changed files
|
||||
// that has some change in its .d.ts signature.
|
||||
|
||||
// Since isolated modules dont change js files, files affected by change in signature is itself
|
||||
// But we need to cleanup semantic diagnostics and queue dts emit for affected files
|
||||
if h.program.snapshot.options.IsolatedModules.IsTrue() {
|
||||
h.forEachFileReferencedBy(
|
||||
affectedFile,
|
||||
func(currentFile *ast.SourceFile, currentPath tspath.Path) (queueForFile bool, fastReturn bool) {
|
||||
if h.handleDtsMayChangeOfGlobalScope(dtsMayChange, currentPath /*invalidateJsFiles*/, false) {
|
||||
return false, true
|
||||
}
|
||||
h.handleDtsMayChangeOf(dtsMayChange, currentPath /*invalidateJsFiles*/, false)
|
||||
if h.isChangedSignature(currentPath) {
|
||||
return true, false
|
||||
}
|
||||
return false, false
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
invalidateJsFiles := false
|
||||
var typeChecker *checker.Checker
|
||||
var done func()
|
||||
// If exported const enum, we need to ensure that js files are emitted as well since the const enum value changed
|
||||
if affectedFile.Symbol != nil {
|
||||
for _, exported := range affectedFile.Symbol.Exports {
|
||||
if exported.Flags&ast.SymbolFlagsConstEnum != 0 {
|
||||
invalidateJsFiles = true
|
||||
break
|
||||
}
|
||||
if typeChecker == nil {
|
||||
typeChecker, done = h.program.program.GetTypeCheckerForFileExclusive(h.ctx, affectedFile)
|
||||
}
|
||||
aliased := checker.SkipAlias(exported, typeChecker)
|
||||
if aliased == exported {
|
||||
continue
|
||||
}
|
||||
if (aliased.Flags & ast.SymbolFlagsConstEnum) != 0 {
|
||||
if slices.ContainsFunc(aliased.Declarations, func(d *ast.Node) bool {
|
||||
return ast.GetSourceFileOfNode(d) == affectedFile
|
||||
}) {
|
||||
invalidateJsFiles = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if done != nil {
|
||||
done()
|
||||
}
|
||||
|
||||
// Go through files that reference affected file and handle dts emit and semantic diagnostics for them and their references
|
||||
for fileReferencingChangedFile := range h.program.snapshot.referencedMap.getReferencedBy(affectedFile.Path()) {
|
||||
if h.handleDtsMayChangeOfGlobalScope(dtsMayChange, fileReferencingChangedFile, invalidateJsFiles) {
|
||||
return
|
||||
}
|
||||
// Since references of changed file = affected files - we would have already handled d.ts emit and semantic diagnostics
|
||||
// for those files. Now we need to handle files referencing those affected files to ensure correctness.
|
||||
for fileReferencingAffectedFile := range h.program.snapshot.referencedMap.getReferencedBy(fileReferencingChangedFile) {
|
||||
if h.handleDtsMayChangeOfFileAndReferences(dtsMayChange, fileReferencingAffectedFile, invalidateJsFiles) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *affectedFilesHandler) handleDtsMayChangeOfFileAndReferences(dtsMayChange dtsMayChange, filePath tspath.Path, invalidateJsFiles bool) bool {
|
||||
if existing, loaded := h.seenFileAndReferences.LoadOrStore(filePath, invalidateJsFiles); loaded && (existing || !invalidateJsFiles) {
|
||||
return false
|
||||
} else if loaded && invalidateJsFiles {
|
||||
h.seenFileAndReferences.Store(filePath, true)
|
||||
}
|
||||
|
||||
if h.handleDtsMayChangeOfGlobalScope(dtsMayChange, filePath, invalidateJsFiles) {
|
||||
return true
|
||||
}
|
||||
h.handleDtsMayChangeOf(dtsMayChange, filePath, invalidateJsFiles)
|
||||
|
||||
// Remove the diagnostics of files that import this file and
|
||||
// any files that are referenced by it (directly or indirectly)
|
||||
for referencingFilePath := range h.program.snapshot.referencedMap.getReferencedBy(filePath) {
|
||||
if h.handleDtsMayChangeOfFileAndReferences(dtsMayChange, referencingFilePath, invalidateJsFiles) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *affectedFilesHandler) handleDtsMayChangeOfGlobalScope(dtsMayChange dtsMayChange, filePath tspath.Path, invalidateJsFiles bool) bool {
|
||||
if info, ok := h.program.snapshot.fileInfos.Load(filePath); !ok || !info.affectsGlobalScope {
|
||||
return false
|
||||
}
|
||||
// Every file needs to be handled
|
||||
for _, file := range h.program.snapshot.getAllFilesExcludingDefaultLibraryFile(h.program.program, nil) {
|
||||
h.handleDtsMayChangeOf(dtsMayChange, file.Path(), invalidateJsFiles)
|
||||
}
|
||||
h.removeDiagnosticsOfLibraryFiles()
|
||||
return true
|
||||
}
|
||||
|
||||
// Handle the dts may change, so they need to be added to pending emit if dts emit is enabled,
|
||||
// Also we need to make sure signature is updated for these files
|
||||
func (h *affectedFilesHandler) handleDtsMayChangeOf(dtsMayChange dtsMayChange, path tspath.Path, invalidateJsFiles bool) {
|
||||
if h.program.snapshot.changedFilesSet.Has(path) {
|
||||
return
|
||||
}
|
||||
file := h.program.program.GetSourceFileByPath(path)
|
||||
if file == nil {
|
||||
return
|
||||
}
|
||||
h.removeSemanticDiagnosticsOf(path)
|
||||
// Even though the js emit doesnt change and we are already handling dts emit and semantic diagnostics
|
||||
// we need to update the signature to reflect correctness of the signature(which is output d.ts emit) of this file
|
||||
// This ensures that we dont later during incremental builds considering wrong signature.
|
||||
// Eg where this also is needed to ensure that .tsbuildinfo generated by incremental build should be same as if it was first fresh build
|
||||
// But we avoid expensive full shape computation, as using file version as shape is enough for correctness.
|
||||
h.updateShapeSignature(file, true)
|
||||
// If not dts emit, nothing more to do
|
||||
if invalidateJsFiles {
|
||||
dtsMayChange.addFileToAffectedFilesPendingEmit(path, GetFileEmitKind(h.program.snapshot.options))
|
||||
} else if h.program.snapshot.options.GetEmitDeclarations() {
|
||||
dtsMayChange.addFileToAffectedFilesPendingEmit(path, core.IfElse(h.program.snapshot.options.DeclarationMap.IsTrue(), FileEmitKindAllDts, FileEmitKindDts))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *affectedFilesHandler) updateSnapshot() {
|
||||
if h.ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
h.updatedSignatures.Range(func(filePath tspath.Path, update *updatedSignature) bool {
|
||||
if info, ok := h.program.snapshot.fileInfos.Load(filePath); ok {
|
||||
info.signature = update.signature
|
||||
if h.program.testingData != nil {
|
||||
h.program.testingData.UpdatedSignatureKinds[filePath] = update.kind
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
h.filesToRemoveDiagnostics.Range(func(file tspath.Path) bool {
|
||||
h.program.snapshot.semanticDiagnosticsPerFile.Delete(file)
|
||||
return true
|
||||
})
|
||||
for _, change := range h.dtsMayChange {
|
||||
for filePath, emitKind := range change {
|
||||
h.program.snapshot.addFileToAffectedFilesPendingEmit(filePath, emitKind)
|
||||
}
|
||||
}
|
||||
h.program.snapshot.changedFilesSet = collections.SyncSet[tspath.Path]{}
|
||||
h.program.snapshot.buildInfoEmitPending.Store(true)
|
||||
}
|
||||
|
||||
func collectAllAffectedFiles(ctx context.Context, program *Program) {
|
||||
if program.snapshot.changedFilesSet.Size() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
handler := affectedFilesHandler{ctx: ctx, program: program}
|
||||
wg := core.NewWorkGroup(handler.program.program.SingleThreaded())
|
||||
var result collections.SyncSet[*ast.SourceFile]
|
||||
program.snapshot.changedFilesSet.Range(func(file tspath.Path) bool {
|
||||
wg.Queue(func() {
|
||||
for _, affectedFile := range handler.getFilesAffectedBy(file) {
|
||||
result.Add(affectedFile)
|
||||
}
|
||||
})
|
||||
return true
|
||||
})
|
||||
wg.RunAndWait()
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// For all the affected files, get all the files that would need to change their dts or js files,
|
||||
// update their diagnostics
|
||||
wg = core.NewWorkGroup(program.program.SingleThreaded())
|
||||
emitKind := GetFileEmitKind(program.snapshot.options)
|
||||
result.Range(func(file *ast.SourceFile) bool {
|
||||
// remove the cached semantic diagnostics and handle dts emit and js emit if needed
|
||||
dtsMayChange := handler.getDtsMayChange(file.Path(), emitKind)
|
||||
wg.Queue(func() {
|
||||
handler.handleDtsMayChangeOfAffectedFile(dtsMayChange, file)
|
||||
})
|
||||
return true
|
||||
})
|
||||
wg.RunAndWait()
|
||||
|
||||
// Update the snapshot with the new state
|
||||
handler.updateSnapshot()
|
||||
}
|
||||
630
tools/tsgo/internal/execute/incremental/buildInfo.go
Normal file
630
tools/tsgo/internal/execute/incremental/buildInfo.go
Normal file
@@ -0,0 +1,630 @@
|
||||
package incremental
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"iter"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/diagnostics"
|
||||
"github.com/microsoft/typescript-go/internal/json"
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
)
|
||||
|
||||
type (
|
||||
BuildInfoFileId int
|
||||
BuildInfoFileIdListId int
|
||||
)
|
||||
|
||||
// buildInfoRoot is
|
||||
// - for incremental program buildinfo
|
||||
// - start and end of FileId for consecutive fileIds to be included as root
|
||||
// - start - single fileId that is root
|
||||
//
|
||||
// - for non incremental program buildinfo
|
||||
// - string that is the root file name
|
||||
type BuildInfoRoot struct {
|
||||
Start BuildInfoFileId
|
||||
End BuildInfoFileId
|
||||
NonIncremental string // Root of a non incremental program
|
||||
}
|
||||
|
||||
func (b *BuildInfoRoot) MarshalJSON() ([]byte, error) {
|
||||
if b.Start != 0 {
|
||||
if b.End != 0 {
|
||||
return json.Marshal([2]BuildInfoFileId{b.Start, b.End})
|
||||
} else {
|
||||
return json.Marshal(b.Start)
|
||||
}
|
||||
} else {
|
||||
return json.Marshal(b.NonIncremental)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BuildInfoRoot) UnmarshalJSON(data []byte) error {
|
||||
var startAndEnd *[2]int
|
||||
if err := json.Unmarshal(data, &startAndEnd); err != nil {
|
||||
var start int
|
||||
if err := json.Unmarshal(data, &start); err != nil {
|
||||
var name string
|
||||
if err := json.Unmarshal(data, &name); err != nil {
|
||||
return fmt.Errorf("invalid BuildInfoRoot: %s", data)
|
||||
}
|
||||
*b = BuildInfoRoot{
|
||||
NonIncremental: name,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
*b = BuildInfoRoot{
|
||||
Start: BuildInfoFileId(start),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
*b = BuildInfoRoot{
|
||||
Start: BuildInfoFileId(startAndEnd[0]),
|
||||
End: BuildInfoFileId(startAndEnd[1]),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type buildInfoFileInfoNoSignature struct {
|
||||
Version string `json:"version,omitzero"`
|
||||
NoSignature bool `json:"noSignature,omitzero"`
|
||||
AffectsGlobalScope bool `json:"affectsGlobalScope,omitzero"`
|
||||
ImpliedNodeFormat core.ResolutionMode `json:"impliedNodeFormat,omitzero"`
|
||||
}
|
||||
|
||||
// Signature is
|
||||
// - undefined if FileInfo.version === FileInfo.signature
|
||||
// - string actual signature
|
||||
type buildInfoFileInfoWithSignature struct {
|
||||
Version string `json:"version,omitzero"`
|
||||
Signature string `json:"signature,omitzero"`
|
||||
AffectsGlobalScope bool `json:"affectsGlobalScope,omitzero"`
|
||||
ImpliedNodeFormat core.ResolutionMode `json:"impliedNodeFormat,omitzero"`
|
||||
}
|
||||
|
||||
type BuildInfoFileInfo struct {
|
||||
signature string
|
||||
noSignature *buildInfoFileInfoNoSignature
|
||||
fileInfo *buildInfoFileInfoWithSignature
|
||||
}
|
||||
|
||||
func newBuildInfoFileInfo(fileInfo *FileInfo) *BuildInfoFileInfo {
|
||||
if fileInfo.version == fileInfo.signature {
|
||||
if !fileInfo.affectsGlobalScope && fileInfo.impliedNodeFormat == core.ResolutionModeCommonJS {
|
||||
return &BuildInfoFileInfo{signature: fileInfo.signature}
|
||||
}
|
||||
} else if fileInfo.signature == "" {
|
||||
return &BuildInfoFileInfo{noSignature: &buildInfoFileInfoNoSignature{
|
||||
Version: fileInfo.version,
|
||||
NoSignature: true,
|
||||
AffectsGlobalScope: fileInfo.affectsGlobalScope,
|
||||
ImpliedNodeFormat: fileInfo.impliedNodeFormat,
|
||||
}}
|
||||
}
|
||||
return &BuildInfoFileInfo{fileInfo: &buildInfoFileInfoWithSignature{
|
||||
Version: fileInfo.version,
|
||||
Signature: core.IfElse(fileInfo.signature == fileInfo.version, "", fileInfo.signature),
|
||||
AffectsGlobalScope: fileInfo.affectsGlobalScope,
|
||||
ImpliedNodeFormat: fileInfo.impliedNodeFormat,
|
||||
}}
|
||||
}
|
||||
|
||||
func (b *BuildInfoFileInfo) GetFileInfo() *FileInfo {
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
if b.signature != "" {
|
||||
return &FileInfo{
|
||||
version: b.signature,
|
||||
signature: b.signature,
|
||||
impliedNodeFormat: core.ResolutionModeCommonJS,
|
||||
}
|
||||
}
|
||||
if b.noSignature != nil {
|
||||
return &FileInfo{
|
||||
version: b.noSignature.Version,
|
||||
affectsGlobalScope: b.noSignature.AffectsGlobalScope,
|
||||
impliedNodeFormat: b.noSignature.ImpliedNodeFormat,
|
||||
}
|
||||
}
|
||||
return &FileInfo{
|
||||
version: b.fileInfo.Version,
|
||||
signature: core.IfElse(b.fileInfo.Signature == "", b.fileInfo.Version, b.fileInfo.Signature),
|
||||
affectsGlobalScope: b.fileInfo.AffectsGlobalScope,
|
||||
impliedNodeFormat: b.fileInfo.ImpliedNodeFormat,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BuildInfoFileInfo) HasSignature() bool {
|
||||
return b.signature != ""
|
||||
}
|
||||
|
||||
func (b *BuildInfoFileInfo) MarshalJSON() ([]byte, error) {
|
||||
if b.signature != "" {
|
||||
return json.Marshal(b.signature)
|
||||
}
|
||||
if b.noSignature != nil {
|
||||
return json.Marshal(b.noSignature)
|
||||
}
|
||||
return json.Marshal(b.fileInfo)
|
||||
}
|
||||
|
||||
func (b *BuildInfoFileInfo) UnmarshalJSON(data []byte) error {
|
||||
var vSignature string
|
||||
if err := json.Unmarshal(data, &vSignature); err != nil {
|
||||
var noSignature buildInfoFileInfoNoSignature
|
||||
if err := json.Unmarshal(data, &noSignature); err != nil || !noSignature.NoSignature {
|
||||
var fileInfo buildInfoFileInfoWithSignature
|
||||
if err := json.Unmarshal(data, &fileInfo); err != nil {
|
||||
return fmt.Errorf("invalid BuildInfoFileInfo: %s", data)
|
||||
}
|
||||
*b = BuildInfoFileInfo{fileInfo: &fileInfo}
|
||||
return nil
|
||||
}
|
||||
*b = BuildInfoFileInfo{noSignature: &noSignature}
|
||||
return nil
|
||||
}
|
||||
*b = BuildInfoFileInfo{signature: vSignature}
|
||||
return nil
|
||||
}
|
||||
|
||||
type BuildInfoReferenceMapEntry struct {
|
||||
FileId BuildInfoFileId
|
||||
FileIdListId BuildInfoFileIdListId
|
||||
}
|
||||
|
||||
func (b *BuildInfoReferenceMapEntry) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal([2]int{int(b.FileId), int(b.FileIdListId)})
|
||||
}
|
||||
|
||||
func (b *BuildInfoReferenceMapEntry) UnmarshalJSON(data []byte) error {
|
||||
var v *[2]int
|
||||
if err := json.Unmarshal(data, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
*b = BuildInfoReferenceMapEntry{
|
||||
FileId: BuildInfoFileId(v[0]),
|
||||
FileIdListId: BuildInfoFileIdListId(v[1]),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type BuildInfoDiagnostic struct {
|
||||
// BuildInfoFileId if it is for a File thats other than its stored for
|
||||
File BuildInfoFileId `json:"file,omitzero"`
|
||||
NoFile bool `json:"noFile,omitzero"`
|
||||
Pos int `json:"pos,omitzero"`
|
||||
End int `json:"end,omitzero"`
|
||||
Code int32 `json:"code,omitzero"`
|
||||
Category diagnostics.Category `json:"category,omitzero"`
|
||||
MessageKey diagnostics.Key `json:"messageKey,omitzero"`
|
||||
MessageArgs []string `json:"messageArgs,omitzero"`
|
||||
MessageChain []*BuildInfoDiagnostic `json:"messageChain,omitzero"`
|
||||
RelatedInformation []*BuildInfoDiagnostic `json:"relatedInformation,omitzero"`
|
||||
ReportsUnnecessary bool `json:"reportsUnnecessary,omitzero"`
|
||||
ReportsDeprecated bool `json:"reportsDeprecated,omitzero"`
|
||||
SkippedOnNoEmit bool `json:"skippedOnNoEmit,omitzero"`
|
||||
RepopulateInfo *BuildInfoRepopulateInfo `json:"repopulateInfo,omitzero"`
|
||||
}
|
||||
|
||||
type BuildInfoRepopulateInfo struct {
|
||||
Kind ast.RepopulateDiagnosticKind `json:"kind"`
|
||||
ModuleReference string `json:"moduleReference,omitzero"`
|
||||
Mode core.ResolutionMode `json:"mode,omitzero"`
|
||||
PackageName string `json:"packageName,omitzero"`
|
||||
}
|
||||
|
||||
type BuildInfoDiagnosticsOfFile struct {
|
||||
FileId BuildInfoFileId
|
||||
Diagnostics []*BuildInfoDiagnostic
|
||||
}
|
||||
|
||||
func (b *BuildInfoDiagnosticsOfFile) MarshalJSON() ([]byte, error) {
|
||||
fileIdAndDiagnostics := make([]any, 0, 2)
|
||||
fileIdAndDiagnostics = append(fileIdAndDiagnostics, b.FileId)
|
||||
fileIdAndDiagnostics = append(fileIdAndDiagnostics, b.Diagnostics)
|
||||
return json.Marshal(fileIdAndDiagnostics)
|
||||
}
|
||||
|
||||
func (b *BuildInfoDiagnosticsOfFile) UnmarshalJSON(data []byte) error {
|
||||
var fileIdAndDiagnostics []json.Value
|
||||
if err := json.Unmarshal(data, &fileIdAndDiagnostics); err != nil {
|
||||
return fmt.Errorf("invalid BuildInfoDiagnosticsOfFile: %s", data)
|
||||
}
|
||||
if len(fileIdAndDiagnostics) != 2 {
|
||||
return fmt.Errorf("invalid BuildInfoDiagnosticsOfFile: expected 2 elements, got %d", len(fileIdAndDiagnostics))
|
||||
}
|
||||
var fileId BuildInfoFileId
|
||||
if err := json.Unmarshal(fileIdAndDiagnostics[0], &fileId); err != nil {
|
||||
return fmt.Errorf("invalid fileId in BuildInfoDiagnosticsOfFile: %w", err)
|
||||
}
|
||||
|
||||
var diagnostics []*BuildInfoDiagnostic
|
||||
if err := json.Unmarshal(fileIdAndDiagnostics[1], &diagnostics); err != nil {
|
||||
return fmt.Errorf("invalid diagnostics in BuildInfoDiagnosticsOfFile: %w", err)
|
||||
}
|
||||
*b = BuildInfoDiagnosticsOfFile{
|
||||
FileId: fileId,
|
||||
Diagnostics: diagnostics,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type BuildInfoSemanticDiagnostic struct {
|
||||
FileId BuildInfoFileId // File is not in changedSet and still doesnt have cached diagnostics
|
||||
Diagnostics *BuildInfoDiagnosticsOfFile // Diagnostics for file
|
||||
}
|
||||
|
||||
func (b *BuildInfoSemanticDiagnostic) MarshalJSON() ([]byte, error) {
|
||||
if b.FileId != 0 {
|
||||
return json.Marshal(b.FileId)
|
||||
}
|
||||
return json.Marshal(b.Diagnostics)
|
||||
}
|
||||
|
||||
func (b *BuildInfoSemanticDiagnostic) UnmarshalJSON(data []byte) error {
|
||||
var fileId BuildInfoFileId
|
||||
if err := json.Unmarshal(data, &fileId); err != nil {
|
||||
var diagnostics BuildInfoDiagnosticsOfFile
|
||||
if err := json.Unmarshal(data, &diagnostics); err != nil {
|
||||
return fmt.Errorf("invalid BuildInfoSemanticDiagnostic: %s", data)
|
||||
}
|
||||
*b = BuildInfoSemanticDiagnostic{
|
||||
Diagnostics: &diagnostics,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
*b = BuildInfoSemanticDiagnostic{
|
||||
FileId: fileId,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// fileId if pending emit is same as what compilerOptions suggest
|
||||
// [fileId] if pending emit is only dts file emit
|
||||
// [fileId, emitKind] if any other type emit is pending
|
||||
type BuildInfoFilePendingEmit struct {
|
||||
FileId BuildInfoFileId
|
||||
EmitKind FileEmitKind
|
||||
}
|
||||
|
||||
func (b *BuildInfoFilePendingEmit) MarshalJSON() ([]byte, error) {
|
||||
if b.EmitKind == 0 {
|
||||
return json.Marshal(b.FileId)
|
||||
}
|
||||
if b.EmitKind == FileEmitKindDts {
|
||||
fileListIds := []BuildInfoFileId{b.FileId}
|
||||
return json.Marshal(fileListIds)
|
||||
}
|
||||
fileAndEmitKind := []int{int(b.FileId), int(b.EmitKind)}
|
||||
return json.Marshal(fileAndEmitKind)
|
||||
}
|
||||
|
||||
func (b *BuildInfoFilePendingEmit) UnmarshalJSON(data []byte) error {
|
||||
var fileId BuildInfoFileId
|
||||
if err := json.Unmarshal(data, &fileId); err != nil {
|
||||
var intTuple []int
|
||||
if err := json.Unmarshal(data, &intTuple); err != nil || len(intTuple) == 0 {
|
||||
return fmt.Errorf("invalid BuildInfoFilePendingEmit: %s", data)
|
||||
}
|
||||
switch len(intTuple) {
|
||||
case 1:
|
||||
*b = BuildInfoFilePendingEmit{
|
||||
FileId: BuildInfoFileId(intTuple[0]),
|
||||
EmitKind: FileEmitKindDts,
|
||||
}
|
||||
return nil
|
||||
case 2:
|
||||
*b = BuildInfoFilePendingEmit{
|
||||
FileId: BuildInfoFileId(intTuple[0]),
|
||||
EmitKind: FileEmitKind(intTuple[1]),
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("invalid BuildInfoFilePendingEmit: expected 1 or 2 integers, got %d", len(intTuple))
|
||||
}
|
||||
}
|
||||
*b = BuildInfoFilePendingEmit{
|
||||
FileId: fileId,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// [fileId, signature] if different from file's signature
|
||||
// fileId if file wasnt emitted
|
||||
type BuildInfoEmitSignature struct {
|
||||
FileId BuildInfoFileId
|
||||
Signature string // Signature if it is different from file's Signature
|
||||
DiffersOnlyInDtsMap bool // true if signature is different only in dtsMap value
|
||||
DiffersInOptions bool // true if signature is different in options used to emit file
|
||||
}
|
||||
|
||||
func (b *BuildInfoEmitSignature) noEmitSignature() bool {
|
||||
return b.Signature == "" && !b.DiffersOnlyInDtsMap && !b.DiffersInOptions
|
||||
}
|
||||
|
||||
func (b *BuildInfoEmitSignature) toEmitSignature(path tspath.Path, emitSignatures *collections.SyncMap[tspath.Path, *emitSignature]) *emitSignature {
|
||||
var signature string
|
||||
var signatureWithDifferentOptions []string
|
||||
if b.DiffersOnlyInDtsMap {
|
||||
signatureWithDifferentOptions = make([]string, 0, 1)
|
||||
info, _ := emitSignatures.Load(path)
|
||||
signatureWithDifferentOptions = append(signatureWithDifferentOptions, info.signature)
|
||||
} else if b.DiffersInOptions {
|
||||
signatureWithDifferentOptions = make([]string, 0, 1)
|
||||
signatureWithDifferentOptions = append(signatureWithDifferentOptions, b.Signature)
|
||||
} else {
|
||||
signature = b.Signature
|
||||
}
|
||||
return &emitSignature{
|
||||
signature: signature,
|
||||
signatureWithDifferentOptions: signatureWithDifferentOptions,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BuildInfoEmitSignature) MarshalJSON() ([]byte, error) {
|
||||
if b.noEmitSignature() {
|
||||
return json.Marshal(b.FileId)
|
||||
}
|
||||
fileIdAndSignature := make([]any, 2)
|
||||
fileIdAndSignature[0] = b.FileId
|
||||
var signature any
|
||||
if b.DiffersOnlyInDtsMap {
|
||||
signature = []string{}
|
||||
} else if b.DiffersInOptions {
|
||||
signature = []string{b.Signature}
|
||||
} else {
|
||||
signature = b.Signature
|
||||
}
|
||||
fileIdAndSignature[1] = signature
|
||||
return json.Marshal(fileIdAndSignature)
|
||||
}
|
||||
|
||||
func (b *BuildInfoEmitSignature) UnmarshalJSON(data []byte) error {
|
||||
var fileId BuildInfoFileId
|
||||
if err := json.Unmarshal(data, &fileId); err != nil {
|
||||
var fileIdAndSignature []any
|
||||
if err := json.Unmarshal(data, &fileIdAndSignature); err != nil {
|
||||
return fmt.Errorf("invalid BuildInfoEmitSignature: %s", data)
|
||||
}
|
||||
if len(fileIdAndSignature) != 2 {
|
||||
return fmt.Errorf("invalid BuildInfoEmitSignature: expected 2 elements, got %d", len(fileIdAndSignature))
|
||||
}
|
||||
var fileId BuildInfoFileId
|
||||
if id, ok := fileIdAndSignature[0].(float64); !ok {
|
||||
return fmt.Errorf("invalid fileId in BuildInfoEmitSignature: expected float64, got %T", fileIdAndSignature[0])
|
||||
} else {
|
||||
fileId = BuildInfoFileId(id)
|
||||
}
|
||||
var signature string
|
||||
var differsOnlyInDtsMap, differsInOptions bool
|
||||
if signatureV, ok := fileIdAndSignature[1].(string); !ok {
|
||||
if signatureList, ok := fileIdAndSignature[1].([]any); !ok {
|
||||
return fmt.Errorf("invalid signature in BuildInfoEmitSignature: expected string or []string, got %T", fileIdAndSignature[1])
|
||||
} else {
|
||||
switch len(signatureList) {
|
||||
case 0:
|
||||
differsOnlyInDtsMap = true
|
||||
case 1:
|
||||
if sig, ok := signatureList[0].(string); !ok {
|
||||
return fmt.Errorf("invalid signature in BuildInfoEmitSignature: expected string, got %T", signatureList[0])
|
||||
} else {
|
||||
signature = sig
|
||||
differsInOptions = true
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("invalid signature in BuildInfoEmitSignature: expected string or []string with 0 or 1 element, got %d elements", len(signatureList))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
signature = signatureV
|
||||
}
|
||||
*b = BuildInfoEmitSignature{
|
||||
FileId: fileId,
|
||||
Signature: signature,
|
||||
DiffersOnlyInDtsMap: differsOnlyInDtsMap,
|
||||
DiffersInOptions: differsInOptions,
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
*b = BuildInfoEmitSignature{
|
||||
FileId: fileId,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type BuildInfoResolvedRoot struct {
|
||||
Resolved BuildInfoFileId
|
||||
Root BuildInfoFileId
|
||||
}
|
||||
|
||||
func (b *BuildInfoResolvedRoot) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal([2]BuildInfoFileId{b.Resolved, b.Root})
|
||||
}
|
||||
|
||||
func (b *BuildInfoResolvedRoot) UnmarshalJSON(data []byte) error {
|
||||
var resolvedAndRoot [2]int
|
||||
if err := json.Unmarshal(data, &resolvedAndRoot); err != nil {
|
||||
return fmt.Errorf("invalid BuildInfoResolvedRoot: %s", data)
|
||||
}
|
||||
*b = BuildInfoResolvedRoot{
|
||||
Resolved: BuildInfoFileId(resolvedAndRoot[0]),
|
||||
Root: BuildInfoFileId(resolvedAndRoot[1]),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type BuildInfo struct {
|
||||
Version string `json:"version,omitzero"`
|
||||
|
||||
// Common between incremental and tsc -b buildinfo for non incremental programs
|
||||
Errors bool `json:"errors,omitzero"`
|
||||
CheckPending bool `json:"checkPending,omitzero"`
|
||||
Root []*BuildInfoRoot `json:"root,omitzero"`
|
||||
PackageJsons []string `json:"packageJsons,omitzero"`
|
||||
MissingPackageJsons []string `json:"missingPackageJsons,omitzero"`
|
||||
|
||||
// IncrementalProgram info
|
||||
FileNames []string `json:"fileNames,omitzero"`
|
||||
FileInfos []*BuildInfoFileInfo `json:"fileInfos,omitzero"`
|
||||
FileIdsList [][]BuildInfoFileId `json:"fileIdsList,omitzero"`
|
||||
Options *collections.OrderedMap[string, any] `json:"options,omitzero"`
|
||||
ReferencedMap []*BuildInfoReferenceMapEntry `json:"referencedMap,omitzero"`
|
||||
SemanticDiagnosticsPerFile []*BuildInfoSemanticDiagnostic `json:"semanticDiagnosticsPerFile,omitzero"`
|
||||
EmitDiagnosticsPerFile []*BuildInfoDiagnosticsOfFile `json:"emitDiagnosticsPerFile,omitzero"`
|
||||
ChangeFileSet []BuildInfoFileId `json:"changeFileSet,omitzero"`
|
||||
AffectedFilesPendingEmit []*BuildInfoFilePendingEmit `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 []*BuildInfoEmitSignature `json:"emitSignatures,omitzero"`
|
||||
ResolvedRoot []*BuildInfoResolvedRoot `json:"resolvedRoot,omitzero"`
|
||||
|
||||
// NonIncrementalProgram info
|
||||
SemanticErrors bool `json:"semanticErrors,omitzero"`
|
||||
}
|
||||
|
||||
func (b *BuildInfo) IsValidVersion() bool {
|
||||
return b.Version == core.Version()
|
||||
}
|
||||
|
||||
func (b *BuildInfo) IsIncremental() bool {
|
||||
return b != nil && len(b.FileNames) != 0
|
||||
}
|
||||
|
||||
func IsBuildInfoFileNameDefaultLibrary(fileName string) bool {
|
||||
return !tspath.PathIsRelative(fileName) && !tspath.PathIsAbsolute(fileName)
|
||||
}
|
||||
|
||||
func (b *BuildInfo) fileName(fileId BuildInfoFileId) string {
|
||||
if fileId < 1 || int(fileId) > len(b.FileNames) {
|
||||
return ""
|
||||
}
|
||||
return b.FileNames[fileId-1]
|
||||
}
|
||||
|
||||
func (b *BuildInfo) fileInfo(fileId BuildInfoFileId) *BuildInfoFileInfo {
|
||||
if fileId < 1 || int(fileId) > len(b.FileInfos) {
|
||||
return nil
|
||||
}
|
||||
return b.FileInfos[fileId-1]
|
||||
}
|
||||
|
||||
func (b *BuildInfo) GetCompilerOptions(buildInfoDirectory string) *core.CompilerOptions {
|
||||
options := &core.CompilerOptions{}
|
||||
for option, value := range b.Options.Entries() {
|
||||
if buildInfoDirectory != "" {
|
||||
result, ok := tsoptions.ConvertOptionToAbsolutePath(option, value, tsoptions.CommandLineCompilerOptionsMap, buildInfoDirectory)
|
||||
if ok {
|
||||
tsoptions.ParseCompilerOptions(option, result, options)
|
||||
continue
|
||||
}
|
||||
}
|
||||
tsoptions.ParseCompilerOptions(option, value, options)
|
||||
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func (b *BuildInfo) IsEmitPending(resolved *tsoptions.ParsedCommandLine, buildInfoDirectory string) bool {
|
||||
// Some of the emit files like source map or dts etc are not yet done
|
||||
if !resolved.CompilerOptions().NoEmit.IsTrue() || resolved.CompilerOptions().GetEmitDeclarations() {
|
||||
pendingEmit := getPendingEmitKindWithOptions(resolved.CompilerOptions(), b.GetCompilerOptions(buildInfoDirectory))
|
||||
if resolved.CompilerOptions().NoEmit.IsTrue() {
|
||||
pendingEmit &= FileEmitKindDtsErrors
|
||||
}
|
||||
return pendingEmit != 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *BuildInfo) GetPackageJsons(buildInfoDirectory string) iter.Seq[string] {
|
||||
return getNormalizedPaths(b.PackageJsons, buildInfoDirectory)
|
||||
}
|
||||
|
||||
func (b *BuildInfo) GetMissingPackageJsons(buildInfoDirectory string) iter.Seq[string] {
|
||||
return getNormalizedPaths(b.MissingPackageJsons, buildInfoDirectory)
|
||||
}
|
||||
|
||||
func getNormalizedPaths(paths []string, buildInfoDirectory string) iter.Seq[string] {
|
||||
return func(yield func(string) bool) {
|
||||
for _, path := range paths {
|
||||
if !yield(tspath.GetNormalizedAbsolutePath(path, 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
|
||||
rootToResolved := collections.NewOrderedMapWithSizeHint[tspath.Path, tspath.Path](len(b.FileNames))
|
||||
resolvedToRoot := make(map[tspath.Path]tspath.Path, len(b.ResolvedRoot))
|
||||
toPath := func(fileName string) tspath.Path {
|
||||
return tspath.ToPath(fileName, buildInfoDirectory, comparePathOptions.UseCaseSensitiveFileNames)
|
||||
}
|
||||
|
||||
// Create map from resolvedRoot to Root
|
||||
for _, resolved := range b.ResolvedRoot {
|
||||
resolvedRoot := b.fileName(resolved.Resolved)
|
||||
root := b.fileName(resolved.Root)
|
||||
if resolvedRoot != "" && root != "" {
|
||||
resolvedToRoot[toPath(resolvedRoot)] = toPath(root)
|
||||
}
|
||||
}
|
||||
|
||||
addRoot := func(resolvedRoot string, fileInfo *BuildInfoFileInfo) {
|
||||
if resolvedRoot == "" {
|
||||
return
|
||||
}
|
||||
resolvedRootPath := toPath(resolvedRoot)
|
||||
if rootPath, ok := resolvedToRoot[resolvedRootPath]; ok {
|
||||
rootToResolved.Set(rootPath, resolvedRootPath)
|
||||
} else {
|
||||
rootToResolved.Set(resolvedRootPath, resolvedRootPath)
|
||||
}
|
||||
if fileInfo != nil {
|
||||
resolvedRootFileInfos[resolvedRootPath] = fileInfo
|
||||
}
|
||||
}
|
||||
|
||||
for _, root := range b.Root {
|
||||
if root.NonIncremental != "" {
|
||||
addRoot(root.NonIncremental, nil)
|
||||
} else if root.End == 0 {
|
||||
addRoot(b.fileName(root.Start), b.fileInfo(root.Start))
|
||||
} else {
|
||||
for i := root.Start; i <= root.End; i++ {
|
||||
addRoot(b.fileName(i), b.fileInfo(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &BuildInfoRootInfoReader{
|
||||
resolvedRootFileInfos: resolvedRootFileInfos,
|
||||
rootToResolved: rootToResolved,
|
||||
}
|
||||
}
|
||||
|
||||
type BuildInfoRootInfoReader struct {
|
||||
resolvedRootFileInfos map[tspath.Path]*BuildInfoFileInfo
|
||||
rootToResolved *collections.OrderedMap[tspath.Path, tspath.Path]
|
||||
}
|
||||
|
||||
func (b *BuildInfoRootInfoReader) GetBuildInfoFileInfo(inputFilePath tspath.Path) (*BuildInfoFileInfo, tspath.Path) {
|
||||
if info, ok := b.resolvedRootFileInfos[inputFilePath]; ok {
|
||||
return info, inputFilePath
|
||||
}
|
||||
if resolved, ok := b.rootToResolved.Get(inputFilePath); ok {
|
||||
return b.resolvedRootFileInfos[resolved], resolved
|
||||
}
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
func (b *BuildInfoRootInfoReader) Roots() iter.Seq[tspath.Path] {
|
||||
return b.rootToResolved.Keys()
|
||||
}
|
||||
198
tools/tsgo/internal/execute/incremental/buildinfotosnapshot.go
Normal file
198
tools/tsgo/internal/execute/incremental/buildinfotosnapshot.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package incremental
|
||||
|
||||
import (
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/compiler"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
)
|
||||
|
||||
func buildInfoToSnapshot(buildInfo *BuildInfo, config *tsoptions.ParsedCommandLine, host compiler.CompilerHost) *snapshot {
|
||||
to := &toSnapshot{
|
||||
buildInfo: buildInfo,
|
||||
buildInfoDirectory: tspath.GetDirectoryPath(tspath.GetNormalizedAbsolutePath(config.GetBuildInfoFileName(), config.GetCurrentDirectory())),
|
||||
filePaths: make([]tspath.Path, 0, len(buildInfo.FileNames)),
|
||||
filePathSet: make([]*collections.Set[tspath.Path], 0, len(buildInfo.FileIdsList)),
|
||||
}
|
||||
to.filePaths = core.Map(buildInfo.FileNames, func(fileName string) tspath.Path {
|
||||
if IsBuildInfoFileNameDefaultLibrary(fileName) {
|
||||
return tspath.ToPath(tspath.CombinePaths(host.DefaultLibraryPath(), fileName), host.GetCurrentDirectory(), host.FS().UseCaseSensitiveFileNames())
|
||||
}
|
||||
return tspath.ToPath(fileName, to.buildInfoDirectory, config.UseCaseSensitiveFileNames())
|
||||
})
|
||||
to.filePathSet = core.Map(buildInfo.FileIdsList, func(fileIdList []BuildInfoFileId) *collections.Set[tspath.Path] {
|
||||
fileSet := collections.NewSetWithSizeHint[tspath.Path](len(fileIdList))
|
||||
for _, fileId := range fileIdList {
|
||||
fileSet.Add(to.toFilePath(fileId))
|
||||
}
|
||||
return fileSet
|
||||
})
|
||||
to.setCompilerOptions()
|
||||
to.setFileInfoAndEmitSignatures()
|
||||
to.setReferencedMap()
|
||||
to.setChangeFileSet()
|
||||
to.setSemanticDiagnostics()
|
||||
to.setEmitDiagnostics()
|
||||
to.setAffectedFilesPendingEmit()
|
||||
if buildInfo.LatestChangedDtsFile != "" {
|
||||
to.snapshot.latestChangedDtsFile = to.toAbsolutePath(buildInfo.LatestChangedDtsFile)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
type toSnapshot struct {
|
||||
buildInfo *BuildInfo
|
||||
buildInfoDirectory string
|
||||
snapshot snapshot
|
||||
filePaths []tspath.Path
|
||||
filePathSet []*collections.Set[tspath.Path]
|
||||
}
|
||||
|
||||
func (t *toSnapshot) toAbsolutePath(path string) string {
|
||||
return tspath.GetNormalizedAbsolutePath(path, t.buildInfoDirectory)
|
||||
}
|
||||
|
||||
func (t *toSnapshot) toFilePath(fileId BuildInfoFileId) tspath.Path {
|
||||
return t.filePaths[fileId-1]
|
||||
}
|
||||
|
||||
func (t *toSnapshot) toFilePathSet(fileIdListId BuildInfoFileIdListId) *collections.Set[tspath.Path] {
|
||||
return t.filePathSet[fileIdListId-1]
|
||||
}
|
||||
|
||||
func (t *toSnapshot) toBuildInfoDiagnosticsWithFileName(diagnostics []*BuildInfoDiagnostic) []*buildInfoDiagnosticWithFileName {
|
||||
return core.Map(diagnostics, func(d *BuildInfoDiagnostic) *buildInfoDiagnosticWithFileName {
|
||||
var file tspath.Path
|
||||
if d.File != 0 {
|
||||
file = t.toFilePath(d.File)
|
||||
}
|
||||
return &buildInfoDiagnosticWithFileName{
|
||||
file: file,
|
||||
noFile: d.NoFile,
|
||||
pos: d.Pos,
|
||||
end: d.End,
|
||||
code: d.Code,
|
||||
category: d.Category,
|
||||
messageKey: d.MessageKey,
|
||||
messageArgs: d.MessageArgs,
|
||||
messageChain: t.toBuildInfoDiagnosticsWithFileName(d.MessageChain),
|
||||
relatedInformation: t.toBuildInfoDiagnosticsWithFileName(d.RelatedInformation),
|
||||
reportsUnnecessary: d.ReportsUnnecessary,
|
||||
reportsDeprecated: d.ReportsDeprecated,
|
||||
skippedOnNoEmit: d.SkippedOnNoEmit,
|
||||
repopulateInfo: fromBuildInfoRepopulateInfo(d.RepopulateInfo),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (t *toSnapshot) toDiagnosticsOrBuildInfoDiagnosticsWithFileName(dig *BuildInfoDiagnosticsOfFile) *DiagnosticsOrBuildInfoDiagnosticsWithFileName {
|
||||
return &DiagnosticsOrBuildInfoDiagnosticsWithFileName{
|
||||
buildInfoDiagnostics: t.toBuildInfoDiagnosticsWithFileName(dig.Diagnostics),
|
||||
}
|
||||
}
|
||||
|
||||
func fromBuildInfoRepopulateInfo(info *BuildInfoRepopulateInfo) *ast.RepopulateDiagnosticInfo {
|
||||
if info == nil {
|
||||
return nil
|
||||
}
|
||||
return &ast.RepopulateDiagnosticInfo{
|
||||
Kind: info.Kind,
|
||||
ModuleReference: info.ModuleReference,
|
||||
Mode: info.Mode,
|
||||
PackageName: info.PackageName,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *toSnapshot) setCompilerOptions() {
|
||||
t.snapshot.options = t.buildInfo.GetCompilerOptions(t.buildInfoDirectory)
|
||||
}
|
||||
|
||||
func (t *toSnapshot) setFileInfoAndEmitSignatures() {
|
||||
isComposite := t.snapshot.options.Composite.IsTrue()
|
||||
for index, buildInfoFileInfo := range t.buildInfo.FileInfos {
|
||||
path := t.toFilePath(BuildInfoFileId(index + 1))
|
||||
info := buildInfoFileInfo.GetFileInfo()
|
||||
t.snapshot.fileInfos.Store(path, info)
|
||||
// Add default emit signature as file's signature
|
||||
if info.signature != "" && isComposite {
|
||||
t.snapshot.emitSignatures.Store(path, &emitSignature{signature: info.signature})
|
||||
}
|
||||
}
|
||||
// Fix up emit signatures
|
||||
for _, value := range t.buildInfo.EmitSignatures {
|
||||
if value.noEmitSignature() {
|
||||
t.snapshot.emitSignatures.Delete(t.toFilePath(value.FileId))
|
||||
} else {
|
||||
path := t.toFilePath(value.FileId)
|
||||
t.snapshot.emitSignatures.Store(path, value.toEmitSignature(path, &t.snapshot.emitSignatures))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *toSnapshot) setReferencedMap() {
|
||||
for _, entry := range t.buildInfo.ReferencedMap {
|
||||
t.snapshot.referencedMap.storeReferences(t.toFilePath(entry.FileId), t.toFilePathSet(entry.FileIdListId))
|
||||
}
|
||||
}
|
||||
|
||||
func (t *toSnapshot) setChangeFileSet() {
|
||||
for _, fileId := range t.buildInfo.ChangeFileSet {
|
||||
filePath := t.toFilePath(fileId)
|
||||
t.snapshot.changedFilesSet.Add(filePath)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *toSnapshot) setSemanticDiagnostics() {
|
||||
t.snapshot.fileInfos.Range(func(path tspath.Path, info *FileInfo) bool {
|
||||
// Initialize to have no diagnostics if its not changed file
|
||||
if !t.snapshot.changedFilesSet.Has(path) {
|
||||
t.snapshot.semanticDiagnosticsPerFile.Store(path, &DiagnosticsOrBuildInfoDiagnosticsWithFileName{})
|
||||
}
|
||||
return true
|
||||
})
|
||||
for _, diagnostic := range t.buildInfo.SemanticDiagnosticsPerFile {
|
||||
if diagnostic.FileId != 0 {
|
||||
filePath := t.toFilePath(diagnostic.FileId)
|
||||
t.snapshot.semanticDiagnosticsPerFile.Delete(filePath) // does not have cached diagnostics
|
||||
} else {
|
||||
filePath := t.toFilePath(diagnostic.Diagnostics.FileId)
|
||||
t.snapshot.semanticDiagnosticsPerFile.Store(filePath, t.toDiagnosticsOrBuildInfoDiagnosticsWithFileName(diagnostic.Diagnostics))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *toSnapshot) setEmitDiagnostics() {
|
||||
for _, diagnostic := range t.buildInfo.EmitDiagnosticsPerFile {
|
||||
filePath := t.toFilePath(diagnostic.FileId)
|
||||
t.snapshot.emitDiagnosticsPerFile.Store(filePath, t.toDiagnosticsOrBuildInfoDiagnosticsWithFileName(diagnostic))
|
||||
}
|
||||
}
|
||||
|
||||
func (t *toSnapshot) setAffectedFilesPendingEmit() {
|
||||
if len(t.buildInfo.AffectedFilesPendingEmit) == 0 {
|
||||
return
|
||||
}
|
||||
ownOptionsEmitKind := GetFileEmitKind(t.snapshot.options)
|
||||
for _, pendingEmit := range t.buildInfo.AffectedFilesPendingEmit {
|
||||
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)
|
||||
}
|
||||
if t.buildInfo.MissingPackageJsons != nil {
|
||||
t.snapshot.missingPackageJsons = core.Map(t.buildInfo.MissingPackageJsons, t.toAbsolutePath)
|
||||
} else {
|
||||
t.snapshot.missingPackageJsons = make([]string, 0)
|
||||
}
|
||||
}
|
||||
343
tools/tsgo/internal/execute/incremental/emitfileshandler.go
Normal file
343
tools/tsgo/internal/execute/incremental/emitfileshandler.go
Normal file
@@ -0,0 +1,343 @@
|
||||
package incremental
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/compiler"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
)
|
||||
|
||||
type emitUpdate struct {
|
||||
pendingKind FileEmitKind
|
||||
result *compiler.EmitResult
|
||||
dtsErrorsFromCache bool
|
||||
}
|
||||
|
||||
type emitFilesHandler struct {
|
||||
ctx context.Context
|
||||
program *Program
|
||||
isForDtsErrors bool
|
||||
signatures collections.SyncMap[tspath.Path, string]
|
||||
emitSignatures collections.SyncMap[tspath.Path, *emitSignature]
|
||||
latestChangedDtsFiles collections.SyncMap[tspath.Path, string]
|
||||
deletedPendingKinds collections.Set[tspath.Path]
|
||||
emitUpdates collections.SyncMap[tspath.Path, *emitUpdate]
|
||||
hasEmitDiagnostics atomic.Bool
|
||||
}
|
||||
|
||||
// Determining what all is pending to be emitted based on previous options or previous file emit flags
|
||||
func (h *emitFilesHandler) getPendingEmitKindForEmitOptions(emitKind FileEmitKind, options compiler.EmitOptions) FileEmitKind {
|
||||
pendingKind := getPendingEmitKind(emitKind, 0)
|
||||
if options.EmitOnly == compiler.EmitOnlyDts {
|
||||
pendingKind &= FileEmitKindAllDts
|
||||
}
|
||||
if h.isForDtsErrors {
|
||||
pendingKind &= FileEmitKindDtsErrors
|
||||
}
|
||||
return pendingKind
|
||||
}
|
||||
|
||||
// Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete
|
||||
// The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host
|
||||
// in that order would be used to write the files
|
||||
func (h *emitFilesHandler) emitAllAffectedFiles(options compiler.EmitOptions) *compiler.EmitResult {
|
||||
// Emit all affected files
|
||||
if h.program.snapshot.canUseIncrementalState() {
|
||||
results := h.emitFilesIncremental(options)
|
||||
if h.isForDtsErrors {
|
||||
if options.TargetSourceFile != nil {
|
||||
// Result from cache
|
||||
diagnostics, _ := h.program.snapshot.emitDiagnosticsPerFile.Load(options.TargetSourceFile.Path())
|
||||
result := &compiler.EmitResult{
|
||||
EmitSkipped: true,
|
||||
Diagnostics: diagnostics.getDiagnostics(h.program.program, options.TargetSourceFile),
|
||||
}
|
||||
h.updateHasEmitDiagnostics(result)
|
||||
return result
|
||||
}
|
||||
for _, result := range results {
|
||||
h.updateHasEmitDiagnostics(result)
|
||||
}
|
||||
return compiler.CombineEmitResults(results)
|
||||
} else {
|
||||
// Combine results and update buildInfo
|
||||
result := compiler.CombineEmitResults(results)
|
||||
h.updateHasEmitDiagnostics(result)
|
||||
h.emitBuildInfo(options, result)
|
||||
return result
|
||||
}
|
||||
} else if !h.isForDtsErrors {
|
||||
result := h.program.program.Emit(h.ctx, h.getEmitOptions(options))
|
||||
h.updateHasEmitDiagnostics(result)
|
||||
h.updateSnapshot()
|
||||
h.emitBuildInfo(options, result)
|
||||
return result
|
||||
} else {
|
||||
result := &compiler.EmitResult{
|
||||
EmitSkipped: true,
|
||||
Diagnostics: h.program.program.GetDeclarationDiagnostics(h.ctx, options.TargetSourceFile),
|
||||
}
|
||||
if len(result.Diagnostics) != 0 {
|
||||
h.updateHasEmitDiagnostics(result)
|
||||
h.program.snapshot.hasEmitDiagnostics = true
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
func (h *emitFilesHandler) updateHasEmitDiagnostics(result *compiler.EmitResult) {
|
||||
if result != nil && len(result.Diagnostics) != 0 {
|
||||
h.hasEmitDiagnostics.Store(true)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *emitFilesHandler) emitBuildInfo(options compiler.EmitOptions, result *compiler.EmitResult) {
|
||||
buildInfoResult := h.program.emitBuildInfo(h.ctx, options)
|
||||
if buildInfoResult != nil {
|
||||
result.Diagnostics = append(result.Diagnostics, buildInfoResult.Diagnostics...)
|
||||
result.EmittedFiles = append(result.EmittedFiles, buildInfoResult.EmittedFiles...)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *emitFilesHandler) emitFilesIncremental(options compiler.EmitOptions) []*compiler.EmitResult {
|
||||
// Get all affected files
|
||||
collectAllAffectedFiles(h.ctx, h.program)
|
||||
if h.ctx.Err() != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
wg := core.NewWorkGroup(h.program.program.SingleThreaded())
|
||||
h.program.snapshot.affectedFilesPendingEmit.Range(func(path tspath.Path, emitKind FileEmitKind) bool {
|
||||
affectedFile := h.program.program.GetSourceFileByPath(path)
|
||||
if affectedFile == nil || !h.program.program.SourceFileMayBeEmitted(affectedFile, false) {
|
||||
h.deletedPendingKinds.Add(path)
|
||||
return true
|
||||
}
|
||||
pendingKind := h.getPendingEmitKindForEmitOptions(emitKind, options)
|
||||
if pendingKind != 0 {
|
||||
wg.Queue(func() {
|
||||
// Determine if we can do partial emit
|
||||
var emitOnly compiler.EmitOnly
|
||||
if (pendingKind & FileEmitKindAllJs) != 0 {
|
||||
emitOnly = compiler.EmitOnlyJs
|
||||
}
|
||||
if (pendingKind & FileEmitKindAllDts) != 0 {
|
||||
if emitOnly == compiler.EmitOnlyJs {
|
||||
emitOnly = compiler.EmitAll
|
||||
} else {
|
||||
emitOnly = compiler.EmitOnlyDts
|
||||
}
|
||||
}
|
||||
var result *compiler.EmitResult
|
||||
if !h.isForDtsErrors {
|
||||
result = h.program.program.Emit(h.ctx, h.getEmitOptions(compiler.EmitOptions{
|
||||
TargetSourceFile: affectedFile,
|
||||
EmitOnly: emitOnly,
|
||||
WriteFile: options.WriteFile,
|
||||
}))
|
||||
} else {
|
||||
result = &compiler.EmitResult{
|
||||
EmitSkipped: true,
|
||||
Diagnostics: h.program.program.GetDeclarationDiagnostics(h.ctx, affectedFile),
|
||||
}
|
||||
}
|
||||
h.updateHasEmitDiagnostics(result)
|
||||
|
||||
// Update the pendingEmit for the file
|
||||
h.emitUpdates.Store(path, &emitUpdate{pendingKind: getPendingEmitKind(emitKind, pendingKind), result: result})
|
||||
})
|
||||
}
|
||||
return true
|
||||
})
|
||||
wg.RunAndWait()
|
||||
if h.ctx.Err() != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get updated errors that were not included in affected files emit
|
||||
h.program.snapshot.emitDiagnosticsPerFile.Range(func(path tspath.Path, diagnostics *DiagnosticsOrBuildInfoDiagnosticsWithFileName) bool {
|
||||
if _, ok := h.emitUpdates.Load(path); !ok {
|
||||
affectedFile := h.program.program.GetSourceFileByPath(path)
|
||||
if affectedFile == nil || !h.program.program.SourceFileMayBeEmitted(affectedFile, false) {
|
||||
h.deletedPendingKinds.Add(path)
|
||||
return true
|
||||
}
|
||||
pendingKind, _ := h.program.snapshot.affectedFilesPendingEmit.Load(path)
|
||||
h.emitUpdates.Store(path, &emitUpdate{
|
||||
pendingKind: pendingKind,
|
||||
result: &compiler.EmitResult{
|
||||
EmitSkipped: true,
|
||||
Diagnostics: diagnostics.getDiagnostics(h.program.program, affectedFile),
|
||||
},
|
||||
dtsErrorsFromCache: true,
|
||||
})
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return h.updateSnapshot()
|
||||
}
|
||||
|
||||
func (h *emitFilesHandler) getEmitOptions(options compiler.EmitOptions) compiler.EmitOptions {
|
||||
if !h.program.snapshot.options.GetEmitDeclarations() {
|
||||
return options
|
||||
}
|
||||
canUseIncrementalState := h.program.snapshot.canUseIncrementalState()
|
||||
return compiler.EmitOptions{
|
||||
TargetSourceFile: options.TargetSourceFile,
|
||||
EmitOnly: options.EmitOnly,
|
||||
WriteFile: func(fileName string, text string, data *compiler.WriteFileData) error {
|
||||
var differsOnlyInMap bool
|
||||
if tspath.IsDeclarationFileName(fileName) {
|
||||
if canUseIncrementalState {
|
||||
var emitSignature string
|
||||
info, _ := h.program.snapshot.fileInfos.Load(options.TargetSourceFile.Path())
|
||||
if info.signature == info.version {
|
||||
signature := h.program.snapshot.computeSignatureWithDiagnostics(options.TargetSourceFile, text, data)
|
||||
// With d.ts diagnostics they are also part of the signature so emitSignature will be different from it since its just hash of d.ts
|
||||
if len(data.Diagnostics) == 0 {
|
||||
emitSignature = signature
|
||||
}
|
||||
if signature != info.version { // Update it
|
||||
h.signatures.Store(options.TargetSourceFile.Path(), signature)
|
||||
}
|
||||
}
|
||||
|
||||
// Store d.ts emit hash so later can be compared to check if d.ts has changed.
|
||||
// Currently we do this only for composite projects since these are the only projects that can be referenced by other projects
|
||||
// and would need their d.ts change time in --build mode
|
||||
if h.skipDtsOutputOfComposite(options.TargetSourceFile, fileName, text, data, emitSignature, &differsOnlyInMap) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var aTime time.Time
|
||||
if differsOnlyInMap {
|
||||
aTime = h.program.host.GetMTime(fileName)
|
||||
}
|
||||
var err error
|
||||
if options.WriteFile != nil {
|
||||
err = options.WriteFile(fileName, text, data)
|
||||
} else {
|
||||
err = h.program.program.Host().FS().WriteFile(fileName, text)
|
||||
}
|
||||
if err == nil && differsOnlyInMap {
|
||||
// Revert the time to original one
|
||||
err = h.program.host.SetMTime(fileName, aTime)
|
||||
}
|
||||
return err
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Compare to existing computed signature and store it or handle the changes in d.ts map option from before
|
||||
// returning undefined means that, we dont need to emit this d.ts file since its contents didnt change
|
||||
func (h *emitFilesHandler) skipDtsOutputOfComposite(file *ast.SourceFile, outputFileName string, text string, data *compiler.WriteFileData, newSignature string, differsOnlyInMap *bool) bool {
|
||||
if !h.program.snapshot.options.Composite.IsTrue() {
|
||||
return false
|
||||
}
|
||||
var oldSignature string
|
||||
oldSignatureFormat, ok := h.program.snapshot.emitSignatures.Load(file.Path())
|
||||
if ok {
|
||||
if oldSignatureFormat.signature != "" {
|
||||
oldSignature = oldSignatureFormat.signature
|
||||
} else {
|
||||
oldSignature = oldSignatureFormat.signatureWithDifferentOptions[0]
|
||||
}
|
||||
}
|
||||
if newSignature == "" {
|
||||
newSignature = h.program.snapshot.computeHash(getTextHandlingSourceMapForSignature(text, data))
|
||||
}
|
||||
// Dont write dts files if they didn't change
|
||||
if newSignature == oldSignature {
|
||||
// If the signature was encoded as string the dts map options match so nothing to do
|
||||
if oldSignatureFormat != nil && oldSignatureFormat.signature == oldSignature {
|
||||
data.SkippedDtsWrite = true
|
||||
return true
|
||||
} else {
|
||||
// Mark as differsOnlyInMap so that we can reverse the timestamp with --build so that
|
||||
// the downstream projects dont detect this as change in d.ts file
|
||||
*differsOnlyInMap = h.program.Options().Build.IsTrue()
|
||||
}
|
||||
} else {
|
||||
h.latestChangedDtsFiles.Store(file.Path(), outputFileName)
|
||||
}
|
||||
h.emitSignatures.Store(file.Path(), &emitSignature{signature: newSignature})
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *emitFilesHandler) updateSnapshot() []*compiler.EmitResult {
|
||||
if h.program.snapshot.canUseIncrementalState() {
|
||||
h.signatures.Range(func(file tspath.Path, signature string) bool {
|
||||
info, _ := h.program.snapshot.fileInfos.Load(file)
|
||||
info.signature = signature
|
||||
if h.program.testingData != nil {
|
||||
h.program.testingData.UpdatedSignatureKinds[file] = SignatureUpdateKindStoredAtEmit
|
||||
}
|
||||
h.program.snapshot.buildInfoEmitPending.Store(true)
|
||||
return true
|
||||
})
|
||||
h.emitSignatures.Range(func(file tspath.Path, signature *emitSignature) bool {
|
||||
h.program.snapshot.emitSignatures.Store(file, signature)
|
||||
h.program.snapshot.buildInfoEmitPending.Store(true)
|
||||
return true
|
||||
})
|
||||
for file := range h.deletedPendingKinds.Keys() {
|
||||
h.program.snapshot.affectedFilesPendingEmit.Delete(file)
|
||||
h.program.snapshot.buildInfoEmitPending.Store(true)
|
||||
}
|
||||
// Always use correct order when to collect the result
|
||||
var results []*compiler.EmitResult
|
||||
for _, file := range h.program.GetSourceFiles() {
|
||||
if latestChangedDtsFile, ok := h.latestChangedDtsFiles.Load(file.Path()); ok {
|
||||
h.program.snapshot.latestChangedDtsFile = latestChangedDtsFile
|
||||
h.program.snapshot.buildInfoEmitPending.Store(true)
|
||||
h.program.snapshot.hasChangedDtsFile = true
|
||||
}
|
||||
if update, ok := h.emitUpdates.Load(file.Path()); ok {
|
||||
if !update.dtsErrorsFromCache {
|
||||
if update.pendingKind == 0 {
|
||||
h.program.snapshot.affectedFilesPendingEmit.Delete(file.Path())
|
||||
} else {
|
||||
h.program.snapshot.affectedFilesPendingEmit.Store(file.Path(), update.pendingKind)
|
||||
}
|
||||
h.program.snapshot.buildInfoEmitPending.Store(true)
|
||||
}
|
||||
if update.result != nil {
|
||||
results = append(results, update.result)
|
||||
if len(update.result.Diagnostics) != 0 {
|
||||
h.program.snapshot.emitDiagnosticsPerFile.Store(file.Path(), &DiagnosticsOrBuildInfoDiagnosticsWithFileName{diagnostics: update.result.Diagnostics})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return results
|
||||
} else if h.hasEmitDiagnostics.Load() {
|
||||
h.program.snapshot.hasEmitDiagnostics = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func emitFiles(ctx context.Context, program *Program, options compiler.EmitOptions, isForDtsErrors bool) *compiler.EmitResult {
|
||||
emitHandler := &emitFilesHandler{ctx: ctx, program: program, isForDtsErrors: isForDtsErrors}
|
||||
|
||||
// Single file emit - do direct from program
|
||||
if !isForDtsErrors && options.TargetSourceFile != nil {
|
||||
result := program.program.Emit(ctx, emitHandler.getEmitOptions(options))
|
||||
emitHandler.updateHasEmitDiagnostics(result)
|
||||
if ctx.Err() != nil {
|
||||
return nil
|
||||
}
|
||||
emitHandler.updateSnapshot()
|
||||
return result
|
||||
}
|
||||
|
||||
// Emit only affected files if using builder for emit
|
||||
return emitHandler.emitAllAffectedFiles(options)
|
||||
}
|
||||
45
tools/tsgo/internal/execute/incremental/host.go
Normal file
45
tools/tsgo/internal/execute/incremental/host.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package incremental
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
type host struct {
|
||||
host compiler.CompilerHost
|
||||
}
|
||||
|
||||
var _ Host = (*host)(nil)
|
||||
|
||||
func (h *host) FS() vfs.FS {
|
||||
return h.host.FS()
|
||||
}
|
||||
|
||||
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 {
|
||||
return &host{host: compilerHost}
|
||||
}
|
||||
|
||||
func GetMTime(host compiler.CompilerHost, fileName string) time.Time {
|
||||
stat := host.FS().Stat(fileName)
|
||||
var mTime time.Time
|
||||
if stat != nil {
|
||||
mTime = stat.ModTime()
|
||||
}
|
||||
return mTime
|
||||
}
|
||||
56
tools/tsgo/internal/execute/incremental/incremental.go
Normal file
56
tools/tsgo/internal/execute/incremental/incremental.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package incremental
|
||||
|
||||
import (
|
||||
"github.com/microsoft/typescript-go/internal/compiler"
|
||||
"github.com/microsoft/typescript-go/internal/json"
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions"
|
||||
)
|
||||
|
||||
type BuildInfoReader interface {
|
||||
ReadBuildInfo(config *tsoptions.ParsedCommandLine) *BuildInfo
|
||||
}
|
||||
|
||||
var _ BuildInfoReader = (*buildInfoReader)(nil)
|
||||
|
||||
type buildInfoReader struct {
|
||||
host compiler.CompilerHost
|
||||
}
|
||||
|
||||
func (r *buildInfoReader) ReadBuildInfo(config *tsoptions.ParsedCommandLine) *BuildInfo {
|
||||
buildInfoFileName := config.GetBuildInfoFileName()
|
||||
if buildInfoFileName == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read build info file
|
||||
data, ok := r.host.FS().ReadFile(buildInfoFileName)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
var buildInfo BuildInfo
|
||||
err := json.Unmarshal([]byte(data), &buildInfo)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &buildInfo
|
||||
}
|
||||
|
||||
func NewBuildInfoReader(
|
||||
host compiler.CompilerHost,
|
||||
) BuildInfoReader {
|
||||
return &buildInfoReader{host: host}
|
||||
}
|
||||
|
||||
func ReadBuildInfoProgram(config *tsoptions.ParsedCommandLine, reader BuildInfoReader, host compiler.CompilerHost) *Program {
|
||||
// Read buildInfo file
|
||||
buildInfo := reader.ReadBuildInfo(config)
|
||||
if buildInfo == nil || !buildInfo.IsValidVersion() || !buildInfo.IsIncremental() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert to information that can be used to create incremental program
|
||||
incrementalProgram := &Program{
|
||||
snapshot: buildInfoToSnapshot(buildInfo, config, host),
|
||||
}
|
||||
return incrementalProgram
|
||||
}
|
||||
453
tools/tsgo/internal/execute/incremental/program.go
Normal file
453
tools/tsgo/internal/execute/incremental/program.go
Normal file
@@ -0,0 +1,453 @@
|
||||
package incremental
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/compiler"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/diagnostics"
|
||||
"github.com/microsoft/typescript-go/internal/json"
|
||||
"github.com/microsoft/typescript-go/internal/outputpaths"
|
||||
"github.com/microsoft/typescript-go/internal/packagejson"
|
||||
"github.com/microsoft/typescript-go/internal/tracing"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
)
|
||||
|
||||
type SignatureUpdateKind byte
|
||||
|
||||
const (
|
||||
SignatureUpdateKindComputedDts SignatureUpdateKind = iota
|
||||
SignatureUpdateKindStoredAtEmit
|
||||
SignatureUpdateKindUsedVersion
|
||||
)
|
||||
|
||||
type Program struct {
|
||||
snapshot *snapshot
|
||||
program *compiler.Program
|
||||
host Host
|
||||
|
||||
// Testing data
|
||||
testingData *TestingData
|
||||
}
|
||||
|
||||
var _ compiler.ProgramLike = (*Program)(nil)
|
||||
|
||||
func NewProgram(program *compiler.Program, oldProgram *Program, host Host, testing bool) *Program {
|
||||
incrementalProgram := &Program{
|
||||
snapshot: programToSnapshot(program, oldProgram, testing),
|
||||
program: program,
|
||||
host: host,
|
||||
}
|
||||
|
||||
if testing {
|
||||
incrementalProgram.testingData = &TestingData{}
|
||||
incrementalProgram.testingData.SemanticDiagnosticsPerFile = &incrementalProgram.snapshot.semanticDiagnosticsPerFile
|
||||
if oldProgram != nil {
|
||||
incrementalProgram.testingData.OldProgramSemanticDiagnosticsPerFile = &oldProgram.snapshot.semanticDiagnosticsPerFile
|
||||
} else {
|
||||
incrementalProgram.testingData.OldProgramSemanticDiagnosticsPerFile = &collections.SyncMap[tspath.Path, *DiagnosticsOrBuildInfoDiagnosticsWithFileName]{}
|
||||
}
|
||||
incrementalProgram.testingData.UpdatedSignatureKinds = make(map[tspath.Path]SignatureUpdateKind)
|
||||
}
|
||||
return incrementalProgram
|
||||
}
|
||||
|
||||
type TestingData struct {
|
||||
SemanticDiagnosticsPerFile *collections.SyncMap[tspath.Path, *DiagnosticsOrBuildInfoDiagnosticsWithFileName]
|
||||
OldProgramSemanticDiagnosticsPerFile *collections.SyncMap[tspath.Path, *DiagnosticsOrBuildInfoDiagnosticsWithFileName]
|
||||
UpdatedSignatureKinds map[tspath.Path]SignatureUpdateKind
|
||||
}
|
||||
|
||||
func (p *Program) GetTestingData() *TestingData {
|
||||
return p.testingData
|
||||
}
|
||||
|
||||
func (p *Program) panicIfNoProgram(method string) {
|
||||
if p.program == nil {
|
||||
panic(method + ": should not be called without program")
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Program) GetProgram() *compiler.Program {
|
||||
p.panicIfNoProgram("GetProgram")
|
||||
return p.program
|
||||
}
|
||||
|
||||
func (p *Program) HasChangedDtsFile() bool {
|
||||
return p.snapshot.hasChangedDtsFile
|
||||
}
|
||||
|
||||
// Options implements compiler.AnyProgram interface.
|
||||
func (p *Program) Options() *core.CompilerOptions {
|
||||
return p.snapshot.options
|
||||
}
|
||||
|
||||
// CommonSourceDirectory implements compiler.AnyProgram interface.
|
||||
func (p *Program) CommonSourceDirectory() string {
|
||||
p.panicIfNoProgram("CommonSourceDirectory")
|
||||
return p.program.CommonSourceDirectory()
|
||||
}
|
||||
|
||||
// Program implements compiler.AnyProgram interface.
|
||||
func (p *Program) Program() *compiler.Program {
|
||||
p.panicIfNoProgram("Program")
|
||||
return p.program
|
||||
}
|
||||
|
||||
// IsSourceFileDefaultLibrary implements compiler.AnyProgram interface.
|
||||
func (p *Program) IsSourceFileDefaultLibrary(path tspath.Path) bool {
|
||||
p.panicIfNoProgram("IsSourceFileDefaultLibrary")
|
||||
return p.program.IsSourceFileDefaultLibrary(path)
|
||||
}
|
||||
|
||||
// GetSourceFiles implements compiler.AnyProgram interface.
|
||||
func (p *Program) GetSourceFiles() []*ast.SourceFile {
|
||||
p.panicIfNoProgram("GetSourceFiles")
|
||||
return p.program.GetSourceFiles()
|
||||
}
|
||||
|
||||
// GetSourceFile implements compiler.AnyProgram interface.
|
||||
func (p *Program) GetSourceFile(path string) *ast.SourceFile {
|
||||
p.panicIfNoProgram("GetSourceFile")
|
||||
return p.program.GetSourceFile(path)
|
||||
}
|
||||
|
||||
// GetConfigFileParsingDiagnostics implements compiler.AnyProgram interface.
|
||||
func (p *Program) GetConfigFileParsingDiagnostics() []*ast.Diagnostic {
|
||||
p.panicIfNoProgram("GetConfigFileParsingDiagnostics")
|
||||
return p.program.GetConfigFileParsingDiagnostics()
|
||||
}
|
||||
|
||||
// GetSyntacticDiagnostics implements compiler.AnyProgram interface.
|
||||
func (p *Program) GetSyntacticDiagnostics(ctx context.Context, file *ast.SourceFile) []*ast.Diagnostic {
|
||||
p.panicIfNoProgram("GetSyntacticDiagnostics")
|
||||
return p.program.GetSyntacticDiagnostics(ctx, file)
|
||||
}
|
||||
|
||||
// GetBindDiagnostics implements compiler.AnyProgram interface.
|
||||
func (p *Program) GetBindDiagnostics(ctx context.Context, file *ast.SourceFile) []*ast.Diagnostic {
|
||||
p.panicIfNoProgram("GetBindDiagnostics")
|
||||
return p.program.GetBindDiagnostics(ctx, file)
|
||||
}
|
||||
|
||||
func (p *Program) GetProgramDiagnostics() []*ast.Diagnostic {
|
||||
p.panicIfNoProgram("GetProgramDiagnostics")
|
||||
return p.program.GetProgramDiagnostics()
|
||||
}
|
||||
|
||||
func (p *Program) GetGlobalDiagnostics(ctx context.Context) []*ast.Diagnostic {
|
||||
p.panicIfNoProgram("GetGlobalDiagnostics")
|
||||
return p.program.GetGlobalDiagnostics(ctx)
|
||||
}
|
||||
|
||||
// GetSemanticDiagnostics implements compiler.AnyProgram interface.
|
||||
func (p *Program) GetSemanticDiagnostics(ctx context.Context, file *ast.SourceFile) []*ast.Diagnostic {
|
||||
p.panicIfNoProgram("GetSemanticDiagnostics")
|
||||
if p.snapshot.options.NoCheck.IsTrue() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ensure all the diagnsotics are cached
|
||||
p.collectSemanticDiagnosticsOfAffectedFiles(ctx, file)
|
||||
if ctx.Err() != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return result from cache
|
||||
if file != nil {
|
||||
return p.getSemanticDiagnosticsOfFile(file)
|
||||
}
|
||||
|
||||
var diagnostics []*ast.Diagnostic
|
||||
for _, file := range p.program.GetSourceFiles() {
|
||||
diagnostics = append(diagnostics, p.getSemanticDiagnosticsOfFile(file)...)
|
||||
}
|
||||
return diagnostics
|
||||
}
|
||||
|
||||
func (p *Program) getSemanticDiagnosticsOfFile(file *ast.SourceFile) []*ast.Diagnostic {
|
||||
cachedDiagnostics, ok := p.snapshot.semanticDiagnosticsPerFile.Load(file.Path())
|
||||
if !ok {
|
||||
panic("After handling all the affected files, there shouldnt be more changes")
|
||||
}
|
||||
return slices.Concat(
|
||||
compiler.FilterNoEmitSemanticDiagnostics(cachedDiagnostics.getDiagnostics(p.program, file), p.snapshot.options),
|
||||
p.program.GetIncludeProcessorDiagnostics(file),
|
||||
)
|
||||
}
|
||||
|
||||
// GetDeclarationDiagnostics implements compiler.AnyProgram interface.
|
||||
func (p *Program) GetDeclarationDiagnostics(ctx context.Context, file *ast.SourceFile) []*ast.Diagnostic {
|
||||
p.panicIfNoProgram("GetDeclarationDiagnostics")
|
||||
result := emitFiles(ctx, p, compiler.EmitOptions{
|
||||
TargetSourceFile: file,
|
||||
}, true)
|
||||
if result != nil {
|
||||
return result.Diagnostics
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSuggestionDiagnostics implements compiler.AnyProgram interface.
|
||||
func (p *Program) GetSuggestionDiagnostics(ctx context.Context, file *ast.SourceFile) []*ast.Diagnostic {
|
||||
p.panicIfNoProgram("GetSuggestionDiagnostics")
|
||||
return p.program.GetSuggestionDiagnostics(ctx, file) // TODO: incremental suggestion diagnostics (only relevant in editor incremental builder?)
|
||||
}
|
||||
|
||||
// GetModeForUsageLocation implements compiler.AnyProgram interface.
|
||||
func (p *Program) Emit(ctx context.Context, options compiler.EmitOptions) *compiler.EmitResult {
|
||||
p.panicIfNoProgram("Emit")
|
||||
|
||||
var result *compiler.EmitResult
|
||||
if p.snapshot.options.NoEmit.IsTrue() {
|
||||
result = &compiler.EmitResult{EmitSkipped: true}
|
||||
} else {
|
||||
result = compiler.HandleNoEmitOnError(ctx, p, options.TargetSourceFile)
|
||||
if ctx.Err() != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if result != nil {
|
||||
if options.TargetSourceFile != nil {
|
||||
return result
|
||||
}
|
||||
|
||||
// Emit buildInfo and combine result
|
||||
buildInfoResult := p.emitBuildInfo(ctx, options)
|
||||
if buildInfoResult != nil {
|
||||
result.Diagnostics = append(result.Diagnostics, buildInfoResult.Diagnostics...)
|
||||
result.EmittedFiles = append(result.EmittedFiles, buildInfoResult.EmittedFiles...)
|
||||
}
|
||||
return result
|
||||
}
|
||||
return emitFiles(ctx, p, options, false)
|
||||
}
|
||||
|
||||
// Handle affected files and cache the semantic diagnostics for all of them or the file asked for
|
||||
func (p *Program) collectSemanticDiagnosticsOfAffectedFiles(ctx context.Context, file *ast.SourceFile) {
|
||||
if p.snapshot.canUseIncrementalState() {
|
||||
// Get all affected files
|
||||
collectAllAffectedFiles(ctx, p)
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if p.snapshot.semanticDiagnosticsPerFile.Size() == len(p.program.GetSourceFiles()) {
|
||||
// If we have all the files,
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var affectedFiles []*ast.SourceFile
|
||||
if file != nil {
|
||||
_, ok := p.snapshot.semanticDiagnosticsPerFile.Load(file.Path())
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
affectedFiles = []*ast.SourceFile{file}
|
||||
} else {
|
||||
for _, file := range p.program.GetSourceFiles() {
|
||||
if _, ok := p.snapshot.semanticDiagnosticsPerFile.Load(file.Path()); !ok {
|
||||
affectedFiles = append(affectedFiles, file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get their diagnostics and cache them
|
||||
diagnosticsPerFile := p.program.GetSemanticDiagnosticsWithoutNoEmitFiltering(ctx, affectedFiles)
|
||||
// commit changes if no err
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Commit changes to snapshot
|
||||
for file, diagnostics := range diagnosticsPerFile {
|
||||
p.snapshot.semanticDiagnosticsPerFile.Store(file.Path(), &DiagnosticsOrBuildInfoDiagnosticsWithFileName{diagnostics: diagnostics})
|
||||
}
|
||||
if p.snapshot.semanticDiagnosticsPerFile.Size() == len(p.program.GetSourceFiles()) && p.snapshot.checkPending && !p.snapshot.options.NoCheck.IsTrue() {
|
||||
p.snapshot.checkPending = false
|
||||
}
|
||||
p.snapshot.buildInfoEmitPending.Store(true)
|
||||
}
|
||||
|
||||
func (p *Program) emitBuildInfo(ctx context.Context, options compiler.EmitOptions) *compiler.EmitResult {
|
||||
if tr := p.program.Tracing(); tr != nil {
|
||||
defer tr.Push(tracing.PhaseEmit, "emitBuildInfo", nil, true)()
|
||||
}
|
||||
buildInfoFileName := outputpaths.GetBuildInfoFileName(p.snapshot.options, tspath.ComparePathsOptions{
|
||||
CurrentDirectory: p.program.GetCurrentDirectory(),
|
||||
UseCaseSensitiveFileNames: p.program.UseCaseSensitiveFileNames(),
|
||||
})
|
||||
if buildInfoFileName == "" || p.program.IsEmitBlocked(buildInfoFileName) {
|
||||
return nil
|
||||
}
|
||||
if p.snapshot.hasErrors == core.TSUnknown {
|
||||
p.ensureHasErrorsForState(ctx, p.program)
|
||||
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) ||
|
||||
!slices.Equal(p.snapshot.missingPackageJsons, p.snapshot.missingPackageJsonsFromOldState) {
|
||||
p.snapshot.buildInfoEmitPending.Store(true)
|
||||
}
|
||||
}
|
||||
if !p.snapshot.buildInfoEmitPending.Load() {
|
||||
return nil
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
return nil
|
||||
}
|
||||
buildInfo := snapshotToBuildInfo(p.snapshot, p.program, buildInfoFileName)
|
||||
text, err := json.Marshal(buildInfo)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to marshal build info: %v", err))
|
||||
}
|
||||
if options.WriteFile != nil {
|
||||
err = options.WriteFile(buildInfoFileName, string(text), &compiler.WriteFileData{
|
||||
BuildInfo: buildInfo,
|
||||
})
|
||||
} else {
|
||||
err = p.program.Host().FS().WriteFile(buildInfoFileName, string(text))
|
||||
}
|
||||
if err != nil {
|
||||
return &compiler.EmitResult{
|
||||
EmitSkipped: true,
|
||||
Diagnostics: []*ast.Diagnostic{
|
||||
ast.NewCompilerDiagnostic(diagnostics.Could_not_write_file_0_Colon_1, buildInfoFileName, err.Error()),
|
||||
},
|
||||
}
|
||||
}
|
||||
p.snapshot.buildInfoEmitPending.Store(false)
|
||||
return &compiler.EmitResult{
|
||||
EmitSkipped: false,
|
||||
EmittedFiles: []string{buildInfoFileName},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Program) ensureHasErrorsForState(ctx context.Context, program *compiler.Program) {
|
||||
var hasIncludeProcessingDiagnostics func() bool
|
||||
var hasEmitDiagnostics bool
|
||||
if p.snapshot.canUseIncrementalState() {
|
||||
if slices.ContainsFunc(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
|
||||
}
|
||||
if hasIncludeProcessingDiagnostics == nil && len(p.program.GetIncludeProcessorDiagnostics(file)) > 0 {
|
||||
hasIncludeProcessingDiagnostics = func() bool { return true }
|
||||
}
|
||||
return false
|
||||
}) {
|
||||
hasEmitDiagnostics = true
|
||||
}
|
||||
if hasIncludeProcessingDiagnostics == nil {
|
||||
hasIncludeProcessingDiagnostics = func() bool { return false }
|
||||
}
|
||||
} else {
|
||||
hasEmitDiagnostics = p.snapshot.hasEmitDiagnostics
|
||||
hasIncludeProcessingDiagnostics = func() bool {
|
||||
return slices.ContainsFunc(program.GetSourceFiles(), func(file *ast.SourceFile) bool {
|
||||
return len(p.program.GetIncludeProcessorDiagnostics(file)) > 0
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if hasEmitDiagnostics {
|
||||
// Record this for only non incremental build info
|
||||
p.snapshot.hasErrors = core.IfElse(p.snapshot.options.IsIncremental(), core.TSFalse, core.TSTrue)
|
||||
// Dont need to encode semantic errors state since the emit diagnostics are encoded
|
||||
p.snapshot.hasSemanticErrors = false
|
||||
return
|
||||
}
|
||||
|
||||
if hasIncludeProcessingDiagnostics() ||
|
||||
len(program.GetConfigFileParsingDiagnostics()) > 0 ||
|
||||
len(program.GetSyntacticDiagnostics(ctx, nil)) > 0 ||
|
||||
len(program.GetProgramDiagnostics()) > 0 ||
|
||||
len(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
|
||||
return
|
||||
}
|
||||
|
||||
p.snapshot.hasErrors = core.TSFalse
|
||||
// Check semantic and emit diagnostics first as we dont need to ask program about it
|
||||
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
|
||||
return p.snapshot.options.IsIncremental()
|
||||
}
|
||||
if len(semanticDiagnostics.diagnostics) > 0 || len(semanticDiagnostics.buildInfoDiagnostics) > 0 {
|
||||
// cached semantic diagnostics will be encoded in buildInfo
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}) {
|
||||
// Because semantic diagnostics are recorded in buildInfo, we dont need to encode hasErrors in incremental buildInfo
|
||||
// But encode as errors in non incremental buildInfo
|
||||
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 == nil {
|
||||
return true
|
||||
}
|
||||
packageJson := tspath.CombinePaths(value.PackageDirectory, "package.json")
|
||||
if value.Exists() || value.DirectoryExists {
|
||||
packageJson = p.program.Host().FS().Realpath(packageJson)
|
||||
}
|
||||
if value.Exists() {
|
||||
p.snapshot.packageJsons = append(p.snapshot.packageJsons, packageJson)
|
||||
} else if strings.Contains(packageJson, "/node_modules/") {
|
||||
p.snapshot.missingPackageJsons = append(p.snapshot.missingPackageJsons, packageJson)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
p.snapshot.packageJsons = normalizePackageJsons(p.snapshot.packageJsons)
|
||||
p.snapshot.missingPackageJsons = normalizePackageJsons(p.snapshot.missingPackageJsons)
|
||||
}
|
||||
|
||||
func normalizePackageJsons(packageJsons []string) []string {
|
||||
if packageJsons == nil {
|
||||
return make([]string, 0)
|
||||
}
|
||||
slices.Sort(packageJsons)
|
||||
return core.Deduplicate(packageJsons)
|
||||
}
|
||||
|
||||
func (p *Program) PackageJsonLookupPaths() []string {
|
||||
config := tspath.GetDirectoryPath(p.program.CommandLine().ConfigName())
|
||||
if config == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var packageJsons []string
|
||||
p.program.PackageJsonCacheEntries(func(key tspath.Path, value *packagejson.InfoCacheEntry) bool {
|
||||
if value == nil {
|
||||
return true
|
||||
}
|
||||
packageJson := tspath.CombinePaths(value.PackageDirectory, "package.json")
|
||||
if value.Exists() || value.DirectoryExists {
|
||||
packageJson = p.program.Host().FS().Realpath(packageJson)
|
||||
}
|
||||
packageJsons = append(packageJsons, packageJson)
|
||||
return true
|
||||
})
|
||||
slices.Sort(packageJsons)
|
||||
return core.Deduplicate(packageJsons)
|
||||
}
|
||||
403
tools/tsgo/internal/execute/incremental/programtosnapshot.go
Normal file
403
tools/tsgo/internal/execute/incremental/programtosnapshot.go
Normal file
@@ -0,0 +1,403 @@
|
||||
package incremental
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/binder"
|
||||
"github.com/microsoft/typescript-go/internal/checker"
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/compiler"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
)
|
||||
|
||||
func programToSnapshot(program *compiler.Program, oldProgram *Program, hashWithText bool) *snapshot {
|
||||
if oldProgram != nil && oldProgram.program == program {
|
||||
return oldProgram.snapshot
|
||||
}
|
||||
snapshot := &snapshot{
|
||||
options: program.Options(),
|
||||
hashWithText: hashWithText,
|
||||
checkPending: program.Options().NoCheck.IsTrue(),
|
||||
}
|
||||
to := &toProgramSnapshot{
|
||||
program: program,
|
||||
oldProgram: oldProgram,
|
||||
snapshot: snapshot,
|
||||
}
|
||||
|
||||
if to.snapshot.canUseIncrementalState() {
|
||||
to.reuseFromOldProgram()
|
||||
to.computeProgramFileChanges()
|
||||
to.handleFileDelete()
|
||||
to.handlePendingEmit()
|
||||
to.handlePendingCheck()
|
||||
}
|
||||
return snapshot
|
||||
}
|
||||
|
||||
type toProgramSnapshot struct {
|
||||
program *compiler.Program
|
||||
oldProgram *Program
|
||||
snapshot *snapshot
|
||||
globalFileRemoved bool
|
||||
}
|
||||
|
||||
func (t *toProgramSnapshot) reuseFromOldProgram() {
|
||||
if t.oldProgram != nil {
|
||||
if t.snapshot.options.Composite.IsTrue() {
|
||||
t.snapshot.latestChangedDtsFile = t.oldProgram.snapshot.latestChangedDtsFile
|
||||
}
|
||||
// Copy old snapshot's changed files set
|
||||
t.oldProgram.snapshot.changedFilesSet.Range(func(key tspath.Path) bool {
|
||||
t.snapshot.changedFilesSet.Add(key)
|
||||
return true
|
||||
})
|
||||
t.oldProgram.snapshot.affectedFilesPendingEmit.Range(func(key tspath.Path, emitKind FileEmitKind) bool {
|
||||
t.snapshot.affectedFilesPendingEmit.Store(key, emitKind)
|
||||
return true
|
||||
})
|
||||
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
|
||||
t.snapshot.missingPackageJsonsFromOldState = t.oldProgram.snapshot.missingPackageJsons
|
||||
} else {
|
||||
t.snapshot.buildInfoEmitPending.Store(t.snapshot.options.IsIncremental())
|
||||
}
|
||||
}
|
||||
|
||||
func (t *toProgramSnapshot) computeProgramFileChanges() {
|
||||
canCopySemanticDiagnostics := t.oldProgram != nil &&
|
||||
!tsoptions.CompilerOptionsAffectSemanticDiagnostics(t.oldProgram.snapshot.options, t.program.Options())
|
||||
// We can only reuse emit signatures (i.e. .d.ts signatures) if the .d.ts file is unchanged,
|
||||
// which will eg be depedent on change in options like declarationDir and outDir options are unchanged.
|
||||
// We need to look in oldState.compilerOptions, rather than oldCompilerOptions (i.e.we need to disregard useOldState) because
|
||||
// oldCompilerOptions can be undefined if there was change in say module from None to some other option
|
||||
// which would make useOldState as false since we can now use reference maps that are needed to track what to emit, what to check etc
|
||||
// but that option change does not affect d.ts file name so emitSignatures should still be reused.
|
||||
canCopyEmitSignatures := t.snapshot.options.Composite.IsTrue() &&
|
||||
t.oldProgram != nil &&
|
||||
!tsoptions.CompilerOptionsAffectDeclarationPath(t.oldProgram.snapshot.options, t.program.Options())
|
||||
copyDeclarationFileDiagnostics := canCopySemanticDiagnostics &&
|
||||
t.snapshot.options.SkipLibCheck.IsTrue() == t.oldProgram.snapshot.options.SkipLibCheck.IsTrue()
|
||||
copyLibFileDiagnostics := copyDeclarationFileDiagnostics &&
|
||||
t.snapshot.options.SkipDefaultLibCheck.IsTrue() == t.oldProgram.snapshot.options.SkipDefaultLibCheck.IsTrue()
|
||||
|
||||
files := t.program.GetSourceFiles()
|
||||
wg := core.NewWorkGroup(t.program.SingleThreaded())
|
||||
for _, file := range files {
|
||||
wg.Queue(func() {
|
||||
version := t.snapshot.computeHash(file.Text())
|
||||
impliedNodeFormat := t.program.GetSourceFileMetaData(file.Path()).ImpliedNodeFormat
|
||||
affectsGlobalScope := fileAffectsGlobalScope(file)
|
||||
var signature string
|
||||
newReferences := getReferencedFiles(t.program, file)
|
||||
if newReferences != nil {
|
||||
t.snapshot.referencedMap.storeReferences(file.Path(), newReferences)
|
||||
}
|
||||
if t.oldProgram != nil {
|
||||
if oldFileInfo, ok := t.oldProgram.snapshot.fileInfos.Load(file.Path()); ok {
|
||||
signature = oldFileInfo.signature
|
||||
if oldFileInfo.version != version || oldFileInfo.affectsGlobalScope != affectsGlobalScope || oldFileInfo.impliedNodeFormat != impliedNodeFormat {
|
||||
t.snapshot.addFileToChangeSet(file.Path())
|
||||
} else if oldReferences, _ := t.oldProgram.snapshot.referencedMap.getReferences(file.Path()); !newReferences.Equals(oldReferences) {
|
||||
// Referenced files changed
|
||||
t.snapshot.addFileToChangeSet(file.Path())
|
||||
} else if newReferences != nil {
|
||||
for refPath := range newReferences.Keys() {
|
||||
if t.program.GetSourceFileByPath(refPath) == nil {
|
||||
if _, ok := t.oldProgram.snapshot.fileInfos.Load(refPath); ok {
|
||||
// Referenced file was deleted in the new program
|
||||
t.snapshot.addFileToChangeSet(file.Path())
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
t.snapshot.addFileToChangeSet(file.Path())
|
||||
}
|
||||
if !t.snapshot.changedFilesSet.Has(file.Path()) {
|
||||
if emitDiagnostics, ok := t.oldProgram.snapshot.emitDiagnosticsPerFile.Load(file.Path()); ok {
|
||||
t.snapshot.emitDiagnosticsPerFile.Store(file.Path(), repopulateDiagnosticsOfFile(emitDiagnostics, t.program, file))
|
||||
}
|
||||
if canCopySemanticDiagnostics {
|
||||
if (!file.IsDeclarationFile || copyDeclarationFileDiagnostics) &&
|
||||
(!t.program.IsSourceFileDefaultLibrary(file.Path()) || copyLibFileDiagnostics) {
|
||||
// Unchanged file copy diagnostics
|
||||
if diagnostics, ok := t.oldProgram.snapshot.semanticDiagnosticsPerFile.Load(file.Path()); ok {
|
||||
t.snapshot.semanticDiagnosticsPerFile.Store(file.Path(), repopulateDiagnosticsOfFile(diagnostics, t.program, file))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if canCopyEmitSignatures {
|
||||
if oldEmitSignature, ok := t.oldProgram.snapshot.emitSignatures.Load(file.Path()); ok {
|
||||
t.snapshot.emitSignatures.Store(file.Path(), oldEmitSignature.getNewEmitSignature(t.oldProgram.snapshot.options, t.snapshot.options))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
t.snapshot.addFileToAffectedFilesPendingEmit(file.Path(), GetFileEmitKind(t.snapshot.options))
|
||||
signature = version
|
||||
}
|
||||
t.snapshot.fileInfos.Store(file.Path(), &FileInfo{
|
||||
version: version,
|
||||
signature: signature,
|
||||
affectsGlobalScope: affectsGlobalScope,
|
||||
impliedNodeFormat: impliedNodeFormat,
|
||||
})
|
||||
})
|
||||
}
|
||||
wg.RunAndWait()
|
||||
}
|
||||
|
||||
func (t *toProgramSnapshot) handleFileDelete() {
|
||||
if t.oldProgram != nil {
|
||||
// If the global file is removed, add all files as changed
|
||||
t.oldProgram.snapshot.fileInfos.Range(func(filePath tspath.Path, oldInfo *FileInfo) bool {
|
||||
if _, ok := t.snapshot.fileInfos.Load(filePath); !ok {
|
||||
if oldInfo.affectsGlobalScope {
|
||||
for _, file := range t.snapshot.getAllFilesExcludingDefaultLibraryFile(t.program, nil) {
|
||||
t.snapshot.addFileToChangeSet(file.Path())
|
||||
}
|
||||
t.globalFileRemoved = true
|
||||
} else {
|
||||
t.snapshot.buildInfoEmitPending.Store(true)
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (t *toProgramSnapshot) handlePendingEmit() {
|
||||
if t.oldProgram != nil && !t.globalFileRemoved {
|
||||
// If options affect emit, then we need to do complete emit per compiler options
|
||||
// otherwise only the js or dts that needs to emitted because its different from previously emitted options
|
||||
var pendingEmitKind FileEmitKind
|
||||
if tsoptions.CompilerOptionsAffectEmit(t.oldProgram.snapshot.options, t.snapshot.options) {
|
||||
pendingEmitKind = GetFileEmitKind(t.snapshot.options)
|
||||
} else {
|
||||
pendingEmitKind = getPendingEmitKindWithOptions(t.snapshot.options, t.oldProgram.snapshot.options)
|
||||
}
|
||||
if pendingEmitKind != FileEmitKindNone {
|
||||
// Add all files to affectedFilesPendingEmit since emit changed
|
||||
for _, file := range t.program.GetSourceFiles() {
|
||||
// Add to affectedFilesPending emit only if not changed since any changed file will do full emit
|
||||
if !t.snapshot.changedFilesSet.Has(file.Path()) {
|
||||
t.snapshot.addFileToAffectedFilesPendingEmit(file.Path(), pendingEmitKind)
|
||||
}
|
||||
}
|
||||
t.snapshot.buildInfoEmitPending.Store(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *toProgramSnapshot) handlePendingCheck() {
|
||||
if t.oldProgram != nil &&
|
||||
t.snapshot.semanticDiagnosticsPerFile.Size() != len(t.program.GetSourceFiles()) &&
|
||||
t.oldProgram.snapshot.checkPending != t.snapshot.checkPending {
|
||||
t.snapshot.buildInfoEmitPending.Store(true)
|
||||
}
|
||||
}
|
||||
|
||||
func fileAffectsGlobalScope(file *ast.SourceFile) bool {
|
||||
binder.BindSourceFile(file)
|
||||
// if file contains anything that augments to global scope we need to build them as if
|
||||
// they are global files as well as module
|
||||
if core.Some(file.ModuleAugmentations, func(augmentation *ast.ModuleName) bool {
|
||||
return ast.IsGlobalScopeAugmentation(augmentation.Parent)
|
||||
}) {
|
||||
return true
|
||||
}
|
||||
|
||||
if ast.IsExternalOrCommonJSModule(file) || ast.IsJsonSourceFile(file) {
|
||||
return false
|
||||
}
|
||||
|
||||
// For script files that contains only ambient external modules, although they are not actually external module files,
|
||||
// they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore,
|
||||
// there are no point to rebuild all script files if these special files have changed. However, if any statement
|
||||
// in the file is not ambient external module, we treat it as a regular script file.
|
||||
return file.Statements != nil &&
|
||||
file.Statements.Nodes != nil &&
|
||||
core.Some(file.Statements.Nodes, func(stmt *ast.Node) bool {
|
||||
return !ast.IsModuleWithStringLiteralName(stmt)
|
||||
})
|
||||
}
|
||||
|
||||
func addReferencedFilesFromSymbol(file *ast.SourceFile, referencedFiles *collections.Set[tspath.Path], symbol *ast.Symbol) {
|
||||
if symbol == nil {
|
||||
return
|
||||
}
|
||||
for _, declaration := range symbol.Declarations {
|
||||
fileOfDecl := ast.GetSourceFileOfNode(declaration)
|
||||
if fileOfDecl == nil {
|
||||
continue
|
||||
}
|
||||
if file != fileOfDecl {
|
||||
referencedFiles.Add(fileOfDecl.Path())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the module source file and all augmenting files from the import name node from file
|
||||
func addReferencedFilesFromImportLiteral(file *ast.SourceFile, referencedFiles *collections.Set[tspath.Path], checker *checker.Checker, importName *ast.LiteralLikeNode) {
|
||||
symbol := checker.GetSymbolAtLocation(importName)
|
||||
addReferencedFilesFromSymbol(file, referencedFiles, symbol)
|
||||
}
|
||||
|
||||
// Gets the path to reference file from file name, it could be resolvedPath if present otherwise path
|
||||
func addReferencedFileFromFileName(program *compiler.Program, fileName string, referencedFiles *collections.Set[tspath.Path], sourceFileDirectory string) {
|
||||
if redirect := program.GetParseFileRedirect(fileName); redirect != "" {
|
||||
referencedFiles.Add(tspath.ToPath(redirect, program.GetCurrentDirectory(), program.UseCaseSensitiveFileNames()))
|
||||
} else {
|
||||
referencedFiles.Add(tspath.ToPath(fileName, sourceFileDirectory, program.UseCaseSensitiveFileNames()))
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true
|
||||
func getReferencedFiles(program *compiler.Program, file *ast.SourceFile) *collections.Set[tspath.Path] {
|
||||
referencedFiles := collections.Set[tspath.Path]{}
|
||||
|
||||
// We need to use a set here since the code can contain the same import twice,
|
||||
// but that will only be one dependency.
|
||||
// To avoid invernal conversion, the key of the referencedFiles map must be of type Path
|
||||
checker, done := program.GetTypeCheckerForFileExclusive(context.TODO(), file)
|
||||
defer done()
|
||||
for _, importName := range file.Imports() {
|
||||
addReferencedFilesFromImportLiteral(file, &referencedFiles, checker, importName)
|
||||
}
|
||||
|
||||
sourceFileDirectory := tspath.GetDirectoryPath(file.FileName())
|
||||
// Handle triple slash references
|
||||
for _, referencedFile := range file.ReferencedFiles {
|
||||
addReferencedFileFromFileName(program, referencedFile.FileName, &referencedFiles, sourceFileDirectory)
|
||||
}
|
||||
|
||||
// Handle type reference directives
|
||||
if typeRefsInFile, ok := program.GetResolvedTypeReferenceDirectives()[file.Path()]; ok {
|
||||
for _, typeRef := range typeRefsInFile {
|
||||
if typeRef.ResolvedFileName != "" {
|
||||
addReferencedFileFromFileName(program, typeRef.ResolvedFileName, &referencedFiles, sourceFileDirectory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add module augmentation as references
|
||||
for _, moduleName := range file.ModuleAugmentations {
|
||||
if !ast.IsStringLiteral(moduleName) {
|
||||
continue
|
||||
}
|
||||
addReferencedFilesFromImportLiteral(file, &referencedFiles, checker, moduleName)
|
||||
}
|
||||
|
||||
// From ambient modules
|
||||
for _, ambientModule := range checker.GetAmbientModules() {
|
||||
addReferencedFilesFromSymbol(file, &referencedFiles, ambientModule)
|
||||
}
|
||||
return core.IfElse(referencedFiles.Len() > 0, &referencedFiles, nil)
|
||||
}
|
||||
|
||||
// repopulateDiagnosticsOfFile repopulates diagnostic chains that depend on program state.
|
||||
// When diagnostics are copied from a previous build, their message chains may reference
|
||||
// stale program state (e.g., resolved module alternate results, package.json scope).
|
||||
// This function recomputes those chains using the current program's state.
|
||||
func repopulateDiagnosticsOfFile(diags *DiagnosticsOrBuildInfoDiagnosticsWithFileName, p *compiler.Program, file *ast.SourceFile) *DiagnosticsOrBuildInfoDiagnosticsWithFileName {
|
||||
if diags.diagnostics != nil {
|
||||
repopulated := repopulateDiagnosticsList(diags.diagnostics, p, file)
|
||||
if repopulated == nil {
|
||||
return diags
|
||||
}
|
||||
return &DiagnosticsOrBuildInfoDiagnosticsWithFileName{diagnostics: repopulated}
|
||||
}
|
||||
// buildInfoDiagnostics will be repopulated via toDiagnostic's repopulateInfo handling
|
||||
return diags
|
||||
}
|
||||
|
||||
// repopulateDiagnosticsList repopulates diagnostic chains in a list of diagnostics.
|
||||
// Returns nil if no diagnostics needed repopulation (i.e., no changes were made).
|
||||
func repopulateDiagnosticsList(diags []*ast.Diagnostic, p *compiler.Program, file *ast.SourceFile) []*ast.Diagnostic {
|
||||
changed := false
|
||||
result := make([]*ast.Diagnostic, len(diags))
|
||||
for i, d := range diags {
|
||||
repopulated := repopulateDiagnosticMessageChain(d.MessageChain(), p, file)
|
||||
if repopulated != nil {
|
||||
clone := d.Clone()
|
||||
clone.SetMessageChain(repopulated)
|
||||
result[i] = clone
|
||||
changed = true
|
||||
} else {
|
||||
result[i] = d
|
||||
}
|
||||
}
|
||||
if !changed {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// repopulateDiagnosticMessageChain repopulates chains that have repopulate info.
|
||||
// Returns nil if no changes were made.
|
||||
func repopulateDiagnosticMessageChain(chain []*ast.Diagnostic, p *compiler.Program, file *ast.SourceFile) []*ast.Diagnostic {
|
||||
if len(chain) == 0 {
|
||||
return nil
|
||||
}
|
||||
changed := false
|
||||
result := make([]*ast.Diagnostic, len(chain))
|
||||
for i, c := range chain {
|
||||
if c.RepopulateInfo() != nil {
|
||||
// Convert to buildInfoDiagnosticWithFileName and repopulate
|
||||
b := &buildInfoDiagnosticWithFileName{
|
||||
pos: c.Pos(),
|
||||
end: c.End(),
|
||||
code: c.Code(),
|
||||
category: c.Category(),
|
||||
messageKey: c.MessageKey(),
|
||||
messageArgs: c.MessageArgs(),
|
||||
repopulateInfo: c.RepopulateInfo(),
|
||||
}
|
||||
// Recursively handle nested chains
|
||||
for _, nested := range c.MessageChain() {
|
||||
b.messageChain = append(b.messageChain, astDiagToBuildInfoDiag(nested))
|
||||
}
|
||||
result[i] = repopulateDiagnosticChain(b, p, file)
|
||||
changed = true
|
||||
} else {
|
||||
// Check nested chains
|
||||
nested := repopulateDiagnosticMessageChain(c.MessageChain(), p, file)
|
||||
if nested != nil {
|
||||
clone := c.Clone()
|
||||
clone.SetMessageChain(nested)
|
||||
result[i] = clone
|
||||
changed = true
|
||||
} else {
|
||||
result[i] = c
|
||||
}
|
||||
}
|
||||
}
|
||||
if !changed {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func astDiagToBuildInfoDiag(d *ast.Diagnostic) *buildInfoDiagnosticWithFileName {
|
||||
b := &buildInfoDiagnosticWithFileName{
|
||||
pos: d.Pos(),
|
||||
end: d.End(),
|
||||
code: d.Code(),
|
||||
category: d.Category(),
|
||||
messageKey: d.MessageKey(),
|
||||
messageArgs: d.MessageArgs(),
|
||||
repopulateInfo: d.RepopulateInfo(),
|
||||
}
|
||||
for _, nested := range d.MessageChain() {
|
||||
b.messageChain = append(b.messageChain, astDiagToBuildInfoDiag(nested))
|
||||
}
|
||||
return b
|
||||
}
|
||||
52
tools/tsgo/internal/execute/incremental/referencemap.go
Normal file
52
tools/tsgo/internal/execute/incremental/referencemap.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package incremental
|
||||
|
||||
import (
|
||||
"iter"
|
||||
"maps"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
)
|
||||
|
||||
type referenceMap struct {
|
||||
references collections.SyncMap[tspath.Path, *collections.Set[tspath.Path]]
|
||||
referencedBy map[tspath.Path]*collections.Set[tspath.Path]
|
||||
referenceBy sync.Once
|
||||
}
|
||||
|
||||
func (r *referenceMap) storeReferences(path tspath.Path, refs *collections.Set[tspath.Path]) {
|
||||
r.references.Store(path, refs)
|
||||
}
|
||||
|
||||
func (r *referenceMap) getReferences(path tspath.Path) (*collections.Set[tspath.Path], bool) {
|
||||
refs, ok := r.references.Load(path)
|
||||
return refs, ok
|
||||
}
|
||||
|
||||
func (r *referenceMap) getPathsWithReferences() []tspath.Path {
|
||||
return slices.Collect(r.references.Keys())
|
||||
}
|
||||
|
||||
func (r *referenceMap) getReferencedBy(path tspath.Path) iter.Seq[tspath.Path] {
|
||||
r.referenceBy.Do(func() {
|
||||
r.referencedBy = make(map[tspath.Path]*collections.Set[tspath.Path])
|
||||
r.references.Range(func(key tspath.Path, value *collections.Set[tspath.Path]) bool {
|
||||
for ref := range value.Keys() {
|
||||
set, ok := r.referencedBy[ref]
|
||||
if !ok {
|
||||
set = &collections.Set[tspath.Path]{}
|
||||
r.referencedBy[ref] = set
|
||||
}
|
||||
set.Add(key)
|
||||
}
|
||||
return true
|
||||
})
|
||||
})
|
||||
refs, ok := r.referencedBy[path]
|
||||
if ok {
|
||||
return maps.Keys(refs.Keys())
|
||||
}
|
||||
return func(yield func(tspath.Path) bool) {}
|
||||
}
|
||||
440
tools/tsgo/internal/execute/incremental/snapshot.go
Normal file
440
tools/tsgo/internal/execute/incremental/snapshot.go
Normal file
@@ -0,0 +1,440 @@
|
||||
package incremental
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/checker"
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/compiler"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/diagnostics"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
"github.com/zeebo/xxh3"
|
||||
)
|
||||
|
||||
type FileInfo struct {
|
||||
version string
|
||||
signature string
|
||||
affectsGlobalScope bool
|
||||
impliedNodeFormat core.ResolutionMode
|
||||
}
|
||||
|
||||
func (f *FileInfo) Version() string { return f.version }
|
||||
func (f *FileInfo) Signature() string { return f.signature }
|
||||
func (f *FileInfo) AffectsGlobalScope() bool { return f.affectsGlobalScope }
|
||||
func (f *FileInfo) ImpliedNodeFormat() core.ResolutionMode { return f.impliedNodeFormat }
|
||||
|
||||
func ComputeHash(text string, hashWithText bool) string {
|
||||
hashBytes := xxh3.HashString128(text).Bytes()
|
||||
hash := hex.EncodeToString(hashBytes[:])
|
||||
if hashWithText {
|
||||
hash += "-" + text
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
type FileEmitKind uint32
|
||||
|
||||
const (
|
||||
FileEmitKindNone FileEmitKind = 0
|
||||
FileEmitKindJs FileEmitKind = 1 << 0 // emit js file
|
||||
FileEmitKindJsMap FileEmitKind = 1 << 1 // emit js.map file
|
||||
FileEmitKindJsInlineMap FileEmitKind = 1 << 2 // emit inline source map in js file
|
||||
FileEmitKindDtsErrors FileEmitKind = 1 << 3 // emit dts errors
|
||||
FileEmitKindDtsEmit FileEmitKind = 1 << 4 // emit d.ts file
|
||||
FileEmitKindDtsMap FileEmitKind = 1 << 5 // emit d.ts.map file
|
||||
|
||||
FileEmitKindDts = FileEmitKindDtsErrors | FileEmitKindDtsEmit
|
||||
FileEmitKindAllJs = FileEmitKindJs | FileEmitKindJsMap | FileEmitKindJsInlineMap
|
||||
FileEmitKindAllDtsEmit = FileEmitKindDtsEmit | FileEmitKindDtsMap
|
||||
FileEmitKindAllDts = FileEmitKindDts | FileEmitKindDtsMap
|
||||
FileEmitKindAll = FileEmitKindAllJs | FileEmitKindAllDts
|
||||
)
|
||||
|
||||
func GetFileEmitKind(options *core.CompilerOptions) FileEmitKind {
|
||||
result := FileEmitKindJs
|
||||
if options.SourceMap.IsTrue() {
|
||||
result |= FileEmitKindJsMap
|
||||
}
|
||||
if options.InlineSourceMap.IsTrue() {
|
||||
result |= FileEmitKindJsInlineMap
|
||||
}
|
||||
if options.GetEmitDeclarations() {
|
||||
result |= FileEmitKindDts
|
||||
}
|
||||
if options.DeclarationMap.IsTrue() {
|
||||
result |= FileEmitKindDtsMap
|
||||
}
|
||||
if options.EmitDeclarationOnly.IsTrue() {
|
||||
result &= FileEmitKindAllDts
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func getPendingEmitKindWithOptions(options *core.CompilerOptions, oldOptions *core.CompilerOptions) FileEmitKind {
|
||||
oldEmitKind := GetFileEmitKind(oldOptions)
|
||||
newEmitKind := GetFileEmitKind(options)
|
||||
return getPendingEmitKind(newEmitKind, oldEmitKind)
|
||||
}
|
||||
|
||||
func getPendingEmitKind(emitKind FileEmitKind, oldEmitKind FileEmitKind) FileEmitKind {
|
||||
if oldEmitKind == emitKind {
|
||||
return FileEmitKindNone
|
||||
}
|
||||
if oldEmitKind == 0 || emitKind == 0 {
|
||||
return emitKind
|
||||
}
|
||||
diff := oldEmitKind ^ emitKind
|
||||
result := FileEmitKindNone
|
||||
// If there is diff in Js emit, pending emit is js emit flags
|
||||
if (diff & FileEmitKindAllJs) != 0 {
|
||||
result |= emitKind & FileEmitKindAllJs
|
||||
}
|
||||
// If dts errors pending, add dts errors flag
|
||||
if (diff & FileEmitKindDtsErrors) != 0 {
|
||||
result |= emitKind & FileEmitKindAllDts
|
||||
}
|
||||
// If there is diff in Dts emit, pending emit is dts emit flags
|
||||
if (diff & FileEmitKindAllDtsEmit) != 0 {
|
||||
result |= emitKind & FileEmitKindAllDtsEmit
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Signature (Hash of d.ts emitted), is string if it was emitted using same d.ts.map option as what compilerOptions indicate,
|
||||
// otherwise tuple of string
|
||||
type emitSignature struct {
|
||||
signature string
|
||||
signatureWithDifferentOptions []string
|
||||
}
|
||||
|
||||
// Covert to Emit signature based on oldOptions and EmitSignature format
|
||||
// If d.ts map options differ then swap the format, otherwise use as is
|
||||
func (e *emitSignature) getNewEmitSignature(oldOptions *core.CompilerOptions, newOptions *core.CompilerOptions) *emitSignature {
|
||||
if oldOptions.DeclarationMap.IsTrue() == newOptions.DeclarationMap.IsTrue() {
|
||||
return e
|
||||
}
|
||||
if e.signatureWithDifferentOptions == nil {
|
||||
return &emitSignature{
|
||||
signatureWithDifferentOptions: []string{e.signature},
|
||||
}
|
||||
} else {
|
||||
return &emitSignature{
|
||||
signature: e.signatureWithDifferentOptions[0],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type buildInfoDiagnosticWithFileName struct {
|
||||
// filename if it is for a File thats other than its stored for
|
||||
file tspath.Path
|
||||
noFile bool
|
||||
pos int
|
||||
end int
|
||||
code int32
|
||||
category diagnostics.Category
|
||||
messageKey diagnostics.Key
|
||||
messageArgs []string
|
||||
messageChain []*buildInfoDiagnosticWithFileName
|
||||
relatedInformation []*buildInfoDiagnosticWithFileName
|
||||
reportsUnnecessary bool
|
||||
reportsDeprecated bool
|
||||
skippedOnNoEmit bool
|
||||
repopulateInfo *ast.RepopulateDiagnosticInfo
|
||||
}
|
||||
|
||||
type DiagnosticsOrBuildInfoDiagnosticsWithFileName struct {
|
||||
diagnostics []*ast.Diagnostic
|
||||
buildInfoDiagnostics []*buildInfoDiagnosticWithFileName
|
||||
}
|
||||
|
||||
func (b *buildInfoDiagnosticWithFileName) toDiagnostic(p *compiler.Program, file *ast.SourceFile) *ast.Diagnostic {
|
||||
var fileForDiagnostic *ast.SourceFile
|
||||
if b.file != "" {
|
||||
fileForDiagnostic = p.GetSourceFileByPath(b.file)
|
||||
} else if !b.noFile {
|
||||
fileForDiagnostic = file
|
||||
}
|
||||
|
||||
if b.repopulateInfo != nil {
|
||||
return repopulateDiagnosticChain(b, p, fileForDiagnostic)
|
||||
}
|
||||
|
||||
var messageChain []*ast.Diagnostic
|
||||
for _, msg := range b.messageChain {
|
||||
messageChain = append(messageChain, msg.toDiagnostic(p, fileForDiagnostic))
|
||||
}
|
||||
var relatedInformation []*ast.Diagnostic
|
||||
for _, info := range b.relatedInformation {
|
||||
relatedInformation = append(relatedInformation, info.toDiagnostic(p, fileForDiagnostic))
|
||||
}
|
||||
return ast.NewDiagnosticFromSerialized(
|
||||
fileForDiagnostic,
|
||||
core.NewTextRange(b.pos, b.end),
|
||||
b.code,
|
||||
b.category,
|
||||
b.messageKey,
|
||||
b.messageArgs,
|
||||
messageChain,
|
||||
relatedInformation,
|
||||
b.reportsUnnecessary,
|
||||
b.reportsDeprecated,
|
||||
b.skippedOnNoEmit,
|
||||
)
|
||||
}
|
||||
|
||||
// repopulateDiagnosticChain recomputes a diagnostic chain entry that depends on
|
||||
// program state which may have changed between incremental builds.
|
||||
func repopulateDiagnosticChain(b *buildInfoDiagnosticWithFileName, p *compiler.Program, file *ast.SourceFile) *ast.Diagnostic {
|
||||
info := b.repopulateInfo
|
||||
switch info.Kind {
|
||||
case ast.RepopulateModeMismatch:
|
||||
return repopulateModeMismatchChain(b, p, file)
|
||||
case ast.RepopulateModuleNotFound:
|
||||
return repopulateModuleNotFoundChain(b, p, file, info)
|
||||
default:
|
||||
// Fall back to using the stored (possibly stale) data
|
||||
return b.toDiagnosticWithoutRepopulate(p, file)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *buildInfoDiagnosticWithFileName) toDiagnosticWithoutRepopulate(p *compiler.Program, file *ast.SourceFile) *ast.Diagnostic {
|
||||
var messageChain []*ast.Diagnostic
|
||||
for _, msg := range b.messageChain {
|
||||
messageChain = append(messageChain, msg.toDiagnostic(p, file))
|
||||
}
|
||||
var relatedInformation []*ast.Diagnostic
|
||||
for _, info := range b.relatedInformation {
|
||||
relatedInformation = append(relatedInformation, info.toDiagnostic(p, file))
|
||||
}
|
||||
return ast.NewDiagnosticFromSerialized(
|
||||
file,
|
||||
core.NewTextRange(b.pos, b.end),
|
||||
b.code,
|
||||
b.category,
|
||||
b.messageKey,
|
||||
b.messageArgs,
|
||||
messageChain,
|
||||
relatedInformation,
|
||||
b.reportsUnnecessary,
|
||||
b.reportsDeprecated,
|
||||
b.skippedOnNoEmit,
|
||||
)
|
||||
}
|
||||
|
||||
func repopulateModeMismatchChain(b *buildInfoDiagnosticWithFileName, p *compiler.Program, file *ast.SourceFile) *ast.Diagnostic {
|
||||
if file == nil {
|
||||
return b.toDiagnosticWithoutRepopulate(p, file)
|
||||
}
|
||||
|
||||
details := checker.CreateModeMismatchDetails(p, file)
|
||||
|
||||
var nextChain []*ast.Diagnostic
|
||||
for _, msg := range b.messageChain {
|
||||
nextChain = append(nextChain, msg.toDiagnostic(p, file))
|
||||
}
|
||||
|
||||
return ast.NewDiagnosticFromSerialized(
|
||||
file,
|
||||
core.NewTextRange(b.pos, b.end),
|
||||
details.Message.Code(),
|
||||
details.Message.Category(),
|
||||
details.Message.Key(),
|
||||
diagnostics.StringifyArgs(details.Args),
|
||||
nextChain,
|
||||
nil,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
func repopulateModuleNotFoundChain(b *buildInfoDiagnosticWithFileName, p *compiler.Program, file *ast.SourceFile, info *ast.RepopulateDiagnosticInfo) *ast.Diagnostic {
|
||||
if file == nil {
|
||||
return b.toDiagnosticWithoutRepopulate(p, file)
|
||||
}
|
||||
|
||||
packageName := info.PackageName
|
||||
if packageName == "" {
|
||||
packageName = info.ModuleReference
|
||||
}
|
||||
|
||||
details := checker.CreateModuleNotFoundChain(p, file, info.ModuleReference, info.Mode, packageName)
|
||||
|
||||
var nextChain []*ast.Diagnostic
|
||||
for _, msg := range b.messageChain {
|
||||
nextChain = append(nextChain, msg.toDiagnostic(p, file))
|
||||
}
|
||||
|
||||
return ast.NewDiagnosticFromSerialized(
|
||||
file,
|
||||
core.NewTextRange(b.pos, b.end),
|
||||
details.Message.Code(),
|
||||
details.Message.Category(),
|
||||
details.Message.Key(),
|
||||
diagnostics.StringifyArgs(details.Args),
|
||||
nextChain,
|
||||
nil,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
func (d *DiagnosticsOrBuildInfoDiagnosticsWithFileName) getDiagnostics(p *compiler.Program, file *ast.SourceFile) []*ast.Diagnostic {
|
||||
if d.diagnostics != nil {
|
||||
return d.diagnostics
|
||||
}
|
||||
// Convert and cache the diagnostics
|
||||
d.diagnostics = core.Map(d.buildInfoDiagnostics, func(diag *buildInfoDiagnosticWithFileName) *ast.Diagnostic {
|
||||
return diag.toDiagnostic(p, file)
|
||||
})
|
||||
return d.diagnostics
|
||||
}
|
||||
|
||||
type snapshot struct {
|
||||
// These are the fields that get serialized
|
||||
|
||||
// Information of the file eg. its version, signature etc
|
||||
fileInfos collections.SyncMap[tspath.Path, *FileInfo]
|
||||
options *core.CompilerOptions
|
||||
// Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled
|
||||
referencedMap referenceMap
|
||||
// Cache of semantic diagnostics for files with their Path being the key
|
||||
semanticDiagnosticsPerFile collections.SyncMap[tspath.Path, *DiagnosticsOrBuildInfoDiagnosticsWithFileName]
|
||||
// Cache of dts emit diagnostics for files with their Path being the key
|
||||
emitDiagnosticsPerFile collections.SyncMap[tspath.Path, *DiagnosticsOrBuildInfoDiagnosticsWithFileName]
|
||||
// The map has key by source file's path that has been changed
|
||||
changedFilesSet collections.SyncSet[tspath.Path]
|
||||
// Files pending to be emitted
|
||||
affectedFilesPendingEmit collections.SyncMap[tspath.Path, FileEmitKind]
|
||||
// Name of the file whose dts was the latest to change
|
||||
latestChangedDtsFile string
|
||||
// Hash of d.ts emitted for the file, use to track when emit of d.ts changes
|
||||
emitSignatures collections.SyncMap[tspath.Path, *emitSignature]
|
||||
// Recorded if program had errors that need to be reported even with --noCheck
|
||||
hasErrors core.Tristate
|
||||
// Recorded if program had semantic errors only for non incremental build
|
||||
hasSemanticErrors bool
|
||||
// If semantic diagnostic check is pending
|
||||
checkPending bool
|
||||
// Looked up package.json files from
|
||||
packageJsons []string
|
||||
missingPackageJsons []string
|
||||
|
||||
// Additional fields that are not serialized but needed to track state
|
||||
|
||||
// true if build info emit is pending
|
||||
buildInfoEmitPending atomic.Bool
|
||||
hasErrorsFromOldState core.Tristate
|
||||
hasSemanticErrorsFromOldState bool
|
||||
allFilesExcludingDefaultLibraryFileOnce sync.Once
|
||||
packageJsonsFromOldState []string
|
||||
missingPackageJsonsFromOldState []string
|
||||
// Cache of all files excluding default library file for the current program
|
||||
allFilesExcludingDefaultLibraryFile []*ast.SourceFile
|
||||
hasChangedDtsFile bool
|
||||
hasEmitDiagnostics bool
|
||||
|
||||
// Used with testing to add text of hash for better comparison
|
||||
hashWithText bool
|
||||
}
|
||||
|
||||
func (s *snapshot) addFileToChangeSet(filePath tspath.Path) {
|
||||
s.changedFilesSet.Add(filePath)
|
||||
s.buildInfoEmitPending.Store(true)
|
||||
}
|
||||
|
||||
func (s *snapshot) addFileToAffectedFilesPendingEmit(filePath tspath.Path, emitKind FileEmitKind) {
|
||||
existingKind, _ := s.affectedFilesPendingEmit.Load(filePath)
|
||||
s.affectedFilesPendingEmit.Store(filePath, existingKind|emitKind)
|
||||
if emitKind&FileEmitKindDtsErrors != 0 {
|
||||
s.emitDiagnosticsPerFile.Delete(filePath)
|
||||
}
|
||||
s.buildInfoEmitPending.Store(true)
|
||||
}
|
||||
|
||||
func (s *snapshot) getAllFilesExcludingDefaultLibraryFile(program *compiler.Program, firstSourceFile *ast.SourceFile) []*ast.SourceFile {
|
||||
s.allFilesExcludingDefaultLibraryFileOnce.Do(func() {
|
||||
files := program.GetSourceFiles()
|
||||
s.allFilesExcludingDefaultLibraryFile = make([]*ast.SourceFile, 0, len(files))
|
||||
addSourceFile := func(file *ast.SourceFile) {
|
||||
if !program.IsSourceFileDefaultLibrary(file.Path()) {
|
||||
s.allFilesExcludingDefaultLibraryFile = append(s.allFilesExcludingDefaultLibraryFile, file)
|
||||
}
|
||||
}
|
||||
if firstSourceFile != nil {
|
||||
addSourceFile(firstSourceFile)
|
||||
}
|
||||
for _, file := range files {
|
||||
if file != firstSourceFile {
|
||||
addSourceFile(file)
|
||||
}
|
||||
}
|
||||
})
|
||||
return s.allFilesExcludingDefaultLibraryFile
|
||||
}
|
||||
|
||||
func getTextHandlingSourceMapForSignature(text string, data *compiler.WriteFileData) string {
|
||||
if data.SourceMapUrlPos != -1 {
|
||||
return text[:data.SourceMapUrlPos]
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
func (s *snapshot) computeSignatureWithDiagnostics(file *ast.SourceFile, text string, data *compiler.WriteFileData) string {
|
||||
var builder strings.Builder
|
||||
builder.WriteString(getTextHandlingSourceMapForSignature(text, data))
|
||||
for _, diag := range data.Diagnostics {
|
||||
diagnosticToStringBuilder(diag, file, &builder)
|
||||
}
|
||||
return s.computeHash(builder.String())
|
||||
}
|
||||
|
||||
func diagnosticToStringBuilder(diagnostic *ast.Diagnostic, file *ast.SourceFile, builder *strings.Builder) {
|
||||
if diagnostic == nil {
|
||||
return
|
||||
}
|
||||
builder.WriteString("\n")
|
||||
if diagnostic.File() != file {
|
||||
builder.WriteString(tspath.EnsurePathIsNonModuleName(tspath.GetRelativePathFromDirectory(
|
||||
tspath.GetDirectoryPath(string(file.Path())),
|
||||
string(diagnostic.File().Path()),
|
||||
tspath.ComparePathsOptions{},
|
||||
)))
|
||||
}
|
||||
if diagnostic.File() != nil {
|
||||
builder.WriteString(fmt.Sprintf("(%d,%d): ", diagnostic.Pos(), diagnostic.Len()))
|
||||
}
|
||||
builder.WriteString(diagnostic.Category().Name())
|
||||
builder.WriteString(fmt.Sprintf("%d: ", diagnostic.Code()))
|
||||
builder.WriteString(string(diagnostic.MessageKey()))
|
||||
builder.WriteString("\n")
|
||||
for _, arg := range diagnostic.MessageArgs() {
|
||||
builder.WriteString(arg)
|
||||
builder.WriteString("\n")
|
||||
}
|
||||
for _, chain := range diagnostic.MessageChain() {
|
||||
diagnosticToStringBuilder(chain, file, builder)
|
||||
}
|
||||
for _, info := range diagnostic.RelatedInformation() {
|
||||
diagnosticToStringBuilder(info, file, builder)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *snapshot) computeHash(text string) string {
|
||||
return ComputeHash(text, s.hashWithText)
|
||||
}
|
||||
|
||||
func (s *snapshot) canUseIncrementalState() bool {
|
||||
if !s.options.IsIncremental() && s.options.Build.IsTrue() {
|
||||
// If not incremental build (with tsc -b), we don't need to track state except diagnostics per file so we can use it
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
381
tools/tsgo/internal/execute/incremental/snapshottobuildinfo.go
Normal file
381
tools/tsgo/internal/execute/incremental/snapshottobuildinfo.go
Normal file
@@ -0,0 +1,381 @@
|
||||
package incremental
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/compiler"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
)
|
||||
|
||||
func snapshotToBuildInfo(snapshot *snapshot, program *compiler.Program, buildInfoFileName string) *BuildInfo {
|
||||
buildInfo := &BuildInfo{
|
||||
Version: core.Version(),
|
||||
}
|
||||
to := &toBuildInfo{
|
||||
snapshot: snapshot,
|
||||
program: program,
|
||||
buildInfo: buildInfo,
|
||||
buildInfoDirectory: tspath.GetDirectoryPath(buildInfoFileName),
|
||||
comparePathsOptions: tspath.ComparePathsOptions{
|
||||
CurrentDirectory: program.GetCurrentDirectory(),
|
||||
UseCaseSensitiveFileNames: program.UseCaseSensitiveFileNames(),
|
||||
},
|
||||
fileNameToFileId: make(map[string]BuildInfoFileId),
|
||||
fileNamesToFileIdListId: make(map[string]BuildInfoFileIdListId),
|
||||
roots: make(map[*ast.SourceFile]tspath.Path),
|
||||
}
|
||||
|
||||
if snapshot.options.IsIncremental() {
|
||||
to.collectRootFiles()
|
||||
to.setFileInfoAndEmitSignatures()
|
||||
to.setRootOfIncrementalProgram()
|
||||
to.setCompilerOptions()
|
||||
to.setReferencedMap()
|
||||
to.setChangeFileSet()
|
||||
to.setSemanticDiagnostics()
|
||||
to.setEmitDiagnostics()
|
||||
to.setAffectedFilesPendingEmit()
|
||||
if snapshot.latestChangedDtsFile != "" {
|
||||
buildInfo.LatestChangedDtsFile = to.relativeToBuildInfo(snapshot.latestChangedDtsFile)
|
||||
}
|
||||
} else {
|
||||
to.setRootOfNonIncrementalProgram()
|
||||
}
|
||||
buildInfo.Errors = snapshot.hasErrors.IsTrue()
|
||||
buildInfo.SemanticErrors = snapshot.hasSemanticErrors
|
||||
buildInfo.CheckPending = snapshot.checkPending
|
||||
to.setPackageJsons()
|
||||
return buildInfo
|
||||
}
|
||||
|
||||
type toBuildInfo struct {
|
||||
snapshot *snapshot
|
||||
program *compiler.Program
|
||||
buildInfo *BuildInfo
|
||||
buildInfoDirectory string
|
||||
comparePathsOptions tspath.ComparePathsOptions
|
||||
fileNameToFileId map[string]BuildInfoFileId
|
||||
fileNamesToFileIdListId map[string]BuildInfoFileIdListId
|
||||
roots map[*ast.SourceFile]tspath.Path
|
||||
}
|
||||
|
||||
func (t *toBuildInfo) relativeToBuildInfo(path string) string {
|
||||
return tspath.EnsurePathIsNonModuleName(tspath.GetRelativePathFromDirectory(t.buildInfoDirectory, path, t.comparePathsOptions))
|
||||
}
|
||||
|
||||
func (t *toBuildInfo) toFileId(path tspath.Path) BuildInfoFileId {
|
||||
fileId := t.fileNameToFileId[string(path)]
|
||||
if fileId == 0 {
|
||||
if libFile := t.program.GetDefaultLibFile(path); libFile != nil && !libFile.Replaced {
|
||||
t.buildInfo.FileNames = append(t.buildInfo.FileNames, libFile.Name)
|
||||
} else {
|
||||
t.buildInfo.FileNames = append(t.buildInfo.FileNames, t.relativeToBuildInfo(string(path)))
|
||||
}
|
||||
fileId = BuildInfoFileId(len(t.buildInfo.FileNames))
|
||||
t.fileNameToFileId[string(path)] = fileId
|
||||
}
|
||||
return fileId
|
||||
}
|
||||
|
||||
func (t *toBuildInfo) toFileIdListId(set *collections.Set[tspath.Path]) BuildInfoFileIdListId {
|
||||
fileIds := core.Map(slices.Collect(maps.Keys(set.Keys())), t.toFileId)
|
||||
slices.Sort(fileIds)
|
||||
key := strings.Join(core.Map(fileIds, func(id BuildInfoFileId) string {
|
||||
return fmt.Sprintf("%d", id)
|
||||
}), ",")
|
||||
|
||||
fileIdListId := t.fileNamesToFileIdListId[key]
|
||||
if fileIdListId == 0 {
|
||||
t.buildInfo.FileIdsList = append(t.buildInfo.FileIdsList, fileIds)
|
||||
fileIdListId = BuildInfoFileIdListId(len(t.buildInfo.FileIdsList))
|
||||
t.fileNamesToFileIdListId[key] = fileIdListId
|
||||
}
|
||||
return fileIdListId
|
||||
}
|
||||
|
||||
func (t *toBuildInfo) toRelativeToBuildInfoCompilerOptionValue(option *tsoptions.CommandLineOption, v any) any {
|
||||
if option.Kind == "list" {
|
||||
if option.Elements().IsFilePath {
|
||||
if arr, ok := v.([]string); ok {
|
||||
return core.Map(arr, t.relativeToBuildInfo)
|
||||
}
|
||||
}
|
||||
} else if option.IsFilePath {
|
||||
if str, ok := v.(string); ok && str != "" {
|
||||
return t.relativeToBuildInfo(v.(string))
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (t *toBuildInfo) toBuildInfoDiagnosticsFromFileNameDiagnostics(diagnostics []*buildInfoDiagnosticWithFileName) []*BuildInfoDiagnostic {
|
||||
return core.Map(diagnostics, func(d *buildInfoDiagnosticWithFileName) *BuildInfoDiagnostic {
|
||||
var file BuildInfoFileId
|
||||
if d.file != "" {
|
||||
file = t.toFileId(d.file)
|
||||
}
|
||||
return &BuildInfoDiagnostic{
|
||||
File: file,
|
||||
NoFile: d.noFile,
|
||||
Pos: d.pos,
|
||||
End: d.end,
|
||||
Code: d.code,
|
||||
Category: d.category,
|
||||
MessageKey: d.messageKey,
|
||||
MessageArgs: d.messageArgs,
|
||||
MessageChain: t.toBuildInfoDiagnosticsFromFileNameDiagnostics(d.messageChain),
|
||||
RelatedInformation: t.toBuildInfoDiagnosticsFromFileNameDiagnostics(d.relatedInformation),
|
||||
ReportsUnnecessary: d.reportsUnnecessary,
|
||||
ReportsDeprecated: d.reportsDeprecated,
|
||||
SkippedOnNoEmit: d.skippedOnNoEmit,
|
||||
RepopulateInfo: toBuildInfoRepopulateInfo(d.repopulateInfo),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (t *toBuildInfo) toBuildInfoDiagnosticsFromDiagnostics(filePath tspath.Path, diagnostics []*ast.Diagnostic) []*BuildInfoDiagnostic {
|
||||
return core.Map(diagnostics, func(d *ast.Diagnostic) *BuildInfoDiagnostic {
|
||||
var file BuildInfoFileId
|
||||
noFile := false
|
||||
if d.File() == nil {
|
||||
noFile = true
|
||||
} else if d.File().Path() != filePath {
|
||||
file = t.toFileId(d.File().Path())
|
||||
}
|
||||
return &BuildInfoDiagnostic{
|
||||
File: file,
|
||||
NoFile: noFile,
|
||||
Pos: d.Loc().Pos(),
|
||||
End: d.Loc().End(),
|
||||
Code: d.Code(),
|
||||
Category: d.Category(),
|
||||
MessageKey: d.MessageKey(),
|
||||
MessageArgs: d.MessageArgs(),
|
||||
MessageChain: t.toBuildInfoDiagnosticsFromDiagnostics(filePath, d.MessageChain()),
|
||||
RelatedInformation: t.toBuildInfoDiagnosticsFromDiagnostics(filePath, d.RelatedInformation()),
|
||||
ReportsUnnecessary: d.ReportsUnnecessary(),
|
||||
ReportsDeprecated: d.ReportsDeprecated(),
|
||||
SkippedOnNoEmit: d.SkippedOnNoEmit(),
|
||||
RepopulateInfo: toBuildInfoRepopulateInfo(d.RepopulateInfo()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func toBuildInfoRepopulateInfo(info *ast.RepopulateDiagnosticInfo) *BuildInfoRepopulateInfo {
|
||||
if info == nil {
|
||||
return nil
|
||||
}
|
||||
return &BuildInfoRepopulateInfo{
|
||||
Kind: info.Kind,
|
||||
ModuleReference: info.ModuleReference,
|
||||
Mode: info.Mode,
|
||||
PackageName: info.PackageName,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *toBuildInfo) toBuildInfoDiagnosticsOfFile(filePath tspath.Path, diags *DiagnosticsOrBuildInfoDiagnosticsWithFileName) *BuildInfoDiagnosticsOfFile {
|
||||
if len(diags.diagnostics) > 0 {
|
||||
return &BuildInfoDiagnosticsOfFile{
|
||||
FileId: t.toFileId(filePath),
|
||||
Diagnostics: t.toBuildInfoDiagnosticsFromDiagnostics(filePath, diags.diagnostics),
|
||||
}
|
||||
}
|
||||
if len(diags.buildInfoDiagnostics) > 0 {
|
||||
return &BuildInfoDiagnosticsOfFile{
|
||||
FileId: t.toFileId(filePath),
|
||||
Diagnostics: t.toBuildInfoDiagnosticsFromFileNameDiagnostics(diags.buildInfoDiagnostics),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *toBuildInfo) collectRootFiles() {
|
||||
for _, fileName := range t.program.CommandLine().FileNames() {
|
||||
var file *ast.SourceFile
|
||||
if redirect := t.program.GetParseFileRedirect(fileName); redirect != "" {
|
||||
file = t.program.GetSourceFile(redirect)
|
||||
} else {
|
||||
file = t.program.GetSourceFile(fileName)
|
||||
}
|
||||
if file != nil {
|
||||
t.roots[file] = tspath.ToPath(fileName, t.comparePathsOptions.CurrentDirectory, t.comparePathsOptions.UseCaseSensitiveFileNames)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *toBuildInfo) setFileInfoAndEmitSignatures() {
|
||||
t.buildInfo.FileInfos = core.Map(t.program.GetSourceFiles(), func(file *ast.SourceFile) *BuildInfoFileInfo {
|
||||
info, _ := t.snapshot.fileInfos.Load(file.Path())
|
||||
fileId := t.toFileId(file.Path())
|
||||
// tryAddRoot(key, fileId);
|
||||
if t.buildInfo.FileNames[fileId-1] != t.relativeToBuildInfo(string(file.Path())) {
|
||||
if libFile := t.program.GetDefaultLibFile(file.Path()); libFile == nil || libFile.Replaced || t.buildInfo.FileNames[fileId-1] != libFile.Name {
|
||||
panic(fmt.Sprintf("File name at index %d does not match expected relative path or libName: %s != %s", fileId-1, t.buildInfo.FileNames[fileId-1], t.relativeToBuildInfo(string(file.Path()))))
|
||||
}
|
||||
}
|
||||
if t.snapshot.options.Composite.IsTrue() {
|
||||
if !ast.IsJsonSourceFile(file) && t.program.SourceFileMayBeEmitted(file, false) {
|
||||
if emitSignature, loaded := t.snapshot.emitSignatures.Load(file.Path()); !loaded {
|
||||
t.buildInfo.EmitSignatures = append(t.buildInfo.EmitSignatures, &BuildInfoEmitSignature{
|
||||
FileId: fileId,
|
||||
})
|
||||
} else if emitSignature.signature != info.signature {
|
||||
incrementalEmitSignature := &BuildInfoEmitSignature{
|
||||
FileId: fileId,
|
||||
}
|
||||
if emitSignature.signature != "" {
|
||||
incrementalEmitSignature.Signature = emitSignature.signature
|
||||
} else if emitSignature.signatureWithDifferentOptions[0] == info.signature {
|
||||
incrementalEmitSignature.DiffersOnlyInDtsMap = true
|
||||
} else {
|
||||
incrementalEmitSignature.Signature = emitSignature.signatureWithDifferentOptions[0]
|
||||
incrementalEmitSignature.DiffersInOptions = true
|
||||
}
|
||||
t.buildInfo.EmitSignatures = append(t.buildInfo.EmitSignatures, incrementalEmitSignature)
|
||||
}
|
||||
}
|
||||
}
|
||||
return newBuildInfoFileInfo(info)
|
||||
})
|
||||
}
|
||||
|
||||
func (t *toBuildInfo) setRootOfIncrementalProgram() {
|
||||
keys := slices.Collect(maps.Keys(t.roots))
|
||||
slices.SortFunc(keys, func(a, b *ast.SourceFile) int {
|
||||
return int(t.toFileId(a.Path())) - int(t.toFileId(b.Path()))
|
||||
})
|
||||
for _, file := range keys {
|
||||
root := t.toFileId(t.roots[file])
|
||||
resolved := t.toFileId(file.Path())
|
||||
if t.buildInfo.Root == nil {
|
||||
// First fileId as is
|
||||
t.buildInfo.Root = append(t.buildInfo.Root, &BuildInfoRoot{Start: resolved})
|
||||
} else {
|
||||
last := t.buildInfo.Root[len(t.buildInfo.Root)-1]
|
||||
if last.End == resolved-1 {
|
||||
// If its [..., last = [start, end = fileId - 1]], update last to [start, fileId]
|
||||
last.End = resolved
|
||||
} else if last.End == 0 && last.Start == resolved-1 {
|
||||
// If its [..., last = start = fileId - 1 ], update last to [start, fileId]
|
||||
last.End = resolved
|
||||
} else {
|
||||
t.buildInfo.Root = append(t.buildInfo.Root, &BuildInfoRoot{Start: resolved})
|
||||
}
|
||||
}
|
||||
if root != resolved {
|
||||
t.buildInfo.ResolvedRoot = append(t.buildInfo.ResolvedRoot, &BuildInfoResolvedRoot{
|
||||
Resolved: resolved,
|
||||
Root: root,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *toBuildInfo) setCompilerOptions() {
|
||||
tsoptions.ForEachCompilerOptionValue(
|
||||
t.snapshot.options,
|
||||
func(option *tsoptions.CommandLineOption) bool {
|
||||
return option.AffectsBuildInfo
|
||||
},
|
||||
func(option *tsoptions.CommandLineOption, value reflect.Value, i int) bool {
|
||||
if value.IsZero() {
|
||||
return false
|
||||
}
|
||||
// Make it relative to buildInfo directory if file path
|
||||
if t.buildInfo.Options == nil {
|
||||
t.buildInfo.Options = &collections.OrderedMap[string, any]{}
|
||||
}
|
||||
t.buildInfo.Options.Set(option.Name, t.toRelativeToBuildInfoCompilerOptionValue(option, value.Interface()))
|
||||
return false
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (t *toBuildInfo) setReferencedMap() {
|
||||
keys := t.snapshot.referencedMap.getPathsWithReferences()
|
||||
slices.Sort(keys)
|
||||
t.buildInfo.ReferencedMap = core.Map(keys, func(filePath tspath.Path) *BuildInfoReferenceMapEntry {
|
||||
references, _ := t.snapshot.referencedMap.getReferences(filePath)
|
||||
return &BuildInfoReferenceMapEntry{
|
||||
FileId: t.toFileId(filePath),
|
||||
FileIdListId: t.toFileIdListId(references),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (t *toBuildInfo) setChangeFileSet() {
|
||||
files := slices.Collect(t.snapshot.changedFilesSet.Keys())
|
||||
slices.Sort(files)
|
||||
t.buildInfo.ChangeFileSet = core.Map(files, t.toFileId)
|
||||
}
|
||||
|
||||
func (t *toBuildInfo) setSemanticDiagnostics() {
|
||||
for _, file := range t.program.GetSourceFiles() {
|
||||
value, ok := t.snapshot.semanticDiagnosticsPerFile.Load(file.Path())
|
||||
if !ok {
|
||||
if !t.snapshot.changedFilesSet.Has(file.Path()) {
|
||||
t.buildInfo.SemanticDiagnosticsPerFile = append(t.buildInfo.SemanticDiagnosticsPerFile, &BuildInfoSemanticDiagnostic{
|
||||
FileId: t.toFileId(file.Path()),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
diagnostics := t.toBuildInfoDiagnosticsOfFile(file.Path(), value)
|
||||
if diagnostics != nil {
|
||||
t.buildInfo.SemanticDiagnosticsPerFile = append(t.buildInfo.SemanticDiagnosticsPerFile, &BuildInfoSemanticDiagnostic{
|
||||
Diagnostics: diagnostics,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *toBuildInfo) setEmitDiagnostics() {
|
||||
files := slices.Collect(t.snapshot.emitDiagnosticsPerFile.Keys())
|
||||
slices.Sort(files)
|
||||
t.buildInfo.EmitDiagnosticsPerFile = core.Map(files, func(filePath tspath.Path) *BuildInfoDiagnosticsOfFile {
|
||||
value, _ := t.snapshot.emitDiagnosticsPerFile.Load(filePath)
|
||||
return t.toBuildInfoDiagnosticsOfFile(filePath, value)
|
||||
})
|
||||
}
|
||||
|
||||
func (t *toBuildInfo) setAffectedFilesPendingEmit() {
|
||||
files := slices.Collect(t.snapshot.affectedFilesPendingEmit.Keys())
|
||||
slices.Sort(files)
|
||||
fullEmitKind := GetFileEmitKind(t.snapshot.options)
|
||||
for _, filePath := range files {
|
||||
file := t.program.GetSourceFileByPath(filePath)
|
||||
if file == nil || !t.program.SourceFileMayBeEmitted(file, false) {
|
||||
continue
|
||||
}
|
||||
pendingEmit, _ := t.snapshot.affectedFilesPendingEmit.Load(filePath)
|
||||
t.buildInfo.AffectedFilesPendingEmit = append(t.buildInfo.AffectedFilesPendingEmit, &BuildInfoFilePendingEmit{
|
||||
FileId: t.toFileId(filePath),
|
||||
EmitKind: core.IfElse(pendingEmit == fullEmitKind, 0, pendingEmit),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (t *toBuildInfo) setRootOfNonIncrementalProgram() {
|
||||
t.buildInfo.Root = core.Map(t.program.CommandLine().FileNames(), func(fileName string) *BuildInfoRoot {
|
||||
return &BuildInfoRoot{
|
||||
NonIncremental: t.relativeToBuildInfo(string(tspath.ToPath(fileName, t.comparePathsOptions.CurrentDirectory, t.comparePathsOptions.UseCaseSensitiveFileNames))),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (t *toBuildInfo) setPackageJsons() {
|
||||
if len(t.snapshot.packageJsons) > 0 {
|
||||
t.buildInfo.PackageJsons = core.Map(t.snapshot.packageJsons, t.relativeToBuildInfo)
|
||||
}
|
||||
if len(t.snapshot.missingPackageJsons) > 0 {
|
||||
t.buildInfo.MissingPackageJsons = core.Map(t.snapshot.missingPackageJsons, t.relativeToBuildInfo)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user