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