In getPropertySymbolsFromContextualType, use union discriminant to filter types (#25914)

This commit is contained in:
Andy 2018-07-25 11:53:20 -07:00 committed by GitHub
parent 9658b476c2
commit d60f4988a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 138 additions and 58 deletions

View File

@ -1394,7 +1394,8 @@ namespace ts.FindAllReferences.Core {
// If the location is in a context sensitive location (i.e. in an object literal) try
// to get a contextual type for it, and add the property symbol from the contextual
// type to the search set
const res = firstDefined(getPropertySymbolsFromContextualType(containingObjectLiteralElement, checker), fromRoot);
const contextualType = checker.getContextualType(containingObjectLiteralElement.parent);
const res = contextualType && firstDefined(getPropertySymbolsFromContextualType(containingObjectLiteralElement, checker, contextualType, /*unionSymbolOk*/ true), fromRoot);
if (res) return res;
// If the location is name of property symbol from object literal destructuring pattern
@ -1462,17 +1463,6 @@ namespace ts.FindAllReferences.Core {
!(search.parents && !search.parents.some(parent => explicitlyInheritsFrom(rootSymbol.parent!, parent, state.inheritsFromCache, checker))));
}
/** Gets all symbols for one property. Does not get symbols for every property. */
function getPropertySymbolsFromContextualType(node: ObjectLiteralElementWithName, checker: TypeChecker): ReadonlyArray<Symbol> {
const contextualType = checker.getContextualType(<ObjectLiteralExpression>node.parent);
if (!contextualType) return emptyArray;
const name = getNameFromPropertyName(node.name);
if (!name) return emptyArray;
const symbol = contextualType.getProperty(name);
return symbol ? [symbol] :
contextualType.isUnion() ? mapDefined(contextualType.types, t => t.getProperty(name)) : emptyArray;
}
/**
* Given an initial searchMeaning, extracted from a location, widen the search scope based on the declarations
* of the corresponding symbol. e.g. if we are searching for "Foo" in value position, but "Foo" references a class

View File

@ -69,11 +69,9 @@ namespace ts.GoToDefinition {
if (isPropertyName(node) && isBindingElement(parent) && isObjectBindingPattern(parent.parent) &&
(node === (parent.propertyName || parent.name))) {
const type = typeChecker.getTypeAtLocation(parent.parent);
if (type) {
const propSymbols = getPropertySymbolsFromType(type, node);
if (propSymbols) {
return flatMap(propSymbols, propSymbol => getDefinitionFromSymbol(typeChecker, propSymbol, node));
}
const propSymbols = getPropertySymbolsFromType(type, node);
if (propSymbols) {
return flatMap(propSymbols, propSymbol => getDefinitionFromSymbol(typeChecker, propSymbol, node));
}
}
@ -87,9 +85,12 @@ namespace ts.GoToDefinition {
// function Foo(arg: Props) {}
// Foo( { pr/*1*/op1: 10, prop2: true })
const element = getContainingObjectLiteralElement(node);
if (element && typeChecker.getContextualType(element.parent as Expression)) {
return flatMap(getPropertySymbolsFromContextualType(typeChecker, element), propertySymbol =>
getDefinitionFromSymbol(typeChecker, propertySymbol, node));
if (element) {
const contextualType = element && typeChecker.getContextualType(element.parent);
if (contextualType) {
return flatMap(getPropertySymbolsFromContextualType(element, typeChecker, contextualType, /*unionSymbolOk*/ false), propertySymbol =>
getDefinitionFromSymbol(typeChecker, propertySymbol, node));
}
}
return getDefinitionFromSymbol(typeChecker, symbol, node);
}

View File

@ -1489,19 +1489,6 @@ namespace ts {
}
}
function getSymbolAtLocationForQuickInfo(node: Node, checker: TypeChecker): Symbol | undefined {
if ((isIdentifier(node) || isStringLiteral(node))
&& isPropertyAssignment(node.parent)
&& node.parent.name === node) {
const type = checker.getContextualType(node.parent.parent);
const property = type && checker.getPropertyOfType(type, getTextOfIdentifierOrLiteral(node));
if (property) {
return property;
}
}
return checker.getSymbolAtLocation(node);
}
/// Goto definition
function getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] | undefined {
synchronizeHostData();
@ -2187,28 +2174,60 @@ namespace ts {
*/
/* @internal */
export function getContainingObjectLiteralElement(node: Node): ObjectLiteralElementWithName | undefined {
const element = getContainingObjectLiteralElementWorker(node);
return element && (isObjectLiteralExpression(element.parent) || isJsxAttributes(element.parent)) ? element as ObjectLiteralElementWithName : undefined;
}
function getContainingObjectLiteralElementWorker(node: Node): ObjectLiteralElement | undefined {
switch (node.kind) {
case SyntaxKind.StringLiteral:
case SyntaxKind.NumericLiteral:
if (node.parent.kind === SyntaxKind.ComputedPropertyName) {
return isObjectLiteralElement(node.parent.parent) ? node.parent.parent as ObjectLiteralElementWithName : undefined;
return isObjectLiteralElement(node.parent.parent) ? node.parent.parent : undefined;
}
// falls through
case SyntaxKind.Identifier:
return isObjectLiteralElement(node.parent) &&
(node.parent.parent.kind === SyntaxKind.ObjectLiteralExpression || node.parent.parent.kind === SyntaxKind.JsxAttributes) &&
node.parent.name === node ? node.parent as ObjectLiteralElementWithName : undefined;
node.parent.name === node ? node.parent : undefined;
}
return undefined;
}
/* @internal */
export type ObjectLiteralElementWithName = ObjectLiteralElement & { name: PropertyName };
/* @internal */
export function getPropertySymbolsFromContextualType(typeChecker: TypeChecker, node: ObjectLiteralElement): Symbol[] {
const objectLiteral = <ObjectLiteralExpression | JsxAttributes>node.parent;
const contextualType = typeChecker.getContextualType(objectLiteral)!; // TODO: GH#18217
return getPropertySymbolsFromType(contextualType, node.name!)!; // TODO: GH#18217
export type ObjectLiteralElementWithName = ObjectLiteralElement & { name: PropertyName; parent: ObjectLiteralExpression | JsxAttributes };
function getSymbolAtLocationForQuickInfo(node: Node, checker: TypeChecker): Symbol | undefined {
const object = getContainingObjectLiteralElement(node);
if (object) {
const contextualType = checker.getContextualType(object.parent);
const properties = contextualType && getPropertySymbolsFromContextualType(object, checker, contextualType, /*unionSymbolOk*/ false);
if (properties && properties.length === 1) {
return first(properties);
}
}
return checker.getSymbolAtLocation(node);
}
/** Gets all symbols for one property. Does not get symbols for every property. */
/* @internal */
export function getPropertySymbolsFromContextualType(node: ObjectLiteralElementWithName, checker: TypeChecker, contextualType: Type, unionSymbolOk: boolean): ReadonlyArray<Symbol> {
const name = getNameFromPropertyName(node.name);
if (!name) return emptyArray;
if (!contextualType.isUnion()) {
const symbol = contextualType.getProperty(name);
return symbol ? [symbol] : emptyArray;
}
const discriminatedPropertySymbols = mapDefined(contextualType.types, t => isObjectLiteralExpression(node.parent) && checker.isTypeInvalidDueToUnionDiscriminant(t, node.parent) ? undefined : t.getProperty(name));
if (unionSymbolOk && (discriminatedPropertySymbols.length === 0 || discriminatedPropertySymbols.length === contextualType.types.length)) {
const symbol = contextualType.getProperty(name);
if (symbol) return [symbol];
}
if (discriminatedPropertySymbols.length === 0) {
// Bad discriminant -- do again without discriminating
return mapDefined(contextualType.types, t => t.getProperty(name));
}
return discriminatedPropertySymbols;
}
/* @internal */

View File

@ -1,8 +1,12 @@
/// <reference path='fourslash.ts'/>
////type T =
//// | { [|{| "isWriteAccess": true, "isDefinition": true |}type|]: "a" }
//// | { [|{| "isWriteAccess": true, "isDefinition": true |}type|]: "b" };
//// | { [|{| "isWriteAccess": true, "isDefinition": true |}type|]: "a", [|{| "isWriteAccess": true, "isDefinition": true |}prop|]: number }
//// | { [|{| "isWriteAccess": true, "isDefinition": true |}type|]: "b", [|{| "isWriteAccess": true, "isDefinition": true |}prop|]: string };
////const tt: T = {
//// [|{| "isWriteAccess": true, "isDefinition": true |}type|]: "a",
//// [|{| "isWriteAccess": true, "isDefinition": true |}prop|]: 0,
////};
////declare const t: T;
////if (t.[|type|] === "a") {
//// t.[|type|];
@ -10,10 +14,14 @@
//// t.[|type|];
////}
const ranges = test.ranges();
const [r0, r1, r2, r3, r4] = ranges;
verify.referenceGroups(ranges, [
{ definition: { text: '(property) type: "a"', range: r0 }, ranges: [r0, r3] },
{ definition: { text: '(property) type: "b"', range: r1 }, ranges: [r1, r4] },
{ definition: { text: '(property) type: "a" | "b"', range: r0 }, ranges: [r2] },
]);
const [t0, p0, t1, p1, t2, p2, t3, t4, t5] = test.ranges();
const a = { definition: { text: '(property) type: "a"', range: t0 }, ranges: [t0, t2, t4] };
const b = { definition: { text: '(property) type: "b"', range: t1 }, ranges: [t1, t5] };
const ab = { definition: { text: '(property) type: "a" | "b"', range: t0 }, ranges: [t3] };
verify.referenceGroups([t0, t1, t3, t4, t5], [a, b, ab]);
verify.referenceGroups(t2, [a, ab]);
const p = { definition: "(property) prop: number", ranges: [p0, p2] };
verify.referenceGroups([p0, p1], [p, { definition: "(property) prop: string", ranges: [p1] }]);
verify.referenceGroups(p2, [p]);

View File

@ -0,0 +1,29 @@
/// <reference path='fourslash.ts'/>
////type U = A | B;
////
////interface A {
//// /*aKind*/kind: "a";
//// /*aProp*/prop: number;
////};
////
////interface B {
//// /*bKind*/kind: "b";
//// /*bProp*/prop: string;
////}
////
////const u: U = {
//// [|/*kind*/kind|]: "a",
//// [|/*prop*/prop|]: 0,
////};
////const u2: U = {
//// [|/*kindBogus*/kind|]: "bogus",
//// [|/*propBogus*/prop|]: 0,
////};
verify.goToDefinition({
kind: "aKind",
prop: "aProp",
kindBogus: ["aKind", "bKind"],
propBogus: ["aProp", "bProp"],
});

View File

@ -18,5 +18,5 @@
//// var c = <Component items={[0, 1, 2]} render/*2*/Item={it/*3*/em => item.toFixed()}
verify.quickInfoAt("0", "(property) Props<number>.renderItem: (item: number) => string");
verify.quickInfoAt("1", "(parameter) item: number");
verify.quickInfoAt("2", "(JSX attribute) renderItem: (item: number) => string");
verify.quickInfoAt("2", "(JSX attribute) Props<number>.renderItem: (item: number) => string");
verify.quickInfoAt("3", "(parameter) item: number");

View File

@ -0,0 +1,34 @@
/// <reference path='fourslash.ts'/>
// @Filename: quickInfoJsDocTags.ts
////type U = A | B;
////
////interface A {
//// /** Kind A */
//// kind: "a";
//// /** Prop A */
//// prop: number;
////}
////
////interface B {
//// /** Kind B */
//// kind: "b";
//// /** Prop B */
//// prop: string;
////}
////
////const u: U = {
//// /*uKind*/kind: "a",
//// /*uProp*/prop: 0,
////}
////const u2: U = {
//// /*u2Kind*/kind: "bogus",
//// /*u2Prop*/prop: 1,
////};
verify.quickInfos({
uKind: ['(property) A.kind: "a"', "Kind A "],
uProp: ["(property) A.prop: number", "Prop A "],
u2Kind: '(property) kind: "bogus"',
u2Prop: "(property) prop: number",
});

View File

@ -34,8 +34,7 @@
////var u1 = { a: 0, b: 0, common: "" };
////var u2 = { b: 0, common: 0 };
const all = test.ranges();
const [aCommon, bCommon, ...unionRefs] = all;
const [aCommon, bCommon, ...unionRefs] = test.ranges();
verify.referenceGroups(aCommon, [
{ definition: "(property) A.common: string", ranges: [aCommon] },
{ definition: "(property) common: string | number", ranges: unionRefs },

View File

@ -28,7 +28,7 @@
//// }
//// function _buildMainButton({ onClick, children, className }: ButtonProps): JSX.Element {
//// return(<button className={className} onClick={onClick}>{ children || 'MAIN BUTTON'}</button>);
//// return(<button className={className} onClick={onClick}>{ children || 'MAIN BUTTON'}</button>);
//// }
//// declare function buildMainLink({ to, children, className }: LinkProps): JSX.Element;
@ -39,17 +39,17 @@
//// );
//// }
//// function buildSomeElement2(): JSX.Element {
//// function buildSomeElement2(): JSX.Element {
//// return (
//// <MainB/*3*/utton onC/*4*/lick={()=>{}}>GO</MainButton>;
//// );
//// }
//// let componenet = <MainButton onClick={()=>{}} ext/*5*/ra-prop>GO</MainButton>;
//// let componenet = <MainButton onClick={()=>{}} ext/*5*/ra-prop>GO</MainButton>;
verify.quickInfos({
1: "function MainButton(linkProps: LinkProps): any (+1 overload)",
2: "(JSX attribute) to: string",
2: "(JSX attribute) LinkProps.to: string",
3: "function MainButton(buttonProps: ButtonProps): any (+1 overload)",
4: "(JSX attribute) onClick: () => void",
4: "(method) ButtonProps.onClick(event?: any): void",
5: "(JSX attribute) extra-prop: true"
});