From 45dbee9b33e656ebae70cb3c15be3ed878698a42 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 17:52:48 -0700 Subject: [PATCH 01/29] starting --- internal/ls/semantictokens.go | 480 ++++++++++++++++++++++++++++++++++ internal/lsp/server.go | 21 ++ 2 files changed, 501 insertions(+) create mode 100644 internal/ls/semantictokens.go diff --git a/internal/ls/semantictokens.go b/internal/ls/semantictokens.go new file mode 100644 index 0000000000..be721fba2a --- /dev/null +++ b/internal/ls/semantictokens.go @@ -0,0 +1,480 @@ +package ls + +import ( + "context" + "slices" + + "github.com/microsoft/typescript-go/internal/ast" + "github.com/microsoft/typescript-go/internal/checker" + "github.com/microsoft/typescript-go/internal/compiler" + "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/ls/lsconv" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/tspath" +) + +// Token types according to LSP specification +const ( + TokenTypeNamespace = iota + TokenTypeClass + TokenTypeEnum + TokenTypeInterface + TokenTypeStruct + TokenTypeTypeParameter + TokenTypeType + TokenTypeParameter + TokenTypeVariable + TokenTypeProperty + TokenTypeEnumMember + TokenTypeDecorator + TokenTypeEvent + TokenTypeFunction + TokenTypeMethod + TokenTypeMacro + TokenTypeLabel + TokenTypeComment + TokenTypeString + TokenTypeKeyword + TokenTypeNumber + TokenTypeRegexp + TokenTypeOperator +) + +// Token modifiers according to LSP specification +const ( + TokenModifierDeclaration = 1 << iota + TokenModifierDefinition + TokenModifierReadonly + TokenModifierStatic + TokenModifierDeprecated + TokenModifierAbstract + TokenModifierAsync + TokenModifierModification + TokenModifierDocumentation + TokenModifierDefaultLibrary +) + +// SemanticTokensLegend returns the legend describing the token types and modifiers +func SemanticTokensLegend() *lsproto.SemanticTokensLegend { + return &lsproto.SemanticTokensLegend{ + TokenTypes: []string{ + "namespace", + "class", + "enum", + "interface", + "struct", + "typeParameter", + "type", + "parameter", + "variable", + "property", + "enumMember", + "decorator", + "event", + "function", + "method", + "macro", + "label", + "comment", + "string", + "keyword", + "number", + "regexp", + "operator", + }, + TokenModifiers: []string{ + "declaration", + "definition", + "readonly", + "static", + "deprecated", + "abstract", + "async", + "modification", + "documentation", + "defaultLibrary", + }, + } +} + +func (l *LanguageService) ProvideSemanticTokens(ctx context.Context, documentURI lsproto.DocumentUri) (lsproto.SemanticTokensResponse, error) { + program, file := l.getProgramAndFile(documentURI) + + c, done := program.GetTypeCheckerForFile(ctx, file) + defer done() + + tokens := l.collectSemanticTokens(c, file, program) + + if len(tokens) == 0 { + return lsproto.SemanticTokensOrNull{}, nil + } + + // Convert to LSP format (relative encoding) + encoded := encodeSemanticTokens(tokens, file, l.converters) + + return lsproto.SemanticTokensOrNull{ + SemanticTokens: &lsproto.SemanticTokens{ + Data: encoded, + }, + }, nil +} + +func (l *LanguageService) ProvideSemanticTokensRange(ctx context.Context, documentURI lsproto.DocumentUri, rang lsproto.Range) (lsproto.SemanticTokensRangeResponse, error) { + program, file := l.getProgramAndFile(documentURI) + + c, done := program.GetTypeCheckerForFile(ctx, file) + defer done() + + start := int(l.converters.LineAndCharacterToPosition(file, rang.Start)) + end := int(l.converters.LineAndCharacterToPosition(file, rang.End)) + + tokens := l.collectSemanticTokensInRange(c, file, program, start, end) + + if len(tokens) == 0 { + return lsproto.SemanticTokensOrNull{}, nil + } + + // Convert to LSP format (relative encoding) + encoded := encodeSemanticTokens(tokens, file, l.converters) + + return lsproto.SemanticTokensOrNull{ + SemanticTokens: &lsproto.SemanticTokens{ + Data: encoded, + }, + }, nil +} + +type semanticToken struct { + pos int + length int + tokenType int + tokenModifier int +} + +func (l *LanguageService) collectSemanticTokens(c *checker.Checker, file *ast.SourceFile, program *compiler.Program) []semanticToken { + return l.collectSemanticTokensInRange(c, file, program, file.Pos(), file.End()) +} + +func (l *LanguageService) collectSemanticTokensInRange(c *checker.Checker, file *ast.SourceFile, program *compiler.Program, spanStart, spanEnd int) []semanticToken { + tokens := []semanticToken{} + + inJSXElement := false + + var visit func(*ast.Node) bool + visit = func(node *ast.Node) bool { + // Note: cancellation is handled at the handler level, not here + + if node == nil { + return false + } + nodeEnd := node.End() + if node.Pos() >= spanEnd || nodeEnd <= spanStart { + return false + } + + prevInJSXElement := inJSXElement + if ast.IsJsxElement(node) || ast.IsJsxSelfClosingElement(node) { + inJSXElement = true + } + if ast.IsJsxExpression(node) { + inJSXElement = false + } + + if ast.IsIdentifier(node) && !inJSXElement && !isInImportClause(node) && !isInfinityOrNaNString(node.Text()) { + symbol := c.GetSymbolAtLocation(node) + if symbol != nil { + // Resolve aliases + if symbol.Flags&ast.SymbolFlagsAlias != 0 { + symbol = c.GetAliasedSymbol(symbol) + } + + tokenType, ok := classifySymbol(symbol, getMeaningFromLocation(node)) + if ok { + tokenModifier := 0 + + // Check if this is a declaration + parent := node.Parent + if parent != nil { + parentIsDeclaration := ast.IsBindingElement(parent) || tokenFromDeclarationMapping(parent.Kind) == tokenType + if parentIsDeclaration && parent.Name() == node { + tokenModifier |= TokenModifierDeclaration + } + } + + // Reclassify parameters as properties in property access context + if tokenType == TokenTypeParameter && ast.IsRightSideOfQualifiedNameOrPropertyAccess(node) { + tokenType = TokenTypeProperty + } + + // Reclassify based on type information + tokenType = reclassifyByType(c, node, tokenType) + + // Get the value declaration to check modifiers + if decl := symbol.ValueDeclaration; decl != nil { + modifiers := ast.GetCombinedModifierFlags(decl) + nodeFlags := ast.GetCombinedNodeFlags(decl) + + if modifiers&ast.ModifierFlagsStatic != 0 { + tokenModifier |= TokenModifierStatic + } + if modifiers&ast.ModifierFlagsAsync != 0 { + tokenModifier |= TokenModifierAsync + } + if tokenType != TokenTypeClass && tokenType != TokenTypeInterface { + if (modifiers&ast.ModifierFlagsReadonly != 0) || (nodeFlags&ast.NodeFlagsConst != 0) || (symbol.Flags&ast.SymbolFlagsEnumMember != 0) { + tokenModifier |= TokenModifierReadonly + } + } + if (tokenType == TokenTypeVariable || tokenType == TokenTypeFunction) && isLocalDeclaration(decl, file) { + // Local variables get no special modifier in LSP, but we track it internally + } + declSourceFile := ast.GetSourceFileOfNode(decl) + if declSourceFile != nil && program.IsSourceFileDefaultLibrary(tspath.Path(declSourceFile.FileName())) { + tokenModifier |= TokenModifierDefaultLibrary + } + } else if symbol.Declarations != nil { + for _, decl := range symbol.Declarations { + declSourceFile := ast.GetSourceFileOfNode(decl) + if declSourceFile != nil && program.IsSourceFileDefaultLibrary(tspath.Path(declSourceFile.FileName())) { + tokenModifier |= TokenModifierDefaultLibrary + break + } + } + } + + tokens = append(tokens, semanticToken{ + pos: node.Pos(), + length: node.End() - node.Pos(), + tokenType: tokenType, + tokenModifier: tokenModifier, + }) + } + } + } + + node.ForEachChild(visit) + inJSXElement = prevInJSXElement + return false + } + + visit(&file.Node) + return tokens +} + +func classifySymbol(symbol *ast.Symbol, meaning ast.SemanticMeaning) (int, bool) { + flags := symbol.Flags + if flags&ast.SymbolFlagsClass != 0 { + return TokenTypeClass, true + } + if flags&ast.SymbolFlagsEnum != 0 { + return TokenTypeEnum, true + } + if flags&ast.SymbolFlagsTypeAlias != 0 { + return TokenTypeType, true + } + if flags&ast.SymbolFlagsInterface != 0 { + if meaning&ast.SemanticMeaningType != 0 { + return TokenTypeInterface, true + } + } + if flags&ast.SymbolFlagsTypeParameter != 0 { + return TokenTypeTypeParameter, true + } + + // Check the value declaration + decl := symbol.ValueDeclaration + if decl == nil && symbol.Declarations != nil && len(symbol.Declarations) > 0 { + decl = symbol.Declarations[0] + } + if decl != nil && ast.IsBindingElement(decl) { + decl = getDeclarationForBindingElement(decl) + } + if decl != nil { + if tokenType := tokenFromDeclarationMapping(decl.Kind); tokenType >= 0 { + return tokenType, true + } + } + + return 0, false +} + +func tokenFromDeclarationMapping(kind ast.Kind) int { + switch kind { + case ast.KindVariableDeclaration: + return TokenTypeVariable + case ast.KindParameter: + return TokenTypeParameter + case ast.KindPropertyDeclaration: + return TokenTypeProperty + case ast.KindModuleDeclaration: + return TokenTypeNamespace + case ast.KindEnumDeclaration: + return TokenTypeEnum + case ast.KindEnumMember: + return TokenTypeEnumMember + case ast.KindClassDeclaration: + return TokenTypeClass + case ast.KindMethodDeclaration: + return TokenTypeMethod + case ast.KindFunctionDeclaration, ast.KindFunctionExpression: + return TokenTypeFunction + case ast.KindMethodSignature: + return TokenTypeMethod + case ast.KindGetAccessor, ast.KindSetAccessor: + return TokenTypeProperty + case ast.KindPropertySignature: + return TokenTypeProperty + case ast.KindInterfaceDeclaration: + return TokenTypeInterface + case ast.KindTypeAliasDeclaration: + return TokenTypeType + case ast.KindTypeParameter: + return TokenTypeTypeParameter + case ast.KindPropertyAssignment, ast.KindShorthandPropertyAssignment: + return TokenTypeProperty + default: + return -1 + } +} + +func reclassifyByType(c *checker.Checker, node *ast.Node, tokenType int) int { + // Type-based reclassification for variables, properties, and parameters + if tokenType == TokenTypeVariable || tokenType == TokenTypeProperty || tokenType == TokenTypeParameter { + typ := c.GetTypeAtLocation(node) + if typ != nil { + test := func(condition func(*checker.Type) bool) bool { + if condition(typ) { + return true + } + if typ.Flags()&checker.TypeFlagsUnion != 0 { + for _, t := range typ.AsUnionType().Types() { + if condition(t) { + return true + } + } + } + return false + } + + // Check for constructor signatures (class-like) + if tokenType != TokenTypeParameter && test(func(t *checker.Type) bool { + return len(c.GetSignaturesOfType(t, checker.SignatureKindConstruct)) > 0 + }) { + return TokenTypeClass + } + + // Check for call signatures (function-like) + if test(func(t *checker.Type) bool { + callSigs := c.GetSignaturesOfType(t, checker.SignatureKindCall) + if len(callSigs) == 0 { + return false + } + // Must have call signatures and no properties (or be used in call context) + return len(t.AsObjectType().Properties()) == 0 || isExpressionInCallExpression(node) + }) { + if tokenType == TokenTypeProperty { + return TokenTypeMethod + } + return TokenTypeFunction + } + } + } + return tokenType +} + +func isLocalDeclaration(decl *ast.Node, sourceFile *ast.SourceFile) bool { + if ast.IsBindingElement(decl) { + decl = getDeclarationForBindingElement(decl) + } + if ast.IsVariableDeclaration(decl) { + parent := decl.Parent + if parent != nil && ast.IsVariableDeclarationList(parent) { + grandparent := parent.Parent + if grandparent != nil { + return (!ast.IsSourceFile(grandparent) || ast.IsCatchClause(grandparent)) && + ast.GetSourceFileOfNode(decl) == sourceFile + } + } + } else if ast.IsFunctionDeclaration(decl) { + parent := decl.Parent + return parent != nil && !ast.IsSourceFile(parent) && ast.GetSourceFileOfNode(decl) == sourceFile + } + return false +} + +func getDeclarationForBindingElement(element *ast.Node) *ast.Node { + for { + parent := element.Parent + if parent != nil && ast.IsBindingPattern(parent) { + grandparent := parent.Parent + if grandparent != nil && ast.IsBindingElement(grandparent) { + element = grandparent + continue + } + } + if parent != nil && ast.IsBindingPattern(parent) { + return parent.Parent + } + return element + } +} + +func isInImportClause(node *ast.Node) bool { + parent := node.Parent + if parent == nil { + return false + } + return ast.IsImportClause(parent) || ast.IsImportSpecifier(parent) || ast.IsNamespaceImport(parent) +} + +func isExpressionInCallExpression(node *ast.Node) bool { + for ast.IsRightSideOfQualifiedNameOrPropertyAccess(node) { + node = node.Parent + } + parent := node.Parent + return parent != nil && ast.IsCallExpression(parent) && parent.Expression() == node +} + +func isInfinityOrNaNString(text string) bool { + return text == "Infinity" || text == "NaN" +} + +// encodeSemanticTokens encodes tokens into the LSP format using relative positioning +func encodeSemanticTokens(tokens []semanticToken, file *ast.SourceFile, converters *lsconv.Converters) []uint32 { + // Sort tokens by position + slices.SortFunc(tokens, func(a, b semanticToken) int { + return a.pos - b.pos + }) + + encoded := []uint32{} + prevLine := uint32(0) + prevChar := uint32(0) + + for _, token := range tokens { + pos := converters.PositionToLineAndCharacter(file, core.TextPos(token.pos)) + line := pos.Line + char := pos.Character + + // Encode as: [deltaLine, deltaChar, length, tokenType, tokenModifiers] + deltaLine := line - prevLine + var deltaChar uint32 + if deltaLine == 0 { + deltaChar = char - prevChar + } else { + deltaChar = char + } + + encoded = append(encoded, + deltaLine, + deltaChar, + uint32(token.length), + uint32(token.tokenType), + uint32(token.tokenModifier), + ) + + prevLine = line + prevChar = char + } + + return encoded +} diff --git a/internal/lsp/server.go b/internal/lsp/server.go index c51a40547f..dab239cabf 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -492,6 +492,8 @@ var handlers = sync.OnceValue(func() handlerMap { registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentSelectionRangeInfo, (*Server).handleSelectionRange) registerRequestHandler(handlers, lsproto.WorkspaceSymbolInfo, (*Server).handleWorkspaceSymbol) registerRequestHandler(handlers, lsproto.CompletionItemResolveInfo, (*Server).handleCompletionItemResolve) + registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentSemanticTokensFullInfo, (*Server).handleSemanticTokensFull) + registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentSemanticTokensRangeInfo, (*Server).handleSemanticTokensRange) return handlers }) @@ -672,6 +674,17 @@ func (s *Server) handleInitialize(ctx context.Context, params *lsproto.Initializ SelectionRangeProvider: &lsproto.BooleanOrSelectionRangeOptionsOrSelectionRangeRegistrationOptions{ Boolean: ptrTo(true), }, + SemanticTokensProvider: &lsproto.SemanticTokensOptionsOrRegistrationOptions{ + Options: &lsproto.SemanticTokensOptions{ + Legend: ls.SemanticTokensLegend(), + Full: &lsproto.BooleanOrSemanticTokensFullDelta{ + Boolean: ptrTo(true), + }, + Range: &lsproto.BooleanOrEmptyObject{ + Boolean: ptrTo(true), + }, + }, + }, }, } @@ -918,6 +931,14 @@ func (s *Server) handleSelectionRange(ctx context.Context, ls *ls.LanguageServic return ls.ProvideSelectionRanges(ctx, params) } +func (s *Server) handleSemanticTokensFull(ctx context.Context, ls *ls.LanguageService, params *lsproto.SemanticTokensParams) (lsproto.SemanticTokensResponse, error) { + return ls.ProvideSemanticTokens(ctx, params.TextDocument.Uri) +} + +func (s *Server) handleSemanticTokensRange(ctx context.Context, ls *ls.LanguageService, params *lsproto.SemanticTokensRangeParams) (lsproto.SemanticTokensRangeResponse, error) { + return ls.ProvideSemanticTokensRange(ctx, params.TextDocument.Uri, params.Range) +} + func (s *Server) Log(msg ...any) { fmt.Fprintln(s.stderr, msg...) } From cf87afba6efafc5d3f969540083f7b313d8714ce Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 17:58:02 -0700 Subject: [PATCH 02/29] more --- internal/ls/semantictokens.go | 252 ++++++++++++++++++---------------- 1 file changed, 136 insertions(+), 116 deletions(-) diff --git a/internal/ls/semantictokens.go b/internal/ls/semantictokens.go index be721fba2a..1b3a87db3b 100644 --- a/internal/ls/semantictokens.go +++ b/internal/ls/semantictokens.go @@ -13,87 +13,107 @@ import ( "github.com/microsoft/typescript-go/internal/tspath" ) -// Token types according to LSP specification +// tokenTypes defines the order of token types for encoding +var tokenTypes = []lsproto.SemanticTokenTypes{ + lsproto.SemanticTokenTypesnamespace, + lsproto.SemanticTokenTypesclass, + lsproto.SemanticTokenTypesenum, + lsproto.SemanticTokenTypesinterface, + lsproto.SemanticTokenTypesstruct, + lsproto.SemanticTokenTypestypeParameter, + lsproto.SemanticTokenTypestype, + lsproto.SemanticTokenTypesparameter, + lsproto.SemanticTokenTypesvariable, + lsproto.SemanticTokenTypesproperty, + lsproto.SemanticTokenTypesenumMember, + lsproto.SemanticTokenTypesdecorator, + lsproto.SemanticTokenTypesevent, + lsproto.SemanticTokenTypesfunction, + lsproto.SemanticTokenTypesmethod, + lsproto.SemanticTokenTypesmacro, + lsproto.SemanticTokenTypeslabel, + lsproto.SemanticTokenTypescomment, + lsproto.SemanticTokenTypesstring, + lsproto.SemanticTokenTypeskeyword, + lsproto.SemanticTokenTypesnumber, + lsproto.SemanticTokenTypesregexp, + lsproto.SemanticTokenTypesoperator, +} + +// tokenModifiers defines the order of token modifiers for encoding +var tokenModifiers = []lsproto.SemanticTokenModifiers{ + lsproto.SemanticTokenModifiersdeclaration, + lsproto.SemanticTokenModifiersdefinition, + lsproto.SemanticTokenModifiersreadonly, + lsproto.SemanticTokenModifiersstatic, + lsproto.SemanticTokenModifiersdeprecated, + lsproto.SemanticTokenModifiersabstract, + lsproto.SemanticTokenModifiersasync, + lsproto.SemanticTokenModifiersmodification, + lsproto.SemanticTokenModifiersdocumentation, + lsproto.SemanticTokenModifiersdefaultLibrary, +} + +// tokenType represents a semantic token type index +type tokenType int + +// Token type indices const ( - TokenTypeNamespace = iota - TokenTypeClass - TokenTypeEnum - TokenTypeInterface - TokenTypeStruct - TokenTypeTypeParameter - TokenTypeType - TokenTypeParameter - TokenTypeVariable - TokenTypeProperty - TokenTypeEnumMember - TokenTypeDecorator - TokenTypeEvent - TokenTypeFunction - TokenTypeMethod - TokenTypeMacro - TokenTypeLabel - TokenTypeComment - TokenTypeString - TokenTypeKeyword - TokenTypeNumber - TokenTypeRegexp - TokenTypeOperator + tokenTypeNamespace tokenType = iota + tokenTypeClass + tokenTypeEnum + tokenTypeInterface + tokenTypeStruct + tokenTypeTypeParameter + tokenTypeType + tokenTypeParameter + tokenTypeVariable + tokenTypeProperty + tokenTypeEnumMember + tokenTypeDecorator + tokenTypeEvent + tokenTypeFunction + tokenTypeMethod + tokenTypeMacro + tokenTypeLabel + tokenTypeComment + tokenTypeString + tokenTypeKeyword + tokenTypeNumber + tokenTypeRegexp + tokenTypeOperator ) -// Token modifiers according to LSP specification +// tokenModifier represents a semantic token modifier bit mask +type tokenModifier int + +// Token modifier bit masks const ( - TokenModifierDeclaration = 1 << iota - TokenModifierDefinition - TokenModifierReadonly - TokenModifierStatic - TokenModifierDeprecated - TokenModifierAbstract - TokenModifierAsync - TokenModifierModification - TokenModifierDocumentation - TokenModifierDefaultLibrary + tokenModifierDeclaration tokenModifier = 1 << iota + tokenModifierDefinition + tokenModifierReadonly + tokenModifierStatic + tokenModifierDeprecated + tokenModifierAbstract + tokenModifierAsync + tokenModifierModification + tokenModifierDocumentation + tokenModifierDefaultLibrary ) // SemanticTokensLegend returns the legend describing the token types and modifiers func SemanticTokensLegend() *lsproto.SemanticTokensLegend { + types := make([]string, len(tokenTypes)) + for i, t := range tokenTypes { + types[i] = string(t) + } + modifiers := make([]string, len(tokenModifiers)) + for i, m := range tokenModifiers { + modifiers[i] = string(m) + } return &lsproto.SemanticTokensLegend{ - TokenTypes: []string{ - "namespace", - "class", - "enum", - "interface", - "struct", - "typeParameter", - "type", - "parameter", - "variable", - "property", - "enumMember", - "decorator", - "event", - "function", - "method", - "macro", - "label", - "comment", - "string", - "keyword", - "number", - "regexp", - "operator", - }, - TokenModifiers: []string{ - "declaration", - "definition", - "readonly", - "static", - "deprecated", - "abstract", - "async", - "modification", - "documentation", - "defaultLibrary", - }, + TokenTypes: types, + TokenModifiers: modifiers, } } @@ -147,8 +167,8 @@ func (l *LanguageService) ProvideSemanticTokensRange(ctx context.Context, docume type semanticToken struct { pos int length int - tokenType int - tokenModifier int + tokenType tokenType + tokenModifier tokenModifier } func (l *LanguageService) collectSemanticTokens(c *checker.Checker, file *ast.SourceFile, program *compiler.Program) []semanticToken { @@ -190,20 +210,20 @@ func (l *LanguageService) collectSemanticTokensInRange(c *checker.Checker, file tokenType, ok := classifySymbol(symbol, getMeaningFromLocation(node)) if ok { - tokenModifier := 0 + tokenModifier := tokenModifier(0) // Check if this is a declaration parent := node.Parent if parent != nil { parentIsDeclaration := ast.IsBindingElement(parent) || tokenFromDeclarationMapping(parent.Kind) == tokenType if parentIsDeclaration && parent.Name() == node { - tokenModifier |= TokenModifierDeclaration + tokenModifier |= tokenModifierDeclaration } } // Reclassify parameters as properties in property access context - if tokenType == TokenTypeParameter && ast.IsRightSideOfQualifiedNameOrPropertyAccess(node) { - tokenType = TokenTypeProperty + if tokenType == tokenTypeParameter && ast.IsRightSideOfQualifiedNameOrPropertyAccess(node) { + tokenType = tokenTypeProperty } // Reclassify based on type information @@ -215,28 +235,28 @@ func (l *LanguageService) collectSemanticTokensInRange(c *checker.Checker, file nodeFlags := ast.GetCombinedNodeFlags(decl) if modifiers&ast.ModifierFlagsStatic != 0 { - tokenModifier |= TokenModifierStatic + tokenModifier |= tokenModifierStatic } if modifiers&ast.ModifierFlagsAsync != 0 { - tokenModifier |= TokenModifierAsync + tokenModifier |= tokenModifierAsync } - if tokenType != TokenTypeClass && tokenType != TokenTypeInterface { + if tokenType != tokenTypeClass && tokenType != tokenTypeInterface { if (modifiers&ast.ModifierFlagsReadonly != 0) || (nodeFlags&ast.NodeFlagsConst != 0) || (symbol.Flags&ast.SymbolFlagsEnumMember != 0) { - tokenModifier |= TokenModifierReadonly + tokenModifier |= tokenModifierReadonly } } - if (tokenType == TokenTypeVariable || tokenType == TokenTypeFunction) && isLocalDeclaration(decl, file) { + if (tokenType == tokenTypeVariable || tokenType == tokenTypeFunction) && isLocalDeclaration(decl, file) { // Local variables get no special modifier in LSP, but we track it internally } declSourceFile := ast.GetSourceFileOfNode(decl) if declSourceFile != nil && program.IsSourceFileDefaultLibrary(tspath.Path(declSourceFile.FileName())) { - tokenModifier |= TokenModifierDefaultLibrary + tokenModifier |= tokenModifierDefaultLibrary } } else if symbol.Declarations != nil { for _, decl := range symbol.Declarations { declSourceFile := ast.GetSourceFileOfNode(decl) if declSourceFile != nil && program.IsSourceFileDefaultLibrary(tspath.Path(declSourceFile.FileName())) { - tokenModifier |= TokenModifierDefaultLibrary + tokenModifier |= tokenModifierDefaultLibrary break } } @@ -261,24 +281,24 @@ func (l *LanguageService) collectSemanticTokensInRange(c *checker.Checker, file return tokens } -func classifySymbol(symbol *ast.Symbol, meaning ast.SemanticMeaning) (int, bool) { +func classifySymbol(symbol *ast.Symbol, meaning ast.SemanticMeaning) (tokenType, bool) { flags := symbol.Flags if flags&ast.SymbolFlagsClass != 0 { - return TokenTypeClass, true + return tokenTypeClass, true } if flags&ast.SymbolFlagsEnum != 0 { - return TokenTypeEnum, true + return tokenTypeEnum, true } if flags&ast.SymbolFlagsTypeAlias != 0 { - return TokenTypeType, true + return tokenTypeType, true } if flags&ast.SymbolFlagsInterface != 0 { if meaning&ast.SemanticMeaningType != 0 { - return TokenTypeInterface, true + return tokenTypeInterface, true } } if flags&ast.SymbolFlagsTypeParameter != 0 { - return TokenTypeTypeParameter, true + return tokenTypeTypeParameter, true } // Check the value declaration @@ -298,48 +318,48 @@ func classifySymbol(symbol *ast.Symbol, meaning ast.SemanticMeaning) (int, bool) return 0, false } -func tokenFromDeclarationMapping(kind ast.Kind) int { +func tokenFromDeclarationMapping(kind ast.Kind) tokenType { switch kind { case ast.KindVariableDeclaration: - return TokenTypeVariable + return tokenTypeVariable case ast.KindParameter: - return TokenTypeParameter + return tokenTypeParameter case ast.KindPropertyDeclaration: - return TokenTypeProperty + return tokenTypeProperty case ast.KindModuleDeclaration: - return TokenTypeNamespace + return tokenTypeNamespace case ast.KindEnumDeclaration: - return TokenTypeEnum + return tokenTypeEnum case ast.KindEnumMember: - return TokenTypeEnumMember + return tokenTypeEnumMember case ast.KindClassDeclaration: - return TokenTypeClass + return tokenTypeClass case ast.KindMethodDeclaration: - return TokenTypeMethod + return tokenTypeMethod case ast.KindFunctionDeclaration, ast.KindFunctionExpression: - return TokenTypeFunction + return tokenTypeFunction case ast.KindMethodSignature: - return TokenTypeMethod + return tokenTypeMethod case ast.KindGetAccessor, ast.KindSetAccessor: - return TokenTypeProperty + return tokenTypeProperty case ast.KindPropertySignature: - return TokenTypeProperty + return tokenTypeProperty case ast.KindInterfaceDeclaration: - return TokenTypeInterface + return tokenTypeInterface case ast.KindTypeAliasDeclaration: - return TokenTypeType + return tokenTypeType case ast.KindTypeParameter: - return TokenTypeTypeParameter + return tokenTypeTypeParameter case ast.KindPropertyAssignment, ast.KindShorthandPropertyAssignment: - return TokenTypeProperty + return tokenTypeProperty default: return -1 } } -func reclassifyByType(c *checker.Checker, node *ast.Node, tokenType int) int { +func reclassifyByType(c *checker.Checker, node *ast.Node, tt tokenType) tokenType { // Type-based reclassification for variables, properties, and parameters - if tokenType == TokenTypeVariable || tokenType == TokenTypeProperty || tokenType == TokenTypeParameter { + if tt == tokenTypeVariable || tt == tokenTypeProperty || tt == tokenTypeParameter { typ := c.GetTypeAtLocation(node) if typ != nil { test := func(condition func(*checker.Type) bool) bool { @@ -357,10 +377,10 @@ func reclassifyByType(c *checker.Checker, node *ast.Node, tokenType int) int { } // Check for constructor signatures (class-like) - if tokenType != TokenTypeParameter && test(func(t *checker.Type) bool { + if tt != tokenTypeParameter && test(func(t *checker.Type) bool { return len(c.GetSignaturesOfType(t, checker.SignatureKindConstruct)) > 0 }) { - return TokenTypeClass + return tokenTypeClass } // Check for call signatures (function-like) @@ -372,14 +392,14 @@ func reclassifyByType(c *checker.Checker, node *ast.Node, tokenType int) int { // Must have call signatures and no properties (or be used in call context) return len(t.AsObjectType().Properties()) == 0 || isExpressionInCallExpression(node) }) { - if tokenType == TokenTypeProperty { - return TokenTypeMethod + if tt == tokenTypeProperty { + return tokenTypeMethod } - return TokenTypeFunction + return tokenTypeFunction } } } - return tokenType + return tt } func isLocalDeclaration(decl *ast.Node, sourceFile *ast.SourceFile) bool { From b891674fafa1ffb4ea81e48a1259c5fb24cf8876 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 18:04:14 -0700 Subject: [PATCH 03/29] more --- internal/ls/semantictokens.go | 93 +++++++++++++++++++++++++++++------ internal/lsp/server.go | 23 +++++++-- 2 files changed, 96 insertions(+), 20 deletions(-) diff --git a/internal/ls/semantictokens.go b/internal/ls/semantictokens.go index 1b3a87db3b..c87d1927ac 100644 --- a/internal/ls/semantictokens.go +++ b/internal/ls/semantictokens.go @@ -101,15 +101,21 @@ const ( tokenModifierDefaultLibrary ) -// SemanticTokensLegend returns the legend describing the token types and modifiers -func SemanticTokensLegend() *lsproto.SemanticTokensLegend { - types := make([]string, len(tokenTypes)) - for i, t := range tokenTypes { - types[i] = string(t) +// SemanticTokensLegend returns the legend describing the token types and modifiers. +// If clientCapabilities is provided, it filters the legend to only include types and modifiers +// that the client supports. +func SemanticTokensLegend(clientCapabilities *lsproto.SemanticTokensClientCapabilities) *lsproto.SemanticTokensLegend { + types := make([]string, 0, len(tokenTypes)) + for _, t := range tokenTypes { + if clientCapabilities == nil || slices.Contains(clientCapabilities.TokenTypes, string(t)) { + types = append(types, string(t)) + } } - modifiers := make([]string, len(tokenModifiers)) - for i, m := range tokenModifiers { - modifiers[i] = string(m) + modifiers := make([]string, 0, len(tokenModifiers)) + for _, m := range tokenModifiers { + if clientCapabilities == nil || slices.Contains(clientCapabilities.TokenModifiers, string(m)) { + modifiers = append(modifiers, string(m)) + } } return &lsproto.SemanticTokensLegend{ TokenTypes: types, @@ -117,7 +123,7 @@ func SemanticTokensLegend() *lsproto.SemanticTokensLegend { } } -func (l *LanguageService) ProvideSemanticTokens(ctx context.Context, documentURI lsproto.DocumentUri) (lsproto.SemanticTokensResponse, error) { +func (l *LanguageService) ProvideSemanticTokens(ctx context.Context, documentURI lsproto.DocumentUri, clientCapabilities *lsproto.SemanticTokensClientCapabilities) (lsproto.SemanticTokensResponse, error) { program, file := l.getProgramAndFile(documentURI) c, done := program.GetTypeCheckerForFile(ctx, file) @@ -130,7 +136,7 @@ func (l *LanguageService) ProvideSemanticTokens(ctx context.Context, documentURI } // Convert to LSP format (relative encoding) - encoded := encodeSemanticTokens(tokens, file, l.converters) + encoded := encodeSemanticTokens(tokens, file, l.converters, clientCapabilities) return lsproto.SemanticTokensOrNull{ SemanticTokens: &lsproto.SemanticTokens{ @@ -139,7 +145,7 @@ func (l *LanguageService) ProvideSemanticTokens(ctx context.Context, documentURI }, nil } -func (l *LanguageService) ProvideSemanticTokensRange(ctx context.Context, documentURI lsproto.DocumentUri, rang lsproto.Range) (lsproto.SemanticTokensRangeResponse, error) { +func (l *LanguageService) ProvideSemanticTokensRange(ctx context.Context, documentURI lsproto.DocumentUri, rang lsproto.Range, clientCapabilities *lsproto.SemanticTokensClientCapabilities) (lsproto.SemanticTokensRangeResponse, error) { program, file := l.getProgramAndFile(documentURI) c, done := program.GetTypeCheckerForFile(ctx, file) @@ -155,7 +161,7 @@ func (l *LanguageService) ProvideSemanticTokensRange(ctx context.Context, docume } // Convert to LSP format (relative encoding) - encoded := encodeSemanticTokens(tokens, file, l.converters) + encoded := encodeSemanticTokens(tokens, file, l.converters, clientCapabilities) return lsproto.SemanticTokensOrNull{ SemanticTokens: &lsproto.SemanticTokens{ @@ -459,8 +465,47 @@ func isInfinityOrNaNString(text string) bool { return text == "Infinity" || text == "NaN" } -// encodeSemanticTokens encodes tokens into the LSP format using relative positioning -func encodeSemanticTokens(tokens []semanticToken, file *ast.SourceFile, converters *lsconv.Converters) []uint32 { +// encodeSemanticTokens encodes tokens into the LSP format using relative positioning. +// It filters tokens based on client capabilities, only including types and modifiers that the client supports. +func encodeSemanticTokens(tokens []semanticToken, file *ast.SourceFile, converters *lsconv.Converters, clientCapabilities *lsproto.SemanticTokensClientCapabilities) []uint32 { + // Build mapping from server token types/modifiers to client indices + typeMapping := make(map[tokenType]int) + modifierMapping := make(map[lsproto.SemanticTokenModifiers]int) + + if clientCapabilities != nil { + // Map server token types to client-supported indices + clientIdx := 0 + for _, serverType := range tokenTypes { + if slices.Contains(clientCapabilities.TokenTypes, string(serverType)) { + // Find the server index for this type + for i, t := range tokenTypes { + if t == serverType { + typeMapping[tokenType(i)] = clientIdx + break + } + } + clientIdx++ + } + } + + // Map server token modifiers to client-supported bit positions + clientBit := 0 + for _, serverModifier := range tokenModifiers { + if slices.Contains(clientCapabilities.TokenModifiers, string(serverModifier)) { + modifierMapping[serverModifier] = clientBit + clientBit++ + } + } + } else { + // No filtering - use direct mapping + for i := range tokenTypes { + typeMapping[tokenType(i)] = i + } + for i, mod := range tokenModifiers { + modifierMapping[mod] = i + } + } + // Sort tokens by position slices.SortFunc(tokens, func(a, b semanticToken) int { return a.pos - b.pos @@ -471,6 +516,22 @@ func encodeSemanticTokens(tokens []semanticToken, file *ast.SourceFile, converte prevChar := uint32(0) for _, token := range tokens { + // Skip tokens with types not supported by the client + clientTypeIdx, typeSupported := typeMapping[token.tokenType] + if !typeSupported { + continue + } + + // Map modifiers to client-supported bit mask + clientModifierMask := uint32(0) + for i, serverModifier := range tokenModifiers { + if token.tokenModifier&(1< Date: Thu, 30 Oct 2025 18:12:33 -0700 Subject: [PATCH 04/29] more --- internal/ls/semantictokens.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/ls/semantictokens.go b/internal/ls/semantictokens.go index c87d1927ac..bb2667baee 100644 --- a/internal/ls/semantictokens.go +++ b/internal/ls/semantictokens.go @@ -396,7 +396,11 @@ func reclassifyByType(c *checker.Checker, node *ast.Node, tt tokenType) tokenTyp return false } // Must have call signatures and no properties (or be used in call context) - return len(t.AsObjectType().Properties()) == 0 || isExpressionInCallExpression(node) + objType := t.AsObjectType() + if objType == nil { + return true + } + return len(objType.Properties()) == 0 || isExpressionInCallExpression(node) }) { if tt == tokenTypeProperty { return tokenTypeMethod From 8578677a3cc521e96f5d7d23d6bcaeae196a32b2 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 18:34:55 -0700 Subject: [PATCH 05/29] more --- .vscode/tasks.json | 2 +- internal/ls/semantictokens.go | 36 ++++++++++++++++++++++------------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index a7233fda96..9390282a25 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -17,7 +17,7 @@ { "label": "Watch", "type": "npm", - "script": "build:watch:debug", + "script": "build:watch", "group": "build", "presentation": { "panel": "dedicated", diff --git a/internal/ls/semantictokens.go b/internal/ls/semantictokens.go index bb2667baee..fb12524fa7 100644 --- a/internal/ls/semantictokens.go +++ b/internal/ls/semantictokens.go @@ -10,6 +10,7 @@ import ( "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/ls/lsconv" "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/scanner" "github.com/microsoft/typescript-go/internal/tspath" ) @@ -171,8 +172,7 @@ func (l *LanguageService) ProvideSemanticTokensRange(ctx context.Context, docume } type semanticToken struct { - pos int - length int + node *ast.Node tokenType tokenType tokenModifier tokenModifier } @@ -269,8 +269,7 @@ func (l *LanguageService) collectSemanticTokensInRange(c *checker.Checker, file } tokens = append(tokens, semanticToken{ - pos: node.Pos(), - length: node.End() - node.Pos(), + node: node, tokenType: tokenType, tokenModifier: tokenModifier, }) @@ -510,11 +509,6 @@ func encodeSemanticTokens(tokens []semanticToken, file *ast.SourceFile, converte } } - // Sort tokens by position - slices.SortFunc(tokens, func(a, b semanticToken) int { - return a.pos - b.pos - }) - encoded := []uint32{} prevLine := uint32(0) prevChar := uint32(0) @@ -536,9 +530,25 @@ func encodeSemanticTokens(tokens []semanticToken, file *ast.SourceFile, converte } } - pos := converters.PositionToLineAndCharacter(file, core.TextPos(token.pos)) - line := pos.Line - char := pos.Character + // Use GetTokenPosOfNode to skip trivia (comments, whitespace) before the identifier + tokenStart := scanner.GetTokenPosOfNode(token.node, file, false) + tokenEnd := token.node.End() + + // Convert both start and end positions to LSP coordinates, then compute length + startPos := converters.PositionToLineAndCharacter(file, core.TextPos(tokenStart)) + endPos := converters.PositionToLineAndCharacter(file, core.TextPos(tokenEnd)) + + // Length is the character difference when on the same line + var tokenLength uint32 + if startPos.Line == endPos.Line { + tokenLength = endPos.Character - startPos.Character + } else { + // Multi-line tokens shouldn't happen for identifiers, but handle it + tokenLength = endPos.Character + } + + line := startPos.Line + char := startPos.Character // Encode as: [deltaLine, deltaChar, length, tokenType, tokenModifiers] deltaLine := line - prevLine @@ -552,7 +562,7 @@ func encodeSemanticTokens(tokens []semanticToken, file *ast.SourceFile, converte encoded = append(encoded, deltaLine, deltaChar, - uint32(token.length), + tokenLength, uint32(clientTypeIdx), clientModifierMask, ) From fdddc67a8673e3e0217a21f7da2e174da9606c4f Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 18:36:29 -0700 Subject: [PATCH 06/29] more --- internal/ls/semantictokens.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/ls/semantictokens.go b/internal/ls/semantictokens.go index fb12524fa7..a6b1c93f7b 100644 --- a/internal/ls/semantictokens.go +++ b/internal/ls/semantictokens.go @@ -550,6 +550,11 @@ func encodeSemanticTokens(tokens []semanticToken, file *ast.SourceFile, converte line := startPos.Line char := startPos.Character + // Verify that positions are strictly increasing (visitor walks in order) + if line < prevLine || (line == prevLine && char <= prevChar) { + panic("semantic tokens: positions must be strictly increasing") + } + // Encode as: [deltaLine, deltaChar, length, tokenType, tokenModifiers] deltaLine := line - prevLine var deltaChar uint32 From 744e6315f8a44de701cdb4c83129d2d609200bdc Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 18:39:02 -0700 Subject: [PATCH 07/29] more --- internal/ls/semantictokens.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/ls/semantictokens.go b/internal/ls/semantictokens.go index a6b1c93f7b..0b5095ec84 100644 --- a/internal/ls/semantictokens.go +++ b/internal/ls/semantictokens.go @@ -509,7 +509,8 @@ func encodeSemanticTokens(tokens []semanticToken, file *ast.SourceFile, converte } } - encoded := []uint32{} + // Each token encodes 5 uint32 values: deltaLine, deltaChar, length, tokenType, tokenModifiers + encoded := make([]uint32, 0, len(tokens)*5) prevLine := uint32(0) prevChar := uint32(0) From 29dbc8b4c8a1f2e9604ea739d03330b0b7495b8d Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 18:40:19 -0700 Subject: [PATCH 08/29] more --- internal/ls/semantictokens.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/internal/ls/semantictokens.go b/internal/ls/semantictokens.go index 0b5095ec84..35a2bd4cfe 100644 --- a/internal/ls/semantictokens.go +++ b/internal/ls/semantictokens.go @@ -372,10 +372,8 @@ func reclassifyByType(c *checker.Checker, node *ast.Node, tt tokenType) tokenTyp return true } if typ.Flags()&checker.TypeFlagsUnion != 0 { - for _, t := range typ.AsUnionType().Types() { - if condition(t) { - return true - } + if slices.ContainsFunc(typ.AsUnionType().Types(), condition) { + return true } } return false From 97d558a5e4d8e64e913c262bd968e1bf77317ded Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 18:43:05 -0700 Subject: [PATCH 09/29] more --- internal/ls/semantictokens.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/ls/semantictokens.go b/internal/ls/semantictokens.go index 35a2bd4cfe..dbff7d6d15 100644 --- a/internal/ls/semantictokens.go +++ b/internal/ls/semantictokens.go @@ -470,12 +470,12 @@ func isInfinityOrNaNString(text string) bool { // It filters tokens based on client capabilities, only including types and modifiers that the client supports. func encodeSemanticTokens(tokens []semanticToken, file *ast.SourceFile, converters *lsconv.Converters, clientCapabilities *lsproto.SemanticTokensClientCapabilities) []uint32 { // Build mapping from server token types/modifiers to client indices - typeMapping := make(map[tokenType]int) - modifierMapping := make(map[lsproto.SemanticTokenModifiers]int) + typeMapping := make(map[tokenType]uint32) + modifierMapping := make(map[lsproto.SemanticTokenModifiers]uint32) if clientCapabilities != nil { // Map server token types to client-supported indices - clientIdx := 0 + clientIdx := uint32(0) for _, serverType := range tokenTypes { if slices.Contains(clientCapabilities.TokenTypes, string(serverType)) { // Find the server index for this type @@ -490,7 +490,7 @@ func encodeSemanticTokens(tokens []semanticToken, file *ast.SourceFile, converte } // Map server token modifiers to client-supported bit positions - clientBit := 0 + clientBit := uint32(0) for _, serverModifier := range tokenModifiers { if slices.Contains(clientCapabilities.TokenModifiers, string(serverModifier)) { modifierMapping[serverModifier] = clientBit @@ -500,10 +500,10 @@ func encodeSemanticTokens(tokens []semanticToken, file *ast.SourceFile, converte } else { // No filtering - use direct mapping for i := range tokenTypes { - typeMapping[tokenType(i)] = i + typeMapping[tokenType(i)] = uint32(i) } for i, mod := range tokenModifiers { - modifierMapping[mod] = i + modifierMapping[mod] = uint32(i) } } @@ -567,7 +567,7 @@ func encodeSemanticTokens(tokens []semanticToken, file *ast.SourceFile, converte deltaLine, deltaChar, tokenLength, - uint32(clientTypeIdx), + clientTypeIdx, clientModifierMask, ) From 914c1439c33c222d823e47c28613323b1d833249 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 18:49:33 -0700 Subject: [PATCH 10/29] more --- internal/ls/semantictokens.go | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/internal/ls/semantictokens.go b/internal/ls/semantictokens.go index dbff7d6d15..f288a619ca 100644 --- a/internal/ls/semantictokens.go +++ b/internal/ls/semantictokens.go @@ -130,7 +130,7 @@ func (l *LanguageService) ProvideSemanticTokens(ctx context.Context, documentURI c, done := program.GetTypeCheckerForFile(ctx, file) defer done() - tokens := l.collectSemanticTokens(c, file, program) + tokens := l.collectSemanticTokens(ctx, c, file, program) if len(tokens) == 0 { return lsproto.SemanticTokensOrNull{}, nil @@ -155,7 +155,7 @@ func (l *LanguageService) ProvideSemanticTokensRange(ctx context.Context, docume start := int(l.converters.LineAndCharacterToPosition(file, rang.Start)) end := int(l.converters.LineAndCharacterToPosition(file, rang.End)) - tokens := l.collectSemanticTokensInRange(c, file, program, start, end) + tokens := l.collectSemanticTokensInRange(ctx, c, file, program, start, end) if len(tokens) == 0 { return lsproto.SemanticTokensOrNull{}, nil @@ -177,18 +177,21 @@ type semanticToken struct { tokenModifier tokenModifier } -func (l *LanguageService) collectSemanticTokens(c *checker.Checker, file *ast.SourceFile, program *compiler.Program) []semanticToken { - return l.collectSemanticTokensInRange(c, file, program, file.Pos(), file.End()) +func (l *LanguageService) collectSemanticTokens(ctx context.Context, c *checker.Checker, file *ast.SourceFile, program *compiler.Program) []semanticToken { + return l.collectSemanticTokensInRange(ctx, c, file, program, file.Pos(), file.End()) } -func (l *LanguageService) collectSemanticTokensInRange(c *checker.Checker, file *ast.SourceFile, program *compiler.Program, spanStart, spanEnd int) []semanticToken { +func (l *LanguageService) collectSemanticTokensInRange(ctx context.Context, c *checker.Checker, file *ast.SourceFile, program *compiler.Program, spanStart, spanEnd int) []semanticToken { tokens := []semanticToken{} inJSXElement := false var visit func(*ast.Node) bool visit = func(node *ast.Node) bool { - // Note: cancellation is handled at the handler level, not here + // Check for cancellation + if ctx.Err() != nil { + return false + } if node == nil { return false @@ -282,7 +285,13 @@ func (l *LanguageService) collectSemanticTokensInRange(c *checker.Checker, file return false } - visit(&file.Node) + visit(file.AsNode()) + + // Check for cancellation after collection + if ctx.Err() != nil { + return nil + } + return tokens } From 4d8bdc17daff1f5e70a39cbc41c5874e98b763b9 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 19:14:50 -0700 Subject: [PATCH 11/29] testing --- .../fourslash/_scripts/convertFourslash.mts | 103 ++++++++- internal/fourslash/semantictokens.go | 195 ++++++++++++++++++ .../tests/gen/renameDefaultImport_test.go | 27 +++ .../tests/gen/semanticClassification1_test.go | 21 ++ .../tests/gen/semanticClassification2_test.go | 22 ++ .../gen/semanticClassificationAlias_test.go | 23 +++ ...anticClassificationClassExpression_test.go | 19 ++ ...lassificationInTemplateExpressions_test.go | 25 +++ ...iatedModuleWithVariableOfSameName1_test.go | 30 +++ ...iatedModuleWithVariableOfSameName2_test.go | 33 +++ .../gen/semanticClassificationModules_test.go | 24 +++ ...iatedModuleWithVariableOfSameName1_test.go | 23 +++ ...iatedModuleWithVariableOfSameName2_test.go | 29 +++ ...manticClassificationWithUnionTypes_test.go | 28 +++ .../semanticClassificatonTypeAlias_test.go | 20 ++ ...rnClassificationCallableVariables2_test.go | 23 +++ ...ernClassificationCallableVariables_test.go | 24 +++ ...odernClassificationClassProperties_test.go | 22 ++ ...dernClassificationConstructorTypes_test.go | 19 ++ ...anticModernClassificationFunctions_test.go | 20 ++ ...ModernClassificationInfinityAndNaN_test.go | 40 ++++ ...nticModernClassificationInterfaces_test.go | 19 ++ ...emanticModernClassificationMembers_test.go | 24 +++ ...dernClassificationObjectProperties_test.go | 19 ++ ...anticModernClassificationVariables_test.go | 22 ++ 25 files changed, 848 insertions(+), 6 deletions(-) create mode 100644 internal/fourslash/semantictokens.go create mode 100644 internal/fourslash/tests/gen/renameDefaultImport_test.go create mode 100644 internal/fourslash/tests/gen/semanticClassification1_test.go create mode 100644 internal/fourslash/tests/gen/semanticClassification2_test.go create mode 100644 internal/fourslash/tests/gen/semanticClassificationAlias_test.go create mode 100644 internal/fourslash/tests/gen/semanticClassificationClassExpression_test.go create mode 100644 internal/fourslash/tests/gen/semanticClassificationInTemplateExpressions_test.go create mode 100644 internal/fourslash/tests/gen/semanticClassificationInstantiatedModuleWithVariableOfSameName1_test.go create mode 100644 internal/fourslash/tests/gen/semanticClassificationInstantiatedModuleWithVariableOfSameName2_test.go create mode 100644 internal/fourslash/tests/gen/semanticClassificationModules_test.go create mode 100644 internal/fourslash/tests/gen/semanticClassificationUninstantiatedModuleWithVariableOfSameName1_test.go create mode 100644 internal/fourslash/tests/gen/semanticClassificationUninstantiatedModuleWithVariableOfSameName2_test.go create mode 100644 internal/fourslash/tests/gen/semanticClassificationWithUnionTypes_test.go create mode 100644 internal/fourslash/tests/gen/semanticClassificatonTypeAlias_test.go create mode 100644 internal/fourslash/tests/gen/semanticModernClassificationCallableVariables2_test.go create mode 100644 internal/fourslash/tests/gen/semanticModernClassificationCallableVariables_test.go create mode 100644 internal/fourslash/tests/gen/semanticModernClassificationClassProperties_test.go create mode 100644 internal/fourslash/tests/gen/semanticModernClassificationConstructorTypes_test.go create mode 100644 internal/fourslash/tests/gen/semanticModernClassificationFunctions_test.go create mode 100644 internal/fourslash/tests/gen/semanticModernClassificationInfinityAndNaN_test.go create mode 100644 internal/fourslash/tests/gen/semanticModernClassificationInterfaces_test.go create mode 100644 internal/fourslash/tests/gen/semanticModernClassificationMembers_test.go create mode 100644 internal/fourslash/tests/gen/semanticModernClassificationObjectProperties_test.go create mode 100644 internal/fourslash/tests/gen/semanticModernClassificationVariables_test.go diff --git a/internal/fourslash/_scripts/convertFourslash.mts b/internal/fourslash/_scripts/convertFourslash.mts index a825d461f4..37a14ff327 100644 --- a/internal/fourslash/_scripts/convertFourslash.mts +++ b/internal/fourslash/_scripts/convertFourslash.mts @@ -85,13 +85,16 @@ function parseFileContent(filename: string, content: string): GoTest | undefined }; for (const statement of statements) { const result = parseFourslashStatement(statement); + if (result === SKIP_STATEMENT) { + // Skip this statement but continue parsing + continue; + } if (!result) { + // Could not parse this statement - mark file as unparsed unparsedFiles.push(filename); return undefined; } - else { - goTest.commands.push(...result); - } + goTest.commands.push(...result); } return goTest; } @@ -134,11 +137,22 @@ function getTestInput(content: string): string { return `\`${testInput.join("\n")}\``; } +// Sentinel value to indicate a statement should be skipped but parsing should continue +const SKIP_STATEMENT: unique symbol = Symbol("SKIP_STATEMENT"); +type SkipStatement = typeof SKIP_STATEMENT; + /** * 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 `false` if the statement could not be parsed. + * @returns an array of commands if the statement is a valid fourslash command, + * SKIP_STATEMENT if the statement should be skipped but parsing should continue, + * or `undefined` if the statement could not be parsed and the file should be marked as unparsed. */ -function parseFourslashStatement(statement: ts.Statement): Cmd[] | undefined { +function parseFourslashStatement(statement: ts.Statement): Cmd[] | SkipStatement | undefined { + // Skip empty statements (bare semicolons) + if (ts.isEmptyStatement(statement)) { + return SKIP_STATEMENT; + } + if (ts.isVariableStatement(statement)) { // variable declarations (for ranges and markers), e.g. `const range = test.ranges()[0];` return []; @@ -209,6 +223,8 @@ function parseFourslashStatement(statement: ts.Statement): Cmd[] | undefined { case "renameInfoSucceeded": case "renameInfoFailed": return parseRenameInfo(func.text, callExpression.arguments); + case "semanticClassificationsAre": + return parseSemanticClassificationsAre(callExpression.arguments); } } // `goTo....` @@ -1434,6 +1450,67 @@ function parseBaselineSmartSelection(args: ts.NodeArray): Cmd { }; } +function parseSemanticClassificationsAre(args: readonly ts.Expression[]): [VerifySemanticClassificationsCmd] | SkipStatement | undefined { + if (args.length < 1) { + console.error("semanticClassificationsAre requires at least a format argument"); + return undefined; + } + + const formatArg = args[0]; + if (!ts.isStringLiteralLike(formatArg)) { + console.error("semanticClassificationsAre first argument must be a string literal"); + return undefined; + } + + const format = formatArg.text; + + // Only handle "2020" format for semantic tokens + if (format !== "2020") { + // Skip other formats like "original" - return sentinel to continue parsing + return SKIP_STATEMENT; + } + + 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)) { + console.error(`Expected call expression for token at index ${i}`); + return undefined; + } + + if (!ts.isPropertyAccessExpression(arg.expression) || arg.expression.name.text !== "semanticToken") { + console.error(`Expected semanticToken call at index ${i}`); + return undefined; + } + + if (arg.arguments.length < 2) { + console.error(`semanticToken requires 2 arguments at index ${i}`); + return undefined; + } + + const typeArg = arg.arguments[0]; + const textArg = arg.arguments[1]; + + if (!ts.isStringLiteralLike(typeArg) || !ts.isStringLiteralLike(textArg)) { + console.error(`semanticToken arguments must be string literals at index ${i}`); + return undefined; + } + + tokens.push({ + type: typeArg.text, + text: textArg.text, + }); + } + + return [{ + kind: "verifySemanticClassifications", + format, + tokens, + }]; +} + function parseKind(expr: ts.Expression): string | undefined { if (!ts.isStringLiteral(expr)) { console.error(`Expected string literal for kind, got ${expr.getText()}`); @@ -1643,6 +1720,12 @@ interface VerifyRenameInfoCmd { preferences: string; } +interface VerifySemanticClassificationsCmd { + kind: "verifySemanticClassifications"; + format: string; + tokens: Array<{ type: string; text: string; }>; +} + type Cmd = | VerifyCompletionsCmd | VerifyApplyCodeActionFromCompletionCmd @@ -1656,7 +1739,8 @@ type Cmd = | EditCmd | VerifyQuickInfoCmd | VerifyBaselineRenameCmd - | VerifyRenameInfoCmd; + | VerifyRenameInfoCmd + | VerifySemanticClassificationsCmd; function generateVerifyCompletions({ marker, args, isNewIdentifierLocation, andApplyCodeActionArgs }: VerifyCompletionsCmd): string { let expectedList: string; @@ -1753,6 +1837,11 @@ function generateBaselineRename({ kind, args, preferences }: VerifyBaselineRenam } } +function generateSemanticClassifications({ format, tokens }: VerifySemanticClassificationsCmd): string { + const tokensStr = tokens.map(t => `{Type: ${getGoStringLiteral(t.type)}, Text: ${getGoStringLiteral(t.text)}}`).join(", "); + return `f.VerifySemanticTokens(t, []fourslash.SemanticToken{${tokensStr}})`; +} + function generateCmd(cmd: Cmd): string { switch (cmd.kind) { case "verifyCompletions": @@ -1789,6 +1878,8 @@ function generateCmd(cmd: Cmd): string { return `f.VerifyRenameSucceeded(t, ${cmd.preferences})`; case "renameInfoFailed": return `f.VerifyRenameFailed(t, ${cmd.preferences})`; + case "verifySemanticClassifications": + return generateSemanticClassifications(cmd); default: let neverCommand: never = cmd; throw new Error(`Unknown command kind: ${neverCommand as Cmd["kind"]}`); diff --git a/internal/fourslash/semantictokens.go b/internal/fourslash/semantictokens.go new file mode 100644 index 0000000000..416531232f --- /dev/null +++ b/internal/fourslash/semantictokens.go @@ -0,0 +1,195 @@ +package fourslash + +import ( + "fmt" + "strings" + "testing" + + "github.com/microsoft/typescript-go/internal/ls/lsconv" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" +) + +type SemanticToken struct { + Type string + Text string +} + +func (f *FourslashTest) VerifySemanticTokens(t *testing.T, expected []SemanticToken) { + t.Helper() + + // Get capabilities for semantic tokens + tokenTypes := defaultTokenTypes() + tokenModifiers := defaultTokenModifiers() + + trueVal := true + caps := &lsproto.SemanticTokensClientCapabilities{ + Requests: &lsproto.ClientSemanticTokensRequestOptions{ + Full: &lsproto.BooleanOrClientSemanticTokensRequestFullDelta{ + Boolean: &trueVal, + }, + }, + TokenTypes: tokenTypes, + TokenModifiers: tokenModifiers, + Formats: []lsproto.TokenFormat{lsproto.TokenFormatRelative}, + } + + params := &lsproto.SemanticTokensParams{ + TextDocument: lsproto.TextDocumentIdentifier{ + Uri: lsconv.FileNameToDocumentURI(f.activeFilename), + }, + } + + resMsg, result, resultOk := sendRequest(t, f, lsproto.TextDocumentSemanticTokensFullInfo, params) + if resMsg == nil { + t.Fatal("Nil response received for semantic tokens request") + } + if !resultOk { + t.Fatalf("Unexpected response type for semantic tokens request: %T", resMsg.AsResponse().Result) + } + + if result.SemanticTokens == nil { + if len(expected) == 0 { + return + } + t.Fatal("Expected semantic tokens but got nil") + } + + // Decode the semantic tokens + actual := decodeSemanticTokens(f, result.SemanticTokens.Data, caps) + + // Compare with expected + if len(actual) != len(expected) { + t.Fatalf("Expected %d semantic tokens, got %d\n\nExpected:\n%s\n\nActual:\n%s", + len(expected), len(actual), + formatSemanticTokens(expected), + formatSemanticTokens(actual)) + } + + for i, exp := range expected { + act := actual[i] + if exp.Type != act.Type || exp.Text != act.Text { + t.Errorf("Token %d mismatch:\n Expected: {Type: %q, Text: %q}\n Actual: {Type: %q, Text: %q}", + i, exp.Type, exp.Text, act.Type, act.Text) + } + } +} + +func decodeSemanticTokens(f *FourslashTest, data []uint32, caps *lsproto.SemanticTokensClientCapabilities) []SemanticToken { + if len(data)%5 != 0 { + panic(fmt.Sprintf("Invalid semantic tokens data length: %d", len(data))) + } + + scriptInfo := f.scriptInfos[f.activeFilename] + converters := lsconv.NewConverters(lsproto.PositionEncodingKindUTF8, func(_ string) *lsconv.LSPLineMap { + return scriptInfo.lineMap + }) + + var tokens []SemanticToken + prevLine := uint32(0) + prevChar := uint32(0) + + for i := 0; i < len(data); i += 5 { + deltaLine := data[i] + deltaChar := data[i+1] + length := data[i+2] + tokenTypeIdx := data[i+3] + tokenModifiers := data[i+4] + + // Calculate absolute position + line := prevLine + deltaLine + var char uint32 + if deltaLine == 0 { + char = prevChar + deltaChar + } else { + char = deltaChar + } + + // Get token type + if int(tokenTypeIdx) >= len(caps.TokenTypes) { + panic(fmt.Sprintf("Token type index out of range: %d", tokenTypeIdx)) + } + tokenType := caps.TokenTypes[tokenTypeIdx] + + // Get modifiers + var modifiers []string + for i, mod := range caps.TokenModifiers { + if tokenModifiers&(1< 0 { + typeStr = typeStr + "." + strings.Join(modifiers, ".") + } + + // Get the text + startPos := lsproto.Position{Line: line, Character: char} + endPos := lsproto.Position{Line: line, Character: char + length} + startOffset := int(converters.LineAndCharacterToPosition(scriptInfo, startPos)) + endOffset := int(converters.LineAndCharacterToPosition(scriptInfo, endPos)) + text := scriptInfo.content[startOffset:endOffset] + + tokens = append(tokens, SemanticToken{ + Type: typeStr, + Text: text, + }) + + prevLine = line + prevChar = char + } + + return tokens +} + +func formatSemanticTokens(tokens []SemanticToken) string { + var lines []string + for i, tok := range tokens { + lines = append(lines, fmt.Sprintf(" [%d] {Type: %q, Text: %q}", i, tok.Type, tok.Text)) + } + return strings.Join(lines, "\n") +} + +func defaultTokenTypes() []string { + return []string{ + string(lsproto.SemanticTokenTypesnamespace), + string(lsproto.SemanticTokenTypesclass), + string(lsproto.SemanticTokenTypesenum), + string(lsproto.SemanticTokenTypesinterface), + string(lsproto.SemanticTokenTypesstruct), + string(lsproto.SemanticTokenTypestypeParameter), + string(lsproto.SemanticTokenTypestype), + string(lsproto.SemanticTokenTypesparameter), + string(lsproto.SemanticTokenTypesvariable), + string(lsproto.SemanticTokenTypesproperty), + string(lsproto.SemanticTokenTypesenumMember), + string(lsproto.SemanticTokenTypesdecorator), + string(lsproto.SemanticTokenTypesevent), + string(lsproto.SemanticTokenTypesfunction), + string(lsproto.SemanticTokenTypesmethod), + string(lsproto.SemanticTokenTypesmacro), + string(lsproto.SemanticTokenTypeslabel), + string(lsproto.SemanticTokenTypescomment), + string(lsproto.SemanticTokenTypesstring), + string(lsproto.SemanticTokenTypeskeyword), + string(lsproto.SemanticTokenTypesnumber), + string(lsproto.SemanticTokenTypesregexp), + string(lsproto.SemanticTokenTypesoperator), + } +} + +func defaultTokenModifiers() []string { + return []string{ + string(lsproto.SemanticTokenModifiersdeclaration), + string(lsproto.SemanticTokenModifiersdefinition), + string(lsproto.SemanticTokenModifiersreadonly), + string(lsproto.SemanticTokenModifiersstatic), + string(lsproto.SemanticTokenModifiersdeprecated), + string(lsproto.SemanticTokenModifiersabstract), + string(lsproto.SemanticTokenModifiersasync), + string(lsproto.SemanticTokenModifiersmodification), + string(lsproto.SemanticTokenModifiersdocumentation), + string(lsproto.SemanticTokenModifiersdefaultLibrary), + } +} diff --git a/internal/fourslash/tests/gen/renameDefaultImport_test.go b/internal/fourslash/tests/gen/renameDefaultImport_test.go new file mode 100644 index 0000000000..e75a3fb9f5 --- /dev/null +++ b/internal/fourslash/tests/gen/renameDefaultImport_test.go @@ -0,0 +1,27 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestRenameDefaultImport(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: B.ts +[|export default class /*1*/[|{| "isWriteAccess": true, "isDefinition": true, "contextRangeIndex": 0 |}B|] { + test() { + } +}|] +// @Filename: A.ts +[|import /*2*/[|{| "isWriteAccess": true, "isDefinition": true, "contextRangeIndex": 2 |}B|] from "./B";|] +let b = new [|B|](); +b.test();` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyBaselineFindAllReferences(t, "1", "2") + f.VerifyBaselineRename(t, nil /*preferences*/, f.Ranges()[1], f.Ranges()[3], f.Ranges()[4]) + f.VerifyBaselineDocumentHighlights(t, nil /*preferences*/, "1") +} diff --git a/internal/fourslash/tests/gen/semanticClassification1_test.go b/internal/fourslash/tests/gen/semanticClassification1_test.go new file mode 100644 index 0000000000..aca165420a --- /dev/null +++ b/internal/fourslash/tests/gen/semanticClassification1_test.go @@ -0,0 +1,21 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSemanticClassification1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `module /*0*/M { + export interface /*1*/I { + } +} +interface /*2*/X extends /*3*/M./*4*/I { }` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "namespace.declaration", Text: "M"}, {Type: "interface.declaration", Text: "I"}, {Type: "interface.declaration", Text: "X"}, {Type: "namespace", Text: "M"}, {Type: "interface", Text: "I"}}) +} diff --git a/internal/fourslash/tests/gen/semanticClassification2_test.go b/internal/fourslash/tests/gen/semanticClassification2_test.go new file mode 100644 index 0000000000..404e73e388 --- /dev/null +++ b/internal/fourslash/tests/gen/semanticClassification2_test.go @@ -0,0 +1,22 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSemanticClassification2(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `interface /*0*/Thing { + toExponential(): number; +} + +var Thing = 0; +Thing.toExponential();` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "interface.declaration", Text: "Thing"}, {Type: "member.declaration", Text: "toExponential"}, {Type: "variable.declaration", Text: "Thing"}, {Type: "variable", Text: "Thing"}, {Type: "member.defaultLibrary", Text: "toExponential"}}) +} diff --git a/internal/fourslash/tests/gen/semanticClassificationAlias_test.go b/internal/fourslash/tests/gen/semanticClassificationAlias_test.go new file mode 100644 index 0000000000..79e684e4ae --- /dev/null +++ b/internal/fourslash/tests/gen/semanticClassificationAlias_test.go @@ -0,0 +1,23 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSemanticClassificationAlias(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /a.ts +export type x = number; +export class y {}; +// @Filename: /b.ts +import { /*0*/x, /*1*/y } from "./a"; +const v: /*2*/x = /*3*/y;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/b.ts") + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "variable.declaration.readonly", Text: "v"}, {Type: "type", Text: "x"}, {Type: "class", Text: "y"}}) +} diff --git a/internal/fourslash/tests/gen/semanticClassificationClassExpression_test.go b/internal/fourslash/tests/gen/semanticClassificationClassExpression_test.go new file mode 100644 index 0000000000..b34668d8cb --- /dev/null +++ b/internal/fourslash/tests/gen/semanticClassificationClassExpression_test.go @@ -0,0 +1,19 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSemanticClassificationClassExpression(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `var x = class /*0*/C {} +class /*1*/C {} +class /*2*/D extends class /*3*/B{} { }` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "class.declaration", Text: "x"}, {Type: "class", Text: "C"}, {Type: "class.declaration", Text: "C"}, {Type: "class.declaration", Text: "D"}, {Type: "class", Text: "B"}}) +} diff --git a/internal/fourslash/tests/gen/semanticClassificationInTemplateExpressions_test.go b/internal/fourslash/tests/gen/semanticClassificationInTemplateExpressions_test.go new file mode 100644 index 0000000000..e6e445e5fd --- /dev/null +++ b/internal/fourslash/tests/gen/semanticClassificationInTemplateExpressions_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSemanticClassificationInTemplateExpressions(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `module /*0*/M { + export class /*1*/C { + static x; + } + export enum /*2*/E { + E1 = 0 + } +} +` + "`" + `abcd${ /*3*/M./*4*/C.x + /*5*/M./*6*/E.E1}efg` + "`" + `` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "namespace.declaration", Text: "M"}, {Type: "class.declaration", Text: "C"}, {Type: "property.declaration.static", Text: "x"}, {Type: "enum.declaration", Text: "E"}, {Type: "enumMember.declaration.readonly", Text: "E1"}, {Type: "namespace", Text: "M"}, {Type: "class", Text: "C"}, {Type: "property.static", Text: "x"}, {Type: "namespace", Text: "M"}, {Type: "enum", Text: "E"}, {Type: "enumMember.readonly", Text: "E1"}}) +} diff --git a/internal/fourslash/tests/gen/semanticClassificationInstantiatedModuleWithVariableOfSameName1_test.go b/internal/fourslash/tests/gen/semanticClassificationInstantiatedModuleWithVariableOfSameName1_test.go new file mode 100644 index 0000000000..7b269ff85b --- /dev/null +++ b/internal/fourslash/tests/gen/semanticClassificationInstantiatedModuleWithVariableOfSameName1_test.go @@ -0,0 +1,30 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSemanticClassificationInstantiatedModuleWithVariableOfSameName1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `module /*0*/M { + export interface /*1*/I { + } + var x = 10; +} + +var /*2*/M = { + foo: 10, + bar: 20 +} + +var v: /*3*/M./*4*/I; + +var x = /*5*/M;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "namespace.declaration", Text: "M"}, {Type: "interface.declaration", Text: "I"}, {Type: "variable.declaration.local", Text: "x"}, {Type: "variable.declaration", Text: "M"}, {Type: "property.declaration", Text: "foo"}, {Type: "property.declaration", Text: "bar"}, {Type: "variable.declaration", Text: "v"}, {Type: "namespace", Text: "M"}, {Type: "interface", Text: "I"}, {Type: "variable.declaration", Text: "x"}, {Type: "namespace", Text: "M"}}) +} diff --git a/internal/fourslash/tests/gen/semanticClassificationInstantiatedModuleWithVariableOfSameName2_test.go b/internal/fourslash/tests/gen/semanticClassificationInstantiatedModuleWithVariableOfSameName2_test.go new file mode 100644 index 0000000000..0f2985ef2a --- /dev/null +++ b/internal/fourslash/tests/gen/semanticClassificationInstantiatedModuleWithVariableOfSameName2_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSemanticClassificationInstantiatedModuleWithVariableOfSameName2(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `module /*0*/M { + export interface /*1*/I { + } +} + +module /*2*/M { + var x = 10; +} + +var /*3*/M = { + foo: 10, + bar: 20 +} + +var v: /*4*/M./*5*/I; + +var x = /*6*/M;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "namespace.declaration", Text: "M"}, {Type: "interface.declaration", Text: "I"}, {Type: "namespace.declaration", Text: "M"}, {Type: "variable.declaration.local", Text: "x"}, {Type: "variable.declaration", Text: "M"}, {Type: "property.declaration", Text: "foo"}, {Type: "property.declaration", Text: "bar"}, {Type: "variable.declaration", Text: "v"}, {Type: "namespace", Text: "M"}, {Type: "interface", Text: "I"}, {Type: "variable.declaration", Text: "x"}, {Type: "namespace", Text: "M"}}) +} diff --git a/internal/fourslash/tests/gen/semanticClassificationModules_test.go b/internal/fourslash/tests/gen/semanticClassificationModules_test.go new file mode 100644 index 0000000000..26632557cb --- /dev/null +++ b/internal/fourslash/tests/gen/semanticClassificationModules_test.go @@ -0,0 +1,24 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSemanticClassificationModules(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `module /*0*/M { + export var v; + export interface /*1*/I { + } +} + +var x: /*2*/M./*3*/I = /*4*/M.v; +var y = /*5*/M;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "namespace.declaration", Text: "M"}, {Type: "variable.declaration.local", Text: "v"}, {Type: "interface.declaration", Text: "I"}, {Type: "variable.declaration", Text: "x"}, {Type: "namespace", Text: "M"}, {Type: "interface", Text: "I"}, {Type: "namespace", Text: "M"}, {Type: "variable.local", Text: "v"}, {Type: "variable.declaration", Text: "y"}, {Type: "namespace", Text: "M"}}) +} diff --git a/internal/fourslash/tests/gen/semanticClassificationUninstantiatedModuleWithVariableOfSameName1_test.go b/internal/fourslash/tests/gen/semanticClassificationUninstantiatedModuleWithVariableOfSameName1_test.go new file mode 100644 index 0000000000..ee91286e27 --- /dev/null +++ b/internal/fourslash/tests/gen/semanticClassificationUninstantiatedModuleWithVariableOfSameName1_test.go @@ -0,0 +1,23 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSemanticClassificationUninstantiatedModuleWithVariableOfSameName1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `declare module /*0*/M { + interface /*1*/I { + + } +} + +var M = { I: 10 };` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "variable", Text: "M"}, {Type: "interface.declaration", Text: "I"}, {Type: "variable.declaration", Text: "M"}, {Type: "property.declaration", Text: "I"}}) +} diff --git a/internal/fourslash/tests/gen/semanticClassificationUninstantiatedModuleWithVariableOfSameName2_test.go b/internal/fourslash/tests/gen/semanticClassificationUninstantiatedModuleWithVariableOfSameName2_test.go new file mode 100644 index 0000000000..c7933fef17 --- /dev/null +++ b/internal/fourslash/tests/gen/semanticClassificationUninstantiatedModuleWithVariableOfSameName2_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSemanticClassificationUninstantiatedModuleWithVariableOfSameName2(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `module /*0*/M { + export interface /*1*/I { + } +} + +var /*2*/M = { + foo: 10, + bar: 20 +} + +var v: /*3*/M./*4*/I; + +var x = /*5*/M;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "variable", Text: "M"}, {Type: "interface.declaration", Text: "I"}, {Type: "variable.declaration", Text: "M"}, {Type: "property.declaration", Text: "foo"}, {Type: "property.declaration", Text: "bar"}, {Type: "variable.declaration", Text: "v"}, {Type: "variable", Text: "M"}, {Type: "interface", Text: "I"}, {Type: "variable.declaration", Text: "x"}, {Type: "variable", Text: "M"}}) +} diff --git a/internal/fourslash/tests/gen/semanticClassificationWithUnionTypes_test.go b/internal/fourslash/tests/gen/semanticClassificationWithUnionTypes_test.go new file mode 100644 index 0000000000..68ed1b69f8 --- /dev/null +++ b/internal/fourslash/tests/gen/semanticClassificationWithUnionTypes_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSemanticClassificationWithUnionTypes(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `module /*0*/M { + export interface /*1*/I { + } +} + +interface /*2*/I { +} +class /*3*/C { +} + +var M: /*4*/M./*5*/I | /*6*/I | /*7*/C; +var I: typeof M | typeof /*8*/C;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "variable", Text: "M"}, {Type: "interface.declaration", Text: "I"}, {Type: "interface.declaration", Text: "I"}, {Type: "class.declaration", Text: "C"}, {Type: "variable.declaration", Text: "M"}, {Type: "variable", Text: "M"}, {Type: "interface", Text: "I"}, {Type: "interface", Text: "I"}, {Type: "class", Text: "C"}, {Type: "class.declaration", Text: "I"}, {Type: "variable", Text: "M"}, {Type: "class", Text: "C"}}) +} diff --git a/internal/fourslash/tests/gen/semanticClassificatonTypeAlias_test.go b/internal/fourslash/tests/gen/semanticClassificatonTypeAlias_test.go new file mode 100644 index 0000000000..bc409b054d --- /dev/null +++ b/internal/fourslash/tests/gen/semanticClassificatonTypeAlias_test.go @@ -0,0 +1,20 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSemanticClassificatonTypeAlias(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `type /*0*/Alias = number +var x: /*1*/Alias; +var y = {}; +function f(x: /*3*/Alias): /*4*/Alias { return undefined; }` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "type.declaration", Text: "Alias"}, {Type: "variable.declaration", Text: "x"}, {Type: "type", Text: "Alias"}, {Type: "variable.declaration", Text: "y"}, {Type: "type", Text: "Alias"}, {Type: "function.declaration", Text: "f"}, {Type: "parameter.declaration", Text: "x"}, {Type: "type", Text: "Alias"}, {Type: "type", Text: "Alias"}}) +} diff --git a/internal/fourslash/tests/gen/semanticModernClassificationCallableVariables2_test.go b/internal/fourslash/tests/gen/semanticModernClassificationCallableVariables2_test.go new file mode 100644 index 0000000000..ea630e61e2 --- /dev/null +++ b/internal/fourslash/tests/gen/semanticModernClassificationCallableVariables2_test.go @@ -0,0 +1,23 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSemanticModernClassificationCallableVariables2(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `import "node"; +var fs = require("fs") +require.resolve('react'); +require.resolve.paths; +interface LanguageMode { getFoldingRanges?: (d: string) => number[]; }; +function (mode: LanguageMode | undefined) { if (mode && mode.getFoldingRanges) { return mode.getFoldingRanges('a'); }}; +function b(a: () => void) { a(); };` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "variable.declaration", Text: "fs"}, {Type: "interface.declaration", Text: "LanguageMode"}, {Type: "member.declaration", Text: "getFoldingRanges"}, {Type: "parameter.declaration", Text: "d"}, {Type: "parameter.declaration", Text: "mode"}, {Type: "interface", Text: "LanguageMode"}, {Type: "parameter", Text: "mode"}, {Type: "parameter", Text: "mode"}, {Type: "member", Text: "getFoldingRanges"}, {Type: "parameter", Text: "mode"}, {Type: "member", Text: "getFoldingRanges"}, {Type: "function.declaration", Text: "b"}, {Type: "function.declaration", Text: "a"}, {Type: "function", Text: "a"}}) +} diff --git a/internal/fourslash/tests/gen/semanticModernClassificationCallableVariables_test.go b/internal/fourslash/tests/gen/semanticModernClassificationCallableVariables_test.go new file mode 100644 index 0000000000..87ef66af2e --- /dev/null +++ b/internal/fourslash/tests/gen/semanticModernClassificationCallableVariables_test.go @@ -0,0 +1,24 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSemanticModernClassificationCallableVariables(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `class A { onEvent: () => void; } +const x = new A().onEvent; +const match = (s: any) => x(); +const other = match; +match({ other }); +interface B = { (): string; }; var b: B +var s: String; +var t: { (): string; foo: string};` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "class.declaration", Text: "A"}, {Type: "member.declaration", Text: "onEvent"}, {Type: "function.declaration.readonly", Text: "x"}, {Type: "class", Text: "A"}, {Type: "member", Text: "onEvent"}, {Type: "function.declaration.readonly", Text: "match"}, {Type: "parameter.declaration", Text: "s"}, {Type: "function.readonly", Text: "x"}, {Type: "function.declaration.readonly", Text: "other"}, {Type: "function.readonly", Text: "match"}, {Type: "function.readonly", Text: "match"}, {Type: "member.declaration", Text: "other"}, {Type: "interface.declaration", Text: "B"}, {Type: "variable.declaration", Text: "b"}, {Type: "interface", Text: "B"}, {Type: "variable.declaration", Text: "s"}, {Type: "interface.defaultLibrary", Text: "String"}, {Type: "variable.declaration", Text: "t"}, {Type: "property.declaration", Text: "foo"}}) +} diff --git a/internal/fourslash/tests/gen/semanticModernClassificationClassProperties_test.go b/internal/fourslash/tests/gen/semanticModernClassificationClassProperties_test.go new file mode 100644 index 0000000000..9b68057505 --- /dev/null +++ b/internal/fourslash/tests/gen/semanticModernClassificationClassProperties_test.go @@ -0,0 +1,22 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSemanticModernClassificationClassProperties(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `class A { + private y: number; + constructor(public x : number, _y : number) { this.y = _y; } + get z() : number { return this.x + this.y; } + set a(v: number) { } +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "class.declaration", Text: "A"}, {Type: "property.declaration", Text: "y"}, {Type: "parameter.declaration", Text: "x"}, {Type: "parameter.declaration", Text: "_y"}, {Type: "property", Text: "y"}, {Type: "parameter", Text: "_y"}, {Type: "property.declaration", Text: "z"}, {Type: "property", Text: "x"}, {Type: "property", Text: "y"}, {Type: "property.declaration", Text: "a"}, {Type: "parameter.declaration", Text: "v"}}) +} diff --git a/internal/fourslash/tests/gen/semanticModernClassificationConstructorTypes_test.go b/internal/fourslash/tests/gen/semanticModernClassificationConstructorTypes_test.go new file mode 100644 index 0000000000..e1bf1c8406 --- /dev/null +++ b/internal/fourslash/tests/gen/semanticModernClassificationConstructorTypes_test.go @@ -0,0 +1,19 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSemanticModernClassificationConstructorTypes(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `Object.create(null); +const x = Promise.resolve(Number.MAX_VALUE); +if (x instanceof Promise) {}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "class.defaultLibrary", Text: "Object"}, {Type: "member.defaultLibrary", Text: "create"}, {Type: "variable.declaration.readonly", Text: "x"}, {Type: "class.defaultLibrary", Text: "Number"}, {Type: "property.readonly.defaultLibrary", Text: "MAX_VALUE"}, {Type: "variable.readonly", Text: "x"}}) +} diff --git a/internal/fourslash/tests/gen/semanticModernClassificationFunctions_test.go b/internal/fourslash/tests/gen/semanticModernClassificationFunctions_test.go new file mode 100644 index 0000000000..3ca0d42cef --- /dev/null +++ b/internal/fourslash/tests/gen/semanticModernClassificationFunctions_test.go @@ -0,0 +1,20 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSemanticModernClassificationFunctions(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `function foo(p1) { + return foo(Math.abs(p1)) +} +` + "`" + `/${window.location}` + "`" + `.split("/").forEach(s => foo(s));` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "function.declaration", Text: "foo"}, {Type: "parameter.declaration", Text: "p1"}, {Type: "function", Text: "foo"}, {Type: "variable.defaultLibrary", Text: "Math"}, {Type: "member.defaultLibrary", Text: "abs"}, {Type: "parameter", Text: "p1"}, {Type: "member.defaultLibrary", Text: "split"}, {Type: "member.defaultLibrary", Text: "forEach"}, {Type: "parameter.declaration", Text: "s"}, {Type: "function", Text: "foo"}, {Type: "parameter", Text: "s"}}) +} diff --git a/internal/fourslash/tests/gen/semanticModernClassificationInfinityAndNaN_test.go b/internal/fourslash/tests/gen/semanticModernClassificationInfinityAndNaN_test.go new file mode 100644 index 0000000000..353caeed56 --- /dev/null +++ b/internal/fourslash/tests/gen/semanticModernClassificationInfinityAndNaN_test.go @@ -0,0 +1,40 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSemanticModernClassificationInfinityAndNaN(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = ` Infinity; + NaN; + +// Regular properties + +const obj1 = { + Infinity: 100, + NaN: 200, + "-Infinity": 300 +}; + +obj1.Infinity; +obj1.NaN; +obj1["-Infinity"]; + +// Shorthand properties + +const obj2 = { + Infinity, + NaN, +} + +obj2.Infinity; +obj2.NaN;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "variable.declaration.readonly", Text: "obj1"}, {Type: "variable.readonly", Text: "obj1"}, {Type: "variable.readonly", Text: "obj1"}, {Type: "variable.readonly", Text: "obj1"}, {Type: "variable.declaration.readonly", Text: "obj2"}, {Type: "variable.readonly", Text: "obj2"}, {Type: "variable.readonly", Text: "obj2"}}) +} diff --git a/internal/fourslash/tests/gen/semanticModernClassificationInterfaces_test.go b/internal/fourslash/tests/gen/semanticModernClassificationInterfaces_test.go new file mode 100644 index 0000000000..0c3f0ae47d --- /dev/null +++ b/internal/fourslash/tests/gen/semanticModernClassificationInterfaces_test.go @@ -0,0 +1,19 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSemanticModernClassificationInterfaces(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `interface Pos { x: number, y: number }; +const p = { x: 1, y: 2 } as Pos; +const foo = (o: Pos) => o.x + o.y;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "interface.declaration", Text: "Pos"}, {Type: "property.declaration", Text: "x"}, {Type: "property.declaration", Text: "y"}, {Type: "variable.declaration.readonly", Text: "p"}, {Type: "property.declaration", Text: "x"}, {Type: "property.declaration", Text: "y"}, {Type: "interface", Text: "Pos"}, {Type: "function.declaration.readonly", Text: "foo"}, {Type: "parameter.declaration", Text: "o"}, {Type: "interface", Text: "Pos"}, {Type: "parameter", Text: "o"}, {Type: "property", Text: "x"}, {Type: "parameter", Text: "o"}, {Type: "property", Text: "y"}}) +} diff --git a/internal/fourslash/tests/gen/semanticModernClassificationMembers_test.go b/internal/fourslash/tests/gen/semanticModernClassificationMembers_test.go new file mode 100644 index 0000000000..9bfbe5ae98 --- /dev/null +++ b/internal/fourslash/tests/gen/semanticModernClassificationMembers_test.go @@ -0,0 +1,24 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSemanticModernClassificationMembers(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `class A { + static x = 9; + f = 9; + async m() { return A.x + await this.m(); }; + get s() { return this.f; + static t() { return new A().f; }; + constructor() {} +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "class.declaration", Text: "A"}, {Type: "property.declaration.static", Text: "x"}, {Type: "property.declaration", Text: "f"}, {Type: "member.declaration.async", Text: "m"}, {Type: "class", Text: "A"}, {Type: "property.static", Text: "x"}, {Type: "member.async", Text: "m"}, {Type: "property.declaration", Text: "s"}, {Type: "property", Text: "f"}, {Type: "member.declaration.static", Text: "t"}, {Type: "class", Text: "A"}, {Type: "property", Text: "f"}}) +} diff --git a/internal/fourslash/tests/gen/semanticModernClassificationObjectProperties_test.go b/internal/fourslash/tests/gen/semanticModernClassificationObjectProperties_test.go new file mode 100644 index 0000000000..4e9945431b --- /dev/null +++ b/internal/fourslash/tests/gen/semanticModernClassificationObjectProperties_test.go @@ -0,0 +1,19 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSemanticModernClassificationObjectProperties(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `let x = 1, y = 1; +const a1 = { e: 1 }; +var a2 = { x };` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "variable.declaration", Text: "x"}, {Type: "variable.declaration", Text: "y"}, {Type: "variable.declaration.readonly", Text: "a1"}, {Type: "property.declaration", Text: "e"}, {Type: "variable.declaration", Text: "a2"}, {Type: "property.declaration", Text: "x"}}) +} diff --git a/internal/fourslash/tests/gen/semanticModernClassificationVariables_test.go b/internal/fourslash/tests/gen/semanticModernClassificationVariables_test.go new file mode 100644 index 0000000000..081ce04de5 --- /dev/null +++ b/internal/fourslash/tests/gen/semanticModernClassificationVariables_test.go @@ -0,0 +1,22 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSemanticModernClassificationVariables(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = ` var x = 9, y1 = [x]; + try { + for (const s of y1) { x = s } + } catch (e) { + throw y1; + }` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "variable.declaration", Text: "x"}, {Type: "variable.declaration", Text: "y1"}, {Type: "variable", Text: "x"}, {Type: "variable.declaration.readonly.local", Text: "s"}, {Type: "variable", Text: "y1"}, {Type: "variable", Text: "x"}, {Type: "variable.readonly.local", Text: "s"}, {Type: "variable.declaration.local", Text: "e"}, {Type: "variable", Text: "y1"}}) +} From 39a68a31c3719158a53facf526c7471406c33319 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 19:22:54 -0700 Subject: [PATCH 12/29] more testing --- internal/fourslash/fourslash.go | 79 ++++++++++++++++++++++++---- internal/fourslash/semantictokens.go | 79 ++++------------------------ 2 files changed, 80 insertions(+), 78 deletions(-) diff --git a/internal/fourslash/fourslash.go b/internal/fourslash/fourslash.go index 93bade8044..7d1abcfe02 100644 --- a/internal/fourslash/fourslash.go +++ b/internal/fourslash/fourslash.go @@ -50,6 +50,10 @@ type FourslashTest struct { lastKnownMarkerName *string activeFilename string selectionEnd *lsproto.Position + + // Semantic token configuration + semanticTokenTypes []string + semanticTokenModifiers []string } type scriptInfo struct { @@ -183,15 +187,17 @@ func NewFourslash(t *testing.T, capabilities *lsproto.ClientCapabilities, conten }) f := &FourslashTest{ - server: server, - in: inputWriter, - out: outputReader, - testData: &testData, - userPreferences: lsutil.NewDefaultUserPreferences(), // !!! parse default preferences for fourslash case? - vfs: fs, - scriptInfos: scriptInfos, - converters: converters, - baselines: make(map[string]*strings.Builder), + server: server, + in: inputWriter, + out: outputReader, + testData: &testData, + userPreferences: lsutil.NewDefaultUserPreferences(), // !!! parse default preferences for fourslash case? + vfs: fs, + scriptInfos: scriptInfos, + converters: converters, + baselines: make(map[string]*strings.Builder), + semanticTokenTypes: defaultSemanticTokenTypes(), + semanticTokenModifiers: defaultSemanticTokenModifiers(), } // !!! temporary; remove when we have `handleDidChangeConfiguration`/implicit project config support @@ -231,6 +237,49 @@ func (f *FourslashTest) initialize(t *testing.T, capabilities *lsproto.ClientCap sendNotification(t, f, lsproto.InitializedInfo, &lsproto.InitializedParams{}) } +func defaultSemanticTokenTypes() []string { + return []string{ + string(lsproto.SemanticTokenTypesnamespace), + string(lsproto.SemanticTokenTypesclass), + string(lsproto.SemanticTokenTypesenum), + string(lsproto.SemanticTokenTypesinterface), + string(lsproto.SemanticTokenTypesstruct), + string(lsproto.SemanticTokenTypestypeParameter), + string(lsproto.SemanticTokenTypestype), + string(lsproto.SemanticTokenTypesparameter), + string(lsproto.SemanticTokenTypesvariable), + string(lsproto.SemanticTokenTypesproperty), + string(lsproto.SemanticTokenTypesenumMember), + string(lsproto.SemanticTokenTypesdecorator), + string(lsproto.SemanticTokenTypesevent), + string(lsproto.SemanticTokenTypesfunction), + string(lsproto.SemanticTokenTypesmethod), + string(lsproto.SemanticTokenTypesmacro), + string(lsproto.SemanticTokenTypeslabel), + string(lsproto.SemanticTokenTypescomment), + string(lsproto.SemanticTokenTypesstring), + string(lsproto.SemanticTokenTypeskeyword), + string(lsproto.SemanticTokenTypesnumber), + string(lsproto.SemanticTokenTypesregexp), + string(lsproto.SemanticTokenTypesoperator), + } +} + +func defaultSemanticTokenModifiers() []string { + return []string{ + string(lsproto.SemanticTokenModifiersdeclaration), + string(lsproto.SemanticTokenModifiersdefinition), + string(lsproto.SemanticTokenModifiersreadonly), + string(lsproto.SemanticTokenModifiersstatic), + string(lsproto.SemanticTokenModifiersdeprecated), + string(lsproto.SemanticTokenModifiersabstract), + string(lsproto.SemanticTokenModifiersasync), + string(lsproto.SemanticTokenModifiersmodification), + string(lsproto.SemanticTokenModifiersdocumentation), + string(lsproto.SemanticTokenModifiersdefaultLibrary), + } +} + var ( ptrTrue = ptrTo(true) defaultCompletionCapabilities = &lsproto.CompletionClientCapabilities{ @@ -272,6 +321,18 @@ func getCapabilitiesWithDefaults(capabilities *lsproto.ClientCapabilities) *lspr }, } } + if capabilitiesWithDefaults.TextDocument.SemanticTokens == nil { + capabilitiesWithDefaults.TextDocument.SemanticTokens = &lsproto.SemanticTokensClientCapabilities{ + Requests: &lsproto.ClientSemanticTokensRequestOptions{ + Full: &lsproto.BooleanOrClientSemanticTokensRequestFullDelta{ + Boolean: ptrTrue, + }, + }, + TokenTypes: defaultSemanticTokenTypes(), + TokenModifiers: defaultSemanticTokenModifiers(), + Formats: []lsproto.TokenFormat{lsproto.TokenFormatRelative}, + } + } if capabilitiesWithDefaults.Workspace == nil { capabilitiesWithDefaults.Workspace = &lsproto.WorkspaceClientCapabilities{} } diff --git a/internal/fourslash/semantictokens.go b/internal/fourslash/semantictokens.go index 416531232f..d345e4e5eb 100644 --- a/internal/fourslash/semantictokens.go +++ b/internal/fourslash/semantictokens.go @@ -17,22 +17,6 @@ type SemanticToken struct { func (f *FourslashTest) VerifySemanticTokens(t *testing.T, expected []SemanticToken) { t.Helper() - // Get capabilities for semantic tokens - tokenTypes := defaultTokenTypes() - tokenModifiers := defaultTokenModifiers() - - trueVal := true - caps := &lsproto.SemanticTokensClientCapabilities{ - Requests: &lsproto.ClientSemanticTokensRequestOptions{ - Full: &lsproto.BooleanOrClientSemanticTokensRequestFullDelta{ - Boolean: &trueVal, - }, - }, - TokenTypes: tokenTypes, - TokenModifiers: tokenModifiers, - Formats: []lsproto.TokenFormat{lsproto.TokenFormatRelative}, - } - params := &lsproto.SemanticTokensParams{ TextDocument: lsproto.TextDocumentIdentifier{ Uri: lsconv.FileNameToDocumentURI(f.activeFilename), @@ -54,8 +38,8 @@ func (f *FourslashTest) VerifySemanticTokens(t *testing.T, expected []SemanticTo t.Fatal("Expected semantic tokens but got nil") } - // Decode the semantic tokens - actual := decodeSemanticTokens(f, result.SemanticTokens.Data, caps) + // Decode the semantic tokens using token types/modifiers from the test configuration + actual := decodeSemanticTokens(f, result.SemanticTokens.Data, f.semanticTokenTypes, f.semanticTokenModifiers) // Compare with expected if len(actual) != len(expected) { @@ -74,7 +58,7 @@ func (f *FourslashTest) VerifySemanticTokens(t *testing.T, expected []SemanticTo } } -func decodeSemanticTokens(f *FourslashTest, data []uint32, caps *lsproto.SemanticTokensClientCapabilities) []SemanticToken { +func decodeSemanticTokens(f *FourslashTest, data []uint32, tokenTypes, tokenModifiers []string) []SemanticToken { if len(data)%5 != 0 { panic(fmt.Sprintf("Invalid semantic tokens data length: %d", len(data))) } @@ -93,7 +77,7 @@ func decodeSemanticTokens(f *FourslashTest, data []uint32, caps *lsproto.Semanti deltaChar := data[i+1] length := data[i+2] tokenTypeIdx := data[i+3] - tokenModifiers := data[i+4] + tokenModifierMask := data[i+4] // Calculate absolute position line := prevLine + deltaLine @@ -105,21 +89,21 @@ func decodeSemanticTokens(f *FourslashTest, data []uint32, caps *lsproto.Semanti } // Get token type - if int(tokenTypeIdx) >= len(caps.TokenTypes) { + if int(tokenTypeIdx) >= len(tokenTypes) { panic(fmt.Sprintf("Token type index out of range: %d", tokenTypeIdx)) } - tokenType := caps.TokenTypes[tokenTypeIdx] + tokenType := tokenTypes[tokenTypeIdx] // Get modifiers var modifiers []string - for i, mod := range caps.TokenModifiers { - if tokenModifiers&(1< 0 { typeStr = typeStr + "." + strings.Join(modifiers, ".") } @@ -150,46 +134,3 @@ func formatSemanticTokens(tokens []SemanticToken) string { } return strings.Join(lines, "\n") } - -func defaultTokenTypes() []string { - return []string{ - string(lsproto.SemanticTokenTypesnamespace), - string(lsproto.SemanticTokenTypesclass), - string(lsproto.SemanticTokenTypesenum), - string(lsproto.SemanticTokenTypesinterface), - string(lsproto.SemanticTokenTypesstruct), - string(lsproto.SemanticTokenTypestypeParameter), - string(lsproto.SemanticTokenTypestype), - string(lsproto.SemanticTokenTypesparameter), - string(lsproto.SemanticTokenTypesvariable), - string(lsproto.SemanticTokenTypesproperty), - string(lsproto.SemanticTokenTypesenumMember), - string(lsproto.SemanticTokenTypesdecorator), - string(lsproto.SemanticTokenTypesevent), - string(lsproto.SemanticTokenTypesfunction), - string(lsproto.SemanticTokenTypesmethod), - string(lsproto.SemanticTokenTypesmacro), - string(lsproto.SemanticTokenTypeslabel), - string(lsproto.SemanticTokenTypescomment), - string(lsproto.SemanticTokenTypesstring), - string(lsproto.SemanticTokenTypeskeyword), - string(lsproto.SemanticTokenTypesnumber), - string(lsproto.SemanticTokenTypesregexp), - string(lsproto.SemanticTokenTypesoperator), - } -} - -func defaultTokenModifiers() []string { - return []string{ - string(lsproto.SemanticTokenModifiersdeclaration), - string(lsproto.SemanticTokenModifiersdefinition), - string(lsproto.SemanticTokenModifiersreadonly), - string(lsproto.SemanticTokenModifiersstatic), - string(lsproto.SemanticTokenModifiersdeprecated), - string(lsproto.SemanticTokenModifiersabstract), - string(lsproto.SemanticTokenModifiersasync), - string(lsproto.SemanticTokenModifiersmodification), - string(lsproto.SemanticTokenModifiersdocumentation), - string(lsproto.SemanticTokenModifiersdefaultLibrary), - } -} From 8a5d8ec8ce7b1234d5aa2e8f953010178b107d6b Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 19:44:25 -0700 Subject: [PATCH 13/29] more testing --- internal/fourslash/_scripts/convertFourslash.mts | 6 +++++- .../fourslash/tests/gen/semanticClassification2_test.go | 2 +- .../semanticModernClassificationCallableVariables2_test.go | 2 +- .../semanticModernClassificationCallableVariables_test.go | 2 +- .../semanticModernClassificationConstructorTypes_test.go | 2 +- .../tests/gen/semanticModernClassificationFunctions_test.go | 2 +- .../tests/gen/semanticModernClassificationMembers_test.go | 2 +- 7 files changed, 11 insertions(+), 7 deletions(-) diff --git a/internal/fourslash/_scripts/convertFourslash.mts b/internal/fourslash/_scripts/convertFourslash.mts index 37a14ff327..a34dce283d 100644 --- a/internal/fourslash/_scripts/convertFourslash.mts +++ b/internal/fourslash/_scripts/convertFourslash.mts @@ -1498,8 +1498,12 @@ function parseSemanticClassificationsAre(args: readonly ts.Expression[]): [Verif return undefined; } + // 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: typeArg.text, + type: tokenType, text: textArg.text, }); } diff --git a/internal/fourslash/tests/gen/semanticClassification2_test.go b/internal/fourslash/tests/gen/semanticClassification2_test.go index 404e73e388..c41897b10c 100644 --- a/internal/fourslash/tests/gen/semanticClassification2_test.go +++ b/internal/fourslash/tests/gen/semanticClassification2_test.go @@ -18,5 +18,5 @@ func TestSemanticClassification2(t *testing.T) { var Thing = 0; Thing.toExponential();` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "interface.declaration", Text: "Thing"}, {Type: "member.declaration", Text: "toExponential"}, {Type: "variable.declaration", Text: "Thing"}, {Type: "variable", Text: "Thing"}, {Type: "member.defaultLibrary", Text: "toExponential"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "interface.declaration", Text: "Thing"}, {Type: "method.declaration", Text: "toExponential"}, {Type: "variable.declaration", Text: "Thing"}, {Type: "variable", Text: "Thing"}, {Type: "method.defaultLibrary", Text: "toExponential"}}) } diff --git a/internal/fourslash/tests/gen/semanticModernClassificationCallableVariables2_test.go b/internal/fourslash/tests/gen/semanticModernClassificationCallableVariables2_test.go index ea630e61e2..2ef4b34309 100644 --- a/internal/fourslash/tests/gen/semanticModernClassificationCallableVariables2_test.go +++ b/internal/fourslash/tests/gen/semanticModernClassificationCallableVariables2_test.go @@ -19,5 +19,5 @@ interface LanguageMode { getFoldingRanges?: (d: string) => number[]; }; function (mode: LanguageMode | undefined) { if (mode && mode.getFoldingRanges) { return mode.getFoldingRanges('a'); }}; function b(a: () => void) { a(); };` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "variable.declaration", Text: "fs"}, {Type: "interface.declaration", Text: "LanguageMode"}, {Type: "member.declaration", Text: "getFoldingRanges"}, {Type: "parameter.declaration", Text: "d"}, {Type: "parameter.declaration", Text: "mode"}, {Type: "interface", Text: "LanguageMode"}, {Type: "parameter", Text: "mode"}, {Type: "parameter", Text: "mode"}, {Type: "member", Text: "getFoldingRanges"}, {Type: "parameter", Text: "mode"}, {Type: "member", Text: "getFoldingRanges"}, {Type: "function.declaration", Text: "b"}, {Type: "function.declaration", Text: "a"}, {Type: "function", Text: "a"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "variable.declaration", Text: "fs"}, {Type: "interface.declaration", Text: "LanguageMode"}, {Type: "method.declaration", Text: "getFoldingRanges"}, {Type: "parameter.declaration", Text: "d"}, {Type: "parameter.declaration", Text: "mode"}, {Type: "interface", Text: "LanguageMode"}, {Type: "parameter", Text: "mode"}, {Type: "parameter", Text: "mode"}, {Type: "method", Text: "getFoldingRanges"}, {Type: "parameter", Text: "mode"}, {Type: "method", Text: "getFoldingRanges"}, {Type: "function.declaration", Text: "b"}, {Type: "function.declaration", Text: "a"}, {Type: "function", Text: "a"}}) } diff --git a/internal/fourslash/tests/gen/semanticModernClassificationCallableVariables_test.go b/internal/fourslash/tests/gen/semanticModernClassificationCallableVariables_test.go index 87ef66af2e..7ddfa955dc 100644 --- a/internal/fourslash/tests/gen/semanticModernClassificationCallableVariables_test.go +++ b/internal/fourslash/tests/gen/semanticModernClassificationCallableVariables_test.go @@ -20,5 +20,5 @@ interface B = { (): string; }; var b: B var s: String; var t: { (): string; foo: string};` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "class.declaration", Text: "A"}, {Type: "member.declaration", Text: "onEvent"}, {Type: "function.declaration.readonly", Text: "x"}, {Type: "class", Text: "A"}, {Type: "member", Text: "onEvent"}, {Type: "function.declaration.readonly", Text: "match"}, {Type: "parameter.declaration", Text: "s"}, {Type: "function.readonly", Text: "x"}, {Type: "function.declaration.readonly", Text: "other"}, {Type: "function.readonly", Text: "match"}, {Type: "function.readonly", Text: "match"}, {Type: "member.declaration", Text: "other"}, {Type: "interface.declaration", Text: "B"}, {Type: "variable.declaration", Text: "b"}, {Type: "interface", Text: "B"}, {Type: "variable.declaration", Text: "s"}, {Type: "interface.defaultLibrary", Text: "String"}, {Type: "variable.declaration", Text: "t"}, {Type: "property.declaration", Text: "foo"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "class.declaration", Text: "A"}, {Type: "method.declaration", Text: "onEvent"}, {Type: "function.declaration.readonly", Text: "x"}, {Type: "class", Text: "A"}, {Type: "method", Text: "onEvent"}, {Type: "function.declaration.readonly", Text: "match"}, {Type: "parameter.declaration", Text: "s"}, {Type: "function.readonly", Text: "x"}, {Type: "function.declaration.readonly", Text: "other"}, {Type: "function.readonly", Text: "match"}, {Type: "function.readonly", Text: "match"}, {Type: "method.declaration", Text: "other"}, {Type: "interface.declaration", Text: "B"}, {Type: "variable.declaration", Text: "b"}, {Type: "interface", Text: "B"}, {Type: "variable.declaration", Text: "s"}, {Type: "interface.defaultLibrary", Text: "String"}, {Type: "variable.declaration", Text: "t"}, {Type: "property.declaration", Text: "foo"}}) } diff --git a/internal/fourslash/tests/gen/semanticModernClassificationConstructorTypes_test.go b/internal/fourslash/tests/gen/semanticModernClassificationConstructorTypes_test.go index e1bf1c8406..7638823de7 100644 --- a/internal/fourslash/tests/gen/semanticModernClassificationConstructorTypes_test.go +++ b/internal/fourslash/tests/gen/semanticModernClassificationConstructorTypes_test.go @@ -15,5 +15,5 @@ func TestSemanticModernClassificationConstructorTypes(t *testing.T) { const x = Promise.resolve(Number.MAX_VALUE); if (x instanceof Promise) {}` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "class.defaultLibrary", Text: "Object"}, {Type: "member.defaultLibrary", Text: "create"}, {Type: "variable.declaration.readonly", Text: "x"}, {Type: "class.defaultLibrary", Text: "Number"}, {Type: "property.readonly.defaultLibrary", Text: "MAX_VALUE"}, {Type: "variable.readonly", Text: "x"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "class.defaultLibrary", Text: "Object"}, {Type: "method.defaultLibrary", Text: "create"}, {Type: "variable.declaration.readonly", Text: "x"}, {Type: "class.defaultLibrary", Text: "Number"}, {Type: "property.readonly.defaultLibrary", Text: "MAX_VALUE"}, {Type: "variable.readonly", Text: "x"}}) } diff --git a/internal/fourslash/tests/gen/semanticModernClassificationFunctions_test.go b/internal/fourslash/tests/gen/semanticModernClassificationFunctions_test.go index 3ca0d42cef..23400a711a 100644 --- a/internal/fourslash/tests/gen/semanticModernClassificationFunctions_test.go +++ b/internal/fourslash/tests/gen/semanticModernClassificationFunctions_test.go @@ -16,5 +16,5 @@ func TestSemanticModernClassificationFunctions(t *testing.T) { } ` + "`" + `/${window.location}` + "`" + `.split("/").forEach(s => foo(s));` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "function.declaration", Text: "foo"}, {Type: "parameter.declaration", Text: "p1"}, {Type: "function", Text: "foo"}, {Type: "variable.defaultLibrary", Text: "Math"}, {Type: "member.defaultLibrary", Text: "abs"}, {Type: "parameter", Text: "p1"}, {Type: "member.defaultLibrary", Text: "split"}, {Type: "member.defaultLibrary", Text: "forEach"}, {Type: "parameter.declaration", Text: "s"}, {Type: "function", Text: "foo"}, {Type: "parameter", Text: "s"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "function.declaration", Text: "foo"}, {Type: "parameter.declaration", Text: "p1"}, {Type: "function", Text: "foo"}, {Type: "variable.defaultLibrary", Text: "Math"}, {Type: "method.defaultLibrary", Text: "abs"}, {Type: "parameter", Text: "p1"}, {Type: "method.defaultLibrary", Text: "split"}, {Type: "method.defaultLibrary", Text: "forEach"}, {Type: "parameter.declaration", Text: "s"}, {Type: "function", Text: "foo"}, {Type: "parameter", Text: "s"}}) } diff --git a/internal/fourslash/tests/gen/semanticModernClassificationMembers_test.go b/internal/fourslash/tests/gen/semanticModernClassificationMembers_test.go index 9bfbe5ae98..7d9889706a 100644 --- a/internal/fourslash/tests/gen/semanticModernClassificationMembers_test.go +++ b/internal/fourslash/tests/gen/semanticModernClassificationMembers_test.go @@ -20,5 +20,5 @@ func TestSemanticModernClassificationMembers(t *testing.T) { constructor() {} }` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "class.declaration", Text: "A"}, {Type: "property.declaration.static", Text: "x"}, {Type: "property.declaration", Text: "f"}, {Type: "member.declaration.async", Text: "m"}, {Type: "class", Text: "A"}, {Type: "property.static", Text: "x"}, {Type: "member.async", Text: "m"}, {Type: "property.declaration", Text: "s"}, {Type: "property", Text: "f"}, {Type: "member.declaration.static", Text: "t"}, {Type: "class", Text: "A"}, {Type: "property", Text: "f"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "class.declaration", Text: "A"}, {Type: "property.declaration.static", Text: "x"}, {Type: "property.declaration", Text: "f"}, {Type: "method.declaration.async", Text: "m"}, {Type: "class", Text: "A"}, {Type: "property.static", Text: "x"}, {Type: "method.async", Text: "m"}, {Type: "property.declaration", Text: "s"}, {Type: "property", Text: "f"}, {Type: "method.declaration.static", Text: "t"}, {Type: "class", Text: "A"}, {Type: "property", Text: "f"}}) } From dc2c8d3298dc9ed966806d5adc5e03cf93dd7b39 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 20:03:50 -0700 Subject: [PATCH 14/29] local --- internal/fourslash/fourslash.go | 1 + internal/ls/semantictokens.go | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/fourslash/fourslash.go b/internal/fourslash/fourslash.go index 7d1abcfe02..f00735996f 100644 --- a/internal/fourslash/fourslash.go +++ b/internal/fourslash/fourslash.go @@ -277,6 +277,7 @@ func defaultSemanticTokenModifiers() []string { string(lsproto.SemanticTokenModifiersmodification), string(lsproto.SemanticTokenModifiersdocumentation), string(lsproto.SemanticTokenModifiersdefaultLibrary), + "local", } } diff --git a/internal/ls/semantictokens.go b/internal/ls/semantictokens.go index f288a619ca..bca053be8f 100644 --- a/internal/ls/semantictokens.go +++ b/internal/ls/semantictokens.go @@ -53,6 +53,7 @@ var tokenModifiers = []lsproto.SemanticTokenModifiers{ lsproto.SemanticTokenModifiersmodification, lsproto.SemanticTokenModifiersdocumentation, lsproto.SemanticTokenModifiersdefaultLibrary, + "local", } // tokenType represents a semantic token type index @@ -100,6 +101,7 @@ const ( tokenModifierModification tokenModifierDocumentation tokenModifierDefaultLibrary + tokenModifierLocal ) // SemanticTokensLegend returns the legend describing the token types and modifiers. @@ -255,7 +257,7 @@ func (l *LanguageService) collectSemanticTokensInRange(ctx context.Context, c *c } } if (tokenType == tokenTypeVariable || tokenType == tokenTypeFunction) && isLocalDeclaration(decl, file) { - // Local variables get no special modifier in LSP, but we track it internally + tokenModifier |= tokenModifierLocal } declSourceFile := ast.GetSourceFileOfNode(decl) if declSourceFile != nil && program.IsSourceFileDefaultLibrary(tspath.Path(declSourceFile.FileName())) { From becb5f758855343dc557534cb8f7ad0e5e7990fd Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 20:27:45 -0700 Subject: [PATCH 15/29] more --- internal/fourslash/semantictokens.go | 6 +++++- internal/ls/semantictokens.go | 11 ++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/internal/fourslash/semantictokens.go b/internal/fourslash/semantictokens.go index d345e4e5eb..e4ae30ac83 100644 --- a/internal/fourslash/semantictokens.go +++ b/internal/fourslash/semantictokens.go @@ -28,7 +28,11 @@ func (f *FourslashTest) VerifySemanticTokens(t *testing.T, expected []SemanticTo t.Fatal("Nil response received for semantic tokens request") } if !resultOk { - t.Fatalf("Unexpected response type for semantic tokens request: %T", resMsg.AsResponse().Result) + resp := resMsg.AsResponse() + if resp.Error != nil { + t.Fatalf("Semantic tokens request returned error: code=%d, message=%s", resp.Error.Code, resp.Error.Message) + } + t.Fatalf("Unexpected response type for semantic tokens request: %T", resp.Result) } if result.SemanticTokens == nil { diff --git a/internal/ls/semantictokens.go b/internal/ls/semantictokens.go index bca053be8f..3f40ad5cf7 100644 --- a/internal/ls/semantictokens.go +++ b/internal/ls/semantictokens.go @@ -2,6 +2,7 @@ package ls import ( "context" + "fmt" "slices" "github.com/microsoft/typescript-go/internal/ast" @@ -429,7 +430,8 @@ func isLocalDeclaration(decl *ast.Node, sourceFile *ast.SourceFile) bool { if parent != nil && ast.IsVariableDeclarationList(parent) { grandparent := parent.Parent if grandparent != nil { - return (!ast.IsSourceFile(grandparent) || ast.IsCatchClause(grandparent)) && + greatGrandparent := grandparent.Parent + return (!ast.IsSourceFile(greatGrandparent) || ast.IsCatchClause(grandparent)) && ast.GetSourceFileOfNode(decl) == sourceFile } } @@ -561,8 +563,11 @@ func encodeSemanticTokens(tokens []semanticToken, file *ast.SourceFile, converte char := startPos.Character // Verify that positions are strictly increasing (visitor walks in order) - if line < prevLine || (line == prevLine && char <= prevChar) { - panic("semantic tokens: positions must be strictly increasing") + // We need to skip the first token (when prevLine == 0 && prevChar == 0 && line == 0 && char == 0) + if (prevLine != 0 || prevChar != 0) && (line < prevLine || (line == prevLine && char < prevChar)) { + // Debug info to understand the issue + panic(fmt.Sprintf("semantic tokens: positions must be strictly increasing: prev=(%d,%d) current=(%d,%d) for token at offset %d", + prevLine, prevChar, line, char, tokenStart)) } // Encode as: [deltaLine, deltaChar, length, tokenType, tokenModifiers] From 8ccf80eeb7026617a28070e2c1f89e8f2a67f481 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 20:28:14 -0700 Subject: [PATCH 16/29] Fix bad commits --- .vscode/tasks.json | 2 +- .../tests/gen/renameDefaultImport_test.go | 27 ------------------- 2 files changed, 1 insertion(+), 28 deletions(-) delete mode 100644 internal/fourslash/tests/gen/renameDefaultImport_test.go diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 9390282a25..a7233fda96 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -17,7 +17,7 @@ { "label": "Watch", "type": "npm", - "script": "build:watch", + "script": "build:watch:debug", "group": "build", "presentation": { "panel": "dedicated", diff --git a/internal/fourslash/tests/gen/renameDefaultImport_test.go b/internal/fourslash/tests/gen/renameDefaultImport_test.go deleted file mode 100644 index e75a3fb9f5..0000000000 --- a/internal/fourslash/tests/gen/renameDefaultImport_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package fourslash_test - -import ( - "testing" - - "github.com/microsoft/typescript-go/internal/fourslash" - "github.com/microsoft/typescript-go/internal/testutil" -) - -func TestRenameDefaultImport(t *testing.T) { - t.Parallel() - - defer testutil.RecoverAndFail(t, "Panic on fourslash test") - const content = `// @Filename: B.ts -[|export default class /*1*/[|{| "isWriteAccess": true, "isDefinition": true, "contextRangeIndex": 0 |}B|] { - test() { - } -}|] -// @Filename: A.ts -[|import /*2*/[|{| "isWriteAccess": true, "isDefinition": true, "contextRangeIndex": 2 |}B|] from "./B";|] -let b = new [|B|](); -b.test();` - f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifyBaselineFindAllReferences(t, "1", "2") - f.VerifyBaselineRename(t, nil /*preferences*/, f.Ranges()[1], f.Ranges()[3], f.Ranges()[4]) - f.VerifyBaselineDocumentHighlights(t, nil /*preferences*/, "1") -} From eb9a47a6f60ae6717b405c5d00ffdfeefb4f26c2 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 20:37:50 -0700 Subject: [PATCH 17/29] Update --- .../fourslash/_scripts/convertFourslash.mts | 6 +++-- .../tests/gen/semanticClassification1_test.go | 8 ++++++- .../tests/gen/semanticClassification2_test.go | 8 ++++++- .../gen/semanticClassificationAlias_test.go | 6 ++++- ...anticClassificationClassExpression_test.go | 8 ++++++- ...lassificationInTemplateExpressions_test.go | 14 +++++++++++- ...iatedModuleWithVariableOfSameName1_test.go | 14 +++++++++++- ...iatedModuleWithVariableOfSameName2_test.go | 15 ++++++++++++- .../gen/semanticClassificationModules_test.go | 13 ++++++++++- ...iatedModuleWithVariableOfSameName1_test.go | 7 +++++- ...iatedModuleWithVariableOfSameName2_test.go | 13 ++++++++++- ...manticClassificationWithUnionTypes_test.go | 15 ++++++++++++- .../semanticClassificatonTypeAlias_test.go | 12 +++++++++- ...rnClassificationCallableVariables2_test.go | 17 +++++++++++++- ...ernClassificationCallableVariables_test.go | 22 ++++++++++++++++++- ...odernClassificationClassProperties_test.go | 14 +++++++++++- ...dernClassificationConstructorTypes_test.go | 9 +++++++- ...anticModernClassificationFunctions_test.go | 14 +++++++++++- ...ModernClassificationInfinityAndNaN_test.go | 10 ++++++++- ...nticModernClassificationInterfaces_test.go | 17 +++++++++++++- ...emanticModernClassificationMembers_test.go | 15 ++++++++++++- ...dernClassificationObjectProperties_test.go | 9 +++++++- ...anticModernClassificationVariables_test.go | 12 +++++++++- 23 files changed, 254 insertions(+), 24 deletions(-) diff --git a/internal/fourslash/_scripts/convertFourslash.mts b/internal/fourslash/_scripts/convertFourslash.mts index a34dce283d..90cecbe520 100644 --- a/internal/fourslash/_scripts/convertFourslash.mts +++ b/internal/fourslash/_scripts/convertFourslash.mts @@ -1842,8 +1842,10 @@ function generateBaselineRename({ kind, args, preferences }: VerifyBaselineRenam } function generateSemanticClassifications({ format, tokens }: VerifySemanticClassificationsCmd): string { - const tokensStr = tokens.map(t => `{Type: ${getGoStringLiteral(t.type)}, Text: ${getGoStringLiteral(t.text)}}`).join(", "); - return `f.VerifySemanticTokens(t, []fourslash.SemanticToken{${tokensStr}})`; + const tokensStr = tokens.map(t => `{Type: ${getGoStringLiteral(t.type)}, Text: ${getGoStringLiteral(t.text)}}`).join(",\n\t\t"); + return `f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + ${tokensStr}, + })`; } function generateCmd(cmd: Cmd): string { diff --git a/internal/fourslash/tests/gen/semanticClassification1_test.go b/internal/fourslash/tests/gen/semanticClassification1_test.go index aca165420a..2b2b086f2d 100644 --- a/internal/fourslash/tests/gen/semanticClassification1_test.go +++ b/internal/fourslash/tests/gen/semanticClassification1_test.go @@ -17,5 +17,11 @@ func TestSemanticClassification1(t *testing.T) { } interface /*2*/X extends /*3*/M./*4*/I { }` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "namespace.declaration", Text: "M"}, {Type: "interface.declaration", Text: "I"}, {Type: "interface.declaration", Text: "X"}, {Type: "namespace", Text: "M"}, {Type: "interface", Text: "I"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "namespace.declaration", Text: "M"}, + {Type: "interface.declaration", Text: "I"}, + {Type: "interface.declaration", Text: "X"}, + {Type: "namespace", Text: "M"}, + {Type: "interface", Text: "I"}, + }) } diff --git a/internal/fourslash/tests/gen/semanticClassification2_test.go b/internal/fourslash/tests/gen/semanticClassification2_test.go index c41897b10c..ac2faf4e12 100644 --- a/internal/fourslash/tests/gen/semanticClassification2_test.go +++ b/internal/fourslash/tests/gen/semanticClassification2_test.go @@ -18,5 +18,11 @@ func TestSemanticClassification2(t *testing.T) { var Thing = 0; Thing.toExponential();` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "interface.declaration", Text: "Thing"}, {Type: "method.declaration", Text: "toExponential"}, {Type: "variable.declaration", Text: "Thing"}, {Type: "variable", Text: "Thing"}, {Type: "method.defaultLibrary", Text: "toExponential"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "interface.declaration", Text: "Thing"}, + {Type: "method.declaration", Text: "toExponential"}, + {Type: "variable.declaration", Text: "Thing"}, + {Type: "variable", Text: "Thing"}, + {Type: "method.defaultLibrary", Text: "toExponential"}, + }) } diff --git a/internal/fourslash/tests/gen/semanticClassificationAlias_test.go b/internal/fourslash/tests/gen/semanticClassificationAlias_test.go index 79e684e4ae..ad89ca96f3 100644 --- a/internal/fourslash/tests/gen/semanticClassificationAlias_test.go +++ b/internal/fourslash/tests/gen/semanticClassificationAlias_test.go @@ -19,5 +19,9 @@ import { /*0*/x, /*1*/y } from "./a"; const v: /*2*/x = /*3*/y;` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) f.GoToFile(t, "/b.ts") - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "variable.declaration.readonly", Text: "v"}, {Type: "type", Text: "x"}, {Type: "class", Text: "y"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "variable.declaration.readonly", Text: "v"}, + {Type: "type", Text: "x"}, + {Type: "class", Text: "y"}, + }) } diff --git a/internal/fourslash/tests/gen/semanticClassificationClassExpression_test.go b/internal/fourslash/tests/gen/semanticClassificationClassExpression_test.go index b34668d8cb..cfb534053e 100644 --- a/internal/fourslash/tests/gen/semanticClassificationClassExpression_test.go +++ b/internal/fourslash/tests/gen/semanticClassificationClassExpression_test.go @@ -15,5 +15,11 @@ func TestSemanticClassificationClassExpression(t *testing.T) { class /*1*/C {} class /*2*/D extends class /*3*/B{} { }` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "class.declaration", Text: "x"}, {Type: "class", Text: "C"}, {Type: "class.declaration", Text: "C"}, {Type: "class.declaration", Text: "D"}, {Type: "class", Text: "B"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "class.declaration", Text: "x"}, + {Type: "class", Text: "C"}, + {Type: "class.declaration", Text: "C"}, + {Type: "class.declaration", Text: "D"}, + {Type: "class", Text: "B"}, + }) } diff --git a/internal/fourslash/tests/gen/semanticClassificationInTemplateExpressions_test.go b/internal/fourslash/tests/gen/semanticClassificationInTemplateExpressions_test.go index e6e445e5fd..73158e28e4 100644 --- a/internal/fourslash/tests/gen/semanticClassificationInTemplateExpressions_test.go +++ b/internal/fourslash/tests/gen/semanticClassificationInTemplateExpressions_test.go @@ -21,5 +21,17 @@ func TestSemanticClassificationInTemplateExpressions(t *testing.T) { } ` + "`" + `abcd${ /*3*/M./*4*/C.x + /*5*/M./*6*/E.E1}efg` + "`" + `` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "namespace.declaration", Text: "M"}, {Type: "class.declaration", Text: "C"}, {Type: "property.declaration.static", Text: "x"}, {Type: "enum.declaration", Text: "E"}, {Type: "enumMember.declaration.readonly", Text: "E1"}, {Type: "namespace", Text: "M"}, {Type: "class", Text: "C"}, {Type: "property.static", Text: "x"}, {Type: "namespace", Text: "M"}, {Type: "enum", Text: "E"}, {Type: "enumMember.readonly", Text: "E1"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "namespace.declaration", Text: "M"}, + {Type: "class.declaration", Text: "C"}, + {Type: "property.declaration.static", Text: "x"}, + {Type: "enum.declaration", Text: "E"}, + {Type: "enumMember.declaration.readonly", Text: "E1"}, + {Type: "namespace", Text: "M"}, + {Type: "class", Text: "C"}, + {Type: "property.static", Text: "x"}, + {Type: "namespace", Text: "M"}, + {Type: "enum", Text: "E"}, + {Type: "enumMember.readonly", Text: "E1"}, + }) } diff --git a/internal/fourslash/tests/gen/semanticClassificationInstantiatedModuleWithVariableOfSameName1_test.go b/internal/fourslash/tests/gen/semanticClassificationInstantiatedModuleWithVariableOfSameName1_test.go index 7b269ff85b..c0e122f37c 100644 --- a/internal/fourslash/tests/gen/semanticClassificationInstantiatedModuleWithVariableOfSameName1_test.go +++ b/internal/fourslash/tests/gen/semanticClassificationInstantiatedModuleWithVariableOfSameName1_test.go @@ -26,5 +26,17 @@ var v: /*3*/M./*4*/I; var x = /*5*/M;` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "namespace.declaration", Text: "M"}, {Type: "interface.declaration", Text: "I"}, {Type: "variable.declaration.local", Text: "x"}, {Type: "variable.declaration", Text: "M"}, {Type: "property.declaration", Text: "foo"}, {Type: "property.declaration", Text: "bar"}, {Type: "variable.declaration", Text: "v"}, {Type: "namespace", Text: "M"}, {Type: "interface", Text: "I"}, {Type: "variable.declaration", Text: "x"}, {Type: "namespace", Text: "M"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "namespace.declaration", Text: "M"}, + {Type: "interface.declaration", Text: "I"}, + {Type: "variable.declaration.local", Text: "x"}, + {Type: "variable.declaration", Text: "M"}, + {Type: "property.declaration", Text: "foo"}, + {Type: "property.declaration", Text: "bar"}, + {Type: "variable.declaration", Text: "v"}, + {Type: "namespace", Text: "M"}, + {Type: "interface", Text: "I"}, + {Type: "variable.declaration", Text: "x"}, + {Type: "namespace", Text: "M"}, + }) } diff --git a/internal/fourslash/tests/gen/semanticClassificationInstantiatedModuleWithVariableOfSameName2_test.go b/internal/fourslash/tests/gen/semanticClassificationInstantiatedModuleWithVariableOfSameName2_test.go index 0f2985ef2a..fa5c4f32b4 100644 --- a/internal/fourslash/tests/gen/semanticClassificationInstantiatedModuleWithVariableOfSameName2_test.go +++ b/internal/fourslash/tests/gen/semanticClassificationInstantiatedModuleWithVariableOfSameName2_test.go @@ -29,5 +29,18 @@ var v: /*4*/M./*5*/I; var x = /*6*/M;` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "namespace.declaration", Text: "M"}, {Type: "interface.declaration", Text: "I"}, {Type: "namespace.declaration", Text: "M"}, {Type: "variable.declaration.local", Text: "x"}, {Type: "variable.declaration", Text: "M"}, {Type: "property.declaration", Text: "foo"}, {Type: "property.declaration", Text: "bar"}, {Type: "variable.declaration", Text: "v"}, {Type: "namespace", Text: "M"}, {Type: "interface", Text: "I"}, {Type: "variable.declaration", Text: "x"}, {Type: "namespace", Text: "M"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "namespace.declaration", Text: "M"}, + {Type: "interface.declaration", Text: "I"}, + {Type: "namespace.declaration", Text: "M"}, + {Type: "variable.declaration.local", Text: "x"}, + {Type: "variable.declaration", Text: "M"}, + {Type: "property.declaration", Text: "foo"}, + {Type: "property.declaration", Text: "bar"}, + {Type: "variable.declaration", Text: "v"}, + {Type: "namespace", Text: "M"}, + {Type: "interface", Text: "I"}, + {Type: "variable.declaration", Text: "x"}, + {Type: "namespace", Text: "M"}, + }) } diff --git a/internal/fourslash/tests/gen/semanticClassificationModules_test.go b/internal/fourslash/tests/gen/semanticClassificationModules_test.go index 26632557cb..8f85e1ec8c 100644 --- a/internal/fourslash/tests/gen/semanticClassificationModules_test.go +++ b/internal/fourslash/tests/gen/semanticClassificationModules_test.go @@ -20,5 +20,16 @@ func TestSemanticClassificationModules(t *testing.T) { var x: /*2*/M./*3*/I = /*4*/M.v; var y = /*5*/M;` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "namespace.declaration", Text: "M"}, {Type: "variable.declaration.local", Text: "v"}, {Type: "interface.declaration", Text: "I"}, {Type: "variable.declaration", Text: "x"}, {Type: "namespace", Text: "M"}, {Type: "interface", Text: "I"}, {Type: "namespace", Text: "M"}, {Type: "variable.local", Text: "v"}, {Type: "variable.declaration", Text: "y"}, {Type: "namespace", Text: "M"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "namespace.declaration", Text: "M"}, + {Type: "variable.declaration.local", Text: "v"}, + {Type: "interface.declaration", Text: "I"}, + {Type: "variable.declaration", Text: "x"}, + {Type: "namespace", Text: "M"}, + {Type: "interface", Text: "I"}, + {Type: "namespace", Text: "M"}, + {Type: "variable.local", Text: "v"}, + {Type: "variable.declaration", Text: "y"}, + {Type: "namespace", Text: "M"}, + }) } diff --git a/internal/fourslash/tests/gen/semanticClassificationUninstantiatedModuleWithVariableOfSameName1_test.go b/internal/fourslash/tests/gen/semanticClassificationUninstantiatedModuleWithVariableOfSameName1_test.go index ee91286e27..f7d547d245 100644 --- a/internal/fourslash/tests/gen/semanticClassificationUninstantiatedModuleWithVariableOfSameName1_test.go +++ b/internal/fourslash/tests/gen/semanticClassificationUninstantiatedModuleWithVariableOfSameName1_test.go @@ -19,5 +19,10 @@ func TestSemanticClassificationUninstantiatedModuleWithVariableOfSameName1(t *te var M = { I: 10 };` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "variable", Text: "M"}, {Type: "interface.declaration", Text: "I"}, {Type: "variable.declaration", Text: "M"}, {Type: "property.declaration", Text: "I"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "variable", Text: "M"}, + {Type: "interface.declaration", Text: "I"}, + {Type: "variable.declaration", Text: "M"}, + {Type: "property.declaration", Text: "I"}, + }) } diff --git a/internal/fourslash/tests/gen/semanticClassificationUninstantiatedModuleWithVariableOfSameName2_test.go b/internal/fourslash/tests/gen/semanticClassificationUninstantiatedModuleWithVariableOfSameName2_test.go index c7933fef17..22fc560706 100644 --- a/internal/fourslash/tests/gen/semanticClassificationUninstantiatedModuleWithVariableOfSameName2_test.go +++ b/internal/fourslash/tests/gen/semanticClassificationUninstantiatedModuleWithVariableOfSameName2_test.go @@ -25,5 +25,16 @@ var v: /*3*/M./*4*/I; var x = /*5*/M;` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "variable", Text: "M"}, {Type: "interface.declaration", Text: "I"}, {Type: "variable.declaration", Text: "M"}, {Type: "property.declaration", Text: "foo"}, {Type: "property.declaration", Text: "bar"}, {Type: "variable.declaration", Text: "v"}, {Type: "variable", Text: "M"}, {Type: "interface", Text: "I"}, {Type: "variable.declaration", Text: "x"}, {Type: "variable", Text: "M"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "variable", Text: "M"}, + {Type: "interface.declaration", Text: "I"}, + {Type: "variable.declaration", Text: "M"}, + {Type: "property.declaration", Text: "foo"}, + {Type: "property.declaration", Text: "bar"}, + {Type: "variable.declaration", Text: "v"}, + {Type: "variable", Text: "M"}, + {Type: "interface", Text: "I"}, + {Type: "variable.declaration", Text: "x"}, + {Type: "variable", Text: "M"}, + }) } diff --git a/internal/fourslash/tests/gen/semanticClassificationWithUnionTypes_test.go b/internal/fourslash/tests/gen/semanticClassificationWithUnionTypes_test.go index 68ed1b69f8..41b09590a9 100644 --- a/internal/fourslash/tests/gen/semanticClassificationWithUnionTypes_test.go +++ b/internal/fourslash/tests/gen/semanticClassificationWithUnionTypes_test.go @@ -24,5 +24,18 @@ class /*3*/C { var M: /*4*/M./*5*/I | /*6*/I | /*7*/C; var I: typeof M | typeof /*8*/C;` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "variable", Text: "M"}, {Type: "interface.declaration", Text: "I"}, {Type: "interface.declaration", Text: "I"}, {Type: "class.declaration", Text: "C"}, {Type: "variable.declaration", Text: "M"}, {Type: "variable", Text: "M"}, {Type: "interface", Text: "I"}, {Type: "interface", Text: "I"}, {Type: "class", Text: "C"}, {Type: "class.declaration", Text: "I"}, {Type: "variable", Text: "M"}, {Type: "class", Text: "C"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "variable", Text: "M"}, + {Type: "interface.declaration", Text: "I"}, + {Type: "interface.declaration", Text: "I"}, + {Type: "class.declaration", Text: "C"}, + {Type: "variable.declaration", Text: "M"}, + {Type: "variable", Text: "M"}, + {Type: "interface", Text: "I"}, + {Type: "interface", Text: "I"}, + {Type: "class", Text: "C"}, + {Type: "class.declaration", Text: "I"}, + {Type: "variable", Text: "M"}, + {Type: "class", Text: "C"}, + }) } diff --git a/internal/fourslash/tests/gen/semanticClassificatonTypeAlias_test.go b/internal/fourslash/tests/gen/semanticClassificatonTypeAlias_test.go index bc409b054d..af1b77a746 100644 --- a/internal/fourslash/tests/gen/semanticClassificatonTypeAlias_test.go +++ b/internal/fourslash/tests/gen/semanticClassificatonTypeAlias_test.go @@ -16,5 +16,15 @@ var x: /*1*/Alias; var y = {}; function f(x: /*3*/Alias): /*4*/Alias { return undefined; }` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "type.declaration", Text: "Alias"}, {Type: "variable.declaration", Text: "x"}, {Type: "type", Text: "Alias"}, {Type: "variable.declaration", Text: "y"}, {Type: "type", Text: "Alias"}, {Type: "function.declaration", Text: "f"}, {Type: "parameter.declaration", Text: "x"}, {Type: "type", Text: "Alias"}, {Type: "type", Text: "Alias"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "type.declaration", Text: "Alias"}, + {Type: "variable.declaration", Text: "x"}, + {Type: "type", Text: "Alias"}, + {Type: "variable.declaration", Text: "y"}, + {Type: "type", Text: "Alias"}, + {Type: "function.declaration", Text: "f"}, + {Type: "parameter.declaration", Text: "x"}, + {Type: "type", Text: "Alias"}, + {Type: "type", Text: "Alias"}, + }) } diff --git a/internal/fourslash/tests/gen/semanticModernClassificationCallableVariables2_test.go b/internal/fourslash/tests/gen/semanticModernClassificationCallableVariables2_test.go index 2ef4b34309..f208cfe259 100644 --- a/internal/fourslash/tests/gen/semanticModernClassificationCallableVariables2_test.go +++ b/internal/fourslash/tests/gen/semanticModernClassificationCallableVariables2_test.go @@ -19,5 +19,20 @@ interface LanguageMode { getFoldingRanges?: (d: string) => number[]; }; function (mode: LanguageMode | undefined) { if (mode && mode.getFoldingRanges) { return mode.getFoldingRanges('a'); }}; function b(a: () => void) { a(); };` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "variable.declaration", Text: "fs"}, {Type: "interface.declaration", Text: "LanguageMode"}, {Type: "method.declaration", Text: "getFoldingRanges"}, {Type: "parameter.declaration", Text: "d"}, {Type: "parameter.declaration", Text: "mode"}, {Type: "interface", Text: "LanguageMode"}, {Type: "parameter", Text: "mode"}, {Type: "parameter", Text: "mode"}, {Type: "method", Text: "getFoldingRanges"}, {Type: "parameter", Text: "mode"}, {Type: "method", Text: "getFoldingRanges"}, {Type: "function.declaration", Text: "b"}, {Type: "function.declaration", Text: "a"}, {Type: "function", Text: "a"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "variable.declaration", Text: "fs"}, + {Type: "interface.declaration", Text: "LanguageMode"}, + {Type: "method.declaration", Text: "getFoldingRanges"}, + {Type: "parameter.declaration", Text: "d"}, + {Type: "parameter.declaration", Text: "mode"}, + {Type: "interface", Text: "LanguageMode"}, + {Type: "parameter", Text: "mode"}, + {Type: "parameter", Text: "mode"}, + {Type: "method", Text: "getFoldingRanges"}, + {Type: "parameter", Text: "mode"}, + {Type: "method", Text: "getFoldingRanges"}, + {Type: "function.declaration", Text: "b"}, + {Type: "function.declaration", Text: "a"}, + {Type: "function", Text: "a"}, + }) } diff --git a/internal/fourslash/tests/gen/semanticModernClassificationCallableVariables_test.go b/internal/fourslash/tests/gen/semanticModernClassificationCallableVariables_test.go index 7ddfa955dc..4fe28264cd 100644 --- a/internal/fourslash/tests/gen/semanticModernClassificationCallableVariables_test.go +++ b/internal/fourslash/tests/gen/semanticModernClassificationCallableVariables_test.go @@ -20,5 +20,25 @@ interface B = { (): string; }; var b: B var s: String; var t: { (): string; foo: string};` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "class.declaration", Text: "A"}, {Type: "method.declaration", Text: "onEvent"}, {Type: "function.declaration.readonly", Text: "x"}, {Type: "class", Text: "A"}, {Type: "method", Text: "onEvent"}, {Type: "function.declaration.readonly", Text: "match"}, {Type: "parameter.declaration", Text: "s"}, {Type: "function.readonly", Text: "x"}, {Type: "function.declaration.readonly", Text: "other"}, {Type: "function.readonly", Text: "match"}, {Type: "function.readonly", Text: "match"}, {Type: "method.declaration", Text: "other"}, {Type: "interface.declaration", Text: "B"}, {Type: "variable.declaration", Text: "b"}, {Type: "interface", Text: "B"}, {Type: "variable.declaration", Text: "s"}, {Type: "interface.defaultLibrary", Text: "String"}, {Type: "variable.declaration", Text: "t"}, {Type: "property.declaration", Text: "foo"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "class.declaration", Text: "A"}, + {Type: "method.declaration", Text: "onEvent"}, + {Type: "function.declaration.readonly", Text: "x"}, + {Type: "class", Text: "A"}, + {Type: "method", Text: "onEvent"}, + {Type: "function.declaration.readonly", Text: "match"}, + {Type: "parameter.declaration", Text: "s"}, + {Type: "function.readonly", Text: "x"}, + {Type: "function.declaration.readonly", Text: "other"}, + {Type: "function.readonly", Text: "match"}, + {Type: "function.readonly", Text: "match"}, + {Type: "method.declaration", Text: "other"}, + {Type: "interface.declaration", Text: "B"}, + {Type: "variable.declaration", Text: "b"}, + {Type: "interface", Text: "B"}, + {Type: "variable.declaration", Text: "s"}, + {Type: "interface.defaultLibrary", Text: "String"}, + {Type: "variable.declaration", Text: "t"}, + {Type: "property.declaration", Text: "foo"}, + }) } diff --git a/internal/fourslash/tests/gen/semanticModernClassificationClassProperties_test.go b/internal/fourslash/tests/gen/semanticModernClassificationClassProperties_test.go index 9b68057505..2849159725 100644 --- a/internal/fourslash/tests/gen/semanticModernClassificationClassProperties_test.go +++ b/internal/fourslash/tests/gen/semanticModernClassificationClassProperties_test.go @@ -18,5 +18,17 @@ func TestSemanticModernClassificationClassProperties(t *testing.T) { set a(v: number) { } }` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "class.declaration", Text: "A"}, {Type: "property.declaration", Text: "y"}, {Type: "parameter.declaration", Text: "x"}, {Type: "parameter.declaration", Text: "_y"}, {Type: "property", Text: "y"}, {Type: "parameter", Text: "_y"}, {Type: "property.declaration", Text: "z"}, {Type: "property", Text: "x"}, {Type: "property", Text: "y"}, {Type: "property.declaration", Text: "a"}, {Type: "parameter.declaration", Text: "v"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "class.declaration", Text: "A"}, + {Type: "property.declaration", Text: "y"}, + {Type: "parameter.declaration", Text: "x"}, + {Type: "parameter.declaration", Text: "_y"}, + {Type: "property", Text: "y"}, + {Type: "parameter", Text: "_y"}, + {Type: "property.declaration", Text: "z"}, + {Type: "property", Text: "x"}, + {Type: "property", Text: "y"}, + {Type: "property.declaration", Text: "a"}, + {Type: "parameter.declaration", Text: "v"}, + }) } diff --git a/internal/fourslash/tests/gen/semanticModernClassificationConstructorTypes_test.go b/internal/fourslash/tests/gen/semanticModernClassificationConstructorTypes_test.go index 7638823de7..625ce7da36 100644 --- a/internal/fourslash/tests/gen/semanticModernClassificationConstructorTypes_test.go +++ b/internal/fourslash/tests/gen/semanticModernClassificationConstructorTypes_test.go @@ -15,5 +15,12 @@ func TestSemanticModernClassificationConstructorTypes(t *testing.T) { const x = Promise.resolve(Number.MAX_VALUE); if (x instanceof Promise) {}` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "class.defaultLibrary", Text: "Object"}, {Type: "method.defaultLibrary", Text: "create"}, {Type: "variable.declaration.readonly", Text: "x"}, {Type: "class.defaultLibrary", Text: "Number"}, {Type: "property.readonly.defaultLibrary", Text: "MAX_VALUE"}, {Type: "variable.readonly", Text: "x"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "class.defaultLibrary", Text: "Object"}, + {Type: "method.defaultLibrary", Text: "create"}, + {Type: "variable.declaration.readonly", Text: "x"}, + {Type: "class.defaultLibrary", Text: "Number"}, + {Type: "property.readonly.defaultLibrary", Text: "MAX_VALUE"}, + {Type: "variable.readonly", Text: "x"}, + }) } diff --git a/internal/fourslash/tests/gen/semanticModernClassificationFunctions_test.go b/internal/fourslash/tests/gen/semanticModernClassificationFunctions_test.go index 23400a711a..f415e3cff4 100644 --- a/internal/fourslash/tests/gen/semanticModernClassificationFunctions_test.go +++ b/internal/fourslash/tests/gen/semanticModernClassificationFunctions_test.go @@ -16,5 +16,17 @@ func TestSemanticModernClassificationFunctions(t *testing.T) { } ` + "`" + `/${window.location}` + "`" + `.split("/").forEach(s => foo(s));` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "function.declaration", Text: "foo"}, {Type: "parameter.declaration", Text: "p1"}, {Type: "function", Text: "foo"}, {Type: "variable.defaultLibrary", Text: "Math"}, {Type: "method.defaultLibrary", Text: "abs"}, {Type: "parameter", Text: "p1"}, {Type: "method.defaultLibrary", Text: "split"}, {Type: "method.defaultLibrary", Text: "forEach"}, {Type: "parameter.declaration", Text: "s"}, {Type: "function", Text: "foo"}, {Type: "parameter", Text: "s"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "function.declaration", Text: "foo"}, + {Type: "parameter.declaration", Text: "p1"}, + {Type: "function", Text: "foo"}, + {Type: "variable.defaultLibrary", Text: "Math"}, + {Type: "method.defaultLibrary", Text: "abs"}, + {Type: "parameter", Text: "p1"}, + {Type: "method.defaultLibrary", Text: "split"}, + {Type: "method.defaultLibrary", Text: "forEach"}, + {Type: "parameter.declaration", Text: "s"}, + {Type: "function", Text: "foo"}, + {Type: "parameter", Text: "s"}, + }) } diff --git a/internal/fourslash/tests/gen/semanticModernClassificationInfinityAndNaN_test.go b/internal/fourslash/tests/gen/semanticModernClassificationInfinityAndNaN_test.go index 353caeed56..e840048a70 100644 --- a/internal/fourslash/tests/gen/semanticModernClassificationInfinityAndNaN_test.go +++ b/internal/fourslash/tests/gen/semanticModernClassificationInfinityAndNaN_test.go @@ -36,5 +36,13 @@ const obj2 = { obj2.Infinity; obj2.NaN;` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "variable.declaration.readonly", Text: "obj1"}, {Type: "variable.readonly", Text: "obj1"}, {Type: "variable.readonly", Text: "obj1"}, {Type: "variable.readonly", Text: "obj1"}, {Type: "variable.declaration.readonly", Text: "obj2"}, {Type: "variable.readonly", Text: "obj2"}, {Type: "variable.readonly", Text: "obj2"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "variable.declaration.readonly", Text: "obj1"}, + {Type: "variable.readonly", Text: "obj1"}, + {Type: "variable.readonly", Text: "obj1"}, + {Type: "variable.readonly", Text: "obj1"}, + {Type: "variable.declaration.readonly", Text: "obj2"}, + {Type: "variable.readonly", Text: "obj2"}, + {Type: "variable.readonly", Text: "obj2"}, + }) } diff --git a/internal/fourslash/tests/gen/semanticModernClassificationInterfaces_test.go b/internal/fourslash/tests/gen/semanticModernClassificationInterfaces_test.go index 0c3f0ae47d..dd80f8ac7b 100644 --- a/internal/fourslash/tests/gen/semanticModernClassificationInterfaces_test.go +++ b/internal/fourslash/tests/gen/semanticModernClassificationInterfaces_test.go @@ -15,5 +15,20 @@ func TestSemanticModernClassificationInterfaces(t *testing.T) { const p = { x: 1, y: 2 } as Pos; const foo = (o: Pos) => o.x + o.y;` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "interface.declaration", Text: "Pos"}, {Type: "property.declaration", Text: "x"}, {Type: "property.declaration", Text: "y"}, {Type: "variable.declaration.readonly", Text: "p"}, {Type: "property.declaration", Text: "x"}, {Type: "property.declaration", Text: "y"}, {Type: "interface", Text: "Pos"}, {Type: "function.declaration.readonly", Text: "foo"}, {Type: "parameter.declaration", Text: "o"}, {Type: "interface", Text: "Pos"}, {Type: "parameter", Text: "o"}, {Type: "property", Text: "x"}, {Type: "parameter", Text: "o"}, {Type: "property", Text: "y"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "interface.declaration", Text: "Pos"}, + {Type: "property.declaration", Text: "x"}, + {Type: "property.declaration", Text: "y"}, + {Type: "variable.declaration.readonly", Text: "p"}, + {Type: "property.declaration", Text: "x"}, + {Type: "property.declaration", Text: "y"}, + {Type: "interface", Text: "Pos"}, + {Type: "function.declaration.readonly", Text: "foo"}, + {Type: "parameter.declaration", Text: "o"}, + {Type: "interface", Text: "Pos"}, + {Type: "parameter", Text: "o"}, + {Type: "property", Text: "x"}, + {Type: "parameter", Text: "o"}, + {Type: "property", Text: "y"}, + }) } diff --git a/internal/fourslash/tests/gen/semanticModernClassificationMembers_test.go b/internal/fourslash/tests/gen/semanticModernClassificationMembers_test.go index 7d9889706a..2131cf7f78 100644 --- a/internal/fourslash/tests/gen/semanticModernClassificationMembers_test.go +++ b/internal/fourslash/tests/gen/semanticModernClassificationMembers_test.go @@ -20,5 +20,18 @@ func TestSemanticModernClassificationMembers(t *testing.T) { constructor() {} }` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "class.declaration", Text: "A"}, {Type: "property.declaration.static", Text: "x"}, {Type: "property.declaration", Text: "f"}, {Type: "method.declaration.async", Text: "m"}, {Type: "class", Text: "A"}, {Type: "property.static", Text: "x"}, {Type: "method.async", Text: "m"}, {Type: "property.declaration", Text: "s"}, {Type: "property", Text: "f"}, {Type: "method.declaration.static", Text: "t"}, {Type: "class", Text: "A"}, {Type: "property", Text: "f"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "class.declaration", Text: "A"}, + {Type: "property.declaration.static", Text: "x"}, + {Type: "property.declaration", Text: "f"}, + {Type: "method.declaration.async", Text: "m"}, + {Type: "class", Text: "A"}, + {Type: "property.static", Text: "x"}, + {Type: "method.async", Text: "m"}, + {Type: "property.declaration", Text: "s"}, + {Type: "property", Text: "f"}, + {Type: "method.declaration.static", Text: "t"}, + {Type: "class", Text: "A"}, + {Type: "property", Text: "f"}, + }) } diff --git a/internal/fourslash/tests/gen/semanticModernClassificationObjectProperties_test.go b/internal/fourslash/tests/gen/semanticModernClassificationObjectProperties_test.go index 4e9945431b..f8419c7525 100644 --- a/internal/fourslash/tests/gen/semanticModernClassificationObjectProperties_test.go +++ b/internal/fourslash/tests/gen/semanticModernClassificationObjectProperties_test.go @@ -15,5 +15,12 @@ func TestSemanticModernClassificationObjectProperties(t *testing.T) { const a1 = { e: 1 }; var a2 = { x };` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "variable.declaration", Text: "x"}, {Type: "variable.declaration", Text: "y"}, {Type: "variable.declaration.readonly", Text: "a1"}, {Type: "property.declaration", Text: "e"}, {Type: "variable.declaration", Text: "a2"}, {Type: "property.declaration", Text: "x"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "variable.declaration", Text: "x"}, + {Type: "variable.declaration", Text: "y"}, + {Type: "variable.declaration.readonly", Text: "a1"}, + {Type: "property.declaration", Text: "e"}, + {Type: "variable.declaration", Text: "a2"}, + {Type: "property.declaration", Text: "x"}, + }) } diff --git a/internal/fourslash/tests/gen/semanticModernClassificationVariables_test.go b/internal/fourslash/tests/gen/semanticModernClassificationVariables_test.go index 081ce04de5..be892f667d 100644 --- a/internal/fourslash/tests/gen/semanticModernClassificationVariables_test.go +++ b/internal/fourslash/tests/gen/semanticModernClassificationVariables_test.go @@ -18,5 +18,15 @@ func TestSemanticModernClassificationVariables(t *testing.T) { throw y1; }` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) - f.VerifySemanticTokens(t, []fourslash.SemanticToken{{Type: "variable.declaration", Text: "x"}, {Type: "variable.declaration", Text: "y1"}, {Type: "variable", Text: "x"}, {Type: "variable.declaration.readonly.local", Text: "s"}, {Type: "variable", Text: "y1"}, {Type: "variable", Text: "x"}, {Type: "variable.readonly.local", Text: "s"}, {Type: "variable.declaration.local", Text: "e"}, {Type: "variable", Text: "y1"}}) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "variable.declaration", Text: "x"}, + {Type: "variable.declaration", Text: "y1"}, + {Type: "variable", Text: "x"}, + {Type: "variable.declaration.readonly.local", Text: "s"}, + {Type: "variable", Text: "y1"}, + {Type: "variable", Text: "x"}, + {Type: "variable.readonly.local", Text: "s"}, + {Type: "variable.declaration.local", Text: "e"}, + {Type: "variable", Text: "y1"}, + }) } From 8e7168fc8429cc0c1db36fe8159fcc7efcada10e Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 20:41:42 -0700 Subject: [PATCH 18/29] Commit test that the changes fixed --- .../tests/gen/renameDefaultImport_test.go | 27 +++++++++++++++++ .../renameDefaultImport.baseline.jsonc | 6 ++++ .../renameDefaultImport.baseline.jsonc | 25 ++++++++++++++++ .../renameDefaultImport.baseline.jsonc | 27 +++++++++++++++++ .../renameDefaultImport.baseline.jsonc.diff | 29 +++++++++++++++++++ 5 files changed, 114 insertions(+) create mode 100644 internal/fourslash/tests/gen/renameDefaultImport_test.go create mode 100644 testdata/baselines/reference/fourslash/documentHighlights/renameDefaultImport.baseline.jsonc create mode 100644 testdata/baselines/reference/fourslash/findAllReferences/renameDefaultImport.baseline.jsonc create mode 100644 testdata/baselines/reference/submodule/fourslash/findRenameLocations/renameDefaultImport.baseline.jsonc create mode 100644 testdata/baselines/reference/submodule/fourslash/findRenameLocations/renameDefaultImport.baseline.jsonc.diff diff --git a/internal/fourslash/tests/gen/renameDefaultImport_test.go b/internal/fourslash/tests/gen/renameDefaultImport_test.go new file mode 100644 index 0000000000..e75a3fb9f5 --- /dev/null +++ b/internal/fourslash/tests/gen/renameDefaultImport_test.go @@ -0,0 +1,27 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestRenameDefaultImport(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: B.ts +[|export default class /*1*/[|{| "isWriteAccess": true, "isDefinition": true, "contextRangeIndex": 0 |}B|] { + test() { + } +}|] +// @Filename: A.ts +[|import /*2*/[|{| "isWriteAccess": true, "isDefinition": true, "contextRangeIndex": 2 |}B|] from "./B";|] +let b = new [|B|](); +b.test();` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyBaselineFindAllReferences(t, "1", "2") + f.VerifyBaselineRename(t, nil /*preferences*/, f.Ranges()[1], f.Ranges()[3], f.Ranges()[4]) + f.VerifyBaselineDocumentHighlights(t, nil /*preferences*/, "1") +} diff --git a/testdata/baselines/reference/fourslash/documentHighlights/renameDefaultImport.baseline.jsonc b/testdata/baselines/reference/fourslash/documentHighlights/renameDefaultImport.baseline.jsonc new file mode 100644 index 0000000000..0f348b352d --- /dev/null +++ b/testdata/baselines/reference/fourslash/documentHighlights/renameDefaultImport.baseline.jsonc @@ -0,0 +1,6 @@ +// === documentHighlights === +// === /B.ts === +// export default class /*HIGHLIGHTS*/[|B|] { +// test() { +// } +// } \ No newline at end of file diff --git a/testdata/baselines/reference/fourslash/findAllReferences/renameDefaultImport.baseline.jsonc b/testdata/baselines/reference/fourslash/findAllReferences/renameDefaultImport.baseline.jsonc new file mode 100644 index 0000000000..6933ed8935 --- /dev/null +++ b/testdata/baselines/reference/fourslash/findAllReferences/renameDefaultImport.baseline.jsonc @@ -0,0 +1,25 @@ +// === findAllReferences === +// === /A.ts === +// import [|B|] from "./B"; +// let b = new [|B|](); +// b.test(); + +// === /B.ts === +// export default class /*FIND ALL REFS*/[|B|] { +// test() { +// } +// } + + + +// === findAllReferences === +// === /A.ts === +// import /*FIND ALL REFS*/[|B|] from "./B"; +// let b = new [|B|](); +// b.test(); + +// === /B.ts === +// export default class [|B|] { +// test() { +// } +// } \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/fourslash/findRenameLocations/renameDefaultImport.baseline.jsonc b/testdata/baselines/reference/submodule/fourslash/findRenameLocations/renameDefaultImport.baseline.jsonc new file mode 100644 index 0000000000..55265b7c8e --- /dev/null +++ b/testdata/baselines/reference/submodule/fourslash/findRenameLocations/renameDefaultImport.baseline.jsonc @@ -0,0 +1,27 @@ +// === findRenameLocations === +// === /A.ts === +// import [|BRENAME|] from "./B"; +// let b = new [|BRENAME|](); +// b.test(); + +// === /B.ts === +// export default class /*RENAME*/[|BRENAME|] { +// test() { +// } +// } + + + +// === findRenameLocations === +// === /A.ts === +// import /*RENAME*/[|BRENAME|] from "./B"; +// let b = new [|BRENAME|](); +// b.test(); + + + +// === findRenameLocations === +// === /A.ts === +// import [|BRENAME|] from "./B"; +// let b = new /*RENAME*/[|BRENAME|](); +// b.test(); \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/fourslash/findRenameLocations/renameDefaultImport.baseline.jsonc.diff b/testdata/baselines/reference/submodule/fourslash/findRenameLocations/renameDefaultImport.baseline.jsonc.diff new file mode 100644 index 0000000000..32d6d701c7 --- /dev/null +++ b/testdata/baselines/reference/submodule/fourslash/findRenameLocations/renameDefaultImport.baseline.jsonc.diff @@ -0,0 +1,29 @@ +--- old.renameDefaultImport.baseline.jsonc ++++ new.renameDefaultImport.baseline.jsonc +@@= skipped -0, +0 lines =@@ + // === findRenameLocations === ++// === /A.ts === ++// import [|BRENAME|] from "./B"; ++// let b = new [|BRENAME|](); ++// b.test(); ++ + // === /B.ts === + // export default class /*RENAME*/[|BRENAME|] { + // test() { + // } + // } + +-// === /A.ts === +-// import [|BRENAME|] from "./B"; +-// let b = new [|BRENAME|](); +-// b.test(); +- + + + // === findRenameLocations === +@@= skipped -24, +24 lines =@@ + // import [|BRENAME|] from "./B"; + // let b = new /*RENAME*/[|BRENAME|](); + // b.test(); +- +- \ No newline at end of file From 07c09f32e8059ded67a17cafc5e4ec74141b7ae8 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 20:49:27 -0700 Subject: [PATCH 19/29] Fix --- internal/ls/semantictokens.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/ls/semantictokens.go b/internal/ls/semantictokens.go index 3f40ad5cf7..8292348f04 100644 --- a/internal/ls/semantictokens.go +++ b/internal/ls/semantictokens.go @@ -427,6 +427,10 @@ func isLocalDeclaration(decl *ast.Node, sourceFile *ast.SourceFile) bool { } if ast.IsVariableDeclaration(decl) { parent := decl.Parent + // Check if this is a catch clause parameter + if parent != nil && ast.IsCatchClause(parent) { + return ast.GetSourceFileOfNode(decl) == sourceFile + } if parent != nil && ast.IsVariableDeclarationList(parent) { grandparent := parent.Parent if grandparent != nil { From b1e875b7798010b0c746110b0a8b1081037954ce Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 20:54:36 -0700 Subject: [PATCH 20/29] Fix --- internal/ls/semantictokens.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ls/semantictokens.go b/internal/ls/semantictokens.go index 8292348f04..b014efecd4 100644 --- a/internal/ls/semantictokens.go +++ b/internal/ls/semantictokens.go @@ -212,7 +212,7 @@ func (l *LanguageService) collectSemanticTokensInRange(ctx context.Context, c *c inJSXElement = false } - if ast.IsIdentifier(node) && !inJSXElement && !isInImportClause(node) && !isInfinityOrNaNString(node.Text()) { + if ast.IsIdentifier(node) && node.Text() != "" && !inJSXElement && !isInImportClause(node) && !isInfinityOrNaNString(node.Text()) { symbol := c.GetSymbolAtLocation(node) if symbol != nil { // Resolve aliases From 02d247d9dc83abe3a8551082a761794b2acc6401 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 20:58:47 -0700 Subject: [PATCH 21/29] Fix bad test --- internal/fourslash/_scripts/manualTests.txt | 1 + .../semanticModernClassificationFunctions_test.go | 2 ++ 2 files changed, 3 insertions(+) rename internal/fourslash/tests/{gen => manual}/semanticModernClassificationFunctions_test.go (90%) diff --git a/internal/fourslash/_scripts/manualTests.txt b/internal/fourslash/_scripts/manualTests.txt index 492d95e520..dbe68104cd 100644 --- a/internal/fourslash/_scripts/manualTests.txt +++ b/internal/fourslash/_scripts/manualTests.txt @@ -4,4 +4,5 @@ completionsSelfDeclaring1 completionsWithDeprecatedTag4 renameDefaultKeyword renameForDefaultExport01 +semanticModernClassificationFunctions tsxCompletion12 diff --git a/internal/fourslash/tests/gen/semanticModernClassificationFunctions_test.go b/internal/fourslash/tests/manual/semanticModernClassificationFunctions_test.go similarity index 90% rename from internal/fourslash/tests/gen/semanticModernClassificationFunctions_test.go rename to internal/fourslash/tests/manual/semanticModernClassificationFunctions_test.go index f415e3cff4..648f9f5076 100644 --- a/internal/fourslash/tests/gen/semanticModernClassificationFunctions_test.go +++ b/internal/fourslash/tests/manual/semanticModernClassificationFunctions_test.go @@ -23,6 +23,8 @@ func TestSemanticModernClassificationFunctions(t *testing.T) { {Type: "variable.defaultLibrary", Text: "Math"}, {Type: "method.defaultLibrary", Text: "abs"}, {Type: "parameter", Text: "p1"}, + {Type: "variable.defaultLibrary", Text: "window"}, + {Type: "property.defaultLibrary", Text: "location"}, {Type: "method.defaultLibrary", Text: "split"}, {Type: "method.defaultLibrary", Text: "forEach"}, {Type: "parameter.declaration", Text: "s"}, From 083f228685fcb5465830e690f54c7ca88a206bb6 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 21:13:58 -0700 Subject: [PATCH 22/29] Add other tests containing semantic tokens --- .../fourslash/_scripts/convertFourslash.mts | 9 +++- ...ncrementalJsDocAdjustsLengthsRight_test.go | 24 +++++++++++ ...cClassificationForJSDocTemplateTag_test.go | 23 ++++++++++ .../syntacticClassificationWithErrors_test.go | 23 ++++++++++ .../gen/syntacticClassifications1_test.go | 43 +++++++++++++++++++ ...assificationsConflictDiff3Markers1_test.go | 28 ++++++++++++ ...assificationsConflictDiff3Markers2_test.go | 25 +++++++++++ ...ticClassificationsConflictMarkers1_test.go | 26 +++++++++++ ...ticClassificationsConflictMarkers2_test.go | 23 ++++++++++ ...yntacticClassificationsDocComment1_test.go | 20 +++++++++ ...yntacticClassificationsDocComment2_test.go | 20 +++++++++ ...yntacticClassificationsDocComment3_test.go | 20 +++++++++ ...yntacticClassificationsDocComment4_test.go | 21 +++++++++ ...tacticClassificationsForOfKeyword2_test.go | 20 +++++++++ ...tacticClassificationsForOfKeyword3_test.go | 21 +++++++++ ...ntacticClassificationsForOfKeyword_test.go | 20 +++++++++ ...lassificationsFunctionWithComments_test.go | 31 +++++++++++++ .../gen/syntacticClassificationsJsx1_test.go | 25 +++++++++++ .../gen/syntacticClassificationsJsx2_test.go | 25 +++++++++++ ...lassificationsMergeConflictMarker1_test.go | 21 +++++++++ ...tacticClassificationsObjectLiteral_test.go | 38 ++++++++++++++++ ...syntacticClassificationsTemplates1_test.go | 26 +++++++++++ ...syntacticClassificationsTemplates2_test.go | 21 +++++++++ 23 files changed, 552 insertions(+), 1 deletion(-) create mode 100644 internal/fourslash/tests/gen/incrementalJsDocAdjustsLengthsRight_test.go create mode 100644 internal/fourslash/tests/gen/syntacticClassificationForJSDocTemplateTag_test.go create mode 100644 internal/fourslash/tests/gen/syntacticClassificationWithErrors_test.go create mode 100644 internal/fourslash/tests/gen/syntacticClassifications1_test.go create mode 100644 internal/fourslash/tests/gen/syntacticClassificationsConflictDiff3Markers1_test.go create mode 100644 internal/fourslash/tests/gen/syntacticClassificationsConflictDiff3Markers2_test.go create mode 100644 internal/fourslash/tests/gen/syntacticClassificationsConflictMarkers1_test.go create mode 100644 internal/fourslash/tests/gen/syntacticClassificationsConflictMarkers2_test.go create mode 100644 internal/fourslash/tests/gen/syntacticClassificationsDocComment1_test.go create mode 100644 internal/fourslash/tests/gen/syntacticClassificationsDocComment2_test.go create mode 100644 internal/fourslash/tests/gen/syntacticClassificationsDocComment3_test.go create mode 100644 internal/fourslash/tests/gen/syntacticClassificationsDocComment4_test.go create mode 100644 internal/fourslash/tests/gen/syntacticClassificationsForOfKeyword2_test.go create mode 100644 internal/fourslash/tests/gen/syntacticClassificationsForOfKeyword3_test.go create mode 100644 internal/fourslash/tests/gen/syntacticClassificationsForOfKeyword_test.go create mode 100644 internal/fourslash/tests/gen/syntacticClassificationsFunctionWithComments_test.go create mode 100644 internal/fourslash/tests/gen/syntacticClassificationsJsx1_test.go create mode 100644 internal/fourslash/tests/gen/syntacticClassificationsJsx2_test.go create mode 100644 internal/fourslash/tests/gen/syntacticClassificationsMergeConflictMarker1_test.go create mode 100644 internal/fourslash/tests/gen/syntacticClassificationsObjectLiteral_test.go create mode 100644 internal/fourslash/tests/gen/syntacticClassificationsTemplates1_test.go create mode 100644 internal/fourslash/tests/gen/syntacticClassificationsTemplates2_test.go diff --git a/internal/fourslash/_scripts/convertFourslash.mts b/internal/fourslash/_scripts/convertFourslash.mts index 90cecbe520..7280af334c 100644 --- a/internal/fourslash/_scripts/convertFourslash.mts +++ b/internal/fourslash/_scripts/convertFourslash.mts @@ -96,6 +96,10 @@ function parseFileContent(filename: string, content: string): GoTest | undefined } goTest.commands.push(...result); } + // Skip tests that have no commands (e.g., only syntactic classifications) + if (goTest.commands.length === 0) { + return undefined; + } return goTest; } @@ -225,6 +229,8 @@ function parseFourslashStatement(statement: ts.Statement): Cmd[] | SkipStatement return parseRenameInfo(func.text, callExpression.arguments); case "semanticClassificationsAre": return parseSemanticClassificationsAre(callExpression.arguments); + case "syntacticClassificationsAre": + return SKIP_STATEMENT; } } // `goTo....` @@ -1843,8 +1849,9 @@ function generateBaselineRename({ kind, args, preferences }: VerifyBaselineRenam 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}, + ${tokensStr}${maybeComma} })`; } diff --git a/internal/fourslash/tests/gen/incrementalJsDocAdjustsLengthsRight_test.go b/internal/fourslash/tests/gen/incrementalJsDocAdjustsLengthsRight_test.go new file mode 100644 index 0000000000..5075e6a3c8 --- /dev/null +++ b/internal/fourslash/tests/gen/incrementalJsDocAdjustsLengthsRight_test.go @@ -0,0 +1,24 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestIncrementalJsDocAdjustsLengthsRight(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @noLib: true + +/** + * Pad ` + "`" + `str` + "`" + ` to ` + "`" + `width` + "`" + `. + * + * @param {String} str + * @param {Number} wid/*1*/` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "1") + f.Insert(t, "th\n@") +} diff --git a/internal/fourslash/tests/gen/syntacticClassificationForJSDocTemplateTag_test.go b/internal/fourslash/tests/gen/syntacticClassificationForJSDocTemplateTag_test.go new file mode 100644 index 0000000000..564378646f --- /dev/null +++ b/internal/fourslash/tests/gen/syntacticClassificationForJSDocTemplateTag_test.go @@ -0,0 +1,23 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSyntacticClassificationForJSDocTemplateTag(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `/** @template T baring strait */ +function ident: T { +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "function.declaration", Text: "ident"}, + {Type: "typeParameter.declaration", Text: "T"}, + {Type: "typeParameter", Text: "T"}, + }) +} diff --git a/internal/fourslash/tests/gen/syntacticClassificationWithErrors_test.go b/internal/fourslash/tests/gen/syntacticClassificationWithErrors_test.go new file mode 100644 index 0000000000..0c19c22edb --- /dev/null +++ b/internal/fourslash/tests/gen/syntacticClassificationWithErrors_test.go @@ -0,0 +1,23 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSyntacticClassificationWithErrors(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `class A { + a: +} +c =` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "class.declaration", Text: "A"}, + {Type: "property.declaration", Text: "a"}, + }) +} diff --git a/internal/fourslash/tests/gen/syntacticClassifications1_test.go b/internal/fourslash/tests/gen/syntacticClassifications1_test.go new file mode 100644 index 0000000000..c6777efe8c --- /dev/null +++ b/internal/fourslash/tests/gen/syntacticClassifications1_test.go @@ -0,0 +1,43 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSyntacticClassifications1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// comment +module M { + var v = 0 + 1; + var s = "string"; + + class C { + } + + enum E { + } + + interface I { + } + + module M1.M2 { + } +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "namespace.declaration", Text: "M"}, + {Type: "variable.declaration.local", Text: "v"}, + {Type: "variable.declaration.local", Text: "s"}, + {Type: "class.declaration", Text: "C"}, + {Type: "typeParameter.declaration", Text: "T"}, + {Type: "enum.declaration", Text: "E"}, + {Type: "interface.declaration", Text: "I"}, + {Type: "namespace.declaration", Text: "M1"}, + {Type: "namespace.declaration", Text: "M2"}, + }) +} diff --git a/internal/fourslash/tests/gen/syntacticClassificationsConflictDiff3Markers1_test.go b/internal/fourslash/tests/gen/syntacticClassificationsConflictDiff3Markers1_test.go new file mode 100644 index 0000000000..a0515370b0 --- /dev/null +++ b/internal/fourslash/tests/gen/syntacticClassificationsConflictDiff3Markers1_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSyntacticClassificationsConflictDiff3Markers1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `class C { +<<<<<<< HEAD + v = 1; +||||||| merged common ancestors + v = 3; +======= + v = 2; +>>>>>>> Branch - a +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "class.declaration", Text: "C"}, + {Type: "property.declaration", Text: "v"}, + }) +} diff --git a/internal/fourslash/tests/gen/syntacticClassificationsConflictDiff3Markers2_test.go b/internal/fourslash/tests/gen/syntacticClassificationsConflictDiff3Markers2_test.go new file mode 100644 index 0000000000..9f077e27a2 --- /dev/null +++ b/internal/fourslash/tests/gen/syntacticClassificationsConflictDiff3Markers2_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSyntacticClassificationsConflictDiff3Markers2(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `<<<<<<< HEAD +class C { } +||||||| merged common ancestors +class E { } +======= +class D { } +>>>>>>> Branch - a` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "class.declaration", Text: "C"}, + }) +} diff --git a/internal/fourslash/tests/gen/syntacticClassificationsConflictMarkers1_test.go b/internal/fourslash/tests/gen/syntacticClassificationsConflictMarkers1_test.go new file mode 100644 index 0000000000..135947355a --- /dev/null +++ b/internal/fourslash/tests/gen/syntacticClassificationsConflictMarkers1_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSyntacticClassificationsConflictMarkers1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `class C { +<<<<<<< HEAD + v = 1; +======= + v = 2; +>>>>>>> Branch - a +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "class.declaration", Text: "C"}, + {Type: "property.declaration", Text: "v"}, + }) +} diff --git a/internal/fourslash/tests/gen/syntacticClassificationsConflictMarkers2_test.go b/internal/fourslash/tests/gen/syntacticClassificationsConflictMarkers2_test.go new file mode 100644 index 0000000000..78c0c3265c --- /dev/null +++ b/internal/fourslash/tests/gen/syntacticClassificationsConflictMarkers2_test.go @@ -0,0 +1,23 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSyntacticClassificationsConflictMarkers2(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `<<<<<<< HEAD +class C { } +======= +class D { } +>>>>>>> Branch - a` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "class.declaration", Text: "C"}, + }) +} diff --git a/internal/fourslash/tests/gen/syntacticClassificationsDocComment1_test.go b/internal/fourslash/tests/gen/syntacticClassificationsDocComment1_test.go new file mode 100644 index 0000000000..c2af43288f --- /dev/null +++ b/internal/fourslash/tests/gen/syntacticClassificationsDocComment1_test.go @@ -0,0 +1,20 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSyntacticClassificationsDocComment1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `/** @type {number} */ +var v;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "variable.declaration", Text: "v"}, + }) +} diff --git a/internal/fourslash/tests/gen/syntacticClassificationsDocComment2_test.go b/internal/fourslash/tests/gen/syntacticClassificationsDocComment2_test.go new file mode 100644 index 0000000000..e5c22d53c4 --- /dev/null +++ b/internal/fourslash/tests/gen/syntacticClassificationsDocComment2_test.go @@ -0,0 +1,20 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSyntacticClassificationsDocComment2(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `/** @param foo { function(x): string } */ +var v;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "variable.declaration", Text: "v"}, + }) +} diff --git a/internal/fourslash/tests/gen/syntacticClassificationsDocComment3_test.go b/internal/fourslash/tests/gen/syntacticClassificationsDocComment3_test.go new file mode 100644 index 0000000000..a97f441a34 --- /dev/null +++ b/internal/fourslash/tests/gen/syntacticClassificationsDocComment3_test.go @@ -0,0 +1,20 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSyntacticClassificationsDocComment3(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `/** @param foo { number /* } */ +var v;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "variable.declaration", Text: "v"}, + }) +} diff --git a/internal/fourslash/tests/gen/syntacticClassificationsDocComment4_test.go b/internal/fourslash/tests/gen/syntacticClassificationsDocComment4_test.go new file mode 100644 index 0000000000..39b2818d2f --- /dev/null +++ b/internal/fourslash/tests/gen/syntacticClassificationsDocComment4_test.go @@ -0,0 +1,21 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSyntacticClassificationsDocComment4(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `/** @param {number} p1 */ +function foo(p1) {}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "function.declaration", Text: "foo"}, + {Type: "parameter.declaration", Text: "p1"}, + }) +} diff --git a/internal/fourslash/tests/gen/syntacticClassificationsForOfKeyword2_test.go b/internal/fourslash/tests/gen/syntacticClassificationsForOfKeyword2_test.go new file mode 100644 index 0000000000..9caf091dcf --- /dev/null +++ b/internal/fourslash/tests/gen/syntacticClassificationsForOfKeyword2_test.go @@ -0,0 +1,20 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSyntacticClassificationsForOfKeyword2(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `for (var of in of) { }` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "variable.declaration", Text: "of"}, + {Type: "variable", Text: "of"}, + }) +} diff --git a/internal/fourslash/tests/gen/syntacticClassificationsForOfKeyword3_test.go b/internal/fourslash/tests/gen/syntacticClassificationsForOfKeyword3_test.go new file mode 100644 index 0000000000..e9b25dad85 --- /dev/null +++ b/internal/fourslash/tests/gen/syntacticClassificationsForOfKeyword3_test.go @@ -0,0 +1,21 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSyntacticClassificationsForOfKeyword3(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `for (var of; of; of) { }` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "variable.declaration", Text: "of"}, + {Type: "variable", Text: "of"}, + {Type: "variable", Text: "of"}, + }) +} diff --git a/internal/fourslash/tests/gen/syntacticClassificationsForOfKeyword_test.go b/internal/fourslash/tests/gen/syntacticClassificationsForOfKeyword_test.go new file mode 100644 index 0000000000..79a3c567c5 --- /dev/null +++ b/internal/fourslash/tests/gen/syntacticClassificationsForOfKeyword_test.go @@ -0,0 +1,20 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSyntacticClassificationsForOfKeyword(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `for (var of of of) { }` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "variable.declaration", Text: "of"}, + {Type: "variable", Text: "of"}, + }) +} diff --git a/internal/fourslash/tests/gen/syntacticClassificationsFunctionWithComments_test.go b/internal/fourslash/tests/gen/syntacticClassificationsFunctionWithComments_test.go new file mode 100644 index 0000000000..30d904a07e --- /dev/null +++ b/internal/fourslash/tests/gen/syntacticClassificationsFunctionWithComments_test.go @@ -0,0 +1,31 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSyntacticClassificationsFunctionWithComments(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `/** + * This is my function. + * There are many like it, but this one is mine. + */ +function myFunction(/* x */ x: any) { + var y = x ? x++ : ++x; +} +// end of file` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "function.declaration", Text: "myFunction"}, + {Type: "parameter.declaration", Text: "x"}, + {Type: "variable.declaration.local", Text: "y"}, + {Type: "parameter", Text: "x"}, + {Type: "parameter", Text: "x"}, + {Type: "parameter", Text: "x"}, + }) +} diff --git a/internal/fourslash/tests/gen/syntacticClassificationsJsx1_test.go b/internal/fourslash/tests/gen/syntacticClassificationsJsx1_test.go new file mode 100644 index 0000000000..7368082074 --- /dev/null +++ b/internal/fourslash/tests/gen/syntacticClassificationsJsx1_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSyntacticClassificationsJsx1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: file1.tsx +let x =
+ some jsx text +
; + +let y = ` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "variable.declaration", Text: "x"}, + {Type: "variable.declaration", Text: "y"}, + }) +} diff --git a/internal/fourslash/tests/gen/syntacticClassificationsJsx2_test.go b/internal/fourslash/tests/gen/syntacticClassificationsJsx2_test.go new file mode 100644 index 0000000000..fafe59372d --- /dev/null +++ b/internal/fourslash/tests/gen/syntacticClassificationsJsx2_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSyntacticClassificationsJsx2(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: file1.tsx +let x = + some jsx text +; + +let y = ` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "variable.declaration", Text: "x"}, + {Type: "variable.declaration", Text: "y"}, + }) +} diff --git a/internal/fourslash/tests/gen/syntacticClassificationsMergeConflictMarker1_test.go b/internal/fourslash/tests/gen/syntacticClassificationsMergeConflictMarker1_test.go new file mode 100644 index 0000000000..8185f8aa3b --- /dev/null +++ b/internal/fourslash/tests/gen/syntacticClassificationsMergeConflictMarker1_test.go @@ -0,0 +1,21 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSyntacticClassificationsMergeConflictMarker1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `<<<<<<< HEAD +"AAAA" +======= +"BBBB" +>>>>>>> Feature` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{}) +} diff --git a/internal/fourslash/tests/gen/syntacticClassificationsObjectLiteral_test.go b/internal/fourslash/tests/gen/syntacticClassificationsObjectLiteral_test.go new file mode 100644 index 0000000000..ae5da78faa --- /dev/null +++ b/internal/fourslash/tests/gen/syntacticClassificationsObjectLiteral_test.go @@ -0,0 +1,38 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSyntacticClassificationsObjectLiteral(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `var v = 10e0; +var x = { + p1: 1, + p2: 2, + any: 3, + function: 4, + var: 5, + void: void 0, + v: v += v, +};` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "variable.declaration", Text: "v"}, + {Type: "variable.declaration", Text: "x"}, + {Type: "property.declaration", Text: "p1"}, + {Type: "property.declaration", Text: "p2"}, + {Type: "property.declaration", Text: "any"}, + {Type: "property.declaration", Text: "function"}, + {Type: "property.declaration", Text: "var"}, + {Type: "property.declaration", Text: "void"}, + {Type: "property.declaration", Text: "v"}, + {Type: "variable", Text: "v"}, + {Type: "variable", Text: "v"}, + }) +} diff --git a/internal/fourslash/tests/gen/syntacticClassificationsTemplates1_test.go b/internal/fourslash/tests/gen/syntacticClassificationsTemplates1_test.go new file mode 100644 index 0000000000..8c394d46f9 --- /dev/null +++ b/internal/fourslash/tests/gen/syntacticClassificationsTemplates1_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSyntacticClassificationsTemplates1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `var v = 10e0; +var x = { + p1: ` + "`" + `hello world` + "`" + `, + p2: ` + "`" + `goodbye ${0} cruel ${0} world` + "`" + `, +};` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "variable.declaration", Text: "v"}, + {Type: "variable.declaration", Text: "x"}, + {Type: "property.declaration", Text: "p1"}, + {Type: "property.declaration", Text: "p2"}, + }) +} diff --git a/internal/fourslash/tests/gen/syntacticClassificationsTemplates2_test.go b/internal/fourslash/tests/gen/syntacticClassificationsTemplates2_test.go new file mode 100644 index 0000000000..cfb65d64e8 --- /dev/null +++ b/internal/fourslash/tests/gen/syntacticClassificationsTemplates2_test.go @@ -0,0 +1,21 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSyntacticClassificationsTemplates2(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `var tiredOfCanonicalExamples = +` + "`" + `goodbye "${ ` + "`" + `hello world` + "`" + ` }" +and ${ ` + "`" + `good${ " " }riddance` + "`" + ` }` + "`" + `;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "variable.declaration", Text: "tiredOfCanonicalExamples"}, + }) +} From c2ca82a1c809fb4667429134c1d24903368ab0e0 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 21:17:45 -0700 Subject: [PATCH 23/29] Simplifications --- internal/ls/semantictokens.go | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/internal/ls/semantictokens.go b/internal/ls/semantictokens.go index b014efecd4..455e45aff8 100644 --- a/internal/ls/semantictokens.go +++ b/internal/ls/semantictokens.go @@ -204,11 +204,9 @@ func (l *LanguageService) collectSemanticTokensInRange(ctx context.Context, c *c return false } - prevInJSXElement := inJSXElement if ast.IsJsxElement(node) || ast.IsJsxSelfClosingElement(node) { inJSXElement = true - } - if ast.IsJsxExpression(node) { + } else if ast.IsJsxExpression(node) { inJSXElement = false } @@ -284,7 +282,6 @@ func (l *LanguageService) collectSemanticTokensInRange(ctx context.Context, c *c } node.ForEachChild(visit) - inJSXElement = prevInJSXElement return false } @@ -320,13 +317,13 @@ func classifySymbol(symbol *ast.Symbol, meaning ast.SemanticMeaning) (tokenType, // Check the value declaration decl := symbol.ValueDeclaration - if decl == nil && symbol.Declarations != nil && len(symbol.Declarations) > 0 { + if decl == nil && len(symbol.Declarations) > 0 { decl = symbol.Declarations[0] } - if decl != nil && ast.IsBindingElement(decl) { - decl = getDeclarationForBindingElement(decl) - } if decl != nil { + if ast.IsBindingElement(decl) { + decl = getDeclarationForBindingElement(decl) + } if tokenType := tokenFromDeclarationMapping(decl.Kind); tokenType >= 0 { return tokenType, true } @@ -455,8 +452,6 @@ func getDeclarationForBindingElement(element *ast.Node) *ast.Node { element = grandparent continue } - } - if parent != nil && ast.IsBindingPattern(parent) { return parent.Parent } return element @@ -465,10 +460,7 @@ func getDeclarationForBindingElement(element *ast.Node) *ast.Node { func isInImportClause(node *ast.Node) bool { parent := node.Parent - if parent == nil { - return false - } - return ast.IsImportClause(parent) || ast.IsImportSpecifier(parent) || ast.IsNamespaceImport(parent) + return parent != nil && (ast.IsImportClause(parent) || ast.IsImportSpecifier(parent) || ast.IsNamespaceImport(parent)) } func isExpressionInCallExpression(node *ast.Node) bool { @@ -493,15 +485,9 @@ func encodeSemanticTokens(tokens []semanticToken, file *ast.SourceFile, converte if clientCapabilities != nil { // Map server token types to client-supported indices clientIdx := uint32(0) - for _, serverType := range tokenTypes { + for i, serverType := range tokenTypes { if slices.Contains(clientCapabilities.TokenTypes, string(serverType)) { - // Find the server index for this type - for i, t := range tokenTypes { - if t == serverType { - typeMapping[tokenType(i)] = clientIdx - break - } - } + typeMapping[tokenType(i)] = clientIdx clientIdx++ } } From 6211ca37660f33509f53fc1854652e9a290f0b56 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 21:21:55 -0700 Subject: [PATCH 24/29] Logic fix --- internal/ls/semantictokens.go | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/internal/ls/semantictokens.go b/internal/ls/semantictokens.go index 455e45aff8..7d9ad8f6e8 100644 --- a/internal/ls/semantictokens.go +++ b/internal/ls/semantictokens.go @@ -396,22 +396,21 @@ func reclassifyByType(c *checker.Checker, node *ast.Node, tt tokenType) tokenTyp } // Check for call signatures (function-like) - if test(func(t *checker.Type) bool { - callSigs := c.GetSignaturesOfType(t, checker.SignatureKindCall) - if len(callSigs) == 0 { - return false - } - // Must have call signatures and no properties (or be used in call context) - objType := t.AsObjectType() - if objType == nil { - return true - } - return len(objType.Properties()) == 0 || isExpressionInCallExpression(node) - }) { - if tt == tokenTypeProperty { - return tokenTypeMethod + // Must have call signatures AND (no properties OR be used in call context) + hasCallSignatures := test(func(t *checker.Type) bool { + return len(c.GetSignaturesOfType(t, checker.SignatureKindCall)) > 0 + }) + if hasCallSignatures { + hasNoProperties := !test(func(t *checker.Type) bool { + objType := t.AsObjectType() + return objType != nil && len(objType.Properties()) > 0 + }) + if hasNoProperties || isExpressionInCallExpression(node) { + if tt == tokenTypeProperty { + return tokenTypeMethod + } + return tokenTypeFunction } - return tokenTypeFunction } } } From b286156836381a8fe02c477458dfdc3d42eced71 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 21:28:02 -0700 Subject: [PATCH 25/29] Doc update --- internal/ls/semantictokens.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/internal/ls/semantictokens.go b/internal/ls/semantictokens.go index 7d9ad8f6e8..09e286a2e6 100644 --- a/internal/ls/semantictokens.go +++ b/internal/ls/semantictokens.go @@ -57,10 +57,8 @@ var tokenModifiers = []lsproto.SemanticTokenModifiers{ "local", } -// tokenType represents a semantic token type index type tokenType int -// Token type indices const ( tokenTypeNamespace tokenType = iota tokenTypeClass @@ -76,7 +74,7 @@ const ( tokenTypeDecorator tokenTypeEvent tokenTypeFunction - tokenTypeMethod + tokenTypeMethod // Previously called "member" in TypeScript tokenTypeMacro tokenTypeLabel tokenTypeComment @@ -87,10 +85,8 @@ const ( tokenTypeOperator ) -// tokenModifier represents a semantic token modifier bit mask type tokenModifier int -// Token modifier bit masks const ( tokenModifierDeclaration tokenModifier = 1 << iota tokenModifierDefinition @@ -231,12 +227,12 @@ func (l *LanguageService) collectSemanticTokensInRange(ctx context.Context, c *c } } - // Reclassify parameters as properties in property access context + // Property declaration in constructor: reclassify parameters as properties in property access context if tokenType == tokenTypeParameter && ast.IsRightSideOfQualifiedNameOrPropertyAccess(node) { tokenType = tokenTypeProperty } - // Reclassify based on type information + // Type-based reclassification tokenType = reclassifyByType(c, node, tokenType) // Get the value declaration to check modifiers From 47c272241691fd40cf8689f451abfed8edd0dffa Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 22:11:54 -0700 Subject: [PATCH 26/29] Fix JSX issue and add a test for it --- .../tests/semanticClassificationJSX_test.go | 25 +++++++++++++++++++ internal/ls/semantictokens.go | 2 ++ 2 files changed, 27 insertions(+) create mode 100644 internal/fourslash/tests/semanticClassificationJSX_test.go diff --git a/internal/fourslash/tests/semanticClassificationJSX_test.go b/internal/fourslash/tests/semanticClassificationJSX_test.go new file mode 100644 index 0000000000..6298471045 --- /dev/null +++ b/internal/fourslash/tests/semanticClassificationJSX_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestSemanticClassificationJSX(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /a.tsx +const Component = () =>
Hello
; +const afterJSX = 42; +const alsoAfterJSX = "test";` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/a.tsx") + f.VerifySemanticTokens(t, []fourslash.SemanticToken{ + {Type: "function.declaration.readonly", Text: "Component"}, + {Type: "variable.declaration.readonly", Text: "afterJSX"}, + {Type: "variable.declaration.readonly", Text: "alsoAfterJSX"}, + }) +} diff --git a/internal/ls/semantictokens.go b/internal/ls/semantictokens.go index 09e286a2e6..ecece68b86 100644 --- a/internal/ls/semantictokens.go +++ b/internal/ls/semantictokens.go @@ -200,6 +200,7 @@ func (l *LanguageService) collectSemanticTokensInRange(ctx context.Context, c *c return false } + prevInJSXElement := inJSXElement if ast.IsJsxElement(node) || ast.IsJsxSelfClosingElement(node) { inJSXElement = true } else if ast.IsJsxExpression(node) { @@ -278,6 +279,7 @@ func (l *LanguageService) collectSemanticTokensInRange(ctx context.Context, c *c } node.ForEachChild(visit) + inJSXElement = prevInJSXElement return false } From a11189ff7034d82e5c935d3007501518c014b8e1 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 30 Oct 2025 22:19:15 -0700 Subject: [PATCH 27/29] Address ordering check issue --- internal/ls/semantictokens.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/ls/semantictokens.go b/internal/ls/semantictokens.go index ecece68b86..3e4041d953 100644 --- a/internal/ls/semantictokens.go +++ b/internal/ls/semantictokens.go @@ -550,9 +550,7 @@ func encodeSemanticTokens(tokens []semanticToken, file *ast.SourceFile, converte char := startPos.Character // Verify that positions are strictly increasing (visitor walks in order) - // We need to skip the first token (when prevLine == 0 && prevChar == 0 && line == 0 && char == 0) - if (prevLine != 0 || prevChar != 0) && (line < prevLine || (line == prevLine && char < prevChar)) { - // Debug info to understand the issue + if len(encoded) > 0 && (line < prevLine || (line == prevLine && char <= prevChar)) { panic(fmt.Sprintf("semantic tokens: positions must be strictly increasing: prev=(%d,%d) current=(%d,%d) for token at offset %d", prevLine, prevChar, line, char, tokenStart)) } From 903dbfe754b927e4a32546cceebfb78bf6bca8c3 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Fri, 31 Oct 2025 10:07:02 -0700 Subject: [PATCH 28/29] Panic on multi-line tokens --- internal/ls/semantictokens.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/ls/semantictokens.go b/internal/ls/semantictokens.go index 3e4041d953..3554d9f03a 100644 --- a/internal/ls/semantictokens.go +++ b/internal/ls/semantictokens.go @@ -542,8 +542,8 @@ func encodeSemanticTokens(tokens []semanticToken, file *ast.SourceFile, converte if startPos.Line == endPos.Line { tokenLength = endPos.Character - startPos.Character } else { - // Multi-line tokens shouldn't happen for identifiers, but handle it - tokenLength = endPos.Character + panic(fmt.Sprintf("semantic tokens: token spans multiple lines: start=(%d,%d) end=(%d,%d) for token at offset %d", + startPos.Line, startPos.Character, endPos.Line, endPos.Character, tokenStart)) } line := startPos.Line From 7eff852e8b8b7cf29e2b65201f934fff82449120 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Fri, 31 Oct 2025 10:08:10 -0700 Subject: [PATCH 29/29] Reuse helper --- internal/lsp/server.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/internal/lsp/server.go b/internal/lsp/server.go index c72adefed0..981925a3ae 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -932,18 +932,12 @@ func (s *Server) handleSelectionRange(ctx context.Context, ls *ls.LanguageServic } func (s *Server) handleSemanticTokensFull(ctx context.Context, ls *ls.LanguageService, params *lsproto.SemanticTokensParams) (lsproto.SemanticTokensResponse, error) { - var clientCapabilities *lsproto.SemanticTokensClientCapabilities - if s.initializeParams.Capabilities != nil && s.initializeParams.Capabilities.TextDocument != nil { - clientCapabilities = s.initializeParams.Capabilities.TextDocument.SemanticTokens - } + clientCapabilities := getSemanticTokensClientCapabilities(s.initializeParams) return ls.ProvideSemanticTokens(ctx, params.TextDocument.Uri, clientCapabilities) } func (s *Server) handleSemanticTokensRange(ctx context.Context, ls *ls.LanguageService, params *lsproto.SemanticTokensRangeParams) (lsproto.SemanticTokensRangeResponse, error) { - var clientCapabilities *lsproto.SemanticTokensClientCapabilities - if s.initializeParams.Capabilities != nil && s.initializeParams.Capabilities.TextDocument != nil { - clientCapabilities = s.initializeParams.Capabilities.TextDocument.SemanticTokens - } + clientCapabilities := getSemanticTokensClientCapabilities(s.initializeParams) return ls.ProvideSemanticTokensRange(ctx, params.TextDocument.Uri, params.Range, clientCapabilities) }