From f465d99492db1d328a42a9b93c1e955cdaa540b3 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Wed, 1 Jul 2015 15:00:06 -0700 Subject: [PATCH 1/2] Fix attribute completion following JSX exprs --- src/services/services.ts | 69 ++++++++++++------- .../incrementalParsingInsertIntoMethod1.ts | 1 - ...yntacticClassificationsConflictMarkers1.ts | 2 +- tests/cases/fourslash/tsxCompletion3.ts | 13 ++++ 4 files changed, 60 insertions(+), 25 deletions(-) create mode 100644 tests/cases/fourslash/tsxCompletion3.ts diff --git a/src/services/services.ts b/src/services/services.ts index 018a11fc4bb..c193b53582b 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3009,6 +3009,7 @@ namespace ts { function tryGetGlobalSymbols(): boolean { let objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken); + let jsxContainer = tryGetContainingJsxElement(contextToken); if (objectLikeContainer) { // Object literal expression, look up possible property names from contextual type isMemberCompletion = true; @@ -3059,30 +3060,22 @@ namespace ts { } return true; } - else if (getAncestor(contextToken, SyntaxKind.JsxElement) || getAncestor(contextToken, SyntaxKind.JsxSelfClosingElement)) { - // Go up until we hit either the element or expression - let jsxNode = contextToken; + else if(jsxContainer) { + let attrsType: Type; + if (jsxContainer.kind === SyntaxKind.JsxSelfClosingElement) { + // Cursor is inside a JSX self-closing element + attrsType = typeChecker.getJsxElementAttributesType(jsxContainer); + } + else if(jsxContainer.kind === SyntaxKind.JsxOpeningElement) { + // Cursor is inside a JSX element + attrsType = typeChecker.getJsxElementAttributesType(jsxContainer); + } - while (jsxNode) { - if (jsxNode.kind === SyntaxKind.JsxExpression) { - // Defer to global completion if we're inside an {expression} - break; - } else if (jsxNode.kind === SyntaxKind.JsxSelfClosingElement || jsxNode.kind === SyntaxKind.JsxElement) { - let attrsType: Type; - if (jsxNode.kind === SyntaxKind.JsxSelfClosingElement) { - // Cursor is inside a JSX self-closing element - attrsType = typeChecker.getJsxElementAttributesType(jsxNode); - } - else { - Debug.assert(jsxNode.kind === SyntaxKind.JsxElement); - // Cursor is inside a JSX element - attrsType = typeChecker.getJsxElementAttributesType((jsxNode).openingElement); - } - symbols = typeChecker.getPropertiesOfType(attrsType); - isMemberCompletion = true; - return true; - } - jsxNode = jsxNode.parent; + if (attrsType) { + symbols = typeChecker.getPropertiesOfType(attrsType); + isMemberCompletion = true; + isNewIdentifierLocation = false; + return true; } } @@ -3268,6 +3261,36 @@ namespace ts { return undefined; } + function tryGetContainingJsxElement(contextToken: Node): JsxOpeningLikeElement { + if (contextToken) { + let parent = contextToken.parent; + switch(contextToken.kind) { + case SyntaxKind.LessThanSlashToken: + case SyntaxKind.SlashToken: + case SyntaxKind.Identifier: + if(parent && (parent.kind === SyntaxKind.JsxSelfClosingElement || parent.kind === SyntaxKind.JsxOpeningElement)) { + return parent; + } + break; + + case SyntaxKind.CloseBraceToken: + // The context token is the closing } of an attribute, which means + // its parent is a JsxExpression, whose parent is a JsxAttribute, + // whose parent is a JsxOpeningLikeElement + if(parent && + parent.kind === SyntaxKind.JsxExpression && + parent.parent && + parent.parent.kind === SyntaxKind.JsxAttribute) { + + return parent.parent.parent; + } + + break; + } + } + return undefined; + } + function isFunction(kind: SyntaxKind): boolean { switch (kind) { case SyntaxKind.FunctionExpression: diff --git a/tests/cases/fourslash/incrementalParsingInsertIntoMethod1.ts b/tests/cases/fourslash/incrementalParsingInsertIntoMethod1.ts index 930c0511ffb..b5deb361056 100644 --- a/tests/cases/fourslash/incrementalParsingInsertIntoMethod1.ts +++ b/tests/cases/fourslash/incrementalParsingInsertIntoMethod1.ts @@ -8,6 +8,5 @@ //// public foo3() { } ////} -debugger; goTo.marker("1"); edit.insert(" + 1"); \ No newline at end of file diff --git a/tests/cases/fourslash/syntacticClassificationsConflictMarkers1.ts b/tests/cases/fourslash/syntacticClassificationsConflictMarkers1.ts index e381856c41e..7f26038c33e 100644 --- a/tests/cases/fourslash/syntacticClassificationsConflictMarkers1.ts +++ b/tests/cases/fourslash/syntacticClassificationsConflictMarkers1.ts @@ -7,7 +7,7 @@ //// v = 2; ////>>>>>>> Branch - a ////} -debugger; + var c = classification; verify.syntacticClassificationsAre( c.keyword("class"), c.className("C"), c.punctuation("{"), diff --git a/tests/cases/fourslash/tsxCompletion3.ts b/tests/cases/fourslash/tsxCompletion3.ts new file mode 100644 index 00000000000..2d87d7ed056 --- /dev/null +++ b/tests/cases/fourslash/tsxCompletion3.ts @@ -0,0 +1,13 @@ +/// + +//@Filename: file.tsx +//// declare module JSX { +//// interface Element { } +//// interface IntrinsicElements { +//// div: { one; two; } +//// } +//// } +////
; + +goTo.marker(); +verify.completionListContains('two'); From ef765e56dfa2a2668bb55b46405ecef11f6612f5 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Wed, 1 Jul 2015 16:04:29 -0700 Subject: [PATCH 2/2] Filter out would-be-duplicate names from JSX attribute completion --- src/services/services.ts | 39 ++++++++++++++++--------- tests/cases/fourslash/tsxCompletion3.ts | 1 + 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index c193b53582b..395bd41ac92 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3062,20 +3062,17 @@ namespace ts { } else if(jsxContainer) { let attrsType: Type; - if (jsxContainer.kind === SyntaxKind.JsxSelfClosingElement) { - // Cursor is inside a JSX self-closing element - attrsType = typeChecker.getJsxElementAttributesType(jsxContainer); - } - else if(jsxContainer.kind === SyntaxKind.JsxOpeningElement) { - // Cursor is inside a JSX element - attrsType = typeChecker.getJsxElementAttributesType(jsxContainer); - } + if ((jsxContainer.kind === SyntaxKind.JsxSelfClosingElement) || (jsxContainer.kind === SyntaxKind.JsxOpeningElement)) { + // Cursor is inside a JSX self-closing element or opening element + attrsType = typeChecker.getJsxElementAttributesType(jsxContainer); + + if (attrsType) { + symbols = filterJsxAttributes((jsxContainer).attributes, typeChecker.getPropertiesOfType(attrsType)); + isMemberCompletion = true; + isNewIdentifierLocation = false; + return true; + } - if (attrsType) { - symbols = typeChecker.getPropertiesOfType(attrsType); - isMemberCompletion = true; - isNewIdentifierLocation = false; - return true; } } @@ -3476,6 +3473,22 @@ namespace ts { } } + function filterJsxAttributes(attributes: NodeArray, symbols: Symbol[]): Symbol[] { + let seenNames: Map = {}; + for(let attr of attributes) { + if(attr.kind === SyntaxKind.JsxAttribute) { + seenNames[(attr).name.text] = true; + } + } + let result: Symbol[] = []; + for(let sym of symbols) { + if(!seenNames[sym.name]) { + result.push(sym); + } + } + return result; + } + function getCompletionsAtPosition(fileName: string, position: number): CompletionInfo { synchronizeHostData(); diff --git a/tests/cases/fourslash/tsxCompletion3.ts b/tests/cases/fourslash/tsxCompletion3.ts index 2d87d7ed056..5ee712f7e96 100644 --- a/tests/cases/fourslash/tsxCompletion3.ts +++ b/tests/cases/fourslash/tsxCompletion3.ts @@ -11,3 +11,4 @@ goTo.marker(); verify.completionListContains('two'); +verify.not.completionListContains('one');