• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import {
2    BindingElement, CancellationToken, Classifications, ClassifiedSpan2020, createTextSpan, Debug, Declaration,
3    EndOfLineState, forEachChild, getCombinedModifierFlags, getCombinedNodeFlags, getMeaningFromLocation,
4    isBindingElement, isCallExpression, isCatchClause, isFunctionDeclaration, isIdentifier, isImportClause,
5    isImportSpecifier, isInfinityOrNaNString, isJsxElement, isJsxExpression, isJsxSelfClosingElement, isNamespaceImport,
6    isPropertyAccessExpression, isQualifiedName, isSourceFile, isVariableDeclaration, Map, ModifierFlags,
7    NamedDeclaration, Node, NodeFlags, ParameterDeclaration, Program, SemanticMeaning, SourceFile, Symbol, SymbolFlags,
8    SyntaxKind, TextSpan, textSpanIntersectsWith, Type, TypeChecker, VariableDeclaration,
9} from "./_namespaces/ts";
10
11/** @internal */
12export const enum TokenEncodingConsts {
13    typeOffset = 8,
14    modifierMask = (1 << typeOffset) - 1
15}
16
17/** @internal */
18export const enum TokenType {
19    class, enum, interface, namespace, typeParameter, type, parameter, variable, enumMember, property, function, member
20}
21
22/** @internal */
23export const enum TokenModifier {
24    declaration, static, async, readonly, defaultLibrary, local
25}
26
27/**
28 * This is mainly used internally for testing
29 *
30 * @internal
31 */
32export function getSemanticClassifications(program: Program, cancellationToken: CancellationToken, sourceFile: SourceFile, span: TextSpan): ClassifiedSpan2020[] {
33    const classifications = getEncodedSemanticClassifications(program, cancellationToken, sourceFile, span);
34
35    Debug.assert(classifications.spans.length % 3 === 0);
36    const dense = classifications.spans;
37    const result: ClassifiedSpan2020[] = [];
38    for (let i = 0; i < dense.length; i += 3) {
39        result.push({
40            textSpan: createTextSpan(dense[i], dense[i + 1]),
41            classificationType: dense[i + 2]
42        });
43    }
44
45    return result;
46}
47
48/** @internal */
49export function getEncodedSemanticClassifications(program: Program, cancellationToken: CancellationToken, sourceFile: SourceFile, span: TextSpan): Classifications {
50    return {
51        spans: getSemanticTokens(program, sourceFile, span, cancellationToken),
52        endOfLineState: EndOfLineState.None
53    };
54}
55
56function getSemanticTokens(program: Program, sourceFile: SourceFile, span: TextSpan, cancellationToken: CancellationToken): number[] {
57    const resultTokens: number[] = [];
58
59    const collector = (node: Node, typeIdx: number, modifierSet: number) => {
60        resultTokens.push(node.getStart(sourceFile), node.getWidth(sourceFile), ((typeIdx + 1) << TokenEncodingConsts.typeOffset) + modifierSet);
61    };
62
63    if (program && sourceFile) {
64        collectTokens(program, sourceFile, span, collector, cancellationToken);
65    }
66    return resultTokens;
67}
68
69function collectTokens(program: Program, sourceFile: SourceFile, span: TextSpan, collector: (node: Node, tokenType: number, tokenModifier: number) => void, cancellationToken: CancellationToken) {
70    const typeChecker = program.getTypeChecker();
71
72    let inJSXElement = false;
73
74    function visit(node: Node) {
75        switch(node.kind) {
76            case SyntaxKind.ModuleDeclaration:
77            case SyntaxKind.ClassDeclaration:
78            case SyntaxKind.InterfaceDeclaration:
79            case SyntaxKind.FunctionDeclaration:
80            case SyntaxKind.ClassExpression:
81            case SyntaxKind.FunctionExpression:
82            case SyntaxKind.ArrowFunction:
83                cancellationToken.throwIfCancellationRequested();
84        }
85
86        if (!node || !textSpanIntersectsWith(span, node.pos, node.getFullWidth()) || node.getFullWidth() === 0) {
87            return;
88        }
89        const prevInJSXElement = inJSXElement;
90        if (isJsxElement(node) || isJsxSelfClosingElement(node)) {
91            inJSXElement = true;
92        }
93        if (isJsxExpression(node)) {
94            inJSXElement = false;
95        }
96
97        if (isIdentifier(node) && !inJSXElement && !inImportClause(node) && !isInfinityOrNaNString(node.escapedText)) {
98            let symbol = typeChecker.getSymbolAtLocation(node);
99            if (symbol) {
100                if (symbol.flags & SymbolFlags.Alias) {
101                    symbol = typeChecker.getAliasedSymbol(symbol);
102                }
103                let typeIdx = classifySymbol(symbol, getMeaningFromLocation(node));
104                if (typeIdx !== undefined) {
105                    let modifierSet = 0;
106                    if (node.parent) {
107                        const parentIsDeclaration = (isBindingElement(node.parent) || tokenFromDeclarationMapping.get(node.parent.kind) === typeIdx);
108                        if (parentIsDeclaration && (node.parent as NamedDeclaration).name === node) {
109                            modifierSet = 1 << TokenModifier.declaration;
110                        }
111                    }
112
113                    // property declaration in constructor
114                    if (typeIdx === TokenType.parameter && isRightSideOfQualifiedNameOrPropertyAccess(node)) {
115                        typeIdx = TokenType.property;
116                    }
117
118                    typeIdx = reclassifyByType(typeChecker, node, typeIdx);
119
120                    const decl = symbol.valueDeclaration;
121                    if (decl) {
122                        const modifiers = getCombinedModifierFlags(decl);
123                        const nodeFlags = getCombinedNodeFlags(decl);
124                        if (modifiers & ModifierFlags.Static) {
125                            modifierSet |= 1 << TokenModifier.static;
126                        }
127                        if (modifiers & ModifierFlags.Async) {
128                            modifierSet |= 1 << TokenModifier.async;
129                        }
130                        if (typeIdx !== TokenType.class && typeIdx !== TokenType.interface) {
131                            if ((modifiers & ModifierFlags.Readonly) || (nodeFlags & NodeFlags.Const) || (symbol.getFlags() & SymbolFlags.EnumMember)) {
132                                modifierSet |= 1 << TokenModifier.readonly;
133                            }
134                        }
135                        if ((typeIdx === TokenType.variable || typeIdx === TokenType.function) && isLocalDeclaration(decl, sourceFile)) {
136                            modifierSet |= 1 << TokenModifier.local;
137                        }
138                        if (program.isSourceFileDefaultLibrary(decl.getSourceFile())) {
139                            modifierSet |= 1 << TokenModifier.defaultLibrary;
140                        }
141                    }
142                    else if (symbol.declarations && symbol.declarations.some(d => program.isSourceFileDefaultLibrary(d.getSourceFile()))) {
143                        modifierSet |= 1 << TokenModifier.defaultLibrary;
144                    }
145
146                    collector(node, typeIdx, modifierSet);
147
148                }
149            }
150        }
151        if (node.virtual) {
152            return;
153        }
154        forEachChild(node, visit);
155
156        inJSXElement = prevInJSXElement;
157    }
158    visit(sourceFile);
159}
160
161function classifySymbol(symbol: Symbol, meaning: SemanticMeaning): TokenType | undefined {
162    const flags = symbol.getFlags();
163    if (flags & SymbolFlags.Class) {
164        return TokenType.class;
165    }
166    else if (flags & SymbolFlags.Enum) {
167        return TokenType.enum;
168    }
169     else if (flags & SymbolFlags.TypeAlias) {
170        return TokenType.type;
171    }
172    else if (flags & SymbolFlags.Interface) {
173        if (meaning & SemanticMeaning.Type) {
174            return TokenType.interface;
175        }
176    }
177    else if (flags & SymbolFlags.TypeParameter) {
178        return TokenType.typeParameter;
179    }
180    let decl = symbol.valueDeclaration || symbol.declarations && symbol.declarations[0];
181    if (decl && isBindingElement(decl)) {
182        decl = getDeclarationForBindingElement(decl);
183    }
184    return decl && tokenFromDeclarationMapping.get(decl.kind);
185}
186
187function reclassifyByType(typeChecker: TypeChecker, node: Node, typeIdx: TokenType): TokenType {
188    // type based classifications
189    if (typeIdx === TokenType.variable || typeIdx === TokenType.property || typeIdx === TokenType.parameter) {
190        const type = typeChecker.getTypeAtLocation(node);
191        if (type) {
192            const test = (condition: (type: Type) => boolean) => {
193                return condition(type) || type.isUnion() && type.types.some(condition);
194            };
195            if (typeIdx !== TokenType.parameter && test(t => t.getConstructSignatures().length > 0)) {
196                return TokenType.class;
197            }
198            if (test(t => t.getCallSignatures().length > 0) && !test(t => t.getProperties().length > 0) || isExpressionInCallExpression(node)) {
199                return typeIdx === TokenType.property ? TokenType.member : TokenType.function;
200            }
201        }
202    }
203    return typeIdx;
204}
205
206function isLocalDeclaration(decl: Declaration, sourceFile: SourceFile): boolean {
207    if (isBindingElement(decl)) {
208        decl = getDeclarationForBindingElement(decl);
209    }
210    if (isVariableDeclaration(decl)) {
211        return (!isSourceFile(decl.parent.parent.parent) || isCatchClause(decl.parent)) && decl.getSourceFile() === sourceFile;
212    }
213    else if (isFunctionDeclaration(decl)) {
214        return !isSourceFile(decl.parent) && decl.getSourceFile() === sourceFile;
215    }
216    return false;
217}
218
219function getDeclarationForBindingElement(element: BindingElement): VariableDeclaration | ParameterDeclaration {
220    while (true) {
221        if (isBindingElement(element.parent.parent)) {
222            element = element.parent.parent;
223        }
224        else {
225            return element.parent.parent;
226        }
227    }
228}
229
230function inImportClause(node: Node): boolean {
231    const parent = node.parent;
232    return parent && (isImportClause(parent) || isImportSpecifier(parent) || isNamespaceImport(parent));
233}
234
235function isExpressionInCallExpression(node: Node): boolean {
236    while (isRightSideOfQualifiedNameOrPropertyAccess(node)) {
237        node = node.parent;
238    }
239    return isCallExpression(node.parent) && node.parent.expression === node;
240}
241
242function isRightSideOfQualifiedNameOrPropertyAccess(node: Node): boolean {
243    return (isQualifiedName(node.parent) && node.parent.right === node) || (isPropertyAccessExpression(node.parent) && node.parent.name === node);
244}
245
246const tokenFromDeclarationMapping = new Map<SyntaxKind, TokenType>([
247    [SyntaxKind.VariableDeclaration, TokenType.variable],
248    [SyntaxKind.Parameter, TokenType.parameter],
249    [SyntaxKind.PropertyDeclaration, TokenType.property],
250    [SyntaxKind.ModuleDeclaration, TokenType.namespace],
251    [SyntaxKind.EnumDeclaration, TokenType.enum],
252    [SyntaxKind.EnumMember, TokenType.enumMember],
253    [SyntaxKind.ClassDeclaration, TokenType.class],
254    [SyntaxKind.MethodDeclaration, TokenType.member],
255    [SyntaxKind.FunctionDeclaration, TokenType.function],
256    [SyntaxKind.FunctionExpression, TokenType.function],
257    [SyntaxKind.MethodSignature, TokenType.member],
258    [SyntaxKind.GetAccessor, TokenType.property],
259    [SyntaxKind.SetAccessor, TokenType.property],
260    [SyntaxKind.PropertySignature, TokenType.property],
261    [SyntaxKind.InterfaceDeclaration, TokenType.interface],
262    [SyntaxKind.TypeAliasDeclaration, TokenType.type],
263    [SyntaxKind.TypeParameter, TokenType.typeParameter],
264    [SyntaxKind.PropertyAssignment, TokenType.property],
265    [SyntaxKind.ShorthandPropertyAssignment, TokenType.property]
266]);
267