Skip to content

Commit 16d1b5d

Browse files
author
Kanchalai Tanglertsampan
committed
Add language service support for JSXAttributes
Add language service support for JSXAttributes Add completion support Add find-all-references support Add goto-definition support
1 parent 41108db commit 16d1b5d

8 files changed

Lines changed: 185 additions & 52 deletions

File tree

src/services/completions.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,10 +1029,10 @@ namespace ts.Completions {
10291029
let attrsType: Type;
10301030
if ((jsxContainer.kind === SyntaxKind.JsxSelfClosingElement) || (jsxContainer.kind === SyntaxKind.JsxOpeningElement)) {
10311031
// Cursor is inside a JSX self-closing element or opening element
1032-
attrsType = typeChecker.getJsxElementAttributesType(<JsxOpeningLikeElement>jsxContainer);
1032+
attrsType = typeChecker.getAllAttributesTypeFromJsxOpeningLikeElement(<JsxOpeningLikeElement>jsxContainer);
10331033

10341034
if (attrsType) {
1035-
symbols = filterJsxAttributes(typeChecker.getPropertiesOfType(attrsType), (<JsxOpeningLikeElement>jsxContainer).attributes);
1035+
symbols = filterJsxAttributes(typeChecker.getPropertiesOfType(attrsType), (<JsxOpeningLikeElement>jsxContainer).attributes.properties);
10361036
isMemberCompletion = true;
10371037
isNewIdentifierLocation = false;
10381038
return true;
@@ -1374,13 +1374,18 @@ namespace ts.Completions {
13741374
case SyntaxKind.LessThanSlashToken:
13751375
case SyntaxKind.SlashToken:
13761376
case SyntaxKind.Identifier:
1377+
case SyntaxKind.JsxAttributes:
13771378
case SyntaxKind.JsxAttribute:
13781379
case SyntaxKind.JsxSpreadAttribute:
13791380
if (parent && (parent.kind === SyntaxKind.JsxSelfClosingElement || parent.kind === SyntaxKind.JsxOpeningElement)) {
13801381
return <JsxOpeningLikeElement>parent;
13811382
}
13821383
else if (parent.kind === SyntaxKind.JsxAttribute) {
1383-
return <JsxOpeningLikeElement>parent.parent;
1384+
// Currently we parse JsxOpeninLikeElement as:
1385+
// JsxOpeninLikeElement
1386+
// attributes: JsxAttributes
1387+
// properties: NodeArray<JsxAttributeLike>
1388+
return /*properties list*/parent./*attributes*/parent.parent as JsxOpeningLikeElement;
13841389
}
13851390
break;
13861391

@@ -1389,21 +1394,33 @@ namespace ts.Completions {
13891394
// whose parent is a JsxOpeningLikeElement
13901395
case SyntaxKind.StringLiteral:
13911396
if (parent && ((parent.kind === SyntaxKind.JsxAttribute) || (parent.kind === SyntaxKind.JsxSpreadAttribute))) {
1392-
return <JsxOpeningLikeElement>parent.parent;
1397+
// Currently we parse JsxOpeninLikeElement as:
1398+
// JsxOpeninLikeElement
1399+
// attributes: JsxAttributes
1400+
// properties: NodeArray<JsxAttributeLike>
1401+
return /*properties list*/parent./*attributes*/parent.parent as JsxOpeningLikeElement;
13931402
}
13941403

13951404
break;
13961405

13971406
case SyntaxKind.CloseBraceToken:
13981407
if (parent &&
13991408
parent.kind === SyntaxKind.JsxExpression &&
1400-
parent.parent &&
1401-
(parent.parent.kind === SyntaxKind.JsxAttribute)) {
1402-
return <JsxOpeningLikeElement>parent.parent.parent;
1409+
parent.parent && parent.parent.kind === SyntaxKind.JsxAttribute) {
1410+
// Currently we parse JsxOpeninLikeElement as:
1411+
// JsxOpeninLikeElement
1412+
// attributes: JsxAttributes
1413+
// properties: NodeArray<JsxAttributeLike>
1414+
// each JsxAttribute can have initializer as JsxExpression
1415+
return /*JsxExpression*/parent./*JsxAttribute*/parent./*JsxAttributes*/parent.parent as JsxOpeningLikeElement;
14031416
}
14041417

14051418
if (parent && parent.kind === SyntaxKind.JsxSpreadAttribute) {
1406-
return <JsxOpeningLikeElement>parent.parent;
1419+
// Currently we parse JsxOpeninLikeElement as:
1420+
// JsxOpeninLikeElement
1421+
// attributes: JsxAttributes
1422+
// properties: NodeArray<JsxAttributeLike>
1423+
return /*properties list*/parent./*attributes*/parent.parent as JsxOpeningLikeElement;
14071424
}
14081425

14091426
break;

src/services/findAllReferences.ts

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1358,35 +1358,6 @@ namespace ts.FindAllReferences {
13581358
});
13591359
}
13601360

1361-
/**
1362-
* Returns the containing object literal property declaration given a possible name node, e.g. "a" in x = { "a": 1 }
1363-
*/
1364-
function getContainingObjectLiteralElement(node: Node): ObjectLiteralElement {
1365-
switch (node.kind) {
1366-
case SyntaxKind.StringLiteral:
1367-
case SyntaxKind.NumericLiteral:
1368-
if (node.parent.kind === SyntaxKind.ComputedPropertyName) {
1369-
return isObjectLiteralPropertyDeclaration(node.parent.parent) ? node.parent.parent : undefined;
1370-
}
1371-
// intential fall through
1372-
case SyntaxKind.Identifier:
1373-
return isObjectLiteralPropertyDeclaration(node.parent) && node.parent.name === node ? node.parent : undefined;
1374-
}
1375-
return undefined;
1376-
}
1377-
1378-
function isObjectLiteralPropertyDeclaration(node: Node): node is ObjectLiteralElement {
1379-
switch (node.kind) {
1380-
case SyntaxKind.PropertyAssignment:
1381-
case SyntaxKind.ShorthandPropertyAssignment:
1382-
case SyntaxKind.MethodDeclaration:
1383-
case SyntaxKind.GetAccessor:
1384-
case SyntaxKind.SetAccessor:
1385-
return true;
1386-
}
1387-
return false;
1388-
}
1389-
13901361
/** 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. */
13911362
function tryGetClassByExtendingIdentifier(node: Node): ClassLikeDeclaration | undefined {
13921363
return tryGetClassExtendingExpressionWithTypeArguments(climbPastPropertyAccess(node).parent);

src/services/goToDefinition.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,39 @@ namespace ts.GoToDefinition {
8585
declaration => createDefinitionInfo(declaration, shorthandSymbolKind, shorthandSymbolName, shorthandContainerName));
8686
}
8787

88+
if (isJsxOpeningLikeElement(node.parent)) {
89+
// For JSX opening-like element, the tag can be resolved either as stateful component (e.g class) or stateless function component.
90+
// Because if it is a stateless function component with an error while trying to resolve the signature, we don't want to return all
91+
// possible overloads but just the first one.
92+
// For example:
93+
// /*firstSource*/declare function MainButton(buttonProps: ButtonProps): JSX.Element;
94+
// /*secondSource*/declare function MainButton(linkProps: LinkProps): JSX.Element;
95+
// /*thirdSource*/declare function MainButton(props: ButtonProps | LinkProps): JSX.Element;
96+
// let opt = <Main/*firstTarget*/Button />; // We get undefined for resolved signature indicating an error, then just return the first declaration
97+
const {symbolName, symbolKind, containerName} = getSymbolInfo(typeChecker, symbol, node);
98+
return [createDefinitionInfo(symbol.valueDeclaration, symbolKind, symbolName, containerName)];
99+
}
100+
101+
// If the current location we want to find its definition is in an object literal, try to get the contextual type for the
102+
// object literal, lookup the property symbol in the contextual type, and use this for goto-definition.
103+
// For example
104+
// interface Props{
105+
// /first*/prop1: number
106+
// prop2: boolean
107+
// }
108+
// function Foo(arg: Props) {}
109+
// Foo( { pr/*1*/op1: 10, prop2: true })
110+
const container = getContainingObjectLiteralElement(node);
111+
if (container) {
112+
const contextualType = typeChecker.getContextualType(node.parent.parent as Expression);
113+
if (contextualType) {
114+
let result: DefinitionInfo[] = [];
115+
forEach(getPropertySymbolsFromContextualType(typeChecker, container), contextualSymbol => {
116+
result = result.concat(getDefinitionFromSymbol(typeChecker, contextualSymbol, node));
117+
});
118+
return result;
119+
}
120+
}
88121
return getDefinitionFromSymbol(typeChecker, symbol, node);
89122
}
90123

@@ -254,6 +287,11 @@ namespace ts.GoToDefinition {
254287

255288
function tryGetSignatureDeclaration(typeChecker: TypeChecker, node: Node): SignatureDeclaration | undefined {
256289
const callLike = getAncestorCallLikeExpression(node);
257-
return callLike && typeChecker.getResolvedSignature(callLike).declaration;
290+
if (callLike) {
291+
const resolvedSignature = typeChecker.getResolvedSignature(callLike);
292+
// We have to check that resolvedSignature is not undefined because in the case of JSX opening-like element,
293+
// it may not be a stateless function component which then will cause getResolvedSignature to return undefined.
294+
return resolvedSignature && resolvedSignature.declaration;
295+
}
258296
}
259297
}

src/services/services.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1990,6 +1990,76 @@ namespace ts {
19901990
}
19911991
}
19921992

1993+
function isObjectLiteralPropertyDeclaration(node: Node): node is ObjectLiteralElement {
1994+
switch (node.kind) {
1995+
case SyntaxKind.JsxAttribute:
1996+
case SyntaxKind.PropertyAssignment:
1997+
case SyntaxKind.ShorthandPropertyAssignment:
1998+
case SyntaxKind.MethodDeclaration:
1999+
case SyntaxKind.GetAccessor:
2000+
case SyntaxKind.SetAccessor:
2001+
return true;
2002+
}
2003+
return false;
2004+
}
2005+
2006+
function getNameFromObjectLiteralElement(node: ObjectLiteralElement) {
2007+
if (node.name.kind === SyntaxKind.ComputedPropertyName) {
2008+
const nameExpression = (<ComputedPropertyName>node.name).expression;
2009+
// treat computed property names where expression is string/numeric literal as just string/numeric literal
2010+
if (isStringOrNumericLiteral(nameExpression.kind)) {
2011+
return (<LiteralExpression>nameExpression).text;
2012+
}
2013+
return undefined;
2014+
}
2015+
return (<Identifier | LiteralExpression>node.name).text;
2016+
}
2017+
2018+
/**
2019+
* Returns the containing object literal property declaration given a possible name node, e.g. "a" in x = { "a": 1 }
2020+
*/
2021+
/* @internal */
2022+
export function getContainingObjectLiteralElement(node: Node): ObjectLiteralElement {
2023+
switch (node.kind) {
2024+
case SyntaxKind.StringLiteral:
2025+
case SyntaxKind.NumericLiteral:
2026+
if (node.parent.kind === SyntaxKind.ComputedPropertyName) {
2027+
return isObjectLiteralPropertyDeclaration(node.parent.parent) ? node.parent.parent : undefined;
2028+
}
2029+
// intentionally fall through
2030+
case SyntaxKind.Identifier:
2031+
return isObjectLiteralPropertyDeclaration(node.parent) &&
2032+
(node.parent.parent.kind === SyntaxKind.ObjectLiteralExpression || node.parent.parent.kind === SyntaxKind.JsxAttributes) &&
2033+
(<ObjectLiteralElement>node.parent).name === node ? node.parent as ObjectLiteralElement : undefined;
2034+
}
2035+
return undefined;
2036+
}
2037+
2038+
/* @internal */
2039+
export function getPropertySymbolsFromContextualType(typeChecker: TypeChecker, node: ObjectLiteralElement): Symbol[] {
2040+
const objectLiteral = <ObjectLiteralExpression>node.parent;
2041+
const contextualType = typeChecker.getContextualType(objectLiteral);
2042+
const name = getNameFromObjectLiteralElement(node);
2043+
if (name && contextualType) {
2044+
const result: Symbol[] = [];
2045+
const symbol = contextualType.getProperty(name);
2046+
if (symbol) {
2047+
result.push(symbol);
2048+
}
2049+
2050+
if (contextualType.flags & TypeFlags.Union) {
2051+
forEach((<UnionType>contextualType).types, t => {
2052+
const symbol = t.getProperty(name);
2053+
if (symbol) {
2054+
result.push(symbol);
2055+
}
2056+
});
2057+
}
2058+
return result;
2059+
}
2060+
return undefined;
2061+
}
2062+
19932063
function isArgumentOfElementAccessExpression(node: Node) {
19942064
return node &&
19952065
node.parent &&

src/services/signatureHelp.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,8 @@ namespace ts.SignatureHelp {
168168
export const enum ArgumentListKind {
169169
TypeArguments,
170170
CallArguments,
171-
TaggedTemplateArguments
171+
TaggedTemplateArguments,
172+
JSXAttributesArguments
172173
}
173174

174175
export interface ArgumentListInfo {
@@ -264,18 +265,18 @@ namespace ts.SignatureHelp {
264265
if (node.parent.kind === SyntaxKind.CallExpression || node.parent.kind === SyntaxKind.NewExpression) {
265266
const callExpression = <CallExpression>node.parent;
266267
// There are 3 cases to handle:
267-
// 1. The token introduces a list, and should begin a sig help session
268+
// 1. The token introduces a list, and should begin a signature help session
268269
// 2. The token is either not associated with a list, or ends a list, so the session should end
269-
// 3. The token is buried inside a list, and should give sig help
270+
// 3. The token is buried inside a list, and should give signature help
270271
//
271272
// The following are examples of each:
272273
//
273274
// Case 1:
274-
// foo<#T, U>(#a, b) -> The token introduces a list, and should begin a sig help session
275+
// foo<#T, U>(#a, b) -> The token introduces a list, and should begin a signature help session
275276
// Case 2:
276277
// fo#o<T, U>#(a, b)# -> The token is either not associated with a list, or ends a list, so the session should end
277278
// Case 3:
278-
// foo<T#, U#>(a#, #b#) -> The token is buried inside a list, and should give sig help
279+
// foo<T#, U#>(a#, #b#) -> The token is buried inside a list, and should give signature help
279280
// Find out if 'node' is an argument, a type argument, or neither
280281
if (node.kind === SyntaxKind.LessThanToken ||
281282
node.kind === SyntaxKind.OpenParenToken) {
@@ -295,7 +296,7 @@ namespace ts.SignatureHelp {
295296

296297
// findListItemInfo can return undefined if we are not in parent's argument list
297298
// or type argument list. This includes cases where the cursor is:
298-
// - To the right of the closing paren, non-substitution template, or template tail.
299+
// - To the right of the closing parenthesize, non-substitution template, or template tail.
299300
// - Between the type arguments and the arguments (greater than token)
300301
// - On the target of the call (parent.func)
301302
// - On the 'new' keyword in a 'new' expression
@@ -352,6 +353,22 @@ namespace ts.SignatureHelp {
352353

353354
return getArgumentListInfoForTemplate(tagExpression, argumentIndex, sourceFile);
354355
}
356+
else if (node.parent && isJsxOpeningLikeElement(node.parent)) {
357+
// Provide a signature help for JSX opening element or JSX self-closing element.
358+
// This is not guarantee that JSX tag-name is resolved into stateless function component. (that is done in "getSignatureHelpItems")
359+
// i.e
360+
// export function MainButton(props: ButtonProps, context: any): JSX.Element { ... }
361+
// <MainBuntton /*signatureHelp*/
362+
const attributeSpanStart = node.parent.attributes.getFullStart();
363+
const attributeSpanEnd = skipTrivia(sourceFile.text, node.parent.attributes.getEnd(), /*stopAfterLineBreak*/ false);
364+
return {
365+
kind: ArgumentListKind.JSXAttributesArguments,
366+
invocation: node.parent,
367+
argumentsSpan: createTextSpan(attributeSpanStart, attributeSpanEnd - attributeSpanStart),
368+
argumentIndex: 0,
369+
argumentCount: 1
370+
};
371+
}
355372

356373
return undefined;
357374
}
@@ -392,7 +409,7 @@ namespace ts.SignatureHelp {
392409
//
393410
// Note: this subtlety only applies to the last comma. If you had "Foo(a,," then
394411
// we'll have: 'a' '<comma>' '<missing>'
395-
// That will give us 2 non-commas. We then add one for the last comma, givin us an
412+
// That will give us 2 non-commas. We then add one for the last comma, giving us an
396413
// arg count of 3.
397414
const listChildren = argumentsList.getChildren();
398415

@@ -435,7 +452,6 @@ namespace ts.SignatureHelp {
435452
: (<TemplateExpression>tagExpression.template).templateSpans.length + 1;
436453

437454
Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount, `argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`);
438-
439455
return {
440456
kind: ArgumentListKind.TaggedTemplateArguments,
441457
invocation: tagExpression,

0 commit comments

Comments
 (0)