Skip to content

Commit 89eb06e

Browse files
author
Andy
authored
For completions of union, exclude types with methods (microsoft#18124)
For completions of union, exclude arrays
1 parent d1e2242 commit 89eb06e

5 files changed

Lines changed: 41 additions & 19 deletions

File tree

src/compiler/checker.ts

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,8 @@ namespace ts {
225225
return tryFindAmbientModule(moduleName, /*withAugmentations*/ false);
226226
},
227227
getApparentType,
228-
getAllPossiblePropertiesOfType,
228+
isArrayLikeType,
229+
getAllPossiblePropertiesOfTypes,
229230
getSuggestionForNonexistentProperty: (node, type) => unescapeLeadingUnderscores(getSuggestionForNonexistentProperty(node, type)),
230231
getSuggestionForNonexistentSymbol: (location, name, meaning) => unescapeLeadingUnderscores(getSuggestionForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning)),
231232
getBaseConstraintOfType,
@@ -5925,25 +5926,21 @@ namespace ts {
59255926
getPropertiesOfObjectType(type);
59265927
}
59275928

5928-
function getAllPossiblePropertiesOfType(type: Type): Symbol[] {
5929-
if (type.flags & TypeFlags.Union) {
5930-
const props = createSymbolTable();
5931-
for (const memberType of (type as UnionType).types) {
5932-
if (memberType.flags & TypeFlags.Primitive) {
5933-
continue;
5934-
}
5929+
function getAllPossiblePropertiesOfTypes(types: Type[]): Symbol[] {
5930+
const unionType = getUnionType(types);
5931+
if (!(unionType.flags & TypeFlags.Union)) {
5932+
return getPropertiesOfType(unionType);
5933+
}
59355934

5936-
for (const { escapedName } of getPropertiesOfType(memberType)) {
5937-
if (!props.has(escapedName)) {
5938-
props.set(escapedName, createUnionOrIntersectionProperty(type as UnionType, escapedName));
5939-
}
5935+
const props = createSymbolTable();
5936+
for (const memberType of types) {
5937+
for (const { escapedName } of getPropertiesOfType(memberType)) {
5938+
if (!props.has(escapedName)) {
5939+
props.set(escapedName, createUnionOrIntersectionProperty(unionType as UnionType, escapedName));
59405940
}
59415941
}
5942-
return arrayFrom(props.values());
5943-
}
5944-
else {
5945-
return getPropertiesOfType(type);
59465942
}
5943+
return arrayFrom(props.values());
59475944
}
59485945

59495946
function getConstraintOfType(type: TypeVariable | UnionOrIntersectionType): Type {

src/compiler/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2703,7 +2703,8 @@ namespace ts {
27032703
* So for `{ a } | { b }`, this will include both `a` and `b`.
27042704
* Does not include properties of primitive types.
27052705
*/
2706-
/* @internal */ getAllPossiblePropertiesOfType(type: Type): Symbol[];
2706+
/* @internal */ isArrayLikeType(type: Type): boolean;
2707+
/* @internal */ getAllPossiblePropertiesOfTypes(type: ReadonlyArray<Type>): Symbol[];
27072708
/* @internal */ resolveName(name: string, location: Node, meaning: SymbolFlags): Symbol | undefined;
27082709
/* @internal */ getJsxNamespace(): string;
27092710
}

src/services/completions.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -978,7 +978,7 @@ namespace ts.Completions {
978978
isNewIdentifierLocation = true;
979979
const typeForObject = typeChecker.getContextualType(<ObjectLiteralExpression>objectLikeContainer);
980980
if (!typeForObject) return false;
981-
typeMembers = typeChecker.getAllPossiblePropertiesOfType(typeForObject);
981+
typeMembers = getPropertiesForCompletion(typeForObject, typeChecker);
982982
existingMembers = (<ObjectLiteralExpression>objectLikeContainer).properties;
983983
}
984984
else {
@@ -1766,4 +1766,20 @@ namespace ts.Completions {
17661766
return node.parent;
17671767
}
17681768
}
1769+
1770+
/**
1771+
* Gets all properties on a type, but if that type is a union of several types,
1772+
* tries to only include those types which declare properties, not methods.
1773+
* This ensures that we don't try providing completions for all the methods on e.g. Array.
1774+
*/
1775+
function getPropertiesForCompletion(type: Type, checker: TypeChecker): Symbol[] {
1776+
if (!(type.flags & TypeFlags.Union)) {
1777+
return checker.getPropertiesOfType(type);
1778+
}
1779+
1780+
const { types } = type as UnionType;
1781+
const filteredTypes = types.filter(memberType => !(memberType.flags & TypeFlags.Primitive || checker.isArrayLikeType(memberType)));
1782+
// If there are no property-only types, just provide completions for every type as usual.
1783+
return checker.getAllPossiblePropertiesOfTypes(filteredTypes);
1784+
}
17691785
}

tests/cases/fourslash/completionListOfUnion.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ verify.completionListContains("b", "(property) b: number | boolean");
1616
verify.completionListContains("c", "(property) c: string");
1717

1818
goTo.marker("f");
19-
verify.completionListContains("a", "(property) a: number");
19+
verify.completionListContains("a", "(property) I.a: number");
2020
// Also contains array members
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////interface I { x: number; }
4+
////interface Many<T> extends ReadonlyArray<T> { extra: number; }
5+
////const x: I | I[] | Many<string> = { /**/ };
6+
7+
// We specifically filter out any array-like types.
8+
verify.completionsAt("", ["x"]);

0 commit comments

Comments
 (0)