Skip to content

Commit 7d91cac

Browse files
authored
Port many more fourslash functions (#2339)
1 parent 59a089d commit 7d91cac

File tree

290 files changed

+11613
-5
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

290 files changed

+11613
-5
lines changed

internal/fourslash/_scripts/convertFourslash.mts

Lines changed: 249 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,22 @@ function parseFourslashStatement(statement: ts.Statement): Cmd[] | undefined {
249249
return parseVerifyNavTree(callExpression.arguments);
250250
case "navigationBar":
251251
return []; // Deprecated.
252+
case "numberOfErrorsInCurrentFile":
253+
return parseNumberOfErrorsInCurrentFile(callExpression.arguments);
254+
case "noErrors":
255+
return [{ kind: "verifyNoErrors" }];
256+
case "errorExistsAtRange":
257+
return parseErrorExistsAtRange(callExpression.arguments);
258+
case "currentLineContentIs":
259+
return parseCurrentLineContentIs(callExpression.arguments);
260+
case "currentFileContentIs":
261+
return parseCurrentFileContentIs(callExpression.arguments);
262+
case "errorExistsBetweenMarkers":
263+
return parseErrorExistsBetweenMarkers(callExpression.arguments);
264+
case "errorExistsAfterMarker":
265+
return parseErrorExistsAfterMarker(callExpression.arguments);
266+
case "errorExistsBeforeMarker":
267+
return parseErrorExistsBeforeMarker(callExpression.arguments);
252268
}
253269
}
254270
// `goTo....`
@@ -313,6 +329,32 @@ function parseEditStatement(funcName: string, args: readonly ts.Expression[]): E
313329
goStatement: `f.Backspace(t, 1)`,
314330
};
315331
}
332+
case "deleteAtCaret": {
333+
const arg = args[0];
334+
if (arg) {
335+
let arg0;
336+
if (arg0 = getNumericLiteral(arg)) {
337+
return {
338+
kind: "edit",
339+
goStatement: `f.DeleteAtCaret(t, ${arg0.text})`,
340+
};
341+
}
342+
// Handle 'string'.length expressions
343+
const lengthValue = getStringLengthExpression(arg);
344+
if (lengthValue !== undefined) {
345+
return {
346+
kind: "edit",
347+
goStatement: `f.DeleteAtCaret(t, ${lengthValue})`,
348+
};
349+
}
350+
console.error(`Expected numeric literal argument in edit.deleteAtCaret, got ${arg.getText()}`);
351+
return undefined;
352+
}
353+
return {
354+
kind: "edit",
355+
goStatement: `f.DeleteAtCaret(t, 1)`,
356+
};
357+
}
316358
default:
317359
console.error(`Unrecognized edit function: ${funcName}`);
318360
return undefined;
@@ -1446,6 +1488,130 @@ function parseExpectedDiagnostic(expr: ts.Expression): string | undefined {
14461488
return `&lsproto.Diagnostic{\n${diagnosticProps.join("\n")}\n}`;
14471489
}
14481490

1491+
function parseNumberOfErrorsInCurrentFile(args: readonly ts.Expression[]): [VerifyNumberOfErrorsInCurrentFileCmd] | undefined {
1492+
let arg0;
1493+
if (args.length !== 1 || !(arg0 = getNumericLiteral(args[0]))) {
1494+
console.error(`Expected a single numeric literal argument in verify.numberOfErrorsInCurrentFile, got ${args.map(arg => arg.getText()).join(", ")}`);
1495+
return undefined;
1496+
}
1497+
return [{
1498+
kind: "verifyNumberOfErrorsInCurrentFile",
1499+
expectedCount: parseInt(arg0.text, 10),
1500+
}];
1501+
}
1502+
1503+
function parseErrorExistsAtRange(args: readonly ts.Expression[]): [VerifyErrorExistsAtRangeCmd] | undefined {
1504+
if (args.length < 2 || args.length > 3) {
1505+
console.error(`Expected 2 or 3 arguments in verify.errorExistsAtRange, got ${args.length}`);
1506+
return undefined;
1507+
}
1508+
1509+
// First arg is a range
1510+
const rangeArg = parseBaselineMarkerOrRangeArg(args[0]);
1511+
if (!rangeArg) {
1512+
console.error(`Expected range argument in verify.errorExistsAtRange, got ${args[0].getText()}`);
1513+
return undefined;
1514+
}
1515+
1516+
// Second arg is error code
1517+
let codeArg;
1518+
if (!(codeArg = getNumericLiteral(args[1]))) {
1519+
console.error(`Expected numeric literal for code in verify.errorExistsAtRange, got ${args[1].getText()}`);
1520+
return undefined;
1521+
}
1522+
1523+
// Third arg is optional message
1524+
let message = "";
1525+
if (args[2]) {
1526+
const messageArg = getStringLiteralLike(args[2]);
1527+
if (!messageArg) {
1528+
console.error(`Expected string literal for message in verify.errorExistsAtRange, got ${args[2].getText()}`);
1529+
return undefined;
1530+
}
1531+
message = messageArg.text;
1532+
}
1533+
1534+
return [{
1535+
kind: "verifyErrorExistsAtRange",
1536+
range: rangeArg,
1537+
code: parseInt(codeArg.text, 10),
1538+
message: message,
1539+
}];
1540+
}
1541+
1542+
function parseCurrentLineContentIs(args: readonly ts.Expression[]): [VerifyCurrentLineContentIsCmd] | undefined {
1543+
let arg0;
1544+
if (args.length !== 1 || !(arg0 = getStringLiteralLike(args[0]))) {
1545+
console.error(`Expected a single string literal argument in verify.currentLineContentIs, got ${args.map(arg => arg.getText()).join(", ")}`);
1546+
return undefined;
1547+
}
1548+
return [{
1549+
kind: "verifyCurrentLineContentIs",
1550+
text: arg0.text,
1551+
}];
1552+
}
1553+
1554+
function parseCurrentFileContentIs(args: readonly ts.Expression[]): [VerifyCurrentFileContentIsCmd] | undefined {
1555+
let arg0;
1556+
if (args.length !== 1 || !(arg0 = getStringLiteralLike(args[0]))) {
1557+
console.error(`Expected a single string literal argument in verify.currentFileContentIs, got ${args.map(arg => arg.getText()).join(", ")}`);
1558+
return undefined;
1559+
}
1560+
return [{
1561+
kind: "verifyCurrentFileContentIs",
1562+
text: arg0.text,
1563+
}];
1564+
}
1565+
1566+
function parseErrorExistsBetweenMarkers(args: readonly ts.Expression[]): [VerifyErrorExistsBetweenMarkersCmd] | undefined {
1567+
if (args.length !== 2) {
1568+
console.error(`Expected 2 arguments in verify.errorExistsBetweenMarkers, got ${args.length}`);
1569+
return undefined;
1570+
}
1571+
let startMarker, endMarker;
1572+
if (!(startMarker = getStringLiteralLike(args[0])) || !(endMarker = getStringLiteralLike(args[1]))) {
1573+
console.error(`Expected string literal arguments in verify.errorExistsBetweenMarkers, got ${args.map(arg => arg.getText()).join(", ")}`);
1574+
return undefined;
1575+
}
1576+
return [{
1577+
kind: "verifyErrorExistsBetweenMarkers",
1578+
startMarker: startMarker.text,
1579+
endMarker: endMarker.text,
1580+
}];
1581+
}
1582+
1583+
function parseErrorExistsAfterMarker(args: readonly ts.Expression[]): [VerifyErrorExistsAfterMarkerCmd] | undefined {
1584+
let markerName = "";
1585+
if (args.length > 0) {
1586+
const arg0 = getStringLiteralLike(args[0]);
1587+
if (!arg0) {
1588+
console.error(`Expected string literal argument in verify.errorExistsAfterMarker, got ${args[0].getText()}`);
1589+
return undefined;
1590+
}
1591+
markerName = arg0.text;
1592+
}
1593+
return [{
1594+
kind: "verifyErrorExistsAfterMarker",
1595+
markerName: markerName,
1596+
}];
1597+
}
1598+
1599+
function parseErrorExistsBeforeMarker(args: readonly ts.Expression[]): [VerifyErrorExistsBeforeMarkerCmd] | undefined {
1600+
let markerName = "";
1601+
if (args.length > 0) {
1602+
const arg0 = getStringLiteralLike(args[0]);
1603+
if (!arg0) {
1604+
console.error(`Expected string literal argument in verify.errorExistsBeforeMarker, got ${args[0].getText()}`);
1605+
return undefined;
1606+
}
1607+
markerName = arg0.text;
1608+
}
1609+
return [{
1610+
kind: "verifyErrorExistsBeforeMarker",
1611+
markerName: markerName,
1612+
}];
1613+
}
1614+
14491615
function stringToTristate(s: string): string {
14501616
switch (s) {
14511617
case "true":
@@ -2689,6 +2855,48 @@ interface VerifyNavTreeCmd {
26892855
kind: "verifyNavigationTree";
26902856
}
26912857

2858+
interface VerifyNumberOfErrorsInCurrentFileCmd {
2859+
kind: "verifyNumberOfErrorsInCurrentFile";
2860+
expectedCount: number;
2861+
}
2862+
2863+
interface VerifyNoErrorsCmd {
2864+
kind: "verifyNoErrors";
2865+
}
2866+
2867+
interface VerifyErrorExistsAtRangeCmd {
2868+
kind: "verifyErrorExistsAtRange";
2869+
range: string;
2870+
code: number;
2871+
message: string;
2872+
}
2873+
2874+
interface VerifyCurrentLineContentIsCmd {
2875+
kind: "verifyCurrentLineContentIs";
2876+
text: string;
2877+
}
2878+
2879+
interface VerifyCurrentFileContentIsCmd {
2880+
kind: "verifyCurrentFileContentIs";
2881+
text: string;
2882+
}
2883+
2884+
interface VerifyErrorExistsBetweenMarkersCmd {
2885+
kind: "verifyErrorExistsBetweenMarkers";
2886+
startMarker: string;
2887+
endMarker: string;
2888+
}
2889+
2890+
interface VerifyErrorExistsAfterMarkerCmd {
2891+
kind: "verifyErrorExistsAfterMarker";
2892+
markerName: string;
2893+
}
2894+
2895+
interface VerifyErrorExistsBeforeMarkerCmd {
2896+
kind: "verifyErrorExistsBeforeMarker";
2897+
markerName: string;
2898+
}
2899+
26922900
type Cmd =
26932901
| VerifyCompletionsCmd
26942902
| VerifyApplyCodeActionFromCompletionCmd
@@ -2715,7 +2923,15 @@ type Cmd =
27152923
| VerifyImportFixModuleSpecifiersCmd
27162924
| VerifyDiagnosticsCmd
27172925
| VerifyBaselineDiagnosticsCmd
2718-
| VerifyOutliningSpansCmd;
2926+
| VerifyOutliningSpansCmd
2927+
| VerifyNumberOfErrorsInCurrentFileCmd
2928+
| VerifyNoErrorsCmd
2929+
| VerifyErrorExistsAtRangeCmd
2930+
| VerifyCurrentLineContentIsCmd
2931+
| VerifyCurrentFileContentIsCmd
2932+
| VerifyErrorExistsBetweenMarkersCmd
2933+
| VerifyErrorExistsAfterMarkerCmd
2934+
| VerifyErrorExistsBeforeMarkerCmd;
27192935

27202936
function generateVerifyOutliningSpans({ foldingRangeKind }: VerifyOutliningSpansCmd): string {
27212937
if (foldingRangeKind) {
@@ -3029,6 +3245,22 @@ function generateCmd(cmd: Cmd): string {
30293245
return generateVerifyOutliningSpans(cmd);
30303246
case "verifyNavigationTree":
30313247
return `f.VerifyBaselineDocumentSymbol(t)`;
3248+
case "verifyNumberOfErrorsInCurrentFile":
3249+
return `f.VerifyNumberOfErrorsInCurrentFile(t, ${cmd.expectedCount})`;
3250+
case "verifyNoErrors":
3251+
return `f.VerifyNoErrors(t)`;
3252+
case "verifyErrorExistsAtRange":
3253+
return `f.VerifyErrorExistsAtRange(t, ${cmd.range}, ${cmd.code}, ${getGoStringLiteral(cmd.message)})`;
3254+
case "verifyCurrentLineContentIs":
3255+
return `f.VerifyCurrentLineContentIs(t, ${getGoStringLiteral(cmd.text)})`;
3256+
case "verifyCurrentFileContentIs":
3257+
return `f.VerifyCurrentFileContentIs(t, ${getGoStringLiteral(cmd.text)})`;
3258+
case "verifyErrorExistsBetweenMarkers":
3259+
return `f.VerifyErrorExistsBetweenMarkers(t, ${getGoStringLiteral(cmd.startMarker)}, ${getGoStringLiteral(cmd.endMarker)})`;
3260+
case "verifyErrorExistsAfterMarker":
3261+
return `f.VerifyErrorExistsAfterMarker(t, ${getGoStringLiteral(cmd.markerName)})`;
3262+
case "verifyErrorExistsBeforeMarker":
3263+
return `f.VerifyErrorExistsBeforeMarker(t, ${getGoStringLiteral(cmd.markerName)})`;
30323264
default:
30333265
let neverCommand: never = cmd;
30343266
throw new Error(`Unknown command kind: ${neverCommand as Cmd["kind"]}`);
@@ -3047,16 +3279,17 @@ function generateGoTest(failingTests: Set<string>, test: GoTest, isServer: boole
30473279
const commands = test.commands.map(cmd => generateCmd(cmd)).join("\n");
30483280
const imports = [`"github.com/microsoft/typescript-go/internal/fourslash"`];
30493281
// Only include these imports if the commands use them to avoid unused import errors.
3050-
if (commands.includes("core.")) {
3282+
// Use regex with word boundary to avoid false positives like "underscore." matching "core."
3283+
if (/\bcore\./.test(commands)) {
30513284
imports.unshift(`"github.com/microsoft/typescript-go/internal/core"`);
30523285
}
3053-
if (commands.includes("ls.")) {
3286+
if (/\bls\./.test(commands)) {
30543287
imports.push(`"github.com/microsoft/typescript-go/internal/ls"`);
30553288
}
3056-
if (commands.includes("lsutil.")) {
3289+
if (/\blsutil\./.test(commands)) {
30573290
imports.push(`"github.com/microsoft/typescript-go/internal/ls/lsutil"`);
30583291
}
3059-
if (commands.includes("lsproto.")) {
3292+
if (/\blsproto\./.test(commands)) {
30603293
imports.push(`"github.com/microsoft/typescript-go/internal/lsp/lsproto"`);
30613294
}
30623295
if (usesFourslashUtil(commands)) {
@@ -3129,6 +3362,17 @@ function getArrayLiteralExpression(node: ts.Node): ts.ArrayLiteralExpression | u
31293362
return getNodeOfKind(node, ts.isArrayLiteralExpression);
31303363
}
31313364

3365+
// Parses expressions like 'string'.length or "string".length and returns the length value
3366+
function getStringLengthExpression(node: ts.Node): number | undefined {
3367+
if (ts.isPropertyAccessExpression(node) && node.name.text === "length") {
3368+
const stringLiteral = getStringLiteralLike(node.expression);
3369+
if (stringLiteral) {
3370+
return stringLiteral.text.length;
3371+
}
3372+
}
3373+
return undefined;
3374+
}
3375+
31323376
function getInitializer(name: ts.Identifier): ts.Expression | undefined {
31333377
const file = name.getSourceFile();
31343378
const varStmts = file.statements.filter(ts.isVariableStatement);

0 commit comments

Comments
 (0)