Repository: microsoft/TypeScript
File: src/services/completions.ts (TypeScript)

Code before:
        return false;
    }
    if (isNamedImports(namedBindings)) {
        // We can only complete on named imports if there are no other named imports already,
        // but parser recovery sometimes puts later statements in the named imports list, so
        // we try to only consider the probably-valid ones.
        const invalidNamedImport = getPotentiallyInvalidImportSpecifier(namedBindings);
        const validImports = invalidNamedImport ? namedBindings.elements.indexOf(invalidNamedImport) : namedBindings.elements.length;
        return validImports < 2;
    }
    return true;
}

function isModuleSpecifierMissingOrEmpty(specifier: ModuleReference | Expression) {
    if (nodeIsMissing(specifier)) return true;
    return !tryCast(isExternalModuleReference(specifier) ? specifier.expression : specifier, isStringLiteralLike)?.text;
}

function getVariableOrParameterDeclaration(contextToken: Node | undefined, location: Node) {
    if (!contextToken) return;

    const possiblyParameterDeclaration = findAncestor(contextToken, node =>
        isFunctionBlock(node) || isArrowFunctionBody(node) || isBindingPattern(node)
            ? "quit"
            : ((isParameter(node) || isTypeParameterDeclaration(node)) && !isIndexSignatureDeclaration(node.parent)));
    const possiblyVariableDeclaration = findAncestor(location, (node) => {
        return isFunctionBlock(node) || isArrowFunctionBody(node) || isBindingPattern(node) ?
            "quit" : isVariableDeclaration(node);
    });
    return (possiblyParameterDeclaration || possiblyVariableDeclaration) as ParameterDeclaration | TypeParameterDeclaration | VariableDeclaration | undefined;
}

function isArrowFunctionBody(node: Node) {
    return node.parent && isArrowFunction(node.parent) &&
        (node.parent.body === node ||
        // const a = () => /**/;
        node.kind === SyntaxKind.EqualsGreaterThanToken
    );
}

/** True if symbol is a type or a module containing at least one type. */
function symbolCanBeReferencedAtTypeLocation(symbol: Symbol, checker: TypeChecker, seenModules = new Map<SymbolId, true>()): boolean {
    // Since an alias can be merged with a local declaration, we need to test both the alias and its target.
    // This code used to just test the result of `skipAlias`, but that would ignore any locally introduced meanings.
    return nonAliasCanBeReferencedAtTypeLocation(symbol) || nonAliasCanBeReferencedAtTypeLocation(skipAlias(symbol.exportSymbol || symbol, checker));

    function nonAliasCanBeReferencedAtTypeLocation(symbol: Symbol): boolean {
        return !!(symbol.flags & SymbolFlags.Type) || checker.isUnknownSymbol(symbol) ||
            !!(symbol.flags & SymbolFlags.Module) && addToSeen(seenModules, getSymbolId(symbol)) &&
            checker.getExportsOfModule(symbol).some(e => symbolCanBeReferencedAtTypeLocation(e, checker, seenModules));
    }

Review comment (suggestion):
Nit:
```suggestion
    const possiblyVariableDeclaration = findAncestor(location, node => {

```

Code after:
        return false;
    }
    if (isNamedImports(namedBindings)) {
        // We can only complete on named imports if there are no other named imports already,
        // but parser recovery sometimes puts later statements in the named imports list, so
        // we try to only consider the probably-valid ones.
        const invalidNamedImport = getPotentiallyInvalidImportSpecifier(namedBindings);
        const validImports = invalidNamedImport ? namedBindings.elements.indexOf(invalidNamedImport) : namedBindings.elements.length;
        return validImports < 2;
    }
    return true;
}

function isModuleSpecifierMissingOrEmpty(specifier: ModuleReference | Expression) {
    if (nodeIsMissing(specifier)) return true;
    return !tryCast(isExternalModuleReference(specifier) ? specifier.expression : specifier, isStringLiteralLike)?.text;
}

function getVariableOrParameterDeclaration(contextToken: Node | undefined, location: Node) {
    if (!contextToken) return;

    const possiblyParameterDeclaration = findAncestor(contextToken, node =>
        isFunctionBlock(node) || isArrowFunctionBody(node) || isBindingPattern(node)
            ? "quit"
            : ((isParameter(node) || isTypeParameterDeclaration(node)) && !isIndexSignatureDeclaration(node.parent)));

    const possiblyVariableDeclaration = findAncestor(location, node =>
        isFunctionBlock(node) || isArrowFunctionBody(node) || isBindingPattern(node)
            ? "quit"
            : isVariableDeclaration(node));

    return (possiblyParameterDeclaration || possiblyVariableDeclaration) as ParameterDeclaration | TypeParameterDeclaration | VariableDeclaration | undefined;
}

function isArrowFunctionBody(node: Node) {
    return node.parent && isArrowFunction(node.parent) &&
        (node.parent.body === node ||
        // const a = () => /**/;
        node.kind === SyntaxKind.EqualsGreaterThanToken
    );
}

/** True if symbol is a type or a module containing at least one type. */
function symbolCanBeReferencedAtTypeLocation(symbol: Symbol, checker: TypeChecker, seenModules = new Map<SymbolId, true>()): boolean {
    // Since an alias can be merged with a local declaration, we need to test both the alias and its target.
    // This code used to just test the result of `skipAlias`, but that would ignore any locally introduced meanings.
    return nonAliasCanBeReferencedAtTypeLocation(symbol) || nonAliasCanBeReferencedAtTypeLocation(skipAlias(symbol.exportSymbol || symbol, checker));

    function nonAliasCanBeReferencedAtTypeLocation(symbol: Symbol): boolean {
        return !!(symbol.flags & SymbolFlags.Type) || checker.isUnknownSymbol(symbol) ||
            !!(symbol.flags & SymbolFlags.Module) && addToSeen(seenModules, getSymbolId(symbol)) &&