namespace ts {
    /** The version of the language service API */
    export const servicesVersion = "0.8";

    function createNode<TKind extends SyntaxKind>(kind: TKind, pos: number, end: number, parent: Node): NodeObject | TokenObject<TKind> | IdentifierObject | PrivateIdentifierObject {
        const node = isNodeKind(kind) ? new NodeObject(kind, pos, end) :
            kind === SyntaxKind.Identifier ? new IdentifierObject(SyntaxKind.Identifier, pos, end) :
            kind === SyntaxKind.PrivateIdentifier ? new PrivateIdentifierObject(SyntaxKind.PrivateIdentifier, pos, end) :
                new TokenObject(kind, pos, end);
        node.parent = parent;
        node.flags = parent.flags & NodeFlags.ContextFlags;
        return node;
    }

    class NodeObject implements Node {
        public kind: SyntaxKind;
        public pos: number;
        public end: number;
        public flags: NodeFlags;
        public modifierFlagsCache: ModifierFlags;
        public transformFlags: TransformFlags;
        public parent: Node;
        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
        public jsDoc?: JSDoc[];
        public original?: Node;
        private _children: Node[] | undefined;

        constructor(kind: SyntaxKind, pos: number, end: number) {
            this.pos = pos;
            this.end = end;
            this.flags = NodeFlags.None;
            this.modifierFlagsCache = ModifierFlags.None;
            this.transformFlags = TransformFlags.None;
            this.parent = undefined!;
            this.kind = kind;
        }

        private assertHasRealPosition(message?: string) {
            // eslint-disable-next-line debug-assert
            Debug.assert(!positionIsSynthesized(this.pos) && !positionIsSynthesized(this.end), message || "Node must have a real position for this operation");
        }

        public getSourceFile(): SourceFile {
            return getSourceFileOfNode(this);
        }

        public getStart(sourceFile?: SourceFileLike, includeJsDocComment?: boolean): number {
            this.assertHasRealPosition();
            return getTokenPosOfNode(this, sourceFile, includeJsDocComment);
        }

        public getFullStart(): number {
            this.assertHasRealPosition();
            return this.pos;
        }

        public getEnd(): number {
            this.assertHasRealPosition();
            return this.end;
        }

        public getWidth(sourceFile?: SourceFile): number {
            this.assertHasRealPosition();
            return this.getEnd() - this.getStart(sourceFile);
        }

        public getFullWidth(): number {
            this.assertHasRealPosition();
            return this.end - this.pos;
        }

        public getLeadingTriviaWidth(sourceFile?: SourceFile): number {
            this.assertHasRealPosition();
            return this.getStart(sourceFile) - this.pos;
        }

        public getFullText(sourceFile?: SourceFile): string {
            this.assertHasRealPosition();
            return (sourceFile || this.getSourceFile()).text.substring(this.pos, this.end);
        }

        public getText(sourceFile?: SourceFile): string {
            this.assertHasRealPosition();
            if (!sourceFile) {
                sourceFile = this.getSourceFile();
            }
            return sourceFile.text.substring(this.getStart(sourceFile), this.getEnd());
        }

        public getChildCount(sourceFile?: SourceFile): number {
            return this.getChildren(sourceFile).length;
        }

        public getChildAt(index: number, sourceFile?: SourceFile): Node {
            return this.getChildren(sourceFile)[index];
        }

        public getChildren(sourceFile?: SourceFileLike): Node[] {
            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");
            return this._children || (this._children = createChildren(this, sourceFile));
        }

        public getFirstToken(sourceFile?: SourceFileLike): Node | undefined {
            this.assertHasRealPosition();
            const children = this.getChildren(sourceFile);
            if (!children.length) {
                return undefined;
            }

            const child = find(children, kid => kid.kind < SyntaxKind.FirstJSDocNode || kid.kind > SyntaxKind.LastJSDocNode)!;
            return child.kind < SyntaxKind.FirstNode ?
                child :
                child.getFirstToken(sourceFile);
        }

        public getLastToken(sourceFile?: SourceFileLike): Node | undefined {
            this.assertHasRealPosition();
            const children = this.getChildren(sourceFile);

            const child = lastOrUndefined(children);
            if (!child) {
                return undefined;
            }

            return child.kind < SyntaxKind.FirstNode ? child : child.getLastToken(sourceFile);
        }

        public forEachChild<T>(cbNode: (node: Node) => T, cbNodeArray?: (nodes: NodeArray<Node>) => T): T | undefined {
            return forEachChild(this, cbNode, cbNodeArray);
        }
    }

    function createChildren(node: Node, sourceFile: SourceFileLike | undefined): Node[] {
        if (!isNodeKind(node.kind)) {
            return emptyArray;
        }

        const children: Node[] = [];

        if (isJSDocCommentContainingNode(node)) {
            /** Don't add trivia for "tokens" since this is in a comment. */
            node.forEachChild(child => { children.push(child); });
            return children;
        }

        scanner.setText((sourceFile || node.getSourceFile()).text);
        let pos = node.pos;
        const processNode = (child: Node) => {
            addSyntheticNodes(children, pos, child.pos, node);
            children.push(child);
            pos = child.end;
        };
        const processNodes = (nodes: NodeArray<Node>) => {
            addSyntheticNodes(children, pos, nodes.pos, node);
            children.push(createSyntaxList(nodes, node));
            pos = nodes.end;
        };
        // jsDocComments need to be the first children
        forEach((node as JSDocContainer).jsDoc, processNode);
        // For syntactic classifications, all trivia are classified together, including jsdoc comments.
        // For that to work, the jsdoc comments should still be the leading trivia of the first child.
        // Restoring the scanner position ensures that.
        pos = node.pos;
        node.forEachChild(processNode, processNodes);
        addSyntheticNodes(children, pos, node.end, node);
        scanner.setText(undefined);
        return children;
    }

    function addSyntheticNodes(nodes: Push<Node>, pos: number, end: number, parent: Node): void {
        scanner.setTextPos(pos);
        while (pos < end) {
            const token = scanner.scan();
            const textPos = scanner.getTextPos();
            if (textPos <= end) {
                if (token === SyntaxKind.Identifier) {
                    Debug.fail(`Did not expect ${Debug.formatSyntaxKind(parent.kind)} to have an Identifier in its trivia`);
                }
                nodes.push(createNode(token, pos, textPos, parent));
            }
            pos = textPos;
            if (token === SyntaxKind.EndOfFileToken) {
                break;
            }
        }
    }

    function createSyntaxList(nodes: NodeArray<Node>, parent: Node): Node {
        const list = createNode(SyntaxKind.SyntaxList, nodes.pos, nodes.end, parent) as any as SyntaxList;
        list._children = [];
        let pos = nodes.pos;
        for (const node of nodes) {
            addSyntheticNodes(list._children, pos, node.pos, parent);
            list._children.push(node);
            pos = node.end;
        }
        addSyntheticNodes(list._children, pos, nodes.end, parent);
        return list;
    }

    class TokenOrIdentifierObject implements Node {
        public kind!: SyntaxKind;
        public pos: number;
        public end: number;
        public flags: NodeFlags;
        public modifierFlagsCache: ModifierFlags;
        public transformFlags: TransformFlags;
        public parent: Node;
        public symbol!: Symbol;
        public jsDocComments?: JSDoc[];

        constructor(pos: number, end: number) {
            // Set properties in same order as NodeObject
            this.pos = pos;
            this.end = end;
            this.flags = NodeFlags.None;
            this.modifierFlagsCache = ModifierFlags.None;
            this.transformFlags = TransformFlags.None;
            this.parent = undefined!;
        }

        public getSourceFile(): SourceFile {
            return getSourceFileOfNode(this);
        }

        public getStart(sourceFile?: SourceFileLike, includeJsDocComment?: boolean): number {
            return getTokenPosOfNode(this, sourceFile, includeJsDocComment);
        }

        public getFullStart(): number {
            return this.pos;
        }

        public getEnd(): number {
            return this.end;
        }

        public getWidth(sourceFile?: SourceFile): number {
            return this.getEnd() - this.getStart(sourceFile);
        }

        public getFullWidth(): number {
            return this.end - this.pos;
        }

        public getLeadingTriviaWidth(sourceFile?: SourceFile): number {
            return this.getStart(sourceFile) - this.pos;
        }

        public getFullText(sourceFile?: SourceFile): string {
            return (sourceFile || this.getSourceFile()).text.substring(this.pos, this.end);
        }

        public getText(sourceFile?: SourceFile): string {
            if (!sourceFile) {
                sourceFile = this.getSourceFile();
            }
            return sourceFile.text.substring(this.getStart(sourceFile), this.getEnd());
        }

        public getChildCount(): number {
            return 0;
        }

        public getChildAt(): Node {
            return undefined!;  // TODO: GH#18217
        }

        public getChildren(): Node[] {
            return this.kind === SyntaxKind.EndOfFileToken ? (this as EndOfFileToken).jsDoc || emptyArray : emptyArray;
        }

        public getFirstToken(): Node | undefined {
            return undefined;
        }

        public getLastToken(): Node | undefined {
            return undefined;
        }

        public forEachChild<T>(): T | undefined {
            return undefined;
        }
    }

    class SymbolObject implements Symbol {
        flags: SymbolFlags;
        escapedName: __String;
        declarations!: Declaration[];
        valueDeclaration!: Declaration;

        // Undefined is used to indicate the value has not been computed. If, after computing, the
        // symbol has no doc comment, then the empty array will be returned.
        documentationComment?: SymbolDisplayPart[];

        // Undefined is used to indicate the value has not been computed. If, after computing, the
        // symbol has no JSDoc tags, then the empty array will be returned.
        tags?: JSDocTagInfo[];

        constructor(flags: SymbolFlags, name: __String) {
            this.flags = flags;
            this.escapedName = name;
        }

        getFlags(): SymbolFlags {
            return this.flags;
        }

        get name(): string {
            return symbolName(this);
        }

        getEscapedName(): __String {
            return this.escapedName;
        }

        getName(): string {
            return this.name;
        }

        getDeclarations(): Declaration[] | undefined {
            return this.declarations;
        }

        getDocumentationComment(checker: TypeChecker | undefined): SymbolDisplayPart[] {
            if (!this.documentationComment) {
                this.documentationComment = emptyArray; // Set temporarily to avoid an infinite loop finding inherited docs
                this.documentationComment = getDocumentationComment(this.declarations, checker);
            }
            return this.documentationComment;
        }

        getJsDocTags(): JSDocTagInfo[] {
            if (this.tags === undefined) {
                this.tags = JsDoc.getJsDocTagsFromDeclarations(this.declarations);
            }

            return this.tags;
        }
    }

    class TokenObject<TKind extends SyntaxKind> extends TokenOrIdentifierObject implements Token<TKind> {
        public kind: TKind;

        constructor(kind: TKind, pos: number, end: number) {
            super(pos, end);
            this.kind = kind;
        }
    }

    class IdentifierObject extends TokenOrIdentifierObject implements Identifier {
        public kind: SyntaxKind.Identifier = SyntaxKind.Identifier;
        public escapedText!: __String;
        public autoGenerateFlags!: GeneratedIdentifierFlags;
        _primaryExpressionBrand: any;
        _memberExpressionBrand: any;
        _leftHandSideExpressionBrand: any;
        _updateExpressionBrand: any;
        _unaryExpressionBrand: any;
        _expressionBrand: any;
        _declarationBrand: any;
        /*@internal*/typeArguments!: NodeArray<TypeNode>;
        constructor(_kind: SyntaxKind.Identifier, pos: number, end: number) {
            super(pos, end);
        }

        get text(): string {
            return idText(this);
        }
    }
    IdentifierObject.prototype.kind = SyntaxKind.Identifier;
    class PrivateIdentifierObject extends TokenOrIdentifierObject implements PrivateIdentifier {
        public kind!: SyntaxKind.PrivateIdentifier;
        public escapedText!: __String;
        public symbol!: Symbol;
        constructor(_kind: SyntaxKind.PrivateIdentifier, pos: number, end: number) {
            super(pos, end);
        }

        get text(): string {
            return idText(this);
        }
    }
    PrivateIdentifierObject.prototype.kind = SyntaxKind.PrivateIdentifier;

    class TypeObject implements Type {
        checker: TypeChecker;
        flags: TypeFlags;
        objectFlags?: ObjectFlags;
        id!: number;
        symbol!: Symbol;
        constructor(checker: TypeChecker, flags: TypeFlags) {
            this.checker = checker;
            this.flags = flags;
        }
        getFlags(): TypeFlags {
            return this.flags;
        }
        getSymbol(): Symbol | undefined {
            return this.symbol;
        }
        getProperties(): Symbol[] {
            return this.checker.getPropertiesOfType(this);
        }
        getProperty(propertyName: string): Symbol | undefined {
            return this.checker.getPropertyOfType(this, propertyName);
        }
        getApparentProperties(): Symbol[] {
            return this.checker.getAugmentedPropertiesOfType(this);
        }
        getCallSignatures(): readonly Signature[] {
            return this.checker.getSignaturesOfType(this, SignatureKind.Call);
        }
        getConstructSignatures(): readonly Signature[] {
            return this.checker.getSignaturesOfType(this, SignatureKind.Construct);
        }
        getStringIndexType(): Type | undefined {
            return this.checker.getIndexTypeOfType(this, IndexKind.String);
        }
        getNumberIndexType(): Type | undefined {
            return this.checker.getIndexTypeOfType(this, IndexKind.Number);
        }
        getBaseTypes(): BaseType[] | undefined {
            return this.isClassOrInterface() ? this.checker.getBaseTypes(this) : undefined;
        }
        isNullableType(): boolean {
            return this.checker.isNullableType(this);
        }
        getNonNullableType(): Type {
            return this.checker.getNonNullableType(this);
        }
        getNonOptionalType(): Type {
            return this.checker.getNonOptionalType(this);
        }
        getConstraint(): Type | undefined {
            return this.checker.getBaseConstraintOfType(this);
        }
        getDefault(): Type | undefined {
            return this.checker.getDefaultFromTypeParameter(this);
        }

        isUnion(): this is UnionType {
            return !!(this.flags & TypeFlags.Union);
        }
        isIntersection(): this is IntersectionType {
            return !!(this.flags & TypeFlags.Intersection);
        }
        isUnionOrIntersection(): this is UnionOrIntersectionType {
            return !!(this.flags & TypeFlags.UnionOrIntersection);
        }
        isLiteral(): this is LiteralType {
            return !!(this.flags & TypeFlags.StringOrNumberLiteral);
        }
        isStringLiteral(): this is StringLiteralType {
            return !!(this.flags & TypeFlags.StringLiteral);
        }
        isNumberLiteral(): this is NumberLiteralType {
            return !!(this.flags & TypeFlags.NumberLiteral);
        }
        isTypeParameter(): this is TypeParameter {
            return !!(this.flags & TypeFlags.TypeParameter);
        }
        isClassOrInterface(): this is InterfaceType {
            return !!(getObjectFlags(this) & ObjectFlags.ClassOrInterface);
        }
        isClass(): this is InterfaceType {
            return !!(getObjectFlags(this) & ObjectFlags.Class);
        }
        /**
         * This polyfills `referenceType.typeArguments` for API consumers
         */
        get typeArguments() {
            if (getObjectFlags(this) & ObjectFlags.Reference) {
                return this.checker.getTypeArguments(this as Type as TypeReference);
            }
            return undefined;
        }
    }

    class SignatureObject implements Signature {
        flags: SignatureFlags;
        checker: TypeChecker;
        declaration!: SignatureDeclaration;
        typeParameters?: TypeParameter[];
        parameters!: Symbol[];
        thisParameter!: Symbol;
        resolvedReturnType!: Type;
        resolvedTypePredicate: TypePredicate | undefined;
        minTypeArgumentCount!: number;
        minArgumentCount!: number;

        // Undefined is used to indicate the value has not been computed. If, after computing, the
        // symbol has no doc comment, then the empty array will be returned.
        documentationComment?: SymbolDisplayPart[];

        // Undefined is used to indicate the value has not been computed. If, after computing, the
        // symbol has no doc comment, then the empty array will be returned.
        jsDocTags?: JSDocTagInfo[];

        constructor(checker: TypeChecker, flags: SignatureFlags) {
            this.checker = checker;
            this.flags = flags;
        }

        getDeclaration(): SignatureDeclaration {
            return this.declaration;
        }
        getTypeParameters(): TypeParameter[] | undefined {
            return this.typeParameters;
        }
        getParameters(): Symbol[] {
            return this.parameters;
        }
        getReturnType(): Type {
            return this.checker.getReturnTypeOfSignature(this);
        }

        getDocumentationComment(): SymbolDisplayPart[] {
            return this.documentationComment || (this.documentationComment = getDocumentationComment(singleElementArray(this.declaration), this.checker));
        }

        getJsDocTags(): JSDocTagInfo[] {
            if (this.jsDocTags === undefined) {
                this.jsDocTags = this.declaration ? JsDoc.getJsDocTagsFromDeclarations([this.declaration]) : [];
            }

            return this.jsDocTags;
        }
    }

    /**
     * Returns whether or not the given node has a JSDoc "inheritDoc" tag on it.
     * @param node the Node in question.
     * @returns `true` if `node` has a JSDoc "inheritDoc" tag on it, otherwise `false`.
     */
    function hasJSDocInheritDocTag(node: Node) {
        return getJSDocTags(node).some(tag => tag.tagName.text === "inheritDoc");
    }

    function getDocumentationComment(declarations: readonly Declaration[] | undefined, checker: TypeChecker | undefined): SymbolDisplayPart[] {
        if (!declarations) return emptyArray;

        let doc = JsDoc.getJsDocCommentsFromDeclarations(declarations);
        if (doc.length === 0 || declarations.some(hasJSDocInheritDocTag)) {
            forEachUnique(declarations, declaration => {
                const inheritedDocs = findInheritedJSDocComments(declaration, declaration.symbol.name, checker!); // TODO: GH#18217
                // TODO: GH#16312 Return a ReadonlyArray, avoid copying inheritedDocs
                if (inheritedDocs) doc = doc.length === 0 ? inheritedDocs.slice() : inheritedDocs.concat(lineBreakPart(), doc);
            });
        }
        return doc;
    }

    /**
     * Attempts to find JSDoc comments for possibly-inherited properties.  Checks superclasses then traverses
     * implemented interfaces until a symbol is found with the same name and with documentation.
     * @param declaration The possibly-inherited declaration to find comments for.
     * @param propertyName The name of the possibly-inherited property.
     * @param typeChecker A TypeChecker, used to find inherited properties.
     * @returns A filled array of documentation comments if any were found, otherwise an empty array.
     */
    function findInheritedJSDocComments(declaration: Declaration, propertyName: string, typeChecker: TypeChecker): readonly SymbolDisplayPart[] | undefined {
        return firstDefined(declaration.parent ? getAllSuperTypeNodes(declaration.parent) : emptyArray, superTypeNode => {
            const superType = typeChecker.getTypeAtLocation(superTypeNode);
            const baseProperty = superType && typeChecker.getPropertyOfType(superType, propertyName);
            const inheritedDocs = baseProperty && baseProperty.getDocumentationComment(typeChecker);
            return inheritedDocs && inheritedDocs.length ? inheritedDocs : undefined;
        });
    }

    class SourceFileObject extends NodeObject implements SourceFile {
        public kind: SyntaxKind.SourceFile = SyntaxKind.SourceFile;
        public _declarationBrand: any;
        public fileName!: string;
        public path!: Path;
        public resolvedPath!: Path;
        public originalFileName!: string;
        public text!: string;
        public scriptSnapshot!: IScriptSnapshot;
        public lineMap!: readonly number[];

        public statements!: NodeArray<Statement>;
        public endOfFileToken!: Token<SyntaxKind.EndOfFileToken>;

        public amdDependencies!: { name: string; path: string }[];
        public moduleName!: string;
        public referencedFiles!: FileReference[];
        public typeReferenceDirectives!: FileReference[];
        public libReferenceDirectives!: FileReference[];

        public syntacticDiagnostics!: DiagnosticWithLocation[];
        public parseDiagnostics!: DiagnosticWithLocation[];
        public bindDiagnostics!: DiagnosticWithLocation[];
        public bindSuggestionDiagnostics?: DiagnosticWithLocation[];

        public isDeclarationFile!: boolean;
        public isDefaultLib!: boolean;
        public hasNoDefaultLib!: boolean;
        public externalModuleIndicator!: Node; // The first node that causes this file to be an external module
        public commonJsModuleIndicator!: Node; // The first node that causes this file to be a CommonJS module
        public nodeCount!: number;
        public identifierCount!: number;
        public symbolCount!: number;
        public version!: string;
        public scriptKind!: ScriptKind;
        public languageVersion!: ScriptTarget;
        public languageVariant!: LanguageVariant;
        public identifiers!: Map<string>;
        public nameTable: UnderscoreEscapedMap<number> | undefined;
        public resolvedModules: Map<ResolvedModuleFull> | undefined;
        public resolvedTypeReferenceDirectiveNames!: Map<ResolvedTypeReferenceDirective>;
        public imports!: readonly StringLiteralLike[];
        public moduleAugmentations!: StringLiteral[];
        private namedDeclarations: Map<Declaration[]> | undefined;
        public ambientModuleNames!: string[];
        public checkJsDirective: CheckJsDirective | undefined;
        public errorExpectations: TextRange[] | undefined;
        public possiblyContainDynamicImport?: boolean;
        public pragmas!: PragmaMap;
        public localJsxFactory: EntityName | undefined;
        public localJsxNamespace: __String | undefined;

        constructor(kind: SyntaxKind, pos: number, end: number) {
            super(kind, pos, end);
        }

        public update(newText: string, textChangeRange: TextChangeRange): SourceFile {
            return updateSourceFile(this, newText, textChangeRange);
        }

        public getLineAndCharacterOfPosition(position: number): LineAndCharacter {
            return getLineAndCharacterOfPosition(this, position);
        }

        public getLineStarts(): readonly number[] {
            return getLineStarts(this);
        }

        public getPositionOfLineAndCharacter(line: number, character: number, allowEdits?: true): number {
            return computePositionOfLineAndCharacter(getLineStarts(this), line, character, this.text, allowEdits);
        }

        public getLineEndOfPosition(pos: number): number {
            const { line } = this.getLineAndCharacterOfPosition(pos);
            const lineStarts = this.getLineStarts();

            let lastCharPos: number | undefined;
            if (line + 1 >= lineStarts.length) {
                lastCharPos = this.getEnd();
            }
            if (!lastCharPos) {
                lastCharPos = lineStarts[line + 1] - 1;
            }

            const fullText = this.getFullText();
            // if the new line is "\r\n", we should return the last non-new-line-character position
            return fullText[lastCharPos] === "\n" && fullText[lastCharPos - 1] === "\r" ? lastCharPos - 1 : lastCharPos;
        }

        public getNamedDeclarations(): Map<Declaration[]> {
            if (!this.namedDeclarations) {
                this.namedDeclarations = this.computeNamedDeclarations();
            }

            return this.namedDeclarations;
        }

        private computeNamedDeclarations(): Map<Declaration[]> {
            const result = createMultiMap<Declaration>();

            this.forEachChild(visit);

            return result;

            function addDeclaration(declaration: Declaration) {
                const name = getDeclarationName(declaration);
                if (name) {
                    result.add(name, declaration);
                }
            }

            function getDeclarations(name: string) {
                let declarations = result.get(name);
                if (!declarations) {
                    result.set(name, declarations = []);
                }
                return declarations;
            }

            function getDeclarationName(declaration: Declaration) {
                const name = getNonAssignedNameOfDeclaration(declaration);
                return name && (isComputedPropertyName(name) && isPropertyAccessExpression(name.expression) ? name.expression.name.text
                    : isPropertyName(name) ? getNameFromPropertyName(name) : undefined);
            }

            function visit(node: Node): void {
                switch (node.kind) {
                    case SyntaxKind.FunctionDeclaration:
                    case SyntaxKind.FunctionExpression:
                    case SyntaxKind.MethodDeclaration:
                    case SyntaxKind.MethodSignature:
                        const functionDeclaration = <FunctionLikeDeclaration>node;
                        const declarationName = getDeclarationName(functionDeclaration);

                        if (declarationName) {
                            const declarations = getDeclarations(declarationName);
                            const lastDeclaration = lastOrUndefined(declarations);

                            // Check whether this declaration belongs to an "overload group".
                            if (lastDeclaration && functionDeclaration.parent === lastDeclaration.parent && functionDeclaration.symbol === lastDeclaration.symbol) {
                                // Overwrite the last declaration if it was an overload
                                // and this one is an implementation.
                                if (functionDeclaration.body && !(<FunctionLikeDeclaration>lastDeclaration).body) {
                                    declarations[declarations.length - 1] = functionDeclaration;
                                }
                            }
                            else {
                                declarations.push(functionDeclaration);
                            }
                        }
                        forEachChild(node, visit);
                        break;

                    case SyntaxKind.ClassDeclaration:
                    case SyntaxKind.ClassExpression:
                    case SyntaxKind.InterfaceDeclaration:
                    case SyntaxKind.TypeAliasDeclaration:
                    case SyntaxKind.EnumDeclaration:
                    case SyntaxKind.ModuleDeclaration:
                    case SyntaxKind.ImportEqualsDeclaration:
                    case SyntaxKind.ExportSpecifier:
                    case SyntaxKind.ImportSpecifier:
                    case SyntaxKind.ImportClause:
                    case SyntaxKind.NamespaceImport:
                    case SyntaxKind.GetAccessor:
                    case SyntaxKind.SetAccessor:
                    case SyntaxKind.TypeLiteral:
                        addDeclaration(<Declaration>node);
                        forEachChild(node, visit);
                        break;

                    case SyntaxKind.Parameter:
                        // Only consider parameter properties
                        if (!hasModifier(node, ModifierFlags.ParameterPropertyModifier)) {
                            break;
                        }
                        // falls through

                    case SyntaxKind.VariableDeclaration:
                    case SyntaxKind.BindingElement: {
                        const decl = <VariableDeclaration>node;
                        if (isBindingPattern(decl.name)) {
                            forEachChild(decl.name, visit);
                            break;
                        }
                        if (decl.initializer) {
                            visit(decl.initializer);
                        }
                    }
                    // falls through
                    case SyntaxKind.EnumMember:
                    case SyntaxKind.PropertyDeclaration:
                    case SyntaxKind.PropertySignature:
                        addDeclaration(<Declaration>node);
                        break;

                    case SyntaxKind.ExportDeclaration:
                        // Handle named exports case e.g.:
                        //    export {a, b as B} from "mod";
                        const exportDeclaration = (<ExportDeclaration>node);
                        if (exportDeclaration.exportClause) {
                            if (isNamedExports(exportDeclaration.exportClause)) {
                                forEach(exportDeclaration.exportClause.elements, visit);
                            }
                            else {
                                visit(exportDeclaration.exportClause.name);
                            }
                        }
                        break;

                    case SyntaxKind.ImportDeclaration:
                        const importClause = (<ImportDeclaration>node).importClause;
                        if (importClause) {
                            // Handle default import case e.g.:
                            //    import d from "mod";
                            if (importClause.name) {
                                addDeclaration(importClause.name);
                            }

                            // Handle named bindings in imports e.g.:
                            //    import * as NS from "mod";
                            //    import {a, b as B} from "mod";
                            if (importClause.namedBindings) {
                                if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) {
                                    addDeclaration(importClause.namedBindings);
                                }
                                else {
                                    forEach(importClause.namedBindings.elements, visit);
                                }
                            }
                        }
                        break;

                    case SyntaxKind.BinaryExpression:
                        if (getAssignmentDeclarationKind(node as BinaryExpression) !== AssignmentDeclarationKind.None) {
                            addDeclaration(node as BinaryExpression);
                        }
                        // falls through

                    default:
                        forEachChild(node, visit);
                }
            }
        }
    }

    class SourceMapSourceObject implements SourceMapSource {
        lineMap!: number[];
        constructor(public fileName: string, public text: string, public skipTrivia?: (pos: number) => number) { }

        public getLineAndCharacterOfPosition(pos: number): LineAndCharacter {
            return getLineAndCharacterOfPosition(this, pos);
        }
    }

    function getServicesObjectAllocator(): ObjectAllocator {
        return {
            getNodeConstructor: () => NodeObject,
            getTokenConstructor: () => TokenObject,

            getIdentifierConstructor: () => IdentifierObject,
            getPrivateIdentifierConstructor: () => PrivateIdentifierObject,
            getSourceFileConstructor: () => SourceFileObject,
            getSymbolConstructor: () => SymbolObject,
            getTypeConstructor: () => TypeObject,
            getSignatureConstructor: () => SignatureObject,
            getSourceMapSourceConstructor: () => SourceMapSourceObject,
        };
    }

    /// Language Service

    // Information about a specific host file.
    interface HostFileInformation {
        hostFileName: string;
        version: string;
        scriptSnapshot: IScriptSnapshot;
        scriptKind: ScriptKind;
    }

    /* @internal */
    export interface DisplayPartsSymbolWriter extends EmitTextWriter {
        displayParts(): SymbolDisplayPart[];
    }

    /* @internal */
    export function toEditorSettings(options: FormatCodeOptions | FormatCodeSettings): FormatCodeSettings;
    export function toEditorSettings(options: EditorOptions | EditorSettings): EditorSettings;
    export function toEditorSettings(optionsAsMap: MapLike<any>): MapLike<any> {
        let allPropertiesAreCamelCased = true;
        for (const key in optionsAsMap) {
            if (hasProperty(optionsAsMap, key) && !isCamelCase(key)) {
                allPropertiesAreCamelCased = false;
                break;
            }
        }
        if (allPropertiesAreCamelCased) {
            return optionsAsMap;
        }
        const settings: MapLike<any> = {};
        for (const key in optionsAsMap) {
            if (hasProperty(optionsAsMap, key)) {
                const newKey = isCamelCase(key) ? key : key.charAt(0).toLowerCase() + key.substr(1);
                settings[newKey] = optionsAsMap[key];
            }
        }
        return settings;
    }

    function isCamelCase(s: string) {
        return !s.length || s.charAt(0) === s.charAt(0).toLowerCase();
    }

    export function displayPartsToString(displayParts: SymbolDisplayPart[] | undefined) {
        if (displayParts) {
            return map(displayParts, displayPart => displayPart.text).join("");
        }

        return "";
    }

    export function getDefaultCompilerOptions(): CompilerOptions {
        // Always default to "ScriptTarget.ES5" for the language service
        return {
            target: ScriptTarget.ES5,
            jsx: JsxEmit.Preserve
        };
    }

    export function getSupportedCodeFixes() {
        return codefix.getSupportedErrorCodes();
    }

    // Either it will be file name if host doesnt have file or it will be the host's file information
    type CachedHostFileInformation = HostFileInformation | string;

    // Cache host information about script Should be refreshed
    // at each language service public entry point, since we don't know when
    // the set of scripts handled by the host changes.
    class HostCache {
        private fileNameToEntry: Map<CachedHostFileInformation>;
        private _compilationSettings: CompilerOptions;
        private currentDirectory: string;

        constructor(private host: LanguageServiceHost, getCanonicalFileName: GetCanonicalFileName) {
            // script id => script index
            this.currentDirectory = host.getCurrentDirectory();
            this.fileNameToEntry = createMap<CachedHostFileInformation>();

            // Initialize the list with the root file names
            const rootFileNames = host.getScriptFileNames();
            for (const fileName of rootFileNames) {
                this.createEntry(fileName, toPath(fileName, this.currentDirectory, getCanonicalFileName));
            }

            // store the compilation settings
            this._compilationSettings = host.getCompilationSettings() || getDefaultCompilerOptions();
        }

        public compilationSettings() {
            return this._compilationSettings;
        }

        public getProjectReferences(): readonly ProjectReference[] | undefined {
            return this.host.getProjectReferences && this.host.getProjectReferences();
        }

        private createEntry(fileName: string, path: Path) {
            let entry: CachedHostFileInformation;
            const scriptSnapshot = this.host.getScriptSnapshot(fileName);
            if (scriptSnapshot) {
                entry = {
                    hostFileName: fileName,
                    version: this.host.getScriptVersion(fileName),
                    scriptSnapshot,
                    scriptKind: getScriptKind(fileName, this.host)
                };
            }
            else {
                entry = fileName;
            }

            this.fileNameToEntry.set(path, entry);
            return entry;
        }

        public getEntryByPath(path: Path): CachedHostFileInformation | undefined {
            return this.fileNameToEntry.get(path);
        }

        public getHostFileInformation(path: Path): HostFileInformation | undefined {
            const entry = this.fileNameToEntry.get(path);
            return !isString(entry) ? entry : undefined;
        }

        public getOrCreateEntryByPath(fileName: string, path: Path): HostFileInformation {
            const info = this.getEntryByPath(path) || this.createEntry(fileName, path);
            return isString(info) ? undefined! : info; // TODO: GH#18217
        }

        public getRootFileNames(): string[] {
            const names: string[] = [];
            this.fileNameToEntry.forEach(entry => {
                if (isString(entry)) {
                    names.push(entry);
                }
                else {
                    names.push(entry.hostFileName);
                }
            });
            return names;
        }

        public getScriptSnapshot(path: Path): IScriptSnapshot {
            const file = this.getHostFileInformation(path);
            return (file && file.scriptSnapshot)!; // TODO: GH#18217
        }
    }

    class SyntaxTreeCache {
        // For our syntactic only features, we also keep a cache of the syntax tree for the
        // currently edited file.
        private currentFileName: string | undefined;
        private currentFileVersion: string | undefined;
        private currentFileScriptSnapshot: IScriptSnapshot | undefined;
        private currentSourceFile: SourceFile | undefined;

        constructor(private host: LanguageServiceHost) {
        }

        public getCurrentSourceFile(fileName: string): SourceFile {
            const scriptSnapshot = this.host.getScriptSnapshot(fileName);
            if (!scriptSnapshot) {
                // The host does not know about this file.
                throw new Error("Could not find file: '" + fileName + "'.");
            }

            const scriptKind = getScriptKind(fileName, this.host);
            const version = this.host.getScriptVersion(fileName);
            let sourceFile: SourceFile | undefined;

            if (this.currentFileName !== fileName) {
                // This is a new file, just parse it
                sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, ScriptTarget.Latest, version, /*setNodeParents*/ true, scriptKind);
            }
            else if (this.currentFileVersion !== version) {
                // This is the same file, just a newer version. Incrementally parse the file.
                const editRange = scriptSnapshot.getChangeRange(this.currentFileScriptSnapshot!);
                sourceFile = updateLanguageServiceSourceFile(this.currentSourceFile!, scriptSnapshot, version, editRange);
            }

            if (sourceFile) {
                // All done, ensure state is up to date
                this.currentFileVersion = version;
                this.currentFileName = fileName;
                this.currentFileScriptSnapshot = scriptSnapshot;
                this.currentSourceFile = sourceFile;
            }

            return this.currentSourceFile!;
        }
    }

    function setSourceFileFields(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string) {
        sourceFile.version = version;
        sourceFile.scriptSnapshot = scriptSnapshot;
    }

    export function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean, scriptKind?: ScriptKind): SourceFile {
        const sourceFile = createSourceFile(fileName, getSnapshotText(scriptSnapshot), scriptTarget, setNodeParents, scriptKind);
        setSourceFileFields(sourceFile, scriptSnapshot, version);
        return sourceFile;
    }

    export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange | undefined, aggressiveChecks?: boolean): SourceFile {
        // If we were given a text change range, and our version or open-ness changed, then
        // incrementally parse this file.
        if (textChangeRange) {
            if (version !== sourceFile.version) {
                let newText: string;

                // grab the fragment from the beginning of the original text to the beginning of the span
                const prefix = textChangeRange.span.start !== 0
                    ? sourceFile.text.substr(0, textChangeRange.span.start)
                    : "";

                // grab the fragment from the end of the span till the end of the original text
                const suffix = textSpanEnd(textChangeRange.span) !== sourceFile.text.length
                    ? sourceFile.text.substr(textSpanEnd(textChangeRange.span))
                    : "";

                if (textChangeRange.newLength === 0) {
                    // edit was a deletion - just combine prefix and suffix
                    newText = prefix && suffix ? prefix + suffix : prefix || suffix;
                }
                else {
                    // it was actual edit, fetch the fragment of new text that correspond to new span
                    const changedText = scriptSnapshot.getText(textChangeRange.span.start, textChangeRange.span.start + textChangeRange.newLength);
                    // combine prefix, changed text and suffix
                    newText = prefix && suffix
                        ? prefix + changedText + suffix
                        : prefix
                            ? (prefix + changedText)
                            : (changedText + suffix);
                }

                const newSourceFile = updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks);
                setSourceFileFields(newSourceFile, scriptSnapshot, version);
                // after incremental parsing nameTable might not be up-to-date
                // drop it so it can be lazily recreated later
                newSourceFile.nameTable = undefined;

                // dispose all resources held by old script snapshot
                if (sourceFile !== newSourceFile && sourceFile.scriptSnapshot) {
                    if (sourceFile.scriptSnapshot.dispose) {
                        sourceFile.scriptSnapshot.dispose();
                    }

                    sourceFile.scriptSnapshot = undefined;
                }

                return newSourceFile;
            }
        }

        // Otherwise, just create a new source file.
        return createLanguageServiceSourceFile(sourceFile.fileName, scriptSnapshot, sourceFile.languageVersion, version, /*setNodeParents*/ true, sourceFile.scriptKind);
    }

    class CancellationTokenObject implements CancellationToken {
        constructor(private cancellationToken: HostCancellationToken | undefined) {
        }

        public isCancellationRequested(): boolean {
            return !!this.cancellationToken && this.cancellationToken.isCancellationRequested();
        }

        public throwIfCancellationRequested(): void {
            if (this.isCancellationRequested()) {
                throw new OperationCanceledException();
            }
        }
    }

    /* @internal */
    /** A cancellation that throttles calls to the host */
    export class ThrottledCancellationToken implements CancellationToken {
        // Store when we last tried to cancel.  Checking cancellation can be expensive (as we have
        // to marshall over to the host layer).  So we only bother actually checking once enough
        // time has passed.
        private lastCancellationCheckTime = 0;

        constructor(private hostCancellationToken: HostCancellationToken, private readonly throttleWaitMilliseconds = 20) {
        }

        public isCancellationRequested(): boolean {
            const time = timestamp();
            const duration = Math.abs(time - this.lastCancellationCheckTime);
            if (duration >= this.throttleWaitMilliseconds) {
                // Check no more than once every throttle wait milliseconds
                this.lastCancellationCheckTime = time;
                return this.hostCancellationToken.isCancellationRequested();
            }

            return false;
        }

        public throwIfCancellationRequested(): void {
            if (this.isCancellationRequested()) {
                throw new OperationCanceledException();
            }
        }
    }

    export function createLanguageService(
        host: LanguageServiceHost,
        documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory()),
        syntaxOnly = false): LanguageService {

        const syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host);
        let program: Program;
        let lastProjectVersion: string;
        let lastTypesRootVersion = 0;

        const cancellationToken = new CancellationTokenObject(host.getCancellationToken && host.getCancellationToken());

        const currentDirectory = host.getCurrentDirectory();
        // Check if the localized messages json is set, otherwise query the host for it
        if (!localizedDiagnosticMessages && host.getLocalizedDiagnosticMessages) {
            setLocalizedDiagnosticMessages(host.getLocalizedDiagnosticMessages());
        }

        function log(message: string) {
            if (host.log) {
                host.log(message);
            }
        }

        const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host);
        const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);

        const sourceMapper = getSourceMapper({
            useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
            getCurrentDirectory: () => currentDirectory,
            getProgram,
            fileExists: maybeBind(host, host.fileExists),
            readFile: maybeBind(host, host.readFile),
            getDocumentPositionMapper: maybeBind(host, host.getDocumentPositionMapper),
            getSourceFileLike: maybeBind(host, host.getSourceFileLike),
            log
        });

        function getValidSourceFile(fileName: string): SourceFile {
            const sourceFile = program.getSourceFile(fileName);
            if (!sourceFile) {
                const error: Error & PossibleProgramFileInfo = new Error(`Could not find source file: '${fileName}'.`);

                // We've been having trouble debugging this, so attach sidecar data for the tsserver log.
                // See https://github.com/microsoft/TypeScript/issues/30180.
                error.ProgramFiles = program.getSourceFiles().map(f => f.fileName);

                throw error;
            }
            return sourceFile;
        }

        function synchronizeHostData(): void {
            Debug.assert(!syntaxOnly);

            // perform fast check if host supports it
            if (host.getProjectVersion) {
                const hostProjectVersion = host.getProjectVersion();
                if (hostProjectVersion) {
                    if (lastProjectVersion === hostProjectVersion && !host.hasChangedAutomaticTypeDirectiveNames) {
                        return;
                    }

                    lastProjectVersion = hostProjectVersion;
                }
            }

            const typeRootsVersion = host.getTypeRootsVersion ? host.getTypeRootsVersion() : 0;
            if (lastTypesRootVersion !== typeRootsVersion) {
                log("TypeRoots version has changed; provide new program");
                program = undefined!; // TODO: GH#18217
                lastTypesRootVersion = typeRootsVersion;
            }

            // Get a fresh cache of the host information
            let hostCache: HostCache | undefined = new HostCache(host, getCanonicalFileName);
            const rootFileNames = hostCache.getRootFileNames();

            const hasInvalidatedResolution: HasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse;
            const projectReferences = hostCache.getProjectReferences();

            // If the program is already up-to-date, we can reuse it
            if (isProgramUptoDate(program, rootFileNames, hostCache.compilationSettings(), (_path, fileName) => host.getScriptVersion(fileName), fileExists, hasInvalidatedResolution, !!host.hasChangedAutomaticTypeDirectiveNames, projectReferences)) {
                return;
            }

            // IMPORTANT - It is critical from this moment onward that we do not check
            // cancellation tokens.  We are about to mutate source files from a previous program
            // instance.  If we cancel midway through, we may end up in an inconsistent state where
            // the program points to old source files that have been invalidated because of
            // incremental parsing.

            const newSettings = hostCache.compilationSettings();

            // Now create a new compiler
            const compilerHost: CompilerHost = {
                getSourceFile: getOrCreateSourceFile,
                getSourceFileByPath: getOrCreateSourceFileByPath,
                getCancellationToken: () => cancellationToken,
                getCanonicalFileName,
                useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
                getNewLine: () => getNewLineCharacter(newSettings, () => getNewLineOrDefaultFromHost(host)),
                getDefaultLibFileName: (options) => host.getDefaultLibFileName(options),
                writeFile: noop,
                getCurrentDirectory: () => currentDirectory,
                fileExists,
                readFile,
                realpath: host.realpath && (path => host.realpath!(path)),
                directoryExists: directoryName => {
                    return directoryProbablyExists(directoryName, host);
                },
                getDirectories: path => {
                    return host.getDirectories ? host.getDirectories(path) : [];
                },
                readDirectory(path, extensions, exclude, include, depth) {
                    Debug.checkDefined(host.readDirectory, "'LanguageServiceHost.readDirectory' must be implemented to correctly process 'projectReferences'");
                    return host.readDirectory!(path, extensions, exclude, include, depth);
                },
                onReleaseOldSourceFile,
                hasInvalidatedResolution,
                hasChangedAutomaticTypeDirectiveNames: host.hasChangedAutomaticTypeDirectiveNames
            };
            if (host.trace) {
                compilerHost.trace = message => host.trace!(message);
            }

            if (host.resolveModuleNames) {
                compilerHost.resolveModuleNames = (...args) => host.resolveModuleNames!(...args);
            }
            if (host.resolveTypeReferenceDirectives) {
                compilerHost.resolveTypeReferenceDirectives = (...args) => host.resolveTypeReferenceDirectives!(...args);
            }
            if (host.useSourceOfProjectReferenceRedirect) {
                compilerHost.useSourceOfProjectReferenceRedirect = () => host.useSourceOfProjectReferenceRedirect!();
            }
            host.setCompilerHost?.(compilerHost);

            const documentRegistryBucketKey = documentRegistry.getKeyForCompilationSettings(newSettings);
            const options: CreateProgramOptions = {
                rootNames: rootFileNames,
                options: newSettings,
                host: compilerHost,
                oldProgram: program,
                projectReferences
            };
            program = createProgram(options);

            // hostCache is captured in the closure for 'getOrCreateSourceFile' but it should not be used past this point.
            // It needs to be cleared to allow all collected snapshots to be released
            hostCache = undefined;

            // 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,
            // Because it only functions until `hostCache` is cleared, while we'll potentially need the functionality to lazily read sourcemap files during
            // the course of whatever called `synchronizeHostData`
            sourceMapper.clearCache();

            // Make sure all the nodes in the program are both bound, and have their parent
            // pointers set property.
            program.getTypeChecker();
            return;

            function fileExists(fileName: string): boolean {
                const path = toPath(fileName, currentDirectory, getCanonicalFileName);
                const entry = hostCache && hostCache.getEntryByPath(path);
                return entry ?
                    !isString(entry) :
                    (!!host.fileExists && host.fileExists(fileName));
            }

            function readFile(fileName: string) {
                // stub missing host functionality
                const path = toPath(fileName, currentDirectory, getCanonicalFileName);
                const entry = hostCache && hostCache.getEntryByPath(path);
                if (entry) {
                    return isString(entry) ? undefined : getSnapshotText(entry.scriptSnapshot);
                }
                return host.readFile && host.readFile(fileName);
            }

            // Release any files we have acquired in the old program but are
            // not part of the new program.
            function onReleaseOldSourceFile(oldSourceFile: SourceFile, oldOptions: CompilerOptions) {
                const oldSettingsKey = documentRegistry.getKeyForCompilationSettings(oldOptions);
                documentRegistry.releaseDocumentWithKey(oldSourceFile.resolvedPath, oldSettingsKey);
            }

            function getOrCreateSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined {
                return getOrCreateSourceFileByPath(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), languageVersion, onError, shouldCreateNewSourceFile);
            }

            function getOrCreateSourceFileByPath(fileName: string, path: Path, _languageVersion: ScriptTarget, _onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined {
                Debug.assert(hostCache !== undefined, "getOrCreateSourceFileByPath called after typical CompilerHost lifetime, check the callstack something with a reference to an old host.");
                // The program is asking for this file, check first if the host can locate it.
                // If the host can not locate the file, then it does not exist. return undefined
                // to the program to allow reporting of errors for missing files.
                const hostFileInformation = hostCache && hostCache.getOrCreateEntryByPath(fileName, path);
                if (!hostFileInformation) {
                    return undefined;
                }

                // Check if the language version has changed since we last created a program; if they are the same,
                // it is safe to reuse the sourceFiles; if not, then the shape of the AST can change, and the oldSourceFile
                // can not be reused. we have to dump all syntax trees and create new ones.
                if (!shouldCreateNewSourceFile) {
                    // Check if the old program had this file already
                    const oldSourceFile = program && program.getSourceFileByPath(path);
                    if (oldSourceFile) {
                        // We already had a source file for this file name.  Go to the registry to
                        // ensure that we get the right up to date version of it.  We need this to
                        // address the following race-condition.  Specifically, say we have the following:
                        //
                        //      LS1
                        //          \
                        //           DocumentRegistry
                        //          /
                        //      LS2
                        //
                        // Each LS has a reference to file 'foo.ts' at version 1.  LS2 then updates
                        // it's version of 'foo.ts' to version 2.  This will cause LS2 and the
                        // DocumentRegistry to have version 2 of the document.  HOwever, LS1 will
                        // have version 1.  And *importantly* this source file will be *corrupt*.
                        // The act of creating version 2 of the file irrevocably damages the version
                        // 1 file.
                        //
                        // So, later when we call into LS1, we need to make sure that it doesn't use
                        // it's source file any more, and instead defers to DocumentRegistry to get
                        // either version 1, version 2 (or some other version) depending on what the
                        // host says should be used.

                        // We do not support the scenario where a host can modify a registered
                        // file's script kind, i.e. in one project some file is treated as ".ts"
                        // and in another as ".js"
                        Debug.assertEqual(hostFileInformation.scriptKind, oldSourceFile.scriptKind, "Registered script kind should match new script kind.");

                        return documentRegistry.updateDocumentWithKey(fileName, path, newSettings, documentRegistryBucketKey, hostFileInformation.scriptSnapshot, hostFileInformation.version, hostFileInformation.scriptKind);
                    }

                    // We didn't already have the file.  Fall through and acquire it from the registry.
                }

                // Could not find this file in the old program, create a new SourceFile for it.
                return documentRegistry.acquireDocumentWithKey(fileName, path, newSettings, documentRegistryBucketKey, hostFileInformation.scriptSnapshot, hostFileInformation.version, hostFileInformation.scriptKind);
            }
        }

        // TODO: GH#18217 frequently asserted as defined
        function getProgram(): Program | undefined {
            if (syntaxOnly) {
                Debug.assert(program === undefined);
                return undefined;
            }

            synchronizeHostData();

            return program;
        }

        function cleanupSemanticCache(): void {
            program = undefined!; // TODO: GH#18217
        }

        function dispose(): void {
            if (program) {
                // Use paths to ensure we are using correct key and paths as document registry could bre created with different current directory than host
                const key = documentRegistry.getKeyForCompilationSettings(program.getCompilerOptions());
                forEach(program.getSourceFiles(), f =>
                    documentRegistry.releaseDocumentWithKey(f.resolvedPath, key));
                program = undefined!; // TODO: GH#18217
            }
            host = undefined!;
        }

        /// Diagnostics
        function getSyntacticDiagnostics(fileName: string): DiagnosticWithLocation[] {
            synchronizeHostData();

            return program.getSyntacticDiagnostics(getValidSourceFile(fileName), cancellationToken).slice();
        }

        /**
         * getSemanticDiagnostics return array of Diagnostics. If '-d' is not enabled, only report semantic errors
         * If '-d' enabled, report both semantic and emitter errors
         */
        function getSemanticDiagnostics(fileName: string): Diagnostic[] {
            synchronizeHostData();

            const targetSourceFile = getValidSourceFile(fileName);

            // Only perform the action per file regardless of '-out' flag as LanguageServiceHost is expected to call this function per file.
            // Therefore only get diagnostics for given file.

            const semanticDiagnostics = program.getSemanticDiagnostics(targetSourceFile, cancellationToken);
            if (!getEmitDeclarations(program.getCompilerOptions())) {
                return semanticDiagnostics.slice();
            }

            // If '-d' is enabled, check for emitter error. One example of emitter error is export class implements non-export interface
            const declarationDiagnostics = program.getDeclarationDiagnostics(targetSourceFile, cancellationToken);
            return [...semanticDiagnostics, ...declarationDiagnostics];
        }

        function getSuggestionDiagnostics(fileName: string): DiagnosticWithLocation[] {
            synchronizeHostData();
            return computeSuggestionDiagnostics(getValidSourceFile(fileName), program, cancellationToken);
        }

        function getCompilerOptionsDiagnostics() {
            synchronizeHostData();
            return [...program.getOptionsDiagnostics(cancellationToken), ...program.getGlobalDiagnostics(cancellationToken)];
        }

        function getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions = emptyOptions): CompletionInfo | undefined {
            // Convert from deprecated options names to new names
            const fullPreferences: UserPreferences = {
                ...identity<UserPreferences>(options), // avoid excess property check
                includeCompletionsForModuleExports: options.includeCompletionsForModuleExports || options.includeExternalModuleExports,
                includeCompletionsWithInsertText: options.includeCompletionsWithInsertText || options.includeInsertTextCompletions,
            };
            synchronizeHostData();
            return Completions.getCompletionsAtPosition(
                host,
                program,
                log,
                getValidSourceFile(fileName),
                position,
                fullPreferences,
                options.triggerCharacter);
        }

        function getCompletionEntryDetails(fileName: string, position: number, name: string, formattingOptions: FormatCodeSettings | undefined, source: string | undefined, preferences: UserPreferences = emptyOptions): CompletionEntryDetails | undefined {
            synchronizeHostData();
            return Completions.getCompletionEntryDetails(
                program,
                log,
                getValidSourceFile(fileName),
                position,
                { name, source },
                host,
                (formattingOptions && formatting.getFormatContext(formattingOptions))!, // TODO: GH#18217
                preferences,
                cancellationToken,
            );
        }

        function getCompletionEntrySymbol(fileName: string, position: number, name: string, source?: string, preferences: UserPreferences = emptyOptions): Symbol | undefined {
            synchronizeHostData();
            return Completions.getCompletionEntrySymbol(program, log, getValidSourceFile(fileName), position, { name, source }, host, preferences);
        }

        function getQuickInfoAtPosition(fileName: string, position: number): QuickInfo | undefined {
            synchronizeHostData();

            const sourceFile = getValidSourceFile(fileName);
            const node = getTouchingPropertyName(sourceFile, position);
            if (node === sourceFile) {
                // Avoid giving quickInfo for the sourceFile as a whole.
                return undefined;
            }

            const typeChecker = program.getTypeChecker();
            const nodeForQuickInfo = getNodeForQuickInfo(node);
            const symbol = getSymbolAtLocationForQuickInfo(nodeForQuickInfo, typeChecker);

            if (!symbol || typeChecker.isUnknownSymbol(symbol)) {
                const type = shouldGetType(sourceFile, nodeForQuickInfo, position) ? typeChecker.getTypeAtLocation(nodeForQuickInfo) : undefined;
                return type && {
                    kind: ScriptElementKind.unknown,
                    kindModifiers: ScriptElementKindModifier.none,
                    textSpan: createTextSpanFromNode(nodeForQuickInfo, sourceFile),
                    displayParts: typeChecker.runWithCancellationToken(cancellationToken, typeChecker => typeToDisplayParts(typeChecker, type, getContainerNode(nodeForQuickInfo))),
                    documentation: type.symbol ? type.symbol.getDocumentationComment(typeChecker) : undefined,
                    tags: type.symbol ? type.symbol.getJsDocTags() : undefined
                };
            }

            const { symbolKind, displayParts, documentation, tags } = typeChecker.runWithCancellationToken(cancellationToken, typeChecker =>
                SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, getContainerNode(nodeForQuickInfo), nodeForQuickInfo)
            );
            return {
                kind: symbolKind,
                kindModifiers: SymbolDisplay.getSymbolModifiers(symbol),
                textSpan: createTextSpanFromNode(nodeForQuickInfo, sourceFile),
                displayParts,
                documentation,
                tags,
            };
        }

        function getNodeForQuickInfo(node: Node): Node {
            if (isNewExpression(node.parent) && node.pos === node.parent.pos) {
                return node.parent.expression;
            }
            return node;
        }

        function shouldGetType(sourceFile: SourceFile, node: Node, position: number): boolean {
            switch (node.kind) {
                case SyntaxKind.Identifier:
                    return !isLabelName(node) && !isTagName(node);
                case SyntaxKind.PropertyAccessExpression:
                case SyntaxKind.QualifiedName:
                    // Don't return quickInfo if inside the comment in `a/**/.b`
                    return !isInComment(sourceFile, position);
                case SyntaxKind.ThisKeyword:
                case SyntaxKind.ThisType:
                case SyntaxKind.SuperKeyword:
                    return true;
                default:
                    return false;
            }
        }

        /// Goto definition
        function getDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined {
            synchronizeHostData();
            return GoToDefinition.getDefinitionAtPosition(program, getValidSourceFile(fileName), position);
        }

        function getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan | undefined {
            synchronizeHostData();
            return GoToDefinition.getDefinitionAndBoundSpan(program, getValidSourceFile(fileName), position);
        }

        function getTypeDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined {
            synchronizeHostData();
            return GoToDefinition.getTypeDefinitionAtPosition(program.getTypeChecker(), getValidSourceFile(fileName), position);
        }

        /// Goto implementation

        function getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] | undefined {
            synchronizeHostData();
            return FindAllReferences.getImplementationsAtPosition(program, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position);
        }

        /// References and Occurrences
        function getOccurrencesAtPosition(fileName: string, position: number): readonly ReferenceEntry[] | undefined {
            return flatMap(
                getDocumentHighlights(fileName, position, [fileName]),
                entry => entry.highlightSpans.map<ReferenceEntry>(highlightSpan => ({
                    fileName: entry.fileName,
                    textSpan: highlightSpan.textSpan,
                    isWriteAccess: highlightSpan.kind === HighlightSpanKind.writtenReference,
                    isDefinition: false,
                    ...highlightSpan.isInString && { isInString: true },
                    ...highlightSpan.contextSpan && { contextSpan: highlightSpan.contextSpan }
                }))
            );
        }

        function getDocumentHighlights(fileName: string, position: number, filesToSearch: readonly string[]): DocumentHighlights[] | undefined {
            const normalizedFileName = normalizePath(fileName);
            Debug.assert(filesToSearch.some(f => normalizePath(f) === normalizedFileName));
            synchronizeHostData();
            const sourceFilesToSearch = mapDefined(filesToSearch, fileName => program.getSourceFile(fileName));
            const sourceFile = getValidSourceFile(fileName);
            return DocumentHighlights.getDocumentHighlights(program, cancellationToken, sourceFile, position, sourceFilesToSearch);
        }

        function findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): RenameLocation[] | undefined {
            synchronizeHostData();
            const sourceFile = getValidSourceFile(fileName);
            const node = getAdjustedRenameLocation(getTouchingPropertyName(sourceFile, position));
            if (isIdentifier(node) && (isJsxOpeningElement(node.parent) || isJsxClosingElement(node.parent)) && isIntrinsicJsxName(node.escapedText)) {
                const { openingElement, closingElement } = node.parent.parent;
                return [openingElement, closingElement].map((node): RenameLocation => {
                    const textSpan = createTextSpanFromNode(node.tagName, sourceFile);
                    return {
                        fileName: sourceFile.fileName,
                        textSpan,
                        ...FindAllReferences.toContextSpan(textSpan, sourceFile, node.parent)
                    };
                });
            }
            else {
                return getReferencesWorker(node, position, { findInStrings, findInComments, providePrefixAndSuffixTextForRename, use: FindAllReferences.FindReferencesUse.Rename },
                    (entry, originalNode, checker) => FindAllReferences.toRenameLocation(entry, originalNode, checker, providePrefixAndSuffixTextForRename || false));
            }
        }

        function getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined {
            synchronizeHostData();
            return getReferencesWorker(getTouchingPropertyName(getValidSourceFile(fileName), position), position, { use: FindAllReferences.FindReferencesUse.References }, FindAllReferences.toReferenceEntry);
        }

        function getReferencesWorker<T>(node: Node, position: number, options: FindAllReferences.Options, cb: FindAllReferences.ToReferenceOrRenameEntry<T>): T[] | undefined {
            synchronizeHostData();

            // Exclude default library when renaming as commonly user don't want to change that file.
            const sourceFiles = options && options.use === FindAllReferences.FindReferencesUse.Rename
                ? program.getSourceFiles().filter(sourceFile => !program.isSourceFileDefaultLibrary(sourceFile))
                : program.getSourceFiles();

            return FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, sourceFiles, node, position, options, cb);
        }

        function findReferences(fileName: string, position: number): ReferencedSymbol[] | undefined {
            synchronizeHostData();
            return FindAllReferences.findReferencedSymbols(program, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position);
        }

        function getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles = false): NavigateToItem[] {
            synchronizeHostData();
            const sourceFiles = fileName ? [getValidSourceFile(fileName)] : program.getSourceFiles();
            return NavigateTo.getNavigateToItems(sourceFiles, program.getTypeChecker(), cancellationToken, searchValue, maxResultCount, excludeDtsFiles);
        }

        function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, forceDtsEmit?: boolean) {
            synchronizeHostData();

            const sourceFile = getValidSourceFile(fileName);
            const customTransformers = host.getCustomTransformers && host.getCustomTransformers();
            return getFileEmitOutput(program, sourceFile, !!emitOnlyDtsFiles, cancellationToken, customTransformers, forceDtsEmit);
        }

        // Signature help
        /**
         * This is a semantic operation.
         */
        function getSignatureHelpItems(fileName: string, position: number, { triggerReason }: SignatureHelpItemsOptions = emptyOptions): SignatureHelpItems | undefined {
            synchronizeHostData();

            const sourceFile = getValidSourceFile(fileName);

            return SignatureHelp.getSignatureHelpItems(program, sourceFile, position, triggerReason, cancellationToken);
        }

        /// Syntactic features
        function getNonBoundSourceFile(fileName: string): SourceFile {
            return syntaxTreeCache.getCurrentSourceFile(fileName);
        }

        function getNameOrDottedNameSpan(fileName: string, startPos: number, _endPos: number): TextSpan | undefined {
            const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);

            // Get node at the location
            const node = getTouchingPropertyName(sourceFile, startPos);

            if (node === sourceFile) {
                return undefined;
            }

            switch (node.kind) {
                case SyntaxKind.PropertyAccessExpression:
                case SyntaxKind.QualifiedName:
                case SyntaxKind.StringLiteral:
                case SyntaxKind.FalseKeyword:
                case SyntaxKind.TrueKeyword:
                case SyntaxKind.NullKeyword:
                case SyntaxKind.SuperKeyword:
                case SyntaxKind.ThisKeyword:
                case SyntaxKind.ThisType:
                case SyntaxKind.Identifier:
                    break;

                // Cant create the text span
                default:
                    return undefined;
            }

            let nodeForStartPos = node;
            while (true) {
                if (isRightSideOfPropertyAccess(nodeForStartPos) || isRightSideOfQualifiedName(nodeForStartPos)) {
                    // 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
                    nodeForStartPos = nodeForStartPos.parent;
                }
                else if (isNameOfModuleDeclaration(nodeForStartPos)) {
                    // If this is name of a module declarations, check if this is right side of dotted module name
                    // 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
                    // Then this name is name from dotted module
                    if (nodeForStartPos.parent.parent.kind === SyntaxKind.ModuleDeclaration &&
                        (<ModuleDeclaration>nodeForStartPos.parent.parent).body === nodeForStartPos.parent) {
                        // Use parent module declarations name for start pos
                        nodeForStartPos = (<ModuleDeclaration>nodeForStartPos.parent.parent).name;
                    }
                    else {
                        // We have to use this name for start pos
                        break;
                    }
                }
                else {
                    // Is not a member expression so we have found the node for start pos
                    break;
                }
            }

            return createTextSpanFromBounds(nodeForStartPos.getStart(), node.getEnd());
        }

        function getBreakpointStatementAtPosition(fileName: string, position: number): TextSpan | undefined {
            // doesn't use compiler - no need to synchronize with host
            const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);

            return BreakpointResolver.spanInSourceFileAtLocation(sourceFile, position);
        }

        function getNavigationBarItems(fileName: string): NavigationBarItem[] {
            return NavigationBar.getNavigationBarItems(syntaxTreeCache.getCurrentSourceFile(fileName), cancellationToken);
        }

        function getNavigationTree(fileName: string): NavigationTree {
            return NavigationBar.getNavigationTree(syntaxTreeCache.getCurrentSourceFile(fileName), cancellationToken);
        }

        function isTsOrTsxFile(fileName: string): boolean {
            const kind = getScriptKind(fileName, host);
            return kind === ScriptKind.TS || kind === ScriptKind.TSX;
        }

        function getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[] {
            if (!isTsOrTsxFile(fileName)) {
                // do not run semantic classification on non-ts-or-tsx files
                return [];
            }
            synchronizeHostData();
            return ts.getSemanticClassifications(program.getTypeChecker(), cancellationToken, getValidSourceFile(fileName), program.getClassifiableNames(), span);
        }

        function getEncodedSemanticClassifications(fileName: string, span: TextSpan): Classifications {
            if (!isTsOrTsxFile(fileName)) {
                // do not run semantic classification on non-ts-or-tsx files
                return { spans: [], endOfLineState: EndOfLineState.None };
            }
            synchronizeHostData();
            return ts.getEncodedSemanticClassifications(program.getTypeChecker(), cancellationToken, getValidSourceFile(fileName), program.getClassifiableNames(), span);
        }

        function getSyntacticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[] {
            // doesn't use compiler - no need to synchronize with host
            return ts.getSyntacticClassifications(cancellationToken, syntaxTreeCache.getCurrentSourceFile(fileName), span);
        }

        function getEncodedSyntacticClassifications(fileName: string, span: TextSpan): Classifications {
            // doesn't use compiler - no need to synchronize with host
            return ts.getEncodedSyntacticClassifications(cancellationToken, syntaxTreeCache.getCurrentSourceFile(fileName), span);
        }

        function getOutliningSpans(fileName: string): OutliningSpan[] {
            // doesn't use compiler - no need to synchronize with host
            const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
            return OutliningElementsCollector.collectElements(sourceFile, cancellationToken);
        }

        const braceMatching = createMapFromTemplate({
            [SyntaxKind.OpenBraceToken]: SyntaxKind.CloseBraceToken,
            [SyntaxKind.OpenParenToken]: SyntaxKind.CloseParenToken,
            [SyntaxKind.OpenBracketToken]: SyntaxKind.CloseBracketToken,
            [SyntaxKind.GreaterThanToken]: SyntaxKind.LessThanToken,
        });
        braceMatching.forEach((value, key) => braceMatching.set(value.toString(), Number(key) as SyntaxKind));

        function getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] {
            const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
            const token = getTouchingToken(sourceFile, position);
            const matchKind = token.getStart(sourceFile) === position ? braceMatching.get(token.kind.toString()) : undefined;
            const match = matchKind && findChildOfKind(token.parent, matchKind, sourceFile);
            // We want to order the braces when we return the result.
            return match ? [createTextSpanFromNode(token, sourceFile), createTextSpanFromNode(match, sourceFile)].sort((a, b) => a.start - b.start) : emptyArray;
        }

        function getIndentationAtPosition(fileName: string, position: number, editorOptions: EditorOptions | EditorSettings) {
            let start = timestamp();
            const settings = toEditorSettings(editorOptions);
            const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
            log("getIndentationAtPosition: getCurrentSourceFile: " + (timestamp() - start));

            start = timestamp();

            const result = formatting.SmartIndenter.getIndentation(position, sourceFile, settings);
            log("getIndentationAtPosition: computeIndentation  : " + (timestamp() - start));

            return result;
        }

        function getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions | FormatCodeSettings): TextChange[] {
            const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
            return formatting.formatSelection(start, end, sourceFile, formatting.getFormatContext(toEditorSettings(options)));
        }

        function getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[] {
            return formatting.formatDocument(syntaxTreeCache.getCurrentSourceFile(fileName), formatting.getFormatContext(toEditorSettings(options)));
        }

        function getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[] {
            const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
            const formatContext = formatting.getFormatContext(toEditorSettings(options));

            if (!isInComment(sourceFile, position)) {
                switch (key) {
                    case "{":
                        return formatting.formatOnOpeningCurly(position, sourceFile, formatContext);
                    case "}":
                        return formatting.formatOnClosingCurly(position, sourceFile, formatContext);
                    case ";":
                        return formatting.formatOnSemicolon(position, sourceFile, formatContext);
                    case "\n":
                        return formatting.formatOnEnter(position, sourceFile, formatContext);
                }
            }

            return [];
        }

        function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: readonly number[], formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): readonly CodeFixAction[] {
            synchronizeHostData();
            const sourceFile = getValidSourceFile(fileName);
            const span = createTextSpanFromBounds(start, end);
            const formatContext = formatting.getFormatContext(formatOptions);

            return flatMap(deduplicate<number>(errorCodes, equateValues, compareValues), errorCode => {
                cancellationToken.throwIfCancellationRequested();
                return codefix.getFixes({ errorCode, sourceFile, span, program, host, cancellationToken, formatContext, preferences });
            });
        }

        function getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): CombinedCodeActions {
            synchronizeHostData();
            Debug.assert(scope.type === "file");
            const sourceFile = getValidSourceFile(scope.fileName);
            const formatContext = formatting.getFormatContext(formatOptions);

            return codefix.getAllFixes({ fixId, sourceFile, program, host, cancellationToken, formatContext, preferences });
        }

        function organizeImports(scope: OrganizeImportsScope, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): readonly FileTextChanges[] {
            synchronizeHostData();
            Debug.assert(scope.type === "file");
            const sourceFile = getValidSourceFile(scope.fileName);
            const formatContext = formatting.getFormatContext(formatOptions);

            return OrganizeImports.organizeImports(sourceFile, formatContext, host, program, preferences);
        }

        function getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): readonly FileTextChanges[] {
            return ts.getEditsForFileRename(getProgram()!, oldFilePath, newFilePath, host, formatting.getFormatContext(formatOptions), preferences, sourceMapper);
        }

        function applyCodeActionCommand(action: CodeActionCommand, formatSettings?: FormatCodeSettings): Promise<ApplyCodeActionCommandResult>;
        function applyCodeActionCommand(action: CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise<ApplyCodeActionCommandResult[]>;
        function applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise<ApplyCodeActionCommandResult | ApplyCodeActionCommandResult[]>;
        function applyCodeActionCommand(fileName: Path, action: CodeActionCommand): Promise<ApplyCodeActionCommandResult>;
        function applyCodeActionCommand(fileName: Path, action: CodeActionCommand[]): Promise<ApplyCodeActionCommandResult[]>;
        function applyCodeActionCommand(fileName: Path | CodeActionCommand | CodeActionCommand[], actionOrFormatSettingsOrUndefined?: CodeActionCommand | CodeActionCommand[] | FormatCodeSettings): Promise<ApplyCodeActionCommandResult | ApplyCodeActionCommandResult[]> {
            const action = typeof fileName === "string" ? actionOrFormatSettingsOrUndefined as CodeActionCommand | CodeActionCommand[] : fileName as CodeActionCommand[];
            return isArray(action) ? Promise.all(action.map(a => applySingleCodeActionCommand(a))) : applySingleCodeActionCommand(action);
        }

        function applySingleCodeActionCommand(action: CodeActionCommand): Promise<ApplyCodeActionCommandResult> {
            const getPath = (path: string): Path => toPath(path, currentDirectory, getCanonicalFileName);
            Debug.assertEqual(action.type, "install package");
            return host.installPackage
                ? host.installPackage({ fileName: getPath(action.file), packageName: action.packageName })
                : Promise.reject("Host does not implement `installPackage`");
        }

        function getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion | undefined {
            return JsDoc.getDocCommentTemplateAtPosition(getNewLineOrDefaultFromHost(host), syntaxTreeCache.getCurrentSourceFile(fileName), position);
        }

        function isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean {
            // '<' is currently not supported, figuring out if we're in a Generic Type vs. a comparison is too
            // expensive to do during typing scenarios
            // i.e. whether we're dealing with:
            //      var x = new foo<| ( with class foo<T>{} )
            // or
            //      var y = 3 <|
            if (openingBrace === CharacterCodes.lessThan) {
                return false;
            }

            const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);

            // Check if in a context where we don't want to perform any insertion
            if (isInString(sourceFile, position)) {
                return false;
            }

            if (isInsideJsxElementOrAttribute(sourceFile, position)) {
                return openingBrace === CharacterCodes.openBrace;
            }

            if (isInTemplateString(sourceFile, position)) {
                return false;
            }

            switch (openingBrace) {
                case CharacterCodes.singleQuote:
                case CharacterCodes.doubleQuote:
                case CharacterCodes.backtick:
                    return !isInComment(sourceFile, position);
            }

            return true;
        }

        function getJsxClosingTagAtPosition(fileName: string, position: number): JsxClosingTagInfo | undefined {
            const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
            const token = findPrecedingToken(position, sourceFile);
            if (!token) return undefined;
            const element = token.kind === SyntaxKind.GreaterThanToken && isJsxOpeningElement(token.parent) ? token.parent.parent
                : isJsxText(token) ? token.parent : undefined;
            if (element && isUnclosedTag(element)) {
                return { newText: `</${element.openingElement.tagName.getText(sourceFile)}>` };
            }
        }

        function isUnclosedTag({ openingElement, closingElement, parent }: JsxElement): boolean {
            return !tagNamesAreEquivalent(openingElement.tagName, closingElement.tagName) ||
                isJsxElement(parent) && tagNamesAreEquivalent(openingElement.tagName, parent.openingElement.tagName) && isUnclosedTag(parent);
        }

        function getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan | undefined {
            const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
            const range = formatting.getRangeOfEnclosingComment(sourceFile, position);
            return range && (!onlyMultiLine || range.kind === SyntaxKind.MultiLineCommentTrivia) ? createTextSpanFromRange(range) : undefined;
        }

        function getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[] {
            // Note: while getting todo comments seems like a syntactic operation, we actually
            // treat it as a semantic operation here.  This is because we expect our host to call
            // this on every single file.  If we treat this syntactically, then that will cause
            // us to populate and throw away the tree in our syntax tree cache for each file.  By
            // treating this as a semantic operation, we can access any tree without throwing
            // anything away.
            synchronizeHostData();

            const sourceFile = getValidSourceFile(fileName);

            cancellationToken.throwIfCancellationRequested();

            const fileContents = sourceFile.text;
            const result: TodoComment[] = [];

            // Exclude node_modules files as we don't want to show the todos of external libraries.
            if (descriptors.length > 0 && !isNodeModulesFile(sourceFile.fileName)) {
                const regExp = getTodoCommentsRegExp();

                let matchArray: RegExpExecArray | null;
                while (matchArray = regExp.exec(fileContents)) {
                    cancellationToken.throwIfCancellationRequested();

                    // If we got a match, here is what the match array will look like.  Say the source text is:
                    //
                    //      "    // hack   1"
                    //
                    // The result array with the regexp:    will be:
                    //
                    //      ["// hack   1", "// ", "hack   1", undefined, "hack"]
                    //
                    // Here are the relevant capture groups:
                    //  0) The full match for the entire regexp.
                    //  1) The preamble to the message portion.
                    //  2) The message portion.
                    //  3...N) The descriptor that was matched - by index.  'undefined' for each
                    //         descriptor that didn't match.  an actual value if it did match.
                    //
                    //  i.e. 'undefined' in position 3 above means TODO(jason) didn't match.
                    //       "hack"      in position 4 means HACK did match.
                    const firstDescriptorCaptureIndex = 3;
                    Debug.assert(matchArray.length === descriptors.length + firstDescriptorCaptureIndex);

                    const preamble = matchArray[1];
                    const matchPosition = matchArray.index + preamble.length;

                    // OK, we have found a match in the file.  This is only an acceptable match if
                    // it is contained within a comment.
                    if (!isInComment(sourceFile, matchPosition)) {
                        continue;
                    }

                    let descriptor: TodoCommentDescriptor | undefined;
                    for (let i = 0; i < descriptors.length; i++) {
                        if (matchArray[i + firstDescriptorCaptureIndex]) {
                            descriptor = descriptors[i];
                        }
                    }
                    if (descriptor === undefined) return Debug.fail();

                    // We don't want to match something like 'TODOBY', so we make sure a non
                    // letter/digit follows the match.
                    if (isLetterOrDigit(fileContents.charCodeAt(matchPosition + descriptor.text.length))) {
                        continue;
                    }

                    const message = matchArray[2];
                    result.push({ descriptor, message, position: matchPosition });
                }
            }

            return result;

            function escapeRegExp(str: string): string {
                return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
            }

            function getTodoCommentsRegExp(): RegExp {
                // NOTE: `?:` means 'non-capture group'.  It allows us to have groups without having to
                // filter them out later in the final result array.

                // TODO comments can appear in one of the following forms:
                //
                //  1)      // TODO     or  /////////// TODO
                //
                //  2)      /* TODO     or  /********** TODO
                //
                //  3)      /*
                //           *   TODO
                //           */
                //
                // The following three regexps are used to match the start of the text up to the TODO
                // comment portion.
                const singleLineCommentStart = /(?:\/\/+\s*)/.source;
                const multiLineCommentStart = /(?:\/\*+\s*)/.source;
                const anyNumberOfSpacesAndAsterisksAtStartOfLine = /(?:^(?:\s|\*)*)/.source;

                // Match any of the above three TODO comment start regexps.
                // Note that the outermost group *is* a capture group.  We want to capture the preamble
                // so that we can determine the starting position of the TODO comment match.
                const preamble = "(" + anyNumberOfSpacesAndAsterisksAtStartOfLine + "|" + singleLineCommentStart + "|" + multiLineCommentStart + ")";

                // Takes the descriptors and forms a regexp that matches them as if they were literals.
                // For example, if the descriptors are "TODO(jason)" and "HACK", then this will be:
                //
                //      (?:(TODO\(jason\))|(HACK))
                //
                // Note that the outermost group is *not* a capture group, but the innermost groups
                // *are* capture groups.  By capturing the inner literals we can determine after
                // matching which descriptor we are dealing with.
                const literals = "(?:" + map(descriptors, d => "(" + escapeRegExp(d.text) + ")").join("|") + ")";

                // After matching a descriptor literal, the following regexp matches the rest of the
                // text up to the end of the line (or */).
                const endOfLineOrEndOfComment = /(?:$|\*\/)/.source;
                const messageRemainder = /(?:.*?)/.source;

                // This is the portion of the match we'll return as part of the TODO comment result. We
                // match the literal portion up to the end of the line or end of comment.
                const messagePortion = "(" + literals + messageRemainder + ")";
                const regExpString = preamble + messagePortion + endOfLineOrEndOfComment;

                // The final regexp will look like this:
                // /((?:\/\/+\s*)|(?:\/\*+\s*)|(?:^(?:\s|\*)*))((?:(TODO\(jason\))|(HACK))(?:.*?))(?:$|\*\/)/gim

                // The flags of the regexp are important here.
                //  'g' is so that we are doing a global search and can find matches several times
                //  in the input.
                //
                //  'i' is for case insensitivity (We do this to match C# TODO comment code).
                //
                //  'm' is so we can find matches in a multi-line input.
                return new RegExp(regExpString, "gim");
            }

            function isLetterOrDigit(char: number): boolean {
                return (char >= CharacterCodes.a && char <= CharacterCodes.z) ||
                    (char >= CharacterCodes.A && char <= CharacterCodes.Z) ||
                    (char >= CharacterCodes._0 && char <= CharacterCodes._9);
            }

            function isNodeModulesFile(path: string): boolean {
                return stringContains(path, "/node_modules/");
            }
        }

        function getRenameInfo(fileName: string, position: number, options?: RenameInfoOptions): RenameInfo {
            synchronizeHostData();
            return Rename.getRenameInfo(program, getValidSourceFile(fileName), position, options);
        }

        function getRefactorContext(file: SourceFile, positionOrRange: number | TextRange, preferences: UserPreferences, formatOptions?: FormatCodeSettings): RefactorContext {
            const [startPosition, endPosition] = typeof positionOrRange === "number" ? [positionOrRange, undefined] : [positionOrRange.pos, positionOrRange.end];
            return {
                file,
                startPosition,
                endPosition,
                program: getProgram()!,
                host,
                formatContext: formatting.getFormatContext(formatOptions!), // TODO: GH#18217
                cancellationToken,
                preferences,
            };
        }

        function getSmartSelectionRange(fileName: string, position: number): SelectionRange {
            return SmartSelectionRange.getSmartSelectionRange(position, syntaxTreeCache.getCurrentSourceFile(fileName));
        }

        function getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences = emptyOptions): ApplicableRefactorInfo[] {
            synchronizeHostData();
            const file = getValidSourceFile(fileName);
            return refactor.getApplicableRefactors(getRefactorContext(file, positionOrRange, preferences));
        }

        function getEditsForRefactor(
            fileName: string,
            formatOptions: FormatCodeSettings,
            positionOrRange: number | TextRange,
            refactorName: string,
            actionName: string,
            preferences: UserPreferences = emptyOptions,
        ): RefactorEditInfo | undefined {
            synchronizeHostData();
            const file = getValidSourceFile(fileName);
            return refactor.getEditsForRefactor(getRefactorContext(file, positionOrRange, preferences, formatOptions), refactorName, actionName);
        }

        function prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined {
            synchronizeHostData();
            const declarations = CallHierarchy.resolveCallHierarchyDeclaration(program, getTouchingPropertyName(getValidSourceFile(fileName), position));
            return declarations && mapOneOrMany(declarations, declaration => CallHierarchy.createCallHierarchyItem(program, declaration));
        }

        function provideCallHierarchyIncomingCalls(fileName: string, position: number): CallHierarchyIncomingCall[] {
            synchronizeHostData();
            const sourceFile = getValidSourceFile(fileName);
            const declaration = firstOrOnly(CallHierarchy.resolveCallHierarchyDeclaration(program, position === 0 ? sourceFile : getTouchingPropertyName(sourceFile, position)));
            return declaration ? CallHierarchy.getIncomingCalls(program, declaration, cancellationToken) : [];
        }

        function provideCallHierarchyOutgoingCalls(fileName: string, position: number): CallHierarchyOutgoingCall[] {
            synchronizeHostData();
            const sourceFile = getValidSourceFile(fileName);
            const declaration = firstOrOnly(CallHierarchy.resolveCallHierarchyDeclaration(program, position === 0 ? sourceFile : getTouchingPropertyName(sourceFile, position)));
            return declaration ? CallHierarchy.getOutgoingCalls(program, declaration) : [];
        }

        return {
            dispose,
            cleanupSemanticCache,
            getSyntacticDiagnostics,
            getSemanticDiagnostics,
            getSuggestionDiagnostics,
            getCompilerOptionsDiagnostics,
            getSyntacticClassifications,
            getSemanticClassifications,
            getEncodedSyntacticClassifications,
            getEncodedSemanticClassifications,
            getCompletionsAtPosition,
            getCompletionEntryDetails,
            getCompletionEntrySymbol,
            getSignatureHelpItems,
            getQuickInfoAtPosition,
            getDefinitionAtPosition,
            getDefinitionAndBoundSpan,
            getImplementationAtPosition,
            getTypeDefinitionAtPosition,
            getReferencesAtPosition,
            findReferences,
            getOccurrencesAtPosition,
            getDocumentHighlights,
            getNameOrDottedNameSpan,
            getBreakpointStatementAtPosition,
            getNavigateToItems,
            getRenameInfo,
            getSmartSelectionRange,
            findRenameLocations,
            getNavigationBarItems,
            getNavigationTree,
            getOutliningSpans,
            getTodoComments,
            getBraceMatchingAtPosition,
            getIndentationAtPosition,
            getFormattingEditsForRange,
            getFormattingEditsForDocument,
            getFormattingEditsAfterKeystroke,
            getDocCommentTemplateAtPosition,
            isValidBraceCompletionAtPosition,
            getJsxClosingTagAtPosition,
            getSpanOfEnclosingComment,
            getCodeFixesAtPosition,
            getCombinedCodeFix,
            applyCodeActionCommand,
            organizeImports,
            getEditsForFileRename,
            getEmitOutput,
            getNonBoundSourceFile,
            getProgram,
            getApplicableRefactors,
            getEditsForRefactor,
            toLineColumnOffset: sourceMapper.toLineColumnOffset,
            getSourceMapper: () => sourceMapper,
            clearSourceMapperCache: () => sourceMapper.clearCache(),
            prepareCallHierarchy,
            provideCallHierarchyIncomingCalls,
            provideCallHierarchyOutgoingCalls
        };
    }

    /* @internal */
    /** Names in the name table are escaped, so an identifier `__foo` will have a name table entry `___foo`. */
    export function getNameTable(sourceFile: SourceFile): UnderscoreEscapedMap<number> {
        if (!sourceFile.nameTable) {
            initializeNameTable(sourceFile);
        }

        return sourceFile.nameTable!; // TODO: GH#18217
    }

    function initializeNameTable(sourceFile: SourceFile): void {
        const nameTable = sourceFile.nameTable = createUnderscoreEscapedMap<number>();
        sourceFile.forEachChild(function walk(node) {
            if (isIdentifier(node) && !isTagName(node) && node.escapedText || isStringOrNumericLiteralLike(node) && literalIsName(node)) {
                const text = getEscapedTextOfIdentifierOrLiteral(node);
                nameTable.set(text, nameTable.get(text) === undefined ? node.pos : -1);
            }
            else if (isPrivateIdentifier(node)) {
                const text = node.escapedText;
                nameTable.set(text, nameTable.get(text) === undefined ? node.pos : -1);
            }

            forEachChild(node, walk);
            if (hasJSDocNodes(node)) {
                for (const jsDoc of node.jsDoc!) {
                    forEachChild(jsDoc, walk);
                }
            }
        });
    }

    /**
     * We want to store any numbers/strings if they were a name that could be
     * related to a declaration.  So, if we have 'import x = require("something")'
     * then we want 'something' to be in the name table.  Similarly, if we have
     * "a['propname']" then we want to store "propname" in the name table.
     */
    function literalIsName(node: StringLiteralLike | NumericLiteral): boolean {
        return isDeclarationName(node) ||
            node.parent.kind === SyntaxKind.ExternalModuleReference ||
            isArgumentOfElementAccessExpression(node) ||
            isLiteralComputedPropertyDeclarationName(node);
    }

    /**
     * Returns the containing object literal property declaration given a possible name node, e.g. "a" in x = { "a": 1 }
     */
    /* @internal */
    export function getContainingObjectLiteralElement(node: Node): ObjectLiteralElementWithName | undefined {
        const element = getContainingObjectLiteralElementWorker(node);
        return element && (isObjectLiteralExpression(element.parent) || isJsxAttributes(element.parent)) ? element as ObjectLiteralElementWithName : undefined;
    }
    function getContainingObjectLiteralElementWorker(node: Node): ObjectLiteralElement | undefined {
        switch (node.kind) {
            case SyntaxKind.StringLiteral:
            case SyntaxKind.NoSubstitutionTemplateLiteral:
            case SyntaxKind.NumericLiteral:
                if (node.parent.kind === SyntaxKind.ComputedPropertyName) {
                    return isObjectLiteralElement(node.parent.parent) ? node.parent.parent : undefined;
                }
                // falls through

            case SyntaxKind.Identifier:
                return isObjectLiteralElement(node.parent) &&
                    (node.parent.parent.kind === SyntaxKind.ObjectLiteralExpression || node.parent.parent.kind === SyntaxKind.JsxAttributes) &&
                    node.parent.name === node ? node.parent : undefined;
        }
        return undefined;
    }

    /* @internal */
    export type ObjectLiteralElementWithName = ObjectLiteralElement & { name: PropertyName; parent: ObjectLiteralExpression | JsxAttributes };

    function getSymbolAtLocationForQuickInfo(node: Node, checker: TypeChecker): Symbol | undefined {
        const object = getContainingObjectLiteralElement(node);
        if (object) {
            const contextualType = checker.getContextualType(object.parent);
            const properties = contextualType && getPropertySymbolsFromContextualType(object, checker, contextualType, /*unionSymbolOk*/ false);
            if (properties && properties.length === 1) {
                return first(properties);
            }
        }
        return checker.getSymbolAtLocation(node);
    }

    /** Gets all symbols for one property. Does not get symbols for every property. */
    /* @internal */
    export function getPropertySymbolsFromContextualType(node: ObjectLiteralElementWithName, checker: TypeChecker, contextualType: Type, unionSymbolOk: boolean): readonly Symbol[] {
        const name = getNameFromPropertyName(node.name);
        if (!name) return emptyArray;
        if (!contextualType.isUnion()) {
            const symbol = contextualType.getProperty(name);
            return symbol ? [symbol] : emptyArray;
        }

        const discriminatedPropertySymbols = mapDefined(contextualType.types, t => isObjectLiteralExpression(node.parent) && checker.isTypeInvalidDueToUnionDiscriminant(t, node.parent) ? undefined : t.getProperty(name));
        if (unionSymbolOk && (discriminatedPropertySymbols.length === 0 || discriminatedPropertySymbols.length === contextualType.types.length)) {
            const symbol = contextualType.getProperty(name);
            if (symbol) return [symbol];
        }
        if (discriminatedPropertySymbols.length === 0) {
            // Bad discriminant -- do again without discriminating
            return mapDefined(contextualType.types, t => t.getProperty(name));
        }
        return discriminatedPropertySymbols;
    }

    function isArgumentOfElementAccessExpression(node: Node) {
        return node &&
            node.parent &&
            node.parent.kind === SyntaxKind.ElementAccessExpression &&
            (<ElementAccessExpression>node.parent).argumentExpression === node;
    }

    /// getDefaultLibraryFilePath
    declare const __dirname: string;

    /**
     * Get the path of the default library files (lib.d.ts) as distributed with the typescript
     * node package.
     * The functionality is not supported if the ts module is consumed outside of a node module.
     */
    export function getDefaultLibFilePath(options: CompilerOptions): string {
        // Check __dirname is defined and that we are on a node.js system.
        if (typeof __dirname !== "undefined") {
            return __dirname + directorySeparator + getDefaultLibFileName(options);
        }

        throw new Error("getDefaultLibFilePath is only supported when consumed as a node module. ");
    }

    setObjectAllocator(getServicesObjectAllocator());
}
