kittenipc/kitcom/internal/tsgo/project/projectreferencesprogram_test.go
2025-10-15 10:12:44 +03:00

405 lines
15 KiB
Go

package project_test
import (
"context"
"fmt"
"testing"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/bundled"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/project"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/testutil/projecttestutil"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs/vfstest"
"gotest.tools/v3/assert"
)
func TestProjectReferencesProgram(t *testing.T) {
t.Parallel()
if !bundled.Embedded {
t.Skip("bundled files are not embedded")
}
t.Run("program for referenced project", func(t *testing.T) {
t.Parallel()
files := filesForReferencedProjectProgram(false)
session, _ := projecttestutil.Setup(files)
snapshot, release := session.Snapshot()
defer release()
assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 0)
uri := lsproto.DocumentUri("file:///user/username/projects/myproject/main/main.ts")
session.DidOpenFile(context.Background(), uri, 1, files["/user/username/projects/myproject/main/main.ts"].(string), lsproto.LanguageKindTypeScript)
snapshot, release = session.Snapshot()
defer release()
assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 1)
projects := snapshot.ProjectCollection.Projects()
p := projects[0]
assert.Equal(t, p.Kind, project.KindConfigured)
file := p.Program.GetSourceFileByPath(tspath.Path("/user/username/projects/myproject/dependency/fns.ts"))
assert.Assert(t, file != nil)
dtsFile := p.Program.GetSourceFileByPath(tspath.Path("/user/username/projects/myproject/decls/fns.d.ts"))
assert.Assert(t, dtsFile == nil)
})
t.Run("program with disableSourceOfProjectReferenceRedirect", func(t *testing.T) {
t.Parallel()
files := filesForReferencedProjectProgram(true)
files["/user/username/projects/myproject/decls/fns.d.ts"] = `
export declare function fn1(): void;
export declare function fn2(): void;
export declare function fn3(): void;
export declare function fn4(): void;
export declare function fn5(): void;
`
session, _ := projecttestutil.Setup(files)
snapshot, release := session.Snapshot()
defer release()
assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 0)
uri := lsproto.DocumentUri("file:///user/username/projects/myproject/main/main.ts")
session.DidOpenFile(context.Background(), uri, 1, files["/user/username/projects/myproject/main/main.ts"].(string), lsproto.LanguageKindTypeScript)
snapshot, release = session.Snapshot()
defer release()
assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 1)
projects := snapshot.ProjectCollection.Projects()
p := projects[0]
assert.Equal(t, p.Kind, project.KindConfigured)
file := p.Program.GetSourceFileByPath(tspath.Path("/user/username/projects/myproject/dependency/fns.ts"))
assert.Assert(t, file == nil)
dtsFile := p.Program.GetSourceFileByPath(tspath.Path("/user/username/projects/myproject/decls/fns.d.ts"))
assert.Assert(t, dtsFile != nil)
})
t.Run("references through symlink with index and typings", func(t *testing.T) {
t.Parallel()
files, aTest, bFoo, bBar := filesForSymlinkReferences(false, "")
session, _ := projecttestutil.Setup(files)
snapshot, release := session.Snapshot()
defer release()
assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 0)
uri := lsproto.DocumentUri("file://" + aTest)
session.DidOpenFile(context.Background(), uri, 1, files[aTest].(string), lsproto.LanguageKindTypeScript)
snapshot, release = session.Snapshot()
defer release()
assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 1)
projects := snapshot.ProjectCollection.Projects()
p := projects[0]
assert.Equal(t, p.Kind, project.KindConfigured)
fooFile := p.Program.GetSourceFile(bFoo)
assert.Assert(t, fooFile != nil)
barFile := p.Program.GetSourceFile(bBar)
assert.Assert(t, barFile != nil)
})
t.Run("references through symlink with index and typings with preserveSymlinks", func(t *testing.T) {
t.Parallel()
files, aTest, bFoo, bBar := filesForSymlinkReferences(true, "")
session, _ := projecttestutil.Setup(files)
snapshot, release := session.Snapshot()
defer release()
assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 0)
uri := lsproto.DocumentUri("file://" + aTest)
session.DidOpenFile(context.Background(), uri, 1, files[aTest].(string), lsproto.LanguageKindTypeScript)
snapshot, release = session.Snapshot()
defer release()
assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 1)
projects := snapshot.ProjectCollection.Projects()
p := projects[0]
assert.Equal(t, p.Kind, project.KindConfigured)
fooFile := p.Program.GetSourceFile(bFoo)
assert.Assert(t, fooFile != nil)
barFile := p.Program.GetSourceFile(bBar)
assert.Assert(t, barFile != nil)
})
t.Run("references through symlink with index and typings scoped package", func(t *testing.T) {
t.Parallel()
files, aTest, bFoo, bBar := filesForSymlinkReferences(false, "@issue/")
session, _ := projecttestutil.Setup(files)
snapshot, release := session.Snapshot()
defer release()
assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 0)
uri := lsproto.DocumentUri("file://" + aTest)
session.DidOpenFile(context.Background(), uri, 1, files[aTest].(string), lsproto.LanguageKindTypeScript)
snapshot, release = session.Snapshot()
defer release()
assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 1)
projects := snapshot.ProjectCollection.Projects()
p := projects[0]
assert.Equal(t, p.Kind, project.KindConfigured)
fooFile := p.Program.GetSourceFile(bFoo)
assert.Assert(t, fooFile != nil)
barFile := p.Program.GetSourceFile(bBar)
assert.Assert(t, barFile != nil)
})
t.Run("references through symlink with index and typings with scoped package preserveSymlinks", func(t *testing.T) {
t.Parallel()
files, aTest, bFoo, bBar := filesForSymlinkReferences(true, "@issue/")
session, _ := projecttestutil.Setup(files)
snapshot, release := session.Snapshot()
defer release()
assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 0)
uri := lsproto.DocumentUri("file://" + aTest)
session.DidOpenFile(context.Background(), uri, 1, files[aTest].(string), lsproto.LanguageKindTypeScript)
snapshot, release = session.Snapshot()
defer release()
assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 1)
projects := snapshot.ProjectCollection.Projects()
p := projects[0]
assert.Equal(t, p.Kind, project.KindConfigured)
fooFile := p.Program.GetSourceFile(bFoo)
assert.Assert(t, fooFile != nil)
barFile := p.Program.GetSourceFile(bBar)
assert.Assert(t, barFile != nil)
})
t.Run("references through symlink referencing from subFolder", func(t *testing.T) {
t.Parallel()
files, aTest, bFoo, bBar := filesForSymlinkReferencesInSubfolder(false, "")
session, _ := projecttestutil.Setup(files)
snapshot, release := session.Snapshot()
defer release()
assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 0)
uri := lsproto.DocumentUri("file://" + aTest)
session.DidOpenFile(context.Background(), uri, 1, files[aTest].(string), lsproto.LanguageKindTypeScript)
snapshot, release = session.Snapshot()
defer release()
assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 1)
projects := snapshot.ProjectCollection.Projects()
p := projects[0]
assert.Equal(t, p.Kind, project.KindConfigured)
fooFile := p.Program.GetSourceFile(bFoo)
assert.Assert(t, fooFile != nil)
barFile := p.Program.GetSourceFile(bBar)
assert.Assert(t, barFile != nil)
})
t.Run("references through symlink referencing from subFolder with preserveSymlinks", func(t *testing.T) {
t.Parallel()
files, aTest, bFoo, bBar := filesForSymlinkReferencesInSubfolder(true, "")
session, _ := projecttestutil.Setup(files)
snapshot, release := session.Snapshot()
defer release()
assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 0)
uri := lsproto.DocumentUri("file://" + aTest)
session.DidOpenFile(context.Background(), uri, 1, files[aTest].(string), lsproto.LanguageKindTypeScript)
snapshot, release = session.Snapshot()
defer release()
assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 1)
projects := snapshot.ProjectCollection.Projects()
p := projects[0]
assert.Equal(t, p.Kind, project.KindConfigured)
fooFile := p.Program.GetSourceFile(bFoo)
assert.Assert(t, fooFile != nil)
barFile := p.Program.GetSourceFile(bBar)
assert.Assert(t, barFile != nil)
})
t.Run("references through symlink referencing from subFolder scoped package", func(t *testing.T) {
t.Parallel()
files, aTest, bFoo, bBar := filesForSymlinkReferencesInSubfolder(false, "@issue/")
session, _ := projecttestutil.Setup(files)
snapshot, release := session.Snapshot()
defer release()
assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 0)
uri := lsproto.DocumentUri("file://" + aTest)
session.DidOpenFile(context.Background(), uri, 1, files[aTest].(string), lsproto.LanguageKindTypeScript)
snapshot, release = session.Snapshot()
defer release()
assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 1)
projects := snapshot.ProjectCollection.Projects()
p := projects[0]
assert.Equal(t, p.Kind, project.KindConfigured)
fooFile := p.Program.GetSourceFile(bFoo)
assert.Assert(t, fooFile != nil)
barFile := p.Program.GetSourceFile(bBar)
assert.Assert(t, barFile != nil)
})
t.Run("references through symlink referencing from subFolder with scoped package preserveSymlinks", func(t *testing.T) {
t.Parallel()
files, aTest, bFoo, bBar := filesForSymlinkReferencesInSubfolder(true, "@issue/")
session, _ := projecttestutil.Setup(files)
snapshot, release := session.Snapshot()
defer release()
assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 0)
uri := lsproto.DocumentUri("file://" + aTest)
session.DidOpenFile(context.Background(), uri, 1, files[aTest].(string), lsproto.LanguageKindTypeScript)
snapshot, release = session.Snapshot()
defer release()
assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 1)
projects := snapshot.ProjectCollection.Projects()
p := projects[0]
assert.Equal(t, p.Kind, project.KindConfigured)
fooFile := p.Program.GetSourceFile(bFoo)
assert.Assert(t, fooFile != nil)
barFile := p.Program.GetSourceFile(bBar)
assert.Assert(t, barFile != nil)
})
t.Run("when new file is added to referenced project", func(t *testing.T) {
t.Parallel()
files := filesForReferencedProjectProgram(false)
session, utils := projecttestutil.Setup(files)
uri := lsproto.DocumentUri("file:///user/username/projects/myproject/main/main.ts")
session.DidOpenFile(context.Background(), uri, 1, files["/user/username/projects/myproject/main/main.ts"].(string), lsproto.LanguageKindTypeScript)
snapshot, release := session.Snapshot()
defer release()
assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 1)
programBefore := snapshot.ProjectCollection.Projects()[0].Program
err := utils.FS().WriteFile("/user/username/projects/myproject/dependency/fns2.ts", `export const x = 2;`, false)
assert.NilError(t, err)
session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{
{
Type: lsproto.FileChangeTypeCreated,
Uri: "file:///user/username/projects/myproject/dependency/fns2.ts",
},
})
_, err = session.GetLanguageService(context.Background(), uri)
assert.NilError(t, err)
snapshot, release = session.Snapshot()
defer release()
assert.Equal(t, len(snapshot.ProjectCollection.Projects()), 1)
assert.Check(t, snapshot.ProjectCollection.Projects()[0].Program != programBefore)
})
}
func filesForReferencedProjectProgram(disableSourceOfProjectReferenceRedirect bool) map[string]any {
return map[string]any{
"/user/username/projects/myproject/main/tsconfig.json": fmt.Sprintf(`{
"compilerOptions": {
"composite": true%s
},
"references": [{ "path": "../dependency" }]
}`, core.IfElse(disableSourceOfProjectReferenceRedirect, `, "disableSourceOfProjectReferenceRedirect": true`, "")),
"/user/username/projects/myproject/main/main.ts": `
import {
fn1,
fn2,
fn3,
fn4,
fn5
} from '../decls/fns'
fn1();
fn2();
fn3();
fn4();
fn5();
`,
"/user/username/projects/myproject/dependency/tsconfig.json": `{
"compilerOptions": {
"composite": true,
"declarationDir": "../decls"
},
}`,
"/user/username/projects/myproject/dependency/fns.ts": `
export function fn1() { }
export function fn2() { }
export function fn3() { }
export function fn4() { }
export function fn5() { }
`,
}
}
func filesForSymlinkReferences(preserveSymlinks bool, scope string) (files map[string]any, aTest string, bFoo string, bBar string) {
aTest = "/user/username/projects/myproject/packages/A/src/index.ts"
bFoo = "/user/username/projects/myproject/packages/B/src/index.ts"
bBar = "/user/username/projects/myproject/packages/B/src/bar.ts"
files = map[string]any{
"/user/username/projects/myproject/packages/B/package.json": `{
"main": "lib/index.js",
"types": "lib/index.d.ts"
}`,
aTest: fmt.Sprintf(`
import { foo } from '%sb';
import { bar } from '%sb/lib/bar';
foo();
bar();
`, scope, scope),
bFoo: `export function foo() { }`,
bBar: `export function bar() { }`,
fmt.Sprintf(`/user/username/projects/myproject/node_modules/%sb`, scope): vfstest.Symlink("/user/username/projects/myproject/packages/B"),
}
addConfigForPackage(files, "A", preserveSymlinks, []string{"../B"})
addConfigForPackage(files, "B", preserveSymlinks, nil)
return files, aTest, bFoo, bBar
}
func filesForSymlinkReferencesInSubfolder(preserveSymlinks bool, scope string) (files map[string]any, aTest string, bFoo string, bBar string) {
aTest = "/user/username/projects/myproject/packages/A/src/test.ts"
bFoo = "/user/username/projects/myproject/packages/B/src/foo.ts"
bBar = "/user/username/projects/myproject/packages/B/src/bar/foo.ts"
files = map[string]any{
"/user/username/projects/myproject/packages/B/package.json": `{}`,
"/user/username/projects/myproject/packages/A/src/test.ts": fmt.Sprintf(`
import { foo } from '%sb/lib/foo';
import { bar } from '%sb/lib/bar/foo';
foo();
bar();
`, scope, scope),
bFoo: `export function foo() { }`,
bBar: `export function bar() { }`,
fmt.Sprintf(`/user/username/projects/myproject/node_modules/%sb`, scope): vfstest.Symlink("/user/username/projects/myproject/packages/B"),
}
addConfigForPackage(files, "A", preserveSymlinks, []string{"../B"})
addConfigForPackage(files, "B", preserveSymlinks, nil)
return files, aTest, bFoo, bBar
}
func addConfigForPackage(files map[string]any, packageName string, preserveSymlinks bool, references []string) {
compilerOptions := map[string]any{
"outDir": "lib",
"rootDir": "src",
"composite": true,
}
if preserveSymlinks {
compilerOptions["preserveSymlinks"] = true
}
var referencesToAdd []map[string]any
for _, ref := range references {
referencesToAdd = append(referencesToAdd, map[string]any{
"path": ref,
})
}
files[fmt.Sprintf("/user/username/projects/myproject/packages/%s/tsconfig.json", packageName)] = core.Must(core.StringifyJson(map[string]any{
"compilerOptions": compilerOptions,
"include": []string{"src"},
"references": referencesToAdd,
}, " ", " "))
}