mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-30 01:01:48 -05:00
Provide completion for partial expression on closing jsx tags (#42029)
* Provide completion for partial expression on closing jsx tags * Add more cases and cover opening tag is parent parent of location * Fix code style indentation * Add some more notes * Guarding null pointer * Guarding null pointer 2 * PR reviews & adjustments 1 * Fix typos * Better namings * Remove failing test-case * PR reviews & adjustments 2 - new approach * More comments * More comments 2 * PR reviews & adjustments 3 * Revert previous test-case file changes * Write explicit completions from ranges * PR reviews & adjustments 4 - adding exact entry * Add another missing test-case * Find jsx closing element by findAncestor * Walk up till find jsx closing element * Add one more test-case * PR reviews & adjustments 4 - Pattern matching to get jsx closing element * Minor change * Linting fixes
This commit is contained in:
@@ -232,22 +232,12 @@ namespace ts.Completions {
|
||||
symbolToSortTextMap,
|
||||
} = completionData;
|
||||
|
||||
if (location && location.parent && isJsxClosingElement(location.parent)) {
|
||||
// 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 = <div> </ /*1*/
|
||||
// The completion list at "1" will contain "div>" with type any
|
||||
// And at `<div> </ /*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 = <div> </ /*1*/
|
||||
// The completion list at "1" will contain "div>" with type any
|
||||
// And at `<div> </ /*1*/ >` (with a closing `>`), the completion list will contain "div".
|
||||
// And at property access expressions `<MainComponent.Child> </MainComponent. /*1*/ >` the completion will
|
||||
// return full closing tag with an optional replacement span
|
||||
// For example:
|
||||
// var x = <MainComponent.Child> </ MainComponent /*1*/ >
|
||||
// var y = <MainComponent.Child> </ /*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,
|
||||
|
||||
Reference in New Issue
Block a user