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