Skip to content
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions internal/fourslash/_scripts/convertFourslash.mts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ function parseFileContent(filename: string, content: string): GoTest | undefined
goTest.commands.push(...result);
}
}
if (goTest.commands.length === 0) {
console.error(`No commands parsed in file: ${filename}`);
unparsedFiles.push(filename);
return undefined;
}
return goTest;
}

Expand Down Expand Up @@ -237,6 +242,10 @@ function parseFourslashStatement(statement: ts.Statement): Cmd[] | undefined {
case "outliningSpansInCurrentFile":
case "outliningHintSpansInCurrentFile":
return parseOutliningSpansArgs(callExpression.arguments);
case "navigationTree":
return parseVerifyNavTree(callExpression.arguments);
case "navigationBar":
return []; // Deprecated.
}
}
// `goTo....`
Expand Down Expand Up @@ -2273,6 +2282,13 @@ function parseVerifyNavigateToArg(arg: ts.Expression): string | undefined {
}`;
}

function parseVerifyNavTree(args: readonly ts.Expression[]): [VerifyNavTreeCmd] | undefined {
// Ignore arguments and use baseline tests intead.
return [{
kind: "verifyNavigationTree",
}];
}

function parseNavToItem(arg: ts.Expression): string | undefined {
let item = getNodeOfKind(arg, ts.isObjectLiteralExpression);
if (!item) {
Expand Down Expand Up @@ -2348,11 +2364,15 @@ function getSymbolKind(kind: ts.Expression): string | undefined {
console.error(`Expected string literal for symbol kind, got ${kind.getText()}`);
return undefined;
}
switch (result.text) {
return getSymbolKindWorker(result.text);
}

function getSymbolKindWorker(kind: string): string {
switch (kind) {
case "script":
return "SymbolKindFile";
case "module":
return "SymbolKindModule";
return "SymbolKindNamespace";
case "class":
case "local class":
return "SymbolKindClass";
Expand Down Expand Up @@ -2400,6 +2420,8 @@ function getSymbolKind(kind: ts.Expression): string | undefined {
return "SymbolKindModule";
case "string":
return "SymbolKindString";
case "type":
return "SymbolKindClass";
default:
return "SymbolKindVariable";
}
Expand Down Expand Up @@ -2567,6 +2589,10 @@ interface VerifyOutliningSpansCmd {
foldingRangeKind?: string;
}

interface VerifyNavTreeCmd {
kind: "verifyNavigationTree";
}

type Cmd =
| VerifyCompletionsCmd
| VerifyApplyCodeActionFromCompletionCmd
Expand All @@ -2587,6 +2613,7 @@ type Cmd =
| VerifyBaselineRenameCmd
| VerifyRenameInfoCmd
| VerifyNavToCmd
| VerifyNavTreeCmd
| VerifyBaselineInlayHintsCmd
| VerifyImportFixAtPositionCmd
| VerifyDiagnosticsCmd
Expand Down Expand Up @@ -2894,6 +2921,8 @@ function generateCmd(cmd: Cmd): string {
return generateNoSignatureHelpForTriggerReason(cmd);
case "verifyOutliningSpans":
return generateVerifyOutliningSpans(cmd);
case "verifyNavigationTree":
return `f.VerifyBaselineDocumentSymbol(t)`;
default:
let neverCommand: never = cmd;
throw new Error(`Unknown command kind: ${neverCommand as Cmd["kind"]}`);
Expand Down
8 changes: 8 additions & 0 deletions internal/fourslash/_scripts/manualTests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ completionListInClosedFunction05
completionsAtIncompleteObjectLiteralProperty
completionsSelfDeclaring1
completionsWithDeprecatedTag4
navigationBarFunctionPrototype
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of those tests had .js files and needed // @allowJs: true to be added. Strada didn't need this because document symbol did not need to obtain the file from a program, it just obtained the file from a syntax cache (?). In Corsa, we try to get the requested file from the program, so these tests crashed. One thing I've been wanting to do is to use the same inferred project default settings for fourslash, but this would break a few import code fix tests, so I didn't do it in this PR.

navigationBarFunctionPrototype2
navigationBarFunctionPrototype3
navigationBarFunctionPrototype4
navigationBarFunctionPrototypeBroken
navigationBarFunctionPrototypeInterlaced
navigationBarFunctionPrototypeNested
navigationBarJsDoc
navigationItemsExactMatch2
navigationItemsSpecialPropertyAssignment
navto_excludeLib1
Expand Down
3 changes: 2 additions & 1 deletion internal/fourslash/baselineutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const (
signatureHelpCmd baselineCommand = "SignatureHelp"
smartSelectionCmd baselineCommand = "Smart Selection"
codeLensesCmd baselineCommand = "Code Lenses"
documentSymbolsCmd baselineCommand = "Document Symbols"
)

type baselineCommand string
Expand Down Expand Up @@ -75,7 +76,7 @@ func getBaselineExtension(command baselineCommand) string {
return "baseline"
case callHierarchyCmd:
return "callHierarchy.txt"
case autoImportsCmd:
case autoImportsCmd, documentSymbolsCmd:
return "baseline.md"
default:
return "baseline.jsonc"
Expand Down
61 changes: 50 additions & 11 deletions internal/fourslash/fourslash.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"unicode/utf8"

"github.com/go-json-experiment/json"
"github.com/go-json-experiment/json/jsontext"
"github.com/google/go-cmp/cmp"
"github.com/microsoft/typescript-go/internal/bundled"
"github.com/microsoft/typescript-go/internal/collections"
Expand Down Expand Up @@ -167,6 +168,7 @@ func NewFourslash(t *testing.T, capabilities *lsproto.ClientCapabilities, conten
testfs[filePath] = vfstest.Symlink(tspath.GetNormalizedAbsolutePath(target, rootDir))
}

// !!! use default compiler options for inferred project as base
compilerOptions := &core.CompilerOptions{
SkipDefaultLibCheck: core.TSTrue,
}
Expand Down Expand Up @@ -479,6 +481,19 @@ var (
defaultHoverCapabilities = &lsproto.HoverClientCapabilities{
ContentFormat: &[]lsproto.MarkupKind{lsproto.MarkupKindMarkdown, lsproto.MarkupKindPlainText},
}
defaultSignatureHelpCapabilities = &lsproto.SignatureHelpClientCapabilities{
SignatureInformation: &lsproto.ClientSignatureInformationOptions{
DocumentationFormat: &[]lsproto.MarkupKind{lsproto.MarkupKindMarkdown, lsproto.MarkupKindPlainText},
ParameterInformation: &lsproto.ClientSignatureParameterInformationOptions{
LabelOffsetSupport: ptrTrue,
},
ActiveParameterSupport: ptrTrue,
},
ContextSupport: ptrTrue,
}
defaultDocumentSymbolCapabilities = &lsproto.DocumentSymbolClientCapabilities{
HierarchicalDocumentSymbolSupport: ptrTrue,
}
)

func getCapabilitiesWithDefaults(capabilities *lsproto.ClientCapabilities) *lsproto.ClientCapabilities {
Expand Down Expand Up @@ -536,16 +551,10 @@ func getCapabilitiesWithDefaults(capabilities *lsproto.ClientCapabilities) *lspr
capabilitiesWithDefaults.TextDocument.Hover = defaultHoverCapabilities
}
if capabilitiesWithDefaults.TextDocument.SignatureHelp == nil {
capabilitiesWithDefaults.TextDocument.SignatureHelp = &lsproto.SignatureHelpClientCapabilities{
SignatureInformation: &lsproto.ClientSignatureInformationOptions{
DocumentationFormat: &[]lsproto.MarkupKind{lsproto.MarkupKindMarkdown, lsproto.MarkupKindPlainText},
ParameterInformation: &lsproto.ClientSignatureParameterInformationOptions{
LabelOffsetSupport: ptrTrue,
},
ActiveParameterSupport: ptrTrue,
},
ContextSupport: ptrTrue,
}
capabilitiesWithDefaults.TextDocument.SignatureHelp = defaultSignatureHelpCapabilities
}
if capabilitiesWithDefaults.TextDocument.DocumentSymbol == nil {
capabilitiesWithDefaults.TextDocument.DocumentSymbol = defaultDocumentSymbolCapabilities
}
return &capabilitiesWithDefaults
}
Expand Down Expand Up @@ -2989,7 +2998,7 @@ func (f *FourslashTest) getCurrentPositionPrefix() string {
if f.lastKnownMarkerName != nil {
return fmt.Sprintf("At marker '%s': ", *f.lastKnownMarkerName)
}
return fmt.Sprintf("At position (Ln %d, Col %d): ", f.currentCaretPosition.Line, f.currentCaretPosition.Character)
return fmt.Sprintf("At position %s(Ln %d, Col %d): ", f.activeFilename, f.currentCaretPosition.Line, f.currentCaretPosition.Character)
}

func (f *FourslashTest) BaselineAutoImportsCompletions(t *testing.T, markerNames []string) {
Expand Down Expand Up @@ -3665,3 +3674,33 @@ func verifyIncludesSymbols(
assertDeepEqual(t, actualSym, sym, fmt.Sprintf("%s: Symbol '%s' at location '%v' mismatch", prefix, sym.Name, sym.Location))
}
}

var marshalSymbolKind = func(v lsproto.SymbolKind) ([]byte, error) {
return []byte(`"` + v.String() + `"`), nil
}

func (f *FourslashTest) VerifyBaselineDocumentSymbol(t *testing.T) {
params := &lsproto.DocumentSymbolParams{
TextDocument: lsproto.TextDocumentIdentifier{
Uri: lsconv.FileNameToDocumentURI(f.activeFilename),
},
}
result := sendRequest(t, f, lsproto.TextDocumentDocumentSymbolInfo, params)
var content strings.Builder
ext := strings.TrimPrefix(tspath.GetAnyExtensionFromPath(f.activeFilename, nil, true), ".")
lang := core.IfElse(ext == "mts" || ext == "cts", "ts", ext)
content.WriteString(codeFence(lang, "// @FileName: "+f.activeFilename+"\n"+f.scriptInfos[f.activeFilename].content))
content.WriteString("\n\n# Symbols\n\n")
content.WriteString("```json\n")
err := json.MarshalWrite(
&content,
result.DocumentSymbols,
jsontext.Multiline(true),
json.WithMarshalers(json.MarshalFunc(marshalSymbolKind)),
)
if err != nil {
t.Fatalf("Failed to marshal document symbols for baseline: %v", err)
}
content.WriteString("\n```")
f.addResultToBaseline(t, documentSymbolsCmd, content.String())
}
35 changes: 35 additions & 0 deletions internal/fourslash/tests/documentSymbolPrivateName_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/typescript-go/internal/fourslash"
"github.com/microsoft/typescript-go/internal/testutil"
)

func TestDocumentSymbolPrivateName(t *testing.T) {
t.Parallel()

defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @Filename: first.ts
class A {
#foo: () => {
class B {
#bar: () => {
function baz () {
}
}
}
}
}
// @Filename: second.ts
class Foo {
#privateMethod() {}
}
`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.VerifyBaselineDocumentSymbol(t)
f.GoToFile(t, "second.ts")
f.VerifyBaselineDocumentSymbol(t)
}
21 changes: 21 additions & 0 deletions internal/fourslash/tests/gen/getNavigationBarItems_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/typescript-go/internal/fourslash"
"github.com/microsoft/typescript-go/internal/testutil"
)

func TestGetNavigationBarItems(t *testing.T) {
t.Parallel()

defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `class C {
foo;
["bar"]: string;
}`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.VerifyBaselineDocumentSymbol(t)
}
27 changes: 27 additions & 0 deletions internal/fourslash/tests/gen/jsdocTypedefTagNavigateTo_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/typescript-go/internal/fourslash"
"github.com/microsoft/typescript-go/internal/testutil"
)

func TestJsdocTypedefTagNavigateTo(t *testing.T) {
t.Parallel()

defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @allowNonTsExtensions: true
// @Filename: jsDocTypedef_form2.js

/** @typedef {(string | number)} NumberLike */
/** @typedef {(string | number | string[])} */
var NumberLike2;

/** @type {/*1*/NumberLike} */
var numberLike;`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.MarkTestAsStradaServer()
f.VerifyBaselineDocumentSymbol(t)
}
55 changes: 55 additions & 0 deletions internal/fourslash/tests/gen/navbar01_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/typescript-go/internal/fourslash"
"github.com/microsoft/typescript-go/internal/testutil"
)

func TestNavbar01(t *testing.T) {
t.Parallel()

defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// Interface
interface IPoint {
getDist(): number;
new(): IPoint;
(): any;
[x:string]: number;
prop: string;
}

/// Module
module Shapes {
// Class
export class Point implements IPoint {
constructor (public x: number, public y: number) { }

// Instance member
getDist() { return Math.sqrt(this.x * this.x + this.y * this.y); }

// Getter
get value(): number { return 0; }

// Setter
set value(newValue: number) { return; }

// Static member
static origin = new Point(0, 0);

// Static method
private static getOrigin() { return Point.origin;}
}

enum Values { value1, value2, value3 }
}

// Local variables
var p: IPoint = new Shapes.Point(3, 4);
var dist = p.getDist();`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.MarkTestAsStradaServer()
f.VerifyBaselineDocumentSymbol(t)
}
20 changes: 20 additions & 0 deletions internal/fourslash/tests/gen/navbarNestedCommonJsExports_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/typescript-go/internal/fourslash"
"github.com/microsoft/typescript-go/internal/testutil"
)

func TestNavbarNestedCommonJsExports(t *testing.T) {
t.Parallel()

defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @allowJs: true
// @Filename: /a.js
exports.a = exports.b = exports.c = 0;`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.VerifyBaselineDocumentSymbol(t)
}
Loading
Loading