/*1*/
- // The completion list at "1" will contain "div>" with type any
- // And at `
/*1*/ >` (with a closing `>`), the completion list will contain "div".
- const tagName = location.parent.parent.openingElement.tagName;
- const hasClosingAngleBracket = !!findChildOfKind(location.parent, SyntaxKind.GreaterThanToken, sourceFile);
- const entry: CompletionEntry = {
- name: tagName.getFullText(sourceFile) + (hasClosingAngleBracket ? "" : ">"),
- kind: ScriptElementKind.classElement,
- kindModifiers: undefined,
- sortText: SortText.LocationPriority,
- };
- return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: false, optionalReplacementSpan: getOptionalReplacementSpan(location), entries: [entry] };
+ // Verify if the file is JSX language variant
+ if (getLanguageVariant(sourceFile.scriptKind) === LanguageVariant.JSX) {
+ const completionInfo = getJsxClosingTagCompletion(location, sourceFile);
+ if (completionInfo) {
+ return completionInfo;
+ }
}
const entries: CompletionEntry[] = [];
@@ -335,6 +325,52 @@ namespace ts.Completions {
}
}
+ function getJsxClosingTagCompletion(location: Node | undefined, sourceFile: SourceFile): CompletionInfo | undefined {
+ // We wanna walk up the tree till we find a JSX closing element
+ const jsxClosingElement = findAncestor(location, node => {
+ switch (node.kind) {
+ case SyntaxKind.JsxClosingElement:
+ return true;
+ case SyntaxKind.SlashToken:
+ case SyntaxKind.GreaterThanToken:
+ case SyntaxKind.Identifier:
+ case SyntaxKind.PropertyAccessExpression:
+ return false;
+ default:
+ return "quit";
+ }
+ }) as JsxClosingElement | undefined;
+
+ if (jsxClosingElement) {
+ // In the TypeScript JSX element, if such element is not defined. When users query for completion at closing tag,
+ // instead of simply giving unknown value, the completion will return the tag-name of an associated opening-element.
+ // For example:
+ // var x =
/*1*/
+ // The completion list at "1" will contain "div>" with type any
+ // And at `
/*1*/ >` (with a closing `>`), the completion list will contain "div".
+ // And at property access expressions `
` the completion will
+ // return full closing tag with an optional replacement span
+ // For example:
+ // var x = MainComponent /*1*/ >
+ // var y = /*2*/ MainComponent >
+ // the completion list at "1" and "2" will contain "MainComponent.Child" with a replacement span of closing tag name
+ const hasClosingAngleBracket = !!findChildOfKind(jsxClosingElement, SyntaxKind.GreaterThanToken, sourceFile);
+ const tagName = jsxClosingElement.parent.openingElement.tagName;
+ const closingTag = tagName.getText(sourceFile);
+ const fullClosingTag = closingTag + (hasClosingAngleBracket ? "" : ">");
+ const replacementSpan = createTextSpanFromNode(jsxClosingElement.tagName);
+
+ const entry: CompletionEntry = {
+ name: fullClosingTag,
+ kind: ScriptElementKind.classElement,
+ kindModifiers: undefined,
+ sortText: SortText.LocationPriority,
+ };
+ return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: false, optionalReplacementSpan: replacementSpan, entries: [entry] };
+ }
+ return;
+ }
+
function getJSCompletionEntries(
sourceFile: SourceFile,
position: number,
diff --git a/tests/cases/fourslash/tsxCompletion15.ts b/tests/cases/fourslash/tsxCompletion15.ts
new file mode 100644
index 00000000000..268f408ff96
--- /dev/null
+++ b/tests/cases/fourslash/tsxCompletion15.ts
@@ -0,0 +1,47 @@
+///
+//@module: commonjs
+//@jsx: preserve
+
+//// declare module JSX {
+//// interface Element { }
+//// interface IntrinsicElements {
+//// }
+//// interface ElementAttributesProperty { props; }
+//// }
+
+//@Filename: exporter.tsx
+//// export module M {
+//// export declare function SFCComp(props: { Three: number; Four: string }): JSX.Element;
+//// }
+
+//@Filename: file.tsx
+//// import * as Exp from './exporter';
+//// var x1 = [|/*1*/|]>;
+//// var x2 = [|Exp./*2*/|]>;
+//// var x3 = [|Exp.M./*3*/|]>;
+//// var x4 = [|Exp.M.SFCComp/*4*/|]
+//// var x5 = [|Exp.M.SFCComp/*5*/|]>;
+//// var x6 = [|Exp./*6*/|]>;
+//// var x7 = [|/*7*/Exp.M.SFCComp|]>;
+//// var x8 = [|Exp/*8*/|]>;
+//// var x9 = [|Exp.M./*9*/|]>;
+//// var x10 = [|/*10*/Exp.M.Foo.Bar.Baz.Wut|]>;
+//// var x11 = [|Exp./*11*/M.SFCComp|]>;
+//// var x12 =
[|Exp.M./*12*/SFCComp|]>;
+
+const ranges = test.ranges();
+
+verify.completions(
+ { marker: '1', exact: 'Exp.M.SFCComp', optionalReplacementSpan: ranges[0] },
+ { marker: '2', exact: 'Exp.M.SFCComp', optionalReplacementSpan: ranges[1] },
+ { marker: '3', exact: 'Exp.M.SFCComp', optionalReplacementSpan: ranges[2] },
+ { marker: '4', exact: 'Exp.M.SFCComp>', optionalReplacementSpan: ranges[3] },
+ { marker: '5', exact: 'Exp.M.SFCComp', optionalReplacementSpan: ranges[4] },
+ { marker: '6', exact: 'Exp.M.SFCComp', optionalReplacementSpan: ranges[5] },
+ { marker: '7', exact: 'Exp.M.SFCComp', optionalReplacementSpan: ranges[6] },
+ { marker: '8', exact: 'Exp.M.SFCComp', optionalReplacementSpan: ranges[7] },
+ { marker: '9', exact: 'Exp.M.SFCComp', optionalReplacementSpan: ranges[8] },
+ { marker: '10', exact: 'Exp.M.SFCComp', optionalReplacementSpan: ranges[9] },
+ { marker: '11', exact: 'Exp.M.SFCComp', optionalReplacementSpan: ranges[10] },
+ { marker: '12', exact: 'Exp.M.SFCComp', optionalReplacementSpan: ranges[11] },
+);