Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
146 changes: 105 additions & 41 deletions 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 @@ -71,7 +72,7 @@ func getBaselineFileName(t *testing.T, command baselineCommand) string {

func getBaselineExtension(command baselineCommand) string {
switch command {
case quickInfoCmd, signatureHelpCmd, smartSelectionCmd, inlayHintsCmd, nonSuggestionDiagnosticsCmd:
case quickInfoCmd, signatureHelpCmd, smartSelectionCmd, inlayHintsCmd, nonSuggestionDiagnosticsCmd, documentSymbolsCmd:
return "baseline"
case callHierarchyCmd:
return "callHierarchy.txt"
Expand Down Expand Up @@ -472,11 +473,49 @@ func (f *FourslashTest) textOfFile(fileName string) (string, bool) {
return f.vfs.ReadFile(fileName)
}

type detailKind int

const (
detailKindMarker detailKind = iota // /*MARKER*/
detailKindContextStart // <|
detailKindTextStart // [|
detailKindTextEnd // |]
detailKindContextEnd // |>
)

func (k detailKind) isEnd() bool {
return k == detailKindContextEnd || k == detailKindTextEnd
}

func (k detailKind) isStart() bool {
return k == detailKindContextStart || k == detailKindTextStart
}

type baselineDetail struct {
pos lsproto.Position
positionMarker string
span *documentSpan
kind string
kind detailKind
}

func (d *baselineDetail) getRange() lsproto.Range {
switch d.kind {
case detailKindContextStart:
return *d.span.contextSpan
case detailKindContextEnd:
return *d.span.contextSpan
case detailKindTextStart:
return d.span.textSpan
case detailKindTextEnd:
return d.span.textSpan
case detailKindMarker:
return lsproto.Range{
Start: d.pos,
End: d.pos,
}
default:
panic("unknown detail kind")
}
}

func (f *FourslashTest) getBaselineContentForFile(
Expand Down Expand Up @@ -504,7 +543,7 @@ func (f *FourslashTest) getBaselineContentForFile(
pos: span.contextSpan.Start,
positionMarker: "<|",
span: &span,
kind: "contextStart",
kind: detailKindContextStart,
})

// Check if context span starts after text span
Expand All @@ -519,16 +558,16 @@ func (f *FourslashTest) getBaselineContentForFile(
startMarker += options.getLocationData(span)
}
details = append(details,
&baselineDetail{pos: span.textSpan.Start, positionMarker: startMarker, span: &span, kind: "textStart"},
&baselineDetail{pos: span.textSpan.End, positionMarker: core.OrElse(options.endMarker, "|]"), span: &span, kind: "textEnd"},
&baselineDetail{pos: span.textSpan.Start, positionMarker: startMarker, span: &span, kind: detailKindTextStart},
&baselineDetail{pos: span.textSpan.End, positionMarker: core.OrElse(options.endMarker, "|]"), span: &span, kind: detailKindTextEnd},
)

if span.contextSpan != nil {
details = append(details, &baselineDetail{
pos: span.contextSpan.End,
positionMarker: "|>",
span: &span,
kind: "contextEnd",
kind: detailKindContextEnd,
})
}

Expand Down Expand Up @@ -566,37 +605,69 @@ func (f *FourslashTest) getBaselineContentForFile(
}
}

slices.SortStableFunc(details, func(d1, d2 *baselineDetail) int {
return lsproto.ComparePositions(d1.pos, d2.pos)
})
// !!! if canDetermineContextIdInline

textWithContext := newTextWithContext(fileName, content)

// Our preferred way to write marker is
// Our preferred way to write markers is
// /*MARKER*/[| some text |]
// [| some /*MARKER*/ text |]
// [| some text |]/*MARKER*/
// Stable sort should handle first two cases but with that marker will be before rangeEnd if locations match
// So we will defer writing marker in this case by checking and finding index of rangeEnd if same
var deferredMarkerIndex *int
slices.SortStableFunc(details, func(d1, d2 *baselineDetail) int {
c := lsproto.ComparePositions(d1.pos, d2.pos)
if c != 0 || d1.kind == detailKindMarker && d2.kind == detailKindMarker {
return c
}

for index, detail := range details {
if detail.span == nil && deferredMarkerIndex == nil {
// If this is marker position and its same as textEnd and/or contextEnd we want to write marker after those
for matchingEndPosIndex := index + 1; matchingEndPosIndex < len(details); matchingEndPosIndex++ {
// Defer after the location if its same as rangeEnd
if details[matchingEndPosIndex].pos == detail.pos && strings.HasSuffix(details[matchingEndPosIndex].kind, "End") {
deferredMarkerIndex = ptrTo(matchingEndPosIndex)
}
// Dont defer further than already determined
break
// /*MARKER*/[| some text |]
if d1.kind == detailKindMarker && d2.kind.isStart() {
return -1
}
if d2.kind == detailKindMarker && d1.kind.isStart() {
return 1
}

// [| some text |]/*MARKER*/
if d1.kind == detailKindMarker && d2.kind.isEnd() {
return 1
}
if d2.kind == detailKindMarker && d1.kind.isEnd() {
return -1
}

// [||] or <||>
if d1.span == d2.span {
return int(d1.kind - d2.kind)
}

// ...|><|...
if d1.kind.isStart() && d2.kind.isEnd() {
return 1
}
if d1.kind.isEnd() && d2.kind.isStart() {
return -1
}

// <| ... [| ... |]|>
if d1.kind.isEnd() && d2.kind.isEnd() {
c := lsproto.ComparePositions(d2.getRange().Start, d1.getRange().Start)
if c != 0 {
return c
}
// Defer writing marker position to deffered marker index
if deferredMarkerIndex != nil {
continue
return int(d1.kind - d2.kind)
}

// <|[| ... |] ... |>
if d1.kind.isStart() && d2.kind.isStart() {
c := lsproto.ComparePositions(d2.getRange().End, d2.getRange().End)
if c != 0 {
return c
}
return int(d1.kind - d2.kind)
}

return 0
})
// !!! if canDetermineContextIdInline

textWithContext := newTextWithContext(fileName, content)
for index, detail := range details {
textWithContext.add(detail)
textWithContext.pos = detail.pos
// Prefix
Expand All @@ -607,13 +678,13 @@ func (f *FourslashTest) getBaselineContentForFile(
textWithContext.newContent.WriteString(detail.positionMarker)
if detail.span != nil {
switch detail.kind {
case "textStart":
case detailKindTextStart:
var text string
if contextId, ok := spanToContextId[*detail.span]; ok {
isAfterContextStart := false
for textStartIndex := index - 1; textStartIndex >= 0; textStartIndex-- {
textStartDetail := details[textStartIndex]
if textStartDetail.kind == "contextStart" && textStartDetail.span == detail.span {
if textStartDetail.kind == detailKindContextStart && textStartDetail.span == detail.span {
isAfterContextStart = true
break
}
Expand All @@ -634,18 +705,11 @@ func (f *FourslashTest) getBaselineContentForFile(
if text != "" {
textWithContext.newContent.WriteString(`{ ` + text + ` |}`)
}
case "contextStart":
case detailKindContextStart:
if canDetermineContextIdInline {
spanToContextId[*detail.span] = len(spanToContextId)
}
}

if deferredMarkerIndex != nil && *deferredMarkerIndex == index {
// Write the marker
textWithContext.newContent.WriteString(options.markerName)
deferredMarkerIndex = nil
detail = details[0] // Marker detail
}
}
if suffix, ok := detailSuffixes[detail]; ok {
textWithContext.newContent.WriteString(suffix)
Expand Down Expand Up @@ -714,7 +778,7 @@ func (t *textWithContext) add(detail *baselineDetail) {
if t.content == "" && detail == nil {
panic("Unsupported")
}
if detail == nil || (detail.kind != "textEnd" && detail.kind != "contextEnd") {
if detail == nil || (detail.kind != detailKindTextEnd && detail.kind != detailKindContextEnd) {
// Calculate pos to location number of lines
posLineIndex := t.lineInfo
if t.posInfo == nil || *t.posInfo != t.pos {
Expand Down
Loading