mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-18 17:27:54 -05:00
Add language service support for JSXAttributes
Add language service support for JSXAttributes Add completion support Add find-all-references support Add goto-definition support
This commit is contained in:
@@ -1029,10 +1029,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;
|
||||
@@ -1374,13 +1374,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 JsxOpeninLikeElement as:
|
||||
// JsxOpeninLikeElement
|
||||
// attributes: JsxAttributes
|
||||
// properties: NodeArray<JsxAttributeLike>
|
||||
return /*properties list*/parent./*attributes*/parent.parent as JsxOpeningLikeElement;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1389,7 +1394,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 JsxOpeninLikeElement as:
|
||||
// JsxOpeninLikeElement
|
||||
// attributes: JsxAttributes
|
||||
// properties: NodeArray<JsxAttributeLike>
|
||||
return /*properties list*/parent./*attributes*/parent.parent as JsxOpeningLikeElement;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -1397,13 +1406,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 JsxOpeninLikeElement as:
|
||||
// JsxOpeninLikeElement
|
||||
// attributes: JsxAttributes
|
||||
// properties: NodeArray<JsxAttributeLike>
|
||||
// each JsxAttribute can have initializer as JsxExpression
|
||||
return /*JsxExpression*/parent./*JsxAttribute*/parent./*JsxAttributes*/parent.parent as JsxOpeningLikeElement;
|
||||
}
|
||||
|
||||
if (parent && parent.kind === SyntaxKind.JsxSpreadAttribute) {
|
||||
return <JsxOpeningLikeElement>parent.parent;
|
||||
// Currently we parse JsxOpeninLikeElement as:
|
||||
// JsxOpeninLikeElement
|
||||
// attributes: JsxAttributes
|
||||
// properties: NodeArray<JsxAttributeLike>
|
||||
return /*properties list*/parent./*attributes*/parent.parent as JsxOpeningLikeElement;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -1358,35 +1358,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);
|
||||
|
||||
@@ -85,6 +85,39 @@ namespace ts.GoToDefinition {
|
||||
declaration => createDefinitionInfo(declaration, shorthandSymbolKind, shorthandSymbolName, shorthandContainerName));
|
||||
}
|
||||
|
||||
if (isJsxOpeningLikeElement(node.parent)) {
|
||||
// For JSX opening-like element, the tag can be resolved either as stateful component (e.g class) or stateless function component.
|
||||
// Because if it is a stateless function component with an error while trying to resolve the signature, we don't want to return all
|
||||
// possible overloads but just the first one.
|
||||
// For example:
|
||||
// /*firstSource*/declare function MainButton(buttonProps: ButtonProps): JSX.Element;
|
||||
// /*secondSource*/declare function MainButton(linkProps: LinkProps): JSX.Element;
|
||||
// /*thirdSource*/declare function MainButton(props: ButtonProps | LinkProps): JSX.Element;
|
||||
// let opt = <Main/*firstTarget*/Button />; // 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 container = getContainingObjectLiteralElement(node);
|
||||
if (container) {
|
||||
const contextualType = typeChecker.getContextualType(node.parent.parent as Expression);
|
||||
if (contextualType) {
|
||||
let result: DefinitionInfo[] = [];
|
||||
forEach(getPropertySymbolsFromContextualType(typeChecker, container), contextualSymbol => {
|
||||
result = result.concat(getDefinitionFromSymbol(typeChecker, contextualSymbol, node));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return getDefinitionFromSymbol(typeChecker, symbol, node);
|
||||
}
|
||||
|
||||
@@ -254,6 +287,11 @@ namespace ts.GoToDefinition {
|
||||
|
||||
function tryGetSignatureDeclaration(typeChecker: TypeChecker, node: Node): SignatureDeclaration | undefined {
|
||||
const callLike = getAncestorCallLikeExpression(node);
|
||||
return callLike && typeChecker.getResolvedSignature(callLike).declaration;
|
||||
if (callLike) {
|
||||
const resolvedSignature = typeChecker.getResolvedSignature(callLike);
|
||||
// We have to check that resolvedSignature is not undefined because in the case of JSX opening-like element,
|
||||
// it may not be a stateless function component which then will cause getResolvedSignature to return undefined.
|
||||
return resolvedSignature && resolvedSignature.declaration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1990,6 +1990,76 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function isObjectLiteralPropertyDeclaration(node: Node): node is ObjectLiteralElement {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.JsxAttribute:
|
||||
case SyntaxKind.PropertyAssignment:
|
||||
case SyntaxKind.ShorthandPropertyAssignment:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
case SyntaxKind.GetAccessor:
|
||||
case SyntaxKind.SetAccessor:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getNameFromObjectLiteralElement(node: ObjectLiteralElement) {
|
||||
if (node.name.kind === SyntaxKind.ComputedPropertyName) {
|
||||
const nameExpression = (<ComputedPropertyName>node.name).expression;
|
||||
// treat computed property names where expression is string/numeric literal as just string/numeric literal
|
||||
if (isStringOrNumericLiteral(nameExpression.kind)) {
|
||||
return (<LiteralExpression>nameExpression).text;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
return (<Identifier | LiteralExpression>node.name).text;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 isObjectLiteralPropertyDeclaration(node.parent.parent) ? node.parent.parent : undefined;
|
||||
}
|
||||
// intentionally fall through
|
||||
case SyntaxKind.Identifier:
|
||||
return isObjectLiteralPropertyDeclaration(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>node.parent;
|
||||
const contextualType = typeChecker.getContextualType(objectLiteral);
|
||||
const name = getNameFromObjectLiteralElement(node);
|
||||
if (name && contextualType) {
|
||||
const result: Symbol[] = [];
|
||||
const symbol = contextualType.getProperty(name);
|
||||
if (symbol) {
|
||||
result.push(symbol);
|
||||
}
|
||||
|
||||
if (contextualType.flags & TypeFlags.Union) {
|
||||
forEach((<UnionType>contextualType).types, t => {
|
||||
const symbol = t.getProperty(name);
|
||||
if (symbol) {
|
||||
result.push(symbol);
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function isArgumentOfElementAccessExpression(node: Node) {
|
||||
return node &&
|
||||
node.parent &&
|
||||
|
||||
@@ -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 parenthesize, 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 { ... }
|
||||
// <MainBuntton /*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,
|
||||
|
||||
@@ -71,6 +71,9 @@ namespace ts.SymbolDisplay {
|
||||
}
|
||||
return unionPropertyKind;
|
||||
}
|
||||
if (location.parent && isJsxAttribute(location.parent)) {
|
||||
return ScriptElementKind.jsxAttribute;
|
||||
}
|
||||
return ScriptElementKind.memberVariableElement;
|
||||
}
|
||||
|
||||
@@ -114,23 +117,33 @@ 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;
|
||||
let useConstructSignatures = false;
|
||||
if (callExpressionLike.kind === SyntaxKind.NewExpression) {
|
||||
useConstructSignatures = true;
|
||||
}
|
||||
else if (isCallExpression(callExpressionLike) && callExpressionLike.expression.kind === SyntaxKind.SuperKeyword) {
|
||||
useConstructSignatures = true;
|
||||
}
|
||||
|
||||
const allSignatures = useConstructSignatures ? type.getConstructSignatures() : type.getCallSignatures();
|
||||
|
||||
if (!contains(allSignatures, signature.target) && !contains(allSignatures, signature)) {
|
||||
@@ -160,6 +173,7 @@ namespace ts.SymbolDisplay {
|
||||
}
|
||||
|
||||
switch (symbolKind) {
|
||||
case ScriptElementKind.jsxAttribute:
|
||||
case ScriptElementKind.memberVariableElement:
|
||||
case ScriptElementKind.variableElement:
|
||||
case ScriptElementKind.constElement:
|
||||
@@ -373,6 +387,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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace ts {
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.ArrowFunction:
|
||||
case SyntaxKind.CatchClause:
|
||||
case SyntaxKind.JsxAttribute:
|
||||
return SemanticMeaning.Value;
|
||||
|
||||
case SyntaxKind.TypeParameter:
|
||||
|
||||
Reference in New Issue
Block a user