1/* @internal */ // Don't expose that we use this 2// Based on lib.es6.d.ts 3interface PromiseConstructor { 4 new <T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): Promise<T>; 5 reject(reason: any): Promise<never>; 6 all<T>(values: (T | PromiseLike<T>)[]): Promise<T[]>; 7} 8/* @internal */ 9declare var Promise: PromiseConstructor; // eslint-disable-line no-var 10 11/* @internal */ 12namespace ts { 13 // These utilities are common to multiple language service features. 14 //#region 15 export const scanner: Scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true); 16 17 export const enum SemanticMeaning { 18 None = 0x0, 19 Value = 0x1, 20 Type = 0x2, 21 Namespace = 0x4, 22 All = Value | Type | Namespace 23 } 24 25 export function getMeaningFromDeclaration(node: Node): SemanticMeaning { 26 switch (node.kind) { 27 case SyntaxKind.VariableDeclaration: 28 return isInJSFile(node) && getJSDocEnumTag(node) ? SemanticMeaning.All : SemanticMeaning.Value; 29 30 case SyntaxKind.Parameter: 31 case SyntaxKind.BindingElement: 32 case SyntaxKind.PropertyDeclaration: 33 case SyntaxKind.PropertySignature: 34 case SyntaxKind.PropertyAssignment: 35 case SyntaxKind.ShorthandPropertyAssignment: 36 case SyntaxKind.MethodDeclaration: 37 case SyntaxKind.MethodSignature: 38 case SyntaxKind.Constructor: 39 case SyntaxKind.GetAccessor: 40 case SyntaxKind.SetAccessor: 41 case SyntaxKind.FunctionDeclaration: 42 case SyntaxKind.FunctionExpression: 43 case SyntaxKind.ArrowFunction: 44 case SyntaxKind.CatchClause: 45 case SyntaxKind.JsxAttribute: 46 return SemanticMeaning.Value; 47 48 case SyntaxKind.TypeParameter: 49 case SyntaxKind.InterfaceDeclaration: 50 case SyntaxKind.TypeAliasDeclaration: 51 case SyntaxKind.TypeLiteral: 52 return SemanticMeaning.Type; 53 54 case SyntaxKind.JSDocTypedefTag: 55 // If it has no name node, it shares the name with the value declaration below it. 56 return (node as JSDocTypedefTag).name === undefined ? SemanticMeaning.Value | SemanticMeaning.Type : SemanticMeaning.Type; 57 58 case SyntaxKind.EnumMember: 59 case SyntaxKind.ClassDeclaration: 60 case SyntaxKind.StructDeclaration: 61 return SemanticMeaning.Value | SemanticMeaning.Type; 62 63 case SyntaxKind.ModuleDeclaration: 64 if (isAmbientModule(<ModuleDeclaration>node)) { 65 return SemanticMeaning.Namespace | SemanticMeaning.Value; 66 } 67 else if (getModuleInstanceState(node as ModuleDeclaration) === ModuleInstanceState.Instantiated) { 68 return SemanticMeaning.Namespace | SemanticMeaning.Value; 69 } 70 else { 71 return SemanticMeaning.Namespace; 72 } 73 74 case SyntaxKind.EnumDeclaration: 75 case SyntaxKind.NamedImports: 76 case SyntaxKind.ImportSpecifier: 77 case SyntaxKind.ImportEqualsDeclaration: 78 case SyntaxKind.ImportDeclaration: 79 case SyntaxKind.ExportAssignment: 80 case SyntaxKind.ExportDeclaration: 81 return SemanticMeaning.All; 82 83 // An external module can be a Value 84 case SyntaxKind.SourceFile: 85 return SemanticMeaning.Namespace | SemanticMeaning.Value; 86 } 87 88 return SemanticMeaning.All; 89 } 90 91 export function getMeaningFromLocation(node: Node): SemanticMeaning { 92 node = getAdjustedReferenceLocation(node); 93 if (node.kind === SyntaxKind.SourceFile) { 94 return SemanticMeaning.Value; 95 } 96 else if (node.parent.kind === SyntaxKind.ExportAssignment 97 || node.parent.kind === SyntaxKind.ExternalModuleReference 98 || node.parent.kind === SyntaxKind.ImportSpecifier 99 || node.parent.kind === SyntaxKind.ImportClause 100 || isImportEqualsDeclaration(node.parent) && node === node.parent.name) { 101 return SemanticMeaning.All; 102 } 103 else if (isInRightSideOfInternalImportEqualsDeclaration(node)) { 104 return getMeaningFromRightHandSideOfImportEquals(node as Identifier); 105 } 106 else if (isDeclarationName(node)) { 107 return getMeaningFromDeclaration(node.parent); 108 } 109 else if (isEntityName(node) && isJSDocNameReference(node.parent)) { 110 return SemanticMeaning.All; 111 } 112 else if (isTypeReference(node)) { 113 return SemanticMeaning.Type; 114 } 115 else if (isNamespaceReference(node)) { 116 return SemanticMeaning.Namespace; 117 } 118 else if (isTypeParameterDeclaration(node.parent)) { 119 Debug.assert(isJSDocTemplateTag(node.parent.parent)); // Else would be handled by isDeclarationName 120 return SemanticMeaning.Type; 121 } 122 else if (isLiteralTypeNode(node.parent)) { 123 // This might be T["name"], which is actually referencing a property and not a type. So allow both meanings. 124 return SemanticMeaning.Type | SemanticMeaning.Value; 125 } 126 else { 127 return SemanticMeaning.Value; 128 } 129 } 130 131 function getMeaningFromRightHandSideOfImportEquals(node: Node): SemanticMeaning { 132 // import a = |b|; // Namespace 133 // import a = |b.c|; // Value, type, namespace 134 // import a = |b.c|.d; // Namespace 135 const name = node.kind === SyntaxKind.QualifiedName ? node : isQualifiedName(node.parent) && node.parent.right === node ? node.parent : undefined; 136 return name && name.parent.kind === SyntaxKind.ImportEqualsDeclaration ? SemanticMeaning.All : SemanticMeaning.Namespace; 137 } 138 139 export function isInRightSideOfInternalImportEqualsDeclaration(node: Node) { 140 while (node.parent.kind === SyntaxKind.QualifiedName) { 141 node = node.parent; 142 } 143 return isInternalModuleImportEqualsDeclaration(node.parent) && node.parent.moduleReference === node; 144 } 145 146 function isNamespaceReference(node: Node): boolean { 147 return isQualifiedNameNamespaceReference(node) || isPropertyAccessNamespaceReference(node); 148 } 149 150 function isQualifiedNameNamespaceReference(node: Node): boolean { 151 let root = node; 152 let isLastClause = true; 153 if (root.parent.kind === SyntaxKind.QualifiedName) { 154 while (root.parent && root.parent.kind === SyntaxKind.QualifiedName) { 155 root = root.parent; 156 } 157 158 isLastClause = (<QualifiedName>root).right === node; 159 } 160 161 return root.parent.kind === SyntaxKind.TypeReference && !isLastClause; 162 } 163 164 function isPropertyAccessNamespaceReference(node: Node): boolean { 165 let root = node; 166 let isLastClause = true; 167 if (root.parent.kind === SyntaxKind.PropertyAccessExpression) { 168 while (root.parent && root.parent.kind === SyntaxKind.PropertyAccessExpression) { 169 root = root.parent; 170 } 171 172 isLastClause = (<PropertyAccessExpression>root).name === node; 173 } 174 175 if (!isLastClause && root.parent.kind === SyntaxKind.ExpressionWithTypeArguments && root.parent.parent.kind === SyntaxKind.HeritageClause) { 176 const decl = root.parent.parent.parent; 177 return ((decl.kind === SyntaxKind.ClassDeclaration || decl.kind === SyntaxKind.StructDeclaration) && (<HeritageClause>root.parent.parent).token === SyntaxKind.ImplementsKeyword) || 178 (decl.kind === SyntaxKind.InterfaceDeclaration && (<HeritageClause>root.parent.parent).token === SyntaxKind.ExtendsKeyword); 179 } 180 181 return false; 182 } 183 184 function isTypeReference(node: Node): boolean { 185 if (isRightSideOfQualifiedNameOrPropertyAccess(node)) { 186 node = node.parent; 187 } 188 189 switch (node.kind) { 190 case SyntaxKind.ThisKeyword: 191 return !isExpressionNode(node); 192 case SyntaxKind.ThisType: 193 return true; 194 } 195 196 switch (node.parent.kind) { 197 case SyntaxKind.TypeReference: 198 return true; 199 case SyntaxKind.ImportType: 200 return !(node.parent as ImportTypeNode).isTypeOf; 201 case SyntaxKind.ExpressionWithTypeArguments: 202 return !isExpressionWithTypeArgumentsInClassExtendsClause(<ExpressionWithTypeArguments>node.parent); 203 } 204 205 return false; 206 } 207 208 export function isCallExpressionTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { 209 return isCalleeWorker(node, isCallExpression, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions); 210 } 211 212 export function isNewExpressionTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { 213 return isCalleeWorker(node, isNewExpression, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions); 214 } 215 216 export function isCallOrNewExpressionTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { 217 return isCalleeWorker(node, isCallOrNewExpression, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions); 218 } 219 220 export function isTaggedTemplateTag(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { 221 return isCalleeWorker(node, isTaggedTemplateExpression, selectTagOfTaggedTemplateExpression, includeElementAccess, skipPastOuterExpressions); 222 } 223 224 export function isDecoratorTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { 225 return isCalleeWorker(node, isDecorator, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions); 226 } 227 228 export function isJsxOpeningLikeElementTagName(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { 229 return isCalleeWorker(node, isJsxOpeningLikeElement, selectTagNameOfJsxOpeningLikeElement, includeElementAccess, skipPastOuterExpressions); 230 } 231 232 function selectExpressionOfCallOrNewExpressionOrDecorator(node: CallExpression | NewExpression | Decorator) { 233 return node.expression; 234 } 235 236 function selectTagOfTaggedTemplateExpression(node: TaggedTemplateExpression) { 237 return node.tag; 238 } 239 240 function selectTagNameOfJsxOpeningLikeElement(node: JsxOpeningLikeElement) { 241 return node.tagName; 242 } 243 244 function isCalleeWorker<T extends CallExpression | NewExpression | TaggedTemplateExpression | Decorator | JsxOpeningLikeElement>(node: Node, pred: (node: Node) => node is T, calleeSelector: (node: T) => Expression, includeElementAccess: boolean, skipPastOuterExpressions: boolean) { 245 let target = includeElementAccess ? climbPastPropertyOrElementAccess(node) : climbPastPropertyAccess(node); 246 if (skipPastOuterExpressions) { 247 target = skipOuterExpressions(target); 248 } 249 return !!target && !!target.parent && pred(target.parent) && calleeSelector(target.parent) === target; 250 } 251 252 export function climbPastPropertyAccess(node: Node) { 253 return isRightSideOfPropertyAccess(node) ? node.parent : node; 254 } 255 256 export function climbPastPropertyOrElementAccess(node: Node) { 257 return isRightSideOfPropertyAccess(node) || isArgumentExpressionOfElementAccess(node) ? node.parent : node; 258 } 259 260 export function getTargetLabel(referenceNode: Node, labelName: string): Identifier | undefined { 261 while (referenceNode) { 262 if (referenceNode.kind === SyntaxKind.LabeledStatement && (<LabeledStatement>referenceNode).label.escapedText === labelName) { 263 return (<LabeledStatement>referenceNode).label; 264 } 265 referenceNode = referenceNode.parent; 266 } 267 return undefined; 268 } 269 270 export function hasPropertyAccessExpressionWithName(node: CallExpression, funcName: string): boolean { 271 if (!isPropertyAccessExpression(node.expression)) { 272 return false; 273 } 274 275 return node.expression.name.text === funcName; 276 } 277 278 export function isJumpStatementTarget(node: Node): node is Identifier & { parent: BreakOrContinueStatement } { 279 return isIdentifier(node) && tryCast(node.parent, isBreakOrContinueStatement)?.label === node; 280 } 281 282 export function isLabelOfLabeledStatement(node: Node): node is Identifier { 283 return isIdentifier(node) && tryCast(node.parent, isLabeledStatement)?.label === node; 284 } 285 286 export function isLabelName(node: Node): boolean { 287 return isLabelOfLabeledStatement(node) || isJumpStatementTarget(node); 288 } 289 290 export function isTagName(node: Node): boolean { 291 return tryCast(node.parent, isJSDocTag)?.tagName === node; 292 } 293 294 export function isRightSideOfQualifiedName(node: Node) { 295 return tryCast(node.parent, isQualifiedName)?.right === node; 296 } 297 298 export function isRightSideOfPropertyAccess(node: Node) { 299 return tryCast(node.parent, isPropertyAccessExpression)?.name === node; 300 } 301 302 export function isArgumentExpressionOfElementAccess(node: Node) { 303 return tryCast(node.parent, isElementAccessExpression)?.argumentExpression === node; 304 } 305 306 export function isNameOfModuleDeclaration(node: Node) { 307 return tryCast(node.parent, isModuleDeclaration)?.name === node; 308 } 309 310 export function isNameOfFunctionDeclaration(node: Node): boolean { 311 return isIdentifier(node) && tryCast(node.parent, isFunctionLike)?.name === node; 312 } 313 314 export function isLiteralNameOfPropertyDeclarationOrIndexAccess(node: StringLiteral | NumericLiteral | NoSubstitutionTemplateLiteral): boolean { 315 switch (node.parent.kind) { 316 case SyntaxKind.PropertyDeclaration: 317 case SyntaxKind.PropertySignature: 318 case SyntaxKind.PropertyAssignment: 319 case SyntaxKind.EnumMember: 320 case SyntaxKind.MethodDeclaration: 321 case SyntaxKind.MethodSignature: 322 case SyntaxKind.GetAccessor: 323 case SyntaxKind.SetAccessor: 324 case SyntaxKind.ModuleDeclaration: 325 return getNameOfDeclaration(<Declaration>node.parent) === node; 326 case SyntaxKind.ElementAccessExpression: 327 return (<ElementAccessExpression>node.parent).argumentExpression === node; 328 case SyntaxKind.ComputedPropertyName: 329 return true; 330 case SyntaxKind.LiteralType: 331 return node.parent.parent.kind === SyntaxKind.IndexedAccessType; 332 default: 333 return false; 334 } 335 } 336 337 export function isExpressionOfExternalModuleImportEqualsDeclaration(node: Node) { 338 return isExternalModuleImportEqualsDeclaration(node.parent.parent) && 339 getExternalModuleImportEqualsDeclarationExpression(node.parent.parent) === node; 340 } 341 342 export function getContainerNode(node: Node): Declaration | undefined { 343 if (isJSDocTypeAlias(node)) { 344 // This doesn't just apply to the node immediately under the comment, but to everything in its parent's scope. 345 // node.parent = the JSDoc comment, node.parent.parent = the node having the comment. 346 // Then we get parent again in the loop. 347 node = node.parent.parent; 348 } 349 350 while (true) { 351 node = node.parent; 352 if (!node) { 353 return undefined; 354 } 355 switch (node.kind) { 356 case SyntaxKind.SourceFile: 357 case SyntaxKind.MethodDeclaration: 358 case SyntaxKind.MethodSignature: 359 case SyntaxKind.FunctionDeclaration: 360 case SyntaxKind.FunctionExpression: 361 case SyntaxKind.GetAccessor: 362 case SyntaxKind.SetAccessor: 363 case SyntaxKind.ClassDeclaration: 364 case SyntaxKind.StructDeclaration: 365 case SyntaxKind.InterfaceDeclaration: 366 case SyntaxKind.EnumDeclaration: 367 case SyntaxKind.ModuleDeclaration: 368 return <Declaration>node; 369 } 370 } 371 } 372 373 export function getNodeKind(node: Node): ScriptElementKind { 374 switch (node.kind) { 375 case SyntaxKind.SourceFile: 376 return isExternalModule(<SourceFile>node) ? ScriptElementKind.moduleElement : ScriptElementKind.scriptElement; 377 case SyntaxKind.ModuleDeclaration: 378 return ScriptElementKind.moduleElement; 379 case SyntaxKind.ClassDeclaration: 380 case SyntaxKind.ClassExpression: 381 return ScriptElementKind.classElement; 382 case SyntaxKind.StructDeclaration: 383 return ScriptElementKind.structElement; 384 case SyntaxKind.InterfaceDeclaration: return ScriptElementKind.interfaceElement; 385 case SyntaxKind.TypeAliasDeclaration: 386 case SyntaxKind.JSDocCallbackTag: 387 case SyntaxKind.JSDocTypedefTag: 388 return ScriptElementKind.typeElement; 389 case SyntaxKind.EnumDeclaration: return ScriptElementKind.enumElement; 390 case SyntaxKind.VariableDeclaration: 391 return getKindOfVariableDeclaration(<VariableDeclaration>node); 392 case SyntaxKind.BindingElement: 393 return getKindOfVariableDeclaration(<VariableDeclaration>getRootDeclaration(node)); 394 case SyntaxKind.ArrowFunction: 395 case SyntaxKind.FunctionDeclaration: 396 case SyntaxKind.FunctionExpression: 397 return ScriptElementKind.functionElement; 398 case SyntaxKind.GetAccessor: return ScriptElementKind.memberGetAccessorElement; 399 case SyntaxKind.SetAccessor: return ScriptElementKind.memberSetAccessorElement; 400 case SyntaxKind.MethodDeclaration: 401 case SyntaxKind.MethodSignature: 402 return ScriptElementKind.memberFunctionElement; 403 case SyntaxKind.PropertyAssignment: 404 const { initializer } = node as PropertyAssignment; 405 return isFunctionLike(initializer) ? ScriptElementKind.memberFunctionElement : ScriptElementKind.memberVariableElement; 406 case SyntaxKind.PropertyDeclaration: 407 case SyntaxKind.PropertySignature: 408 case SyntaxKind.ShorthandPropertyAssignment: 409 case SyntaxKind.SpreadAssignment: 410 return ScriptElementKind.memberVariableElement; 411 case SyntaxKind.IndexSignature: return ScriptElementKind.indexSignatureElement; 412 case SyntaxKind.ConstructSignature: return ScriptElementKind.constructSignatureElement; 413 case SyntaxKind.CallSignature: return ScriptElementKind.callSignatureElement; 414 case SyntaxKind.Constructor: return ScriptElementKind.constructorImplementationElement; 415 case SyntaxKind.TypeParameter: return ScriptElementKind.typeParameterElement; 416 case SyntaxKind.EnumMember: return ScriptElementKind.enumMemberElement; 417 case SyntaxKind.Parameter: return hasSyntacticModifier(node, ModifierFlags.ParameterPropertyModifier) ? ScriptElementKind.memberVariableElement : ScriptElementKind.parameterElement; 418 case SyntaxKind.ImportEqualsDeclaration: 419 case SyntaxKind.ImportSpecifier: 420 case SyntaxKind.ExportSpecifier: 421 case SyntaxKind.NamespaceImport: 422 case SyntaxKind.NamespaceExport: 423 return ScriptElementKind.alias; 424 case SyntaxKind.BinaryExpression: 425 const kind = getAssignmentDeclarationKind(node as BinaryExpression); 426 const { right } = node as BinaryExpression; 427 switch (kind) { 428 case AssignmentDeclarationKind.ObjectDefinePropertyValue: 429 case AssignmentDeclarationKind.ObjectDefinePropertyExports: 430 case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: 431 case AssignmentDeclarationKind.None: 432 return ScriptElementKind.unknown; 433 case AssignmentDeclarationKind.ExportsProperty: 434 case AssignmentDeclarationKind.ModuleExports: 435 const rightKind = getNodeKind(right); 436 return rightKind === ScriptElementKind.unknown ? ScriptElementKind.constElement : rightKind; 437 case AssignmentDeclarationKind.PrototypeProperty: 438 return isFunctionExpression(right) ? ScriptElementKind.memberFunctionElement : ScriptElementKind.memberVariableElement; 439 case AssignmentDeclarationKind.ThisProperty: 440 return ScriptElementKind.memberVariableElement; // property 441 case AssignmentDeclarationKind.Property: 442 // static method / property 443 return isFunctionExpression(right) ? ScriptElementKind.memberFunctionElement : ScriptElementKind.memberVariableElement; 444 case AssignmentDeclarationKind.Prototype: 445 return ScriptElementKind.localClassElement; 446 default: { 447 assertType<never>(kind); 448 return ScriptElementKind.unknown; 449 } 450 } 451 case SyntaxKind.Identifier: 452 return isImportClause(node.parent) ? ScriptElementKind.alias : ScriptElementKind.unknown; 453 case SyntaxKind.ExportAssignment: 454 const scriptKind = getNodeKind((node as ExportAssignment).expression); 455 // If the expression didn't come back with something (like it does for an identifiers) 456 return scriptKind === ScriptElementKind.unknown ? ScriptElementKind.constElement : scriptKind; 457 default: 458 return ScriptElementKind.unknown; 459 } 460 461 function getKindOfVariableDeclaration(v: VariableDeclaration): ScriptElementKind { 462 return isVarConst(v) 463 ? ScriptElementKind.constElement 464 : isLet(v) 465 ? ScriptElementKind.letElement 466 : ScriptElementKind.variableElement; 467 } 468 } 469 470 export function isThis(node: Node): boolean { 471 switch (node.kind) { 472 case SyntaxKind.ThisKeyword: 473 // case SyntaxKind.ThisType: TODO: GH#9267 474 return true; 475 case SyntaxKind.Identifier: 476 // 'this' as a parameter 477 return identifierIsThisKeyword(node as Identifier) && node.parent.kind === SyntaxKind.Parameter; 478 default: 479 return false; 480 } 481 } 482 483 // Matches the beginning of a triple slash directive 484 const tripleSlashDirectivePrefixRegex = /^\/\/\/\s*</; 485 486 export interface ListItemInfo { 487 listItemIndex: number; 488 list: Node; 489 } 490 491 export function getLineStartPositionForPosition(position: number, sourceFile: SourceFileLike): number { 492 const lineStarts = getLineStarts(sourceFile); 493 const line = sourceFile.getLineAndCharacterOfPosition(position).line; 494 return lineStarts[line]; 495 } 496 497 export function rangeContainsRange(r1: TextRange, r2: TextRange): boolean { 498 return startEndContainsRange(r1.pos, r1.end, r2); 499 } 500 501 export function rangeContainsRangeExclusive(r1: TextRange, r2: TextRange): boolean { 502 return rangeContainsPositionExclusive(r1, r2.pos) && rangeContainsPositionExclusive(r1, r2.end); 503 } 504 505 export function rangeContainsPosition(r: TextRange, pos: number): boolean { 506 return r.pos <= pos && pos <= r.end; 507 } 508 509 export function rangeContainsPositionExclusive(r: TextRange, pos: number) { 510 return r.pos < pos && pos < r.end; 511 } 512 513 export function startEndContainsRange(start: number, end: number, range: TextRange): boolean { 514 return start <= range.pos && end >= range.end; 515 } 516 517 export function rangeContainsStartEnd(range: TextRange, start: number, end: number): boolean { 518 return range.pos <= start && range.end >= end; 519 } 520 521 export function rangeOverlapsWithStartEnd(r1: TextRange, start: number, end: number) { 522 return startEndOverlapsWithStartEnd(r1.pos, r1.end, start, end); 523 } 524 525 export function nodeOverlapsWithStartEnd(node: Node, sourceFile: SourceFile, start: number, end: number) { 526 return startEndOverlapsWithStartEnd(node.getStart(sourceFile), node.end, start, end); 527 } 528 529 export function startEndOverlapsWithStartEnd(start1: number, end1: number, start2: number, end2: number) { 530 const start = Math.max(start1, start2); 531 const end = Math.min(end1, end2); 532 return start < end; 533 } 534 535 /** 536 * Assumes `candidate.start <= position` holds. 537 */ 538 export function positionBelongsToNode(candidate: Node, position: number, sourceFile: SourceFile): boolean { 539 Debug.assert(candidate.pos <= position); 540 return position < candidate.end || !isCompletedNode(candidate, sourceFile); 541 } 542 543 function isCompletedNode(n: Node | undefined, sourceFile: SourceFile): boolean { 544 if (n === undefined || nodeIsMissing(n)) { 545 return false; 546 } 547 548 switch (n.kind) { 549 case SyntaxKind.ClassDeclaration: 550 case SyntaxKind.StructDeclaration: 551 case SyntaxKind.InterfaceDeclaration: 552 case SyntaxKind.EnumDeclaration: 553 case SyntaxKind.ObjectLiteralExpression: 554 case SyntaxKind.ObjectBindingPattern: 555 case SyntaxKind.TypeLiteral: 556 case SyntaxKind.Block: 557 case SyntaxKind.ModuleBlock: 558 case SyntaxKind.CaseBlock: 559 case SyntaxKind.NamedImports: 560 case SyntaxKind.NamedExports: 561 return nodeEndsWith(n, SyntaxKind.CloseBraceToken, sourceFile); 562 case SyntaxKind.CatchClause: 563 return isCompletedNode((<CatchClause>n).block, sourceFile); 564 case SyntaxKind.NewExpression: 565 if (!(<NewExpression>n).arguments) { 566 return true; 567 } 568 // falls through 569 570 case SyntaxKind.CallExpression: 571 case SyntaxKind.ParenthesizedExpression: 572 case SyntaxKind.ParenthesizedType: 573 return nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile); 574 575 case SyntaxKind.FunctionType: 576 case SyntaxKind.ConstructorType: 577 return isCompletedNode((<SignatureDeclaration>n).type, sourceFile); 578 579 case SyntaxKind.Constructor: 580 case SyntaxKind.GetAccessor: 581 case SyntaxKind.SetAccessor: 582 case SyntaxKind.FunctionDeclaration: 583 case SyntaxKind.FunctionExpression: 584 case SyntaxKind.MethodDeclaration: 585 case SyntaxKind.MethodSignature: 586 case SyntaxKind.ConstructSignature: 587 case SyntaxKind.CallSignature: 588 case SyntaxKind.ArrowFunction: 589 if ((<FunctionLikeDeclaration>n).body) { 590 return isCompletedNode((<FunctionLikeDeclaration>n).body, sourceFile); 591 } 592 593 if ((<FunctionLikeDeclaration>n).type) { 594 return isCompletedNode((<FunctionLikeDeclaration>n).type, sourceFile); 595 } 596 597 // Even though type parameters can be unclosed, we can get away with 598 // having at least a closing paren. 599 return hasChildOfKind(n, SyntaxKind.CloseParenToken, sourceFile); 600 601 case SyntaxKind.ModuleDeclaration: 602 return !!(<ModuleDeclaration>n).body && isCompletedNode((<ModuleDeclaration>n).body, sourceFile); 603 604 case SyntaxKind.IfStatement: 605 if ((<IfStatement>n).elseStatement) { 606 return isCompletedNode((<IfStatement>n).elseStatement, sourceFile); 607 } 608 return isCompletedNode((<IfStatement>n).thenStatement, sourceFile); 609 610 case SyntaxKind.ExpressionStatement: 611 return isCompletedNode((<ExpressionStatement>n).expression, sourceFile) || 612 hasChildOfKind(n, SyntaxKind.SemicolonToken, sourceFile); 613 614 case SyntaxKind.ArrayLiteralExpression: 615 case SyntaxKind.ArrayBindingPattern: 616 case SyntaxKind.ElementAccessExpression: 617 case SyntaxKind.ComputedPropertyName: 618 case SyntaxKind.TupleType: 619 return nodeEndsWith(n, SyntaxKind.CloseBracketToken, sourceFile); 620 621 case SyntaxKind.IndexSignature: 622 if ((<IndexSignatureDeclaration>n).type) { 623 return isCompletedNode((<IndexSignatureDeclaration>n).type, sourceFile); 624 } 625 626 return hasChildOfKind(n, SyntaxKind.CloseBracketToken, sourceFile); 627 628 case SyntaxKind.CaseClause: 629 case SyntaxKind.DefaultClause: 630 // there is no such thing as terminator token for CaseClause/DefaultClause so for simplicity always consider them non-completed 631 return false; 632 633 case SyntaxKind.ForStatement: 634 case SyntaxKind.ForInStatement: 635 case SyntaxKind.ForOfStatement: 636 case SyntaxKind.WhileStatement: 637 return isCompletedNode((<IterationStatement>n).statement, sourceFile); 638 case SyntaxKind.DoStatement: 639 // rough approximation: if DoStatement has While keyword - then if node is completed is checking the presence of ')'; 640 return hasChildOfKind(n, SyntaxKind.WhileKeyword, sourceFile) 641 ? nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile) 642 : isCompletedNode((<DoStatement>n).statement, sourceFile); 643 644 case SyntaxKind.TypeQuery: 645 return isCompletedNode((<TypeQueryNode>n).exprName, sourceFile); 646 647 case SyntaxKind.TypeOfExpression: 648 case SyntaxKind.DeleteExpression: 649 case SyntaxKind.VoidExpression: 650 case SyntaxKind.YieldExpression: 651 case SyntaxKind.SpreadElement: 652 const unaryWordExpression = n as (TypeOfExpression | DeleteExpression | VoidExpression | YieldExpression | SpreadElement); 653 return isCompletedNode(unaryWordExpression.expression, sourceFile); 654 655 case SyntaxKind.TaggedTemplateExpression: 656 return isCompletedNode((<TaggedTemplateExpression>n).template, sourceFile); 657 case SyntaxKind.TemplateExpression: 658 const lastSpan = lastOrUndefined((<TemplateExpression>n).templateSpans); 659 return isCompletedNode(lastSpan, sourceFile); 660 case SyntaxKind.TemplateSpan: 661 return nodeIsPresent((<TemplateSpan>n).literal); 662 663 case SyntaxKind.ExportDeclaration: 664 case SyntaxKind.ImportDeclaration: 665 return nodeIsPresent((<ExportDeclaration | ImportDeclaration>n).moduleSpecifier); 666 667 case SyntaxKind.PrefixUnaryExpression: 668 return isCompletedNode((<PrefixUnaryExpression>n).operand, sourceFile); 669 case SyntaxKind.BinaryExpression: 670 return isCompletedNode((<BinaryExpression>n).right, sourceFile); 671 case SyntaxKind.ConditionalExpression: 672 return isCompletedNode((<ConditionalExpression>n).whenFalse, sourceFile); 673 674 default: 675 return true; 676 } 677 } 678 679 /* 680 * Checks if node ends with 'expectedLastToken'. 681 * If child at position 'length - 1' is 'SemicolonToken' it is skipped and 'expectedLastToken' is compared with child at position 'length - 2'. 682 */ 683 function nodeEndsWith(n: Node, expectedLastToken: SyntaxKind, sourceFile: SourceFile): boolean { 684 const children = n.getChildren(sourceFile); 685 if (children.length) { 686 const lastChild = last(children); 687 if (lastChild.kind === expectedLastToken) { 688 return true; 689 } 690 else if (lastChild.kind === SyntaxKind.SemicolonToken && children.length !== 1) { 691 return children[children.length - 2].kind === expectedLastToken; 692 } 693 } 694 return false; 695 } 696 697 export function findListItemInfo(node: Node): ListItemInfo | undefined { 698 const list = findContainingList(node); 699 700 // It is possible at this point for syntaxList to be undefined, either if 701 // node.parent had no list child, or if none of its list children contained 702 // the span of node. If this happens, return undefined. The caller should 703 // handle this case. 704 if (!list) { 705 return undefined; 706 } 707 708 const children = list.getChildren(); 709 const listItemIndex = indexOfNode(children, node); 710 711 return { 712 listItemIndex, 713 list 714 }; 715 } 716 717 export function hasChildOfKind(n: Node, kind: SyntaxKind, sourceFile: SourceFile): boolean { 718 return !!findChildOfKind(n, kind, sourceFile); 719 } 720 721 export function findChildOfKind<T extends Node>(n: Node, kind: T["kind"], sourceFile: SourceFileLike): T | undefined { 722 return find(n.getChildren(sourceFile), (c): c is T => c.kind === kind); 723 } 724 725 export function findContainingList(node: Node): SyntaxList | undefined { 726 // The node might be a list element (nonsynthetic) or a comma (synthetic). Either way, it will 727 // be parented by the container of the SyntaxList, not the SyntaxList itself. 728 // In order to find the list item index, we first need to locate SyntaxList itself and then search 729 // for the position of the relevant node (or comma). 730 const syntaxList = find(node.parent.getChildren(), (c): c is SyntaxList => isSyntaxList(c) && rangeContainsRange(c, node)); 731 // Either we didn't find an appropriate list, or the list must contain us. 732 Debug.assert(!syntaxList || contains(syntaxList.getChildren(), node)); 733 return syntaxList; 734 } 735 736 function isDefaultModifier(node: Node) { 737 return node.kind === SyntaxKind.DefaultKeyword; 738 } 739 740 function isClassKeyword(node: Node) { 741 return node.kind === SyntaxKind.ClassKeyword; 742 } 743 744 function isFunctionKeyword(node: Node) { 745 return node.kind === SyntaxKind.FunctionKeyword; 746 } 747 748 function getAdjustedLocationForClass(node: ClassDeclaration | ClassExpression | StructDeclaration) { 749 if (isNamedDeclaration(node)) { 750 return node.name; 751 } 752 if (isClassDeclaration(node) || isStructDeclaration(node)) { 753 // for class and function declarations, use the `default` modifier 754 // when the declaration is unnamed. 755 const defaultModifier = find(node.modifiers!, isDefaultModifier); 756 if (defaultModifier) return defaultModifier; 757 } 758 if (isClassExpression(node)) { 759 // for class expressions, use the `class` keyword when the class is unnamed 760 const classKeyword = find(node.getChildren(), isClassKeyword); 761 if (classKeyword) return classKeyword; 762 } 763 } 764 765 function getAdjustedLocationForFunction(node: FunctionDeclaration | FunctionExpression) { 766 if (isNamedDeclaration(node)) { 767 return node.name; 768 } 769 if (isFunctionDeclaration(node)) { 770 // for class and function declarations, use the `default` modifier 771 // when the declaration is unnamed. 772 const defaultModifier = find(node.modifiers!, isDefaultModifier); 773 if (defaultModifier) return defaultModifier; 774 } 775 if (isFunctionExpression(node)) { 776 // for function expressions, use the `function` keyword when the function is unnamed 777 const functionKeyword = find(node.getChildren(), isFunctionKeyword); 778 if (functionKeyword) return functionKeyword; 779 } 780 } 781 782 function getAncestorTypeNode(node: Node) { 783 let lastTypeNode: TypeNode | undefined; 784 findAncestor(node, a => { 785 if (isTypeNode(a)) { 786 lastTypeNode = a; 787 } 788 return !isQualifiedName(a.parent) && !isTypeNode(a.parent) && !isTypeElement(a.parent); 789 }); 790 return lastTypeNode; 791 } 792 793 export function getContextualTypeOrAncestorTypeNodeType(node: Expression, checker: TypeChecker) { 794 const contextualType = checker.getContextualType(node); 795 if (contextualType) { 796 return contextualType; 797 } 798 799 const ancestorTypeNode = getAncestorTypeNode(node); 800 return ancestorTypeNode && checker.getTypeAtLocation(ancestorTypeNode); 801 } 802 803 function getAdjustedLocationForDeclaration(node: Node, forRename: boolean) { 804 if (!forRename) { 805 switch (node.kind) { 806 case SyntaxKind.ClassDeclaration: 807 case SyntaxKind.ClassExpression: 808 case SyntaxKind.StructDeclaration: 809 return getAdjustedLocationForClass(node as ClassDeclaration | ClassExpression | StructDeclaration); 810 case SyntaxKind.FunctionDeclaration: 811 case SyntaxKind.FunctionExpression: 812 return getAdjustedLocationForFunction(node as FunctionDeclaration | FunctionExpression); 813 } 814 } 815 if (isNamedDeclaration(node)) { 816 return node.name; 817 } 818 } 819 820 function getAdjustedLocationForImportDeclaration(node: ImportDeclaration, forRename: boolean) { 821 if (node.importClause) { 822 if (node.importClause.name && node.importClause.namedBindings) { 823 // do not adjust if we have both a name and named bindings 824 return; 825 } 826 827 // /**/import [|name|] from ...; 828 // import /**/type [|name|] from ...; 829 if (node.importClause.name) { 830 return node.importClause.name; 831 } 832 833 // /**/import { [|name|] } from ...; 834 // /**/import { propertyName as [|name|] } from ...; 835 // /**/import * as [|name|] from ...; 836 // import /**/type { [|name|] } from ...; 837 // import /**/type { propertyName as [|name|] } from ...; 838 // import /**/type * as [|name|] from ...; 839 if (node.importClause.namedBindings) { 840 if (isNamedImports(node.importClause.namedBindings)) { 841 // do nothing if there is more than one binding 842 const onlyBinding = singleOrUndefined(node.importClause.namedBindings.elements); 843 if (!onlyBinding) { 844 return; 845 } 846 return onlyBinding.name; 847 } 848 else if (isNamespaceImport(node.importClause.namedBindings)) { 849 return node.importClause.namedBindings.name; 850 } 851 } 852 } 853 if (!forRename) { 854 // /**/import "[|module|]"; 855 // /**/import ... from "[|module|]"; 856 // import /**/type ... from "[|module|]"; 857 return node.moduleSpecifier; 858 } 859 } 860 861 function getAdjustedLocationForExportDeclaration(node: ExportDeclaration, forRename: boolean) { 862 if (node.exportClause) { 863 // /**/export { [|name|] } ... 864 // /**/export { propertyName as [|name|] } ... 865 // /**/export * as [|name|] ... 866 // export /**/type { [|name|] } from ... 867 // export /**/type { propertyName as [|name|] } from ... 868 // export /**/type * as [|name|] ... 869 if (isNamedExports(node.exportClause)) { 870 // do nothing if there is more than one binding 871 const onlyBinding = singleOrUndefined(node.exportClause.elements); 872 if (!onlyBinding) { 873 return; 874 } 875 return node.exportClause.elements[0].name; 876 } 877 else if (isNamespaceExport(node.exportClause)) { 878 return node.exportClause.name; 879 } 880 } 881 if (!forRename) { 882 // /**/export * from "[|module|]"; 883 // export /**/type * from "[|module|]"; 884 return node.moduleSpecifier; 885 } 886 } 887 888 function getAdjustedLocationForHeritageClause(node: HeritageClause) { 889 // /**/extends [|name|] 890 // /**/implements [|name|] 891 if (node.types.length === 1) { 892 return node.types[0].expression; 893 } 894 895 // /**/extends name1, name2 ... 896 // /**/implements name1, name2 ... 897 } 898 899 function getAdjustedLocation(node: Node, forRename: boolean): Node { 900 const { parent } = node; 901 // /**/<modifier> [|name|] ... 902 // /**/<modifier> <class|interface|type|enum|module|namespace|function|get|set> [|name|] ... 903 // /**/<class|interface|type|enum|module|namespace|function|get|set> [|name|] ... 904 // /**/import [|name|] = ... 905 // 906 // NOTE: If the node is a modifier, we don't adjust its location if it is the `default` modifier as that is handled 907 // specially by `getSymbolAtLocation`. 908 if (isModifier(node) && (forRename || node.kind !== SyntaxKind.DefaultKeyword) ? contains(parent.modifiers, node) : 909 node.kind === SyntaxKind.ClassKeyword ? isClassDeclaration(parent) || isClassExpression(node) : 910 node.kind === SyntaxKind.FunctionKeyword ? isFunctionDeclaration(parent) || isFunctionExpression(node) : 911 node.kind === SyntaxKind.InterfaceKeyword ? isInterfaceDeclaration(parent) : 912 node.kind === SyntaxKind.EnumKeyword ? isEnumDeclaration(parent) : 913 node.kind === SyntaxKind.TypeKeyword ? isTypeAliasDeclaration(parent) : 914 node.kind === SyntaxKind.NamespaceKeyword || node.kind === SyntaxKind.ModuleKeyword ? isModuleDeclaration(parent) : 915 node.kind === SyntaxKind.ImportKeyword ? isImportEqualsDeclaration(parent) : 916 node.kind === SyntaxKind.GetKeyword ? isGetAccessorDeclaration(parent) : 917 node.kind === SyntaxKind.SetKeyword && isSetAccessorDeclaration(parent)) { 918 const location = getAdjustedLocationForDeclaration(parent, forRename); 919 if (location) { 920 return location; 921 } 922 } 923 // /**/<var|let|const> [|name|] ... 924 if ((node.kind === SyntaxKind.VarKeyword || node.kind === SyntaxKind.ConstKeyword || node.kind === SyntaxKind.LetKeyword) && 925 isVariableDeclarationList(parent) && parent.declarations.length === 1) { 926 const decl = parent.declarations[0]; 927 if (isIdentifier(decl.name)) { 928 return decl.name; 929 } 930 } 931 if (node.kind === SyntaxKind.TypeKeyword) { 932 // import /**/type [|name|] from ...; 933 // import /**/type { [|name|] } from ...; 934 // import /**/type { propertyName as [|name|] } from ...; 935 // import /**/type ... from "[|module|]"; 936 if (isImportClause(parent) && parent.isTypeOnly) { 937 const location = getAdjustedLocationForImportDeclaration(parent.parent, forRename); 938 if (location) { 939 return location; 940 } 941 } 942 // export /**/type { [|name|] } from ...; 943 // export /**/type { propertyName as [|name|] } from ...; 944 // export /**/type * from "[|module|]"; 945 // export /**/type * as ... from "[|module|]"; 946 if (isExportDeclaration(parent) && parent.isTypeOnly) { 947 const location = getAdjustedLocationForExportDeclaration(parent, forRename); 948 if (location) { 949 return location; 950 } 951 } 952 } 953 // import { propertyName /**/as [|name|] } ... 954 // import * /**/as [|name|] ... 955 // export { propertyName /**/as [|name|] } ... 956 // export * /**/as [|name|] ... 957 if (node.kind === SyntaxKind.AsKeyword) { 958 if (isImportSpecifier(parent) && parent.propertyName || 959 isExportSpecifier(parent) && parent.propertyName || 960 isNamespaceImport(parent) || 961 isNamespaceExport(parent)) { 962 return parent.name; 963 } 964 if (isExportDeclaration(parent) && parent.exportClause && isNamespaceExport(parent.exportClause)) { 965 return parent.exportClause.name; 966 } 967 } 968 // /**/import [|name|] from ...; 969 // /**/import { [|name|] } from ...; 970 // /**/import { propertyName as [|name|] } from ...; 971 // /**/import ... from "[|module|]"; 972 // /**/import "[|module|]"; 973 if (node.kind === SyntaxKind.ImportKeyword && isImportDeclaration(parent)) { 974 const location = getAdjustedLocationForImportDeclaration(parent, forRename); 975 if (location) { 976 return location; 977 } 978 } 979 if (node.kind === SyntaxKind.ExportKeyword) { 980 // /**/export { [|name|] } ...; 981 // /**/export { propertyName as [|name|] } ...; 982 // /**/export * from "[|module|]"; 983 // /**/export * as ... from "[|module|]"; 984 if (isExportDeclaration(parent)) { 985 const location = getAdjustedLocationForExportDeclaration(parent, forRename); 986 if (location) { 987 return location; 988 } 989 } 990 // NOTE: We don't adjust the location of the `default` keyword as that is handled specially by `getSymbolAtLocation`. 991 // /**/export default [|name|]; 992 // /**/export = [|name|]; 993 if (isExportAssignment(parent)) { 994 return skipOuterExpressions(parent.expression); 995 } 996 } 997 // import name = /**/require("[|module|]"); 998 if (node.kind === SyntaxKind.RequireKeyword && isExternalModuleReference(parent)) { 999 return parent.expression; 1000 } 1001 // import ... /**/from "[|module|]"; 1002 // export ... /**/from "[|module|]"; 1003 if (node.kind === SyntaxKind.FromKeyword && (isImportDeclaration(parent) || isExportDeclaration(parent)) && parent.moduleSpecifier) { 1004 return parent.moduleSpecifier; 1005 } 1006 // class ... /**/extends [|name|] ... 1007 // class ... /**/implements [|name|] ... 1008 // class ... /**/implements name1, name2 ... 1009 // interface ... /**/extends [|name|] ... 1010 // interface ... /**/extends name1, name2 ... 1011 if ((node.kind === SyntaxKind.ExtendsKeyword || node.kind === SyntaxKind.ImplementsKeyword) && isHeritageClause(parent) && parent.token === node.kind) { 1012 const location = getAdjustedLocationForHeritageClause(parent); 1013 if (location) { 1014 return location; 1015 } 1016 } 1017 if (node.kind === SyntaxKind.ExtendsKeyword) { 1018 // ... <T /**/extends [|U|]> ... 1019 if (isTypeParameterDeclaration(parent) && parent.constraint && isTypeReferenceNode(parent.constraint)) { 1020 return parent.constraint.typeName; 1021 } 1022 // ... T /**/extends [|U|] ? ... 1023 if (isConditionalTypeNode(parent) && isTypeReferenceNode(parent.extendsType)) { 1024 return parent.extendsType.typeName; 1025 } 1026 } 1027 // ... T extends /**/infer [|U|] ? ... 1028 if (node.kind === SyntaxKind.InferKeyword && isInferTypeNode(parent)) { 1029 return parent.typeParameter.name; 1030 } 1031 // { [ [|K|] /**/in keyof T]: ... } 1032 if (node.kind === SyntaxKind.InKeyword && isTypeParameterDeclaration(parent) && isMappedTypeNode(parent.parent)) { 1033 return parent.name; 1034 } 1035 // /**/keyof [|T|] 1036 if (node.kind === SyntaxKind.KeyOfKeyword && isTypeOperatorNode(parent) && parent.operator === SyntaxKind.KeyOfKeyword && 1037 isTypeReferenceNode(parent.type)) { 1038 return parent.type.typeName; 1039 } 1040 // /**/readonly [|name|][] 1041 if (node.kind === SyntaxKind.ReadonlyKeyword && isTypeOperatorNode(parent) && parent.operator === SyntaxKind.ReadonlyKeyword && 1042 isArrayTypeNode(parent.type) && isTypeReferenceNode(parent.type.elementType)) { 1043 return parent.type.elementType.typeName; 1044 } 1045 if (!forRename) { 1046 // /**/new [|name|] 1047 // /**/void [|name|] 1048 // /**/void obj.[|name|] 1049 // /**/typeof [|name|] 1050 // /**/typeof obj.[|name|] 1051 // /**/await [|name|] 1052 // /**/await obj.[|name|] 1053 // /**/yield [|name|] 1054 // /**/yield obj.[|name|] 1055 // /**/delete obj.[|name|] 1056 if (node.kind === SyntaxKind.NewKeyword && isNewExpression(parent) || 1057 node.kind === SyntaxKind.VoidKeyword && isVoidExpression(parent) || 1058 node.kind === SyntaxKind.TypeOfKeyword && isTypeOfExpression(parent) || 1059 node.kind === SyntaxKind.AwaitKeyword && isAwaitExpression(parent) || 1060 node.kind === SyntaxKind.YieldKeyword && isYieldExpression(parent) || 1061 node.kind === SyntaxKind.DeleteKeyword && isDeleteExpression(parent)) { 1062 if (parent.expression) { 1063 return skipOuterExpressions(parent.expression); 1064 } 1065 } 1066 // left /**/in [|name|] 1067 // left /**/instanceof [|name|] 1068 if ((node.kind === SyntaxKind.InKeyword || node.kind === SyntaxKind.InstanceOfKeyword) && isBinaryExpression(parent) && parent.operatorToken === node) { 1069 return skipOuterExpressions(parent.right); 1070 } 1071 // left /**/as [|name|] 1072 if (node.kind === SyntaxKind.AsKeyword && isAsExpression(parent) && isTypeReferenceNode(parent.type)) { 1073 return parent.type.typeName; 1074 } 1075 // for (... /**/in [|name|]) 1076 // for (... /**/of [|name|]) 1077 if (node.kind === SyntaxKind.InKeyword && isForInStatement(parent) || 1078 node.kind === SyntaxKind.OfKeyword && isForOfStatement(parent)) { 1079 return skipOuterExpressions(parent.expression); 1080 } 1081 } 1082 return node; 1083 } 1084 1085 /** 1086 * Adjusts the location used for "find references" and "go to definition" when the cursor was not 1087 * on a property name. 1088 */ 1089 export function getAdjustedReferenceLocation(node: Node): Node { 1090 return getAdjustedLocation(node, /*forRename*/ false); 1091 } 1092 1093 /** 1094 * Adjusts the location used for "rename" when the cursor was not on a property name. 1095 */ 1096 export function getAdjustedRenameLocation(node: Node): Node { 1097 return getAdjustedLocation(node, /*forRename*/ true); 1098 } 1099 1100 /** 1101 * Gets the token whose text has range [start, end) and 1102 * position >= start and (position < end or (position === end && token is literal or keyword or identifier)) 1103 */ 1104 export function getTouchingPropertyName(sourceFile: SourceFile, position: number): Node { 1105 return getTouchingToken(sourceFile, position, n => isPropertyNameLiteral(n) || isKeyword(n.kind) || isPrivateIdentifier(n)); 1106 } 1107 1108 /** 1109 * Returns the token if position is in [start, end). 1110 * If position === end, returns the preceding token if includeItemAtEndPosition(previousToken) === true 1111 */ 1112 export function getTouchingToken(sourceFile: SourceFile, position: number, includePrecedingTokenAtEndPosition?: (n: Node) => boolean): Node { 1113 return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ false, includePrecedingTokenAtEndPosition, /*includeEndPosition*/ false); 1114 } 1115 1116 /** Returns a token if position is in [start-of-leading-trivia, end) */ 1117 export function getTokenAtPosition(sourceFile: SourceFile, position: number): Node { 1118 return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ true, /*includePrecedingTokenAtEndPosition*/ undefined, /*includeEndPosition*/ false); 1119 } 1120 1121 /** Get the token whose text contains the position */ 1122 function getTokenAtPositionWorker(sourceFile: SourceFile, position: number, allowPositionInLeadingTrivia: boolean, includePrecedingTokenAtEndPosition: ((n: Node) => boolean) | undefined, includeEndPosition: boolean): Node { 1123 let current: Node = sourceFile; 1124 outer: while (true) { 1125 // find the child that contains 'position' 1126 for (const child of current.getChildren(sourceFile)) { 1127 const start = allowPositionInLeadingTrivia ? child.getFullStart() : child.getStart(sourceFile, /*includeJsDoc*/ true); 1128 if (start > position) { 1129 // If this child begins after position, then all subsequent children will as well. 1130 break; 1131 } 1132 1133 const end = child.getEnd(); 1134 if (position < end || (position === end && (child.kind === SyntaxKind.EndOfFileToken || includeEndPosition))) { 1135 current = child; 1136 continue outer; 1137 } 1138 else if (includePrecedingTokenAtEndPosition && end === position) { 1139 const previousToken = findPrecedingToken(position, sourceFile, child); 1140 if (previousToken && includePrecedingTokenAtEndPosition(previousToken)) { 1141 return previousToken; 1142 } 1143 } 1144 } 1145 1146 return current; 1147 } 1148 } 1149 1150 /** 1151 * The token on the left of the position is the token that strictly includes the position 1152 * or sits to the left of the cursor if it is on a boundary. For example 1153 * 1154 * fo|o -> will return foo 1155 * foo <comment> |bar -> will return foo 1156 * 1157 */ 1158 export function findTokenOnLeftOfPosition(file: SourceFile, position: number): Node | undefined { 1159 // Ideally, getTokenAtPosition should return a token. However, it is currently 1160 // broken, so we do a check to make sure the result was indeed a token. 1161 const tokenAtPosition = getTokenAtPosition(file, position); 1162 if (isToken(tokenAtPosition) && position > tokenAtPosition.getStart(file) && position < tokenAtPosition.getEnd()) { 1163 return tokenAtPosition; 1164 } 1165 1166 return findPrecedingToken(position, file); 1167 } 1168 1169 export function findNextToken(previousToken: Node, parent: Node, sourceFile: SourceFileLike): Node | undefined { 1170 return find(parent); 1171 1172 function find(n: Node): Node | undefined { 1173 if (isToken(n) && n.pos === previousToken.end) { 1174 // this is token that starts at the end of previous token - return it 1175 return n; 1176 } 1177 return firstDefined(n.getChildren(sourceFile), child => { 1178 const shouldDiveInChildNode = 1179 // previous token is enclosed somewhere in the child 1180 (child.pos <= previousToken.pos && child.end > previousToken.end) || 1181 // previous token ends exactly at the beginning of child 1182 (child.pos === previousToken.end); 1183 return shouldDiveInChildNode && nodeHasTokens(child, sourceFile) ? find(child) : undefined; 1184 }); 1185 } 1186 } 1187 1188 /** 1189 * Finds the rightmost token satisfying `token.end <= position`, 1190 * excluding `JsxText` tokens containing only whitespace. 1191 */ 1192 export function findPrecedingToken(position: number, sourceFile: SourceFile, startNode?: Node, excludeJsdoc?: boolean): Node | undefined { 1193 const result = find(startNode || sourceFile); 1194 Debug.assert(!(result && isWhiteSpaceOnlyJsxText(result))); 1195 return result; 1196 1197 function find(n: Node): Node | undefined { 1198 if (isNonWhitespaceToken(n) && n.kind !== SyntaxKind.EndOfFileToken) { 1199 return n; 1200 } 1201 1202 const children = n.getChildren(sourceFile); 1203 const i = binarySearchKey(children, position, (_, i) => i, (middle, _) => { 1204 // This last callback is more of a selector than a comparator - 1205 // `EqualTo` causes the `middle` result to be returned 1206 // `GreaterThan` causes recursion on the left of the middle 1207 // `LessThan` causes recursion on the right of the middle 1208 if (position < children[middle].end) { 1209 // first element whose end position is greater than the input position 1210 if (!children[middle - 1] || position >= children[middle - 1].end) { 1211 return Comparison.EqualTo; 1212 } 1213 return Comparison.GreaterThan; 1214 } 1215 return Comparison.LessThan; 1216 }); 1217 if (i >= 0 && children[i]) { 1218 const child = children[i]; 1219 // Note that the span of a node's tokens is [node.getStart(...), node.end). 1220 // Given that `position < child.end` and child has constituent tokens, we distinguish these cases: 1221 // 1) `position` precedes `child`'s tokens or `child` has no tokens (ie: in a comment or whitespace preceding `child`): 1222 // we need to find the last token in a previous child. 1223 // 2) `position` is within the same span: we recurse on `child`. 1224 if (position < child.end) { 1225 const start = child.getStart(sourceFile, /*includeJsDoc*/ !excludeJsdoc); 1226 const lookInPreviousChild = 1227 (start >= position) || // cursor in the leading trivia 1228 !nodeHasTokens(child, sourceFile) || 1229 isWhiteSpaceOnlyJsxText(child); 1230 1231 if (lookInPreviousChild) { 1232 // actual start of the node is past the position - previous token should be at the end of previous child 1233 const candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ i, sourceFile); 1234 return candidate && findRightmostToken(candidate, sourceFile); 1235 } 1236 else { 1237 // candidate should be in this node 1238 return find(child); 1239 } 1240 } 1241 } 1242 1243 Debug.assert(startNode !== undefined || n.kind === SyntaxKind.SourceFile || n.kind === SyntaxKind.EndOfFileToken || isJSDocCommentContainingNode(n)); 1244 1245 // Here we know that none of child token nodes embrace the position, 1246 // the only known case is when position is at the end of the file. 1247 // Try to find the rightmost token in the file without filtering. 1248 // Namely we are skipping the check: 'position < node.end' 1249 const candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ children.length, sourceFile); 1250 return candidate && findRightmostToken(candidate, sourceFile); 1251 } 1252 } 1253 1254 function isNonWhitespaceToken(n: Node): boolean { 1255 return isToken(n) && !isWhiteSpaceOnlyJsxText(n); 1256 } 1257 1258 function findRightmostToken(n: Node, sourceFile: SourceFile): Node | undefined { 1259 if (isNonWhitespaceToken(n)) { 1260 return n; 1261 } 1262 1263 const children = n.getChildren(sourceFile); 1264 if (children.length === 0) { 1265 return n; 1266 } 1267 1268 const candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ children.length, sourceFile); 1269 return candidate && findRightmostToken(candidate, sourceFile); 1270 } 1271 1272 /** 1273 * Finds the rightmost child to the left of `children[exclusiveStartPosition]` which is a non-all-whitespace token or has constituent tokens. 1274 */ 1275 function findRightmostChildNodeWithTokens(children: Node[], exclusiveStartPosition: number, sourceFile: SourceFile): Node | undefined { 1276 for (let i = exclusiveStartPosition - 1; i >= 0; i--) { 1277 const child = children[i]; 1278 1279 if (isWhiteSpaceOnlyJsxText(child)) { 1280 Debug.assert(i > 0, "`JsxText` tokens should not be the first child of `JsxElement | JsxSelfClosingElement`"); 1281 } 1282 else if (nodeHasTokens(children[i], sourceFile)) { 1283 return children[i]; 1284 } 1285 } 1286 } 1287 1288 export function isInString(sourceFile: SourceFile, position: number, previousToken = findPrecedingToken(position, sourceFile)): boolean { 1289 if (previousToken && isStringTextContainingNode(previousToken)) { 1290 const start = previousToken.getStart(sourceFile); 1291 const end = previousToken.getEnd(); 1292 1293 // To be "in" one of these literals, the position has to be: 1294 // 1. entirely within the token text. 1295 // 2. at the end position of an unterminated token. 1296 // 3. at the end of a regular expression (due to trailing flags like '/foo/g'). 1297 if (start < position && position < end) { 1298 return true; 1299 } 1300 1301 if (position === end) { 1302 return !!(<LiteralExpression>previousToken).isUnterminated; 1303 } 1304 } 1305 1306 return false; 1307 } 1308 1309 /** 1310 * returns true if the position is in between the open and close elements of an JSX expression. 1311 */ 1312 export function isInsideJsxElementOrAttribute(sourceFile: SourceFile, position: number) { 1313 const token = getTokenAtPosition(sourceFile, position); 1314 1315 if (!token) { 1316 return false; 1317 } 1318 1319 if (token.kind === SyntaxKind.JsxText) { 1320 return true; 1321 } 1322 1323 // <div>Hello |</div> 1324 if (token.kind === SyntaxKind.LessThanToken && token.parent.kind === SyntaxKind.JsxText) { 1325 return true; 1326 } 1327 1328 // <div> { | </div> or <div a={| </div> 1329 if (token.kind === SyntaxKind.LessThanToken && token.parent.kind === SyntaxKind.JsxExpression) { 1330 return true; 1331 } 1332 1333 // <div> { 1334 // | 1335 // } < /div> 1336 if (token && token.kind === SyntaxKind.CloseBraceToken && token.parent.kind === SyntaxKind.JsxExpression) { 1337 return true; 1338 } 1339 1340 // <div>|</div> 1341 if (token.kind === SyntaxKind.LessThanToken && token.parent.kind === SyntaxKind.JsxClosingElement) { 1342 return true; 1343 } 1344 1345 return false; 1346 } 1347 1348 function isWhiteSpaceOnlyJsxText(node: Node): boolean { 1349 return isJsxText(node) && node.containsOnlyTriviaWhiteSpaces; 1350 } 1351 1352 export function isInTemplateString(sourceFile: SourceFile, position: number) { 1353 const token = getTokenAtPosition(sourceFile, position); 1354 return isTemplateLiteralKind(token.kind) && position > token.getStart(sourceFile); 1355 } 1356 1357 export function isInJSXText(sourceFile: SourceFile, position: number) { 1358 const token = getTokenAtPosition(sourceFile, position); 1359 if (isJsxText(token)) { 1360 return true; 1361 } 1362 if (token.kind === SyntaxKind.OpenBraceToken && isJsxExpression(token.parent) && isJsxElement(token.parent.parent)) { 1363 return true; 1364 } 1365 if (token.kind === SyntaxKind.LessThanToken && isJsxOpeningLikeElement(token.parent) && isJsxElement(token.parent.parent)) { 1366 return true; 1367 } 1368 return false; 1369 } 1370 1371 export function isInsideJsxElement(sourceFile: SourceFile, position: number): boolean { 1372 function isInsideJsxElementTraversal(node: Node): boolean { 1373 while (node) { 1374 if (node.kind >= SyntaxKind.JsxSelfClosingElement && node.kind <= SyntaxKind.JsxExpression 1375 || node.kind === SyntaxKind.JsxText 1376 || node.kind === SyntaxKind.LessThanToken 1377 || node.kind === SyntaxKind.GreaterThanToken 1378 || node.kind === SyntaxKind.Identifier 1379 || node.kind === SyntaxKind.CloseBraceToken 1380 || node.kind === SyntaxKind.OpenBraceToken 1381 || node.kind === SyntaxKind.SlashToken) { 1382 node = node.parent; 1383 } 1384 else if (node.kind === SyntaxKind.JsxElement) { 1385 if (position > node.getStart(sourceFile)) return true; 1386 1387 node = node.parent; 1388 } 1389 else { 1390 return false; 1391 } 1392 } 1393 1394 return false; 1395 } 1396 1397 return isInsideJsxElementTraversal(getTokenAtPosition(sourceFile, position)); 1398 } 1399 1400 export function findPrecedingMatchingToken(token: Node, matchingTokenKind: SyntaxKind.OpenBraceToken | SyntaxKind.OpenParenToken | SyntaxKind.OpenBracketToken, sourceFile: SourceFile) { 1401 const closeTokenText = tokenToString(token.kind)!; 1402 const matchingTokenText = tokenToString(matchingTokenKind)!; 1403 const tokenFullStart = token.getFullStart(); 1404 // Text-scan based fast path - can be bamboozled by comments and other trivia, but often provides 1405 // a good, fast approximation without too much extra work in the cases where it fails. 1406 const bestGuessIndex = sourceFile.text.lastIndexOf(matchingTokenText, tokenFullStart); 1407 if (bestGuessIndex === -1) { 1408 return undefined; // if the token text doesn't appear in the file, there can't be a match - super fast bail 1409 } 1410 // we can only use the textual result directly if we didn't have to count any close tokens within the range 1411 if (sourceFile.text.lastIndexOf(closeTokenText, tokenFullStart - 1) < bestGuessIndex) { 1412 const nodeAtGuess = findPrecedingToken(bestGuessIndex + 1, sourceFile); 1413 if (nodeAtGuess && nodeAtGuess.kind === matchingTokenKind) { 1414 return nodeAtGuess; 1415 } 1416 } 1417 const tokenKind = token.kind; 1418 let remainingMatchingTokens = 0; 1419 while (true) { 1420 const preceding = findPrecedingToken(token.getFullStart(), sourceFile); 1421 if (!preceding) { 1422 return undefined; 1423 } 1424 token = preceding; 1425 1426 if (token.kind === matchingTokenKind) { 1427 if (remainingMatchingTokens === 0) { 1428 return token; 1429 } 1430 1431 remainingMatchingTokens--; 1432 } 1433 else if (token.kind === tokenKind) { 1434 remainingMatchingTokens++; 1435 } 1436 } 1437 } 1438 1439 export function removeOptionality(type: Type, isOptionalExpression: boolean, isOptionalChain: boolean) { 1440 return isOptionalExpression ? type.getNonNullableType() : 1441 isOptionalChain ? type.getNonOptionalType() : 1442 type; 1443 } 1444 1445 export function isPossiblyTypeArgumentPosition(token: Node, sourceFile: SourceFile, checker: TypeChecker): boolean { 1446 const info = getPossibleTypeArgumentsInfo(token, sourceFile); 1447 return info !== undefined && (isPartOfTypeNode(info.called) || 1448 getPossibleGenericSignatures(info.called, info.nTypeArguments, checker).length !== 0 || 1449 isPossiblyTypeArgumentPosition(info.called, sourceFile, checker)); 1450 } 1451 1452 export function getPossibleGenericSignatures(called: Expression, typeArgumentCount: number, checker: TypeChecker): readonly Signature[] { 1453 let type = checker.getTypeAtLocation(called); 1454 if (isOptionalChain(called.parent)) { 1455 type = removeOptionality(type, isOptionalChainRoot(called.parent), /*isOptionalChain*/ true); 1456 } 1457 1458 const signatures = isNewExpression(called.parent) ? type.getConstructSignatures() : type.getCallSignatures(); 1459 return signatures.filter(candidate => !!candidate.typeParameters && candidate.typeParameters.length >= typeArgumentCount); 1460 } 1461 1462 export interface PossibleTypeArgumentInfo { 1463 readonly called: Identifier; 1464 readonly nTypeArguments: number; 1465 } 1466 1467 export interface PossibleProgramFileInfo { 1468 ProgramFiles?: string[]; 1469 } 1470 1471 // Get info for an expression like `f <` that may be the start of type arguments. 1472 export function getPossibleTypeArgumentsInfo(tokenIn: Node | undefined, sourceFile: SourceFile): PossibleTypeArgumentInfo | undefined { 1473 // This is a rare case, but one that saves on a _lot_ of work if true - if the source file has _no_ `<` character, 1474 // then there obviously can't be any type arguments - no expensive brace-matching backwards scanning required 1475 1476 if (sourceFile.text.lastIndexOf("<", tokenIn ? tokenIn.pos : sourceFile.text.length) === -1) { 1477 return undefined; 1478 } 1479 1480 let token: Node | undefined = tokenIn; 1481 // This function determines if the node could be type argument position 1482 // Since during editing, when type argument list is not complete, 1483 // the tree could be of any shape depending on the tokens parsed before current node, 1484 // scanning of the previous identifier followed by "<" before current node would give us better result 1485 // Note that we also balance out the already provided type arguments, arrays, object literals while doing so 1486 let remainingLessThanTokens = 0; 1487 let nTypeArguments = 0; 1488 while (token) { 1489 switch (token.kind) { 1490 case SyntaxKind.LessThanToken: 1491 // Found the beginning of the generic argument expression 1492 token = findPrecedingToken(token.getFullStart(), sourceFile); 1493 if (token && token.kind === SyntaxKind.QuestionDotToken) { 1494 token = findPrecedingToken(token.getFullStart(), sourceFile); 1495 } 1496 if (!token || !isIdentifier(token)) return undefined; 1497 if (!remainingLessThanTokens) { 1498 return isDeclarationName(token) ? undefined : { called: token, nTypeArguments }; 1499 } 1500 remainingLessThanTokens--; 1501 break; 1502 1503 case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: 1504 remainingLessThanTokens = + 3; 1505 break; 1506 1507 case SyntaxKind.GreaterThanGreaterThanToken: 1508 remainingLessThanTokens = + 2; 1509 break; 1510 1511 case SyntaxKind.GreaterThanToken: 1512 remainingLessThanTokens++; 1513 break; 1514 1515 case SyntaxKind.CloseBraceToken: 1516 // This can be object type, skip until we find the matching open brace token 1517 // Skip until the matching open brace token 1518 token = findPrecedingMatchingToken(token, SyntaxKind.OpenBraceToken, sourceFile); 1519 if (!token) return undefined; 1520 break; 1521 1522 case SyntaxKind.CloseParenToken: 1523 // This can be object type, skip until we find the matching open brace token 1524 // Skip until the matching open brace token 1525 token = findPrecedingMatchingToken(token, SyntaxKind.OpenParenToken, sourceFile); 1526 if (!token) return undefined; 1527 break; 1528 1529 case SyntaxKind.CloseBracketToken: 1530 // This can be object type, skip until we find the matching open brace token 1531 // Skip until the matching open brace token 1532 token = findPrecedingMatchingToken(token, SyntaxKind.OpenBracketToken, sourceFile); 1533 if (!token) return undefined; 1534 break; 1535 1536 // Valid tokens in a type name. Skip. 1537 case SyntaxKind.CommaToken: 1538 nTypeArguments++; 1539 break; 1540 1541 case SyntaxKind.EqualsGreaterThanToken: 1542 // falls through 1543 1544 case SyntaxKind.Identifier: 1545 case SyntaxKind.StringLiteral: 1546 case SyntaxKind.NumericLiteral: 1547 case SyntaxKind.BigIntLiteral: 1548 case SyntaxKind.TrueKeyword: 1549 case SyntaxKind.FalseKeyword: 1550 // falls through 1551 1552 case SyntaxKind.TypeOfKeyword: 1553 case SyntaxKind.ExtendsKeyword: 1554 case SyntaxKind.KeyOfKeyword: 1555 case SyntaxKind.DotToken: 1556 case SyntaxKind.BarToken: 1557 case SyntaxKind.QuestionToken: 1558 case SyntaxKind.ColonToken: 1559 break; 1560 1561 default: 1562 if (isTypeNode(token)) { 1563 break; 1564 } 1565 1566 // Invalid token in type 1567 return undefined; 1568 } 1569 1570 token = findPrecedingToken(token.getFullStart(), sourceFile); 1571 } 1572 1573 return undefined; 1574 } 1575 1576 /** 1577 * Returns true if the cursor at position in sourceFile is within a comment. 1578 * 1579 * @param tokenAtPosition Must equal `getTokenAtPosition(sourceFile, position) 1580 * @param predicate Additional predicate to test on the comment range. 1581 */ 1582 export function isInComment(sourceFile: SourceFile, position: number, tokenAtPosition?: Node): CommentRange | undefined { 1583 return formatting.getRangeOfEnclosingComment(sourceFile, position, /*precedingToken*/ undefined, tokenAtPosition); 1584 } 1585 1586 export function hasDocComment(sourceFile: SourceFile, position: number): boolean { 1587 const token = getTokenAtPosition(sourceFile, position); 1588 return !!findAncestor(token, isJSDoc); 1589 } 1590 1591 function nodeHasTokens(n: Node, sourceFile: SourceFileLike): boolean { 1592 // If we have a token or node that has a non-zero width, it must have tokens. 1593 // Note: getWidth() does not take trivia into account. 1594 return n.kind === SyntaxKind.EndOfFileToken ? !!(n as EndOfFileToken).jsDoc : n.getWidth(sourceFile) !== 0; 1595 } 1596 1597 export function getNodeModifiers(node: Node, excludeFlags = ModifierFlags.None): string { 1598 const result: string[] = []; 1599 const flags = isDeclaration(node) 1600 ? getCombinedNodeFlagsAlwaysIncludeJSDoc(node) & ~excludeFlags 1601 : ModifierFlags.None; 1602 1603 if (flags & ModifierFlags.Private) result.push(ScriptElementKindModifier.privateMemberModifier); 1604 if (flags & ModifierFlags.Protected) result.push(ScriptElementKindModifier.protectedMemberModifier); 1605 if (flags & ModifierFlags.Public) result.push(ScriptElementKindModifier.publicMemberModifier); 1606 if (flags & ModifierFlags.Static) result.push(ScriptElementKindModifier.staticModifier); 1607 if (flags & ModifierFlags.Abstract) result.push(ScriptElementKindModifier.abstractModifier); 1608 if (flags & ModifierFlags.Export) result.push(ScriptElementKindModifier.exportedModifier); 1609 if (flags & ModifierFlags.Deprecated) result.push(ScriptElementKindModifier.deprecatedModifier); 1610 if (node.flags & NodeFlags.Ambient) result.push(ScriptElementKindModifier.ambientModifier); 1611 if (node.kind === SyntaxKind.ExportAssignment) result.push(ScriptElementKindModifier.exportedModifier); 1612 1613 return result.length > 0 ? result.join(",") : ScriptElementKindModifier.none; 1614 } 1615 1616 export function getTypeArgumentOrTypeParameterList(node: Node): NodeArray<Node> | undefined { 1617 if (node.kind === SyntaxKind.TypeReference || node.kind === SyntaxKind.CallExpression) { 1618 return (<CallExpression>node).typeArguments; 1619 } 1620 1621 if (isFunctionLike(node) || node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.InterfaceDeclaration) { 1622 return (<FunctionLikeDeclaration>node).typeParameters; 1623 } 1624 1625 return undefined; 1626 } 1627 1628 export function isComment(kind: SyntaxKind): boolean { 1629 return kind === SyntaxKind.SingleLineCommentTrivia || kind === SyntaxKind.MultiLineCommentTrivia; 1630 } 1631 1632 export function isStringOrRegularExpressionOrTemplateLiteral(kind: SyntaxKind): boolean { 1633 if (kind === SyntaxKind.StringLiteral 1634 || kind === SyntaxKind.RegularExpressionLiteral 1635 || isTemplateLiteralKind(kind)) { 1636 return true; 1637 } 1638 return false; 1639 } 1640 1641 export function isPunctuation(kind: SyntaxKind): boolean { 1642 return SyntaxKind.FirstPunctuation <= kind && kind <= SyntaxKind.LastPunctuation; 1643 } 1644 1645 export function isInsideTemplateLiteral(node: TemplateLiteralToken, position: number, sourceFile: SourceFile): boolean { 1646 return isTemplateLiteralKind(node.kind) 1647 && (node.getStart(sourceFile) < position && position < node.end) || (!!node.isUnterminated && position === node.end); 1648 } 1649 1650 export function isAccessibilityModifier(kind: SyntaxKind) { 1651 switch (kind) { 1652 case SyntaxKind.PublicKeyword: 1653 case SyntaxKind.PrivateKeyword: 1654 case SyntaxKind.ProtectedKeyword: 1655 return true; 1656 } 1657 1658 return false; 1659 } 1660 1661 export function cloneCompilerOptions(options: CompilerOptions): CompilerOptions { 1662 const result = clone(options); 1663 setConfigFileInOptions(result, options && options.configFile); 1664 return result; 1665 } 1666 1667 export function isArrayLiteralOrObjectLiteralDestructuringPattern(node: Node) { 1668 if (node.kind === SyntaxKind.ArrayLiteralExpression || 1669 node.kind === SyntaxKind.ObjectLiteralExpression) { 1670 // [a,b,c] from: 1671 // [a, b, c] = someExpression; 1672 if (node.parent.kind === SyntaxKind.BinaryExpression && 1673 (<BinaryExpression>node.parent).left === node && 1674 (<BinaryExpression>node.parent).operatorToken.kind === SyntaxKind.EqualsToken) { 1675 return true; 1676 } 1677 1678 // [a, b, c] from: 1679 // for([a, b, c] of expression) 1680 if (node.parent.kind === SyntaxKind.ForOfStatement && 1681 (<ForOfStatement>node.parent).initializer === node) { 1682 return true; 1683 } 1684 1685 // [a, b, c] of 1686 // [x, [a, b, c] ] = someExpression 1687 // or 1688 // {x, a: {a, b, c} } = someExpression 1689 if (isArrayLiteralOrObjectLiteralDestructuringPattern(node.parent.kind === SyntaxKind.PropertyAssignment ? node.parent.parent : node.parent)) { 1690 return true; 1691 } 1692 } 1693 1694 return false; 1695 } 1696 1697 export function isInReferenceComment(sourceFile: SourceFile, position: number): boolean { 1698 return isInReferenceCommentWorker(sourceFile, position, /*shouldBeReference*/ true); 1699 } 1700 1701 export function isInNonReferenceComment(sourceFile: SourceFile, position: number): boolean { 1702 return isInReferenceCommentWorker(sourceFile, position, /*shouldBeReference*/ false); 1703 } 1704 1705 function isInReferenceCommentWorker(sourceFile: SourceFile, position: number, shouldBeReference: boolean): boolean { 1706 const range = isInComment(sourceFile, position, /*tokenAtPosition*/ undefined); 1707 return !!range && shouldBeReference === tripleSlashDirectivePrefixRegex.test(sourceFile.text.substring(range.pos, range.end)); 1708 } 1709 1710 export function getReplacementSpanForContextToken(contextToken: Node | undefined) { 1711 if (!contextToken) return undefined; 1712 1713 switch (contextToken.kind) { 1714 case SyntaxKind.StringLiteral: 1715 case SyntaxKind.NoSubstitutionTemplateLiteral: 1716 return createTextSpanFromStringLiteralLikeContent(<StringLiteralLike>contextToken); 1717 default: 1718 return createTextSpanFromNode(contextToken); 1719 } 1720 } 1721 1722 export function createTextSpanFromNode(node: Node, sourceFile?: SourceFile, endNode?: Node): TextSpan { 1723 return createTextSpanFromBounds(node.getStart(sourceFile), (endNode || node).getEnd()); 1724 } 1725 1726 export function createTextSpanFromStringLiteralLikeContent(node: StringLiteralLike) { 1727 if (node.isUnterminated) return undefined; 1728 return createTextSpanFromBounds(node.getStart() + 1, node.getEnd() - 1); 1729 } 1730 1731 export function createTextRangeFromNode(node: Node, sourceFile: SourceFile): TextRange { 1732 return createRange(node.getStart(sourceFile), node.end); 1733 } 1734 1735 export function createTextSpanFromRange(range: TextRange): TextSpan { 1736 return createTextSpanFromBounds(range.pos, range.end); 1737 } 1738 1739 export function createTextRangeFromSpan(span: TextSpan): TextRange { 1740 return createRange(span.start, span.start + span.length); 1741 } 1742 1743 export function createTextChangeFromStartLength(start: number, length: number, newText: string): TextChange { 1744 return createTextChange(createTextSpan(start, length), newText); 1745 } 1746 1747 export function createTextChange(span: TextSpan, newText: string): TextChange { 1748 return { span, newText }; 1749 } 1750 1751 export const typeKeywords: readonly SyntaxKind[] = [ 1752 SyntaxKind.AnyKeyword, 1753 SyntaxKind.AssertsKeyword, 1754 SyntaxKind.BigIntKeyword, 1755 SyntaxKind.BooleanKeyword, 1756 SyntaxKind.FalseKeyword, 1757 SyntaxKind.InferKeyword, 1758 SyntaxKind.KeyOfKeyword, 1759 SyntaxKind.NeverKeyword, 1760 SyntaxKind.NullKeyword, 1761 SyntaxKind.NumberKeyword, 1762 SyntaxKind.ObjectKeyword, 1763 SyntaxKind.ReadonlyKeyword, 1764 SyntaxKind.StringKeyword, 1765 SyntaxKind.SymbolKeyword, 1766 SyntaxKind.TrueKeyword, 1767 SyntaxKind.VoidKeyword, 1768 SyntaxKind.UndefinedKeyword, 1769 SyntaxKind.UniqueKeyword, 1770 SyntaxKind.UnknownKeyword, 1771 ]; 1772 1773 export function isTypeKeyword(kind: SyntaxKind): boolean { 1774 return contains(typeKeywords, kind); 1775 } 1776 1777 export function isTypeKeywordToken(node: Node): node is Token<SyntaxKind.TypeKeyword> { 1778 return node.kind === SyntaxKind.TypeKeyword; 1779 } 1780 1781 /** True if the symbol is for an external module, as opposed to a namespace. */ 1782 export function isExternalModuleSymbol(moduleSymbol: Symbol): boolean { 1783 return !!(moduleSymbol.flags & SymbolFlags.Module) && moduleSymbol.name.charCodeAt(0) === CharacterCodes.doubleQuote; 1784 } 1785 1786 /** Returns `true` the first time it encounters a node and `false` afterwards. */ 1787 export type NodeSeenTracker<T = Node> = (node: T) => boolean; 1788 export function nodeSeenTracker<T extends Node>(): NodeSeenTracker<T> { 1789 const seen: true[] = []; 1790 return node => { 1791 const id = getNodeId(node); 1792 return !seen[id] && (seen[id] = true); 1793 }; 1794 } 1795 1796 export function getSnapshotText(snap: IScriptSnapshot): string { 1797 return snap.getText(0, snap.getLength()); 1798 } 1799 1800 export function repeatString(str: string, count: number): string { 1801 let result = ""; 1802 for (let i = 0; i < count; i++) { 1803 result += str; 1804 } 1805 return result; 1806 } 1807 1808 export function skipConstraint(type: Type): Type { 1809 return type.isTypeParameter() ? type.getConstraint() || type : type; 1810 } 1811 1812 export function getNameFromPropertyName(name: PropertyName): string | undefined { 1813 return name.kind === SyntaxKind.ComputedPropertyName 1814 // treat computed property names where expression is string/numeric literal as just string/numeric literal 1815 ? isStringOrNumericLiteralLike(name.expression) ? name.expression.text : undefined 1816 : isPrivateIdentifier(name) ? idText(name) : getTextOfIdentifierOrLiteral(name); 1817 } 1818 1819 export function programContainsModules(program: Program): boolean { 1820 return program.getSourceFiles().some(s => !s.isDeclarationFile && !program.isSourceFileFromExternalLibrary(s) && !!(s.externalModuleIndicator || s.commonJsModuleIndicator)); 1821 } 1822 export function programContainsEs6Modules(program: Program): boolean { 1823 return program.getSourceFiles().some(s => !s.isDeclarationFile && !program.isSourceFileFromExternalLibrary(s) && !!s.externalModuleIndicator); 1824 } 1825 export function compilerOptionsIndicateEs6Modules(compilerOptions: CompilerOptions): boolean { 1826 return !!compilerOptions.module || compilerOptions.target! >= ScriptTarget.ES2015 || !!compilerOptions.noEmit; 1827 } 1828 1829 export function createModuleSpecifierResolutionHost(program: Program, host: LanguageServiceHost): ModuleSpecifierResolutionHost { 1830 // Mix in `getProbableSymlinks` from Program when host doesn't have it 1831 // in order for non-Project hosts to have a symlinks cache. 1832 return { 1833 fileExists: fileName => program.fileExists(fileName), 1834 getCurrentDirectory: () => host.getCurrentDirectory(), 1835 readFile: maybeBind(host, host.readFile), 1836 useCaseSensitiveFileNames: maybeBind(host, host.useCaseSensitiveFileNames), 1837 getSymlinkCache: maybeBind(host, host.getSymlinkCache) || program.getSymlinkCache, 1838 getGlobalTypingsCacheLocation: maybeBind(host, host.getGlobalTypingsCacheLocation), 1839 getSourceFiles: () => program.getSourceFiles(), 1840 redirectTargetsMap: program.redirectTargetsMap, 1841 getProjectReferenceRedirect: fileName => program.getProjectReferenceRedirect(fileName), 1842 isSourceOfProjectReferenceRedirect: fileName => program.isSourceOfProjectReferenceRedirect(fileName), 1843 getNearestAncestorDirectoryWithPackageJson: maybeBind(host, host.getNearestAncestorDirectoryWithPackageJson), 1844 getFileIncludeReasons: () => program.getFileIncludeReasons(), 1845 }; 1846 } 1847 1848 export function getModuleSpecifierResolverHost(program: Program, host: LanguageServiceHost): SymbolTracker["moduleResolverHost"] { 1849 return { 1850 ...createModuleSpecifierResolutionHost(program, host), 1851 getCommonSourceDirectory: () => program.getCommonSourceDirectory(), 1852 }; 1853 } 1854 1855 export function makeImportIfNecessary(defaultImport: Identifier | undefined, namedImports: readonly ImportSpecifier[] | undefined, moduleSpecifier: string, quotePreference: QuotePreference): ImportDeclaration | undefined { 1856 return defaultImport || namedImports && namedImports.length ? makeImport(defaultImport, namedImports, moduleSpecifier, quotePreference) : undefined; 1857 } 1858 1859 export function makeImport(defaultImport: Identifier | undefined, namedImports: readonly ImportSpecifier[] | undefined, moduleSpecifier: string | Expression, quotePreference: QuotePreference, isTypeOnly?: boolean): ImportDeclaration { 1860 return factory.createImportDeclaration( 1861 /*decorators*/ undefined, 1862 /*modifiers*/ undefined, 1863 defaultImport || namedImports 1864 ? factory.createImportClause(!!isTypeOnly, defaultImport, namedImports && namedImports.length ? factory.createNamedImports(namedImports) : undefined) 1865 : undefined, 1866 typeof moduleSpecifier === "string" ? makeStringLiteral(moduleSpecifier, quotePreference) : moduleSpecifier); 1867 } 1868 1869 export function makeStringLiteral(text: string, quotePreference: QuotePreference): StringLiteral { 1870 return factory.createStringLiteral(text, quotePreference === QuotePreference.Single); 1871 } 1872 1873 export const enum QuotePreference { Single, Double } 1874 1875 export function quotePreferenceFromString(str: StringLiteral, sourceFile: SourceFile): QuotePreference { 1876 return isStringDoubleQuoted(str, sourceFile) ? QuotePreference.Double : QuotePreference.Single; 1877 } 1878 1879 export function getQuotePreference(sourceFile: SourceFile, preferences: UserPreferences): QuotePreference { 1880 if (preferences.quotePreference && preferences.quotePreference !== "auto") { 1881 return preferences.quotePreference === "single" ? QuotePreference.Single : QuotePreference.Double; 1882 } 1883 else { 1884 // ignore synthetic import added when importHelpers: true 1885 const firstModuleSpecifier = sourceFile.imports && 1886 find(sourceFile.imports, n => isStringLiteral(n) && !nodeIsSynthesized(n.parent)) as StringLiteral; 1887 return firstModuleSpecifier ? quotePreferenceFromString(firstModuleSpecifier, sourceFile) : QuotePreference.Double; 1888 } 1889 } 1890 1891 export function getQuoteFromPreference(qp: QuotePreference): string { 1892 switch (qp) { 1893 case QuotePreference.Single: return "'"; 1894 case QuotePreference.Double: return '"'; 1895 default: return Debug.assertNever(qp); 1896 } 1897 } 1898 1899 export function symbolNameNoDefault(symbol: Symbol): string | undefined { 1900 const escaped = symbolEscapedNameNoDefault(symbol); 1901 return escaped === undefined ? undefined : unescapeLeadingUnderscores(escaped); 1902 } 1903 1904 export function symbolEscapedNameNoDefault(symbol: Symbol): __String | undefined { 1905 if (symbol.escapedName !== InternalSymbolName.Default) { 1906 return symbol.escapedName; 1907 } 1908 1909 return firstDefined(symbol.declarations, decl => { 1910 const name = getNameOfDeclaration(decl); 1911 return name && name.kind === SyntaxKind.Identifier ? name.escapedText : undefined; 1912 }); 1913 } 1914 1915 export type ObjectBindingElementWithoutPropertyName = BindingElement & { name: Identifier }; 1916 1917 export function isObjectBindingElementWithoutPropertyName(bindingElement: Node): bindingElement is ObjectBindingElementWithoutPropertyName { 1918 return isBindingElement(bindingElement) && 1919 isObjectBindingPattern(bindingElement.parent) && 1920 isIdentifier(bindingElement.name) && 1921 !bindingElement.propertyName; 1922 } 1923 1924 export function getPropertySymbolFromBindingElement(checker: TypeChecker, bindingElement: ObjectBindingElementWithoutPropertyName): Symbol | undefined { 1925 const typeOfPattern = checker.getTypeAtLocation(bindingElement.parent); 1926 return typeOfPattern && checker.getPropertyOfType(typeOfPattern, bindingElement.name.text); 1927 } 1928 1929 export function getParentNodeInSpan(node: Node | undefined, file: SourceFile, span: TextSpan): Node | undefined { 1930 if (!node) return undefined; 1931 1932 while (node.parent) { 1933 if (isSourceFile(node.parent) || !spanContainsNode(span, node.parent, file)) { 1934 return node; 1935 } 1936 1937 node = node.parent; 1938 } 1939 } 1940 1941 function spanContainsNode(span: TextSpan, node: Node, file: SourceFile): boolean { 1942 return textSpanContainsPosition(span, node.getStart(file)) && 1943 node.getEnd() <= textSpanEnd(span); 1944 } 1945 1946 export function findModifier(node: Node, kind: Modifier["kind"]): Modifier | undefined { 1947 return node.modifiers && find(node.modifiers, m => m.kind === kind); 1948 } 1949 1950 export function insertImports(changes: textChanges.ChangeTracker, sourceFile: SourceFile, imports: AnyImportOrRequireStatement | readonly AnyImportOrRequireStatement[], blankLineBetween: boolean): void { 1951 const decl = isArray(imports) ? imports[0] : imports; 1952 const importKindPredicate: (node: Node) => node is AnyImportOrRequireStatement = decl.kind === SyntaxKind.VariableStatement ? isRequireVariableStatement : isAnyImportSyntax; 1953 const existingImportStatements = filter(sourceFile.statements, importKindPredicate); 1954 const sortedNewImports = isArray(imports) ? stableSort(imports, OrganizeImports.compareImportsOrRequireStatements) : [imports]; 1955 if (!existingImportStatements.length) { 1956 changes.insertNodesAtTopOfFile(sourceFile, sortedNewImports, blankLineBetween); 1957 } 1958 else if (existingImportStatements && OrganizeImports.importsAreSorted(existingImportStatements)) { 1959 for (const newImport of sortedNewImports) { 1960 const insertionIndex = OrganizeImports.getImportDeclarationInsertionIndex(existingImportStatements, newImport); 1961 if (insertionIndex === 0) { 1962 // If the first import is top-of-file, insert after the leading comment which is likely the header. 1963 const options = existingImportStatements[0] === sourceFile.statements[0] ? 1964 { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude } : {}; 1965 changes.insertNodeBefore(sourceFile, existingImportStatements[0], newImport, /*blankLineBetween*/ false, options); 1966 } 1967 else { 1968 const prevImport = existingImportStatements[insertionIndex - 1]; 1969 changes.insertNodeAfter(sourceFile, prevImport, newImport); 1970 } 1971 } 1972 } 1973 else { 1974 const lastExistingImport = lastOrUndefined(existingImportStatements); 1975 if (lastExistingImport) { 1976 changes.insertNodesAfter(sourceFile, lastExistingImport, sortedNewImports); 1977 } 1978 else { 1979 changes.insertNodesAtTopOfFile(sourceFile, sortedNewImports, blankLineBetween); 1980 } 1981 } 1982 } 1983 1984 export function getTypeKeywordOfTypeOnlyImport(importClause: ImportClause, sourceFile: SourceFile): Token<SyntaxKind.TypeKeyword> { 1985 Debug.assert(importClause.isTypeOnly); 1986 return cast(importClause.getChildAt(0, sourceFile), isTypeKeywordToken); 1987 } 1988 1989 export function textSpansEqual(a: TextSpan | undefined, b: TextSpan | undefined): boolean { 1990 return !!a && !!b && a.start === b.start && a.length === b.length; 1991 } 1992 export function documentSpansEqual(a: DocumentSpan, b: DocumentSpan): boolean { 1993 return a.fileName === b.fileName && textSpansEqual(a.textSpan, b.textSpan); 1994 } 1995 1996 /** 1997 * Iterates through 'array' by index and performs the callback on each element of array until the callback 1998 * returns a truthy value, then returns that value. 1999 * If no such value is found, the callback is applied to each element of array and undefined is returned. 2000 */ 2001 export function forEachUnique<T, U>(array: readonly T[] | undefined, callback: (element: T, index: number) => U): U | undefined { 2002 if (array) { 2003 for (let i = 0; i < array.length; i++) { 2004 if (array.indexOf(array[i]) === i) { 2005 const result = callback(array[i], i); 2006 if (result) { 2007 return result; 2008 } 2009 } 2010 } 2011 } 2012 return undefined; 2013 } 2014 2015 export function isTextWhiteSpaceLike(text: string, startPos: number, endPos: number): boolean { 2016 for (let i = startPos; i < endPos; i++) { 2017 if (!isWhiteSpaceLike(text.charCodeAt(i))) { 2018 return false; 2019 } 2020 } 2021 2022 return true; 2023 } 2024 2025 // #endregion 2026 2027 // Display-part writer helpers 2028 // #region 2029 export function isFirstDeclarationOfSymbolParameter(symbol: Symbol) { 2030 return symbol.declarations && symbol.declarations.length > 0 && symbol.declarations[0].kind === SyntaxKind.Parameter; 2031 } 2032 2033 const displayPartWriter = getDisplayPartWriter(); 2034 function getDisplayPartWriter(): DisplayPartsSymbolWriter { 2035 const absoluteMaximumLength = defaultMaximumTruncationLength * 10; // A hard cutoff to avoid overloading the messaging channel in worst-case scenarios 2036 let displayParts: SymbolDisplayPart[]; 2037 let lineStart: boolean; 2038 let indent: number; 2039 let length: number; 2040 2041 resetWriter(); 2042 const unknownWrite = (text: string) => writeKind(text, SymbolDisplayPartKind.text); 2043 return { 2044 displayParts: () => { 2045 const finalText = displayParts.length && displayParts[displayParts.length - 1].text; 2046 if (length > absoluteMaximumLength && finalText && finalText !== "...") { 2047 if (!isWhiteSpaceLike(finalText.charCodeAt(finalText.length - 1))) { 2048 displayParts.push(displayPart(" ", SymbolDisplayPartKind.space)); 2049 } 2050 displayParts.push(displayPart("...", SymbolDisplayPartKind.punctuation)); 2051 } 2052 return displayParts; 2053 }, 2054 writeKeyword: text => writeKind(text, SymbolDisplayPartKind.keyword), 2055 writeOperator: text => writeKind(text, SymbolDisplayPartKind.operator), 2056 writePunctuation: text => writeKind(text, SymbolDisplayPartKind.punctuation), 2057 writeTrailingSemicolon: text => writeKind(text, SymbolDisplayPartKind.punctuation), 2058 writeSpace: text => writeKind(text, SymbolDisplayPartKind.space), 2059 writeStringLiteral: text => writeKind(text, SymbolDisplayPartKind.stringLiteral), 2060 writeParameter: text => writeKind(text, SymbolDisplayPartKind.parameterName), 2061 writeProperty: text => writeKind(text, SymbolDisplayPartKind.propertyName), 2062 writeLiteral: text => writeKind(text, SymbolDisplayPartKind.stringLiteral), 2063 writeSymbol, 2064 writeLine, 2065 write: unknownWrite, 2066 writeComment: unknownWrite, 2067 getText: () => "", 2068 getTextPos: () => 0, 2069 getColumn: () => 0, 2070 getLine: () => 0, 2071 isAtStartOfLine: () => false, 2072 hasTrailingWhitespace: () => false, 2073 hasTrailingComment: () => false, 2074 rawWrite: notImplemented, 2075 getIndent: () => indent, 2076 increaseIndent: () => { indent++; }, 2077 decreaseIndent: () => { indent--; }, 2078 clear: resetWriter, 2079 trackSymbol: noop, 2080 reportInaccessibleThisError: noop, 2081 reportInaccessibleUniqueSymbolError: noop, 2082 reportPrivateInBaseOfClassExpression: noop, 2083 }; 2084 2085 function writeIndent() { 2086 if (length > absoluteMaximumLength) return; 2087 if (lineStart) { 2088 const indentString = getIndentString(indent); 2089 if (indentString) { 2090 length += indentString.length; 2091 displayParts.push(displayPart(indentString, SymbolDisplayPartKind.space)); 2092 } 2093 lineStart = false; 2094 } 2095 } 2096 2097 function writeKind(text: string, kind: SymbolDisplayPartKind) { 2098 if (length > absoluteMaximumLength) return; 2099 writeIndent(); 2100 length += text.length; 2101 displayParts.push(displayPart(text, kind)); 2102 } 2103 2104 function writeSymbol(text: string, symbol: Symbol) { 2105 if (length > absoluteMaximumLength) return; 2106 writeIndent(); 2107 length += text.length; 2108 displayParts.push(symbolPart(text, symbol)); 2109 } 2110 2111 function writeLine() { 2112 if (length > absoluteMaximumLength) return; 2113 length += 1; 2114 displayParts.push(lineBreakPart()); 2115 lineStart = true; 2116 } 2117 2118 function resetWriter() { 2119 displayParts = []; 2120 lineStart = true; 2121 indent = 0; 2122 length = 0; 2123 } 2124 } 2125 2126 export function symbolPart(text: string, symbol: Symbol) { 2127 return displayPart(text, displayPartKind(symbol)); 2128 2129 function displayPartKind(symbol: Symbol): SymbolDisplayPartKind { 2130 const flags = symbol.flags; 2131 2132 if (flags & SymbolFlags.Variable) { 2133 return isFirstDeclarationOfSymbolParameter(symbol) ? SymbolDisplayPartKind.parameterName : SymbolDisplayPartKind.localName; 2134 } 2135 else if (flags & SymbolFlags.Property) { return SymbolDisplayPartKind.propertyName; } 2136 else if (flags & SymbolFlags.GetAccessor) { return SymbolDisplayPartKind.propertyName; } 2137 else if (flags & SymbolFlags.SetAccessor) { return SymbolDisplayPartKind.propertyName; } 2138 else if (flags & SymbolFlags.EnumMember) { return SymbolDisplayPartKind.enumMemberName; } 2139 else if (flags & SymbolFlags.Function) { return SymbolDisplayPartKind.functionName; } 2140 else if (flags & SymbolFlags.Class) { return SymbolDisplayPartKind.className; } 2141 else if (flags & SymbolFlags.Interface) { return SymbolDisplayPartKind.interfaceName; } 2142 else if (flags & SymbolFlags.Enum) { return SymbolDisplayPartKind.enumName; } 2143 else if (flags & SymbolFlags.Module) { return SymbolDisplayPartKind.moduleName; } 2144 else if (flags & SymbolFlags.Method) { return SymbolDisplayPartKind.methodName; } 2145 else if (flags & SymbolFlags.TypeParameter) { return SymbolDisplayPartKind.typeParameterName; } 2146 else if (flags & SymbolFlags.TypeAlias) { return SymbolDisplayPartKind.aliasName; } 2147 else if (flags & SymbolFlags.Alias) { return SymbolDisplayPartKind.aliasName; } 2148 2149 return SymbolDisplayPartKind.text; 2150 } 2151 } 2152 2153 export function displayPart(text: string, kind: SymbolDisplayPartKind): SymbolDisplayPart { 2154 return { text, kind: SymbolDisplayPartKind[kind] }; 2155 } 2156 2157 export function spacePart() { 2158 return displayPart(" ", SymbolDisplayPartKind.space); 2159 } 2160 2161 export function keywordPart(kind: SyntaxKind) { 2162 return displayPart(tokenToString(kind)!, SymbolDisplayPartKind.keyword); 2163 } 2164 2165 export function punctuationPart(kind: SyntaxKind) { 2166 return displayPart(tokenToString(kind)!, SymbolDisplayPartKind.punctuation); 2167 } 2168 2169 export function operatorPart(kind: SyntaxKind) { 2170 return displayPart(tokenToString(kind)!, SymbolDisplayPartKind.operator); 2171 } 2172 2173 export function textOrKeywordPart(text: string) { 2174 const kind = stringToToken(text); 2175 return kind === undefined 2176 ? textPart(text) 2177 : keywordPart(kind); 2178 } 2179 2180 export function textPart(text: string) { 2181 return displayPart(text, SymbolDisplayPartKind.text); 2182 } 2183 2184 const carriageReturnLineFeed = "\r\n"; 2185 /** 2186 * The default is CRLF. 2187 */ 2188 export function getNewLineOrDefaultFromHost(host: FormattingHost, formatSettings?: FormatCodeSettings) { 2189 return formatSettings?.newLineCharacter || 2190 host.getNewLine?.() || 2191 carriageReturnLineFeed; 2192 } 2193 2194 export function lineBreakPart() { 2195 return displayPart("\n", SymbolDisplayPartKind.lineBreak); 2196 } 2197 2198 export function mapToDisplayParts(writeDisplayParts: (writer: DisplayPartsSymbolWriter) => void): SymbolDisplayPart[] { 2199 try { 2200 writeDisplayParts(displayPartWriter); 2201 return displayPartWriter.displayParts(); 2202 } 2203 finally { 2204 displayPartWriter.clear(); 2205 } 2206 } 2207 2208 export function typeToDisplayParts(typechecker: TypeChecker, type: Type, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.None): SymbolDisplayPart[] { 2209 return mapToDisplayParts(writer => { 2210 typechecker.writeType(type, enclosingDeclaration, flags | TypeFormatFlags.MultilineObjectLiterals | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer); 2211 }); 2212 } 2213 2214 export function symbolToDisplayParts(typeChecker: TypeChecker, symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags: SymbolFormatFlags = SymbolFormatFlags.None): SymbolDisplayPart[] { 2215 return mapToDisplayParts(writer => { 2216 typeChecker.writeSymbol(symbol, enclosingDeclaration, meaning, flags | SymbolFormatFlags.UseAliasDefinedOutsideCurrentScope, writer); 2217 }); 2218 } 2219 2220 export function signatureToDisplayParts(typechecker: TypeChecker, signature: Signature, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.None): SymbolDisplayPart[] { 2221 flags |= TypeFormatFlags.UseAliasDefinedOutsideCurrentScope | TypeFormatFlags.MultilineObjectLiterals | TypeFormatFlags.WriteTypeArgumentsOfSignature | TypeFormatFlags.OmitParameterModifiers; 2222 return mapToDisplayParts(writer => { 2223 typechecker.writeSignature(signature, enclosingDeclaration, flags, /*signatureKind*/ undefined, writer); 2224 }); 2225 } 2226 2227 export function isImportOrExportSpecifierName(location: Node): location is Identifier { 2228 return !!location.parent && isImportOrExportSpecifier(location.parent) && location.parent.propertyName === location; 2229 } 2230 2231 export function getScriptKind(fileName: string, host: LanguageServiceHost): ScriptKind { 2232 // First check to see if the script kind was specified by the host. Chances are the host 2233 // may override the default script kind for the file extension. 2234 return ensureScriptKind(fileName, host.getScriptKind && host.getScriptKind(fileName)); 2235 } 2236 2237 export function getSymbolTarget(symbol: Symbol, checker: TypeChecker): Symbol { 2238 let next: Symbol = symbol; 2239 while (isAliasSymbol(next) || (isTransientSymbol(next) && next.target)) { 2240 if (isTransientSymbol(next) && next.target) { 2241 next = next.target; 2242 } 2243 else { 2244 next = skipAlias(next, checker); 2245 } 2246 } 2247 return next; 2248 } 2249 2250 function isTransientSymbol(symbol: Symbol): symbol is TransientSymbol { 2251 return (symbol.flags & SymbolFlags.Transient) !== 0; 2252 } 2253 2254 function isAliasSymbol(symbol: Symbol): boolean { 2255 return (symbol.flags & SymbolFlags.Alias) !== 0; 2256 } 2257 2258 export function getUniqueSymbolId(symbol: Symbol, checker: TypeChecker) { 2259 return getSymbolId(skipAlias(symbol, checker)); 2260 } 2261 2262 export function getFirstNonSpaceCharacterPosition(text: string, position: number) { 2263 while (isWhiteSpaceLike(text.charCodeAt(position))) { 2264 position += 1; 2265 } 2266 return position; 2267 } 2268 2269 export function getPrecedingNonSpaceCharacterPosition(text: string, position: number) { 2270 while (position > -1 && isWhiteSpaceSingleLine(text.charCodeAt(position))) { 2271 position -= 1; 2272 } 2273 return position + 1; 2274 } 2275 2276 /** 2277 * Creates a deep, memberwise clone of a node with no source map location. 2278 * 2279 * WARNING: This is an expensive operation and is only intended to be used in refactorings 2280 * and code fixes (because those are triggered by explicit user actions). 2281 */ 2282 export function getSynthesizedDeepClone<T extends Node | undefined>(node: T, includeTrivia = true): T { 2283 const clone = node && getSynthesizedDeepCloneWorker(node as NonNullable<T>); 2284 if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone); 2285 return clone; 2286 } 2287 2288 export function getSynthesizedDeepCloneWithReplacements<T extends Node>( 2289 node: T, 2290 includeTrivia: boolean, 2291 replaceNode: (node: Node) => Node | undefined 2292 ): T { 2293 let clone = replaceNode(node); 2294 if (clone) { 2295 setOriginalNode(clone, node); 2296 } 2297 else { 2298 clone = getSynthesizedDeepCloneWorker(node as NonNullable<T>, replaceNode); 2299 } 2300 2301 if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone); 2302 return clone as T; 2303 } 2304 2305 function getSynthesizedDeepCloneWorker<T extends Node>(node: T, replaceNode?: (node: Node) => Node | undefined): T { 2306 const visited = replaceNode ? 2307 visitEachChild(node, n => getSynthesizedDeepCloneWithReplacements(n, /*includeTrivia*/ true, replaceNode), nullTransformationContext) : 2308 visitEachChild(node, getSynthesizedDeepClone, nullTransformationContext); 2309 2310 if (visited === node) { 2311 // This only happens for leaf nodes - internal nodes always see their children change. 2312 const clone = 2313 isStringLiteral(node) ? setOriginalNode(factory.createStringLiteralFromNode(node), node) as Node as T : 2314 isNumericLiteral(node) ? setOriginalNode(factory.createNumericLiteral(node.text, node.numericLiteralFlags), node) as Node as T : 2315 factory.cloneNode(node); 2316 return setTextRange(clone, node); 2317 } 2318 2319 // PERF: As an optimization, rather than calling getSynthesizedClone, we'll update 2320 // the new node created by visitEachChild with the extra changes getSynthesizedClone 2321 // would have made. 2322 (visited as Mutable<T>).parent = undefined!; 2323 return visited; 2324 } 2325 2326 export function getSynthesizedDeepClones<T extends Node>(nodes: NodeArray<T>, includeTrivia?: boolean): NodeArray<T>; 2327 export function getSynthesizedDeepClones<T extends Node>(nodes: NodeArray<T> | undefined, includeTrivia?: boolean): NodeArray<T> | undefined; 2328 export function getSynthesizedDeepClones<T extends Node>(nodes: NodeArray<T> | undefined, includeTrivia = true): NodeArray<T> | undefined { 2329 return nodes && factory.createNodeArray(nodes.map(n => getSynthesizedDeepClone(n, includeTrivia)), nodes.hasTrailingComma); 2330 } 2331 2332 export function getSynthesizedDeepClonesWithReplacements<T extends Node>( 2333 nodes: NodeArray<T>, 2334 includeTrivia: boolean, 2335 replaceNode: (node: Node) => Node | undefined 2336 ): NodeArray<T> { 2337 return factory.createNodeArray(nodes.map(n => getSynthesizedDeepCloneWithReplacements(n, includeTrivia, replaceNode)), nodes.hasTrailingComma); 2338 } 2339 2340 /** 2341 * Sets EmitFlags to suppress leading and trailing trivia on the node. 2342 */ 2343 export function suppressLeadingAndTrailingTrivia(node: Node) { 2344 suppressLeadingTrivia(node); 2345 suppressTrailingTrivia(node); 2346 } 2347 2348 /** 2349 * Sets EmitFlags to suppress leading trivia on the node. 2350 */ 2351 export function suppressLeadingTrivia(node: Node) { 2352 addEmitFlagsRecursively(node, EmitFlags.NoLeadingComments, getFirstChild); 2353 } 2354 2355 /** 2356 * Sets EmitFlags to suppress trailing trivia on the node. 2357 */ 2358 export function suppressTrailingTrivia(node: Node) { 2359 addEmitFlagsRecursively(node, EmitFlags.NoTrailingComments, getLastChild); 2360 } 2361 2362 export function copyComments(sourceNode: Node, targetNode: Node) { 2363 const sourceFile = sourceNode.getSourceFile(); 2364 const text = sourceFile.text; 2365 if (hasLeadingLineBreak(sourceNode, text)) { 2366 copyLeadingComments(sourceNode, targetNode, sourceFile); 2367 } 2368 else { 2369 copyTrailingAsLeadingComments(sourceNode, targetNode, sourceFile); 2370 } 2371 copyTrailingComments(sourceNode, targetNode, sourceFile); 2372 } 2373 2374 function hasLeadingLineBreak(node: Node, text: string) { 2375 const start = node.getFullStart(); 2376 const end = node.getStart(); 2377 for (let i = start; i < end; i++) { 2378 if (text.charCodeAt(i) === CharacterCodes.lineFeed) return true; 2379 } 2380 return false; 2381 } 2382 2383 function addEmitFlagsRecursively(node: Node, flag: EmitFlags, getChild: (n: Node) => Node | undefined) { 2384 addEmitFlags(node, flag); 2385 const child = getChild(node); 2386 if (child) addEmitFlagsRecursively(child, flag, getChild); 2387 } 2388 2389 function getFirstChild(node: Node): Node | undefined { 2390 return node.forEachChild(child => child); 2391 } 2392 2393 export function getUniqueName(baseName: string, sourceFile: SourceFile): string { 2394 let nameText = baseName; 2395 for (let i = 1; !isFileLevelUniqueName(sourceFile, nameText); i++) { 2396 nameText = `${baseName}_${i}`; 2397 } 2398 return nameText; 2399 } 2400 2401 /** 2402 * @return The index of the (only) reference to the extracted symbol. We want the cursor 2403 * to be on the reference, rather than the declaration, because it's closer to where the 2404 * user was before extracting it. 2405 */ 2406 export function getRenameLocation(edits: readonly FileTextChanges[], renameFilename: string, name: string, preferLastLocation: boolean): number { 2407 let delta = 0; 2408 let lastPos = -1; 2409 for (const { fileName, textChanges } of edits) { 2410 Debug.assert(fileName === renameFilename); 2411 for (const change of textChanges) { 2412 const { span, newText } = change; 2413 const index = indexInTextChange(newText, name); 2414 if (index !== -1) { 2415 lastPos = span.start + delta + index; 2416 2417 // If the reference comes first, return immediately. 2418 if (!preferLastLocation) { 2419 return lastPos; 2420 } 2421 } 2422 delta += newText.length - span.length; 2423 } 2424 } 2425 2426 // If the declaration comes first, return the position of the last occurrence. 2427 Debug.assert(preferLastLocation); 2428 Debug.assert(lastPos >= 0); 2429 return lastPos; 2430 } 2431 2432 export function copyLeadingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) { 2433 forEachLeadingCommentRange(sourceFile.text, sourceNode.pos, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticLeadingComment)); 2434 } 2435 2436 2437 export function copyTrailingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) { 2438 forEachTrailingCommentRange(sourceFile.text, sourceNode.end, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticTrailingComment)); 2439 } 2440 2441 /** 2442 * This function copies the trailing comments for the token that comes before `sourceNode`, as leading comments of `targetNode`. 2443 * This is useful because sometimes a comment that refers to `sourceNode` will be a leading comment for `sourceNode`, according to the 2444 * notion of trivia ownership, and instead will be a trailing comment for the token before `sourceNode`, e.g.: 2445 * `function foo(\* not leading comment for a *\ a: string) {}` 2446 * The comment refers to `a` but belongs to the `(` token, but we might want to copy it. 2447 */ 2448 export function copyTrailingAsLeadingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) { 2449 forEachTrailingCommentRange(sourceFile.text, sourceNode.pos, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticLeadingComment)); 2450 } 2451 2452 function getAddCommentsFunction(targetNode: Node, sourceFile: SourceFile, commentKind: CommentKind | undefined, hasTrailingNewLine: boolean | undefined, cb: (node: Node, kind: CommentKind, text: string, hasTrailingNewLine?: boolean) => void) { 2453 return (pos: number, end: number, kind: CommentKind, htnl: boolean) => { 2454 if (kind === SyntaxKind.MultiLineCommentTrivia) { 2455 // Remove leading /* 2456 pos += 2; 2457 // Remove trailing */ 2458 end -= 2; 2459 } 2460 else { 2461 // Remove leading // 2462 pos += 2; 2463 } 2464 cb(targetNode, commentKind || kind, sourceFile.text.slice(pos, end), hasTrailingNewLine !== undefined ? hasTrailingNewLine : htnl); 2465 }; 2466 } 2467 2468 function indexInTextChange(change: string, name: string): number { 2469 if (startsWith(change, name)) return 0; 2470 // Add a " " to avoid references inside words 2471 let idx = change.indexOf(" " + name); 2472 if (idx === -1) idx = change.indexOf("." + name); 2473 if (idx === -1) idx = change.indexOf('"' + name); 2474 return idx === -1 ? -1 : idx + 1; 2475 } 2476 2477 /* @internal */ 2478 export function needsParentheses(expression: Expression) { 2479 return isBinaryExpression(expression) && expression.operatorToken.kind === SyntaxKind.CommaToken || isObjectLiteralExpression(expression); 2480 } 2481 2482 export function getContextualTypeFromParent(node: Expression, checker: TypeChecker): Type | undefined { 2483 const { parent } = node; 2484 switch (parent.kind) { 2485 case SyntaxKind.NewExpression: 2486 return checker.getContextualType(parent as NewExpression); 2487 case SyntaxKind.BinaryExpression: { 2488 const { left, operatorToken, right } = parent as BinaryExpression; 2489 return isEqualityOperatorKind(operatorToken.kind) 2490 ? checker.getTypeAtLocation(node === right ? left : right) 2491 : checker.getContextualType(node); 2492 } 2493 case SyntaxKind.CaseClause: 2494 return (parent as CaseClause).expression === node ? getSwitchedType(parent as CaseClause, checker) : undefined; 2495 default: 2496 return checker.getContextualType(node); 2497 } 2498 } 2499 2500 export function quote(sourceFile: SourceFile, preferences: UserPreferences, text: string): string { 2501 // Editors can pass in undefined or empty string - we want to infer the preference in those cases. 2502 const quotePreference = getQuotePreference(sourceFile, preferences); 2503 const quoted = JSON.stringify(text); 2504 return quotePreference === QuotePreference.Single ? `'${stripQuotes(quoted).replace(/'/g, "\\'").replace(/\\"/g, '"')}'` : quoted; 2505 } 2506 2507 export function isEqualityOperatorKind(kind: SyntaxKind): kind is EqualityOperator { 2508 switch (kind) { 2509 case SyntaxKind.EqualsEqualsEqualsToken: 2510 case SyntaxKind.EqualsEqualsToken: 2511 case SyntaxKind.ExclamationEqualsEqualsToken: 2512 case SyntaxKind.ExclamationEqualsToken: 2513 return true; 2514 default: 2515 return false; 2516 } 2517 } 2518 2519 export function isStringLiteralOrTemplate(node: Node): node is StringLiteralLike | TemplateExpression | TaggedTemplateExpression { 2520 switch (node.kind) { 2521 case SyntaxKind.StringLiteral: 2522 case SyntaxKind.NoSubstitutionTemplateLiteral: 2523 case SyntaxKind.TemplateExpression: 2524 case SyntaxKind.TaggedTemplateExpression: 2525 return true; 2526 default: 2527 return false; 2528 } 2529 } 2530 2531 export function hasIndexSignature(type: Type): boolean { 2532 return !!type.getStringIndexType() || !!type.getNumberIndexType(); 2533 } 2534 2535 export function getSwitchedType(caseClause: CaseClause, checker: TypeChecker): Type | undefined { 2536 return checker.getTypeAtLocation(caseClause.parent.parent.expression); 2537 } 2538 2539 export const ANONYMOUS = "anonymous function"; 2540 2541 export function getTypeNodeIfAccessible(type: Type, enclosingScope: Node, program: Program, host: LanguageServiceHost): TypeNode | undefined { 2542 const checker = program.getTypeChecker(); 2543 let typeIsAccessible = true; 2544 const notAccessible = () => { typeIsAccessible = false; }; 2545 const res = checker.typeToTypeNode(type, enclosingScope, NodeBuilderFlags.NoTruncation, { 2546 trackSymbol: (symbol, declaration, meaning) => { 2547 typeIsAccessible = typeIsAccessible && checker.isSymbolAccessible(symbol, declaration, meaning, /*shouldComputeAliasToMarkVisible*/ false).accessibility === SymbolAccessibility.Accessible; 2548 }, 2549 reportInaccessibleThisError: notAccessible, 2550 reportPrivateInBaseOfClassExpression: notAccessible, 2551 reportInaccessibleUniqueSymbolError: notAccessible, 2552 moduleResolverHost: getModuleSpecifierResolverHost(program, host) 2553 }); 2554 return typeIsAccessible ? res : undefined; 2555 } 2556 2557 export function syntaxRequiresTrailingCommaOrSemicolonOrASI(kind: SyntaxKind) { 2558 return kind === SyntaxKind.CallSignature 2559 || kind === SyntaxKind.ConstructSignature 2560 || kind === SyntaxKind.IndexSignature 2561 || kind === SyntaxKind.PropertySignature 2562 || kind === SyntaxKind.MethodSignature; 2563 } 2564 2565 export function syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI(kind: SyntaxKind) { 2566 return kind === SyntaxKind.FunctionDeclaration 2567 || kind === SyntaxKind.Constructor 2568 || kind === SyntaxKind.MethodDeclaration 2569 || kind === SyntaxKind.GetAccessor 2570 || kind === SyntaxKind.SetAccessor; 2571 } 2572 2573 export function syntaxRequiresTrailingModuleBlockOrSemicolonOrASI(kind: SyntaxKind) { 2574 return kind === SyntaxKind.ModuleDeclaration; 2575 } 2576 2577 export function syntaxRequiresTrailingSemicolonOrASI(kind: SyntaxKind) { 2578 return kind === SyntaxKind.VariableStatement 2579 || kind === SyntaxKind.ExpressionStatement 2580 || kind === SyntaxKind.DoStatement 2581 || kind === SyntaxKind.ContinueStatement 2582 || kind === SyntaxKind.BreakStatement 2583 || kind === SyntaxKind.ReturnStatement 2584 || kind === SyntaxKind.ThrowStatement 2585 || kind === SyntaxKind.DebuggerStatement 2586 || kind === SyntaxKind.PropertyDeclaration 2587 || kind === SyntaxKind.TypeAliasDeclaration 2588 || kind === SyntaxKind.ImportDeclaration 2589 || kind === SyntaxKind.ImportEqualsDeclaration 2590 || kind === SyntaxKind.ExportDeclaration 2591 || kind === SyntaxKind.NamespaceExportDeclaration 2592 || kind === SyntaxKind.ExportAssignment; 2593 } 2594 2595 export const syntaxMayBeASICandidate = or( 2596 syntaxRequiresTrailingCommaOrSemicolonOrASI, 2597 syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI, 2598 syntaxRequiresTrailingModuleBlockOrSemicolonOrASI, 2599 syntaxRequiresTrailingSemicolonOrASI); 2600 2601 function nodeIsASICandidate(node: Node, sourceFile: SourceFileLike): boolean { 2602 const lastToken = node.getLastToken(sourceFile); 2603 if (lastToken && lastToken.kind === SyntaxKind.SemicolonToken) { 2604 return false; 2605 } 2606 2607 if (syntaxRequiresTrailingCommaOrSemicolonOrASI(node.kind)) { 2608 if (lastToken && lastToken.kind === SyntaxKind.CommaToken) { 2609 return false; 2610 } 2611 } 2612 else if (syntaxRequiresTrailingModuleBlockOrSemicolonOrASI(node.kind)) { 2613 const lastChild = last(node.getChildren(sourceFile)); 2614 if (lastChild && isModuleBlock(lastChild)) { 2615 return false; 2616 } 2617 } 2618 else if (syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI(node.kind)) { 2619 const lastChild = last(node.getChildren(sourceFile)); 2620 if (lastChild && isFunctionBlock(lastChild)) { 2621 return false; 2622 } 2623 } 2624 else if (!syntaxRequiresTrailingSemicolonOrASI(node.kind)) { 2625 return false; 2626 } 2627 2628 // See comment in parser’s `parseDoStatement` 2629 if (node.kind === SyntaxKind.DoStatement) { 2630 return true; 2631 } 2632 2633 const topNode = findAncestor(node, ancestor => !ancestor.parent)!; 2634 const nextToken = findNextToken(node, topNode, sourceFile); 2635 if (!nextToken || nextToken.kind === SyntaxKind.CloseBraceToken) { 2636 return true; 2637 } 2638 2639 const startLine = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line; 2640 const endLine = sourceFile.getLineAndCharacterOfPosition(nextToken.getStart(sourceFile)).line; 2641 return startLine !== endLine; 2642 } 2643 2644 export function positionIsASICandidate(pos: number, context: Node, sourceFile: SourceFileLike): boolean { 2645 const contextAncestor = findAncestor(context, ancestor => { 2646 if (ancestor.end !== pos) { 2647 return "quit"; 2648 } 2649 return syntaxMayBeASICandidate(ancestor.kind); 2650 }); 2651 2652 return !!contextAncestor && nodeIsASICandidate(contextAncestor, sourceFile); 2653 } 2654 2655 export function probablyUsesSemicolons(sourceFile: SourceFile): boolean { 2656 let withSemicolon = 0; 2657 let withoutSemicolon = 0; 2658 const nStatementsToObserve = 5; 2659 forEachChild(sourceFile, function visit(node): boolean | undefined { 2660 if (syntaxRequiresTrailingSemicolonOrASI(node.kind)) { 2661 const lastToken = node.getLastToken(sourceFile); 2662 if (lastToken && lastToken.kind === SyntaxKind.SemicolonToken) { 2663 withSemicolon++; 2664 } 2665 else { 2666 withoutSemicolon++; 2667 } 2668 } 2669 if (withSemicolon + withoutSemicolon >= nStatementsToObserve) { 2670 return true; 2671 } 2672 2673 return forEachChild(node, visit); 2674 }); 2675 2676 // One statement missing a semicolon isn’t sufficient evidence to say the user 2677 // doesn’t want semicolons, because they may not even be done writing that statement. 2678 if (withSemicolon === 0 && withoutSemicolon <= 1) { 2679 return true; 2680 } 2681 2682 // If even 2/5 places have a semicolon, the user probably wants semicolons 2683 return withSemicolon / withoutSemicolon > 1 / nStatementsToObserve; 2684 } 2685 2686 export function tryGetDirectories(host: Pick<LanguageServiceHost, "getDirectories">, directoryName: string): string[] { 2687 return tryIOAndConsumeErrors(host, host.getDirectories, directoryName) || []; 2688 } 2689 2690 export function tryReadDirectory(host: Pick<LanguageServiceHost, "readDirectory">, path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[]): readonly string[] { 2691 return tryIOAndConsumeErrors(host, host.readDirectory, path, extensions, exclude, include) || emptyArray; 2692 } 2693 2694 export function tryFileExists(host: Pick<LanguageServiceHost, "fileExists">, path: string): boolean { 2695 return tryIOAndConsumeErrors(host, host.fileExists, path); 2696 } 2697 2698 export function tryDirectoryExists(host: LanguageServiceHost, path: string): boolean { 2699 return tryAndIgnoreErrors(() => directoryProbablyExists(path, host)) || false; 2700 } 2701 2702 export function tryAndIgnoreErrors<T>(cb: () => T): T | undefined { 2703 try { return cb(); } 2704 catch { return undefined; } 2705 } 2706 2707 export function tryIOAndConsumeErrors<T>(host: unknown, toApply: ((...a: any[]) => T) | undefined, ...args: any[]) { 2708 return tryAndIgnoreErrors(() => toApply && toApply.apply(host, args)); 2709 } 2710 2711 export function findPackageJsons(startDirectory: string, host: LanguageServiceHost, stopDirectory?: string): string[] { 2712 const paths: string[] = []; 2713 forEachAncestorDirectory(startDirectory, ancestor => { 2714 if (ancestor === stopDirectory) { 2715 return true; 2716 } 2717 const currentConfigPath = combinePaths(ancestor, getPackageJsonByPMType(host.getCompilationSettings().packageManagerType)); 2718 if (tryFileExists(host, currentConfigPath)) { 2719 paths.push(currentConfigPath); 2720 } 2721 }); 2722 return paths; 2723 } 2724 2725 export function findPackageJson(directory: string, host: LanguageServiceHost): string | undefined { 2726 let packageJson: string | undefined; 2727 forEachAncestorDirectory(directory, ancestor => { 2728 const moduleType: string = getModuleByPMType(host.getCompilationSettings().packageManagerType); 2729 const packageJsonType: string = getPackageJsonByPMType(host.getCompilationSettings().packageManagerType); 2730 if (ancestor === moduleType) return true; 2731 packageJson = findConfigFile(ancestor, (f) => tryFileExists(host, f), packageJsonType); 2732 if (packageJson) { 2733 return true; // break out 2734 } 2735 }); 2736 return packageJson; 2737 } 2738 2739 export function getPackageJsonsVisibleToFile(fileName: string, host: LanguageServiceHost): readonly PackageJsonInfo[] { 2740 if (!host.fileExists) { 2741 return []; 2742 } 2743 const packageJsons: PackageJsonInfo[] = []; 2744 forEachAncestorDirectory(getDirectoryPath(fileName), ancestor => { 2745 const packageJsonFileName = combinePaths(ancestor, getPackageJsonByPMType(host.getCompilationSettings().packageManagerType)); 2746 if (host.fileExists!(packageJsonFileName)) { 2747 const info = createPackageJsonInfo(packageJsonFileName, host); 2748 if (info) { 2749 packageJsons.push(info); 2750 } 2751 } 2752 }); 2753 2754 return packageJsons; 2755 } 2756 2757 export function createPackageJsonInfo(fileName: string, host: { readFile?(fileName: string): string | undefined }): PackageJsonInfo | undefined { 2758 if (!host.readFile) { 2759 return undefined; 2760 } 2761 2762 type PackageJsonRaw = Record<typeof dependencyKeys[number], Record<string, string> | undefined>; 2763 const dependencyKeys = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"] as const; 2764 const stringContent = host.readFile(fileName) || ""; 2765 const content = tryParseJson(stringContent) as PackageJsonRaw | undefined; 2766 const info: Pick<PackageJsonInfo, typeof dependencyKeys[number]> = {}; 2767 if (content) { 2768 for (const key of dependencyKeys) { 2769 const dependencies = content[key]; 2770 if (!dependencies) { 2771 continue; 2772 } 2773 const dependencyMap = new Map<string, string>(); 2774 for (const packageName in dependencies) { 2775 dependencyMap.set(packageName, dependencies[packageName]); 2776 } 2777 info[key] = dependencyMap; 2778 } 2779 } 2780 2781 const dependencyGroups = [ 2782 [PackageJsonDependencyGroup.Dependencies, info.dependencies], 2783 [PackageJsonDependencyGroup.DevDependencies, info.devDependencies], 2784 [PackageJsonDependencyGroup.OptionalDependencies, info.optionalDependencies], 2785 [PackageJsonDependencyGroup.PeerDependencies, info.peerDependencies], 2786 ] as const; 2787 2788 return { 2789 ...info, 2790 parseable: !!content, 2791 fileName, 2792 get, 2793 has(dependencyName, inGroups) { 2794 return !!get(dependencyName, inGroups); 2795 }, 2796 }; 2797 2798 function get(dependencyName: string, inGroups = PackageJsonDependencyGroup.All) { 2799 for (const [group, deps] of dependencyGroups) { 2800 if (deps && (inGroups & group)) { 2801 const dep = deps.get(dependencyName); 2802 if (dep !== undefined) { 2803 return dep; 2804 } 2805 } 2806 } 2807 } 2808 } 2809 2810 function tryParseJson(text: string) { 2811 try { 2812 return JSON.parse(text); 2813 } 2814 catch { 2815 return undefined; 2816 } 2817 } 2818 2819 export function consumesNodeCoreModules(sourceFile: SourceFile): boolean { 2820 return some(sourceFile.imports, ({ text }) => JsTyping.nodeCoreModules.has(text)); 2821 } 2822 2823 export function isInsideNodeModules(fileOrDirectory: string): boolean { 2824 return contains(getPathComponents(fileOrDirectory), "node_modules"); 2825 } 2826 2827 export function isDiagnosticWithLocation(diagnostic: Diagnostic): diagnostic is DiagnosticWithLocation { 2828 return diagnostic.file !== undefined && diagnostic.start !== undefined && diagnostic.length !== undefined; 2829 } 2830 2831 export function findDiagnosticForNode(node: Node, sortedFileDiagnostics: readonly Diagnostic[]): DiagnosticWithLocation | undefined { 2832 const span: Partial<TextSpan> = createTextSpanFromNode(node); 2833 const index = binarySearchKey(sortedFileDiagnostics, span, identity, compareTextSpans); 2834 if (index >= 0) { 2835 const diagnostic = sortedFileDiagnostics[index]; 2836 Debug.assertEqual(diagnostic.file, node.getSourceFile(), "Diagnostics proided to 'findDiagnosticForNode' must be from a single SourceFile"); 2837 return cast(diagnostic, isDiagnosticWithLocation); 2838 } 2839 } 2840 2841 export function getDiagnosticsWithinSpan(span: TextSpan, sortedFileDiagnostics: readonly Diagnostic[]): readonly DiagnosticWithLocation[] { 2842 let index = binarySearchKey(sortedFileDiagnostics, span.start, diag => diag.start, compareValues); 2843 if (index < 0) { 2844 index = ~index; 2845 } 2846 while (sortedFileDiagnostics[index - 1]?.start === span.start) { 2847 index--; 2848 } 2849 2850 const result: DiagnosticWithLocation[] = []; 2851 const end = textSpanEnd(span); 2852 while (true) { 2853 const diagnostic = tryCast(sortedFileDiagnostics[index], isDiagnosticWithLocation); 2854 if (!diagnostic || diagnostic.start > end) { 2855 break; 2856 } 2857 if (textSpanContainsTextSpan(span, diagnostic)) { 2858 result.push(diagnostic); 2859 } 2860 index++; 2861 } 2862 2863 return result; 2864 } 2865 2866 /* @internal */ 2867 export function getRefactorContextSpan({ startPosition, endPosition }: RefactorContext): TextSpan { 2868 return createTextSpanFromBounds(startPosition, endPosition === undefined ? startPosition : endPosition); 2869 } 2870 2871 /** 2872 * If the provided value is an array, the mapping function is applied to each element; otherwise, the mapping function is applied 2873 * to the provided value itself. 2874 */ 2875 export function mapOneOrMany<T, U>(valueOrArray: T | readonly T[], f: (x: T, i: number) => U): U | U[]; 2876 export function mapOneOrMany<T, U>(valueOrArray: T | readonly T[] | undefined, f: (x: T, i: number) => U): U | U[] | undefined; 2877 export function mapOneOrMany<T, U>(valueOrArray: T | readonly T[], f: (x: T, i: number) => U, resultSelector: (x: U[]) => U): U; 2878 export function mapOneOrMany<T, U>(valueOrArray: T | readonly T[] | undefined, f: (x: T, i: number) => U, resultSelector: (x: U[]) => U): U | undefined; 2879 export function mapOneOrMany<T, U>(valueOrArray: T | readonly T[] | undefined, f: (x: T, i: number) => U, resultSelector: (x: U[]) => U | U[] = identity): U | U[] | undefined { 2880 return valueOrArray ? isArray(valueOrArray) ? resultSelector(map(valueOrArray, f)) : f(valueOrArray, 0) : undefined; 2881 } 2882 2883 /** 2884 * If the provided value is an array, the first element of the array is returned; otherwise, the provided value is returned instead. 2885 */ 2886 export function firstOrOnly<T>(valueOrArray: T | readonly T[]): T { 2887 return isArray(valueOrArray) ? first(valueOrArray) : valueOrArray; 2888 } 2889 2890 export function getNameForExportedSymbol(symbol: Symbol, scriptTarget: ScriptTarget | undefined) { 2891 if (!(symbol.flags & SymbolFlags.Transient) && (symbol.escapedName === InternalSymbolName.ExportEquals || symbol.escapedName === InternalSymbolName.Default)) { 2892 // Name of "export default foo;" is "foo". Name of "export default 0" is the filename converted to camelCase. 2893 return firstDefined(symbol.declarations, d => isExportAssignment(d) ? tryCast(skipOuterExpressions(d.expression), isIdentifier)?.text : undefined) 2894 || codefix.moduleSymbolToValidIdentifier(getSymbolParentOrFail(symbol), scriptTarget); 2895 } 2896 return symbol.name; 2897 } 2898 2899 function getSymbolParentOrFail(symbol: Symbol) { 2900 return Debug.checkDefined( 2901 symbol.parent, 2902 `Symbol parent was undefined. Flags: ${Debug.formatSymbolFlags(symbol.flags)}. ` + 2903 `Declarations: ${symbol.declarations?.map(d => { 2904 const kind = Debug.formatSyntaxKind(d.kind); 2905 const inJS = isInJSFile(d); 2906 const { expression } = d as any; 2907 return (inJS ? "[JS]" : "") + kind + (expression ? ` (expression: ${Debug.formatSyntaxKind(expression.kind)})` : ""); 2908 }).join(", ")}.`); 2909 } 2910 2911 /** 2912 * Useful to check whether a string contains another string at a specific index 2913 * without allocating another string or traversing the entire contents of the outer string. 2914 * 2915 * This function is useful in place of either of the following: 2916 * 2917 * ```ts 2918 * // Allocates 2919 * haystack.substr(startIndex, needle.length) === needle 2920 * 2921 * // Full traversal 2922 * haystack.indexOf(needle, startIndex) === startIndex 2923 * ``` 2924 * 2925 * @param haystack The string that potentially contains `needle`. 2926 * @param needle The string whose content might sit within `haystack`. 2927 * @param startIndex The index within `haystack` to start searching for `needle`. 2928 */ 2929 export function stringContainsAt(haystack: string, needle: string, startIndex: number) { 2930 const needleLength = needle.length; 2931 if (needleLength + startIndex > haystack.length) { 2932 return false; 2933 } 2934 for (let i = 0; i < needleLength; i++) { 2935 if (needle.charCodeAt(i) !== haystack.charCodeAt(i + startIndex)) return false; 2936 } 2937 return true; 2938 } 2939 2940 export function startsWithUnderscore(name: string): boolean { 2941 return name.charCodeAt(0) === CharacterCodes._; 2942 } 2943 2944 export function isGlobalDeclaration(declaration: Declaration) { 2945 return !isNonGlobalDeclaration(declaration); 2946 } 2947 2948 export function isNonGlobalDeclaration(declaration: Declaration) { 2949 const sourceFile = declaration.getSourceFile(); 2950 // If the file is not a module, the declaration is global 2951 if (!sourceFile.externalModuleIndicator && !sourceFile.commonJsModuleIndicator) { 2952 return false; 2953 } 2954 // If the file is a module written in TypeScript, it still might be in a `declare global` augmentation 2955 return isInJSFile(declaration) || !findAncestor(declaration, isGlobalScopeAugmentation); 2956 } 2957 2958 export function isVirtualConstructor(checker: TypeChecker, symbol: Symbol, node: Node): boolean { 2959 const symbolName = checker.symbolToString(symbol); // Do not get scoped name, just the name of the symbol 2960 const symbolKind = SymbolDisplay.getSymbolKind(checker, symbol, node); 2961 if (node.virtual && symbolName === InternalSymbolName.Constructor && symbolKind === ScriptElementKind.constructorImplementationElement) { 2962 return true; 2963 } 2964 return false; 2965 } 2966 2967 // #endregion 2968} 2969