diff --git a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts index 38a87159a32..2824a880d4c 100644 --- a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts +++ b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts @@ -6,44 +6,63 @@ import { TextDocument, SemanticTokenData } from './languageModes'; import * as ts from 'typescript'; +export function getSemanticTokenLegend() { + if (tokenTypes.length !== TokenType._) { + console.warn('TokenType has added new entries.'); + } + if (tokenModifiers.length !== TokenModifier._) { + console.warn('TokenModifier has added new entries.'); + } + return { types: tokenTypes, modifiers: tokenModifiers }; +} export function getSemanticTokens(jsLanguageService: ts.LanguageService, currentTextDocument: TextDocument, fileName: string): SemanticTokenData[] { //https://ts-ast-viewer.com/#code/AQ0g2CmAuwGbALzAJwG4BQZQGNwEMBnQ4AQQEYBmYAb2C22zgEtJwATJVTRxgcwD27AQAp8AGmAAjAJS0A9POB8+7NQ168oscAJz5wANXwAnLug2bsJmAFcTAO2XAA1MHyvgu-UdOeWbOw8ViAAvpagocBAA let resultTokens: SemanticTokenData[] = []; + const collector = (node: ts.Node, typeIdx: number, modifierSet: number) => { + resultTokens.push({ start: currentTextDocument.positionAt(node.getStart()), length: node.getWidth(), typeIdx, modifierSet }); + }; + collectTokens(jsLanguageService, fileName, { start: 0, length: currentTextDocument.getText().length }, collector); + + return resultTokens; +} + +function collectTokens(jsLanguageService: ts.LanguageService, fileName: string, span: ts.TextSpan, collector: (node: ts.Node, tokenType: number, tokenModifier: number) => void) { const program = jsLanguageService.getProgram(); if (program) { const typeChecker = program.getTypeChecker(); function visit(node: ts.Node) { + if (!node || !ts.textSpanIntersectsWith(span, node.pos, node.getFullWidth())) { + return; + } if (ts.isIdentifier(node)) { const symbol = typeChecker.getSymbolAtLocation(node); if (symbol) { let typeIdx = classifySymbol(symbol); - if (typeIdx !== undefined) { - let modifierSet = 0; if (node.parent) { const parentTypeIdx = tokenFromDeclarationMapping[node.parent.kind]; if (parentTypeIdx === typeIdx && (node.parent).name === node) { - modifierSet = TokenModifier.declaration; + modifierSet = 1 << TokenModifier.declaration; } } const decl = symbol.valueDeclaration; const modifiers = decl ? ts.getCombinedModifierFlags(decl) : 0; const nodeFlags = decl ? ts.getCombinedNodeFlags(decl) : 0; if (modifiers & ts.ModifierFlags.Static) { - modifierSet |= TokenModifier.static; + modifierSet |= 1 << TokenModifier.static; } if (modifiers & ts.ModifierFlags.Async) { - modifierSet |= TokenModifier.async; + modifierSet |= 1 << TokenModifier.async; } if ((modifiers & ts.ModifierFlags.Readonly) || (nodeFlags & ts.NodeFlags.Const) || (symbol.getFlags() & ts.SymbolFlags.EnumMember)) { - modifierSet |= TokenModifier.readonly; + modifierSet |= 1 << TokenModifier.readonly; } - resultTokens.push({ start: currentTextDocument.positionAt(node.getStart()), length: node.getWidth(), typeIdx, modifierSet }); + collector(node, typeIdx, modifierSet); } } } @@ -55,12 +74,9 @@ export function getSemanticTokens(jsLanguageService: ts.LanguageService, current visit(sourceFile); } } - - return resultTokens; } function classifySymbol(symbol: ts.Symbol) { - const flags = symbol.getFlags(); if (flags & ts.SymbolFlags.Class) { return TokenType.class; @@ -79,38 +95,32 @@ function classifySymbol(symbol: ts.Symbol) { return decl && tokenFromDeclarationMapping[decl.kind]; } - - -export function getSemanticTokenLegend() { - return { types: tokenTypes, modifiers: tokenModifiers }; +export const enum TokenType { + class, enum, interface, namespace, typeParameter, type, parameter, variable, property, function, member, _ } - -const tokenTypes: string[] = ['class', 'enum', 'interface', 'namespace', 'typeParameter', 'type', 'parameter', 'variable', 'property', 'constant', 'function', 'member']; -const tokenModifiers: string[] = ['declaration', 'static', 'async', 'readonly']; - -const enum TokenType { - 'class' = 0, - 'enum' = 1, - 'interface' = 2, - 'namespace' = 3, - 'typeParameter' = 4, - 'type' = 5, - 'parameter' = 6, - 'variable' = 7, - 'property' = 8, - 'constant' = 9, - 'function' = 10, - 'member' = 11 +export const enum TokenModifier { + declaration, static, async, readonly, _ } +const tokenTypes: string[] = []; +tokenTypes[TokenType.class] = 'class'; +tokenTypes[TokenType.enum] = 'enum'; +tokenTypes[TokenType.interface] = 'interface'; +tokenTypes[TokenType.namespace] = 'namespace'; +tokenTypes[TokenType.typeParameter] = 'typeParameter'; +tokenTypes[TokenType.type] = 'type'; +tokenTypes[TokenType.parameter] = 'parameter'; +tokenTypes[TokenType.variable] = 'variable'; +tokenTypes[TokenType.property] = 'property'; +tokenTypes[TokenType.function] = 'function'; +tokenTypes[TokenType.member] = 'member'; -const enum TokenModifier { - 'declaration' = 0x01, - 'static' = 0x02, - 'async' = 0x04, - 'readonly' = 0x08, -} +const tokenModifiers: string[] = []; +tokenModifiers[TokenModifier.async] = 'async'; +tokenModifiers[TokenModifier.declaration] = 'declaration'; +tokenModifiers[TokenModifier.readonly] = 'readonly'; +tokenModifiers[TokenModifier.static] = 'static'; const tokenFromDeclarationMapping: { [name: string]: TokenType } = { [ts.SyntaxKind.VariableDeclaration]: TokenType.variable,