• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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