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