Skip to content

Commit be34d99

Browse files
authored
ExplainFiles to handle duplicated files (microsoft#2617)
1 parent 8258580 commit be34d99

9 files changed

Lines changed: 117 additions & 72 deletions

internal/compiler/fileInclude.go

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"sync"
66

77
"github.com/microsoft/typescript-go/internal/ast"
8-
"github.com/microsoft/typescript-go/internal/core"
98
"github.com/microsoft/typescript-go/internal/diagnostics"
109
"github.com/microsoft/typescript-go/internal/module"
1110
"github.com/microsoft/typescript-go/internal/scanner"
@@ -23,8 +22,6 @@ const (
2322
fileIncludeKindLibReferenceDirective
2423

2524
fileIncludeKindRootFile
26-
fileIncludeKindSourceFromProjectReference
27-
fileIncludeKindOutputFromProjectReference
2825
fileIncludeKindLibFile
2926
fileIncludeKindAutomaticTypeDirectiveFile
3027
)
@@ -192,15 +189,6 @@ func (r *FileIncludeReason) computeDiagnostic(program *Program, toFileName func(
192189
} else {
193190
return ast.NewCompilerDiagnostic(diagnostics.Root_file_specified_for_compilation)
194191
}
195-
case fileIncludeKindSourceFromProjectReference,
196-
fileIncludeKindOutputFromProjectReference:
197-
diag := core.IfElse(
198-
r.kind == fileIncludeKindOutputFromProjectReference,
199-
diagnostics.Output_from_referenced_project_0_included_because_module_is_specified_as_none,
200-
diagnostics.Source_from_referenced_project_0_included_because_module_is_specified_as_none,
201-
)
202-
referencedResolvedRef := program.projectReferenceFileMapper.getResolvedProjectReferences()[r.asIndex()]
203-
return ast.NewCompilerDiagnostic(diag, toFileName(referencedResolvedRef.ConfigName()))
204192
case fileIncludeKindAutomaticTypeDirectiveFile:
205193
data := r.asAutomaticTypeDirectiveFileData()
206194
if program.Options().Types != nil {
@@ -288,16 +276,6 @@ func (r *FileIncludeReason) toRelatedInfo(program *Program) *ast.Diagnostic {
288276
return tsoptions.CreateDiagnosticForNodeInSourceFile(config.ConfigFile.SourceFile, includeNode.AsNode(), diagnostics.File_is_matched_by_include_pattern_specified_here)
289277
}
290278
}
291-
case fileIncludeKindSourceFromProjectReference,
292-
fileIncludeKindOutputFromProjectReference:
293-
return tsoptions.CreateDiagnosticAtReferenceSyntax(
294-
config,
295-
r.asIndex(),
296-
core.IfElse(
297-
r.kind == fileIncludeKindOutputFromProjectReference,
298-
diagnostics.File_is_output_from_referenced_project_specified_here,
299-
diagnostics.File_is_source_from_referenced_project_specified_here,
300-
))
301279
case fileIncludeKindAutomaticTypeDirectiveFile:
302280
if program.Options().Types != nil {
303281
data := r.asAutomaticTypeDirectiveFileData()

internal/compiler/fileloader.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,24 @@ type fileLoader struct {
5050
pathForLibFileResolutions collections.SyncMap[tspath.Path, *libResolution]
5151
}
5252

53+
type redirectsFile struct {
54+
// Index of file at which this redirect file needs to be iterated
55+
index int
56+
fileName string
57+
path tspath.Path
58+
target tspath.Path
59+
}
60+
61+
var _ ast.HasFileName = (*redirectsFile)(nil)
62+
63+
func (r *redirectsFile) FileName() string {
64+
return r.fileName
65+
}
66+
67+
func (r *redirectsFile) Path() tspath.Path {
68+
return r.path
69+
}
70+
5371
type processedFiles struct {
5472
resolver *module.Resolver
5573
files []*ast.SourceFile
@@ -70,9 +88,9 @@ type processedFiles struct {
7088
outputFileToProjectReferenceSource map[tspath.Path]string
7189
// Key is a file path. Value is the list of files that redirect to it (same package, different install location)
7290
redirectTargetsMap map[tspath.Path][]string
73-
// Any paths involved in deduplication, including canonical paths and redirected paths
74-
deduplicatedPaths collections.Set[tspath.Path]
75-
finishedProcessing bool
91+
// filesByPath for redirect files
92+
redirectFilesByPath map[tspath.Path]*redirectsFile
93+
finishedProcessing bool
7694
}
7795

7896
type jsxRuntimeImportSpecifier struct {

internal/compiler/filesparser.go

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -293,11 +293,11 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles {
293293
libFilesMap := make(map[tspath.Path]*LibFile, libFileCount)
294294

295295
var redirectTargetsMap map[tspath.Path][]string
296-
var deduplicatedPaths collections.Set[tspath.Path]
297-
var packageIdToCanonicalPath map[module.PackageId]tspath.Path
296+
var redirectFilesByPath map[tspath.Path]*redirectsFile
297+
var packageIdToSourceFile map[module.PackageId]*ast.SourceFile
298298
if !loader.opts.Config.CompilerOptions().DeduplicatePackages.IsFalse() {
299299
redirectTargetsMap = make(map[tspath.Path][]string)
300-
packageIdToCanonicalPath = make(map[module.PackageId]tspath.Path)
300+
packageIdToSourceFile = make(map[module.PackageId]*ast.SourceFile)
301301
}
302302

303303
var collectFiles func(tasks []*parseTask, seen map[*parseTaskData]string)
@@ -349,22 +349,31 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles {
349349
loader.opts.Host.Trace(trace.Message, trace.Args...)
350350
}
351351

352-
var existingCanonicalPath tspath.Path
353-
if packageIdToCanonicalPath != nil && data.packageId.Name != "" {
354-
if canonical, exists := packageIdToCanonicalPath[data.packageId]; exists {
355-
redirectTargetsMap[canonical] = append(redirectTargetsMap[canonical], task.normalizedFilePath)
356-
existingCanonicalPath = canonical
357-
deduplicatedPaths.Add(task.path)
358-
deduplicatedPaths.Add(canonical)
359-
} else {
360-
packageIdToCanonicalPath[data.packageId] = task.path
352+
file := task.file
353+
if packageIdToSourceFile != nil && data.packageId.Name != "" {
354+
if packageIdFile, exists := packageIdToSourceFile[data.packageId]; exists {
355+
redirectTargetsMap[packageIdFile.Path()] = append(redirectTargetsMap[packageIdFile.Path()], task.normalizedFilePath)
356+
if redirectFilesByPath == nil {
357+
redirectFilesByPath = make(map[tspath.Path]*redirectsFile, totalFileCount)
358+
}
359+
redirectFilesByPath[task.path] = &redirectsFile{
360+
index: len(files) + len(redirectFilesByPath),
361+
fileName: task.normalizedFilePath,
362+
path: task.path,
363+
target: packageIdFile.Path(),
364+
}
365+
filesByPath[task.path] = packageIdFile
366+
if data.lowestDepth > 0 {
367+
sourceFilesFoundSearchingNodeModules.Add(task.path)
368+
}
369+
continue
370+
} else if file != nil {
371+
packageIdToSourceFile[data.packageId] = file
361372
}
362373
}
363374

364-
if existingCanonicalPath == "" {
365-
if subTasks := task.subTasks; len(subTasks) > 0 {
366-
collectFiles(subTasks, seen)
367-
}
375+
if subTasks := task.subTasks; len(subTasks) > 0 {
376+
collectFiles(subTasks, seen)
368377
}
369378

370379
// Exclude automatic type directive tasks from include reason processing,
@@ -381,10 +390,6 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles {
381390
typeResolutionsInFile[task.path] = task.typeResolutionsInFile
382391
continue
383392
}
384-
file := task.file
385-
if existingCanonicalPath != "" {
386-
file = filesByPath[existingCanonicalPath]
387-
}
388393

389394
path := task.path
390395

@@ -401,7 +406,7 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles {
401406
if task.libFile != nil {
402407
libFiles = append(libFiles, file)
403408
libFilesMap[path] = task.libFile
404-
} else if existingCanonicalPath == "" {
409+
} else {
405410
files = append(files, file)
406411
}
407412
filesByPath[path] = file
@@ -431,6 +436,9 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles {
431436
loader.sortLibs(libFiles)
432437

433438
allFiles := append(libFiles, files...)
439+
for _, redirectFile := range redirectFilesByPath {
440+
redirectFile.index += len(libFiles)
441+
}
434442

435443
keys := slices.Collect(loader.pathForLibFileResolutions.Keys())
436444
slices.Sort(keys)
@@ -461,7 +469,7 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles {
461469
includeProcessor: includeProcessor,
462470
outputFileToProjectReferenceSource: outputFileToProjectReferenceSource,
463471
redirectTargetsMap: redirectTargetsMap,
464-
deduplicatedPaths: deduplicatedPaths,
472+
redirectFilesByPath: redirectFilesByPath,
465473
}
466474
}
467475

internal/compiler/includeprocessor.go

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -122,28 +122,38 @@ func (i *includeProcessor) getRelatedInfo(r *FileIncludeReason, program *Program
122122

123123
func (i *includeProcessor) explainRedirectAndImpliedFormat(
124124
program *Program,
125-
file *ast.SourceFile,
125+
filePath tspath.Path,
126126
toFileName func(fileName string) string,
127127
) []*ast.Diagnostic {
128-
if existing, ok := i.redirectAndFileFormat.Load(file.Path()); ok {
128+
if existing, ok := i.redirectAndFileFormat.Load(filePath); ok {
129129
return existing
130130
}
131+
var file ast.HasFileName
132+
var sourceFile *ast.SourceFile
133+
redirectsFile := program.redirectFilesByPath[filePath]
134+
if redirectsFile != nil {
135+
file = redirectsFile
136+
} else {
137+
sourceFile = program.GetSourceFileByPath(filePath)
138+
file = sourceFile
139+
}
131140
var result []*ast.Diagnostic
132141
if source := program.GetSourceOfProjectReferenceIfOutputIncluded(file); source != file.FileName() {
133142
result = append(result, ast.NewCompilerDiagnostic(
134143
diagnostics.File_is_output_of_project_reference_source_0,
135144
toFileName(source),
136145
))
137146
}
138-
// !!! redirects
139-
// if (file.redirectInfo) {
140-
// (result ??= []).push(chainDiagnosticMessages(
141-
// /*details*/ undefined,
142-
// Diagnostics.File_redirects_to_file_0,
143-
// toFileName(file.redirectInfo.redirectTarget, fileNameConvertor),
144-
// ));
145-
// }
146-
if ast.IsExternalOrCommonJSModule(file) {
147+
148+
if redirectsFile != nil {
149+
targetFile := program.GetSourceFileByPath(redirectsFile.target)
150+
result = append(result, ast.NewCompilerDiagnostic(
151+
diagnostics.File_redirects_to_file_0,
152+
toFileName(targetFile.FileName()),
153+
))
154+
}
155+
156+
if sourceFile != nil && ast.IsExternalOrCommonJSModule(sourceFile) {
147157
metaData := program.GetSourceFileMetaData(file.Path())
148158
switch program.GetImpliedNodeFormatForEmit(file) {
149159
case core.ModuleKindESNext:
@@ -166,6 +176,6 @@ func (i *includeProcessor) explainRedirectAndImpliedFormat(
166176
}
167177
}
168178

169-
result, _ = i.redirectAndFileFormat.LoadOrStore(file.Path(), result)
179+
result, _ = i.redirectAndFileFormat.LoadOrStore(filePath, result)
170180
return result
171181
}

internal/compiler/processingDiagnostic.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ func (d *processingDiagnostic) createDiagnosticExplainingFile(program *Program)
102102
for _, reason := range reasons {
103103
processInclude(reason)
104104
}
105-
redirectInfo = program.includeProcessor.explainRedirectAndImpliedFormat(program, program.GetSourceFileByPath(diag.file), func(fileName string) string { return fileName })
105+
redirectInfo = program.includeProcessor.explainRedirectAndImpliedFormat(program, diag.file, func(fileName string) string { return fileName })
106106
}
107107
if diag.diagnosticReason != nil {
108108
processInclude(diag.diagnosticReason)

internal/compiler/program.go

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -241,18 +241,19 @@ func NewProgram(opts ProgramOptions) *Program {
241241
// Return an updated program for which it is known that only the file with the given path has changed.
242242
// In addition to a new program, return a boolean indicating whether the data of the old program was reused.
243243
func (p *Program) UpdateProgram(changedFilePath tspath.Path, newHost CompilerHost) (*Program, bool) {
244-
oldFile := p.filesByPath[changedFilePath]
245244
newOpts := p.opts
246245
newOpts.Host = newHost
247-
newFile := newHost.GetSourceFile(oldFile.ParseOptions())
248-
if !canReplaceFileInProgram(oldFile, newFile) {
249-
return NewProgram(newOpts), false
250-
}
246+
251247
// If this file is part of a package redirect group (same package installed in multiple
252248
// node_modules locations), we need to rebuild the program because the redirect targets
253249
// might need recalculation.
254-
if p.deduplicatedPaths.Has(changedFilePath) {
255-
// File is either a canonical file or a redirect target; either way, need full rebuild
250+
if _, exists := p.redirectFilesByPath[changedFilePath]; exists {
251+
return NewProgram(newOpts), false
252+
}
253+
254+
oldFile := p.filesByPath[changedFilePath]
255+
newFile := newHost.GetSourceFile(oldFile.ParseOptions())
256+
if !canReplaceFileInProgram(oldFile, newFile) {
256257
return NewProgram(newOpts), false
257258
}
258259
// TODO: reverify compiler options when config has changed?
@@ -1576,6 +1577,8 @@ func (p *Program) HasSameFileNames(other *Program) bool {
15761577
return maps.EqualFunc(p.filesByPath, other.filesByPath, func(a, b *ast.SourceFile) bool {
15771578
// checks for casing differences on case-insensitive file systems
15781579
return a.FileName() == b.FileName()
1580+
}) && maps.EqualFunc(p.redirectFilesByPath, other.redirectFilesByPath, func(a, b *redirectsFile) bool {
1581+
return a.FileName() == b.FileName()
15791582
})
15801583
}
15811584

@@ -1599,15 +1602,40 @@ func (p *Program) ExplainFiles(w io.Writer, locale locale.Locale) {
15991602
toRelativeFileName := func(fileName string) string {
16001603
return tspath.GetRelativePathFromDirectory(p.GetCurrentDirectory(), fileName, p.comparePathsOptions)
16011604
}
1602-
for _, file := range p.GetSourceFiles() {
1605+
filesExplained := 0
1606+
explainFile := func(file ast.HasFileName) {
16031607
fmt.Fprintln(w, toRelativeFileName(file.FileName()))
16041608
for _, reason := range p.includeProcessor.fileIncludeReasons[file.Path()] {
16051609
fmt.Fprintln(w, " ", reason.toDiagnostic(p, true).Localize(locale))
16061610
}
1607-
for _, diag := range p.includeProcessor.explainRedirectAndImpliedFormat(p, file, toRelativeFileName) {
1611+
for _, diag := range p.includeProcessor.explainRedirectAndImpliedFormat(p, file.Path(), toRelativeFileName) {
16081612
fmt.Fprintln(w, " ", diag.Localize(locale))
16091613
}
1614+
filesExplained++
1615+
}
1616+
1617+
redirectFiles := slices.Collect(maps.Values(p.redirectFilesByPath))
1618+
slices.SortFunc(redirectFiles, func(a, b *redirectsFile) int {
1619+
return a.index - b.index
1620+
})
1621+
1622+
files := p.GetSourceFiles()
1623+
sourceFileIndex := 0
1624+
explainSourceFiles := func(endIndex int) {
1625+
for filesExplained < endIndex {
1626+
explainFile(files[sourceFileIndex])
1627+
sourceFileIndex++
1628+
}
16101629
}
1630+
1631+
for _, redirectFile := range redirectFiles {
1632+
// Explain all sourceFiles till we reach this redirectFile index
1633+
explainSourceFiles(redirectFile.index)
1634+
explainFile(redirectFile)
1635+
}
1636+
1637+
// Explain any remaining sourceFiles
1638+
explainSourceFiles(len(files) + len(redirectFiles))
16111639
}
16121640

16131641
func (p *Program) GetLibFileFromReference(ref *ast.FileReference) *ast.SourceFile {

internal/execute/tsctests/tsc_test.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,6 @@ func TestTscComposite(t *testing.T) {
257257
commandLineArgs: []string{"--composite", "false"},
258258
},
259259
{
260-
// !!! sheetal null is not reflected in final options
261260
subScenario: "when setting composite null on command line",
262261
files: FileMap{
263262
"/home/src/workspaces/project/src/main.ts": "export const x = 10;",
@@ -725,7 +724,6 @@ func TestTscDeclarationEmit(t *testing.T) {
725724
commandLineArgs: []string{"-p", "D:\\Work\\pkg1", "--explainFiles"},
726725
},
727726
{
728-
// !!! sheetal redirected files not yet implemented
729727
subScenario: "when same version is referenced through source and another symlinked package",
730728
files: FileMap{
731729
`/user/username/projects/myproject/plugin-two/index.d.ts`: pluginTwoDts(),
@@ -742,7 +740,6 @@ func TestTscDeclarationEmit(t *testing.T) {
742740
commandLineArgs: []string{"-p", "plugin-one", "--explainFiles"},
743741
},
744742
{
745-
// !!! sheetal redirected files not yet implemented
746743
subScenario: "when same version is referenced through source and another symlinked package with indirect link",
747744
files: FileMap{
748745
`/user/username/projects/myproject/plugin-two/package.json`: stringtestutil.Dedent(`

testdata/baselines/reference/tsc/declarationEmit/when-same-version-is-referenced-through-source-and-another-symlinked-package-with-indirect-link.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ plugin-two/node_modules/typescript-fsa/index.d.ts
170170
Imported via "typescript-fsa" from file 'plugin-two/dist/commonjs/index.d.ts' with packageId 'typescript-fsa/index.d.ts@3.0.0-beta-2'
171171
plugin-two/dist/commonjs/index.d.ts
172172
Imported via "plugin-two" from file 'plugin-one/index.ts' with packageId 'plugin-two/dist/commonjs/index.d.ts@0.1.3'
173+
plugin-one/node_modules/typescript-fsa/index.d.ts
174+
Imported via "typescript-fsa" from file 'plugin-one/index.ts' with packageId 'typescript-fsa/index.d.ts@3.0.0-beta-2'
175+
File redirects to file 'plugin-two/node_modules/typescript-fsa/index.d.ts'
173176
plugin-one/index.ts
174177
Matched by default include pattern '**/*'
175178
//// [/home/src/tslibs/TS/Lib/lib.d.ts] *Lib*

testdata/baselines/reference/tsc/declarationEmit/when-same-version-is-referenced-through-source-and-another-symlinked-package.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,9 @@ plugin-one/node_modules/typescript-fsa/index.d.ts
157157
Imported via "typescript-fsa" from file 'plugin-one/action.ts' with packageId 'typescript-fsa/index.d.ts@3.0.0-beta-2'
158158
plugin-one/action.ts
159159
Matched by default include pattern '**/*'
160+
plugin-two/node_modules/typescript-fsa/index.d.ts
161+
Imported via "typescript-fsa" from file 'plugin-two/index.d.ts' with packageId 'typescript-fsa/index.d.ts@3.0.0-beta-2'
162+
File redirects to file 'plugin-one/node_modules/typescript-fsa/index.d.ts'
160163
plugin-two/index.d.ts
161164
Imported via "plugin-two" from file 'plugin-one/index.ts'
162165
plugin-one/index.ts

0 commit comments

Comments
 (0)