• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts {
2    /** The version of the language service API */
3    export const servicesVersion = "0.8";
4
5    function createNode<TKind extends SyntaxKind>(kind: TKind, pos: number, end: number, parent: Node): NodeObject | TokenObject<TKind> | IdentifierObject | PrivateIdentifierObject {
6        const node = isNodeKind(kind) ? new NodeObject(kind, pos, end) :
7            kind === SyntaxKind.Identifier ? new IdentifierObject(SyntaxKind.Identifier, pos, end) :
8                kind === SyntaxKind.PrivateIdentifier ? new PrivateIdentifierObject(SyntaxKind.PrivateIdentifier, pos, end) :
9                    new TokenObject(kind, pos, end);
10        node.parent = parent;
11        node.flags = parent.flags & NodeFlags.ContextFlags;
12        return node;
13    }
14
15    class NodeObject implements Node {
16        public kind: SyntaxKind;
17        public pos: number;
18        public end: number;
19        public flags: NodeFlags;
20        public modifierFlagsCache: ModifierFlags;
21        public transformFlags: TransformFlags;
22        public parent: Node;
23        public symbol!: Symbol; // Actually optional, but it was too annoying to access `node.symbol!` everywhere since in many cases we know it must be defined
24        public jsDoc?: JSDoc[];
25        public original?: Node;
26        private _children: Node[] | undefined;
27
28        constructor(kind: SyntaxKind, pos: number, end: number) {
29            this.pos = pos;
30            this.end = end;
31            this.flags = NodeFlags.None;
32            this.modifierFlagsCache = ModifierFlags.None;
33            this.transformFlags = TransformFlags.None;
34            this.parent = undefined!;
35            this.kind = kind;
36        }
37
38        private assertHasRealPosition(message?: string) {
39            // eslint-disable-next-line local/debug-assert
40            Debug.assert(!positionIsSynthesized(this.pos) && !positionIsSynthesized(this.end), message || "Node must have a real position for this operation");
41        }
42
43        public getSourceFile(): SourceFile {
44            return getSourceFileOfNode(this);
45        }
46
47        public getStart(sourceFile?: SourceFileLike, includeJsDocComment?: boolean): number {
48            this.assertHasRealPosition();
49            return getTokenPosOfNode(this, sourceFile, includeJsDocComment);
50        }
51
52        public getFullStart(): number {
53            this.assertHasRealPosition();
54            return this.pos;
55        }
56
57        public getEnd(): number {
58            this.assertHasRealPosition();
59            return this.end;
60        }
61
62        public getWidth(sourceFile?: SourceFile): number {
63            this.assertHasRealPosition();
64            return this.getEnd() - this.getStart(sourceFile);
65        }
66
67        public getFullWidth(): number {
68            this.assertHasRealPosition();
69            return this.end - this.pos;
70        }
71
72        public getLeadingTriviaWidth(sourceFile?: SourceFile): number {
73            this.assertHasRealPosition();
74            return this.getStart(sourceFile) - this.pos;
75        }
76
77        public getFullText(sourceFile?: SourceFile): string {
78            this.assertHasRealPosition();
79            return (sourceFile || this.getSourceFile()).text.substring(this.pos, this.end);
80        }
81
82        public getText(sourceFile?: SourceFile): string {
83            this.assertHasRealPosition();
84            if (!sourceFile) {
85                sourceFile = this.getSourceFile();
86            }
87            return sourceFile.text.substring(this.getStart(sourceFile), this.getEnd());
88        }
89
90        public getChildCount(sourceFile?: SourceFile): number {
91            return this.getChildren(sourceFile).length;
92        }
93
94        public getChildAt(index: number, sourceFile?: SourceFile): Node {
95            return this.getChildren(sourceFile)[index];
96        }
97
98        public getChildren(sourceFile?: SourceFileLike): Node[] {
99            this.assertHasRealPosition("Node without a real position cannot be scanned and thus has no token nodes - use forEachChild and collect the result if that's fine");
100            return this._children || (this._children = createChildren(this, sourceFile));
101        }
102
103        public getFirstToken(sourceFile?: SourceFileLike): Node | undefined {
104            this.assertHasRealPosition();
105            const children = this.getChildren(sourceFile);
106            if (!children.length) {
107                return undefined;
108            }
109
110            const child = find(children, kid => kid.kind < SyntaxKind.FirstJSDocNode || kid.kind > SyntaxKind.LastJSDocNode)!;
111            return child.kind < SyntaxKind.FirstNode ?
112                child :
113                child.getFirstToken(sourceFile);
114        }
115
116        public getLastToken(sourceFile?: SourceFileLike): Node | undefined {
117            this.assertHasRealPosition();
118            const children = this.getChildren(sourceFile);
119
120            const child = lastOrUndefined(children);
121            if (!child) {
122                return undefined;
123            }
124
125            return child.kind < SyntaxKind.FirstNode ? child : child.getLastToken(sourceFile);
126        }
127
128        public forEachChild<T>(cbNode: (node: Node) => T, cbNodeArray?: (nodes: NodeArray<Node>) => T): T | undefined {
129            return forEachChild(this, cbNode, cbNodeArray);
130        }
131    }
132
133    function createChildren(node: Node, sourceFile: SourceFileLike | undefined): Node[] {
134        if (!isNodeKind(node.kind)) {
135            return emptyArray;
136        }
137
138        const children: Node[] = [];
139
140        if (isJSDocCommentContainingNode(node)) {
141            /** Don't add trivia for "tokens" since this is in a comment. */
142            node.forEachChild(child => {
143                children.push(child);
144            });
145            return children;
146        }
147
148        const sourceFileName: string | undefined = sourceFile ? sourceFile.fileName : node.getSourceFile().fileName;
149        if (sourceFileName && getScriptKindFromFileName(sourceFileName) === ScriptKind.ETS) {
150            scanner.setEtsContext(true);
151        }
152
153        scanner.setText((sourceFile || node.getSourceFile()).text);
154        let pos = node.pos;
155        const processNode = (child: Node) => {
156            addSyntheticNodes(children, pos, child.pos, node);
157            children.push(child);
158            pos = child.end;
159        };
160        const processNodes = (nodes: NodeArray<Node>) => {
161            addSyntheticNodes(children, pos, nodes.pos, node);
162            children.push(createSyntaxList(nodes, node));
163            pos = nodes.end;
164        };
165        // jsDocComments need to be the first children
166        forEach((node as JSDocContainer).jsDoc, processNode);
167        // For syntactic classifications, all trivia are classified together, including jsdoc comments.
168        // For that to work, the jsdoc comments should still be the leading trivia of the first child.
169        // Restoring the scanner position ensures that.
170        pos = node.pos;
171        node.forEachChild(processNode, processNodes);
172        addSyntheticNodes(children, pos, node.end, node);
173        scanner.setText(undefined);
174        scanner.setEtsContext(false);
175        return children;
176    }
177
178    function addSyntheticNodes(nodes: Push<Node>, pos: number, end: number, parent: Node): void {
179        // position start === end mean the node is virtual
180        if(parent.virtual){
181            return;
182        }
183        scanner.setTextPos(pos);
184        while (pos < end) {
185            const token = scanner.scan();
186            const textPos = scanner.getTextPos();
187            const isSourceFileOrInMarkedKitImport: boolean = !isSourceFile(parent) || !isInMarkedKitImport(parent, pos, textPos);
188            if (isSourceFileOrInMarkedKitImport && textPos <= end) {
189                if (token === SyntaxKind.Identifier) {
190                    Debug.fail(`Did not expect ${Debug.formatSyntaxKind(parent.kind)} to have an Identifier in its trivia`);
191                }
192                nodes.push(createNode(token, pos, textPos, parent));
193            }
194            pos = textPos;
195            if (token === SyntaxKind.EndOfFileToken) {
196                break;
197            }
198        }
199    }
200
201    function createSyntaxList(nodes: NodeArray<Node>, parent: Node): Node {
202        const list = createNode(SyntaxKind.SyntaxList, nodes.pos, nodes.end, parent) as any as SyntaxList;
203        list._children = [];
204        let pos = nodes.pos;
205        for (const node of nodes) {
206            // position start === end mean the node is visual
207            if (node.virtual) {
208                continue;
209            }
210            addSyntheticNodes(list._children, pos, node.pos, parent);
211            list._children.push(node);
212            pos = node.end;
213        }
214        addSyntheticNodes(list._children, pos, nodes.end, parent);
215        return list;
216    }
217
218    class TokenOrIdentifierObject implements Node {
219        public kind!: SyntaxKind;
220        public pos: number;
221        public end: number;
222        public flags: NodeFlags;
223        public modifierFlagsCache: ModifierFlags;
224        public transformFlags: TransformFlags;
225        public parent: Node;
226        public symbol!: Symbol;
227        public jsDocComments?: JSDoc[];
228
229        constructor(pos: number, end: number) {
230            // Set properties in same order as NodeObject
231            this.pos = pos;
232            this.end = end;
233            this.flags = NodeFlags.None;
234            this.modifierFlagsCache = ModifierFlags.None;
235            this.transformFlags = TransformFlags.None;
236            this.parent = undefined!;
237        }
238
239        public getSourceFile(): SourceFile {
240            return getSourceFileOfNode(this);
241        }
242
243        public getStart(sourceFile?: SourceFileLike, includeJsDocComment?: boolean): number {
244            return getTokenPosOfNode(this, sourceFile, includeJsDocComment);
245        }
246
247        public getFullStart(): number {
248            return this.pos;
249        }
250
251        public getEnd(): number {
252            return this.end;
253        }
254
255        public getWidth(sourceFile?: SourceFile): number {
256            return this.getEnd() - this.getStart(sourceFile);
257        }
258
259        public getFullWidth(): number {
260            return this.end - this.pos;
261        }
262
263        public getLeadingTriviaWidth(sourceFile?: SourceFile): number {
264            return this.getStart(sourceFile) - this.pos;
265        }
266
267        public getFullText(sourceFile?: SourceFile): string {
268            return (sourceFile || this.getSourceFile()).text.substring(this.pos, this.end);
269        }
270
271        public getText(sourceFile?: SourceFile): string {
272            if (!sourceFile) {
273                sourceFile = this.getSourceFile();
274            }
275            return sourceFile.text.substring(this.getStart(sourceFile), this.getEnd());
276        }
277
278        public getChildCount(): number {
279            return this.getChildren().length;
280        }
281
282        public getChildAt(index: number): Node {
283            return this.getChildren()[index];
284        }
285
286        public getChildren(): Node[] {
287            return this.kind === SyntaxKind.EndOfFileToken ? (this as EndOfFileToken).jsDoc || emptyArray : emptyArray;
288        }
289
290        public getFirstToken(): Node | undefined {
291            return undefined;
292        }
293
294        public getLastToken(): Node | undefined {
295            return undefined;
296        }
297
298        public forEachChild<T>(): T | undefined {
299            return undefined;
300        }
301    }
302
303    class SymbolObject implements Symbol {
304        flags: SymbolFlags;
305        escapedName: __String;
306        declarations!: Declaration[];
307        valueDeclaration!: Declaration;
308
309        // Undefined is used to indicate the value has not been computed. If, after computing, the
310        // symbol has no doc comment, then the empty array will be returned.
311        documentationComment?: SymbolDisplayPart[];
312        tags?: JSDocTagInfo[]; // same
313
314        contextualGetAccessorDocumentationComment?: SymbolDisplayPart[];
315        contextualSetAccessorDocumentationComment?: SymbolDisplayPart[];
316
317        contextualGetAccessorTags?: JSDocTagInfo[];
318        contextualSetAccessorTags?: JSDocTagInfo[];
319
320        constructor(flags: SymbolFlags, name: __String) {
321            this.flags = flags;
322            this.escapedName = name;
323        }
324
325        getFlags(): SymbolFlags {
326            return this.flags;
327        }
328
329        get name(): string {
330            return symbolName(this);
331        }
332
333        getEscapedName(): __String {
334            return this.escapedName;
335        }
336
337        getName(): string {
338            return this.name;
339        }
340
341        getDeclarations(): Declaration[] | undefined {
342            return this.declarations;
343        }
344
345        getDocumentationComment(checker: TypeChecker | undefined): SymbolDisplayPart[] {
346            if (!this.documentationComment) {
347                this.documentationComment = emptyArray; // Set temporarily to avoid an infinite loop finding inherited docs
348
349                if (!this.declarations && (this as Symbol as TransientSymbol).target && ((this as Symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration) {
350                    const labelDecl = ((this as Symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration!;
351                    this.documentationComment = getDocumentationComment([labelDecl], checker);
352                }
353                else {
354                    this.documentationComment = getDocumentationComment(this.declarations, checker);
355                }
356            }
357            return this.documentationComment;
358        }
359
360        getContextualDocumentationComment(context: Node | undefined, checker: TypeChecker | undefined): SymbolDisplayPart[] {
361            if (context) {
362                if (isGetAccessor(context)) {
363                    if (!this.contextualGetAccessorDocumentationComment) {
364                        this.contextualGetAccessorDocumentationComment = getDocumentationComment(filter(this.declarations, isGetAccessor), checker);
365                    }
366                    if (length(this.contextualGetAccessorDocumentationComment)) {
367                        return this.contextualGetAccessorDocumentationComment;
368                    }
369                }
370                if (isSetAccessor(context)) {
371                    if (!this.contextualSetAccessorDocumentationComment) {
372                        this.contextualSetAccessorDocumentationComment = getDocumentationComment(filter(this.declarations, isSetAccessor), checker);
373                    }
374                    if (length(this.contextualSetAccessorDocumentationComment)) {
375                        return this.contextualSetAccessorDocumentationComment;
376                    }
377                }
378            }
379            return this.getDocumentationComment(checker);
380        }
381
382        getJsDocTags(checker?: TypeChecker): JSDocTagInfo[] {
383            if (this.tags === undefined) {
384                this.tags = getJsDocTagsOfDeclarations(this.declarations, checker);
385            }
386
387            return this.tags;
388        }
389
390        getContextualJsDocTags(context: Node | undefined, checker: TypeChecker | undefined): JSDocTagInfo[] {
391            if (context) {
392                if (isGetAccessor(context)) {
393                    if (!this.contextualGetAccessorTags) {
394                        this.contextualGetAccessorTags = getJsDocTagsOfDeclarations(filter(this.declarations, isGetAccessor), checker);
395                    }
396                    if (length(this.contextualGetAccessorTags)) {
397                        return this.contextualGetAccessorTags;
398                    }
399                }
400                if (isSetAccessor(context)) {
401                    if (!this.contextualSetAccessorTags) {
402                        this.contextualSetAccessorTags = getJsDocTagsOfDeclarations(filter(this.declarations, isSetAccessor), checker);
403                    }
404                    if (length(this.contextualSetAccessorTags)) {
405                        return this.contextualSetAccessorTags;
406                    }
407                }
408            }
409            return this.getJsDocTags(checker);
410        }
411    }
412
413    class TokenObject<TKind extends SyntaxKind> extends TokenOrIdentifierObject implements Token<TKind> {
414        public kind: TKind;
415
416        constructor(kind: TKind, pos: number, end: number) {
417            super(pos, end);
418            this.kind = kind;
419        }
420    }
421
422    class IdentifierObject extends TokenOrIdentifierObject implements Identifier {
423        public kind: SyntaxKind.Identifier = SyntaxKind.Identifier;
424        public escapedText!: __String;
425        public autoGenerateFlags!: GeneratedIdentifierFlags;
426        _primaryExpressionBrand: any;
427        _memberExpressionBrand: any;
428        _leftHandSideExpressionBrand: any;
429        _updateExpressionBrand: any;
430        _unaryExpressionBrand: any;
431        _expressionBrand: any;
432        _declarationBrand: any;
433        /*@internal*/typeArguments!: NodeArray<TypeNode>;
434        constructor(_kind: SyntaxKind.Identifier, pos: number, end: number) {
435            super(pos, end);
436        }
437
438        get text(): string {
439            return idText(this);
440        }
441    }
442    IdentifierObject.prototype.kind = SyntaxKind.Identifier;
443    class PrivateIdentifierObject extends TokenOrIdentifierObject implements PrivateIdentifier {
444        public kind!: SyntaxKind.PrivateIdentifier;
445        public escapedText!: __String;
446        public symbol!: Symbol;
447        _primaryExpressionBrand: any;
448        _memberExpressionBrand: any;
449        _leftHandSideExpressionBrand: any;
450        _updateExpressionBrand: any;
451        _unaryExpressionBrand: any;
452        _expressionBrand: any;
453        constructor(_kind: SyntaxKind.PrivateIdentifier, pos: number, end: number) {
454            super(pos, end);
455        }
456
457        get text(): string {
458            return idText(this);
459        }
460    }
461    PrivateIdentifierObject.prototype.kind = SyntaxKind.PrivateIdentifier;
462
463    class TypeObject implements Type {
464        checker: TypeChecker;
465        flags: TypeFlags;
466        objectFlags?: ObjectFlags;
467        id!: number;
468        symbol!: Symbol;
469        constructor(checker: TypeChecker, flags: TypeFlags) {
470            this.checker = checker;
471            this.flags = flags;
472        }
473        getFlags(): TypeFlags {
474            return this.flags;
475        }
476        getSymbol(): Symbol | undefined {
477            return this.symbol;
478        }
479        getProperties(): Symbol[] {
480            return this.checker.getPropertiesOfType(this);
481        }
482        getProperty(propertyName: string): Symbol | undefined {
483            return this.checker.getPropertyOfType(this, propertyName);
484        }
485        getApparentProperties(): Symbol[] {
486            return this.checker.getAugmentedPropertiesOfType(this);
487        }
488        getCallSignatures(): readonly Signature[] {
489            return this.checker.getSignaturesOfType(this, SignatureKind.Call);
490        }
491        getConstructSignatures(): readonly Signature[] {
492            return this.checker.getSignaturesOfType(this, SignatureKind.Construct);
493        }
494        getStringIndexType(): Type | undefined {
495            return this.checker.getIndexTypeOfType(this, IndexKind.String);
496        }
497        getNumberIndexType(): Type | undefined {
498            return this.checker.getIndexTypeOfType(this, IndexKind.Number);
499        }
500        getBaseTypes(): BaseType[] | undefined {
501            return this.isClassOrInterface() ? this.checker.getBaseTypes(this) : undefined;
502        }
503        isNullableType(): boolean {
504            return this.checker.isNullableType(this);
505        }
506        getNonNullableType(): Type {
507            return this.checker.getNonNullableType(this);
508        }
509        getNonOptionalType(): Type {
510            return this.checker.getNonOptionalType(this);
511        }
512        getConstraint(): Type | undefined {
513            return this.checker.getBaseConstraintOfType(this);
514        }
515        getDefault(): Type | undefined {
516            return this.checker.getDefaultFromTypeParameter(this);
517        }
518
519        isUnion(): this is UnionType {
520            return !!(this.flags & TypeFlags.Union);
521        }
522        isIntersection(): this is IntersectionType {
523            return !!(this.flags & TypeFlags.Intersection);
524        }
525        isUnionOrIntersection(): this is UnionOrIntersectionType {
526            return !!(this.flags & TypeFlags.UnionOrIntersection);
527        }
528        isLiteral(): this is LiteralType {
529            return !!(this.flags & TypeFlags.StringOrNumberLiteral);
530        }
531        isStringLiteral(): this is StringLiteralType {
532            return !!(this.flags & TypeFlags.StringLiteral);
533        }
534        isNumberLiteral(): this is NumberLiteralType {
535            return !!(this.flags & TypeFlags.NumberLiteral);
536        }
537        isTypeParameter(): this is TypeParameter {
538            return !!(this.flags & TypeFlags.TypeParameter);
539        }
540        isClassOrInterface(): this is InterfaceType {
541            return !!(getObjectFlags(this) & ObjectFlags.ClassOrInterface);
542        }
543        isClass(): this is InterfaceType {
544            return !!(getObjectFlags(this) & ObjectFlags.Class);
545        }
546        isIndexType(): this is IndexType {
547            return !!(this.flags & TypeFlags.Index);
548        }
549        /**
550         * This polyfills `referenceType.typeArguments` for API consumers
551         */
552        get typeArguments() {
553            if (getObjectFlags(this) & ObjectFlags.Reference) {
554                return this.checker.getTypeArguments(this as Type as TypeReference);
555            }
556            return undefined;
557        }
558    }
559
560    class SignatureObject implements Signature {
561        flags: SignatureFlags;
562        checker: TypeChecker;
563        declaration!: SignatureDeclaration;
564        typeParameters?: TypeParameter[];
565        parameters!: Symbol[];
566        thisParameter!: Symbol;
567        resolvedReturnType!: Type;
568        resolvedTypePredicate: TypePredicate | undefined;
569        minTypeArgumentCount!: number;
570        minArgumentCount!: number;
571
572        // Undefined is used to indicate the value has not been computed. If, after computing, the
573        // symbol has no doc comment, then the empty array will be returned.
574        documentationComment?: SymbolDisplayPart[];
575        jsDocTags?: JSDocTagInfo[]; // same
576
577        constructor(checker: TypeChecker, flags: SignatureFlags) {
578            this.checker = checker;
579            this.flags = flags;
580        }
581
582        getDeclaration(): SignatureDeclaration {
583            return this.declaration;
584        }
585        getTypeParameters(): TypeParameter[] | undefined {
586            return this.typeParameters;
587        }
588        getParameters(): Symbol[] {
589            return this.parameters;
590        }
591        getReturnType(): Type {
592            return this.checker.getReturnTypeOfSignature(this);
593        }
594        getTypeParameterAtPosition(pos: number): Type {
595            const type = this.checker.getParameterType(this, pos);
596            if (type.isIndexType() && isThisTypeParameter(type.type)) {
597                const constraint = type.type.getConstraint();
598                if (constraint) {
599                    return this.checker.getIndexType(constraint);
600                }
601            }
602            return type;
603        }
604
605        getDocumentationComment(): SymbolDisplayPart[] {
606            return this.documentationComment || (this.documentationComment = getDocumentationComment(singleElementArray(this.declaration), this.checker));
607        }
608
609        getJsDocTags(): JSDocTagInfo[] {
610            return this.jsDocTags || (this.jsDocTags = getJsDocTagsOfDeclarations(singleElementArray(this.declaration), this.checker));
611        }
612    }
613
614    /**
615     * Returns whether or not the given node has a JSDoc "inheritDoc" tag on it.
616     * @param node the Node in question.
617     * @returns `true` if `node` has a JSDoc "inheritDoc" tag on it, otherwise `false`.
618     */
619    function hasJSDocInheritDocTag(node: Node) {
620        return getJSDocTags(node).some(tag => tag.tagName.text === "inheritDoc" || tag.tagName.text === "inheritdoc");
621    }
622
623    function getJsDocTagsOfDeclarations(declarations: Declaration[] | undefined, checker: TypeChecker | undefined): JSDocTagInfo[] {
624        if (!declarations) return emptyArray;
625
626        let tags = JsDoc.getJsDocTagsFromDeclarations(declarations, checker);
627        if (checker && (tags.length === 0 || declarations.some(hasJSDocInheritDocTag))) {
628            const seenSymbols = new Set<Symbol>();
629            for (const declaration of declarations) {
630                const inheritedTags = findBaseOfDeclaration(checker, declaration, symbol => {
631                    if (!seenSymbols.has(symbol)) {
632                        seenSymbols.add(symbol);
633                        if (declaration.kind === SyntaxKind.GetAccessor || declaration.kind === SyntaxKind.SetAccessor) {
634                            return symbol.getContextualJsDocTags(declaration, checker);
635                        }
636                        return symbol.declarations?.length === 1 ? symbol.getJsDocTags() : undefined;
637                    }
638                });
639                if (inheritedTags) {
640                    tags = [...inheritedTags, ...tags];
641                }
642            }
643        }
644        return tags;
645    }
646
647    function getDocumentationComment(declarations: readonly Declaration[] | undefined, checker: TypeChecker | undefined): SymbolDisplayPart[] {
648        if (!declarations) return emptyArray;
649
650        let doc = JsDoc.getJsDocCommentsFromDeclarations(declarations, checker);
651        if (checker && (doc.length === 0 || declarations.some(hasJSDocInheritDocTag))) {
652            const seenSymbols = new Set<Symbol>();
653            for (const declaration of declarations) {
654                const inheritedDocs = findBaseOfDeclaration(checker, declaration, symbol => {
655                    if (!seenSymbols.has(symbol)) {
656                        seenSymbols.add(symbol);
657                        if (declaration.kind === SyntaxKind.GetAccessor || declaration.kind === SyntaxKind.SetAccessor) {
658                            return symbol.getContextualDocumentationComment(declaration, checker);
659                        }
660                        return symbol.getDocumentationComment(checker);
661                    }
662                });
663                // TODO: GH#16312 Return a ReadonlyArray, avoid copying inheritedDocs
664                if (inheritedDocs) doc = doc.length === 0 ? inheritedDocs.slice() : inheritedDocs.concat(lineBreakPart(), doc);
665            }
666        }
667        return doc;
668    }
669
670    function findBaseOfDeclaration<T>(checker: TypeChecker, declaration: Declaration, cb: (symbol: Symbol) => T[] | undefined): T[] | undefined {
671        const classOrInterfaceDeclaration = declaration.parent?.kind === SyntaxKind.Constructor ? declaration.parent.parent : declaration.parent;
672        if (!classOrInterfaceDeclaration) return;
673
674        const isStaticMember = hasStaticModifier(declaration);
675        return firstDefined(getAllSuperTypeNodes(classOrInterfaceDeclaration), superTypeNode => {
676            const baseType = checker.getTypeAtLocation(superTypeNode);
677            const type = isStaticMember && baseType.symbol ? checker.getTypeOfSymbol(baseType.symbol) : baseType;
678            const symbol = checker.getPropertyOfType(type, declaration.symbol.name);
679            return symbol ? cb(symbol) : undefined;
680        });
681    }
682
683    class SourceFileObject extends NodeObject implements SourceFile {
684        public kind: SyntaxKind.SourceFile = SyntaxKind.SourceFile;
685        public _declarationBrand: any;
686        public fileName!: string;
687        public path!: Path;
688        public resolvedPath!: Path;
689        public originalFileName!: string;
690        public text!: string;
691        public scriptSnapshot!: IScriptSnapshot;
692        public lineMap!: readonly number[];
693
694        public statements!: NodeArray<Statement>;
695        public endOfFileToken!: Token<SyntaxKind.EndOfFileToken>;
696
697        public amdDependencies!: { name: string; path: string }[];
698        public moduleName!: string;
699        public referencedFiles!: FileReference[];
700        public typeReferenceDirectives!: FileReference[];
701        public libReferenceDirectives!: FileReference[];
702
703        public syntacticDiagnostics!: DiagnosticWithLocation[];
704        public parseDiagnostics!: DiagnosticWithLocation[];
705        public bindDiagnostics!: DiagnosticWithLocation[];
706        public bindSuggestionDiagnostics?: DiagnosticWithLocation[];
707
708        public isDeclarationFile!: boolean;
709        public isDefaultLib!: boolean;
710        public hasNoDefaultLib!: boolean;
711        public externalModuleIndicator!: Node; // The first node that causes this file to be an external module
712        public commonJsModuleIndicator!: Node; // The first node that causes this file to be a CommonJS module
713        public nodeCount!: number;
714        public identifierCount!: number;
715        public symbolCount!: number;
716        public version!: string;
717        public scriptKind!: ScriptKind;
718        public languageVersion!: ScriptTarget;
719        public languageVariant!: LanguageVariant;
720        public identifiers!: ESMap<string, string>;
721        public nameTable: UnderscoreEscapedMap<number> | undefined;
722        public resolvedModules: ModeAwareCache<ResolvedModuleFull> | undefined;
723        public resolvedTypeReferenceDirectiveNames!: ModeAwareCache<ResolvedTypeReferenceDirective>;
724        public imports!: readonly StringLiteralLike[];
725        public moduleAugmentations!: StringLiteral[];
726        private namedDeclarations: ESMap<string, Declaration[]> | undefined;
727        public ambientModuleNames!: string[];
728        public checkJsDirective: CheckJsDirective | undefined;
729        public errorExpectations: TextRange[] | undefined;
730        public possiblyContainDynamicImport?: boolean;
731        public pragmas!: PragmaMap;
732        public localJsxFactory: EntityName | undefined;
733        public localJsxNamespace: __String | undefined;
734
735        constructor(kind: SyntaxKind, pos: number, end: number) {
736            super(kind, pos, end);
737        }
738
739        public update(newText: string, textChangeRange: TextChangeRange): SourceFile {
740            return updateSourceFile(this, newText, textChangeRange);
741        }
742
743        public getLineAndCharacterOfPosition(position: number): LineAndCharacter {
744            return getLineAndCharacterOfPosition(this, position);
745        }
746
747        public getLineStarts(): readonly number[] {
748            return getLineStarts(this);
749        }
750
751        public getPositionOfLineAndCharacter(line: number, character: number, allowEdits?: true): number {
752            return computePositionOfLineAndCharacter(getLineStarts(this), line, character, this.text, allowEdits);
753        }
754
755        public getLineEndOfPosition(pos: number): number {
756            const { line } = this.getLineAndCharacterOfPosition(pos);
757            const lineStarts = this.getLineStarts();
758
759            let lastCharPos: number | undefined;
760            if (line + 1 >= lineStarts.length) {
761                lastCharPos = this.getEnd();
762            }
763            if (!lastCharPos) {
764                lastCharPos = lineStarts[line + 1] - 1;
765            }
766
767            const fullText = this.getFullText();
768            // if the new line is "\r\n", we should return the last non-new-line-character position
769            return fullText[lastCharPos] === "\n" && fullText[lastCharPos - 1] === "\r" ? lastCharPos - 1 : lastCharPos;
770        }
771
772        public getNamedDeclarations(): ESMap<string, Declaration[]> {
773            if (!this.namedDeclarations) {
774                this.namedDeclarations = this.computeNamedDeclarations();
775            }
776
777            return this.namedDeclarations;
778        }
779
780        private computeNamedDeclarations(): ESMap<string, Declaration[]> {
781            const result = createMultiMap<Declaration>();
782
783            this.forEachChild(visit);
784
785            return result;
786
787            function addDeclaration(declaration: Declaration) {
788                const name = getDeclarationName(declaration);
789                if (name) {
790                    result.add(name, declaration);
791                }
792            }
793
794            function getDeclarations(name: string) {
795                let declarations = result.get(name);
796                if (!declarations) {
797                    result.set(name, declarations = []);
798                }
799                return declarations;
800            }
801
802            function getDeclarationName(declaration: Declaration) {
803                const name = getNonAssignedNameOfDeclaration(declaration);
804                return name && (isComputedPropertyName(name) && isPropertyAccessExpression(name.expression) ? name.expression.name.text
805                    : isPropertyName(name) ? getNameFromPropertyName(name) : undefined);
806            }
807
808            function visit(node: Node): void {
809                switch (node.kind) {
810                    case SyntaxKind.FunctionDeclaration:
811                    case SyntaxKind.FunctionExpression:
812                    case SyntaxKind.MethodDeclaration:
813                    case SyntaxKind.MethodSignature:
814                        const functionDeclaration = node as FunctionLikeDeclaration;
815                        const declarationName = getDeclarationName(functionDeclaration);
816
817                        if (declarationName) {
818                            const declarations = getDeclarations(declarationName);
819                            const lastDeclaration = lastOrUndefined(declarations);
820
821                            // Check whether this declaration belongs to an "overload group".
822                            if (lastDeclaration && functionDeclaration.parent === lastDeclaration.parent && functionDeclaration.symbol === lastDeclaration.symbol) {
823                                // Overwrite the last declaration if it was an overload
824                                // and this one is an implementation.
825                                if (functionDeclaration.body && !(lastDeclaration as FunctionLikeDeclaration).body) {
826                                    declarations[declarations.length - 1] = functionDeclaration;
827                                }
828                            }
829                            else {
830                                declarations.push(functionDeclaration);
831                            }
832                        }
833                        forEachChild(node, visit);
834                        break;
835
836                    case SyntaxKind.ClassDeclaration:
837                    case SyntaxKind.ClassExpression:
838                    case SyntaxKind.StructDeclaration:
839                    case SyntaxKind.InterfaceDeclaration:
840                    case SyntaxKind.TypeAliasDeclaration:
841                    case SyntaxKind.EnumDeclaration:
842                    case SyntaxKind.ModuleDeclaration:
843                    case SyntaxKind.ImportEqualsDeclaration:
844                    case SyntaxKind.ExportSpecifier:
845                    case SyntaxKind.ImportSpecifier:
846                    case SyntaxKind.ImportClause:
847                    case SyntaxKind.NamespaceImport:
848                    case SyntaxKind.GetAccessor:
849                    case SyntaxKind.SetAccessor:
850                    case SyntaxKind.TypeLiteral:
851                        addDeclaration(node as Declaration);
852                        forEachChild(node, visit);
853                        break;
854
855                    case SyntaxKind.Parameter:
856                        // Only consider parameter properties
857                        if (!hasSyntacticModifier(node, ModifierFlags.ParameterPropertyModifier)) {
858                            break;
859                        }
860                    // falls through
861
862                    case SyntaxKind.VariableDeclaration:
863                    case SyntaxKind.BindingElement: {
864                        const decl = node as VariableDeclaration;
865                        if (isBindingPattern(decl.name)) {
866                            forEachChild(decl.name, visit);
867                            break;
868                        }
869                        if (decl.initializer) {
870                            visit(decl.initializer);
871                        }
872                    }
873                    // falls through
874                    case SyntaxKind.EnumMember:
875                    case SyntaxKind.PropertyDeclaration:
876                    case SyntaxKind.PropertySignature:
877                        addDeclaration(node as Declaration);
878                        break;
879
880                    case SyntaxKind.ExportDeclaration:
881                        // Handle named exports case e.g.:
882                        //    export {a, b as B} from "mod";
883                        const exportDeclaration = node as ExportDeclaration;
884                        if (exportDeclaration.exportClause) {
885                            if (isNamedExports(exportDeclaration.exportClause)) {
886                                forEach(exportDeclaration.exportClause.elements, visit);
887                            }
888                            else {
889                                visit(exportDeclaration.exportClause.name);
890                            }
891                        }
892                        break;
893
894                    case SyntaxKind.ImportDeclaration:
895                        const importClause = (node as ImportDeclaration).importClause;
896                        if (importClause) {
897                            // Handle default import case e.g.:
898                            //    import d from "mod";
899                            if (importClause.name) {
900                                addDeclaration(importClause.name);
901                            }
902
903                            // Handle named bindings in imports e.g.:
904                            //    import * as NS from "mod";
905                            //    import {a, b as B} from "mod";
906                            if (importClause.namedBindings) {
907                                if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) {
908                                    addDeclaration(importClause.namedBindings);
909                                }
910                                else {
911                                    forEach(importClause.namedBindings.elements, visit);
912                                }
913                            }
914                        }
915                        break;
916
917                    case SyntaxKind.BinaryExpression:
918                        if (getAssignmentDeclarationKind(node as BinaryExpression) !== AssignmentDeclarationKind.None) {
919                            addDeclaration(node as BinaryExpression);
920                        }
921                    // falls through
922
923                    default:
924                        forEachChild(node, visit);
925                }
926            }
927        }
928    }
929
930    class SourceMapSourceObject implements SourceMapSource {
931        lineMap!: number[];
932        constructor(public fileName: string, public text: string, public skipTrivia?: (pos: number) => number) { }
933
934        public getLineAndCharacterOfPosition(pos: number): LineAndCharacter {
935            return getLineAndCharacterOfPosition(this, pos);
936        }
937    }
938
939    function getServicesObjectAllocator(): ObjectAllocator {
940        return {
941            getNodeConstructor: () => NodeObject,
942            getTokenConstructor: () => TokenObject,
943
944            getIdentifierConstructor: () => IdentifierObject,
945            getPrivateIdentifierConstructor: () => PrivateIdentifierObject,
946            getSourceFileConstructor: () => SourceFileObject,
947            getSymbolConstructor: () => SymbolObject,
948            getTypeConstructor: () => TypeObject,
949            getSignatureConstructor: () => SignatureObject,
950            getSourceMapSourceConstructor: () => SourceMapSourceObject,
951        };
952    }
953
954    /// Language Service
955
956    /* @internal */
957    export interface DisplayPartsSymbolWriter extends EmitTextWriter {
958        displayParts(): SymbolDisplayPart[];
959    }
960
961    /* @internal */
962    export function toEditorSettings(options: FormatCodeOptions | FormatCodeSettings): FormatCodeSettings;
963    export function toEditorSettings(options: EditorOptions | EditorSettings): EditorSettings;
964    export function toEditorSettings(optionsAsMap: MapLike<any>): MapLike<any> {
965        let allPropertiesAreCamelCased = true;
966        for (const key in optionsAsMap) {
967            if (hasProperty(optionsAsMap, key) && !isCamelCase(key)) {
968                allPropertiesAreCamelCased = false;
969                break;
970            }
971        }
972        if (allPropertiesAreCamelCased) {
973            return optionsAsMap;
974        }
975        const settings: MapLike<any> = {};
976        for (const key in optionsAsMap) {
977            if (hasProperty(optionsAsMap, key)) {
978                const newKey = isCamelCase(key) ? key : key.charAt(0).toLowerCase() + key.substr(1);
979                settings[newKey] = optionsAsMap[key];
980            }
981        }
982        return settings;
983    }
984
985    function isCamelCase(s: string) {
986        return !s.length || s.charAt(0) === s.charAt(0).toLowerCase();
987    }
988
989    export function displayPartsToString(displayParts: SymbolDisplayPart[] | undefined) {
990        if (displayParts) {
991            return map(displayParts, displayPart => displayPart.text).join("");
992        }
993
994        return "";
995    }
996
997    export function getDefaultCompilerOptions(): CompilerOptions {
998        // Always default to "ScriptTarget.ES5" for the language service
999        return {
1000            target: ScriptTarget.ES5,
1001            jsx: JsxEmit.Preserve
1002        };
1003    }
1004
1005    export function getSupportedCodeFixes() {
1006        return codefix.getSupportedErrorCodes();
1007    }
1008
1009    class SyntaxTreeCache {
1010        // For our syntactic only features, we also keep a cache of the syntax tree for the
1011        // currently edited file.
1012        private currentFileName: string | undefined;
1013        private currentFileVersion: string | undefined;
1014        private currentFileScriptSnapshot: IScriptSnapshot | undefined;
1015        private currentSourceFile: SourceFile | undefined;
1016
1017        constructor(private host: LanguageServiceHost) {
1018        }
1019
1020        public getCurrentSourceFile(fileName: string): SourceFile {
1021            const scriptSnapshot = this.host.getScriptSnapshot(fileName);
1022            if (!scriptSnapshot) {
1023                // The host does not know about this file.
1024                throw new Error("Could not find file: '" + fileName + "'.");
1025            }
1026
1027            const scriptKind = getScriptKind(fileName, this.host);
1028            const version = this.host.getScriptVersion(fileName);
1029            let sourceFile: SourceFile | undefined;
1030
1031            if (this.currentFileName !== fileName) {
1032                // This is a new file, just parse it
1033                const options: CreateSourceFileOptions = {
1034                    languageVersion: ScriptTarget.Latest,
1035                    impliedNodeFormat: getImpliedNodeFormatForFile(
1036                        toPath(fileName, this.host.getCurrentDirectory(), this.host.getCompilerHost?.()?.getCanonicalFileName || hostGetCanonicalFileName(this.host)),
1037                        this.host.getCompilerHost?.()?.getModuleResolutionCache?.()?.getPackageJsonInfoCache(),
1038                        this.host,
1039                        this.host.getCompilationSettings()
1040                    ),
1041                    setExternalModuleIndicator: getSetExternalModuleIndicator(this.host.getCompilationSettings())
1042                };
1043                sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, options, version, /*setNodeParents*/ true, scriptKind, this.host.getCompilationSettings());
1044            }
1045            else if (this.currentFileVersion !== version) {
1046                // This is the same file, just a newer version. Incrementally parse the file.
1047                const editRange = scriptSnapshot.getChangeRange(this.currentFileScriptSnapshot!);
1048                sourceFile = updateLanguageServiceSourceFile(this.currentSourceFile!, scriptSnapshot, version, editRange, /*aggressiveChecks*/ undefined, this.host.getCompilationSettings());
1049            }
1050
1051            if (sourceFile) {
1052                // All done, ensure state is up to date
1053                this.currentFileVersion = version;
1054                this.currentFileName = fileName;
1055                this.currentFileScriptSnapshot = scriptSnapshot;
1056                this.currentSourceFile = sourceFile;
1057            }
1058
1059            return this.currentSourceFile!;
1060        }
1061    }
1062
1063    function setSourceFileFields(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string) {
1064        sourceFile.version = version;
1065        sourceFile.scriptSnapshot = scriptSnapshot;
1066    }
1067
1068    export function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTargetOrOptions: ScriptTarget | CreateSourceFileOptions, version: string, setNodeParents: boolean, scriptKind?: ScriptKind, option?: CompilerOptions): SourceFile {
1069        const sourceFile = createSourceFile(fileName, getSnapshotText(scriptSnapshot), scriptTargetOrOptions, setNodeParents, scriptKind, option);
1070        setSourceFileFields(sourceFile, scriptSnapshot, version);
1071        return sourceFile;
1072    }
1073
1074    export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange | undefined, aggressiveChecks?: boolean, option?: CompilerOptions): SourceFile {
1075        // If we were given a text change range, and our version or open-ness changed, then
1076        // incrementally parse this file.
1077        if (textChangeRange) {
1078            if (version !== sourceFile.version) {
1079                let newText: string;
1080
1081                // grab the fragment from the beginning of the original text to the beginning of the span
1082                const prefix = textChangeRange.span.start !== 0
1083                    ? sourceFile.text.substr(0, textChangeRange.span.start)
1084                    : "";
1085
1086                // grab the fragment from the end of the span till the end of the original text
1087                const suffix = textSpanEnd(textChangeRange.span) !== sourceFile.text.length
1088                    ? sourceFile.text.substr(textSpanEnd(textChangeRange.span))
1089                    : "";
1090
1091                if (textChangeRange.newLength === 0) {
1092                    // edit was a deletion - just combine prefix and suffix
1093                    newText = prefix && suffix ? prefix + suffix : prefix || suffix;
1094                }
1095                else {
1096                    // it was actual edit, fetch the fragment of new text that correspond to new span
1097                    const changedText = scriptSnapshot.getText(textChangeRange.span.start, textChangeRange.span.start + textChangeRange.newLength);
1098                    // combine prefix, changed text and suffix
1099                    newText = prefix && suffix
1100                        ? prefix + changedText + suffix
1101                        : prefix
1102                            ? (prefix + changedText)
1103                            : (changedText + suffix);
1104                }
1105
1106                const newSourceFile = updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks, option);
1107                setSourceFileFields(newSourceFile, scriptSnapshot, version);
1108                // after incremental parsing nameTable might not be up-to-date
1109                // drop it so it can be lazily recreated later
1110                newSourceFile.nameTable = undefined;
1111
1112                // dispose all resources held by old script snapshot
1113                if (sourceFile !== newSourceFile && sourceFile.scriptSnapshot) {
1114                    if (sourceFile.scriptSnapshot.dispose) {
1115                        sourceFile.scriptSnapshot.dispose();
1116                    }
1117
1118                    sourceFile.scriptSnapshot = undefined;
1119                }
1120
1121                return newSourceFile;
1122            }
1123        }
1124
1125        const options: CreateSourceFileOptions = {
1126            languageVersion: sourceFile.languageVersion,
1127            impliedNodeFormat: sourceFile.impliedNodeFormat,
1128            setExternalModuleIndicator: sourceFile.setExternalModuleIndicator,
1129        };
1130        // Otherwise, just create a new source file.
1131        return createLanguageServiceSourceFile(sourceFile.fileName, scriptSnapshot, options, version, /*setNodeParents*/ true, sourceFile.scriptKind, option);
1132    }
1133
1134    const NoopCancellationToken: CancellationToken = {
1135        isCancellationRequested: returnFalse,
1136        throwIfCancellationRequested: noop,
1137    };
1138
1139    class CancellationTokenObject implements CancellationToken {
1140        constructor(private cancellationToken: HostCancellationToken) {
1141        }
1142
1143        public isCancellationRequested(): boolean {
1144            return this.cancellationToken.isCancellationRequested();
1145        }
1146
1147        public throwIfCancellationRequested(): void {
1148            if (this.isCancellationRequested()) {
1149                tracing?.instant(tracing.Phase.Session, "cancellationThrown", { kind: "CancellationTokenObject" });
1150                throw new OperationCanceledException();
1151            }
1152        }
1153    }
1154
1155    /* @internal */
1156    /** A cancellation that throttles calls to the host */
1157    export class ThrottledCancellationToken implements CancellationToken {
1158        // Store when we last tried to cancel.  Checking cancellation can be expensive (as we have
1159        // to marshall over to the host layer).  So we only bother actually checking once enough
1160        // time has passed.
1161        private lastCancellationCheckTime = 0;
1162
1163        constructor(private hostCancellationToken: HostCancellationToken, private readonly throttleWaitMilliseconds = 20) {
1164        }
1165
1166        public isCancellationRequested(): boolean {
1167            const time = timestamp();
1168            const duration = Math.abs(time - this.lastCancellationCheckTime);
1169            if (duration >= this.throttleWaitMilliseconds) {
1170                // Check no more than once every throttle wait milliseconds
1171                this.lastCancellationCheckTime = time;
1172                return this.hostCancellationToken.isCancellationRequested();
1173            }
1174
1175            return false;
1176        }
1177
1178        public throwIfCancellationRequested(): void {
1179            if (this.isCancellationRequested()) {
1180                tracing?.instant(tracing.Phase.Session, "cancellationThrown", { kind: "ThrottledCancellationToken" });
1181                throw new OperationCanceledException();
1182            }
1183        }
1184    }
1185
1186    const invalidOperationsInPartialSemanticMode: readonly (keyof LanguageService)[] = [
1187        "getSemanticDiagnostics",
1188        "getSuggestionDiagnostics",
1189        "getCompilerOptionsDiagnostics",
1190        "getSemanticClassifications",
1191        "getEncodedSemanticClassifications",
1192        "getCodeFixesAtPosition",
1193        "getCombinedCodeFix",
1194        "applyCodeActionCommand",
1195        "organizeImports",
1196        "getEditsForFileRename",
1197        "getEmitOutput",
1198        "getApplicableRefactors",
1199        "getEditsForRefactor",
1200        "prepareCallHierarchy",
1201        "provideCallHierarchyIncomingCalls",
1202        "provideCallHierarchyOutgoingCalls",
1203        "provideInlayHints"
1204    ];
1205
1206    const invalidOperationsInSyntacticMode: readonly (keyof LanguageService)[] = [
1207        ...invalidOperationsInPartialSemanticMode,
1208        "getCompletionsAtPosition",
1209        "getCompletionEntryDetails",
1210        "getCompletionEntrySymbol",
1211        "getSignatureHelpItems",
1212        "getQuickInfoAtPosition",
1213        "getDefinitionAtPosition",
1214        "getDefinitionAndBoundSpan",
1215        "getImplementationAtPosition",
1216        "getTypeDefinitionAtPosition",
1217        "getReferencesAtPosition",
1218        "findReferences",
1219        "getOccurrencesAtPosition",
1220        "getDocumentHighlights",
1221        "getNavigateToItems",
1222        "getRenameInfo",
1223        "findRenameLocations",
1224        "getApplicableRefactors",
1225    ];
1226    export function createLanguageService(
1227        host: LanguageServiceHost,
1228        documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory()),
1229        syntaxOnlyOrLanguageServiceMode?: boolean | LanguageServiceMode,
1230    ): LanguageService {
1231        let languageServiceMode: LanguageServiceMode;
1232        if (syntaxOnlyOrLanguageServiceMode === undefined) {
1233            languageServiceMode = LanguageServiceMode.Semantic;
1234        }
1235        else if (typeof syntaxOnlyOrLanguageServiceMode === "boolean") {
1236            // languageServiceMode = SyntaxOnly
1237            languageServiceMode = syntaxOnlyOrLanguageServiceMode ? LanguageServiceMode.Syntactic : LanguageServiceMode.Semantic;
1238        }
1239        else {
1240            languageServiceMode = syntaxOnlyOrLanguageServiceMode;
1241        }
1242
1243        const syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host);
1244        let program: Program;
1245        let builderProgram: BuilderProgram;
1246        let lastProjectVersion: string;
1247        let lastTypesRootVersion = 0;
1248
1249        const cancellationToken = host.getCancellationToken
1250            ? new CancellationTokenObject(host.getCancellationToken())
1251            : NoopCancellationToken;
1252
1253        const currentDirectory = host.getCurrentDirectory();
1254
1255        // Checks if the localized messages json is set, and if not, query the host for it
1256        maybeSetLocalizedDiagnosticMessages(host.getLocalizedDiagnosticMessages?.bind(host));
1257
1258        function log(message: string) {
1259            if (host.log) {
1260                host.log(message);
1261            }
1262        }
1263
1264        const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host);
1265        const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
1266
1267        const sourceMapper = getSourceMapper({
1268            useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
1269            getCurrentDirectory: () => currentDirectory,
1270            getProgram,
1271            fileExists: maybeBind(host, host.fileExists),
1272            readFile: maybeBind(host, host.readFile),
1273            getDocumentPositionMapper: maybeBind(host, host.getDocumentPositionMapper),
1274            getSourceFileLike: maybeBind(host, host.getSourceFileLike),
1275            log
1276        });
1277
1278        function getValidSourceFile(fileName: string): SourceFile {
1279            const sourceFile = program.getSourceFile(fileName);
1280            if (!sourceFile) {
1281                const error: Error & PossibleProgramFileInfo = new Error(`Could not find source file: '${fileName}'.`);
1282
1283                // We've been having trouble debugging this, so attach sidecar data for the tsserver log.
1284                // See https://github.com/microsoft/TypeScript/issues/30180.
1285                error.ProgramFiles = program.getSourceFiles().map(f => f.fileName);
1286
1287                throw error;
1288            }
1289            return sourceFile;
1290        }
1291
1292        function synchronizeHostData(isCreateIncrementalProgramMode: boolean = false, withLinterProgram?: boolean): void {
1293            Debug.assert(languageServiceMode !== LanguageServiceMode.Syntactic);
1294            // perform fast check if host supports it
1295            if (host.getProjectVersion) {
1296                const hostProjectVersion = host.getProjectVersion();
1297                if (hostProjectVersion) {
1298                    if (lastProjectVersion === hostProjectVersion && !host.hasChangedAutomaticTypeDirectiveNames?.()) {
1299                        return;
1300                    }
1301
1302                    lastProjectVersion = hostProjectVersion;
1303                }
1304            }
1305
1306            const typeRootsVersion = host.getTypeRootsVersion ? host.getTypeRootsVersion() : 0;
1307            if (lastTypesRootVersion !== typeRootsVersion) {
1308                log("TypeRoots version has changed; provide new program");
1309                program = undefined!; // TODO: GH#18217
1310                lastTypesRootVersion = typeRootsVersion;
1311            }
1312
1313            // This array is retained by the program and will be used to determine if the program is up to date,
1314            // so we need to make a copy in case the host mutates the underlying array - otherwise it would look
1315            // like every program always has the host's current list of root files.
1316            const rootFileNames = host.getScriptFileNames().slice();
1317
1318            // Get a fresh cache of the host information
1319            const newSettings = host.getCompilationSettings() || getDefaultCompilerOptions();
1320            const hasInvalidatedResolutions: HasInvalidatedResolutions = host.hasInvalidatedResolutions || returnFalse;
1321            const hasChangedAutomaticTypeDirectiveNames = maybeBind(host, host.hasChangedAutomaticTypeDirectiveNames);
1322            const projectReferences = host.getProjectReferences?.();
1323            let parsedCommandLines: ESMap<Path, ParsedCommandLine | false> | undefined;
1324
1325            // Now create a new compiler
1326            let compilerHost: CompilerHost | undefined = {
1327                getSourceFile: getOrCreateSourceFile,
1328                getSourceFileByPath: getOrCreateSourceFileByPath,
1329                getCancellationToken: () => cancellationToken,
1330                getCanonicalFileName,
1331                useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
1332                getNewLine: () => getNewLineCharacter(newSettings, () => getNewLineOrDefaultFromHost(host)),
1333                getDefaultLibFileName: options => host.getDefaultLibFileName(options),
1334                writeFile: noop,
1335                getCurrentDirectory: () => currentDirectory,
1336                fileExists: fileName => host.fileExists(fileName),
1337                readFile: fileName => host.readFile && host.readFile(fileName),
1338                getSymlinkCache: maybeBind(host, host.getSymlinkCache),
1339                realpath: maybeBind(host, host.realpath),
1340                directoryExists: directoryName => {
1341                    return directoryProbablyExists(directoryName, host);
1342                },
1343                getDirectories: path => {
1344                    return host.getDirectories ? host.getDirectories(path) : [];
1345                },
1346                readDirectory: (path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number) => {
1347                    Debug.checkDefined(host.readDirectory, "'LanguageServiceHost.readDirectory' must be implemented to correctly process 'projectReferences'");
1348                    return host.readDirectory!(path, extensions, exclude, include, depth);
1349                },
1350                onReleaseOldSourceFile,
1351                onReleaseParsedCommandLine,
1352                hasInvalidatedResolutions,
1353                hasChangedAutomaticTypeDirectiveNames,
1354                trace: maybeBind(host, host.trace),
1355                resolveModuleNames: maybeBind(host, host.resolveModuleNames),
1356                getModuleResolutionCache: maybeBind(host, host.getModuleResolutionCache),
1357                resolveTypeReferenceDirectives: maybeBind(host, host.resolveTypeReferenceDirectives),
1358                useSourceOfProjectReferenceRedirect: maybeBind(host, host.useSourceOfProjectReferenceRedirect),
1359                getParsedCommandLine,
1360                getJsDocNodeCheckedConfig: maybeBind(host, host.getJsDocNodeCheckedConfig),
1361                getJsDocNodeConditionCheckedResult: maybeBind(host, host.getJsDocNodeConditionCheckedResult),
1362                getFileCheckedModuleInfo: maybeBind(host, host.getFileCheckedModuleInfo),
1363            };
1364
1365            const originalGetSourceFile = compilerHost.getSourceFile;
1366
1367            const { getSourceFileWithCache } = changeCompilerHostLikeToUseCache(
1368                compilerHost,
1369                fileName => toPath(fileName, currentDirectory, getCanonicalFileName),
1370                (...args) => originalGetSourceFile.call(compilerHost, ...args)
1371            );
1372            compilerHost.getSourceFile = getSourceFileWithCache!;
1373
1374            host.setCompilerHost?.(compilerHost);
1375
1376            const parseConfigHost: ParseConfigFileHost = {
1377                useCaseSensitiveFileNames,
1378                fileExists: fileName => compilerHost!.fileExists(fileName),
1379                readFile: fileName => compilerHost!.readFile(fileName),
1380                readDirectory: (...args) => compilerHost!.readDirectory!(...args),
1381                trace: compilerHost.trace,
1382                getCurrentDirectory: compilerHost.getCurrentDirectory,
1383                onUnRecoverableConfigFileDiagnostic: noop,
1384            };
1385
1386            // The call to isProgramUptoDate below may refer back to documentRegistryBucketKey;
1387            // calculate this early so it's not undefined if downleveled to a var (or, if emitted
1388            // as a const variable without downleveling, doesn't crash).
1389            const documentRegistryBucketKey = documentRegistry.getKeyForCompilationSettings(newSettings);
1390
1391            // If the program is already up-to-date, we can reuse it
1392            if (isProgramUptoDate(program, rootFileNames, newSettings, (_path, fileName) => host.getScriptVersion(fileName), fileName => compilerHost!.fileExists(fileName), hasInvalidatedResolutions, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences)) {
1393                return;
1394            }
1395
1396            // clear ArkUI collected properties
1397            host.clearProps && host.clearProps();
1398
1399            compilerHost.getLastCompiledProgram = () => { return program; }
1400
1401            // IMPORTANT - It is critical from this moment onward that we do not check
1402            // cancellation tokens.  We are about to mutate source files from a previous program
1403            // instance.  If we cancel midway through, we may end up in an inconsistent state where
1404            // the program points to old source files that have been invalidated because of
1405            // incremental parsing.
1406
1407            const options: CreateProgramOptions = {
1408                rootNames: rootFileNames,
1409                options: newSettings,
1410                host: compilerHost,
1411                oldProgram: program,
1412                projectReferences
1413            };
1414
1415            if (isCreateIncrementalProgramMode) {
1416                if (withLinterProgram) {
1417                    builderProgram = createIncrementalProgramForArkTs(options);
1418                } else {
1419                    builderProgram = createIncrementalProgram(options);
1420                }
1421                program = builderProgram.getProgram();
1422            } else {
1423                program = createProgram(options);
1424            }
1425
1426            // 'getOrCreateSourceFile' depends on caching but should be used past this point.
1427            // After this point, the cache needs to be cleared to allow all collected snapshots to be released
1428            compilerHost = undefined;
1429            parsedCommandLines = undefined;
1430
1431            // We reset this cache on structure invalidation so we don't hold on to outdated files for long; however we can't use the `compilerHost` above,
1432            // Because it only functions until `hostCache` is cleared, while we'll potentially need the functionality to lazily read sourcemap files during
1433            // the course of whatever called `synchronizeHostData`
1434            sourceMapper.clearCache();
1435
1436            // Make sure all the nodes in the program are both bound, and have their parent
1437            // pointers set property.
1438            program.getTypeChecker();
1439            return;
1440
1441            function getParsedCommandLine(fileName: string): ParsedCommandLine | undefined {
1442                const path = toPath(fileName, currentDirectory, getCanonicalFileName);
1443                const existing = parsedCommandLines?.get(path);
1444                if (existing !== undefined) return existing || undefined;
1445
1446                const result = host.getParsedCommandLine ?
1447                    host.getParsedCommandLine(fileName) :
1448                    getParsedCommandLineOfConfigFileUsingSourceFile(fileName);
1449                (parsedCommandLines ||= new Map()).set(path, result || false);
1450                return result;
1451            }
1452
1453            function getParsedCommandLineOfConfigFileUsingSourceFile(configFileName: string): ParsedCommandLine | undefined {
1454                const result = getOrCreateSourceFile(configFileName, ScriptTarget.JSON) as JsonSourceFile | undefined;
1455                if (!result) return undefined;
1456                result.path = toPath(configFileName, currentDirectory, getCanonicalFileName);
1457                result.resolvedPath = result.path;
1458                result.originalFileName = result.fileName;
1459                return parseJsonSourceFileConfigFileContent(
1460                    result,
1461                    parseConfigHost,
1462                    getNormalizedAbsolutePath(getDirectoryPath(configFileName), currentDirectory),
1463                    /*optionsToExtend*/ undefined,
1464                    getNormalizedAbsolutePath(configFileName, currentDirectory),
1465                );
1466            }
1467
1468            function onReleaseParsedCommandLine(configFileName: string, oldResolvedRef: ResolvedProjectReference | undefined, oldOptions: CompilerOptions) {
1469                if (host.getParsedCommandLine) {
1470                    host.onReleaseParsedCommandLine?.(configFileName, oldResolvedRef, oldOptions);
1471                }
1472                else if (oldResolvedRef) {
1473                    onReleaseOldSourceFile(oldResolvedRef.sourceFile, oldOptions);
1474                }
1475            }
1476
1477            // Release any files we have acquired in the old program but are
1478            // not part of the new program.
1479            function onReleaseOldSourceFile(oldSourceFile: SourceFile, oldOptions: CompilerOptions) {
1480                const oldSettingsKey = documentRegistry.getKeyForCompilationSettings(oldOptions);
1481                documentRegistry.releaseDocumentWithKey(oldSourceFile.resolvedPath, oldSettingsKey, oldSourceFile.scriptKind, oldSourceFile.impliedNodeFormat);
1482            }
1483
1484            function getOrCreateSourceFile(fileName: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined {
1485                return getOrCreateSourceFileByPath(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), languageVersionOrOptions, onError, shouldCreateNewSourceFile);
1486            }
1487
1488            function getOrCreateSourceFileByPath(fileName: string, path: Path, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, _onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined {
1489                // There was originally an assert here to ensure that getOrCreateSourceFileByPath was called in typical CompilerHost lifetime.
1490                // We removed this assert because currently this function may also be called outside of the typical CompilerHost lifetime,
1491                // in which case the compilerhost may be undefined.
1492                // The program is asking for this file, check first if the host can locate it.
1493                // If the host can not locate the file, then it does not exist. return undefined
1494                // to the program to allow reporting of errors for missing files.
1495                const scriptSnapshot = host.getScriptSnapshot(fileName);
1496                if (!scriptSnapshot) {
1497                    return undefined;
1498                }
1499
1500                const scriptKind = getScriptKind(fileName, host);
1501                const scriptVersion = host.getScriptVersion(fileName);
1502
1503                // Check if the language version has changed since we last created a program; if they are the same,
1504                // it is safe to reuse the sourceFiles; if not, then the shape of the AST can change, and the oldSourceFile
1505                // can not be reused. we have to dump all syntax trees and create new ones.
1506                if (!shouldCreateNewSourceFile) {
1507                    // Check if the old program had this file already
1508                    const oldSourceFile = program && program.getSourceFileByPath(path);
1509                    if (oldSourceFile) {
1510                        // We already had a source file for this file name.  Go to the registry to
1511                        // ensure that we get the right up to date version of it.  We need this to
1512                        // address the following race-condition.  Specifically, say we have the following:
1513                        //
1514                        //      LS1
1515                        //          \
1516                        //           DocumentRegistry
1517                        //          /
1518                        //      LS2
1519                        //
1520                        // Each LS has a reference to file 'foo.ts' at version 1.  LS2 then updates
1521                        // it's version of 'foo.ts' to version 2.  This will cause LS2 and the
1522                        // DocumentRegistry to have version 2 of the document.  However, LS1 will
1523                        // have version 1.  And *importantly* this source file will be *corrupt*.
1524                        // The act of creating version 2 of the file irrevocably damages the version
1525                        // 1 file.
1526                        //
1527                        // So, later when we call into LS1, we need to make sure that it doesn't use
1528                        // it's source file any more, and instead defers to DocumentRegistry to get
1529                        // either version 1, version 2 (or some other version) depending on what the
1530                        // host says should be used.
1531
1532                        // We do not support the scenario where a host can modify a registered
1533                        // file's script kind, i.e. in one project some file is treated as ".ts"
1534                        // and in another as ".js"
1535                        if (scriptKind === oldSourceFile.scriptKind) {
1536                            return documentRegistry.updateDocumentWithKey(fileName, path, host, documentRegistryBucketKey, scriptSnapshot, scriptVersion, scriptKind, languageVersionOrOptions);
1537                        }
1538                        else {
1539                            // Release old source file and fall through to aquire new file with new script kind
1540                            documentRegistry.releaseDocumentWithKey(oldSourceFile.resolvedPath, documentRegistry.getKeyForCompilationSettings(program.getCompilerOptions()), oldSourceFile.scriptKind, oldSourceFile.impliedNodeFormat);
1541                        }
1542                    }
1543
1544                    // We didn't already have the file.  Fall through and acquire it from the registry.
1545                }
1546
1547                // Could not find this file in the old program, create a new SourceFile for it.
1548                return documentRegistry.acquireDocumentWithKey(fileName, path, host, documentRegistryBucketKey, scriptSnapshot, scriptVersion, scriptKind, languageVersionOrOptions);
1549            }
1550        }
1551
1552        // TODO: GH#18217 frequently asserted as defined
1553        function getProgram(): Program | undefined {
1554            if (languageServiceMode === LanguageServiceMode.Syntactic) {
1555                Debug.assert(program === undefined);
1556                return undefined;
1557            }
1558
1559            synchronizeHostData();
1560
1561            return program;
1562        }
1563
1564        function getBuilderProgram(withLinterProgram?: boolean): BuilderProgram | undefined {
1565            if (languageServiceMode === LanguageServiceMode.Syntactic) {
1566                Debug.assert(builderProgram === undefined);
1567                return undefined;
1568            }
1569
1570            synchronizeHostData(isIncrementalCompilation(host.getCompilationSettings()), withLinterProgram);
1571
1572            return builderProgram;
1573        }
1574
1575        function getAutoImportProvider(): Program | undefined {
1576            return host.getPackageJsonAutoImportProvider?.();
1577        }
1578
1579        function updateIsDefinitionOfReferencedSymbols(referencedSymbols: readonly ReferencedSymbol[], knownSymbolSpans: Set<DocumentSpan>): boolean {
1580            const checker = program.getTypeChecker();
1581            const symbol = getSymbolForProgram();
1582
1583            if (!symbol) return false;
1584
1585            for (const referencedSymbol of referencedSymbols) {
1586                for (const ref of referencedSymbol.references) {
1587                    const refNode = getNodeForSpan(ref);
1588                    Debug.assertIsDefined(refNode);
1589                    if (knownSymbolSpans.has(ref) || FindAllReferences.isDeclarationOfSymbol(refNode, symbol)) {
1590                        knownSymbolSpans.add(ref);
1591                        ref.isDefinition = true;
1592                        const mappedSpan = getMappedDocumentSpan(ref, sourceMapper, maybeBind(host, host.fileExists));
1593                        if (mappedSpan) {
1594                            knownSymbolSpans.add(mappedSpan);
1595                        }
1596                    }
1597                    else {
1598                        ref.isDefinition = false;
1599                    }
1600                }
1601            }
1602
1603            return true;
1604
1605            function getSymbolForProgram(): Symbol | undefined {
1606                for (const referencedSymbol of referencedSymbols) {
1607                    for (const ref of referencedSymbol.references) {
1608                        if (knownSymbolSpans.has(ref)) {
1609                            const refNode = getNodeForSpan(ref);
1610                            Debug.assertIsDefined(refNode);
1611                            return checker.getSymbolAtLocation(refNode);
1612                        }
1613                        const mappedSpan = getMappedDocumentSpan(ref, sourceMapper, maybeBind(host, host.fileExists));
1614                        if (mappedSpan && knownSymbolSpans.has(mappedSpan)) {
1615                            const refNode = getNodeForSpan(mappedSpan);
1616                            if (refNode) {
1617                                return checker.getSymbolAtLocation(refNode);
1618                            }
1619                        }
1620                    }
1621                }
1622
1623                return undefined;
1624            }
1625
1626            function getNodeForSpan(docSpan: DocumentSpan): Node | undefined {
1627                const sourceFile = program.getSourceFile(docSpan.fileName);
1628                if (!sourceFile) return undefined;
1629                const rawNode = getTouchingPropertyName(sourceFile, docSpan.textSpan.start);
1630                const adjustedNode = FindAllReferences.Core.getAdjustedNode(rawNode, { use: FindAllReferences.FindReferencesUse.References });
1631                return adjustedNode;
1632            }
1633        }
1634
1635        function cleanupSemanticCache(): void {
1636            program = undefined!; // TODO: GH#18217
1637        }
1638
1639        function dispose(): void {
1640            if (program) {
1641                // Use paths to ensure we are using correct key and paths as document registry could be created with different current directory than host
1642                const key = documentRegistry.getKeyForCompilationSettings(program.getCompilerOptions());
1643                forEach(program.getSourceFiles(), f =>
1644                    documentRegistry.releaseDocumentWithKey(f.resolvedPath, key, f.scriptKind, f.impliedNodeFormat));
1645                program = undefined!; // TODO: GH#18217
1646            }
1647            host = undefined!;
1648        }
1649
1650        /// Diagnostics
1651        function getSyntacticDiagnostics(fileName: string): DiagnosticWithLocation[] {
1652            synchronizeHostData();
1653
1654            return program.getSyntacticDiagnostics(getValidSourceFile(fileName), cancellationToken).slice();
1655        }
1656
1657        /**
1658         * getSemanticDiagnostics return array of Diagnostics. If '-d' is not enabled, only report semantic errors
1659         * If '-d' enabled, report both semantic and emitter errors
1660         */
1661        function getSemanticDiagnostics(fileName: string): Diagnostic[] {
1662            synchronizeHostData();
1663
1664            const targetSourceFile = getValidSourceFile(fileName);
1665
1666            // Only perform the action per file regardless of '-out' flag as LanguageServiceHost is expected to call this function per file.
1667            // Therefore only get diagnostics for given file.
1668
1669            const semanticDiagnostics = program.getSemanticDiagnostics(targetSourceFile, cancellationToken);
1670            if (!getEmitDeclarations(program.getCompilerOptions())) {
1671                return semanticDiagnostics.slice();
1672            }
1673
1674            // If '-d' is enabled, check for emitter error. One example of emitter error is export class implements non-export interface
1675            const declarationDiagnostics = program.getDeclarationDiagnostics(targetSourceFile, cancellationToken);
1676            return [...semanticDiagnostics, ...declarationDiagnostics];
1677        }
1678
1679        function getSuggestionDiagnostics(fileName: string): DiagnosticWithLocation[] {
1680            synchronizeHostData();
1681            return computeSuggestionDiagnostics(getValidSourceFile(fileName), program, cancellationToken);
1682        }
1683
1684        function getCompilerOptionsDiagnostics() {
1685            synchronizeHostData();
1686            return [...program.getOptionsDiagnostics(cancellationToken), ...program.getGlobalDiagnostics(cancellationToken)];
1687        }
1688
1689        function getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions = emptyOptions, formattingSettings?: FormatCodeSettings): CompletionInfo | undefined {
1690            // Convert from deprecated options names to new names
1691            const fullPreferences: UserPreferences = {
1692                ...identity<UserPreferences>(options), // avoid excess property check
1693                includeCompletionsForModuleExports: options.includeCompletionsForModuleExports || options.includeExternalModuleExports,
1694                includeCompletionsWithInsertText: options.includeCompletionsWithInsertText || options.includeInsertTextCompletions,
1695            };
1696            synchronizeHostData();
1697            return Completions.getCompletionsAtPosition(
1698                host,
1699                program,
1700                log,
1701                getValidSourceFile(fileName),
1702                position,
1703                fullPreferences,
1704                options.triggerCharacter,
1705                options.triggerKind,
1706                cancellationToken,
1707                formattingSettings && formatting.getFormatContext(formattingSettings, host));
1708        }
1709
1710        function getCompletionEntryDetails(fileName: string, position: number, name: string, formattingOptions: FormatCodeSettings | undefined, source: string | undefined, preferences: UserPreferences = emptyOptions, data?: CompletionEntryData): CompletionEntryDetails | undefined {
1711            synchronizeHostData();
1712            return Completions.getCompletionEntryDetails(
1713                program,
1714                log,
1715                getValidSourceFile(fileName),
1716                position,
1717                { name, source, data },
1718                host,
1719                (formattingOptions && formatting.getFormatContext(formattingOptions, host))!, // TODO: GH#18217
1720                preferences,
1721                cancellationToken,
1722            );
1723        }
1724
1725        function getCompletionEntrySymbol(fileName: string, position: number, name: string, source?: string, preferences: UserPreferences = emptyOptions): Symbol | undefined {
1726            synchronizeHostData();
1727            return Completions.getCompletionEntrySymbol(program, log, getValidSourceFile(fileName), position, { name, source }, host, preferences);
1728        }
1729
1730        function getQuickInfoAtPosition(fileName: string, position: number): QuickInfo | undefined {
1731            synchronizeHostData();
1732
1733            const sourceFile = getValidSourceFile(fileName);
1734            const node = getTouchingPropertyName(sourceFile, position);
1735            if (node === sourceFile) {
1736                // Avoid giving quickInfo for the sourceFile as a whole.
1737                return undefined;
1738            }
1739
1740            const typeChecker = program.getTypeChecker();
1741            const nodeForQuickInfo = getNodeForQuickInfo(node);
1742            const symbol = getSymbolAtLocationForQuickInfo(nodeForQuickInfo, typeChecker);
1743
1744            if (!symbol || typeChecker.isUnknownSymbol(symbol)) {
1745                const type = shouldGetType(sourceFile, nodeForQuickInfo, position) ? typeChecker.getTypeAtLocation(nodeForQuickInfo) : undefined;
1746                return type && {
1747                    kind: ScriptElementKind.unknown,
1748                    kindModifiers: ScriptElementKindModifier.none,
1749                    textSpan: createTextSpanFromNode(nodeForQuickInfo, sourceFile),
1750                    displayParts: typeChecker.runWithCancellationToken(cancellationToken, typeChecker => typeToDisplayParts(typeChecker, type, getContainerNode(nodeForQuickInfo))),
1751                    documentation: type.symbol ? type.symbol.getDocumentationComment(typeChecker) : undefined,
1752                    tags: type.symbol ? type.symbol.getJsDocTags(typeChecker) : undefined
1753                };
1754            }
1755
1756            const { symbolKind, displayParts, documentation, tags } = typeChecker.runWithCancellationToken(cancellationToken, typeChecker =>
1757                SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, getContainerNode(nodeForQuickInfo), nodeForQuickInfo)
1758            );
1759            return {
1760                kind: symbolKind,
1761                kindModifiers: SymbolDisplay.getSymbolModifiers(typeChecker, symbol),
1762                textSpan: createTextSpanFromNode(nodeForQuickInfo, sourceFile),
1763                displayParts,
1764                documentation,
1765                tags,
1766            };
1767        }
1768
1769        function getNodeForQuickInfo(node: Node): Node {
1770            if (isNewExpression(node.parent) && node.pos === node.parent.pos) {
1771                return node.parent.expression;
1772            }
1773            if (isNamedTupleMember(node.parent) && node.pos === node.parent.pos) {
1774                return node.parent;
1775            }
1776            if (isImportMeta(node.parent) && node.parent.name === node) {
1777                return node.parent;
1778            }
1779            return node;
1780        }
1781
1782        function shouldGetType(sourceFile: SourceFile, node: Node, position: number): boolean {
1783            switch (node.kind) {
1784                case SyntaxKind.Identifier:
1785                    return !isLabelName(node) && !isTagName(node) && !isConstTypeReference(node.parent);
1786                case SyntaxKind.PropertyAccessExpression:
1787                case SyntaxKind.QualifiedName:
1788                    // Don't return quickInfo if inside the comment in `a/**/.b`
1789                    return !isInComment(sourceFile, position);
1790                case SyntaxKind.ThisKeyword:
1791                case SyntaxKind.ThisType:
1792                case SyntaxKind.SuperKeyword:
1793                case SyntaxKind.NamedTupleMember:
1794                    return true;
1795                case SyntaxKind.MetaProperty:
1796                    return isImportMeta(node);
1797                default:
1798                    return false;
1799            }
1800        }
1801
1802        /// Goto definition
1803        function getDefinitionAtPosition(fileName: string, position: number, searchOtherFilesOnly?: boolean, stopAtAlias?: boolean): readonly DefinitionInfo[] | undefined {
1804            synchronizeHostData();
1805            return GoToDefinition.getDefinitionAtPosition(program, getValidSourceFile(fileName), position, searchOtherFilesOnly, stopAtAlias);
1806        }
1807
1808        function getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan | undefined {
1809            synchronizeHostData();
1810            return GoToDefinition.getDefinitionAndBoundSpan(program, getValidSourceFile(fileName), position);
1811        }
1812
1813        function getTypeDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined {
1814            synchronizeHostData();
1815            return GoToDefinition.getTypeDefinitionAtPosition(program.getTypeChecker(), getValidSourceFile(fileName), position);
1816        }
1817
1818        /// Goto implementation
1819
1820        function getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] | undefined {
1821            synchronizeHostData();
1822            return FindAllReferences.getImplementationsAtPosition(program, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position);
1823        }
1824
1825        /// References and Occurrences
1826        function getOccurrencesAtPosition(fileName: string, position: number): readonly ReferenceEntry[] | undefined {
1827            return flatMap(
1828                getDocumentHighlights(fileName, position, [fileName]),
1829                entry => entry.highlightSpans.map<ReferenceEntry>(highlightSpan => ({
1830                    fileName: entry.fileName,
1831                    textSpan: highlightSpan.textSpan,
1832                    isWriteAccess: highlightSpan.kind === HighlightSpanKind.writtenReference,
1833                    ...highlightSpan.isInString && { isInString: true },
1834                    ...highlightSpan.contextSpan && { contextSpan: highlightSpan.contextSpan }
1835                }))
1836            );
1837        }
1838
1839        function getDocumentHighlights(fileName: string, position: number, filesToSearch: readonly string[]): DocumentHighlights[] | undefined {
1840            const normalizedFileName = normalizePath(fileName);
1841            Debug.assert(filesToSearch.some(f => normalizePath(f) === normalizedFileName));
1842            synchronizeHostData();
1843            const sourceFilesToSearch = mapDefined(filesToSearch, fileName => program.getSourceFile(fileName));
1844            const sourceFile = getValidSourceFile(fileName);
1845            return DocumentHighlights.getDocumentHighlights(program, cancellationToken, sourceFile, position, sourceFilesToSearch);
1846        }
1847
1848        function findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): RenameLocation[] | undefined {
1849            synchronizeHostData();
1850            const sourceFile = getValidSourceFile(fileName);
1851            const node = getAdjustedRenameLocation(getTouchingPropertyName(sourceFile, position));
1852            if (!Rename.nodeIsEligibleForRename(node)) return undefined;
1853            if (isIdentifier(node) && (isJsxOpeningElement(node.parent) || isJsxClosingElement(node.parent)) && isIntrinsicJsxName(node.escapedText)) {
1854                const { openingElement, closingElement } = node.parent.parent;
1855                return [openingElement, closingElement].map((node): RenameLocation => {
1856                    const textSpan = createTextSpanFromNode(node.tagName, sourceFile);
1857                    return {
1858                        fileName: sourceFile.fileName,
1859                        textSpan,
1860                        ...FindAllReferences.toContextSpan(textSpan, sourceFile, node.parent)
1861                    };
1862                });
1863            }
1864            else {
1865                return getReferencesWorker(node, position, { findInStrings, findInComments, providePrefixAndSuffixTextForRename, use: FindAllReferences.FindReferencesUse.Rename },
1866                    (entry, originalNode, checker) => FindAllReferences.toRenameLocation(entry, originalNode, checker, providePrefixAndSuffixTextForRename || false));
1867            }
1868        }
1869
1870        function getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined {
1871            synchronizeHostData();
1872            return getReferencesWorker(getTouchingPropertyName(getValidSourceFile(fileName), position), position, { use: FindAllReferences.FindReferencesUse.References }, FindAllReferences.toReferenceEntry);
1873        }
1874
1875        function getReferencesWorker<T>(node: Node, position: number, options: FindAllReferences.Options, cb: FindAllReferences.ToReferenceOrRenameEntry<T>): T[] | undefined {
1876            synchronizeHostData();
1877
1878            // Exclude default library when renaming as commonly user don't want to change that file.
1879            const sourceFiles = options && options.use === FindAllReferences.FindReferencesUse.Rename
1880                ? program.getSourceFiles().filter(sourceFile => !program.isSourceFileDefaultLibrary(sourceFile))
1881                : program.getSourceFiles();
1882
1883            return FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, sourceFiles, node, position, options, cb);
1884        }
1885
1886        function findReferences(fileName: string, position: number): ReferencedSymbol[] | undefined {
1887            synchronizeHostData();
1888            return FindAllReferences.findReferencedSymbols(program, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position);
1889        }
1890
1891        function getFileReferences(fileName: string): ReferenceEntry[] {
1892            synchronizeHostData();
1893            return FindAllReferences.Core.getReferencesForFileName(fileName, program, program.getSourceFiles()).map(FindAllReferences.toReferenceEntry);
1894        }
1895
1896        function getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles = false): NavigateToItem[] {
1897            synchronizeHostData();
1898            const sourceFiles = fileName ? [getValidSourceFile(fileName)] : program.getSourceFiles();
1899            return NavigateTo.getNavigateToItems(sourceFiles, program.getTypeChecker(), cancellationToken, searchValue, maxResultCount, excludeDtsFiles);
1900        }
1901
1902        function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, forceDtsEmit?: boolean) {
1903            synchronizeHostData();
1904
1905            const sourceFile = getValidSourceFile(fileName);
1906            const customTransformers = host.getCustomTransformers && host.getCustomTransformers();
1907            return getFileEmitOutput(program, sourceFile, !!emitOnlyDtsFiles, cancellationToken, customTransformers, forceDtsEmit);
1908        }
1909
1910        // Signature help
1911        /**
1912         * This is a semantic operation.
1913         */
1914        function getSignatureHelpItems(fileName: string, position: number, { triggerReason }: SignatureHelpItemsOptions = emptyOptions): SignatureHelpItems | undefined {
1915            synchronizeHostData();
1916
1917            const sourceFile = getValidSourceFile(fileName);
1918
1919            return SignatureHelp.getSignatureHelpItems(program, sourceFile, position, triggerReason, cancellationToken);
1920        }
1921
1922        /// Syntactic features
1923        function getNonBoundSourceFile(fileName: string): SourceFile {
1924            return syntaxTreeCache.getCurrentSourceFile(fileName);
1925        }
1926
1927        function getNameOrDottedNameSpan(fileName: string, startPos: number, _endPos: number): TextSpan | undefined {
1928            const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
1929
1930            // Get node at the location
1931            const node = getTouchingPropertyName(sourceFile, startPos);
1932
1933            if (node === sourceFile) {
1934                return undefined;
1935            }
1936
1937            switch (node.kind) {
1938                case SyntaxKind.PropertyAccessExpression:
1939                case SyntaxKind.QualifiedName:
1940                case SyntaxKind.StringLiteral:
1941                case SyntaxKind.FalseKeyword:
1942                case SyntaxKind.TrueKeyword:
1943                case SyntaxKind.NullKeyword:
1944                case SyntaxKind.SuperKeyword:
1945                case SyntaxKind.ThisKeyword:
1946                case SyntaxKind.ThisType:
1947                case SyntaxKind.Identifier:
1948                    break;
1949
1950                // Cant create the text span
1951                default:
1952                    return undefined;
1953            }
1954
1955            let nodeForStartPos = node;
1956            while (true) {
1957                if (isRightSideOfPropertyAccess(nodeForStartPos) || isRightSideOfQualifiedName(nodeForStartPos)) {
1958                    // If on the span is in right side of the the property or qualified name, return the span from the qualified name pos to end of this node
1959                    nodeForStartPos = nodeForStartPos.parent;
1960                }
1961                else if (isNameOfModuleDeclaration(nodeForStartPos)) {
1962                    // If this is name of a module declarations, check if this is right side of dotted module name
1963                    // If parent of the module declaration which is parent of this node is module declaration and its body is the module declaration that this node is name of
1964                    // Then this name is name from dotted module
1965                    if (nodeForStartPos.parent.parent.kind === SyntaxKind.ModuleDeclaration &&
1966                        (nodeForStartPos.parent.parent as ModuleDeclaration).body === nodeForStartPos.parent) {
1967                        // Use parent module declarations name for start pos
1968                        nodeForStartPos = (nodeForStartPos.parent.parent as ModuleDeclaration).name;
1969                    }
1970                    else {
1971                        // We have to use this name for start pos
1972                        break;
1973                    }
1974                }
1975                else {
1976                    // Is not a member expression so we have found the node for start pos
1977                    break;
1978                }
1979            }
1980
1981            return createTextSpanFromBounds(nodeForStartPos.getStart(), node.getEnd());
1982        }
1983
1984        function getBreakpointStatementAtPosition(fileName: string, position: number): TextSpan | undefined {
1985            // doesn't use compiler - no need to synchronize with host
1986            const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
1987
1988            return BreakpointResolver.spanInSourceFileAtLocation(sourceFile, position);
1989        }
1990
1991        function getNavigationBarItems(fileName: string): NavigationBarItem[] {
1992            return NavigationBar.getNavigationBarItems(syntaxTreeCache.getCurrentSourceFile(fileName), cancellationToken);
1993        }
1994
1995        function getNavigationTree(fileName: string): NavigationTree {
1996            return NavigationBar.getNavigationTree(syntaxTreeCache.getCurrentSourceFile(fileName), cancellationToken);
1997        }
1998
1999        function getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[];
2000        function getSemanticClassifications(fileName: string, span: TextSpan, format?: SemanticClassificationFormat): ClassifiedSpan[] | ClassifiedSpan2020[] {
2001            synchronizeHostData();
2002
2003            const responseFormat = format || SemanticClassificationFormat.Original;
2004            if (responseFormat === SemanticClassificationFormat.TwentyTwenty) {
2005                return classifier.v2020.getSemanticClassifications(program, cancellationToken, getValidSourceFile(fileName), span);
2006            }
2007            else {
2008                return ts.getSemanticClassifications(program.getTypeChecker(), cancellationToken, getValidSourceFile(fileName), program.getClassifiableNames(), span);
2009            }
2010        }
2011
2012        function getEncodedSemanticClassifications(fileName: string, span: TextSpan, format?: SemanticClassificationFormat): Classifications {
2013            synchronizeHostData();
2014
2015            const responseFormat = format || SemanticClassificationFormat.Original;
2016            if (responseFormat === SemanticClassificationFormat.Original) {
2017                return ts.getEncodedSemanticClassifications(program.getTypeChecker(), cancellationToken, getValidSourceFile(fileName), program.getClassifiableNames(), span);
2018            }
2019            else {
2020                return classifier.v2020.getEncodedSemanticClassifications(program, cancellationToken, getValidSourceFile(fileName), span);
2021            }
2022        }
2023
2024        function getSyntacticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[] {
2025            // doesn't use compiler - no need to synchronize with host
2026            return ts.getSyntacticClassifications(cancellationToken, syntaxTreeCache.getCurrentSourceFile(fileName), span);
2027        }
2028
2029        function getEncodedSyntacticClassifications(fileName: string, span: TextSpan): Classifications {
2030            // doesn't use compiler - no need to synchronize with host
2031            return ts.getEncodedSyntacticClassifications(cancellationToken, syntaxTreeCache.getCurrentSourceFile(fileName), span);
2032        }
2033
2034        function getOutliningSpans(fileName: string): OutliningSpan[] {
2035            // doesn't use compiler - no need to synchronize with host
2036            const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
2037            return OutliningElementsCollector.collectElements(sourceFile, cancellationToken);
2038        }
2039
2040        const braceMatching = new Map(getEntries({
2041            [SyntaxKind.OpenBraceToken]: SyntaxKind.CloseBraceToken,
2042            [SyntaxKind.OpenParenToken]: SyntaxKind.CloseParenToken,
2043            [SyntaxKind.OpenBracketToken]: SyntaxKind.CloseBracketToken,
2044            [SyntaxKind.GreaterThanToken]: SyntaxKind.LessThanToken,
2045        }));
2046        braceMatching.forEach((value, key) => braceMatching.set(value.toString(), Number(key) as SyntaxKind));
2047
2048        function getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] {
2049            const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
2050            const token = getTouchingToken(sourceFile, position);
2051            const matchKind = token.getStart(sourceFile) === position ? braceMatching.get(token.kind.toString()) : undefined;
2052            const match = matchKind && findChildOfKind(token.parent, matchKind, sourceFile);
2053            // We want to order the braces when we return the result.
2054            return match ? [createTextSpanFromNode(token, sourceFile), createTextSpanFromNode(match, sourceFile)].sort((a, b) => a.start - b.start) : emptyArray;
2055        }
2056
2057        function getIndentationAtPosition(fileName: string, position: number, editorOptions: EditorOptions | EditorSettings) {
2058            let start = timestamp();
2059            const settings = toEditorSettings(editorOptions);
2060            const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
2061            log("getIndentationAtPosition: getCurrentSourceFile: " + (timestamp() - start));
2062
2063            start = timestamp();
2064
2065            const result = formatting.SmartIndenter.getIndentation(position, sourceFile, settings);
2066            log("getIndentationAtPosition: computeIndentation  : " + (timestamp() - start));
2067
2068            return result;
2069        }
2070
2071        function getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions | FormatCodeSettings): TextChange[] {
2072            const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
2073            return formatting.formatSelection(start, end, sourceFile, formatting.getFormatContext(toEditorSettings(options), host));
2074        }
2075
2076        function getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[] {
2077            return formatting.formatDocument(syntaxTreeCache.getCurrentSourceFile(fileName), formatting.getFormatContext(toEditorSettings(options), host));
2078        }
2079
2080        function getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[] {
2081            const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
2082            const formatContext = formatting.getFormatContext(toEditorSettings(options), host);
2083
2084            if (!isInComment(sourceFile, position)) {
2085                switch (key) {
2086                    case "{":
2087                        return formatting.formatOnOpeningCurly(position, sourceFile, formatContext);
2088                    case "}":
2089                        return formatting.formatOnClosingCurly(position, sourceFile, formatContext);
2090                    case ";":
2091                        return formatting.formatOnSemicolon(position, sourceFile, formatContext);
2092                    case "\n":
2093                        return formatting.formatOnEnter(position, sourceFile, formatContext);
2094                }
2095            }
2096
2097            return [];
2098        }
2099
2100        function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: readonly number[], formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): readonly CodeFixAction[] {
2101            synchronizeHostData();
2102            const sourceFile = getValidSourceFile(fileName);
2103            const span = createTextSpanFromBounds(start, end);
2104            const formatContext = formatting.getFormatContext(formatOptions, host);
2105
2106            return flatMap(deduplicate<number>(errorCodes, equateValues, compareValues), errorCode => {
2107                cancellationToken.throwIfCancellationRequested();
2108                return codefix.getFixes({ errorCode, sourceFile, span, program, host, cancellationToken, formatContext, preferences });
2109            });
2110        }
2111
2112        function getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): CombinedCodeActions {
2113            synchronizeHostData();
2114            Debug.assert(scope.type === "file");
2115            const sourceFile = getValidSourceFile(scope.fileName);
2116            const formatContext = formatting.getFormatContext(formatOptions, host);
2117
2118            return codefix.getAllFixes({ fixId, sourceFile, program, host, cancellationToken, formatContext, preferences });
2119        }
2120
2121        function organizeImports(args: OrganizeImportsArgs, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): readonly FileTextChanges[] {
2122            synchronizeHostData();
2123            Debug.assert(args.type === "file");
2124            const sourceFile = getValidSourceFile(args.fileName);
2125            const formatContext = formatting.getFormatContext(formatOptions, host);
2126
2127            const mode = args.mode ?? (args.skipDestructiveCodeActions ? OrganizeImportsMode.SortAndCombine : OrganizeImportsMode.All);
2128            return OrganizeImports.organizeImports(sourceFile, formatContext, host, program, preferences, mode);
2129        }
2130
2131        function getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): readonly FileTextChanges[] {
2132            return ts.getEditsForFileRename(getProgram()!, oldFilePath, newFilePath, host, formatting.getFormatContext(formatOptions, host), preferences, sourceMapper);
2133        }
2134
2135        function applyCodeActionCommand(action: CodeActionCommand, formatSettings?: FormatCodeSettings): Promise<ApplyCodeActionCommandResult>;
2136        function applyCodeActionCommand(action: CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise<ApplyCodeActionCommandResult[]>;
2137        function applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise<ApplyCodeActionCommandResult | ApplyCodeActionCommandResult[]>;
2138        function applyCodeActionCommand(fileName: Path, action: CodeActionCommand): Promise<ApplyCodeActionCommandResult>;
2139        function applyCodeActionCommand(fileName: Path, action: CodeActionCommand[]): Promise<ApplyCodeActionCommandResult[]>;
2140        function applyCodeActionCommand(fileName: Path | CodeActionCommand | CodeActionCommand[], actionOrFormatSettingsOrUndefined?: CodeActionCommand | CodeActionCommand[] | FormatCodeSettings): Promise<ApplyCodeActionCommandResult | ApplyCodeActionCommandResult[]> {
2141            const action = typeof fileName === "string" ? actionOrFormatSettingsOrUndefined as CodeActionCommand | CodeActionCommand[] : fileName as CodeActionCommand[];
2142            return isArray(action) ? Promise.all(action.map(a => applySingleCodeActionCommand(a))) : applySingleCodeActionCommand(action);
2143        }
2144
2145        function applySingleCodeActionCommand(action: CodeActionCommand): Promise<ApplyCodeActionCommandResult> {
2146            const getPath = (path: string): Path => toPath(path, currentDirectory, getCanonicalFileName);
2147            Debug.assertEqual(action.type, "install package");
2148            return host.installPackage
2149                ? host.installPackage({ fileName: getPath(action.file), packageName: action.packageName })
2150                : Promise.reject("Host does not implement `installPackage`");
2151        }
2152
2153        function getDocCommentTemplateAtPosition(fileName: string, position: number, options?: DocCommentTemplateOptions): TextInsertion | undefined {
2154            return JsDoc.getDocCommentTemplateAtPosition(getNewLineOrDefaultFromHost(host), syntaxTreeCache.getCurrentSourceFile(fileName), position, options);
2155        }
2156
2157        function isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean {
2158            // '<' is currently not supported, figuring out if we're in a Generic Type vs. a comparison is too
2159            // expensive to do during typing scenarios
2160            // i.e. whether we're dealing with:
2161            //      var x = new foo<| ( with class foo<T>{} )
2162            // or
2163            //      var y = 3 <|
2164            if (openingBrace === CharacterCodes.lessThan) {
2165                return false;
2166            }
2167
2168            const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
2169
2170            // Check if in a context where we don't want to perform any insertion
2171            if (isInString(sourceFile, position)) {
2172                return false;
2173            }
2174
2175            if (isInsideJsxElementOrAttribute(sourceFile, position)) {
2176                return openingBrace === CharacterCodes.openBrace;
2177            }
2178
2179            if (isInTemplateString(sourceFile, position)) {
2180                return false;
2181            }
2182
2183            switch (openingBrace) {
2184                case CharacterCodes.singleQuote:
2185                case CharacterCodes.doubleQuote:
2186                case CharacterCodes.backtick:
2187                    return !isInComment(sourceFile, position);
2188            }
2189
2190            return true;
2191        }
2192
2193        function getJsxClosingTagAtPosition(fileName: string, position: number): JsxClosingTagInfo | undefined {
2194            const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
2195            const token = findPrecedingToken(position, sourceFile);
2196            if (!token) return undefined;
2197            const element = token.kind === SyntaxKind.GreaterThanToken && isJsxOpeningElement(token.parent) ? token.parent.parent
2198                : isJsxText(token) && isJsxElement(token.parent) ? token.parent : undefined;
2199            if (element && isUnclosedTag(element)) {
2200                return { newText: `</${element.openingElement.tagName.getText(sourceFile)}>` };
2201            }
2202            const fragment = token.kind === SyntaxKind.GreaterThanToken && isJsxOpeningFragment(token.parent) ? token.parent.parent
2203                : isJsxText(token) && isJsxFragment(token.parent) ? token.parent : undefined;
2204            if (fragment && isUnclosedFragment(fragment)) {
2205                return { newText: "</>" };
2206            }
2207        }
2208
2209        function getLinesForRange(sourceFile: SourceFile, textRange: TextRange) {
2210            return {
2211                lineStarts: sourceFile.getLineStarts(),
2212                firstLine: sourceFile.getLineAndCharacterOfPosition(textRange.pos).line,
2213                lastLine: sourceFile.getLineAndCharacterOfPosition(textRange.end).line
2214            };
2215        }
2216
2217        function toggleLineComment(fileName: string, textRange: TextRange, insertComment?: boolean): TextChange[] {
2218            const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
2219            const textChanges: TextChange[] = [];
2220            const { lineStarts, firstLine, lastLine } = getLinesForRange(sourceFile, textRange);
2221
2222            let isCommenting = insertComment || false;
2223            let leftMostPosition = Number.MAX_VALUE;
2224            const lineTextStarts = new Map<string, number>();
2225            const firstNonWhitespaceCharacterRegex = new RegExp(/\S/);
2226            const isJsx = isInsideJsxElement(sourceFile, lineStarts[firstLine]);
2227            const openComment = isJsx ? "{/*" : "//";
2228
2229            // Check each line before any text changes.
2230            for (let i = firstLine; i <= lastLine; i++) {
2231                const lineText = sourceFile.text.substring(lineStarts[i], sourceFile.getLineEndOfPosition(lineStarts[i]));
2232
2233                // Find the start of text and the left-most character. No-op on empty lines.
2234                const regExec = firstNonWhitespaceCharacterRegex.exec(lineText);
2235                if (regExec) {
2236                    leftMostPosition = Math.min(leftMostPosition, regExec.index);
2237                    lineTextStarts.set(i.toString(), regExec.index);
2238
2239                    if (lineText.substr(regExec.index, openComment.length) !== openComment) {
2240                        isCommenting = insertComment === undefined || insertComment;
2241                    }
2242                }
2243            }
2244
2245            // Push all text changes.
2246            for (let i = firstLine; i <= lastLine; i++) {
2247                // If the range is multiline and ends on a beginning of a line, don't comment/uncomment.
2248                if (firstLine !== lastLine && lineStarts[i] === textRange.end) {
2249                    continue;
2250                }
2251
2252                const lineTextStart = lineTextStarts.get(i.toString());
2253
2254                // If the line is not an empty line; otherwise no-op.
2255                if (lineTextStart !== undefined) {
2256                    if (isJsx) {
2257                        textChanges.push.apply(textChanges, toggleMultilineComment(fileName, { pos: lineStarts[i] + leftMostPosition, end: sourceFile.getLineEndOfPosition(lineStarts[i]) }, isCommenting, isJsx));
2258                    }
2259                    else if (isCommenting) {
2260                        textChanges.push({
2261                            newText: openComment,
2262                            span: {
2263                                length: 0,
2264                                start: lineStarts[i] + leftMostPosition
2265                            }
2266                        });
2267                    }
2268                    else if (sourceFile.text.substr(lineStarts[i] + lineTextStart, openComment.length) === openComment) {
2269                        textChanges.push({
2270                            newText: "",
2271                            span: {
2272                                length: openComment.length,
2273                                start: lineStarts[i] + lineTextStart
2274                            }
2275                        });
2276                    }
2277                }
2278            }
2279
2280            return textChanges;
2281        }
2282
2283        function toggleMultilineComment(fileName: string, textRange: TextRange, insertComment?: boolean, isInsideJsx?: boolean): TextChange[] {
2284            const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
2285            const textChanges: TextChange[] = [];
2286            const { text } = sourceFile;
2287
2288            let hasComment = false;
2289            let isCommenting = insertComment || false;
2290            const positions = [] as number[] as SortedArray<number>;
2291
2292            let { pos } = textRange;
2293            const isJsx = isInsideJsx !== undefined ? isInsideJsx : isInsideJsxElement(sourceFile, pos);
2294
2295            const openMultiline = isJsx ? "{/*" : "/*";
2296            const closeMultiline = isJsx ? "*/}" : "*/";
2297            const openMultilineRegex = isJsx ? "\\{\\/\\*" : "\\/\\*";
2298            const closeMultilineRegex = isJsx ? "\\*\\/\\}" : "\\*\\/";
2299
2300            // Get all comment positions
2301            while (pos <= textRange.end) {
2302                // Start of comment is considered inside comment.
2303                const offset = text.substr(pos, openMultiline.length) === openMultiline ? openMultiline.length : 0;
2304                const commentRange = isInComment(sourceFile, pos + offset);
2305
2306                // If position is in a comment add it to the positions array.
2307                if (commentRange) {
2308                    // Comment range doesn't include the brace character. Increase it to include them.
2309                    if (isJsx) {
2310                        commentRange.pos--;
2311                        commentRange.end++;
2312                    }
2313
2314                    positions.push(commentRange.pos);
2315                    if (commentRange.kind === SyntaxKind.MultiLineCommentTrivia) {
2316                        positions.push(commentRange.end);
2317                    }
2318
2319                    hasComment = true;
2320                    pos = commentRange.end + 1;
2321                }
2322                else { // If it's not in a comment range, then we need to comment the uncommented portions.
2323                    const newPos = text.substring(pos, textRange.end).search(`(${openMultilineRegex})|(${closeMultilineRegex})`);
2324
2325                    isCommenting = insertComment !== undefined
2326                        ? insertComment
2327                        : isCommenting || !isTextWhiteSpaceLike(text, pos, newPos === -1 ? textRange.end : pos + newPos); // If isCommenting is already true we don't need to check whitespace again.
2328                    pos = newPos === -1 ? textRange.end + 1 : pos + newPos + closeMultiline.length;
2329                }
2330            }
2331
2332            // If it didn't found a comment and isCommenting is false means is only empty space.
2333            // We want to insert comment in this scenario.
2334            if (isCommenting || !hasComment) {
2335                if (isInComment(sourceFile, textRange.pos)?.kind !== SyntaxKind.SingleLineCommentTrivia) {
2336                    insertSorted(positions, textRange.pos, compareValues);
2337                }
2338                insertSorted(positions, textRange.end, compareValues);
2339
2340                // Insert open comment if the first position is not a comment already.
2341                const firstPos = positions[0];
2342                if (text.substr(firstPos, openMultiline.length) !== openMultiline) {
2343                    textChanges.push({
2344                        newText: openMultiline,
2345                        span: {
2346                            length: 0,
2347                            start: firstPos
2348                        }
2349                    });
2350                }
2351
2352                // Insert open and close comment to all positions between first and last. Exclusive.
2353                for (let i = 1; i < positions.length - 1; i++) {
2354                    if (text.substr(positions[i] - closeMultiline.length, closeMultiline.length) !== closeMultiline) {
2355                        textChanges.push({
2356                            newText: closeMultiline,
2357                            span: {
2358                                length: 0,
2359                                start: positions[i]
2360                            }
2361                        });
2362                    }
2363
2364                    if (text.substr(positions[i], openMultiline.length) !== openMultiline) {
2365                        textChanges.push({
2366                            newText: openMultiline,
2367                            span: {
2368                                length: 0,
2369                                start: positions[i]
2370                            }
2371                        });
2372                    }
2373                }
2374
2375                // Insert open comment if the last position is not a comment already.
2376                if (textChanges.length % 2 !== 0) {
2377                    textChanges.push({
2378                        newText: closeMultiline,
2379                        span: {
2380                            length: 0,
2381                            start: positions[positions.length - 1]
2382                        }
2383                    });
2384                }
2385            }
2386            else {
2387                // If is not commenting then remove all comments found.
2388                for (const pos of positions) {
2389                    const from = pos - closeMultiline.length > 0 ? pos - closeMultiline.length : 0;
2390                    const offset = text.substr(from, closeMultiline.length) === closeMultiline ? closeMultiline.length : 0;
2391                    textChanges.push({
2392                        newText: "",
2393                        span: {
2394                            length: openMultiline.length,
2395                            start: pos - offset
2396                        }
2397                    });
2398                }
2399            }
2400
2401            return textChanges;
2402        }
2403
2404        function commentSelection(fileName: string, textRange: TextRange): TextChange[] {
2405            const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
2406            const { firstLine, lastLine } = getLinesForRange(sourceFile, textRange);
2407
2408            // If there is a selection that is on the same line, add multiline.
2409            return firstLine === lastLine && textRange.pos !== textRange.end
2410                ? toggleMultilineComment(fileName, textRange, /*insertComment*/ true)
2411                : toggleLineComment(fileName, textRange, /*insertComment*/ true);
2412        }
2413
2414        function uncommentSelection(fileName: string, textRange: TextRange): TextChange[] {
2415            const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
2416            const textChanges: TextChange[] = [];
2417            const { pos } = textRange;
2418            let { end } = textRange;
2419
2420            // If cursor is not a selection we need to increase the end position
2421            // to include the start of the comment.
2422            if (pos === end) {
2423                end += isInsideJsxElement(sourceFile, pos) ? 2 : 1;
2424            }
2425
2426            for (let i = pos; i <= end; i++) {
2427                const commentRange = isInComment(sourceFile, i);
2428                if (commentRange) {
2429                    switch (commentRange.kind) {
2430                        case SyntaxKind.SingleLineCommentTrivia:
2431                            textChanges.push.apply(textChanges, toggleLineComment(fileName, { end: commentRange.end, pos: commentRange.pos + 1 }, /*insertComment*/ false));
2432                            break;
2433                        case SyntaxKind.MultiLineCommentTrivia:
2434                            textChanges.push.apply(textChanges, toggleMultilineComment(fileName, { end: commentRange.end, pos: commentRange.pos + 1 }, /*insertComment*/ false));
2435                    }
2436
2437                    i = commentRange.end + 1;
2438                }
2439            }
2440
2441            return textChanges;
2442        }
2443
2444        function isUnclosedTag({ openingElement, closingElement, parent }: JsxElement): boolean {
2445            return !tagNamesAreEquivalent(openingElement.tagName, closingElement.tagName) ||
2446                isJsxElement(parent) && tagNamesAreEquivalent(openingElement.tagName, parent.openingElement.tagName) && isUnclosedTag(parent);
2447        }
2448
2449        function isUnclosedFragment({ closingFragment, parent }: JsxFragment): boolean {
2450            return !!(closingFragment.flags & NodeFlags.ThisNodeHasError) || (isJsxFragment(parent) && isUnclosedFragment(parent));
2451        }
2452
2453        function getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan | undefined {
2454            const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
2455            const range = formatting.getRangeOfEnclosingComment(sourceFile, position);
2456            return range && (!onlyMultiLine || range.kind === SyntaxKind.MultiLineCommentTrivia) ? createTextSpanFromRange(range) : undefined;
2457        }
2458
2459        function getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[] {
2460            // Note: while getting todo comments seems like a syntactic operation, we actually
2461            // treat it as a semantic operation here.  This is because we expect our host to call
2462            // this on every single file.  If we treat this syntactically, then that will cause
2463            // us to populate and throw away the tree in our syntax tree cache for each file.  By
2464            // treating this as a semantic operation, we can access any tree without throwing
2465            // anything away.
2466            synchronizeHostData();
2467
2468            const sourceFile = getValidSourceFile(fileName);
2469
2470            cancellationToken.throwIfCancellationRequested();
2471
2472            const fileContents = sourceFile.text;
2473            const result: TodoComment[] = [];
2474
2475            // Exclude node_modules or oh_modules files as we don't want to show the todos of external libraries.
2476            if (descriptors.length > 0 && !isNodeModulesFile(sourceFile.fileName) && !isOHModulesFile(sourceFile.fileName)) {
2477                const regExp = getTodoCommentsRegExp();
2478
2479                let matchArray: RegExpExecArray | null;
2480                while (matchArray = regExp.exec(fileContents)) {
2481                    cancellationToken.throwIfCancellationRequested();
2482
2483                    // If we got a match, here is what the match array will look like.  Say the source text is:
2484                    //
2485                    //      "    // hack   1"
2486                    //
2487                    // The result array with the regexp:    will be:
2488                    //
2489                    //      ["// hack   1", "// ", "hack   1", undefined, "hack"]
2490                    //
2491                    // Here are the relevant capture groups:
2492                    //  0) The full match for the entire regexp.
2493                    //  1) The preamble to the message portion.
2494                    //  2) The message portion.
2495                    //  3...N) The descriptor that was matched - by index.  'undefined' for each
2496                    //         descriptor that didn't match.  an actual value if it did match.
2497                    //
2498                    //  i.e. 'undefined' in position 3 above means TODO(jason) didn't match.
2499                    //       "hack"      in position 4 means HACK did match.
2500                    const firstDescriptorCaptureIndex = 3;
2501                    Debug.assert(matchArray.length === descriptors.length + firstDescriptorCaptureIndex);
2502
2503                    const preamble = matchArray[1];
2504                    const matchPosition = matchArray.index + preamble.length;
2505
2506                    // OK, we have found a match in the file.  This is only an acceptable match if
2507                    // it is contained within a comment.
2508                    if (!isInComment(sourceFile, matchPosition)) {
2509                        continue;
2510                    }
2511
2512                    let descriptor: TodoCommentDescriptor | undefined;
2513                    for (let i = 0; i < descriptors.length; i++) {
2514                        if (matchArray[i + firstDescriptorCaptureIndex]) {
2515                            descriptor = descriptors[i];
2516                        }
2517                    }
2518                    if (descriptor === undefined) return Debug.fail();
2519
2520                    // We don't want to match something like 'TODOBY', so we make sure a non
2521                    // letter/digit follows the match.
2522                    if (isLetterOrDigit(fileContents.charCodeAt(matchPosition + descriptor.text.length))) {
2523                        continue;
2524                    }
2525
2526                    const message = matchArray[2];
2527                    result.push({ descriptor, message, position: matchPosition });
2528                }
2529            }
2530
2531            return result;
2532
2533            function escapeRegExp(str: string): string {
2534                return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
2535            }
2536
2537            function getTodoCommentsRegExp(): RegExp {
2538                // NOTE: `?:` means 'non-capture group'.  It allows us to have groups without having to
2539                // filter them out later in the final result array.
2540
2541                // TODO comments can appear in one of the following forms:
2542                //
2543                //  1)      // TODO     or  /////////// TODO
2544                //
2545                //  2)      /* TODO     or  /********** TODO
2546                //
2547                //  3)      /*
2548                //           *   TODO
2549                //           */
2550                //
2551                // The following three regexps are used to match the start of the text up to the TODO
2552                // comment portion.
2553                const singleLineCommentStart = /(?:\/\/+\s*)/.source;
2554                const multiLineCommentStart = /(?:\/\*+\s*)/.source;
2555                const anyNumberOfSpacesAndAsterisksAtStartOfLine = /(?:^(?:\s|\*)*)/.source;
2556
2557                // Match any of the above three TODO comment start regexps.
2558                // Note that the outermost group *is* a capture group.  We want to capture the preamble
2559                // so that we can determine the starting position of the TODO comment match.
2560                const preamble = "(" + anyNumberOfSpacesAndAsterisksAtStartOfLine + "|" + singleLineCommentStart + "|" + multiLineCommentStart + ")";
2561
2562                // Takes the descriptors and forms a regexp that matches them as if they were literals.
2563                // For example, if the descriptors are "TODO(jason)" and "HACK", then this will be:
2564                //
2565                //      (?:(TODO\(jason\))|(HACK))
2566                //
2567                // Note that the outermost group is *not* a capture group, but the innermost groups
2568                // *are* capture groups.  By capturing the inner literals we can determine after
2569                // matching which descriptor we are dealing with.
2570                const literals = "(?:" + map(descriptors, d => "(" + escapeRegExp(d.text) + ")").join("|") + ")";
2571
2572                // After matching a descriptor literal, the following regexp matches the rest of the
2573                // text up to the end of the line (or */).
2574                const endOfLineOrEndOfComment = /(?:$|\*\/)/.source;
2575                const messageRemainder = /(?:.*?)/.source;
2576
2577                // This is the portion of the match we'll return as part of the TODO comment result. We
2578                // match the literal portion up to the end of the line or end of comment.
2579                const messagePortion = "(" + literals + messageRemainder + ")";
2580                const regExpString = preamble + messagePortion + endOfLineOrEndOfComment;
2581
2582                // The final regexp will look like this:
2583                // /((?:\/\/+\s*)|(?:\/\*+\s*)|(?:^(?:\s|\*)*))((?:(TODO\(jason\))|(HACK))(?:.*?))(?:$|\*\/)/gim
2584
2585                // The flags of the regexp are important here.
2586                //  'g' is so that we are doing a global search and can find matches several times
2587                //  in the input.
2588                //
2589                //  'i' is for case insensitivity (We do this to match C# TODO comment code).
2590                //
2591                //  'm' is so we can find matches in a multi-line input.
2592                return new RegExp(regExpString, "gim");
2593            }
2594
2595            function isLetterOrDigit(char: number): boolean {
2596                return (char >= CharacterCodes.a && char <= CharacterCodes.z) ||
2597                    (char >= CharacterCodes.A && char <= CharacterCodes.Z) ||
2598                    (char >= CharacterCodes._0 && char <= CharacterCodes._9);
2599            }
2600
2601            function isNodeModulesFile(path: string): boolean {
2602                return stringContains(path, "/node_modules/");
2603            }
2604
2605            function isOHModulesFile(path: string): boolean {
2606                return stringContains(path, "/oh_modules/");
2607            }
2608        }
2609
2610        function getRenameInfo(fileName: string, position: number, preferences: UserPreferences | RenameInfoOptions | undefined): RenameInfo {
2611            synchronizeHostData();
2612            return Rename.getRenameInfo(program, getValidSourceFile(fileName), position, preferences || {});
2613        }
2614
2615        function getRefactorContext(file: SourceFile, positionOrRange: number | TextRange, preferences: UserPreferences, formatOptions?: FormatCodeSettings, triggerReason?: RefactorTriggerReason, kind?: string): RefactorContext {
2616            const [startPosition, endPosition] = typeof positionOrRange === "number" ? [positionOrRange, undefined] : [positionOrRange.pos, positionOrRange.end];
2617            return {
2618                file,
2619                startPosition,
2620                endPosition,
2621                program: getProgram()!,
2622                host,
2623                formatContext: formatting.getFormatContext(formatOptions!, host), // TODO: GH#18217
2624                cancellationToken,
2625                preferences,
2626                triggerReason,
2627                kind
2628            };
2629        }
2630
2631        function getInlayHintsContext(file: SourceFile, span: TextSpan, preferences: UserPreferences): InlayHintsContext {
2632            return {
2633                file,
2634                program: getProgram()!,
2635                host,
2636                span,
2637                preferences,
2638                cancellationToken,
2639            };
2640        }
2641
2642        function getSmartSelectionRange(fileName: string, position: number): SelectionRange {
2643            return SmartSelectionRange.getSmartSelectionRange(position, syntaxTreeCache.getCurrentSourceFile(fileName));
2644        }
2645
2646        function getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences = emptyOptions, triggerReason: RefactorTriggerReason, kind: string): ApplicableRefactorInfo[] {
2647            synchronizeHostData();
2648            const file = getValidSourceFile(fileName);
2649            return refactor.getApplicableRefactors(getRefactorContext(file, positionOrRange, preferences, emptyOptions, triggerReason, kind));
2650        }
2651
2652        function getEditsForRefactor(
2653            fileName: string,
2654            formatOptions: FormatCodeSettings,
2655            positionOrRange: number | TextRange,
2656            refactorName: string,
2657            actionName: string,
2658            preferences: UserPreferences = emptyOptions,
2659        ): RefactorEditInfo | undefined {
2660            synchronizeHostData();
2661            const file = getValidSourceFile(fileName);
2662            return refactor.getEditsForRefactor(getRefactorContext(file, positionOrRange, preferences, formatOptions), refactorName, actionName);
2663        }
2664
2665        function toLineColumnOffset(fileName: string, position: number): LineAndCharacter {
2666            // Go to Definition supports returning a zero-length span at position 0 for
2667            // non-existent files. We need to special-case the conversion of position 0
2668            // to avoid a crash trying to get the text for that file, since this function
2669            // otherwise assumes that 'fileName' is the name of a file that exists.
2670            if (position === 0) {
2671                return { line: 0, character: 0 };
2672            }
2673            return sourceMapper.toLineColumnOffset(fileName, position);
2674        }
2675
2676        function prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined {
2677            synchronizeHostData();
2678            const declarations = CallHierarchy.resolveCallHierarchyDeclaration(program, getTouchingPropertyName(getValidSourceFile(fileName), position));
2679            return declarations && mapOneOrMany(declarations, declaration => CallHierarchy.createCallHierarchyItem(program, declaration));
2680        }
2681
2682        function provideCallHierarchyIncomingCalls(fileName: string, position: number): CallHierarchyIncomingCall[] {
2683            synchronizeHostData();
2684            const sourceFile = getValidSourceFile(fileName);
2685            const declaration = firstOrOnly(CallHierarchy.resolveCallHierarchyDeclaration(program, position === 0 ? sourceFile : getTouchingPropertyName(sourceFile, position)));
2686            return declaration ? CallHierarchy.getIncomingCalls(program, declaration, cancellationToken) : [];
2687        }
2688
2689        function provideCallHierarchyOutgoingCalls(fileName: string, position: number): CallHierarchyOutgoingCall[] {
2690            synchronizeHostData();
2691            const sourceFile = getValidSourceFile(fileName);
2692            const declaration = firstOrOnly(CallHierarchy.resolveCallHierarchyDeclaration(program, position === 0 ? sourceFile : getTouchingPropertyName(sourceFile, position)));
2693            return declaration ? CallHierarchy.getOutgoingCalls(program, declaration) : [];
2694        }
2695
2696        function provideInlayHints(fileName: string, span: TextSpan, preferences: UserPreferences = emptyOptions): InlayHint[] {
2697            synchronizeHostData();
2698            const sourceFile = getValidSourceFile(fileName);
2699            return InlayHints.provideInlayHints(getInlayHintsContext(sourceFile, span, preferences));
2700        }
2701
2702        function updateRootFiles(rootFiles: string[]) {
2703            host.getScriptFileNames = () => rootFiles
2704        }
2705
2706        function getProps(): string[] {
2707            return host.uiProps ? host.uiProps : [];
2708        }
2709
2710        const ls: LanguageService = {
2711            dispose,
2712            cleanupSemanticCache,
2713            getSyntacticDiagnostics,
2714            getSemanticDiagnostics,
2715            getSuggestionDiagnostics,
2716            getCompilerOptionsDiagnostics,
2717            getSyntacticClassifications,
2718            getSemanticClassifications,
2719            getEncodedSyntacticClassifications,
2720            getEncodedSemanticClassifications,
2721            getCompletionsAtPosition,
2722            getCompletionEntryDetails,
2723            getCompletionEntrySymbol,
2724            getSignatureHelpItems,
2725            getQuickInfoAtPosition,
2726            getDefinitionAtPosition,
2727            getDefinitionAndBoundSpan,
2728            getImplementationAtPosition,
2729            getTypeDefinitionAtPosition,
2730            getReferencesAtPosition,
2731            findReferences,
2732            getFileReferences,
2733            getOccurrencesAtPosition,
2734            getDocumentHighlights,
2735            getNameOrDottedNameSpan,
2736            getBreakpointStatementAtPosition,
2737            getNavigateToItems,
2738            getRenameInfo,
2739            getSmartSelectionRange,
2740            findRenameLocations,
2741            getNavigationBarItems,
2742            getNavigationTree,
2743            getOutliningSpans,
2744            getTodoComments,
2745            getBraceMatchingAtPosition,
2746            getIndentationAtPosition,
2747            getFormattingEditsForRange,
2748            getFormattingEditsForDocument,
2749            getFormattingEditsAfterKeystroke,
2750            getDocCommentTemplateAtPosition,
2751            isValidBraceCompletionAtPosition,
2752            getJsxClosingTagAtPosition,
2753            getSpanOfEnclosingComment,
2754            getCodeFixesAtPosition,
2755            getCombinedCodeFix,
2756            applyCodeActionCommand,
2757            organizeImports,
2758            getEditsForFileRename,
2759            getEmitOutput,
2760            getNonBoundSourceFile,
2761            getProgram,
2762            getBuilderProgram,
2763            getCurrentProgram: () => program,
2764            getAutoImportProvider,
2765            updateIsDefinitionOfReferencedSymbols,
2766            getApplicableRefactors,
2767            getEditsForRefactor,
2768            toLineColumnOffset,
2769            getSourceMapper: () => sourceMapper,
2770            clearSourceMapperCache: () => sourceMapper.clearCache(),
2771            prepareCallHierarchy,
2772            provideCallHierarchyIncomingCalls,
2773            provideCallHierarchyOutgoingCalls,
2774            toggleLineComment,
2775            toggleMultilineComment,
2776            commentSelection,
2777            uncommentSelection,
2778            provideInlayHints,
2779            updateRootFiles,
2780            getProps
2781        };
2782
2783        switch (languageServiceMode) {
2784            case LanguageServiceMode.Semantic:
2785                break;
2786            case LanguageServiceMode.PartialSemantic:
2787                invalidOperationsInPartialSemanticMode.forEach(key =>
2788                    ls[key] = () => {
2789                        throw new Error(`LanguageService Operation: ${key} not allowed in LanguageServiceMode.PartialSemantic`);
2790                    }
2791                );
2792                break;
2793            case LanguageServiceMode.Syntactic:
2794                invalidOperationsInSyntacticMode.forEach(key =>
2795                    ls[key] = () => {
2796                        throw new Error(`LanguageService Operation: ${key} not allowed in LanguageServiceMode.Syntactic`);
2797                    }
2798                );
2799                break;
2800            default:
2801                Debug.assertNever(languageServiceMode);
2802        }
2803        return ls;
2804    }
2805
2806    /* @internal */
2807    /** Names in the name table are escaped, so an identifier `__foo` will have a name table entry `___foo`. */
2808    export function getNameTable(sourceFile: SourceFile): UnderscoreEscapedMap<number> {
2809        if (!sourceFile.nameTable) {
2810            initializeNameTable(sourceFile);
2811        }
2812
2813        return sourceFile.nameTable!; // TODO: GH#18217
2814    }
2815
2816    function initializeNameTable(sourceFile: SourceFile): void {
2817        const nameTable = sourceFile.nameTable = new Map();
2818        sourceFile.forEachChild(function walk(node) {
2819            if (isIdentifier(node) && !isTagName(node) && node.escapedText || isStringOrNumericLiteralLike(node) && literalIsName(node)) {
2820                const text = getEscapedTextOfIdentifierOrLiteral(node);
2821                nameTable.set(text, nameTable.get(text) === undefined ? node.pos : -1);
2822            }
2823            else if (isPrivateIdentifier(node)) {
2824                const text = node.escapedText;
2825                nameTable.set(text, nameTable.get(text) === undefined ? node.pos : -1);
2826            }
2827
2828            forEachChild(node, walk);
2829            if (hasJSDocNodes(node)) {
2830                for (const jsDoc of node.jsDoc!) {
2831                    forEachChild(jsDoc, walk);
2832                }
2833            }
2834        });
2835    }
2836
2837    /**
2838     * We want to store any numbers/strings if they were a name that could be
2839     * related to a declaration.  So, if we have 'import x = require("something")'
2840     * then we want 'something' to be in the name table.  Similarly, if we have
2841     * "a['propname']" then we want to store "propname" in the name table.
2842     */
2843    function literalIsName(node: StringLiteralLike | NumericLiteral): boolean {
2844        return isDeclarationName(node) ||
2845            node.parent.kind === SyntaxKind.ExternalModuleReference ||
2846            isArgumentOfElementAccessExpression(node) ||
2847            isLiteralComputedPropertyDeclarationName(node);
2848    }
2849
2850    /**
2851     * Returns the containing object literal property declaration given a possible name node, e.g. "a" in x = { "a": 1 }
2852     */
2853    /* @internal */
2854    export function getContainingObjectLiteralElement(node: Node): ObjectLiteralElementWithName | undefined {
2855        const element = getContainingObjectLiteralElementWorker(node);
2856        return element && (isObjectLiteralExpression(element.parent) || isJsxAttributes(element.parent)) ? element as ObjectLiteralElementWithName : undefined;
2857    }
2858    function getContainingObjectLiteralElementWorker(node: Node): ObjectLiteralElement | undefined {
2859        switch (node.kind) {
2860            case SyntaxKind.StringLiteral:
2861            case SyntaxKind.NoSubstitutionTemplateLiteral:
2862            case SyntaxKind.NumericLiteral:
2863                if (node.parent.kind === SyntaxKind.ComputedPropertyName) {
2864                    return isObjectLiteralElement(node.parent.parent) ? node.parent.parent : undefined;
2865                }
2866            // falls through
2867
2868            case SyntaxKind.Identifier:
2869                return isObjectLiteralElement(node.parent) &&
2870                    (node.parent.parent.kind === SyntaxKind.ObjectLiteralExpression || node.parent.parent.kind === SyntaxKind.JsxAttributes) &&
2871                    node.parent.name === node ? node.parent : undefined;
2872        }
2873        return undefined;
2874    }
2875
2876    /* @internal */
2877    export type ObjectLiteralElementWithName = ObjectLiteralElement & { name: PropertyName; parent: ObjectLiteralExpression | JsxAttributes };
2878
2879    function getSymbolAtLocationForQuickInfo(node: Node, checker: TypeChecker): Symbol | undefined {
2880        const object = getContainingObjectLiteralElement(node);
2881        if (object) {
2882            const contextualType = checker.getContextualType(object.parent);
2883            const properties = contextualType && getPropertySymbolsFromContextualType(object, checker, contextualType, /*unionSymbolOk*/ false);
2884            if (properties && properties.length === 1) {
2885                return first(properties);
2886            }
2887        }
2888        return checker.getSymbolAtLocation(node);
2889    }
2890
2891    /** Gets all symbols for one property. Does not get symbols for every property. */
2892    /* @internal */
2893    export function getPropertySymbolsFromContextualType(node: ObjectLiteralElementWithName, checker: TypeChecker, contextualType: Type, unionSymbolOk: boolean): readonly Symbol[] {
2894        const name = getNameFromPropertyName(node.name);
2895        if (!name) return emptyArray;
2896        if (!contextualType.isUnion()) {
2897            const symbol = contextualType.getProperty(name);
2898            return symbol ? [symbol] : emptyArray;
2899        }
2900
2901        const discriminatedPropertySymbols = mapDefined(contextualType.types, t => (isObjectLiteralExpression(node.parent)|| isJsxAttributes(node.parent)) && checker.isTypeInvalidDueToUnionDiscriminant(t, node.parent) ? undefined : t.getProperty(name));
2902        if (unionSymbolOk && (discriminatedPropertySymbols.length === 0 || discriminatedPropertySymbols.length === contextualType.types.length)) {
2903            const symbol = contextualType.getProperty(name);
2904            if (symbol) return [symbol];
2905        }
2906        if (discriminatedPropertySymbols.length === 0) {
2907            // Bad discriminant -- do again without discriminating
2908            return mapDefined(contextualType.types, t => t.getProperty(name));
2909        }
2910        return discriminatedPropertySymbols;
2911    }
2912
2913    function isArgumentOfElementAccessExpression(node: Node) {
2914        return node &&
2915            node.parent &&
2916            node.parent.kind === SyntaxKind.ElementAccessExpression &&
2917            (node.parent as ElementAccessExpression).argumentExpression === node;
2918    }
2919
2920    /// getDefaultLibraryFilePath
2921    declare const __dirname: string;
2922
2923    /**
2924     * Get the path of the default library files (lib.d.ts) as distributed with the typescript
2925     * node package.
2926     * The functionality is not supported if the ts module is consumed outside of a node module.
2927     */
2928    export function getDefaultLibFilePath(options: CompilerOptions): string {
2929        // Check __dirname is defined and that we are on a node.js system.
2930        if (typeof __dirname !== "undefined") {
2931            return combinePaths(__dirname, getDefaultLibFileName(options));
2932        }
2933
2934        throw new Error("getDefaultLibFilePath is only supported when consumed as a node module. ");
2935    }
2936
2937    setObjectAllocator(getServicesObjectAllocator());
2938}
2939