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