Skip to content

Commit 77d9362

Browse files
committed
Merge pull request microsoft#3699 from RyanCavanaugh/fixJsxAttribCompletion
Fix attribute completion following JSX exprs
2 parents ef67f02 + ef765e5 commit 77d9362

File tree

4 files changed

+71
-22
lines changed

4 files changed

+71
-22
lines changed

src/services/services.ts

+56-20
Original file line numberDiff line numberDiff line change
@@ -3011,6 +3011,7 @@ namespace ts {
30113011

30123012
function tryGetGlobalSymbols(): boolean {
30133013
let objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken);
3014+
let jsxContainer = tryGetContainingJsxElement(contextToken);
30143015
if (objectLikeContainer) {
30153016
// Object literal expression, look up possible property names from contextual type
30163017
isMemberCompletion = true;
@@ -3061,30 +3062,19 @@ namespace ts {
30613062
}
30623063
return true;
30633064
}
3064-
else if (getAncestor(contextToken, SyntaxKind.JsxElement) || getAncestor(contextToken, SyntaxKind.JsxSelfClosingElement)) {
3065-
// Go up until we hit either the element or expression
3066-
let jsxNode = contextToken;
3065+
else if(jsxContainer) {
3066+
let attrsType: Type;
3067+
if ((jsxContainer.kind === SyntaxKind.JsxSelfClosingElement) || (jsxContainer.kind === SyntaxKind.JsxOpeningElement)) {
3068+
// Cursor is inside a JSX self-closing element or opening element
3069+
attrsType = typeChecker.getJsxElementAttributesType(<JsxOpeningLikeElement>jsxContainer);
30673070

3068-
while (jsxNode) {
3069-
if (jsxNode.kind === SyntaxKind.JsxExpression) {
3070-
// Defer to global completion if we're inside an {expression}
3071-
break;
3072-
} else if (jsxNode.kind === SyntaxKind.JsxSelfClosingElement || jsxNode.kind === SyntaxKind.JsxElement) {
3073-
let attrsType: Type;
3074-
if (jsxNode.kind === SyntaxKind.JsxSelfClosingElement) {
3075-
// Cursor is inside a JSX self-closing element
3076-
attrsType = typeChecker.getJsxElementAttributesType(<JsxSelfClosingElement>jsxNode);
3077-
}
3078-
else {
3079-
Debug.assert(jsxNode.kind === SyntaxKind.JsxElement);
3080-
// Cursor is inside a JSX element
3081-
attrsType = typeChecker.getJsxElementAttributesType((<JsxElement>jsxNode).openingElement);
3082-
}
3083-
symbols = typeChecker.getPropertiesOfType(attrsType);
3071+
if (attrsType) {
3072+
symbols = filterJsxAttributes((<JsxOpeningLikeElement>jsxContainer).attributes, typeChecker.getPropertiesOfType(attrsType));
30843073
isMemberCompletion = true;
3074+
isNewIdentifierLocation = false;
30853075
return true;
30863076
}
3087-
jsxNode = jsxNode.parent;
3077+
30883078
}
30893079
}
30903080

@@ -3270,6 +3260,36 @@ namespace ts {
32703260
return undefined;
32713261
}
32723262

3263+
function tryGetContainingJsxElement(contextToken: Node): JsxOpeningLikeElement {
3264+
if (contextToken) {
3265+
let parent = contextToken.parent;
3266+
switch(contextToken.kind) {
3267+
case SyntaxKind.LessThanSlashToken:
3268+
case SyntaxKind.SlashToken:
3269+
case SyntaxKind.Identifier:
3270+
if(parent && (parent.kind === SyntaxKind.JsxSelfClosingElement || parent.kind === SyntaxKind.JsxOpeningElement)) {
3271+
return <JsxOpeningLikeElement>parent;
3272+
}
3273+
break;
3274+
3275+
case SyntaxKind.CloseBraceToken:
3276+
// The context token is the closing } of an attribute, which means
3277+
// its parent is a JsxExpression, whose parent is a JsxAttribute,
3278+
// whose parent is a JsxOpeningLikeElement
3279+
if(parent &&
3280+
parent.kind === SyntaxKind.JsxExpression &&
3281+
parent.parent &&
3282+
parent.parent.kind === SyntaxKind.JsxAttribute) {
3283+
3284+
return <JsxOpeningLikeElement>parent.parent.parent;
3285+
}
3286+
3287+
break;
3288+
}
3289+
}
3290+
return undefined;
3291+
}
3292+
32733293
function isFunction(kind: SyntaxKind): boolean {
32743294
switch (kind) {
32753295
case SyntaxKind.FunctionExpression:
@@ -3455,6 +3475,22 @@ namespace ts {
34553475
}
34563476
}
34573477

3478+
function filterJsxAttributes(attributes: NodeArray<JsxAttribute|JsxSpreadAttribute>, symbols: Symbol[]): Symbol[] {
3479+
let seenNames: Map<boolean> = {};
3480+
for(let attr of attributes) {
3481+
if(attr.kind === SyntaxKind.JsxAttribute) {
3482+
seenNames[(<JsxAttribute>attr).name.text] = true;
3483+
}
3484+
}
3485+
let result: Symbol[] = [];
3486+
for(let sym of symbols) {
3487+
if(!seenNames[sym.name]) {
3488+
result.push(sym);
3489+
}
3490+
}
3491+
return result;
3492+
}
3493+
34583494
function getCompletionsAtPosition(fileName: string, position: number): CompletionInfo {
34593495
synchronizeHostData();
34603496

tests/cases/fourslash/incrementalParsingInsertIntoMethod1.ts

-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,5 @@
88
//// public foo3() { }
99
////}
1010

11-
debugger;
1211
goTo.marker("1");
1312
edit.insert(" + 1");

tests/cases/fourslash/syntacticClassificationsConflictMarkers1.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
//// v = 2;
88
////>>>>>>> Branch - a
99
////}
10-
debugger;
10+
1111
var c = classification;
1212
verify.syntacticClassificationsAre(
1313
c.keyword("class"), c.className("C"), c.punctuation("{"),
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//@Filename: file.tsx
4+
//// declare module JSX {
5+
//// interface Element { }
6+
//// interface IntrinsicElements {
7+
//// div: { one; two; }
8+
//// }
9+
//// }
10+
//// <div one={1} /**//>;
11+
12+
goTo.marker();
13+
verify.completionListContains('two');
14+
verify.not.completionListContains('one');

0 commit comments

Comments
 (0)