Merge pull request #15541 from Microsoft/union-completion
For completions of union type, get all possible properties
This commit is contained in:
commit
e6d472c291
4 changed files with 49 additions and 13 deletions
|
@ -205,6 +205,7 @@ namespace ts {
|
||||||
return tryFindAmbientModule(moduleName, /*withAugmentations*/ false);
|
return tryFindAmbientModule(moduleName, /*withAugmentations*/ false);
|
||||||
},
|
},
|
||||||
getApparentType,
|
getApparentType,
|
||||||
|
getAllPossiblePropertiesOfType,
|
||||||
getSuggestionForNonexistentProperty,
|
getSuggestionForNonexistentProperty,
|
||||||
getSuggestionForNonexistentSymbol,
|
getSuggestionForNonexistentSymbol,
|
||||||
getBaseConstraintOfType,
|
getBaseConstraintOfType,
|
||||||
|
@ -5695,6 +5696,22 @@ namespace ts {
|
||||||
getPropertiesOfObjectType(type);
|
getPropertiesOfObjectType(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAllPossiblePropertiesOfType(type: Type): Symbol[] {
|
||||||
|
if (type.flags & TypeFlags.Union) {
|
||||||
|
const props = createMap<Symbol>();
|
||||||
|
for (const memberType of (type as UnionType).types) {
|
||||||
|
for (const { name } of getPropertiesOfType(memberType)) {
|
||||||
|
if (!props.has(name)) {
|
||||||
|
props.set(name, createUnionOrIntersectionProperty(type as UnionType, name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arrayFrom(props.values());
|
||||||
|
} else {
|
||||||
|
return getPropertiesOfType(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getConstraintOfType(type: TypeVariable | UnionOrIntersectionType): Type {
|
function getConstraintOfType(type: TypeVariable | UnionOrIntersectionType): Type {
|
||||||
return type.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(<TypeParameter>type) :
|
return type.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(<TypeParameter>type) :
|
||||||
type.flags & TypeFlags.IndexedAccess ? getConstraintOfIndexedAccess(<IndexedAccessType>type) :
|
type.flags & TypeFlags.IndexedAccess ? getConstraintOfIndexedAccess(<IndexedAccessType>type) :
|
||||||
|
|
|
@ -2573,6 +2573,12 @@ namespace ts {
|
||||||
/* @internal */ getIdentifierCount(): number;
|
/* @internal */ getIdentifierCount(): number;
|
||||||
/* @internal */ getSymbolCount(): number;
|
/* @internal */ getSymbolCount(): number;
|
||||||
/* @internal */ getTypeCount(): number;
|
/* @internal */ getTypeCount(): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For a union, will include a property if it's defined in *any* of the member types.
|
||||||
|
* So for `{ a } | { b }`, this will include both `a` and `b`.
|
||||||
|
*/
|
||||||
|
/* @internal */ getAllPossiblePropertiesOfType(type: Type): Symbol[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum NodeBuilderFlags {
|
export enum NodeBuilderFlags {
|
||||||
|
|
|
@ -813,19 +813,16 @@ namespace ts.Completions {
|
||||||
// We're looking up possible property names from contextual/inferred/declared type.
|
// We're looking up possible property names from contextual/inferred/declared type.
|
||||||
isMemberCompletion = true;
|
isMemberCompletion = true;
|
||||||
|
|
||||||
let typeForObject: Type;
|
let typeMembers: Symbol[];
|
||||||
let existingMembers: Declaration[];
|
let existingMembers: Declaration[];
|
||||||
|
|
||||||
if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) {
|
if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) {
|
||||||
// We are completing on contextual types, but may also include properties
|
// We are completing on contextual types, but may also include properties
|
||||||
// other than those within the declared type.
|
// other than those within the declared type.
|
||||||
isNewIdentifierLocation = true;
|
isNewIdentifierLocation = true;
|
||||||
|
const typeForObject = typeChecker.getContextualType(<ObjectLiteralExpression>objectLikeContainer);
|
||||||
// If the object literal is being assigned to something of type 'null | { hello: string }',
|
if (!typeForObject) return false;
|
||||||
// it clearly isn't trying to satisfy the 'null' type. So we grab the non-nullable type if possible.
|
typeMembers = typeChecker.getAllPossiblePropertiesOfType(typeForObject);
|
||||||
typeForObject = typeChecker.getContextualType(<ObjectLiteralExpression>objectLikeContainer);
|
|
||||||
typeForObject = typeForObject && typeForObject.getNonNullableType();
|
|
||||||
|
|
||||||
existingMembers = (<ObjectLiteralExpression>objectLikeContainer).properties;
|
existingMembers = (<ObjectLiteralExpression>objectLikeContainer).properties;
|
||||||
}
|
}
|
||||||
else if (objectLikeContainer.kind === SyntaxKind.ObjectBindingPattern) {
|
else if (objectLikeContainer.kind === SyntaxKind.ObjectBindingPattern) {
|
||||||
|
@ -849,7 +846,10 @@ namespace ts.Completions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (canGetType) {
|
if (canGetType) {
|
||||||
typeForObject = typeChecker.getTypeAtLocation(objectLikeContainer);
|
const typeForObject = typeChecker.getTypeAtLocation(objectLikeContainer);
|
||||||
|
if (!typeForObject) return false;
|
||||||
|
// In a binding pattern, get only known properties. Everywhere else we will get all possible properties.
|
||||||
|
typeMembers = typeChecker.getPropertiesOfType(typeForObject);
|
||||||
existingMembers = (<ObjectBindingPattern>objectLikeContainer).elements;
|
existingMembers = (<ObjectBindingPattern>objectLikeContainer).elements;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -861,11 +861,6 @@ namespace ts.Completions {
|
||||||
Debug.fail("Expected object literal or binding pattern, got " + objectLikeContainer.kind);
|
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) {
|
if (typeMembers && typeMembers.length > 0) {
|
||||||
// Add filtered items to the completion list
|
// Add filtered items to the completion list
|
||||||
symbols = filterObjectMembersList(typeMembers, existingMembers);
|
symbols = filterObjectMembersList(typeMembers, existingMembers);
|
||||||
|
|
18
tests/cases/fourslash/completionListOfUnion.ts
Normal file
18
tests/cases/fourslash/completionListOfUnion.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
/// <reference path='fourslash.ts' />
|
||||||
|
|
||||||
|
// @strictNullChecks: true
|
||||||
|
|
||||||
|
// Non-objects should be skipped, so `| number | null` should have no effect on completions.
|
||||||
|
////const x: { a: number, b: number } | { a: string, c: string } | { b: boolean } | number | null = { /*x*/ };
|
||||||
|
|
||||||
|
////interface I { a: number; }
|
||||||
|
////function f(...args: Array<I | I[]>) {}
|
||||||
|
////f({ /*f*/ });
|
||||||
|
|
||||||
|
goTo.marker("x");
|
||||||
|
verify.completionListContains("a", "(property) a: string | number");
|
||||||
|
verify.completionListContains("b", "(property) b: number | boolean");
|
||||||
|
verify.completionListContains("c", "(property) c: string");
|
||||||
|
|
||||||
|
goTo.marker("f");
|
||||||
|
verify.completionListContains("a", "(property) a: number");
|
Loading…
Reference in a new issue