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