Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
45dbee9
starting
jakebailey Oct 31, 2025
cf87afb
more
jakebailey Oct 31, 2025
b891674
more
jakebailey Oct 31, 2025
8dd9707
more
jakebailey Oct 31, 2025
8578677
more
jakebailey Oct 31, 2025
fdddc67
more
jakebailey Oct 31, 2025
744e631
more
jakebailey Oct 31, 2025
29dbc8b
more
jakebailey Oct 31, 2025
97d558a
more
jakebailey Oct 31, 2025
914c143
more
jakebailey Oct 31, 2025
4d8bdc1
testing
jakebailey Oct 31, 2025
39a68a3
more testing
jakebailey Oct 31, 2025
8a5d8ec
more testing
jakebailey Oct 31, 2025
dc2c8d3
local
jakebailey Oct 31, 2025
becb5f7
more
jakebailey Oct 31, 2025
8ccf80e
Fix bad commits
jakebailey Oct 31, 2025
eb9a47a
Update
jakebailey Oct 31, 2025
8e7168f
Commit test that the changes fixed
jakebailey Oct 31, 2025
07c09f3
Fix
jakebailey Oct 31, 2025
b1e875b
Fix
jakebailey Oct 31, 2025
02d247d
Fix bad test
jakebailey Oct 31, 2025
083f228
Add other tests containing semantic tokens
jakebailey Oct 31, 2025
c2ca82a
Simplifications
jakebailey Oct 31, 2025
6211ca3
Logic fix
jakebailey Oct 31, 2025
b286156
Doc update
jakebailey Oct 31, 2025
47c2722
Fix JSX issue and add a test for it
jakebailey Oct 31, 2025
a11189f
Address ordering check issue
jakebailey Oct 31, 2025
903dbfe
Panic on multi-line tokens
jakebailey Oct 31, 2025
7eff852
Reuse helper
jakebailey Oct 31, 2025
c4c6995
Merge branch 'main' into jabaile/semantic-tokens
jakebailey Oct 31, 2025
bf5fdec
Merge branch 'main' into jabaile/semantic-tokens
jakebailey Nov 6, 2025
b1c4362
Merge branch 'main' into jabaile/semantic-tokens
jakebailey Nov 10, 2025
50d3dd9
Merge branch 'main' into jabaile/semantic-tokens
jakebailey Nov 12, 2025
5c8e991
Merge branch 'main' into jabaile/semantic-tokens
jakebailey Nov 17, 2025
885d1ef
Merge branch 'main' into jabaile/semantic-tokens
jakebailey Nov 26, 2025
c1d105c
Merge branch 'main' into jabaile/semantic-tokens
jakebailey Dec 1, 2025
27b6301
Merge branch 'main' into jabaile/semantic-tokens
jakebailey Dec 2, 2025
eac5894
Merge branch 'main' into jabaile/semantic-tokens
jakebailey Dec 3, 2025
560025f
Merge branch 'main' into jabaile/semantic-tokens
jakebailey Dec 3, 2025
582194d
Merge branch 'main' into jabaile/semantic-tokens
jakebailey Dec 9, 2025
b421da0
Merge branch 'main' into jabaile/semantic-tokens
jakebailey Dec 17, 2025
6463a4e
Merge branch 'main' into jabaile/semantic-tokens
jakebailey Jan 31, 2026
d90ccd5
Merge branch 'main' into jabaile/semantic-tokens
jakebailey Feb 5, 2026
6e14514
Update tests
jakebailey Feb 5, 2026
f4f81e7
Merge branch 'main' into jabaile/semantic-tokens
jakebailey Feb 15, 2026
715991f
Merge branch 'main' into jabaile/semantic-tokens
jakebailey Feb 27, 2026
9a04841
Merge branch 'main' into jabaile/semantic-tokens
jakebailey Mar 5, 2026
881a3c0
Update fourslash
jakebailey Mar 5, 2026
ab6bf06
Merge branch 'main' into jabaile/semantic-tokens
jakebailey Mar 18, 2026
9bc28f0
Fix
jakebailey Mar 18, 2026
6273b33
Don't log files with intentionally no tests
jakebailey Mar 18, 2026
d11bece
Merge branch 'main' into jabaile/semantic-tokens
jakebailey Mar 30, 2026
536a806
PR feedback
jakebailey Mar 30, 2026
49e4ea7
Class expression is declaration
jakebailey Mar 31, 2026
829bf8b
Merge branch 'main' into jabaile/semantic-tokens
jakebailey Apr 8, 2026
39bc53d
Merge branch 'main' into jabaile/semantic-tokens
jakebailey Apr 11, 2026
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
93 changes: 87 additions & 6 deletions internal/fourslash/_scripts/convertFourslash.mts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ function parseTypeScriptFiles(manualTests: Set<string>, folder: string): void {
const isServer = filePath.split(path.sep).includes("server");
try {
const test = parseFileContent(file, content);
if (test === NO_TEST) return;
const testContent = generateGoTest(test, isServer);
const testPath = path.join(outputDir, `${test.name}_test.go`);
fs.writeFileSync(testPath, testContent, "utf-8");
Expand All @@ -126,7 +127,10 @@ function parseTypeScriptFiles(manualTests: Set<string>, folder: string): void {
});
}

function parseFileContent(filename: string, content: string): GoTest {
const NO_TEST: unique symbol = Symbol("NO_TEST");
type NoTest = typeof NO_TEST;

function parseFileContent(filename: string, content: string): GoTest | NoTest {
console.error(`Parsing file: ${filename}`);
const sourceFile = ts.createSourceFile("temp.ts", content, ts.ScriptTarget.Latest, true /*setParentNodes*/);
const statements = sourceFile.statements;
Expand All @@ -140,7 +144,8 @@ function parseFileContent(filename: string, content: string): GoTest {
goTest.commands.push(...result);
}
if (goTest.commands.length === 0) {
throw new Error(`No commands parsed in file: ${filename}`);
console.error(`No commands parsed in file (skipping): ${filename}`);
return NO_TEST;
}
validateCodeFixCommands(goTest.commands);
return goTest;
Expand Down Expand Up @@ -222,10 +227,6 @@ function getBadStatementText(statement: ts.Statement): string {
return statement.getText();
}

/**
* Parses a Strada fourslash statement and returns the corresponding Corsa commands.
* @returns an array of commands if the statement is a valid fourslash command, or `undefined` if the statement could not be parsed.
*/
function parseFourslashStatement(statement: ts.Statement): Cmd[] {
if (ts.isVariableStatement(statement)) {
// variable declarations (for ranges and markers), e.g. `const range = test.ranges()[0];`
Expand Down Expand Up @@ -368,6 +369,10 @@ function parseFourslashStatement(statement: ts.Statement): Cmd[] {
return parseCodeFixAvailableArgs(callExpression.arguments);
case "codeFixAll":
return parseCodeFixAllArgs(callExpression.arguments);
case "semanticClassificationsAre":
return parseSemanticClassificationsAre(callExpression.arguments);
case "syntacticClassificationsAre":
return [];
}
}
// `goTo....`
Expand Down Expand Up @@ -2819,6 +2824,65 @@ function parseOutliningSpansArgs(args: readonly ts.Expression[]): [VerifyOutlini
}];
}

function parseSemanticClassificationsAre(args: readonly ts.Expression[]): [VerifySemanticClassificationsCmd] | [] {
if (args.length < 1) {
throw new Error("semanticClassificationsAre requires at least a format argument");
}

const formatArg = args[0];
if (!ts.isStringLiteralLike(formatArg)) {
throw new Error("semanticClassificationsAre first argument must be a string literal");
}

const format = formatArg.text;

// Only handle "2020" format for semantic tokens
if (format !== "2020") {
// Skip other formats like "original"
return [];
}

const tokens: Array<{ type: string; text: string; }> = [];

// Parse the classification tokens (c2.semanticToken("type", "text"))
for (let i = 1; i < args.length; i++) {
const arg = args[i];
if (!ts.isCallExpression(arg)) {
throw new Error(`Expected call expression for token at index ${i}`);
}

if (!ts.isPropertyAccessExpression(arg.expression) || arg.expression.name.text !== "semanticToken") {
throw new Error(`Expected semanticToken call at index ${i}`);
}

if (arg.arguments.length < 2) {
throw new Error(`semanticToken requires 2 arguments at index ${i}`);
}

const typeArg = arg.arguments[0];
const textArg = arg.arguments[1];

if (!ts.isStringLiteralLike(typeArg) || !ts.isStringLiteralLike(textArg)) {
throw new Error(`semanticToken arguments must be string literals at index ${i}`);
}

// Map TypeScript's internal "member" type to LSP's "method" type
let tokenType = typeArg.text;
tokenType = tokenType.replace(/\bmember\b/g, "method");

tokens.push({
type: tokenType,
text: textArg.text,
});
}

return [{
kind: "verifySemanticClassifications",
format,
tokens,
}];
}

function parseKind(expr: ts.Expression): string {
if (!ts.isStringLiteral(expr)) {
throw new Error(`Expected string literal for kind, got ${expr.getText()}`);
Expand Down Expand Up @@ -3391,6 +3455,12 @@ interface VerifyCodeFixAllCmd {
newFileContent: string;
}

interface VerifySemanticClassificationsCmd {
kind: "verifySemanticClassifications";
format: string;
tokens: Array<{ type: string; text: string; }>;
}

type Cmd =
| VerifyCompletionsCmd
| VerifyApplyCodeActionFromCompletionCmd
Expand Down Expand Up @@ -3422,6 +3492,7 @@ type Cmd =
| VerifyImportFixModuleSpecifiersCmd
| VerifyDiagnosticsCmd
| VerifyBaselineDiagnosticsCmd
| VerifySemanticClassificationsCmd
| VerifyOutliningSpansCmd
| VerifyNumberOfErrorsInCurrentFileCmd
| VerifyNoErrorsCmd
Expand Down Expand Up @@ -3698,6 +3769,14 @@ function generateNavigateTo({ args }: VerifyNavToCmd): string {
return `f.VerifyWorkspaceSymbol(t, []*fourslash.VerifyWorkspaceSymbolCase{\n${args.join(", ")}})`;
}

function generateSemanticClassifications({ format, tokens }: VerifySemanticClassificationsCmd): string {
const tokensStr = tokens.map(t => `{Type: ${getGoStringLiteral(t.type)}, Text: ${getGoStringLiteral(t.text)}}`).join(",\n\t\t");
const maybeComma = tokens.length > 0 ? "," : "";
return `f.VerifySemanticTokens(t, []fourslash.SemanticToken{
${tokensStr}${maybeComma}
})`;
}

function generateCmd(cmd: Cmd, imports: Set<string>): string {
switch (cmd.kind) {
case "verifyCompletions":
Expand Down Expand Up @@ -3806,6 +3885,8 @@ function generateCmd(cmd: Cmd, imports: Set<string>): string {
FixID: ${getGoStringLiteral(cmd.fixId)},
NewFileContent: ${getGoMultiLineStringLiteral(cmd.newFileContent)},
})`;
case "verifySemanticClassifications":
return generateSemanticClassifications(cmd);
default:
let neverCommand: never = cmd;
throw new Error(`Unknown command kind: ${neverCommand as Cmd["kind"]}`);
Expand Down
2 changes: 2 additions & 0 deletions internal/fourslash/_scripts/manualTests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ pathCompletionsTypesVersionsWildcard6
quickInfoForOverloadOnConst1
renameDefaultKeyword
renameForDefaultExport01
semanticClassificationClassExpression
semanticModernClassificationFunctions
semicolonFormattingInsideAComment
semicolonFormattingInsideAStringLiteral
signatureHelpImportStarFromExportEquals
Expand Down
Loading
Loading