Skip to content

Commit a94299b

Browse files
authored
Fix three snapshots bugs (#1633)
1 parent 7376eb2 commit a94299b

File tree

6 files changed

+135
-12
lines changed

6 files changed

+135
-12
lines changed

internal/project/configfilechanges_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,4 +153,34 @@ func TestConfigFileChanges(t *testing.T) {
153153
defer release()
154154
assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 1)
155155
})
156+
157+
t.Run("should update project when missing extended config is created", func(t *testing.T) {
158+
t.Parallel()
159+
// Start with a project whose tsconfig extends a base config that doesn't exist yet
160+
missingBaseFiles := map[string]any{}
161+
for k, v := range files {
162+
if k == "/tsconfig.base.json" {
163+
continue
164+
}
165+
missingBaseFiles[k] = v
166+
}
167+
168+
session, utils := projecttestutil.Setup(missingBaseFiles)
169+
session.DidOpenFile(context.Background(), "file:///src/index.ts", 1, missingBaseFiles["/src/index.ts"].(string), lsproto.LanguageKindTypeScript)
170+
171+
// Create the previously-missing base config file that is extended by /src/tsconfig.json
172+
err := utils.FS().WriteFile("/tsconfig.base.json", `{"compilerOptions": {"strict": true}}`, false /*writeByteOrderMark*/)
173+
assert.NilError(t, err)
174+
session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{
175+
{
176+
Uri: lsproto.DocumentUri("file:///tsconfig.base.json"),
177+
Type: lsproto.FileChangeTypeCreated,
178+
},
179+
})
180+
181+
// Accessing the language service should trigger project update
182+
ls, err := session.GetLanguageService(context.Background(), lsproto.DocumentUri("file:///src/index.ts"))
183+
assert.NilError(t, err)
184+
assert.Equal(t, ls.GetProgram().Options().Strict, core.TSTrue)
185+
})
156186
}

internal/project/extendedconfigcache.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,14 @@ type extendedConfigCacheEntry struct {
2323
func (c *extendedConfigCache) Acquire(fh FileHandle, path tspath.Path, parse func() *tsoptions.ExtendedConfigCacheEntry) *tsoptions.ExtendedConfigCacheEntry {
2424
entry, loaded := c.loadOrStoreNewLockedEntry(path)
2525
defer entry.mu.Unlock()
26-
if !loaded || entry.hash != fh.Hash() {
26+
var hash xxh3.Uint128
27+
if fh != nil {
28+
hash = fh.Hash()
29+
}
30+
if !loaded || entry.hash != hash {
2731
// Reparse the config if the hash has changed, or parse for the first time.
2832
entry.entry = parse()
29-
entry.hash = fh.Hash()
33+
entry.hash = hash
3034
}
3135
return entry.entry
3236
}

internal/project/project_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,48 @@ func TestProjectProgramUpdateKind(t *testing.T) {
130130
assert.Equal(t, configured.ProgramUpdateKind, project.ProgramUpdateKindSameFileNames)
131131
})
132132
}
133+
134+
func TestProject(t *testing.T) {
135+
t.Parallel()
136+
if !bundled.Embedded {
137+
t.Skip("bundled files are not embedded")
138+
}
139+
140+
t.Run("commandLineWithTypingsFiles is reset on CommandLine change", func(t *testing.T) {
141+
t.Parallel()
142+
files := map[string]any{
143+
"/user/username/projects/project1/app.js": ``,
144+
"/user/username/projects/project1/package.json": `{"name":"p1","dependencies":{"jquery":"^3.1.0"}}`,
145+
"/user/username/projects/project2/app.js": ``,
146+
}
147+
148+
session, utils := projecttestutil.SetupWithTypingsInstaller(files, &projecttestutil.TypingsInstallerOptions{
149+
PackageToFile: map[string]string{
150+
// Provide typings content to be installed for jquery so ATA actually installs something
151+
"jquery": `declare const $: { x: number }`,
152+
},
153+
})
154+
155+
// 1) Open an inferred project file that triggers ATA
156+
uri1 := lsproto.DocumentUri("file:///user/username/projects/project1/app.js")
157+
session.DidOpenFile(context.Background(), uri1, 1, files["/user/username/projects/project1/app.js"].(string), lsproto.LanguageKindJavaScript)
158+
159+
// 2) Wait for ATA/background tasks to finish, then get a language service for the first file
160+
session.WaitForBackgroundTasks()
161+
// Sanity check: ensure ATA performed at least one install
162+
npmCalls := utils.NpmExecutor().NpmInstallCalls()
163+
assert.Assert(t, len(npmCalls) > 0, "expected at least one npm install call from ATA")
164+
_, err := session.GetLanguageService(context.Background(), uri1)
165+
assert.NilError(t, err)
166+
167+
// 3) Open another inferred project file
168+
uri2 := lsproto.DocumentUri("file:///user/username/projects/project2/app.js")
169+
session.DidOpenFile(context.Background(), uri2, 1, ``, lsproto.LanguageKindJavaScript)
170+
171+
// 4) Get a language service for the second file
172+
// If commandLineWithTypingsFiles was not reset, the new program command line
173+
// won't include the newly opened file and this will fail.
174+
_, err = session.GetLanguageService(context.Background(), uri2)
175+
assert.NilError(t, err)
176+
})
177+
}

internal/project/projectcollectionbuilder.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,7 @@ func (b *projectCollectionBuilder) findOrCreateDefaultConfiguredProjectWorker(
515515
configFilePath := b.toPath(node.configFileName)
516516
config := b.configFileRegistryBuilder.findOrAcquireConfigForOpenFile(node.configFileName, configFilePath, path, node.loadKind, node.logger.Fork("Acquiring config for open file"))
517517
if config == nil {
518+
node.logger.Log("Config file for project does not already exist")
518519
return false, false
519520
}
520521
configs.Store(configFilePath, config)
@@ -535,6 +536,11 @@ func (b *projectCollectionBuilder) findOrCreateDefaultConfiguredProjectWorker(
535536
}
536537

537538
project := b.findOrCreateProject(node.configFileName, configFilePath, node.loadKind, node.logger)
539+
if project == nil {
540+
node.logger.Log("Project does not already exist")
541+
return false, false
542+
}
543+
538544
if node.loadKind == projectLoadKindCreate {
539545
// Ensure project is up to date before checking for file inclusion
540546
b.updateProgram(project, node.logger)
@@ -720,6 +726,7 @@ func (b *projectCollectionBuilder) updateInferredProjectRoots(rootFileNames []st
720726
logger.Log(fmt.Sprintf("Updating inferred project config with %d root files", len(rootFileNames)))
721727
}
722728
p.CommandLine = newCommandLine
729+
p.commandLineWithTypingsFiles = nil
723730
p.dirty = true
724731
p.dirtyFilePath = ""
725732
},
@@ -753,7 +760,10 @@ func (b *projectCollectionBuilder) updateProgram(entry dirty.Value[*Project], lo
753760
filesChanged = true
754761
return
755762
}
756-
entry.Change(func(p *Project) { p.CommandLine = commandLine })
763+
entry.Change(func(p *Project) {
764+
p.CommandLine = commandLine
765+
p.commandLineWithTypingsFiles = nil
766+
})
757767
}
758768
}
759769
if !updateProgram {

internal/project/projectcollectionbuilder_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,38 @@ func TestProjectCollectionBuilder(t *testing.T) {
407407
assert.Assert(t, snapshot.ConfigFileRegistry.GetConfig("/home/src/projects/project/tsconfig.json") == nil)
408408
assert.Assert(t, snapshot.ConfigFileRegistry.GetConfig("/home/src/projects/project/tsconfig.node.json") == nil)
409409
})
410+
411+
t.Run("#1630", func(t *testing.T) {
412+
t.Parallel()
413+
files := map[string]any{
414+
"/project/lib/tsconfig.json": `{
415+
"files": ["a.ts"]
416+
}`,
417+
"/project/lib/a.ts": `export const a = 1;`,
418+
"/project/lib/b.ts": `export const b = 1;`,
419+
"/project/tsconfig.json": `{
420+
"files": [],
421+
"references": [{ "path": "./lib" }],
422+
"compilerOptions": {
423+
"disableReferencedProjectLoad": true
424+
}
425+
}`,
426+
"/project/index.ts": ``,
427+
}
428+
429+
session, _ := projecttestutil.Setup(files)
430+
431+
// opening b.ts puts /project/lib/tsconfig.json in the config file registry and creates the project,
432+
// but the project is ultimately not a match
433+
session.DidOpenFile(context.Background(), "file:///project/lib/b.ts", 1, files["/project/lib/b.ts"].(string), lsproto.LanguageKindTypeScript)
434+
// opening an unrelated file triggers cleanup of /project/lib/tsconfig.json since no open file is part of that project,
435+
// but will keep the config file in the registry since lib/b.ts is still open
436+
session.DidOpenFile(context.Background(), "untitled:Untitled-1", 1, "", lsproto.LanguageKindTypeScript)
437+
// Opening index.ts searches /project/tsconfig.json and then checks /project/lib/tsconfig.json without opening it.
438+
// No early return on config file existence means we try to find an already open project, which returns nil,
439+
// triggering a crash.
440+
session.DidOpenFile(context.Background(), "file:///project/index.ts", 1, files["/project/index.ts"].(string), lsproto.LanguageKindTypeScript)
441+
})
410442
}
411443

412444
func filesForSolutionConfigFile(solutionRefs []string, compilerOptions string, ownFiles []string) map[string]any {

internal/tsoptions/tsconfigparsing.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -961,20 +961,22 @@ func getExtendedConfig(
961961
cacheEntry = parse()
962962
}
963963

964-
if cacheEntry != nil && len(cacheEntry.errors) > 0 {
964+
if len(cacheEntry.errors) > 0 {
965965
errors = append(errors, cacheEntry.errors...)
966966
}
967967

968-
if sourceFile != nil {
969-
result.extendedSourceFiles.Add(cacheEntry.extendedResult.SourceFile.FileName())
970-
for _, extendedSourceFile := range cacheEntry.extendedResult.ExtendedSourceFiles {
971-
result.extendedSourceFiles.Add(extendedSourceFile)
968+
if cacheEntry.extendedResult != nil {
969+
if sourceFile != nil {
970+
result.extendedSourceFiles.Add(cacheEntry.extendedResult.SourceFile.FileName())
971+
for _, extendedSourceFile := range cacheEntry.extendedResult.ExtendedSourceFiles {
972+
result.extendedSourceFiles.Add(extendedSourceFile)
973+
}
972974
}
973-
}
974975

975-
if len(cacheEntry.extendedResult.SourceFile.Diagnostics()) != 0 {
976-
errors = append(errors, cacheEntry.extendedResult.SourceFile.Diagnostics()...)
977-
return nil, errors
976+
if len(cacheEntry.extendedResult.SourceFile.Diagnostics()) != 0 {
977+
errors = append(errors, cacheEntry.extendedResult.SourceFile.Diagnostics()...)
978+
return nil, errors
979+
}
978980
}
979981
return cacheEntry.extendedConfig, errors
980982
}

0 commit comments

Comments
 (0)