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

Code before:
        }

        // GH#39946. Pulling on the type of a node inside of a function with a contextual `this` parameter can result in a circularity
        // if the `node` is part of the exprssion of a `yield` or `return`. This circularity doesn't exist at compile time because
        // we will check (and cache) the type of `this` *before* checking the type of the node.
        typeChecker.tryGetThisTypeAt(node, /*includeGlobalThis*/ false);
        let type = typeChecker.getTypeAtLocation(node).getNonOptionalType();

        if (!isTypeLocation) {
            let insertQuestionDot = false;
            if (type.isNullableType()) {
                const canCorrectToQuestionDot =
                    isRightOfDot &&
                    !isRightOfQuestionDot &&
                    preferences.includeAutomaticOptionalChainCompletions !== false;

                if (canCorrectToQuestionDot || isRightOfQuestionDot) {
                    type = type.getNonNullableType();
                    if (canCorrectToQuestionDot) {
                        insertQuestionDot = true;
                    }
                }
            }
            addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot);
        }
        else if (isInTypeQuery(node) && !type.isNullableType()) {
            addTypeProperties(type, /*insertAwait*/ false, /*insertQuestionDot*/ false);
        }
    }

    function addTypeProperties(type: Type, insertAwait: boolean, insertQuestionDot: boolean): void {
        isNewIdentifierLocation = !!type.getStringIndexType();
        if (isRightOfQuestionDot && some(type.getCallSignatures())) {
            isNewIdentifierLocation = true;
        }

        const propertyAccess = node.kind === SyntaxKind.ImportType ? node as ImportTypeNode : node.parent as PropertyAccessExpression | QualifiedName;
        if (inCheckedFile) {
            for (const symbol of type.getApparentProperties()) {
                if (typeChecker.isValidPropertyAccessForCompletions(propertyAccess, type, symbol)) {
                    addPropertySymbol(symbol, /*insertAwait*/ false, insertQuestionDot);
                }
            }
        }
        else {
            // 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.
            symbols.push(...filter(getPropertiesForCompletion(type, typeChecker), s => typeChecker.isValidPropertyAccessForCompletions(propertyAccess, type, s)));

Review comment (suggestion):
hm, perhaps this shouldn't even check if the type is not nullable since this autocomplete just fine (it just reports an error afterward):
```ts
declare const obj: { a: { b: string } } | undefined

type Test = typeof obj./**/
```

Code after:
        }

        if (!isTypeLocation || isInTypeQuery(node)) {
            // GH#39946. Pulling on the type of a node inside of a function with a contextual `this` parameter can result in a circularity
            // if the `node` is part of the exprssion of a `yield` or `return`. This circularity doesn't exist at compile time because
            // we will check (and cache) the type of `this` *before* checking the type of the node.
            typeChecker.tryGetThisTypeAt(node, /*includeGlobalThis*/ false);
            let type = typeChecker.getTypeAtLocation(node).getNonOptionalType();

            if (!isTypeLocation) {
                let insertQuestionDot = false;
                if (type.isNullableType()) {
                    const canCorrectToQuestionDot =
                        isRightOfDot &&
                        !isRightOfQuestionDot &&
                        preferences.includeAutomaticOptionalChainCompletions !== false;

                    if (canCorrectToQuestionDot || isRightOfQuestionDot) {
                        type = type.getNonNullableType();
                        if (canCorrectToQuestionDot) {
                            insertQuestionDot = true;
                        }
                    }
                }
                addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot);
            }
            else {
                addTypeProperties(type.getNonNullableType(), /*insertAwait*/ false, /*insertQuestionDot*/ false);
            }
        }
    }

    function addTypeProperties(type: Type, insertAwait: boolean, insertQuestionDot: boolean): void {
        isNewIdentifierLocation = !!type.getStringIndexType();
        if (isRightOfQuestionDot && some(type.getCallSignatures())) {
            isNewIdentifierLocation = true;
        }

        const propertyAccess = node.kind === SyntaxKind.ImportType ? node as ImportTypeNode : node.parent as PropertyAccessExpression | QualifiedName;
        if (inCheckedFile) {
            for (const symbol of type.getApparentProperties()) {
                if (typeChecker.isValidPropertyAccessForCompletions(propertyAccess, type, symbol)) {
                    addPropertySymbol(symbol, /*insertAwait*/ false, insertQuestionDot);
                }
            }
        }
        else {
            // 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