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