Skip to content

Commit bbaa0f5

Browse files
authored
Port non-baseline diagnostics tests (#2079)
1 parent 944083a commit bbaa0f5

36 files changed

+1143
-11
lines changed

internal/fourslash/_scripts/convertFourslash.mts

Lines changed: 115 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,10 @@ function parseFourslashStatement(statement: ts.Statement): Cmd[] | undefined {
213213
case "renameInfoSucceeded":
214214
case "renameInfoFailed":
215215
return parseRenameInfo(func.text, callExpression.arguments);
216+
case "getSemanticDiagnostics":
217+
case "getSuggestionDiagnostics":
218+
case "getSyntacticDiagnostics":
219+
return parseVerifyDiagnostics(func.text, callExpression.arguments);
216220
}
217221
}
218222
// `goTo....`
@@ -1260,6 +1264,105 @@ function parseBaselineInlayHints(args: readonly ts.Expression[]): [VerifyBaselin
12601264
}];
12611265
}
12621266

1267+
function parseVerifyDiagnostics(funcName: string, args: readonly ts.Expression[]): [VerifyDiagnosticsCmd] | undefined {
1268+
if (!args[0] || !ts.isArrayLiteralExpression(args[0])) {
1269+
console.error(`Expected an array literal argument in verify.${funcName}`);
1270+
return undefined;
1271+
}
1272+
const goArgs: string[] = [];
1273+
for (const expr of args[0].elements) {
1274+
const diag = parseExpectedDiagnostic(expr);
1275+
if (diag === undefined) {
1276+
return undefined;
1277+
}
1278+
goArgs.push(diag);
1279+
}
1280+
return [{
1281+
kind: "verifyDiagnostics",
1282+
arg: goArgs.length > 0 ? `[]*lsproto.Diagnostic{\n${goArgs.join(",\n")},\n}` : "nil",
1283+
isSuggestion: funcName === "getSuggestionDiagnostics",
1284+
}];
1285+
}
1286+
1287+
function parseExpectedDiagnostic(expr: ts.Expression): string | undefined {
1288+
if (!ts.isObjectLiteralExpression(expr)) {
1289+
console.error(`Expected object literal expression for expected diagnostic, got ${expr.getText()}`);
1290+
return undefined;
1291+
}
1292+
1293+
const diagnosticProps: string[] = [];
1294+
1295+
for (const prop of expr.properties) {
1296+
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) {
1297+
console.error(`Expected property assignment with identifier name for expected diagnostic, got ${prop.getText()}`);
1298+
return undefined;
1299+
}
1300+
1301+
const propName = prop.name.text;
1302+
const init = prop.initializer;
1303+
1304+
switch (propName) {
1305+
case "message": {
1306+
let messageInit;
1307+
if (messageInit = getStringLiteralLike(init)) {
1308+
messageInit.text = messageInit.text.replace("/tests/cases/fourslash", "");
1309+
diagnosticProps.push(`Message: ${getGoStringLiteral(messageInit.text)},`);
1310+
}
1311+
else {
1312+
console.error(`Expected string literal for diagnostic message, got ${init.getText()}`);
1313+
return undefined;
1314+
}
1315+
break;
1316+
}
1317+
case "code": {
1318+
let codeInit;
1319+
if (codeInit = getNumericLiteral(init)) {
1320+
diagnosticProps.push(`Code: &lsproto.IntegerOrString{Integer: PtrTo[int32](${codeInit.text})},`);
1321+
}
1322+
else {
1323+
console.error(`Expected numeric literal for diagnostic code, got ${init.getText()}`);
1324+
return undefined;
1325+
}
1326+
break;
1327+
}
1328+
case "range": {
1329+
// Handle range references like ranges[0]
1330+
const rangeArg = parseBaselineMarkerOrRangeArg(init);
1331+
if (rangeArg) {
1332+
diagnosticProps.push(`Range: ${rangeArg}.LSRange,`);
1333+
}
1334+
else {
1335+
console.error(`Expected range reference for diagnostic range, got ${init.getText()}`);
1336+
return undefined;
1337+
}
1338+
break;
1339+
}
1340+
case "reportsDeprecated": {
1341+
if (init.kind === ts.SyntaxKind.TrueKeyword) {
1342+
diagnosticProps.push(`Tags: &[]lsproto.DiagnosticTag{lsproto.DiagnosticTagDeprecated},`);
1343+
}
1344+
break;
1345+
}
1346+
case "reportsUnnecessary": {
1347+
if (init.kind === ts.SyntaxKind.TrueKeyword) {
1348+
diagnosticProps.push(`Tags: &[]lsproto.DiagnosticTag{lsproto.DiagnosticTagUnnecessary},`);
1349+
}
1350+
break;
1351+
}
1352+
default:
1353+
console.error(`Unrecognized property in expected diagnostic: ${propName}`);
1354+
return undefined;
1355+
}
1356+
}
1357+
1358+
if (diagnosticProps.length === 0) {
1359+
console.error(`No valid properties found in diagnostic object`);
1360+
return undefined;
1361+
}
1362+
1363+
return `&lsproto.Diagnostic{\n${diagnosticProps.join("\n")}\n}`;
1364+
}
1365+
12631366
function stringToTristate(s: string): string {
12641367
switch (s) {
12651368
case "true":
@@ -1395,7 +1498,7 @@ function parseBaselineMarkerOrRangeArg(arg: ts.Expression): string | undefined {
13951498
return result;
13961499
}
13971500
}
1398-
console.error(`Unrecognized argument in verify.baselineRename: ${arg.getText()}`);
1501+
console.error(`Unrecognized range argument: ${arg.getText()}`);
13991502
return undefined;
14001503
}
14011504

@@ -1716,12 +1819,6 @@ interface VerifyBaselineFindAllReferencesCmd {
17161819
ranges?: boolean;
17171820
}
17181821

1719-
interface VerifyBaselineFindAllReferencesCmd {
1720-
kind: "verifyBaselineFindAllReferences";
1721-
markers: string[];
1722-
ranges?: boolean;
1723-
}
1724-
17251822
interface VerifyBaselineGoToDefinitionCmd {
17261823
kind: "verifyBaselineGoToDefinition" | "verifyBaselineGoToType";
17271824
markers: string[];
@@ -1789,6 +1886,12 @@ interface VerifyRenameInfoCmd {
17891886
preferences: string;
17901887
}
17911888

1889+
interface VerifyDiagnosticsCmd {
1890+
kind: "verifyDiagnostics";
1891+
arg: string;
1892+
isSuggestion: boolean;
1893+
}
1894+
17921895
type Cmd =
17931896
| VerifyCompletionsCmd
17941897
| VerifyApplyCodeActionFromCompletionCmd
@@ -1804,7 +1907,8 @@ type Cmd =
18041907
| VerifyBaselineRenameCmd
18051908
| VerifyRenameInfoCmd
18061909
| VerifyBaselineInlayHintsCmd
1807-
| VerifyImportFixAtPositionCmd;
1910+
| VerifyImportFixAtPositionCmd
1911+
| VerifyDiagnosticsCmd;
18081912

18091913
function generateVerifyCompletions({ marker, args, isNewIdentifierLocation, andApplyCodeActionArgs }: VerifyCompletionsCmd): string {
18101914
let expectedList: string;
@@ -1953,6 +2057,9 @@ function generateCmd(cmd: Cmd): string {
19532057
return generateBaselineInlayHints(cmd);
19542058
case "verifyImportFixAtPosition":
19552059
return generateImportFixAtPosition(cmd);
2060+
case "verifyDiagnostics":
2061+
const funcName = cmd.isSuggestion ? "VerifySuggestionDiagnostics" : "VerifyNonSuggestionDiagnostics";
2062+
return `f.${funcName}(t, ${cmd.arg})`;
19562063
default:
19572064
let neverCommand: never = cmd;
19582065
throw new Error(`Unknown command kind: ${neverCommand as Cmd["kind"]}`);

internal/fourslash/_scripts/failingTests.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ TestAutoImportCrossProject_symlinks_toDist
1313
TestAutoImportCrossProject_symlinks_toSrc
1414
TestAutoImportFileExcludePatterns3
1515
TestAutoImportJsDocImport1
16+
TestAutoImportModuleNone1
1617
TestAutoImportNodeNextJSRequire
1718
TestAutoImportPathsAliasesAndBarrels
1819
TestAutoImportPnpm
@@ -328,6 +329,7 @@ TestInstanceTypesForGenericType1
328329
TestJavascriptModules20
329330
TestJavascriptModulesTypeImport
330331
TestJsDocAugments
332+
TestJsDocAugmentsAndExtends
331333
TestJsDocExtends
332334
TestJsDocFunctionSignatures10
333335
TestJsDocFunctionSignatures11

internal/fourslash/_scripts/manualTests.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ completionListInClosedFunction05
22
completionsAtIncompleteObjectLiteralProperty
33
completionsSelfDeclaring1
44
completionsWithDeprecatedTag4
5+
parserCorruptionAfterMapInClass
56
renameDefaultKeyword
67
renameForDefaultExport01
78
tsxCompletion12

internal/fourslash/fourslash.go

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,16 @@ func (f *FourslashTest) Ranges() []*RangeMarker {
556556
return f.testData.Ranges
557557
}
558558

559+
func (f *FourslashTest) getRangesInFile(fileName string) []*RangeMarker {
560+
var rangesInFile []*RangeMarker
561+
for _, rangeMarker := range f.testData.Ranges {
562+
if rangeMarker.FileName() == fileName {
563+
rangesInFile = append(rangesInFile, rangeMarker)
564+
}
565+
}
566+
return rangesInFile
567+
}
568+
559569
func (f *FourslashTest) ensureActiveFile(t *testing.T, filename string) {
560570
if f.activeFilename != filename {
561571
f.openFile(t, filename)
@@ -913,8 +923,9 @@ func ignorePaths(paths ...string) cmp.Option {
913923
}
914924

915925
var (
916-
completionIgnoreOpts = ignorePaths(".Kind", ".SortText", ".FilterText", ".Data")
917-
autoImportIgnoreOpts = ignorePaths(".Kind", ".SortText", ".FilterText", ".Data", ".LabelDetails", ".Detail", ".AdditionalTextEdits")
926+
completionIgnoreOpts = ignorePaths(".Kind", ".SortText", ".FilterText", ".Data")
927+
autoImportIgnoreOpts = ignorePaths(".Kind", ".SortText", ".FilterText", ".Data", ".LabelDetails", ".Detail", ".AdditionalTextEdits")
928+
diagnosticsIgnoreOpts = ignorePaths(".Severity", ".Source", ".RelatedInformation")
918929
)
919930

920931
func (f *FourslashTest) verifyCompletionItem(t *testing.T, prefix string, actual *lsproto.CompletionItem, expected *lsproto.CompletionItem) {
@@ -1815,7 +1826,12 @@ func (f *FourslashTest) ReplaceLine(t *testing.T, lineIndex int, text string) {
18151826
func (f *FourslashTest) selectLine(t *testing.T, lineIndex int) {
18161827
script := f.getScriptInfo(f.activeFilename)
18171828
start := script.lineMap.LineStarts[lineIndex]
1818-
end := script.lineMap.LineStarts[lineIndex+1] - 1
1829+
var end core.TextPos
1830+
if lineIndex+1 >= len(script.lineMap.LineStarts) {
1831+
end = core.TextPos(len(script.content))
1832+
} else {
1833+
end = script.lineMap.LineStarts[lineIndex+1] - 1
1834+
}
18191835
f.selectRange(t, core.NewTextRange(int(start), int(end)))
18201836
}
18211837

@@ -2474,6 +2490,65 @@ func (f *FourslashTest) VerifyBaselineInlayHints(
24742490
f.addResultToBaseline(t, "Inlay Hints", strings.Join(annotations, "\n\n"))
24752491
}
24762492

2493+
func (f *FourslashTest) VerifyDiagnostics(t *testing.T, expected []*lsproto.Diagnostic) {
2494+
f.verifyDiagnostics(t, expected, func(d *lsproto.Diagnostic) bool { return true })
2495+
}
2496+
2497+
// Similar to `VerifyDiagnostics`, but excludes suggestion diagnostics returned from server.
2498+
func (f *FourslashTest) VerifyNonSuggestionDiagnostics(t *testing.T, expected []*lsproto.Diagnostic) {
2499+
f.verifyDiagnostics(t, expected, func(d *lsproto.Diagnostic) bool { return !isSuggestionDiagnostic(d) })
2500+
}
2501+
2502+
// Similar to `VerifyDiagnostics`, but includes only suggestion diagnostics returned from server.
2503+
func (f *FourslashTest) VerifySuggestionDiagnostics(t *testing.T, expected []*lsproto.Diagnostic) {
2504+
f.verifyDiagnostics(t, expected, isSuggestionDiagnostic)
2505+
}
2506+
2507+
func (f *FourslashTest) verifyDiagnostics(t *testing.T, expected []*lsproto.Diagnostic, filterDiagnostics func(*lsproto.Diagnostic) bool) {
2508+
params := &lsproto.DocumentDiagnosticParams{
2509+
TextDocument: lsproto.TextDocumentIdentifier{
2510+
Uri: lsconv.FileNameToDocumentURI(f.activeFilename),
2511+
},
2512+
}
2513+
resMsg, result, resultOk := sendRequest(t, f, lsproto.TextDocumentDiagnosticInfo, params)
2514+
if resMsg == nil {
2515+
t.Fatal("Nil response received for diagnostics request")
2516+
}
2517+
if !resultOk {
2518+
t.Fatalf("Unexpected response type for diagnostics request: %T", resMsg.AsResponse().Result)
2519+
}
2520+
2521+
var actualDiagnostics []*lsproto.Diagnostic
2522+
if result.FullDocumentDiagnosticReport != nil {
2523+
actualDiagnostics = append(actualDiagnostics, result.FullDocumentDiagnosticReport.Items...)
2524+
}
2525+
actualDiagnostics = core.Filter(actualDiagnostics, filterDiagnostics)
2526+
emptyRange := lsproto.Range{}
2527+
expectedWithRanges := make([]*lsproto.Diagnostic, len(expected))
2528+
for i, diag := range expected {
2529+
if diag.Range == emptyRange {
2530+
rangesInFile := f.getRangesInFile(f.activeFilename)
2531+
if len(rangesInFile) == 0 {
2532+
t.Fatalf("No ranges found in file %s to assign to diagnostic with empty range", f.activeFilename)
2533+
}
2534+
diagWithRange := *diag
2535+
diagWithRange.Range = rangesInFile[0].LSRange
2536+
expectedWithRanges[i] = &diagWithRange
2537+
} else {
2538+
expectedWithRanges[i] = diag
2539+
}
2540+
}
2541+
if len(actualDiagnostics) == 0 && len(expectedWithRanges) == 0 {
2542+
return
2543+
}
2544+
assertDeepEqual(t, actualDiagnostics, expectedWithRanges, "Diagnostics do not match expected", diagnosticsIgnoreOpts)
2545+
}
2546+
2547+
func isSuggestionDiagnostic(diag *lsproto.Diagnostic) bool {
2548+
return diag.Tags != nil && len(*diag.Tags) > 0 ||
2549+
(diag.Severity != nil && *diag.Severity == lsproto.DiagnosticSeverityHint)
2550+
}
2551+
24772552
func isLibFile(fileName string) bool {
24782553
baseName := tspath.GetBaseFileName(fileName)
24792554
if strings.HasPrefix(baseName, "lib.") && strings.HasSuffix(baseName, ".d.ts") {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package fourslash_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/microsoft/typescript-go/internal/fourslash"
7+
"github.com/microsoft/typescript-go/internal/testutil"
8+
)
9+
10+
func TestAnnotateWithTypeFromJSDoc2(t *testing.T) {
11+
t.Parallel()
12+
13+
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
14+
const content = `// @Filename: test123.ts
15+
/** @type {number} */
16+
var [|x|]: string;`
17+
f := fourslash.NewFourslash(t, nil /*capabilities*/, content)
18+
f.VerifySuggestionDiagnostics(t, nil)
19+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package fourslash_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/microsoft/typescript-go/internal/fourslash"
7+
. "github.com/microsoft/typescript-go/internal/fourslash/tests/util"
8+
"github.com/microsoft/typescript-go/internal/testutil"
9+
)
10+
11+
func TestAutoImportModuleNone1(t *testing.T) {
12+
t.Parallel()
13+
t.Skip()
14+
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
15+
const content = `// @module: none
16+
// @moduleResolution: bundler
17+
// @target: es5
18+
// @Filename: /node_modules/dep/index.d.ts
19+
export const x: number;
20+
// @Filename: /index.ts
21+
x/**/`
22+
f := fourslash.NewFourslash(t, nil /*capabilities*/, content)
23+
f.VerifyCompletions(t, "", &fourslash.CompletionsExpectedList{
24+
IsIncomplete: false,
25+
ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{
26+
CommitCharacters: &DefaultCommitCharacters,
27+
EditRange: Ignored,
28+
},
29+
Items: &fourslash.CompletionsExpectedItems{
30+
Excludes: []string{
31+
"x",
32+
},
33+
},
34+
})
35+
f.ReplaceLine(t, 0, "import { x } from 'dep'; x;")
36+
f.VerifyNonSuggestionDiagnostics(t, nil)
37+
}

0 commit comments

Comments
 (0)