@@ -249,6 +249,22 @@ function parseFourslashStatement(statement: ts.Statement): Cmd[] | undefined {
249249return parseVerifyNavTree ( callExpression . arguments ) ;
250250case "navigationBar" :
251251return [ ] ; // 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
313329goStatement :`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+ }
316358default :
317359console . error ( `Unrecognized edit function:${ funcName } ` ) ;
318360return undefined ;
@@ -1446,6 +1488,130 @@ function parseExpectedDiagnostic(expr: ts.Expression): string | undefined {
14461488return `&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+
14491615function stringToTristate ( s :string ) :string {
14501616switch ( s ) {
14511617case "true" :
@@ -2689,6 +2855,48 @@ interface VerifyNavTreeCmd {
26892855kind :"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+
26922900type 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
27202936function generateVerifyOutliningSpans ( { foldingRangeKind} :VerifyOutliningSpansCmd ) :string {
27212937if ( foldingRangeKind ) {
@@ -3029,6 +3245,22 @@ function generateCmd(cmd: Cmd): string {
30293245return generateVerifyOutliningSpans ( cmd ) ;
30303246case "verifyNavigationTree" :
30313247return `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 ) } )` ;
30323264default :
30333265let neverCommand :never = cmd ;
30343266throw new Error ( `Unknown command kind:${ neverCommand as Cmd [ "kind" ] } ` ) ;
@@ -3047,16 +3279,17 @@ function generateGoTest(failingTests: Set<string>, test: GoTest, isServer: boole
30473279const commands = test . commands . map ( cmd => generateCmd ( cmd ) ) . join ( "\n" ) ;
30483280const 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 ( / \b c o r e \. / . test ( commands ) ) {
30513284imports . unshift ( `"github.com/microsoft/typescript-go/internal/core"` ) ;
30523285}
3053- if ( commands . includes ( "ls." ) ) {
3286+ if ( / \b l s \. / . test ( commands ) ) {
30543287imports . push ( `"github.com/microsoft/typescript-go/internal/ls"` ) ;
30553288}
3056- if ( commands . includes ( "lsutil." ) ) {
3289+ if ( / \b l s u t i l \. / . test ( commands ) ) {
30573290imports . push ( `"github.com/microsoft/typescript-go/internal/ls/lsutil"` ) ;
30583291}
3059- if ( commands . includes ( "lsproto." ) ) {
3292+ if ( / \b l s p r o t o \. / . test ( commands ) ) {
30603293imports . push ( `"github.com/microsoft/typescript-go/internal/lsp/lsproto"` ) ;
30613294}
30623295if ( usesFourslashUtil ( commands ) ) {
@@ -3129,6 +3362,17 @@ function getArrayLiteralExpression(node: ts.Node): ts.ArrayLiteralExpression | u
31293362return 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+
31323376function getInitializer ( name :ts . Identifier ) :ts . Expression | undefined {
31333377const file = name . getSourceFile ( ) ;
31343378const varStmts = file . statements . filter ( ts . isVariableStatement ) ;