432 lines
12 KiB
Go
432 lines
12 KiB
Go
package fourslash_test
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/microsoft/typescript-go/internal/core"
|
|
"github.com/microsoft/typescript-go/internal/fourslash"
|
|
"github.com/microsoft/typescript-go/internal/testutil"
|
|
)
|
|
|
|
func TestDeclarationMapsOpeningOriginalLocationProject(t *testing.T) {
|
|
t.Parallel()
|
|
for _, disableSourceOfProjectReferenceRedirect := range []bool{false, true} {
|
|
t.Run("TestDeclarationMapsOpeningOriginalLocationProject"+core.IfElse(disableSourceOfProjectReferenceRedirect, "DisableSourceOfProjectReferenceRedirect", ""), func(t *testing.T) {
|
|
t.Parallel()
|
|
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
|
|
content := fmt.Sprintf(`
|
|
// @stateBaseline: true
|
|
// @Filename: a/a.ts
|
|
export class A { }
|
|
// @Filename: a/tsconfig.json
|
|
{}
|
|
// @Filename: a/a.d.ts
|
|
export declare class A {
|
|
}
|
|
//# sourceMappingURL=a.d.ts.map
|
|
// @Filename: a/a.d.ts.map
|
|
{
|
|
"version": 3,
|
|
"file": "a.d.ts",
|
|
"sourceRoot": "",
|
|
"sources": ["./a.ts"],
|
|
"names": [],
|
|
"mappings": "AAAA,qBAAa,CAAC;CAAI"
|
|
}
|
|
// @Filename: b/b.ts
|
|
import {A} from "../a/a";
|
|
new /*1*/A();
|
|
// @Filename: b/tsconfig.json
|
|
{
|
|
"compilerOptions": {
|
|
"disableSourceOfProjectReferenceRedirect": %t
|
|
},
|
|
"references": [
|
|
{ "path": "../a" }
|
|
]
|
|
}`, disableSourceOfProjectReferenceRedirect)
|
|
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
|
|
defer done()
|
|
f.VerifyBaselineFindAllReferences(t, "1")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDeclarationMapTestCasesForMaps(t *testing.T) {
|
|
t.Parallel()
|
|
type testCase struct {
|
|
name string
|
|
goToMarker string
|
|
opMarker string
|
|
}
|
|
tests := []testCase{
|
|
{"FindAllRefs", "userFnA", "userFnA"},
|
|
{"FindAllRefsStartingAtDefinition", "userFnA", "fnADef"},
|
|
{"FindAllRefsTargetDoesNotExist", "userFnB", "userFnB"},
|
|
{"Rename", "userFnA", "userFnA"},
|
|
{"RenameStartingAtDefinition", "userFnA", "fnADef"},
|
|
{"RenameTargetDoesNotExist", "userFnB", "userFnB"},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run("TestDeclarationMaps"+tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
|
|
content := `
|
|
// @stateBaseline: true
|
|
// @Filename: a/a.ts
|
|
export function /*fnADef*/fnA() {}
|
|
export interface IfaceA {}
|
|
export const instanceA: IfaceA = {};
|
|
// @Filename: a/tsconfig.json
|
|
{
|
|
"compilerOptions": {
|
|
"outDir": "bin",
|
|
"declarationMap": true,
|
|
"composite": true
|
|
}
|
|
}
|
|
// @Filename: a/bin/a.d.ts.map
|
|
{
|
|
"version": 3,
|
|
"file": "a.d.ts",
|
|
"sourceRoot": "",
|
|
"sources": ["../a.ts"],
|
|
"names": [],
|
|
"mappings": "AAAA,wBAAgB,GAAG,SAAK;AACxB,MAAM,WAAW,MAAM;CAAG;AAC1B,eAAO,MAAM,SAAS,EAAE,MAAW,CAAC"
|
|
}
|
|
// @Filename: a/bin/a.d.ts
|
|
export declare function fnA(): void;
|
|
export interface IfaceA {
|
|
}
|
|
export declare const instanceA: IfaceA;
|
|
//# sourceMappingURL=a.d.ts.map
|
|
// @Filename: b/tsconfig.json
|
|
{
|
|
"compilerOptions": {
|
|
"outDir": "bin",
|
|
"declarationMap": true,
|
|
"composite": true
|
|
}
|
|
}
|
|
// @Filename: b/bin/b.d.ts.map
|
|
{
|
|
"version": 3,
|
|
"file": "b.d.ts",
|
|
"sourceRoot": "",
|
|
"sources": ["../b.ts"],
|
|
"names": [],
|
|
"mappings": "AAAA,wBAAgB,GAAG,SAAK"
|
|
}
|
|
// @Filename: b/bin/b.d.ts
|
|
export declare function fnB(): void;
|
|
//# sourceMappingURL=b.d.ts.map
|
|
// @Filename: user/user.ts
|
|
import * as a from "../a/bin/a";
|
|
import * as b from "../b/bin/b";
|
|
export function fnUser() { a./*userFnA*/fnA(); b./*userFnB*/fnB(); a.instanceA; }
|
|
// @Filename: dummy/dummy.ts
|
|
/*dummy*/export const a = 10;
|
|
// @Filename: dummy/tsconfig.json
|
|
{}`
|
|
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
|
|
defer done()
|
|
f.GoToMarker(t, tc.goToMarker)
|
|
// Ref projects are loaded after as part of this command
|
|
if strings.HasPrefix(tc.name, "Rename") {
|
|
f.VerifyBaselineRename(t, nil /*preferences*/, tc.opMarker)
|
|
} else {
|
|
f.VerifyBaselineFindAllReferences(t, tc.opMarker)
|
|
}
|
|
// Open temp file and verify all projects alive
|
|
f.CloseFileOfMarker(t, tc.goToMarker)
|
|
f.GoToMarker(t, "dummy")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDeclarationMapsWorkspaceSymbols(t *testing.T) {
|
|
t.Parallel()
|
|
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
|
|
content := `// @stateBaseline: true
|
|
// @Filename: a/a.ts
|
|
export function fnA() {}
|
|
export interface IfaceA {}
|
|
export const instanceA: IfaceA = {};
|
|
// @Filename: a/tsconfig.json
|
|
{
|
|
"compilerOptions": {
|
|
"outDir": "bin",
|
|
"declarationMap": true,
|
|
"composite": true
|
|
}
|
|
}
|
|
// @Filename: a/bin/a.d.ts.map
|
|
{
|
|
"version": 3,
|
|
"file": "a.d.ts",
|
|
"sourceRoot": "",
|
|
"sources": ["../a.ts"],
|
|
"names": [],
|
|
"mappings": "AAAA,wBAAgB,GAAG,SAAK;AACxB,MAAM,WAAW,MAAM;CAAG;AAC1B,eAAO,MAAM,SAAS,EAAE,MAAW,CAAC"
|
|
}
|
|
// @Filename: a/bin/a.d.ts
|
|
export declare function fnA(): void;
|
|
export interface IfaceA {
|
|
}
|
|
export declare const instanceA: IfaceA;
|
|
//# sourceMappingURL=a.d.ts.map
|
|
// @Filename: b/b.ts
|
|
export function fnB() {}
|
|
// @Filename: b/c.ts
|
|
export function fnC() {}
|
|
// @Filename: b/tsconfig.json
|
|
{
|
|
"compilerOptions": {
|
|
"outDir": "bin",
|
|
"declarationMap": true,
|
|
"composite": true
|
|
}
|
|
}
|
|
// @Filename: b/bin/b.d.ts.map
|
|
{
|
|
"version": 3,
|
|
"file": "b.d.ts",
|
|
"sourceRoot": "",
|
|
"sources": ["../b.ts"],
|
|
"names": [],
|
|
"mappings": "AAAA,wBAAgB,GAAG,SAAK"
|
|
}
|
|
// @Filename: b/bin/b.d.ts
|
|
export declare function fnB(): void;
|
|
//# sourceMappingURL=b.d.ts.map
|
|
// @Filename: user/user.ts
|
|
/*user*/import * as a from "../a/a";
|
|
import * as b from "../b/b";
|
|
export function fnUser() {
|
|
a.fnA();
|
|
b.fnB();
|
|
a.instanceA;
|
|
}
|
|
// @Filename: user/tsconfig.json
|
|
{
|
|
"references": [
|
|
{ "path": "../a" },
|
|
{ "path": "../b" }
|
|
]
|
|
}
|
|
// @Filename: dummy/dummy.ts
|
|
/*dummy*/export const a = 10;
|
|
// @Filename: dummy/tsconfig.json
|
|
{}`
|
|
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
|
|
defer done()
|
|
f.GoToMarker(t, "user")
|
|
// Ref projects are loaded after as part of this command
|
|
f.VerifyBaselineWorkspaceSymbol(t, "fn")
|
|
// Open temp file and verify all projects alive
|
|
f.CloseFileOfMarker(t, "user")
|
|
f.GoToMarker(t, "dummy")
|
|
}
|
|
|
|
func TestDeclarationMapsFindAllRefsDefinitionInMappedFile(t *testing.T) {
|
|
t.Parallel()
|
|
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
|
|
content := `
|
|
// @stateBaseline: true
|
|
//@Filename: a/a.ts
|
|
export function f() {}
|
|
// @Filename: a/tsconfig.json
|
|
{
|
|
"compilerOptions": {
|
|
"outDir": "../bin",
|
|
"declarationMap": true,
|
|
"composite": true
|
|
}
|
|
}
|
|
//@Filename: b/b.ts
|
|
import { f } from "../bin/a";
|
|
/*1*/f();
|
|
// @Filename: b/tsconfig.json
|
|
{
|
|
"references": [
|
|
{ "path": "../a" }
|
|
]
|
|
}
|
|
// @Filename: bin/a.d.ts
|
|
export declare function f(): void;
|
|
//# sourceMappingURL=a.d.ts.map
|
|
// @Filename: bin/a.d.ts.map
|
|
{
|
|
"version":3,
|
|
"file":"a.d.ts",
|
|
"sourceRoot":"",
|
|
"sources":["a.ts"],
|
|
"names":[],
|
|
"mappings":"AAAA,wBAAgB,CAAC,SAAK"
|
|
}`
|
|
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
|
|
defer done()
|
|
f.VerifyBaselineFindAllReferences(t, "1")
|
|
}
|
|
|
|
func TestDeclarationMapsRename(t *testing.T) {
|
|
t.Parallel()
|
|
type testCase struct {
|
|
name string
|
|
dontBuild bool
|
|
mainWithNoRef bool
|
|
disableSourceOfProjectReferenceRedirect bool
|
|
tsconfigNotSolution bool
|
|
}
|
|
for _, tc := range []testCase{
|
|
{name: "ProjectReferences", dontBuild: true},
|
|
{name: "DisableSourceOfProjectReferenceRedirect", disableSourceOfProjectReferenceRedirect: true},
|
|
{name: "SourceMaps", mainWithNoRef: true},
|
|
{name: "SourceMapsNotSolution", mainWithNoRef: true, tsconfigNotSolution: true},
|
|
} {
|
|
buildStr := core.IfElse(!tc.dontBuild, "// @tsc: --build /myproject/dependency,--build /myproject/main", "")
|
|
mainRefsStr := core.IfElse(!tc.mainWithNoRef, `"references": [{ "path": "../dependency" }]`, "")
|
|
filesStr := core.IfElse(!tc.tsconfigNotSolution, `"files": [],`, "")
|
|
content := fmt.Sprintf(`
|
|
// @stateBaseline: true
|
|
%s
|
|
//@Filename: myproject/dependency/FnS.ts
|
|
/*firstLine*/export function fn1() { }
|
|
export function fn2() { }
|
|
export function /*rename*/fn3() { }
|
|
export function fn4() { }
|
|
export function fn5() { }
|
|
/*lastLine*/
|
|
// @Filename: myproject/dependency/tsconfig.json
|
|
{
|
|
"compilerOptions": {
|
|
"composite": true,
|
|
"declarationMap": true,
|
|
"declarationDir": "../decls"
|
|
}
|
|
}
|
|
//@Filename: myproject/main/main.ts
|
|
import {
|
|
fn1,
|
|
fn2,
|
|
fn3,
|
|
fn4,
|
|
fn5
|
|
} from "../decls/FnS";
|
|
|
|
fn1();
|
|
fn2();
|
|
fn3();
|
|
fn4();
|
|
fn5();
|
|
// @Filename: myproject/main/tsconfig.json
|
|
{
|
|
"compilerOptions": {
|
|
"composite": true,
|
|
"declarationMap": true,
|
|
"disableSourceOfProjectReferenceRedirect": %t
|
|
},
|
|
%s
|
|
}
|
|
// @Filename: myproject/tsconfig.json
|
|
{
|
|
"compilerOptions": {
|
|
"disableSourceOfProjectReferenceRedirect": %t
|
|
},
|
|
%s
|
|
"references": [
|
|
{ "path": "dependency" },
|
|
{ "path": "main" }
|
|
]
|
|
}
|
|
// @Filename: random/random.ts
|
|
/*dummy*/export const a = 10;
|
|
// @Filename: random/tsconfig.json
|
|
{}`, buildStr, tc.disableSourceOfProjectReferenceRedirect, mainRefsStr, tc.disableSourceOfProjectReferenceRedirect, filesStr)
|
|
t.Run("TestDeclarationMapsRenameWith"+tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
|
|
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
|
|
defer done()
|
|
f.GoToMarker(t, "dummy")
|
|
// Ref projects are loaded after as part of this command
|
|
f.VerifyBaselineRename(t, nil /*preferences*/, "rename")
|
|
// Collecting at this point retains dependency.d.ts and map
|
|
f.CloseFileOfMarker(t, "dummy")
|
|
f.GoToMarker(t, "dummy")
|
|
// Closing open file, removes dependencies too
|
|
f.CloseFileOfMarker(t, "rename")
|
|
f.CloseFileOfMarker(t, "dummy")
|
|
f.GoToMarker(t, "dummy")
|
|
})
|
|
t.Run("TestDeclarationMapsRenameWith"+tc.name+"Edit", func(t *testing.T) {
|
|
t.Parallel()
|
|
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
|
|
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
|
|
defer done()
|
|
// Ref projects are loaded after as part of this command
|
|
f.VerifyBaselineRename(t, nil /*preferences*/, "rename")
|
|
f.GoToMarker(t, "firstLine")
|
|
f.Insert(t, "function fooBar() { }\n")
|
|
f.VerifyBaselineRename(t, nil /*preferences*/, "rename")
|
|
})
|
|
t.Run("TestDeclarationMapsRenameWith"+tc.name+"EditEnd", func(t *testing.T) {
|
|
t.Parallel()
|
|
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
|
|
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
|
|
defer done()
|
|
// Ref projects are loaded after as part of this command
|
|
f.VerifyBaselineRename(t, nil /*preferences*/, "rename")
|
|
f.GoToMarker(t, "lastLine")
|
|
f.Insert(t, "const x = 10;")
|
|
f.VerifyBaselineRename(t, nil /*preferences*/, "rename")
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestDeclarationMapsNonMonotonicMappings verifies that getMappedLocation clamps
|
|
// inverted ranges caused by non-monotonic source map mappings.
|
|
//
|
|
// The baseline comparison catches regressions: without the fix, the output shows
|
|
// inverted markers (e.g., "|]|>" appearing before "<|"), while with the fix,
|
|
// the ranges are clamped to valid zero-length ranges (e.g., "<||>").
|
|
func TestDeclarationMapsNonMonotonicMappings(t *testing.T) {
|
|
t.Parallel()
|
|
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
|
|
// The source map creates a non-monotonic mapping:
|
|
// - .d.ts line 0 col 24 ('b' identifier) -> source line 1, col 16
|
|
// - .d.ts line 0 col 25 (right after 'b') -> source line 0, col 0 (EARLIER!)
|
|
//
|
|
// When looking up 'b' identifier [24, 25), start maps to ~byte 39,
|
|
// but end maps to byte 0, creating an inverted range.
|
|
// The fix in getMappedLocation clamps this to prevent negative ranges.
|
|
const content = `
|
|
// @Filename: /src/index.ts
|
|
export function a() {}
|
|
export function b() {}
|
|
// @Filename: /src/indexdef.d.ts.map
|
|
{
|
|
"version": 3,
|
|
"file": "indexdef.d.ts",
|
|
"sourceRoot": "",
|
|
"sources": ["index.ts"],
|
|
"names": [],
|
|
"mappings": "AACA,wBAAgB,CADhB;AAAA,wBAAgB"
|
|
}
|
|
// @Filename: /src/indexdef.d.ts
|
|
export declare function b(): void;
|
|
export declare function a(): void;
|
|
//# sourceMappingURL=indexdef.d.ts.map
|
|
// @Filename: /src/user.ts
|
|
import { a, b } from "./indexdef";
|
|
/*1*/a();
|
|
/*2*/b();
|
|
// @Filename: /src/tsconfig.json
|
|
{}`
|
|
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
|
|
defer done()
|
|
f.VerifyBaselineGoToDefinition(t, true, "1", "2")
|
|
}
|