/* @internal */
namespace ts.Completions {
    export type Log = (message: string) => void;

    export function getCompletionsAtPosition(host: LanguageServiceHost, typeChecker: TypeChecker, log: Log, compilerOptions: CompilerOptions, sourceFile: SourceFile, position: number): CompletionInfo | undefined {
        if (isInReferenceComment(sourceFile, position)) {
            return getTripleSlashReferenceCompletion(sourceFile, position, compilerOptions, host);
        }

        if (isInString(sourceFile, position)) {
            return getStringLiteralCompletionEntries(sourceFile, position, typeChecker, compilerOptions, host, log);
        }

        const completionData = getCompletionData(typeChecker, log, sourceFile, position);
        if (!completionData) {
            return undefined;
        }

        const { symbols, isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, location, isJsDocTagName } = completionData;

        if (isJsDocTagName) {
            // If the current position is a jsDoc tag name, only tag names should be provided for completion
            return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries: JsDoc.getAllJsDocCompletionEntries() };
        }

        const entries: CompletionEntry[] = [];

        if (isSourceFileJavaScript(sourceFile)) {
            const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true, typeChecker, compilerOptions.target, log);
            addRange(entries, getJavaScriptCompletionEntries(sourceFile, location.pos, uniqueNames, compilerOptions.target));
        }
        else {
            if (!symbols || symbols.length === 0) {
                if (sourceFile.languageVariant === LanguageVariant.JSX &&
                    location.parent && location.parent.kind === SyntaxKind.JsxClosingElement) {
                    // In the TypeScript JSX element, if such element is not defined. When users query for completion at closing tag,
                    // instead of simply giving unknown value, the completion will return the tag-name of an associated opening-element.
                    // For example:
                    //     var x = <div> </ /*1*/>  completion list at "1" will contain "div" with type any
                    const tagName = (<JsxElement>location.parent.parent).openingElement.tagName;
                    entries.push({
                        name: (<Identifier>tagName).text,
                        kind: undefined,
                        kindModifiers: undefined,
                        sortText: "0",
                    });
                }
                else {
                    return undefined;
                }
            }

            getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true, typeChecker, compilerOptions.target, log);
        }

        // Add keywords if this is not a member completion list
        if (!isMemberCompletion && !isJsDocTagName) {
            addRange(entries, keywordCompletions);
        }

        return { isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation: isNewIdentifierLocation, entries };
    }

    function getJavaScriptCompletionEntries(sourceFile: SourceFile, position: number, uniqueNames: Map<string>, target: ScriptTarget): CompletionEntry[] {
        const entries: CompletionEntry[] = [];

        const nameTable = getNameTable(sourceFile);
        nameTable.forEach((pos, name) => {
            // Skip identifiers produced only from the current location
            if (pos === position) {
                return;
            }

            if (!uniqueNames.get(name)) {
                uniqueNames.set(name, name);
                const displayName = getCompletionEntryDisplayName(unescapeIdentifier(name), target, /*performCharacterChecks*/ true);
                if (displayName) {
                    const entry = {
                        name: displayName,
                        kind: ScriptElementKind.warning,
                        kindModifiers: "",
                        sortText: "1"
                    };
                    entries.push(entry);
                }
            }
        });

        return entries;
    }

    function createCompletionEntry(symbol: Symbol, location: Node, performCharacterChecks: boolean, typeChecker: TypeChecker, target: ScriptTarget): CompletionEntry {
        // Try to get a valid display name for this symbol, if we could not find one, then ignore it.
        // We would like to only show things that can be added after a dot, so for instance numeric properties can
        // not be accessed with a dot (a.1 <- invalid)
        const displayName = getCompletionEntryDisplayNameForSymbol(typeChecker, symbol, target, performCharacterChecks, location);
        if (!displayName) {
            return undefined;
        }

        // TODO(drosen): Right now we just permit *all* semantic meanings when calling
        // 'getSymbolKind' which is permissible given that it is backwards compatible; but
        // really we should consider passing the meaning for the node so that we don't report
        // that a suggestion for a value is an interface.  We COULD also just do what
        // 'getSymbolModifiers' does, which is to use the first declaration.

        // Use a 'sortText' of 0' so that all symbol completion entries come before any other
        // entries (like JavaScript identifier entries).
        return {
            name: displayName,
            kind: SymbolDisplay.getSymbolKind(typeChecker, symbol, location),
            kindModifiers: SymbolDisplay.getSymbolModifiers(symbol),
            sortText: "0",
        };
    }

    function getCompletionEntriesFromSymbols(symbols: Symbol[], entries: Push<CompletionEntry>, location: Node, performCharacterChecks: boolean, typeChecker: TypeChecker, target: ScriptTarget, log: Log): Map<string> {
        const start = timestamp();
        const uniqueNames = createMap<string>();
        if (symbols) {
            for (const symbol of symbols) {
                const entry = createCompletionEntry(symbol, location, performCharacterChecks, typeChecker, target);
                if (entry) {
                    const id = escapeIdentifier(entry.name);
                    if (!uniqueNames.get(id)) {
                        entries.push(entry);
                        uniqueNames.set(id, id);
                    }
                }
            }
        }

        log("getCompletionsAtPosition: getCompletionEntriesFromSymbols: " + (timestamp() - start));
        return uniqueNames;
    }

    function getStringLiteralCompletionEntries(sourceFile: SourceFile, position: number, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost, log: Log): CompletionInfo | undefined {
        const node = findPrecedingToken(position, sourceFile);
        if (!node || node.kind !== SyntaxKind.StringLiteral) {
            return undefined;
        }

        if (node.parent.kind === SyntaxKind.PropertyAssignment &&
            node.parent.parent.kind === SyntaxKind.ObjectLiteralExpression &&
            (<PropertyAssignment>node.parent).name === node) {
            // Get quoted name of properties of the object literal expression
            // i.e. interface ConfigFiles {
            //          'jspm:dev': string
            //      }
            //      let files: ConfigFiles = {
            //          '/*completion position*/'
            //      }
            //
            //      function foo(c: ConfigFiles) {}
            //      foo({
            //          '/*completion position*/'
            //      });
            return getStringLiteralCompletionEntriesFromPropertyAssignment(<ObjectLiteralElement>node.parent, typeChecker, compilerOptions.target, log);
        }
        else if (isElementAccessExpression(node.parent) && node.parent.argumentExpression === node) {
            // Get all names of properties on the expression
            // i.e. interface A {
            //      'prop1': string
            // }
            // let a: A;
            // a['/*completion position*/']
            return getStringLiteralCompletionEntriesFromElementAccess(node.parent, typeChecker, compilerOptions.target, log);
        }
        else if (node.parent.kind === SyntaxKind.ImportDeclaration || isExpressionOfExternalModuleImportEqualsDeclaration(node) || isRequireCall(node.parent, false)) {
            // Get all known external module names or complete a path to a module
            // i.e. import * as ns from "/*completion position*/";
            //      import x = require("/*completion position*/");
            //      var y = require("/*completion position*/");
            return getStringLiteralCompletionEntriesFromModuleNames(<StringLiteral>node, compilerOptions, host, typeChecker);
        }
        else if (isEqualityExpression(node.parent)) {
            // Get completions from the type of the other operand
            // i.e. switch (a) {
            //         case '/*completion position*/'
            //      }
            return getStringLiteralCompletionEntriesFromType(typeChecker.getTypeAtLocation(node.parent.left === node ? node.parent.right : node.parent.left), typeChecker);
        }
        else if (isCaseOrDefaultClause(node.parent)) {
            // Get completions from the type of the switch expression
            // i.e. x === '/*completion position'
            return getStringLiteralCompletionEntriesFromType(typeChecker.getTypeAtLocation((<SwitchStatement>node.parent.parent.parent).expression), typeChecker);
        }
        else {
            const argumentInfo = SignatureHelp.getImmediatelyContainingArgumentInfo(node, position, sourceFile);
            if (argumentInfo) {
                // Get string literal completions from specialized signatures of the target
                // i.e. declare function f(a: 'A');
                // f("/*completion position*/")
                return getStringLiteralCompletionEntriesFromCallExpression(argumentInfo, typeChecker);
            }

            // Get completion for string literal from string literal type
            // i.e. var x: "hi" | "hello" = "/*completion position*/"
            return getStringLiteralCompletionEntriesFromType(typeChecker.getContextualType(<StringLiteral>node), typeChecker);
        }
    }

    function getStringLiteralCompletionEntriesFromPropertyAssignment(element: ObjectLiteralElement, typeChecker: TypeChecker, target: ScriptTarget, log: Log): CompletionInfo | undefined {
        const type = typeChecker.getContextualType((<ObjectLiteralExpression>element.parent));
        const entries: CompletionEntry[] = [];
        if (type) {
            getCompletionEntriesFromSymbols(type.getApparentProperties(), entries, element, /*performCharacterChecks*/false, typeChecker, target, log);
            if (entries.length) {
                return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: true, entries };
            }
        }
    }

    function getStringLiteralCompletionEntriesFromCallExpression(argumentInfo: SignatureHelp.ArgumentListInfo, typeChecker: TypeChecker): CompletionInfo | undefined {
        const candidates: Signature[] = [];
        const entries: CompletionEntry[] = [];

        typeChecker.getResolvedSignature(argumentInfo.invocation, candidates);

        for (const candidate of candidates) {
            addStringLiteralCompletionsFromType(typeChecker.getParameterType(candidate, argumentInfo.argumentIndex), entries, typeChecker);
        }

        if (entries.length) {
            return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: true, entries };
        }

        return undefined;
    }

    function getStringLiteralCompletionEntriesFromElementAccess(node: ElementAccessExpression, typeChecker: TypeChecker, target: ScriptTarget, log: Log): CompletionInfo | undefined {
        const type = typeChecker.getTypeAtLocation(node.expression);
        const entries: CompletionEntry[] = [];
        if (type) {
            getCompletionEntriesFromSymbols(type.getApparentProperties(), entries, node, /*performCharacterChecks*/false, typeChecker, target, log);
            if (entries.length) {
                return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: true, entries };
            }
        }
        return undefined;
    }

    function getStringLiteralCompletionEntriesFromType(type: Type, typeChecker: TypeChecker): CompletionInfo | undefined {
        if (type) {
            const entries: CompletionEntry[] = [];
            addStringLiteralCompletionsFromType(type, entries, typeChecker);
            if (entries.length) {
                return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries };
            }
        }
        return undefined;
    }

    function addStringLiteralCompletionsFromType(type: Type, result: Push<CompletionEntry>, typeChecker: TypeChecker): void {
        if (type && type.flags & TypeFlags.TypeParameter) {
            type = typeChecker.getApparentType(type);
        }
        if (!type) {
            return;
        }
        if (type.flags & TypeFlags.Union) {
            for (const t of (<UnionType>type).types) {
                addStringLiteralCompletionsFromType(t, result, typeChecker);
            }
        }
        else if (type.flags & TypeFlags.StringLiteral) {
            result.push({
                name: (<LiteralType>type).text,
                kindModifiers: ScriptElementKindModifier.none,
                kind: ScriptElementKind.variableElement,
                sortText: "0"
            });
        }
    }

    function getStringLiteralCompletionEntriesFromModuleNames(node: StringLiteral, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): CompletionInfo {
        const literalValue = normalizeSlashes(node.text);

        const scriptPath = node.getSourceFile().path;
        const scriptDirectory = getDirectoryPath(scriptPath);

        const span = getDirectoryFragmentTextSpan((<StringLiteral>node).text, node.getStart() + 1);
        let entries: CompletionEntry[];
        if (isPathRelativeToScript(literalValue) || isRootedDiskPath(literalValue)) {
            const extensions = getSupportedExtensions(compilerOptions);
            if (compilerOptions.rootDirs) {
                entries = getCompletionEntriesForDirectoryFragmentWithRootDirs(
                    compilerOptions.rootDirs, literalValue, scriptDirectory, extensions, /*includeExtensions*/false, span, compilerOptions, host, scriptPath);
            }
            else {
                entries = getCompletionEntriesForDirectoryFragment(
                    literalValue, scriptDirectory, extensions, /*includeExtensions*/false, span, host, scriptPath);
            }
        }
        else {
            // Check for node modules
            entries = getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, span, compilerOptions, host, typeChecker);
        }
        return {
            isGlobalCompletion: false,
            isMemberCompletion: false,
            isNewIdentifierLocation: true,
            entries
        };
    }

    /**
     * Takes a script path and returns paths for all potential folders that could be merged with its
     * containing folder via the "rootDirs" compiler option
     */
    function getBaseDirectoriesFromRootDirs(rootDirs: string[], basePath: string, scriptPath: string, ignoreCase: boolean): string[] {
        // Make all paths absolute/normalized if they are not already
        rootDirs = map(rootDirs, rootDirectory => normalizePath(isRootedDiskPath(rootDirectory) ? rootDirectory : combinePaths(basePath, rootDirectory)));

        // Determine the path to the directory containing the script relative to the root directory it is contained within
        let relativeDirectory: string;
        for (const rootDirectory of rootDirs) {
            if (containsPath(rootDirectory, scriptPath, basePath, ignoreCase)) {
                relativeDirectory = scriptPath.substr(rootDirectory.length);
                break;
            }
        }

        // Now find a path for each potential directory that is to be merged with the one containing the script
        return deduplicate(map(rootDirs, rootDirectory => combinePaths(rootDirectory, relativeDirectory)));
    }

    function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, span: TextSpan, compilerOptions: CompilerOptions, host: LanguageServiceHost, exclude?: string): CompletionEntry[] {
        const basePath = compilerOptions.project || host.getCurrentDirectory();
        const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames());
        const baseDirectories = getBaseDirectoriesFromRootDirs(rootDirs, basePath, scriptPath, ignoreCase);

        const result: CompletionEntry[] = [];

        for (const baseDirectory of baseDirectories) {
            getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensions, includeExtensions, span, host, exclude, result);
        }

        return result;
    }

    /**
     * Given a path ending at a directory, gets the completions for the path, and filters for those entries containing the basename.
     */
    function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, span: TextSpan, host: LanguageServiceHost, exclude?: string, result: CompletionEntry[] = []): CompletionEntry[] {
        if (fragment === undefined) {
            fragment = "";
        }

        fragment = normalizeSlashes(fragment);

        /**
         * Remove the basename from the path. Note that we don't use the basename to filter completions;
         * the client is responsible for refining completions.
         */
        fragment = getDirectoryPath(fragment);

        if (fragment === "") {
            fragment = "." + directorySeparator;
        }

        fragment = ensureTrailingDirectorySeparator(fragment);

        const absolutePath = normalizeAndPreserveTrailingSlash(isRootedDiskPath(fragment) ? fragment : combinePaths(scriptPath, fragment));
        const baseDirectory = getDirectoryPath(absolutePath);
        const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames());

        if (tryDirectoryExists(host, baseDirectory)) {
            // Enumerate the available files if possible
            const files = tryReadDirectory(host, baseDirectory, extensions, /*exclude*/undefined, /*include*/["./*"]);

            if (files) {
                /**
                 * Multiple file entries might map to the same truncated name once we remove extensions
                 * (happens iff includeExtensions === false)so we use a set-like data structure. Eg:
                 *
                 * both foo.ts and foo.tsx become foo
                 */
                const foundFiles = createMap<boolean>();
                for (let filePath of files) {
                    filePath = normalizePath(filePath);
                    if (exclude && comparePaths(filePath, exclude, scriptPath, ignoreCase) === Comparison.EqualTo) {
                        continue;
                    }

                    const foundFileName = includeExtensions ? getBaseFileName(filePath) : removeFileExtension(getBaseFileName(filePath));

                    if (!foundFiles.get(foundFileName)) {
                        foundFiles.set(foundFileName, true);
                    }
                }

                forEachKey(foundFiles, foundFile => {
                    result.push(createCompletionEntryForModule(foundFile, ScriptElementKind.scriptElement, span));
                });
            }

            // If possible, get folder completion as well
            const directories = tryGetDirectories(host, baseDirectory);

            if (directories) {
                for (const directory of directories) {
                    const directoryName = getBaseFileName(normalizePath(directory));

                    result.push(createCompletionEntryForModule(directoryName, ScriptElementKind.directory, span));
                }
            }
        }

        return result;
    }

    /**
     * Check all of the declared modules and those in node modules. Possible sources of modules:
     *      Modules that are found by the type checker
     *      Modules found relative to "baseUrl" compliler options (including patterns from "paths" compiler option)
     *      Modules from node_modules (i.e. those listed in package.json)
     *          This includes all files that are found in node_modules/moduleName/ with acceptable file extensions
     */
    function getCompletionEntriesForNonRelativeModules(fragment: string, scriptPath: string, span: TextSpan, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): CompletionEntry[] {
        const { baseUrl, paths } = compilerOptions;

        let result: CompletionEntry[];

        if (baseUrl) {
            const fileExtensions = getSupportedExtensions(compilerOptions);
            const projectDir = compilerOptions.project || host.getCurrentDirectory();
            const absolute = isRootedDiskPath(baseUrl) ? baseUrl : combinePaths(projectDir, baseUrl);
            result = getCompletionEntriesForDirectoryFragment(fragment, normalizePath(absolute), fileExtensions, /*includeExtensions*/false, span, host);

            if (paths) {
                for (const path in paths) {
                    if (paths.hasOwnProperty(path)) {
                        if (path === "*") {
                            if (paths[path]) {
                                for (const pattern of paths[path]) {
                                    for (const match of getModulesForPathsPattern(fragment, baseUrl, pattern, fileExtensions, host)) {
                                        result.push(createCompletionEntryForModule(match, ScriptElementKind.externalModuleName, span));
                                    }
                                }
                            }
                        }
                        else if (startsWith(path, fragment)) {
                            const entry = paths[path] && paths[path].length === 1 && paths[path][0];
                            if (entry) {
                                result.push(createCompletionEntryForModule(path, ScriptElementKind.externalModuleName, span));
                            }
                        }
                    }
                }
            }
        }
        else {
            result = [];
        }

        getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, span, result);

        for (const moduleName of enumeratePotentialNonRelativeModules(fragment, scriptPath, compilerOptions, typeChecker, host)) {
            result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName, span));
        }

        return result;
    }

    function getModulesForPathsPattern(fragment: string, baseUrl: string, pattern: string, fileExtensions: string[], host: LanguageServiceHost): string[] {
        if (host.readDirectory) {
            const parsed = hasZeroOrOneAsteriskCharacter(pattern) ? tryParsePattern(pattern) : undefined;
            if (parsed) {
                // The prefix has two effective parts: the directory path and the base component after the filepath that is not a
                // full directory component. For example: directory/path/of/prefix/base*
                const normalizedPrefix = normalizeAndPreserveTrailingSlash(parsed.prefix);
                const normalizedPrefixDirectory = getDirectoryPath(normalizedPrefix);
                const normalizedPrefixBase = getBaseFileName(normalizedPrefix);

                const fragmentHasPath = fragment.indexOf(directorySeparator) !== -1;

                // Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call
                const expandedPrefixDirectory = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + getDirectoryPath(fragment)) : normalizedPrefixDirectory;

                const normalizedSuffix = normalizePath(parsed.suffix);
                const baseDirectory = combinePaths(baseUrl, expandedPrefixDirectory);
                const completePrefix = fragmentHasPath ? baseDirectory : ensureTrailingDirectorySeparator(baseDirectory) + normalizedPrefixBase;

                // If we have a suffix, then we need to read the directory all the way down. We could create a glob
                // that encodes the suffix, but we would have to escape the character "?" which readDirectory
                // doesn't support. For now, this is safer but slower
                const includeGlob = normalizedSuffix ? "**/*" : "./*";

                const matches = tryReadDirectory(host, baseDirectory, fileExtensions, undefined, [includeGlob]);
                if (matches) {
                    const result: string[] = [];

                    // Trim away prefix and suffix
                    for (const match of matches) {
                        const normalizedMatch = normalizePath(match);
                        if (!endsWith(normalizedMatch, normalizedSuffix) || !startsWith(normalizedMatch, completePrefix)) {
                            continue;
                        }

                        const start = completePrefix.length;
                        const length = normalizedMatch.length - start - normalizedSuffix.length;

                        result.push(removeFileExtension(normalizedMatch.substr(start, length)));
                    }
                    return result;
                }
            }
        }

        return undefined;
    }

    function enumeratePotentialNonRelativeModules(fragment: string, scriptPath: string, options: CompilerOptions, typeChecker: TypeChecker, host: LanguageServiceHost): string[] {
        // Check If this is a nested module
        const isNestedModule = fragment.indexOf(directorySeparator) !== -1;
        const moduleNameFragment = isNestedModule ? fragment.substr(0, fragment.lastIndexOf(directorySeparator)) : undefined;

        // Get modules that the type checker picked up
        const ambientModules = map(typeChecker.getAmbientModules(), sym => stripQuotes(sym.name));
        let nonRelativeModules = filter(ambientModules, moduleName => startsWith(moduleName, fragment));

        // Nested modules of the form "module-name/sub" need to be adjusted to only return the string
        // after the last '/' that appears in the fragment because that's where the replacement span
        // starts
        if (isNestedModule) {
            const moduleNameWithSeperator = ensureTrailingDirectorySeparator(moduleNameFragment);
            nonRelativeModules = map(nonRelativeModules, moduleName => {
                if (startsWith(fragment, moduleNameWithSeperator)) {
                    return moduleName.substr(moduleNameWithSeperator.length);
                }
                return moduleName;
            });
        }


        if (!options.moduleResolution || options.moduleResolution === ModuleResolutionKind.NodeJs) {
            for (const visibleModule of enumerateNodeModulesVisibleToScript(host, scriptPath)) {
                if (!isNestedModule) {
                    nonRelativeModules.push(visibleModule.moduleName);
                }
                else if (startsWith(visibleModule.moduleName, moduleNameFragment)) {
                    const nestedFiles = tryReadDirectory(host, visibleModule.moduleDir, supportedTypeScriptExtensions, /*exclude*/undefined, /*include*/["./*"]);
                    if (nestedFiles) {
                        for (let f of nestedFiles) {
                            f = normalizePath(f);
                            const nestedModule = removeFileExtension(getBaseFileName(f));
                            nonRelativeModules.push(nestedModule);
                        }
                    }
                }
            }
        }

        return deduplicate(nonRelativeModules);
    }

    function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number, compilerOptions: CompilerOptions, host: LanguageServiceHost): CompletionInfo {
        const token = getTokenAtPosition(sourceFile, position);
        if (!token) {
            return undefined;
        }
        const commentRanges: CommentRange[] = getLeadingCommentRanges(sourceFile.text, token.pos);

        if (!commentRanges || !commentRanges.length) {
            return undefined;
        }

        const range = forEach(commentRanges, commentRange => position >= commentRange.pos && position <= commentRange.end && commentRange);

        if (!range) {
            return undefined;
        }

        const completionInfo: CompletionInfo = {
            /**
             * We don't want the editor to offer any other completions, such as snippets, inside a comment.
             */
            isGlobalCompletion: false,
            isMemberCompletion: false,
            /**
             * The user may type in a path that doesn't yet exist, creating a "new identifier"
             * with respect to the collection of identifiers the server is aware of.
             */
            isNewIdentifierLocation: true,

            entries: []
        };

        const text = sourceFile.text.substr(range.pos, position - range.pos);

        const match = tripleSlashDirectiveFragmentRegex.exec(text);

        if (match) {
            const prefix = match[1];
            const kind = match[2];
            const toComplete = match[3];

            const scriptPath = getDirectoryPath(sourceFile.path);
            if (kind === "path") {
                // Give completions for a relative path
                const span: TextSpan = getDirectoryFragmentTextSpan(toComplete, range.pos + prefix.length);
                completionInfo.entries = getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getSupportedExtensions(compilerOptions), /*includeExtensions*/true, span, host, sourceFile.path);
            }
            else {
                // Give completions based on the typings available
                const span: TextSpan = { start: range.pos + prefix.length, length: match[0].length - prefix.length };
                completionInfo.entries = getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, span);
            }
        }

        return completionInfo;
    }

    function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, span: TextSpan, result: CompletionEntry[] = []): CompletionEntry[] {
        // Check for typings specified in compiler options
        if (options.types) {
            for (const moduleName of options.types) {
                result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName, span));
            }
        }
        else if (host.getDirectories) {
            let typeRoots: string[];
            try {
                // Wrap in try catch because getEffectiveTypeRoots touches the filesystem
                typeRoots = getEffectiveTypeRoots(options, host);
            }
            catch (e) {}

            if (typeRoots) {
                for (const root of typeRoots) {
                    getCompletionEntriesFromDirectories(host, root, span, result);
                }
            }
        }

        if (host.getDirectories) {
            // Also get all @types typings installed in visible node_modules directories
            for (const packageJson of findPackageJsons(scriptPath, host)) {
                const typesDir = combinePaths(getDirectoryPath(packageJson), "node_modules/@types");
                getCompletionEntriesFromDirectories(host, typesDir, span, result);
            }
        }

        return result;
    }

    function getCompletionEntriesFromDirectories(host: LanguageServiceHost, directory: string, span: TextSpan, result: Push<CompletionEntry>) {
        if (host.getDirectories && tryDirectoryExists(host, directory)) {
            const directories = tryGetDirectories(host, directory);
            if (directories) {
                for (let typeDirectory of directories) {
                    typeDirectory = normalizePath(typeDirectory);
                    result.push(createCompletionEntryForModule(getBaseFileName(typeDirectory), ScriptElementKind.externalModuleName, span));
                }
            }
        }
    }

    function findPackageJsons(currentDir: string, host: LanguageServiceHost): string[] {
        const paths: string[] = [];
        let currentConfigPath: string;
        while (true) {
            currentConfigPath = findConfigFile(currentDir, (f) => tryFileExists(host, f), "package.json");
            if (currentConfigPath) {
                paths.push(currentConfigPath);

                currentDir = getDirectoryPath(currentConfigPath);
                const parent = getDirectoryPath(currentDir);
                if (currentDir === parent) {
                    break;
                }
                currentDir = parent;
            }
            else {
                break;
            }
        }

        return paths;
    }

    function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string) {
        const result: VisibleModuleInfo[] = [];

        if (host.readFile && host.fileExists) {
            for (const packageJson of findPackageJsons(scriptPath, host)) {
                const contents = tryReadingPackageJson(packageJson);
                if (!contents) {
                    return;
                }

                const nodeModulesDir = combinePaths(getDirectoryPath(packageJson), "node_modules");
                const foundModuleNames: string[] = [];

                // Provide completions for all non @types dependencies
                for (const key of nodeModulesDependencyKeys) {
                    addPotentialPackageNames(contents[key], foundModuleNames);
                }

                for (const moduleName of foundModuleNames) {
                    const moduleDir = combinePaths(nodeModulesDir, moduleName);
                    result.push({
                        moduleName,
                        moduleDir
                    });
                }
            }
        }

        return result;

        function tryReadingPackageJson(filePath: string) {
            try {
                const fileText = tryReadFile(host, filePath);
                return fileText ? JSON.parse(fileText) : undefined;
            }
            catch (e) {
                return undefined;
            }
        }

        function addPotentialPackageNames(dependencies: any, result: string[]) {
            if (dependencies) {
                for (const dep in dependencies) {
                    if (dependencies.hasOwnProperty(dep) && !startsWith(dep, "@types/")) {
                        result.push(dep);
                    }
                }
            }
        }
    }

    function createCompletionEntryForModule(name: string, kind: string, replacementSpan: TextSpan): CompletionEntry {
        return { name, kind, kindModifiers: ScriptElementKindModifier.none, sortText: name, replacementSpan };
    }

    // Replace everything after the last directory seperator that appears
    function getDirectoryFragmentTextSpan(text: string, textStart: number): TextSpan {
        const index = text.lastIndexOf(directorySeparator);
        const offset = index !== -1 ? index + 1 : 0;
        return { start: textStart + offset, length: text.length - offset };
    }

    // Returns true if the path is explicitly relative to the script (i.e. relative to . or ..)
    function isPathRelativeToScript(path: string) {
        if (path && path.length >= 2 && path.charCodeAt(0) === CharacterCodes.dot) {
            const slashIndex = path.length >= 3 && path.charCodeAt(1) === CharacterCodes.dot ? 2 : 1;
            const slashCharCode = path.charCodeAt(slashIndex);
            return slashCharCode === CharacterCodes.slash || slashCharCode === CharacterCodes.backslash;
        }
        return false;
    }

    function normalizeAndPreserveTrailingSlash(path: string) {
        return hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(normalizePath(path)) : normalizePath(path);
    }

    export function getCompletionEntryDetails(typeChecker: TypeChecker, log: (message: string) => void, compilerOptions: CompilerOptions, sourceFile: SourceFile, position: number, entryName: string): CompletionEntryDetails {
        // Compute all the completion symbols again.
        const completionData = getCompletionData(typeChecker, log, sourceFile, position);
        if (completionData) {
            const { symbols, location } = completionData;

            // Find the symbol with the matching entry name.
            // We don't need to perform character checks here because we're only comparing the
            // name against 'entryName' (which is known to be good), not building a new
            // completion entry.
            const symbol = forEach(symbols, s => getCompletionEntryDisplayNameForSymbol(typeChecker, s, compilerOptions.target, /*performCharacterChecks*/ false, location) === entryName ? s : undefined);

            if (symbol) {
                const { displayParts, documentation, symbolKind } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, location, location, SemanticMeaning.All);
                return {
                    name: entryName,
                    kindModifiers: SymbolDisplay.getSymbolModifiers(symbol),
                    kind: symbolKind,
                    displayParts,
                    documentation
                };
            }
        }

        // Didn't find a symbol with this name.  See if we can find a keyword instead.
        const keywordCompletion = forEach(keywordCompletions, c => c.name === entryName);
        if (keywordCompletion) {
            return {
                name: entryName,
                kind: ScriptElementKind.keyword,
                kindModifiers: ScriptElementKindModifier.none,
                displayParts: [displayPart(entryName, SymbolDisplayPartKind.keyword)],
                documentation: undefined
            };
        }

        return undefined;
    }

    export function getCompletionEntrySymbol(typeChecker: TypeChecker, log: (message: string) => void, compilerOptions: CompilerOptions, sourceFile: SourceFile, position: number, entryName: string): Symbol {
        // Compute all the completion symbols again.
        const completionData = getCompletionData(typeChecker, log, sourceFile, position);
        if (completionData) {
            const { symbols, location } = completionData;

            // Find the symbol with the matching entry name.
            // We don't need to perform character checks here because we're only comparing the
            // name against 'entryName' (which is known to be good), not building a new
            // completion entry.
            return forEach(symbols, s => getCompletionEntryDisplayNameForSymbol(typeChecker, s, compilerOptions.target, /*performCharacterChecks*/ false, location) === entryName ? s : undefined);
        }

        return undefined;
    }

    function getCompletionData(typeChecker: TypeChecker, log: (message: string) => void, sourceFile: SourceFile, position: number) {
        const isJavaScriptFile = isSourceFileJavaScript(sourceFile);

        let isJsDocTagName = false;

        let start = timestamp();
        const currentToken = getTokenAtPosition(sourceFile, position);
        log("getCompletionData: Get current token: " + (timestamp() - start));

        start = timestamp();
        // Completion not allowed inside comments, bail out if this is the case
        const insideComment = isInsideComment(sourceFile, currentToken, position);
        log("getCompletionData: Is inside comment: " + (timestamp() - start));

        if (insideComment) {
            // The current position is next to the '@' sign, when no tag name being provided yet.
            // Provide a full list of tag names
            if (hasDocComment(sourceFile, position) && sourceFile.text.charCodeAt(position - 1) === CharacterCodes.at) {
                isJsDocTagName = true;
            }

            // Completion should work inside certain JsDoc tags. For example:
            //     /** @type {number | string} */
            // Completion should work in the brackets
            let insideJsDocTagExpression = false;
            const tag = getJsDocTagAtPosition(sourceFile, position);
            if (tag) {
                if (tag.tagName.pos <= position && position <= tag.tagName.end) {
                    isJsDocTagName = true;
                }

                switch (tag.kind) {
                    case SyntaxKind.JSDocTypeTag:
                    case SyntaxKind.JSDocParameterTag:
                    case SyntaxKind.JSDocReturnTag:
                        const tagWithExpression = <JSDocTypeTag | JSDocParameterTag | JSDocReturnTag>tag;
                        if (tagWithExpression.typeExpression) {
                            insideJsDocTagExpression = tagWithExpression.typeExpression.pos < position && position < tagWithExpression.typeExpression.end;
                        }
                        break;
                }
            }

            if (isJsDocTagName) {
                return { symbols: undefined, isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, location: undefined, isRightOfDot: false, isJsDocTagName };
            }

            if (!insideJsDocTagExpression) {
                // Proceed if the current position is in jsDoc tag expression; otherwise it is a normal
                // comment or the plain text part of a jsDoc comment, so no completion should be available
                log("Returning an empty list because completion was inside a regular comment or plain text part of a JsDoc comment.");
                return undefined;
            }
        }

        start = timestamp();
        const previousToken = findPrecedingToken(position, sourceFile);
        log("getCompletionData: Get previous token 1: " + (timestamp() - start));

        // The decision to provide completion depends on the contextToken, which is determined through the previousToken.
        // Note: 'previousToken' (and thus 'contextToken') can be undefined if we are the beginning of the file
        let contextToken = previousToken;

        // Check if the caret is at the end of an identifier; this is a partial identifier that we want to complete: e.g. a.toS|
        // Skip this partial identifier and adjust the contextToken to the token that precedes it.
        if (contextToken && position <= contextToken.end && isWord(contextToken.kind)) {
            const start = timestamp();
            contextToken = findPrecedingToken(contextToken.getFullStart(), sourceFile);
            log("getCompletionData: Get previous token 2: " + (timestamp() - start));
        }

        // Find the node where completion is requested on.
        // Also determine whether we are trying to complete with members of that node
        // or attributes of a JSX tag.
        let node = currentToken;
        let isRightOfDot = false;
        let isRightOfOpenTag = false;
        let isStartingCloseTag = false;

        let location = getTouchingPropertyName(sourceFile, position);
        if (contextToken) {
            // Bail out if this is a known invalid completion location
            if (isCompletionListBlocker(contextToken)) {
                log("Returning an empty list because completion was requested in an invalid position.");
                return undefined;
            }

            const { parent, kind } = contextToken;
            if (kind === SyntaxKind.DotToken) {
                if (parent.kind === SyntaxKind.PropertyAccessExpression) {
                    node = (<PropertyAccessExpression>contextToken.parent).expression;
                    isRightOfDot = true;
                }
                else if (parent.kind === SyntaxKind.QualifiedName) {
                    node = (<QualifiedName>contextToken.parent).left;
                    isRightOfDot = true;
                }
                else {
                    // There is nothing that precedes the dot, so this likely just a stray character
                    // or leading into a '...' token. Just bail out instead.
                    return undefined;
                }
            }
            else if (sourceFile.languageVariant === LanguageVariant.JSX) {
                if (kind === SyntaxKind.LessThanToken) {
                    isRightOfOpenTag = true;
                    location = contextToken;
                }
                else if (kind === SyntaxKind.SlashToken && contextToken.parent.kind === SyntaxKind.JsxClosingElement) {
                    isStartingCloseTag = true;
                    location = contextToken;
                }
            }
        }

        const semanticStart = timestamp();
        let isGlobalCompletion = false;
        let isMemberCompletion: boolean;
        let isNewIdentifierLocation: boolean;
        let symbols: Symbol[] = [];

        if (isRightOfDot) {
            getTypeScriptMemberSymbols();
        }
        else if (isRightOfOpenTag) {
            const tagSymbols = typeChecker.getJsxIntrinsicTagNames();
            if (tryGetGlobalSymbols()) {
                symbols = tagSymbols.concat(symbols.filter(s => !!(s.flags & (SymbolFlags.Value | SymbolFlags.Alias))));
            }
            else {
                symbols = tagSymbols;
            }
            isMemberCompletion = true;
            isNewIdentifierLocation = false;
        }
        else if (isStartingCloseTag) {
            const tagName = (<JsxElement>contextToken.parent.parent).openingElement.tagName;
            const tagSymbol = typeChecker.getSymbolAtLocation(tagName);

            if (!typeChecker.isUnknownSymbol(tagSymbol)) {
                symbols = [tagSymbol];
            }
            isMemberCompletion = true;
            isNewIdentifierLocation = false;
        }
        else {
            // For JavaScript or TypeScript, if we're not after a dot, then just try to get the
            // global symbols in scope.  These results should be valid for either language as
            // the set of symbols that can be referenced from this location.
            if (!tryGetGlobalSymbols()) {
                return undefined;
            }
        }

        log("getCompletionData: Semantic work: " + (timestamp() - semanticStart));

        return { symbols, isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, location, isRightOfDot: (isRightOfDot || isRightOfOpenTag), isJsDocTagName };

        function getTypeScriptMemberSymbols(): void {
            // Right of dot member completion list
            isGlobalCompletion = false;
            isMemberCompletion = true;
            isNewIdentifierLocation = false;

            if (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName || node.kind === SyntaxKind.PropertyAccessExpression) {
                let symbol = typeChecker.getSymbolAtLocation(node);

                // This is an alias, follow what it aliases
                if (symbol && symbol.flags & SymbolFlags.Alias) {
                    symbol = typeChecker.getAliasedSymbol(symbol);
                }

                if (symbol && symbol.flags & SymbolFlags.HasExports) {
                    // Extract module or enum members
                    const exportedSymbols = typeChecker.getExportsOfModule(symbol);
                    forEach(exportedSymbols, symbol => {
                        if (typeChecker.isValidPropertyAccess(<PropertyAccessExpression>(node.parent), symbol.name)) {
                            symbols.push(symbol);
                        }
                    });
                }
            }

            const type = typeChecker.getTypeAtLocation(node);
            addTypeProperties(type);
        }

        function addTypeProperties(type: Type) {
            if (type) {
                // Filter private properties
                for (const symbol of type.getApparentProperties()) {
                    if (typeChecker.isValidPropertyAccess(<PropertyAccessExpression>(node.parent), symbol.name)) {
                        symbols.push(symbol);
                    }
                }

                if (isJavaScriptFile && type.flags & TypeFlags.Union) {
                    // In javascript files, for union types, we don't just get the members that
                    // the individual types have in common, we also include all the members that
                    // each individual type has.  This is because we're going to add all identifiers
                    // anyways.  So we might as well elevate the members that were at least part
                    // of the individual types to a higher status since we know what they are.
                    const unionType = <UnionType>type;
                    for (const elementType of unionType.types) {
                        addTypeProperties(elementType);
                    }
                }
            }
        }

        function tryGetGlobalSymbols(): boolean {
            let objectLikeContainer: ObjectLiteralExpression | BindingPattern;
            let namedImportsOrExports: NamedImportsOrExports;
            let jsxContainer: JsxOpeningLikeElement;

            if (objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken)) {
                return tryGetObjectLikeCompletionSymbols(objectLikeContainer);
            }

            if (namedImportsOrExports = tryGetNamedImportsOrExportsForCompletion(contextToken)) {
                // cursor is in an import clause
                // try to show exported member for imported module
                return tryGetImportOrExportClauseCompletionSymbols(namedImportsOrExports);
            }

            if (jsxContainer = tryGetContainingJsxElement(contextToken)) {
                let attrsType: Type;
                if ((jsxContainer.kind === SyntaxKind.JsxSelfClosingElement) || (jsxContainer.kind === SyntaxKind.JsxOpeningElement)) {
                    // Cursor is inside a JSX self-closing element or opening element
                    attrsType = typeChecker.getJsxElementAttributesType(<JsxOpeningLikeElement>jsxContainer);

                    if (attrsType) {
                        symbols = filterJsxAttributes(typeChecker.getPropertiesOfType(attrsType), (<JsxOpeningLikeElement>jsxContainer).attributes);
                        isMemberCompletion = true;
                        isNewIdentifierLocation = false;
                        return true;
                    }
                }
            }

            // Get all entities in the current scope.
            isMemberCompletion = false;
            isNewIdentifierLocation = isNewIdentifierDefinitionLocation(contextToken);

            if (previousToken !== contextToken) {
                Debug.assert(!!previousToken, "Expected 'contextToken' to be defined when different from 'previousToken'.");
            }
            // We need to find the node that will give us an appropriate scope to begin
            // aggregating completion candidates. This is achieved in 'getScopeNode'
            // by finding the first node that encompasses a position, accounting for whether a node
            // is "complete" to decide whether a position belongs to the node.
            //
            // However, at the end of an identifier, we are interested in the scope of the identifier
            // itself, but fall outside of the identifier. For instance:
            //
            //      xyz => x$
            //
            // the cursor is outside of both the 'x' and the arrow function 'xyz => x',
            // so 'xyz' is not returned in our results.
            //
            // We define 'adjustedPosition' so that we may appropriately account for
            // being at the end of an identifier. The intention is that if requesting completion
            // at the end of an identifier, it should be effectively equivalent to requesting completion
            // anywhere inside/at the beginning of the identifier. So in the previous case, the
            // 'adjustedPosition' will work as if requesting completion in the following:
            //
            //      xyz => $x
            //
            // If previousToken !== contextToken, then
            //   - 'contextToken' was adjusted to the token prior to 'previousToken'
            //      because we were at the end of an identifier.
            //   - 'previousToken' is defined.
            const adjustedPosition = previousToken !== contextToken ?
                previousToken.getStart() :
                position;

            const scopeNode = getScopeNode(contextToken, adjustedPosition, sourceFile) || sourceFile;
            if (scopeNode) {
                isGlobalCompletion =
                    scopeNode.kind === SyntaxKind.SourceFile ||
                    scopeNode.kind === SyntaxKind.TemplateExpression ||
                    scopeNode.kind === SyntaxKind.JsxExpression ||
                    isStatement(scopeNode);
            }

            /// TODO filter meaning based on the current context
            const symbolMeanings = SymbolFlags.Type | SymbolFlags.Value | SymbolFlags.Namespace | SymbolFlags.Alias;
            symbols = typeChecker.getSymbolsInScope(scopeNode, symbolMeanings);

            return true;
        }

        /**
         * Finds the first node that "embraces" the position, so that one may
         * accurately aggregate locals from the closest containing scope.
         */
        function getScopeNode(initialToken: Node, position: number, sourceFile: SourceFile) {
            let scope = initialToken;
            while (scope && !positionBelongsToNode(scope, position, sourceFile)) {
                scope = scope.parent;
            }
            return scope;
        }

        function isCompletionListBlocker(contextToken: Node): boolean {
            const start = timestamp();
            const result = isInStringOrRegularExpressionOrTemplateLiteral(contextToken) ||
                isSolelyIdentifierDefinitionLocation(contextToken) ||
                isDotOfNumericLiteral(contextToken) ||
                isInJsxText(contextToken);
            log("getCompletionsAtPosition: isCompletionListBlocker: " + (timestamp() - start));
            return result;
        }

        function isInJsxText(contextToken: Node): boolean {
            if (contextToken.kind === SyntaxKind.JsxText) {
                return true;
            }

            if (contextToken.kind === SyntaxKind.GreaterThanToken && contextToken.parent) {
                if (contextToken.parent.kind === SyntaxKind.JsxOpeningElement) {
                    return true;
                }

                if (contextToken.parent.kind === SyntaxKind.JsxClosingElement || contextToken.parent.kind === SyntaxKind.JsxSelfClosingElement) {
                    return contextToken.parent.parent && contextToken.parent.parent.kind === SyntaxKind.JsxElement;
                }
            }
            return false;
        }

        function isNewIdentifierDefinitionLocation(previousToken: Node): boolean {
            if (previousToken) {
                const containingNodeKind = previousToken.parent.kind;
                switch (previousToken.kind) {
                    case SyntaxKind.CommaToken:
                        return containingNodeKind === SyntaxKind.CallExpression               // func( a, |
                            || containingNodeKind === SyntaxKind.Constructor                  // constructor( a, |   /* public, protected, private keywords are allowed here, so show completion */
                            || containingNodeKind === SyntaxKind.NewExpression                // new C(a, |
                            || containingNodeKind === SyntaxKind.ArrayLiteralExpression       // [a, |
                            || containingNodeKind === SyntaxKind.BinaryExpression             // const x = (a, |
                            || containingNodeKind === SyntaxKind.FunctionType;                // var x: (s: string, list|

                    case SyntaxKind.OpenParenToken:
                        return containingNodeKind === SyntaxKind.CallExpression               // func( |
                            || containingNodeKind === SyntaxKind.Constructor                  // constructor( |
                            || containingNodeKind === SyntaxKind.NewExpression                // new C(a|
                            || containingNodeKind === SyntaxKind.ParenthesizedExpression      // const x = (a|
                            || containingNodeKind === SyntaxKind.ParenthesizedType;           // function F(pred: (a| /* this can become an arrow function, where 'a' is the argument */

                    case SyntaxKind.OpenBracketToken:
                        return containingNodeKind === SyntaxKind.ArrayLiteralExpression       // [ |
                            || containingNodeKind === SyntaxKind.IndexSignature               // [ | : string ]
                            || containingNodeKind === SyntaxKind.ComputedPropertyName;         // [ |    /* this can become an index signature */

                    case SyntaxKind.ModuleKeyword:                                            // module |
                    case SyntaxKind.NamespaceKeyword:                                         // namespace |
                        return true;

                    case SyntaxKind.DotToken:
                        return containingNodeKind === SyntaxKind.ModuleDeclaration;           // module A.|

                    case SyntaxKind.OpenBraceToken:
                        return containingNodeKind === SyntaxKind.ClassDeclaration;            // class A{ |

                    case SyntaxKind.EqualsToken:
                        return containingNodeKind === SyntaxKind.VariableDeclaration          // const x = a|
                            || containingNodeKind === SyntaxKind.BinaryExpression;            // x = a|

                    case SyntaxKind.TemplateHead:
                        return containingNodeKind === SyntaxKind.TemplateExpression;          // `aa ${|

                    case SyntaxKind.TemplateMiddle:
                        return containingNodeKind === SyntaxKind.TemplateSpan;                // `aa ${10} dd ${|

                    case SyntaxKind.PublicKeyword:
                    case SyntaxKind.PrivateKeyword:
                    case SyntaxKind.ProtectedKeyword:
                        return containingNodeKind === SyntaxKind.PropertyDeclaration;         // class A{ public |
                }

                // Previous token may have been a keyword that was converted to an identifier.
                switch (previousToken.getText()) {
                    case "public":
                    case "protected":
                    case "private":
                        return true;
                }
            }

            return false;
        }

        function isInStringOrRegularExpressionOrTemplateLiteral(contextToken: Node): boolean {
            if (contextToken.kind === SyntaxKind.StringLiteral
                || contextToken.kind === SyntaxKind.RegularExpressionLiteral
                || isTemplateLiteralKind(contextToken.kind)) {
                const start = contextToken.getStart();
                const end = contextToken.getEnd();

                // To be "in" one of these literals, the position has to be:
                //   1. entirely within the token text.
                //   2. at the end position of an unterminated token.
                //   3. at the end of a regular expression (due to trailing flags like '/foo/g').
                if (start < position && position < end) {
                    return true;
                }

                if (position === end) {
                    return !!(<LiteralExpression>contextToken).isUnterminated
                        || contextToken.kind === SyntaxKind.RegularExpressionLiteral;
                }
            }

            return false;
        }

        /**
         * Aggregates relevant symbols for completion in object literals and object binding patterns.
         * Relevant symbols are stored in the captured 'symbols' variable.
         *
         * @returns true if 'symbols' was successfully populated; false otherwise.
         */
        function tryGetObjectLikeCompletionSymbols(objectLikeContainer: ObjectLiteralExpression | BindingPattern): boolean {
            // We're looking up possible property names from contextual/inferred/declared type.
            isMemberCompletion = true;

            let typeForObject: Type;
            let existingMembers: Declaration[];

            if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) {
                // We are completing on contextual types, but may also include properties
                // other than those within the declared type.
                isNewIdentifierLocation = true;

                // If the object literal is being assigned to something of type 'null | { hello: string }',
                // it clearly isn't trying to satisfy the 'null' type. So we grab the non-nullable type if possible.
                typeForObject = typeChecker.getContextualType(<ObjectLiteralExpression>objectLikeContainer);
                typeForObject = typeForObject && typeForObject.getNonNullableType();

                existingMembers = (<ObjectLiteralExpression>objectLikeContainer).properties;
            }
            else if (objectLikeContainer.kind === SyntaxKind.ObjectBindingPattern) {
                // We are *only* completing on properties from the type being destructured.
                isNewIdentifierLocation = false;

                const rootDeclaration = getRootDeclaration(objectLikeContainer.parent);
                if (isVariableLike(rootDeclaration)) {
                    // We don't want to complete using the type acquired by the shape
                    // of the binding pattern; we are only interested in types acquired
                    // through type declaration or inference.
                    // Also proceed if rootDeclaration is a parameter and if its containing function expression/arrow function is contextually typed -
                    // type of parameter will flow in from the contextual type of the function
                    let canGetType = !!(rootDeclaration.initializer || rootDeclaration.type);
                    if (!canGetType && rootDeclaration.kind === SyntaxKind.Parameter) {
                        if (isExpression(rootDeclaration.parent)) {
                            canGetType = !!typeChecker.getContextualType(<Expression>rootDeclaration.parent);
                        }
                        else if (rootDeclaration.parent.kind === SyntaxKind.MethodDeclaration || rootDeclaration.parent.kind === SyntaxKind.SetAccessor) {
                            canGetType = isExpression(rootDeclaration.parent.parent) && !!typeChecker.getContextualType(<Expression>rootDeclaration.parent.parent);
                        }
                    }
                    if (canGetType) {
                        typeForObject = typeChecker.getTypeAtLocation(objectLikeContainer);
                        existingMembers = (<ObjectBindingPattern>objectLikeContainer).elements;
                    }
                }
                else {
                    Debug.fail("Root declaration is not variable-like.");
                }
            }
            else {
                Debug.fail("Expected object literal or binding pattern, got " + objectLikeContainer.kind);
            }

            if (!typeForObject) {
                return false;
            }

            const typeMembers = typeChecker.getPropertiesOfType(typeForObject);
            if (typeMembers && typeMembers.length > 0) {
                // Add filtered items to the completion list
                symbols = filterObjectMembersList(typeMembers, existingMembers);
            }
            return true;
        }

        /**
         * Aggregates relevant symbols for completion in import clauses and export clauses
         * whose declarations have a module specifier; for instance, symbols will be aggregated for
         *
         *      import { | } from "moduleName";
         *      export { a as foo, | } from "moduleName";
         *
         * but not for
         *
         *      export { | };
         *
         * Relevant symbols are stored in the captured 'symbols' variable.
         *
         * @returns true if 'symbols' was successfully populated; false otherwise.
         */
        function tryGetImportOrExportClauseCompletionSymbols(namedImportsOrExports: NamedImportsOrExports): boolean {
            const declarationKind = namedImportsOrExports.kind === SyntaxKind.NamedImports ?
                SyntaxKind.ImportDeclaration :
                SyntaxKind.ExportDeclaration;
            const importOrExportDeclaration = <ImportDeclaration | ExportDeclaration>getAncestor(namedImportsOrExports, declarationKind);
            const moduleSpecifier = importOrExportDeclaration.moduleSpecifier;

            if (!moduleSpecifier) {
                return false;
            }

            isMemberCompletion = true;
            isNewIdentifierLocation = false;

            const moduleSpecifierSymbol = typeChecker.getSymbolAtLocation(moduleSpecifier);
            if (!moduleSpecifierSymbol) {
                symbols = emptyArray;
                return true;
            }

            const exports = typeChecker.getExportsAndPropertiesOfModule(moduleSpecifierSymbol);
            symbols = filterNamedImportOrExportCompletionItems(exports, namedImportsOrExports.elements);
            return true;
        }

        /**
         * Returns the immediate owning object literal or binding pattern of a context token,
         * on the condition that one exists and that the context implies completion should be given.
         */
        function tryGetObjectLikeCompletionContainer(contextToken: Node): ObjectLiteralExpression | BindingPattern {
            if (contextToken) {
                switch (contextToken.kind) {
                    case SyntaxKind.OpenBraceToken:  // const x = { |
                    case SyntaxKind.CommaToken:      // const x = { a: 0, |
                        const parent = contextToken.parent;
                        if (parent && (parent.kind === SyntaxKind.ObjectLiteralExpression || parent.kind === SyntaxKind.ObjectBindingPattern)) {
                            return <ObjectLiteralExpression | BindingPattern>parent;
                        }
                        break;
                }
            }

            return undefined;
        }

        /**
         * Returns the containing list of named imports or exports of a context token,
         * on the condition that one exists and that the context implies completion should be given.
         */
        function tryGetNamedImportsOrExportsForCompletion(contextToken: Node): NamedImportsOrExports {
            if (contextToken) {
                switch (contextToken.kind) {
                    case SyntaxKind.OpenBraceToken:  // import { |
                    case SyntaxKind.CommaToken:      // import { a as 0, |
                        switch (contextToken.parent.kind) {
                            case SyntaxKind.NamedImports:
                            case SyntaxKind.NamedExports:
                                return <NamedImportsOrExports>contextToken.parent;
                        }
                }
            }

            return undefined;
        }

        function tryGetContainingJsxElement(contextToken: Node): JsxOpeningLikeElement {
            if (contextToken) {
                const parent = contextToken.parent;
                switch (contextToken.kind) {
                    case SyntaxKind.LessThanSlashToken:
                    case SyntaxKind.SlashToken:
                    case SyntaxKind.Identifier:
                    case SyntaxKind.JsxAttribute:
                    case SyntaxKind.JsxSpreadAttribute:
                        if (parent && (parent.kind === SyntaxKind.JsxSelfClosingElement || parent.kind === SyntaxKind.JsxOpeningElement)) {
                            return <JsxOpeningLikeElement>parent;
                        }
                        else if (parent.kind === SyntaxKind.JsxAttribute) {
                            return <JsxOpeningLikeElement>parent.parent;
                        }
                        break;

                    // The context token is the closing } or " of an attribute, which means
                    // its parent is a JsxExpression, whose parent is a JsxAttribute,
                    // whose parent is a JsxOpeningLikeElement
                    case SyntaxKind.StringLiteral:
                        if (parent && ((parent.kind === SyntaxKind.JsxAttribute) || (parent.kind === SyntaxKind.JsxSpreadAttribute))) {
                            return <JsxOpeningLikeElement>parent.parent;
                        }

                        break;

                    case SyntaxKind.CloseBraceToken:
                        if (parent &&
                            parent.kind === SyntaxKind.JsxExpression &&
                            parent.parent &&
                            (parent.parent.kind === SyntaxKind.JsxAttribute)) {
                            return <JsxOpeningLikeElement>parent.parent.parent;
                        }

                        if (parent && parent.kind === SyntaxKind.JsxSpreadAttribute) {
                            return <JsxOpeningLikeElement>parent.parent;
                        }

                        break;
                }
            }
            return undefined;
        }

        function isFunction(kind: SyntaxKind): boolean {
            switch (kind) {
                case SyntaxKind.FunctionExpression:
                case SyntaxKind.ArrowFunction:
                case SyntaxKind.FunctionDeclaration:
                case SyntaxKind.MethodDeclaration:
                case SyntaxKind.MethodSignature:
                case SyntaxKind.GetAccessor:
                case SyntaxKind.SetAccessor:
                case SyntaxKind.CallSignature:
                case SyntaxKind.ConstructSignature:
                case SyntaxKind.IndexSignature:
                    return true;
            }
            return false;
        }

        /**
         * @returns true if we are certain that the currently edited location must define a new location; false otherwise.
         */
        function isSolelyIdentifierDefinitionLocation(contextToken: Node): boolean {
            const containingNodeKind = contextToken.parent.kind;
            switch (contextToken.kind) {
                case SyntaxKind.CommaToken:
                    return containingNodeKind === SyntaxKind.VariableDeclaration ||
                        containingNodeKind === SyntaxKind.VariableDeclarationList ||
                        containingNodeKind === SyntaxKind.VariableStatement ||
                        containingNodeKind === SyntaxKind.EnumDeclaration ||                        // enum a { foo, |
                        isFunction(containingNodeKind) ||
                        containingNodeKind === SyntaxKind.ClassDeclaration ||                       // class A<T, |
                        containingNodeKind === SyntaxKind.ClassExpression ||                        // var C = class D<T, |
                        containingNodeKind === SyntaxKind.InterfaceDeclaration ||                   // interface A<T, |
                        containingNodeKind === SyntaxKind.ArrayBindingPattern ||                    // var [x, y|
                        containingNodeKind === SyntaxKind.TypeAliasDeclaration;                     // type Map, K, |

                case SyntaxKind.DotToken:
                    return containingNodeKind === SyntaxKind.ArrayBindingPattern;                   // var [.|

                case SyntaxKind.ColonToken:
                    return containingNodeKind === SyntaxKind.BindingElement;                        // var {x :html|

                case SyntaxKind.OpenBracketToken:
                    return containingNodeKind === SyntaxKind.ArrayBindingPattern;                   // var [x|

                case SyntaxKind.OpenParenToken:
                    return containingNodeKind === SyntaxKind.CatchClause ||
                        isFunction(containingNodeKind);

                case SyntaxKind.OpenBraceToken:
                    return containingNodeKind === SyntaxKind.EnumDeclaration ||                     // enum a { |
                        containingNodeKind === SyntaxKind.InterfaceDeclaration ||                   // interface a { |
                        containingNodeKind === SyntaxKind.TypeLiteral;                              // const x : { |

                case SyntaxKind.SemicolonToken:
                    return containingNodeKind === SyntaxKind.PropertySignature &&
                        contextToken.parent && contextToken.parent.parent &&
                        (contextToken.parent.parent.kind === SyntaxKind.InterfaceDeclaration ||    // interface a { f; |
                            contextToken.parent.parent.kind === SyntaxKind.TypeLiteral);           // const x : { a; |

                case SyntaxKind.LessThanToken:
                    return containingNodeKind === SyntaxKind.ClassDeclaration ||                    // class A< |
                        containingNodeKind === SyntaxKind.ClassExpression ||                        // var C = class D< |
                        containingNodeKind === SyntaxKind.InterfaceDeclaration ||                   // interface A< |
                        containingNodeKind === SyntaxKind.TypeAliasDeclaration ||                   // type List< |
                        isFunction(containingNodeKind);

                case SyntaxKind.StaticKeyword:
                    return containingNodeKind === SyntaxKind.PropertyDeclaration;

                case SyntaxKind.DotDotDotToken:
                    return containingNodeKind === SyntaxKind.Parameter ||
                        (contextToken.parent && contextToken.parent.parent &&
                            contextToken.parent.parent.kind === SyntaxKind.ArrayBindingPattern);  // var [...z|

                case SyntaxKind.PublicKeyword:
                case SyntaxKind.PrivateKeyword:
                case SyntaxKind.ProtectedKeyword:
                    return containingNodeKind === SyntaxKind.Parameter;

                case SyntaxKind.AsKeyword:
                    return containingNodeKind === SyntaxKind.ImportSpecifier ||
                        containingNodeKind === SyntaxKind.ExportSpecifier ||
                        containingNodeKind === SyntaxKind.NamespaceImport;

                case SyntaxKind.ClassKeyword:
                case SyntaxKind.EnumKeyword:
                case SyntaxKind.InterfaceKeyword:
                case SyntaxKind.FunctionKeyword:
                case SyntaxKind.VarKeyword:
                case SyntaxKind.GetKeyword:
                case SyntaxKind.SetKeyword:
                case SyntaxKind.ImportKeyword:
                case SyntaxKind.LetKeyword:
                case SyntaxKind.ConstKeyword:
                case SyntaxKind.YieldKeyword:
                case SyntaxKind.TypeKeyword:  // type htm|
                    return true;
            }

            // Previous token may have been a keyword that was converted to an identifier.
            switch (contextToken.getText()) {
                case "abstract":
                case "async":
                case "class":
                case "const":
                case "declare":
                case "enum":
                case "function":
                case "interface":
                case "let":
                case "private":
                case "protected":
                case "public":
                case "static":
                case "var":
                case "yield":
                    return true;
            }

            return false;
        }

        function isDotOfNumericLiteral(contextToken: Node): boolean {
            if (contextToken.kind === SyntaxKind.NumericLiteral) {
                const text = contextToken.getFullText();
                return text.charAt(text.length - 1) === ".";
            }

            return false;
        }

        /**
         * Filters out completion suggestions for named imports or exports.
         *
         * @param exportsOfModule          The list of symbols which a module exposes.
         * @param namedImportsOrExports    The list of existing import/export specifiers in the import/export clause.
         *
         * @returns Symbols to be suggested at an import/export clause, barring those whose named imports/exports
         *          do not occur at the current position and have not otherwise been typed.
         */
        function filterNamedImportOrExportCompletionItems(exportsOfModule: Symbol[], namedImportsOrExports: ImportOrExportSpecifier[]): Symbol[] {
            const existingImportsOrExports = createMap<boolean>();

            for (const element of namedImportsOrExports) {
                // If this is the current item we are editing right now, do not filter it out
                if (element.getStart() <= position && position <= element.getEnd()) {
                    continue;
                }

                const name = element.propertyName || element.name;
                existingImportsOrExports.set(name.text, true);
            }

            if (existingImportsOrExports.size === 0) {
                return filter(exportsOfModule, e => e.name !== "default");
            }

            return filter(exportsOfModule, e => e.name !== "default" && !existingImportsOrExports.get(e.name));
        }

        /**
         * Filters out completion suggestions for named imports or exports.
         *
         * @returns Symbols to be suggested in an object binding pattern or object literal expression, barring those whose declarations
         *          do not occur at the current position and have not otherwise been typed.
         */
        function filterObjectMembersList(contextualMemberSymbols: Symbol[], existingMembers: Declaration[]): Symbol[] {
            if (!existingMembers || existingMembers.length === 0) {
                return contextualMemberSymbols;
            }

            const existingMemberNames = createMap<boolean>();
            for (const m of existingMembers) {
                // Ignore omitted expressions for missing members
                if (m.kind !== SyntaxKind.PropertyAssignment &&
                    m.kind !== SyntaxKind.ShorthandPropertyAssignment &&
                    m.kind !== SyntaxKind.BindingElement &&
                    m.kind !== SyntaxKind.MethodDeclaration &&
                    m.kind !== SyntaxKind.GetAccessor &&
                    m.kind !== SyntaxKind.SetAccessor) {
                    continue;
                }

                // If this is the current item we are editing right now, do not filter it out
                if (m.getStart() <= position && position <= m.getEnd()) {
                    continue;
                }

                let existingName: string;

                if (m.kind === SyntaxKind.BindingElement && (<BindingElement>m).propertyName) {
                    // include only identifiers in completion list
                    if ((<BindingElement>m).propertyName.kind === SyntaxKind.Identifier) {
                        existingName = (<Identifier>(<BindingElement>m).propertyName).text;
                    }
                }
                else {
                    // TODO(jfreeman): Account for computed property name
                    // NOTE: if one only performs this step when m.name is an identifier,
                    // things like '__proto__' are not filtered out.
                    existingName = (<Identifier>m.name).text;
                }

                existingMemberNames.set(existingName, true);
            }

            return filter(contextualMemberSymbols, m => !existingMemberNames.get(m.name));
        }

        /**
         * Filters out completion suggestions from 'symbols' according to existing JSX attributes.
         *
         * @returns Symbols to be suggested in a JSX element, barring those whose attributes
         *          do not occur at the current position and have not otherwise been typed.
         */
        function filterJsxAttributes(symbols: Symbol[], attributes: NodeArray<JsxAttribute | JsxSpreadAttribute>): Symbol[] {
            const seenNames = createMap<boolean>();
            for (const attr of attributes) {
                // If this is the current item we are editing right now, do not filter it out
                if (attr.getStart() <= position && position <= attr.getEnd()) {
                    continue;
                }

                if (attr.kind === SyntaxKind.JsxAttribute) {
                    seenNames.set((<JsxAttribute>attr).name.text, true);
                }
            }

            return filter(symbols, a => !seenNames.get(a.name));
        }
    }

    /**
     * Get the name to be display in completion from a given symbol.
     *
     * @return undefined if the name is of external module otherwise a name with striped of any quote
     */
    function getCompletionEntryDisplayNameForSymbol(typeChecker: TypeChecker, symbol: Symbol, target: ScriptTarget, performCharacterChecks: boolean, location: Node): string {
        const displayName: string = getDeclaredName(typeChecker, symbol, location);

        if (displayName) {
            const firstCharCode = displayName.charCodeAt(0);
            // First check of the displayName is not external module; if it is an external module, it is not valid entry
            if ((symbol.flags & SymbolFlags.Namespace) && (firstCharCode === CharacterCodes.singleQuote || firstCharCode === CharacterCodes.doubleQuote)) {
                // If the symbol is external module, don't show it in the completion list
                // (i.e declare module "http" { const x; } | // <= request completion here, "http" should not be there)
                return undefined;
            }
        }

        return getCompletionEntryDisplayName(displayName, target, performCharacterChecks);
    }

    /**
     * Get a displayName from a given for completion list, performing any necessary quotes stripping
     * and checking whether the name is valid identifier name.
     */
    function getCompletionEntryDisplayName(name: string, target: ScriptTarget, performCharacterChecks: boolean): string {
        if (!name) {
            return undefined;
        }

        name = stripQuotes(name);

        if (!name) {
            return undefined;
        }

        // If the user entered name for the symbol was quoted, removing the quotes is not enough, as the name could be an
        // invalid identifier name. We need to check if whatever was inside the quotes is actually a valid identifier name.
        // e.g "b a" is valid quoted name but when we strip off the quotes, it is invalid.
        // We, thus, need to check if whatever was inside the quotes is actually a valid identifier name.
        if (performCharacterChecks) {
            if (!isIdentifierText(name, target)) {
                return undefined;
            }
        }

        return name;
    }

    // A cache of completion entries for keywords, these do not change between sessions
    const keywordCompletions: CompletionEntry[] = [];
    for (let i = SyntaxKind.FirstKeyword; i <= SyntaxKind.LastKeyword; i++) {
        keywordCompletions.push({
            name: tokenToString(i),
            kind: ScriptElementKind.keyword,
            kindModifiers: ScriptElementKindModifier.none,
            sortText: "0"
        });
    }

    /**
     * Matches a triple slash reference directive with an incomplete string literal for its path. Used
     * to determine if the caret is currently within the string literal and capture the literal fragment
     * for completions.
     * For example, this matches
     *
     * /// <reference path="fragment
     *
     * but not
     *
     * /// <reference path="fragment"
     */
    const tripleSlashDirectiveFragmentRegex = /^(\/\/\/\s*<reference\s+(path|types)\s*=\s*(?:'|"))([^\3"]*)$/;

    interface VisibleModuleInfo {
        moduleName: string;
        moduleDir: string;
    }

    const nodeModulesDependencyKeys = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];

    function tryGetDirectories(host: LanguageServiceHost, directoryName: string): string[] {
        return tryIOAndConsumeErrors(host, host.getDirectories, directoryName);
    }

    function tryReadDirectory(host: LanguageServiceHost, path: string, extensions?: string[], exclude?: string[], include?: string[]): string[] {
        return tryIOAndConsumeErrors(host, host.readDirectory, path, extensions, exclude, include);
    }

    function tryReadFile(host: LanguageServiceHost, path: string): string {
        return tryIOAndConsumeErrors(host, host.readFile, path);
    }

    function tryFileExists(host: LanguageServiceHost, path: string): boolean {
        return tryIOAndConsumeErrors(host, host.fileExists, path);
    }

    function tryDirectoryExists(host: LanguageServiceHost, path: string): boolean {
        try {
            return directoryProbablyExists(path, host);
        }
        catch (e) {}
        return undefined;
    }

    function tryIOAndConsumeErrors<T>(host: LanguageServiceHost, toApply: (...a: any[]) => T, ...args: any[]) {
        try {
            return toApply && toApply.apply(host, args);
        }
        catch (e) {}
        return undefined;
    }

    function isEqualityExpression(node: Node): node is BinaryExpression {
        return isBinaryExpression(node) && isEqualityOperatorKind(node.operatorToken.kind);
    }

    function isEqualityOperatorKind(kind: SyntaxKind) {
        return kind == SyntaxKind.EqualsEqualsToken ||
            kind === SyntaxKind.ExclamationEqualsToken ||
            kind === SyntaxKind.EqualsEqualsEqualsToken ||
            kind === SyntaxKind.ExclamationEqualsEqualsToken;
    }
}
