Merge pull request #13640 from Microsoft/wip-master-statelessOverload

Using overload to figure out function signature for SFC
This commit is contained in:
Yui
2017-02-15 07:17:32 -08:00
committed by GitHub
268 changed files with 11483 additions and 979 deletions
+25 -8
View File
@@ -1056,10 +1056,10 @@ namespace ts.Completions {
let attrsType: Type;
if ((jsxContainer.kind === SyntaxKind.JsxSelfClosingElement) || (jsxContainer.kind === SyntaxKind.JsxOpeningElement)) {
// Cursor is inside a JSX self-closing element or opening element
attrsType = typeChecker.getJsxElementAttributesType(<JsxOpeningLikeElement>jsxContainer);
attrsType = typeChecker.getAllAttributesTypeFromJsxOpeningLikeElement(<JsxOpeningLikeElement>jsxContainer);
if (attrsType) {
symbols = filterJsxAttributes(typeChecker.getPropertiesOfType(attrsType), (<JsxOpeningLikeElement>jsxContainer).attributes);
symbols = filterJsxAttributes(typeChecker.getPropertiesOfType(attrsType), (<JsxOpeningLikeElement>jsxContainer).attributes.properties);
isMemberCompletion = true;
isNewIdentifierLocation = false;
return true;
@@ -1401,13 +1401,18 @@ namespace ts.Completions {
case SyntaxKind.LessThanSlashToken:
case SyntaxKind.SlashToken:
case SyntaxKind.Identifier:
case SyntaxKind.JsxAttributes:
case SyntaxKind.JsxAttribute:
case SyntaxKind.JsxSpreadAttribute:
if (parent && (parent.kind === SyntaxKind.JsxSelfClosingElement || parent.kind === SyntaxKind.JsxOpeningElement)) {
return <JsxOpeningLikeElement>parent;
}
else if (parent.kind === SyntaxKind.JsxAttribute) {
return <JsxOpeningLikeElement>parent.parent;
// Currently we parse JsxOpeningLikeElement as:
// JsxOpeningLikeElement
// attributes: JsxAttributes
// properties: NodeArray<JsxAttributeLike>
return parent.parent.parent as JsxOpeningLikeElement;
}
break;
@@ -1416,7 +1421,11 @@ namespace ts.Completions {
// whose parent is a JsxOpeningLikeElement
case SyntaxKind.StringLiteral:
if (parent && ((parent.kind === SyntaxKind.JsxAttribute) || (parent.kind === SyntaxKind.JsxSpreadAttribute))) {
return <JsxOpeningLikeElement>parent.parent;
// Currently we parse JsxOpeningLikeElement as:
// JsxOpeningLikeElement
// attributes: JsxAttributes
// properties: NodeArray<JsxAttributeLike>
return parent.parent.parent as JsxOpeningLikeElement;
}
break;
@@ -1424,13 +1433,21 @@ namespace ts.Completions {
case SyntaxKind.CloseBraceToken:
if (parent &&
parent.kind === SyntaxKind.JsxExpression &&
parent.parent &&
(parent.parent.kind === SyntaxKind.JsxAttribute)) {
return <JsxOpeningLikeElement>parent.parent.parent;
parent.parent && parent.parent.kind === SyntaxKind.JsxAttribute) {
// Currently we parse JsxOpeningLikeElement as:
// JsxOpeningLikeElement
// attributes: JsxAttributes
// properties: NodeArray<JsxAttributeLike>
// each JsxAttribute can have initializer as JsxExpression
return parent.parent.parent.parent as JsxOpeningLikeElement;
}
if (parent && parent.kind === SyntaxKind.JsxSpreadAttribute) {
return <JsxOpeningLikeElement>parent.parent;
// Currently we parse JsxOpeningLikeElement as:
// JsxOpeningLikeElement
// attributes: JsxAttributes
// properties: NodeArray<JsxAttributeLike>
return parent.parent.parent as JsxOpeningLikeElement;
}
break;
-29
View File
@@ -1416,35 +1416,6 @@ namespace ts.FindAllReferences {
});
}
/**
* Returns the containing object literal property declaration given a possible name node, e.g. "a" in x = { "a": 1 }
*/
function getContainingObjectLiteralElement(node: Node): ObjectLiteralElement {
switch (node.kind) {
case SyntaxKind.StringLiteral:
case SyntaxKind.NumericLiteral:
if (node.parent.kind === SyntaxKind.ComputedPropertyName) {
return isObjectLiteralPropertyDeclaration(node.parent.parent) ? node.parent.parent : undefined;
}
// intential fall through
case SyntaxKind.Identifier:
return isObjectLiteralPropertyDeclaration(node.parent) && node.parent.name === node ? node.parent : undefined;
}
return undefined;
}
function isObjectLiteralPropertyDeclaration(node: Node): node is ObjectLiteralElement {
switch (node.kind) {
case SyntaxKind.PropertyAssignment:
case SyntaxKind.ShorthandPropertyAssignment:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
return true;
}
return false;
}
/** Get `C` given `N` if `N` is in the position `class C extends N` or `class C extends foo.N` where `N` is an identifier. */
function tryGetClassByExtendingIdentifier(node: Node): ClassLikeDeclaration | undefined {
return tryGetClassExtendingExpressionWithTypeArguments(climbPastPropertyAccess(node).parent);
+38 -4
View File
@@ -1,4 +1,4 @@
/* @internal */
/* @internal */
namespace ts.GoToDefinition {
export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile, position: number): DefinitionInfo[] {
/// Triple slash reference comments
@@ -85,6 +85,37 @@ namespace ts.GoToDefinition {
declaration => createDefinitionInfo(declaration, shorthandSymbolKind, shorthandSymbolName, shorthandContainerName));
}
if (isJsxOpeningLikeElement(node.parent)) {
// If there are errors when trying to figure out stateless component function, just return the first declaration
// For example:
// declare function /*firstSource*/MainButton(buttonProps: ButtonProps): JSX.Element;
// declare function /*secondSource*/MainButton(linkProps: LinkProps): JSX.Element;
// declare function /*thirdSource*/MainButton(props: ButtonProps | LinkProps): JSX.Element;
// let opt = <Main/*firstTarget*/Button />; // Error - We get undefined for resolved signature indicating an error, then just return the first declaration
const {symbolName, symbolKind, containerName} = getSymbolInfo(typeChecker, symbol, node);
return [createDefinitionInfo(symbol.valueDeclaration, symbolKind, symbolName, containerName)];
}
// If the current location we want to find its definition is in an object literal, try to get the contextual type for the
// object literal, lookup the property symbol in the contextual type, and use this for goto-definition.
// For example
// interface Props{
// /*first*/prop1: number
// prop2: boolean
// }
// function Foo(arg: Props) {}
// Foo( { pr/*1*/op1: 10, prop2: true })
const element = getContainingObjectLiteralElement(node);
if (element) {
if (typeChecker.getContextualType(element.parent as Expression)) {
const result: DefinitionInfo[] = [];
const propertySymbols = getPropertySymbolsFromContextualType(typeChecker, element);
for (const propertySymbol of propertySymbols) {
result.push(...getDefinitionFromSymbol(typeChecker, propertySymbol, node));
}
return result;
}
}
return getDefinitionFromSymbol(typeChecker, symbol, node);
}
@@ -262,9 +293,12 @@ namespace ts.GoToDefinition {
function tryGetSignatureDeclaration(typeChecker: TypeChecker, node: Node): SignatureDeclaration | undefined {
const callLike = getAncestorCallLikeExpression(node);
const decl = callLike && typeChecker.getResolvedSignature(callLike).declaration;
if (decl && isSignatureDeclaration(decl)) {
return decl;
const signature = callLike && typeChecker.getResolvedSignature(callLike);
if (signature) {
const decl = signature.declaration;
if (decl && isSignatureDeclaration(decl)) {
return decl;
}
}
// Don't go to a function type, go to the value having that type.
return undefined;
+60
View File
@@ -1991,6 +1991,66 @@ namespace ts {
}
}
function isObjectLiteralElement(node: Node): node is ObjectLiteralElement {
switch (node.kind) {
case SyntaxKind.JsxAttribute:
case SyntaxKind.JsxSpreadAttribute:
case SyntaxKind.PropertyAssignment:
case SyntaxKind.ShorthandPropertyAssignment:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
return true;
}
return false;
}
/**
* Returns the containing object literal property declaration given a possible name node, e.g. "a" in x = { "a": 1 }
*/
/* @internal */
export function getContainingObjectLiteralElement(node: Node): ObjectLiteralElement {
switch (node.kind) {
case SyntaxKind.StringLiteral:
case SyntaxKind.NumericLiteral:
if (node.parent.kind === SyntaxKind.ComputedPropertyName) {
return isObjectLiteralElement(node.parent.parent) ? node.parent.parent : undefined;
}
// intentionally fall through
case SyntaxKind.Identifier:
return isObjectLiteralElement(node.parent) &&
(node.parent.parent.kind === SyntaxKind.ObjectLiteralExpression || node.parent.parent.kind === SyntaxKind.JsxAttributes) &&
(<ObjectLiteralElement>node.parent).name === node ? node.parent as ObjectLiteralElement : undefined;
}
return undefined;
}
/* @internal */
export function getPropertySymbolsFromContextualType(typeChecker: TypeChecker, node: ObjectLiteralElement): Symbol[] {
const objectLiteral = <ObjectLiteralExpression | JsxAttributes>node.parent;
const contextualType = typeChecker.getContextualType(objectLiteral);
const name = getTextOfPropertyName(node.name);
if (name && contextualType) {
const result: Symbol[] = [];
const symbol = contextualType.getProperty(name);
if (contextualType.flags & TypeFlags.Union) {
forEach((<UnionType>contextualType).types, t => {
const symbol = t.getProperty(name);
if (symbol) {
result.push(symbol);
}
});
return result;
}
if (symbol) {
result.push(symbol);
return result;
}
}
return undefined;
}
function isArgumentOfElementAccessExpression(node: Node) {
return node &&
node.parent &&
+24 -8
View File
@@ -168,7 +168,8 @@ namespace ts.SignatureHelp {
export const enum ArgumentListKind {
TypeArguments,
CallArguments,
TaggedTemplateArguments
TaggedTemplateArguments,
JSXAttributesArguments
}
export interface ArgumentListInfo {
@@ -264,18 +265,18 @@ namespace ts.SignatureHelp {
if (node.parent.kind === SyntaxKind.CallExpression || node.parent.kind === SyntaxKind.NewExpression) {
const callExpression = <CallExpression>node.parent;
// There are 3 cases to handle:
// 1. The token introduces a list, and should begin a sig help session
// 1. The token introduces a list, and should begin a signature help session
// 2. The token is either not associated with a list, or ends a list, so the session should end
// 3. The token is buried inside a list, and should give sig help
// 3. The token is buried inside a list, and should give signature help
//
// The following are examples of each:
//
// Case 1:
// foo<#T, U>(#a, b) -> The token introduces a list, and should begin a sig help session
// foo<#T, U>(#a, b) -> The token introduces a list, and should begin a signature help session
// Case 2:
// fo#o<T, U>#(a, b)# -> The token is either not associated with a list, or ends a list, so the session should end
// Case 3:
// foo<T#, U#>(a#, #b#) -> The token is buried inside a list, and should give sig help
// foo<T#, U#>(a#, #b#) -> The token is buried inside a list, and should give signature help
// Find out if 'node' is an argument, a type argument, or neither
if (node.kind === SyntaxKind.LessThanToken ||
node.kind === SyntaxKind.OpenParenToken) {
@@ -295,7 +296,7 @@ namespace ts.SignatureHelp {
// findListItemInfo can return undefined if we are not in parent's argument list
// or type argument list. This includes cases where the cursor is:
// - To the right of the closing paren, non-substitution template, or template tail.
// - To the right of the closing parenthesis, non-substitution template, or template tail.
// - Between the type arguments and the arguments (greater than token)
// - On the target of the call (parent.func)
// - On the 'new' keyword in a 'new' expression
@@ -352,6 +353,22 @@ namespace ts.SignatureHelp {
return getArgumentListInfoForTemplate(tagExpression, argumentIndex, sourceFile);
}
else if (node.parent && isJsxOpeningLikeElement(node.parent)) {
// Provide a signature help for JSX opening element or JSX self-closing element.
// This is not guarantee that JSX tag-name is resolved into stateless function component. (that is done in "getSignatureHelpItems")
// i.e
// export function MainButton(props: ButtonProps, context: any): JSX.Element { ... }
// <MainButton /*signatureHelp*/
const attributeSpanStart = node.parent.attributes.getFullStart();
const attributeSpanEnd = skipTrivia(sourceFile.text, node.parent.attributes.getEnd(), /*stopAfterLineBreak*/ false);
return {
kind: ArgumentListKind.JSXAttributesArguments,
invocation: node.parent,
argumentsSpan: createTextSpan(attributeSpanStart, attributeSpanEnd - attributeSpanStart),
argumentIndex: 0,
argumentCount: 1
};
}
return undefined;
}
@@ -392,7 +409,7 @@ namespace ts.SignatureHelp {
//
// Note: this subtlety only applies to the last comma. If you had "Foo(a,," then
// we'll have: 'a' '<comma>' '<missing>'
// That will give us 2 non-commas. We then add one for the last comma, givin us an
// That will give us 2 non-commas. We then add one for the last comma, giving us an
// arg count of 3.
const listChildren = argumentsList.getChildren();
@@ -435,7 +452,6 @@ namespace ts.SignatureHelp {
: (<TemplateExpression>tagExpression.template).templateSpans.length + 1;
Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount, `argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`);
return {
kind: ArgumentListKind.TaggedTemplateArguments,
invocation: tagExpression,
+15 -6
View File
@@ -71,6 +71,9 @@ namespace ts.SymbolDisplay {
}
return unionPropertyKind;
}
if (location.parent && isJsxAttribute(location.parent)) {
return ScriptElementKind.jsxAttribute;
}
return ScriptElementKind.memberVariableElement;
}
@@ -114,23 +117,27 @@ namespace ts.SymbolDisplay {
}
// try get the call/construct signature from the type if it matches
let callExpression: CallExpression | NewExpression;
let callExpressionLike: CallExpression | NewExpression | JsxOpeningLikeElement;
if (location.kind === SyntaxKind.CallExpression || location.kind === SyntaxKind.NewExpression) {
callExpression = <CallExpression | NewExpression>location;
callExpressionLike = <CallExpression | NewExpression>location;
}
else if (isCallExpressionTarget(location) || isNewExpressionTarget(location)) {
callExpression = <CallExpression | NewExpression>location.parent;
callExpressionLike = <CallExpression | NewExpression>location.parent;
}
else if (location.parent && isJsxOpeningLikeElement(location.parent) && isFunctionLike(symbol.valueDeclaration)) {
callExpressionLike = <JsxOpeningLikeElement>location.parent;
}
if (callExpression) {
if (callExpressionLike) {
const candidateSignatures: Signature[] = [];
signature = typeChecker.getResolvedSignature(callExpression, candidateSignatures);
signature = typeChecker.getResolvedSignature(callExpressionLike, candidateSignatures);
if (!signature && candidateSignatures.length) {
// Use the first candidate:
signature = candidateSignatures[0];
}
const useConstructSignatures = callExpression.kind === SyntaxKind.NewExpression || callExpression.expression.kind === SyntaxKind.SuperKeyword;
const useConstructSignatures = callExpressionLike.kind === SyntaxKind.NewExpression || (isCallExpression(callExpressionLike) && callExpressionLike.expression.kind === SyntaxKind.SuperKeyword);
const allSignatures = useConstructSignatures ? type.getConstructSignatures() : type.getCallSignatures();
if (!contains(allSignatures, signature.target) && !contains(allSignatures, signature)) {
@@ -160,6 +167,7 @@ namespace ts.SymbolDisplay {
}
switch (symbolKind) {
case ScriptElementKind.jsxAttribute:
case ScriptElementKind.memberVariableElement:
case ScriptElementKind.variableElement:
case ScriptElementKind.constElement:
@@ -373,6 +381,7 @@ namespace ts.SymbolDisplay {
// For properties, variables and local vars: show the type
if (symbolKind === ScriptElementKind.memberVariableElement ||
symbolKind === ScriptElementKind.jsxAttribute ||
symbolFlags & SymbolFlags.Variable ||
symbolKind === ScriptElementKind.localVariableElement ||
isThisExpression) {
+5
View File
@@ -765,6 +765,11 @@ namespace ts {
export const directory = "directory";
export const externalModuleName = "external module name";
/**
* <JsxTagName attribute1 attribute2={0} />
**/
export const jsxAttribute = "JSX attribute";
}
export namespace ScriptElementKindModifier {
+1
View File
@@ -31,6 +31,7 @@ namespace ts {
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
case SyntaxKind.CatchClause:
case SyntaxKind.JsxAttribute:
return SemanticMeaning.Value;
case SyntaxKind.TypeParameter: