Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions packages/language-server/src/completion-items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
} from "@ui5-language-assistant/xml-views-completion";
import { ui5NodeToFQN } from "@ui5-language-assistant/logic-utils";
import { getNodeDocumentation, getNodeDetail } from "./documentation";
import { assertNever } from "assert-never";

export function getCompletionItems(
model: UI5SemanticModel,
Expand Down Expand Up @@ -103,8 +104,7 @@ export function computeLSPKind(
case "BooleanValueInXMLAttributeValue":
return CompletionItemKind.Constant;
default:
// TODO: we probably need a logging solution to highlight edge cases we
// do not handle...
assertNever(suggestion, true);
return CompletionItemKind.Text;
}
}
Expand Down Expand Up @@ -179,11 +179,20 @@ function createTextEdits(
// Tag name
case "UI5AggregationsInXMLTagName": {
range = getXMLTagNameRange(suggestion.astNode) ?? range;
const tagName = suggestion.ui5Node.name;
let parentNS: string | undefined = undefined;
/* istanbul ignore else - defensive programming (aggregation suggestions wil not be returned in the root tag) */
if (suggestion.astNode.parent.type === "XMLElement") {
parentNS = suggestion.astNode.parent.ns;
if (parentNS !== undefined && parentNS !== "") {
parentNS += ":";
}
}
const tagName = `${parentNS ?? ""}${suggestion.ui5Node.name}`;
filterText = `${parentNS ?? ""}${suggestion.ui5Node.name}`;
// Auto-close tag
/* istanbul ignore else */
if (shouldCloseXMLElement(suggestion.astNode)) {
newText += `>\${0}</${tagName}>`;
newText = `${tagName}>\${0}</${tagName}>`;
} else {
additionalTextEdits.push(
...getClosingTagTextEdits(suggestion.astNode, tagName)
Expand Down
30 changes: 15 additions & 15 deletions packages/language-server/test/completion-items-classes-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,9 @@ describe("the UI5 language assistant Code Completion Services - classes", () =>
it("will not insert the namespace when selecting completion for class in inner tag and namespace is already defined", () => {
assertClassesCompletions({
xmlSnippet: `<mvc:View xmlns:mvc="sap.ui.core.mvc" xmlns:m="sap.m">
<content>
<mvc:content>
<sap.m.MenuButto⇶n
</content>
</mvc:content>
</m:View>`,
expected: [
{
Expand All @@ -282,9 +282,9 @@ describe("the UI5 language assistant Code Completion Services - classes", () =>
it("will insert the namespace when selecting completion for class in inner tag and namespace is not defined", () => {
assertClassesCompletions({
xmlSnippet: `<m:View⭲⭰ xmlns:m="sap.ui.core.mvc">
<content>
<m:content>
<MenuButton⇶
</content>
</m:content>
</m:View>`,
expected: [
{
Expand Down Expand Up @@ -383,7 +383,7 @@ describe("the UI5 language assistant Code Completion Services - classes", () =>
xmlSnippet: `<mvc:View
xmlns:mvc="sap.ui.core.mvc"
xmlns:unified="sap.ui.unified">
<content> <ContentS⇶`,
<mvc:content> <ContentS⇶`,
expected: [
{
label: "ContentSwitcher",
Expand All @@ -401,9 +401,9 @@ describe("the UI5 language assistant Code Completion Services - classes", () =>
it("will replace the class closing tag name when the tag is closed and has the same name as the opening tag", () => {
assertClassesCompletions({
xmlSnippet: `<mvc:View xmlns:mvc="sap.ui.core.mvc" xmlns:m="sap.m" xmlns:commons="sap.ui.commons">
<content>
<mvc:content>
<MenuButton⇶></⭲MenuButton⭰>
</content>
</mvc:content>
</m:View>`,
expected: [
{
Expand Down Expand Up @@ -435,9 +435,9 @@ describe("the UI5 language assistant Code Completion Services - classes", () =>
it("will not replace the class closing tag name when the tag is closed and has a different name from the opening tag", () => {
assertClassesCompletions({
xmlSnippet: `<mvc:View xmlns:mvc="sap.ui.core.mvc" xmlns:m="sap.m" xmlns:commons="sap.ui.commons">
<content>
<mvc:content>
<MenuButton⇶></MenuButton1>
</content>
</mvc:content>
</m:View>`,
expected: [
{
Expand All @@ -459,9 +459,9 @@ describe("the UI5 language assistant Code Completion Services - classes", () =>
it("will not replace the class closing tag name when the tag is closed and the opening tag doesn't have a name", () => {
assertClassesCompletions({
xmlSnippet: `<mvc:View xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc">
<customData>
<mvc:customData>
<⇶></MenuButton1>
</customData>
</mvc:customData>
</mvc:View>`,
expected: [
{
Expand All @@ -477,9 +477,9 @@ describe("the UI5 language assistant Code Completion Services - classes", () =>
it("will replace the class closing tag name when the tag is closed and does not have a name", () => {
assertClassesCompletions({
xmlSnippet: `<mvc:View xmlns:mvc="sap.ui.core.mvc" xmlns:m="sap.m" xmlns:commons="sap.ui.commons">
<content>
<mvc:content>
<MenuButton⇶>⭲</>⭰
</content>
</mvc:content>
</m:View>`,
expected: [
{
Expand Down Expand Up @@ -511,9 +511,9 @@ describe("the UI5 language assistant Code Completion Services - classes", () =>
it("will replace the class closing tag name when also inserting the namespace", () => {
assertClassesCompletions({
xmlSnippet: `<mvc:View⭲⭰ xmlns:mvc="sap.ui.core.mvc">
<content>
<mvc:content>
<MenuButton⇶></⭲MenuButton⭰>
</content>
</mvc:content>
</m:View>`,
expected: [
{
Expand Down
134 changes: 126 additions & 8 deletions packages/language-server/test/completion-items-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,30 +224,55 @@ describe("the UI5 language assistant Code Completion Services", () => {
expect(suggestionKinds).to.deep.equal([CompletionItemKind.Reference]);
});

it("will get completion values for UI5 aggregation", () => {
it("will get completion values for UI5 aggregation in the default namespace", () => {
const xmlSnippet = `<mvc:View
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m">
<List> <te⇶`;
const suggestions = getSuggestions(xmlSnippet, ui5SemanticModel);
const suggestionsDetails = map(suggestions, (suggestion) => ({
label: suggestion.label,
tagName: getTagName(suggestion.textEdit),
replacedText: getTextInRange(xmlSnippet, suggestion.textEdit?.range),
}));
const suggestionKinds = uniq(
map(suggestions, (suggestion) => suggestion.kind)
);

expect(suggestionsDetails).to.deep.equalInAnyOrder([
{ label: "contextMenu", tagName: "contextMenu", replacedText: "te" },
{ label: "items", tagName: "items", replacedText: "te" },
{ label: "swipeContent", tagName: "swipeContent", replacedText: "te" },
]);

expect(suggestionKinds).to.deep.equal([CompletionItemKind.Field]);
});

it("will get completion values for UI5 aggregation in a non-default namespace", () => {
const xmlSnippet = `<mvc:View
xmlns:mvc="sap.ui.core.mvc"
xmlns:m="sap.m">
<m:List> <te⇶`;
const suggestions = getSuggestions(xmlSnippet, ui5SemanticModel);
const suggestionsDetails = map(suggestions, (suggestion) => ({
label: suggestion.label,
tagName: getTagName(suggestion.textEdit),
replacedText: getTextInRange(xmlSnippet, suggestion.textEdit?.range),
}));
const suggestionKinds = uniq(
map(suggestions, (suggestion) => suggestion.kind)
);

expect(suggestionsDetails).to.deep.equalInAnyOrder([
{ label: "contextMenu", replacedText: "te" },
{ label: "items", replacedText: "te" },
{ label: "swipeContent", replacedText: "te" },
{ label: "contextMenu", tagName: "m:contextMenu", replacedText: "te" },
{ label: "items", tagName: "m:items", replacedText: "te" },
{ label: "swipeContent", tagName: "m:swipeContent", replacedText: "te" },
]);

expect(suggestionKinds).to.deep.equal([CompletionItemKind.Field]);
});

it("will get completion values for UI5 aggregation when the cursor is in the middle of a name", () => {
it("will get completion values for UI5 aggregation when the cursor is in the middle of a name in the default namespace", () => {
const xmlSnippet = `<mvc:View
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m">
Expand Down Expand Up @@ -275,7 +300,39 @@ describe("the UI5 language assistant Code Completion Services", () => {
expect(suggestionKinds).to.deep.equal([CompletionItemKind.Field]);
});

it("will replace the aggregation closing tag name when the tag is closed and has the same name as the opening tag", () => {
it("will get completion values for UI5 aggregation when the cursor is in the middle of a name in a non-default namespace", () => {
const xmlSnippet = `<mvc:View
xmlns:mvc="sap.ui.core.mvc"
xmlns:m="sap.m">
<m:List> <te⇶Menu`;
const suggestions = getSuggestions(xmlSnippet, ui5SemanticModel);
const suggestionsDetails = map(suggestions, (suggestion) => ({
label: suggestion.label,
replacedText: getTextInRange(xmlSnippet, suggestion.textEdit?.range),
tagName: getTagName(suggestion.textEdit),
}));
const suggestionKinds = uniq(
map(suggestions, (suggestion) => suggestion.kind)
);

expect(suggestionsDetails).to.deep.equalInAnyOrder([
{
label: "contextMenu",
replacedText: "teMenu",
tagName: "m:contextMenu",
},
{ label: "items", replacedText: "teMenu", tagName: "m:items" },
{
label: "swipeContent",
replacedText: "teMenu",
tagName: "m:swipeContent",
},
]);

expect(suggestionKinds).to.deep.equal([CompletionItemKind.Field]);
});

it("will replace the aggregation closing tag name when the tag is closed and has the same name as the opening tag in the default namespace", () => {
const xmlSnippet = `<mvc:View
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m">
Expand Down Expand Up @@ -336,7 +393,68 @@ describe("the UI5 language assistant Code Completion Services", () => {
expect(suggestionKinds).to.deep.equal([CompletionItemKind.Field]);
});

it("will not replace the class closing tag name when the tag is closed and has a different name from the opening tag", () => {
it("will replace the aggregation closing tag name when the tag is closed and has the same name as the opening tag in a non-default namespace", () => {
const xmlSnippet = `<mvc:View
xmlns:mvc="sap.ui.core.mvc"
xmlns:m="sap.m">
<m:List>
<m:te⇶></⭲m:te⭰>
</m:List>
</mvc:View>`;
const suggestions = getSuggestions(xmlSnippet, ui5SemanticModel);
const suggestionsDetails = map(suggestions, (suggestion) => ({
label: suggestion.label,
tagName: getTagName(suggestion.textEdit),
additionalTextEdits: suggestion.additionalTextEdits,
replacedText: getTextInRange(xmlSnippet, suggestion.textEdit?.range),
}));
const suggestionKinds = uniq(
map(suggestions, (suggestion) => suggestion.kind)
);

const ranges = getRanges(xmlSnippet);
expect(ranges, "additional text edits ranges").to.have.lengthOf(1);

expect(suggestionsDetails).to.deep.equalInAnyOrder([
{
label: "contextMenu",
tagName: "contextMenu",
additionalTextEdits: [
{
range: ranges[0],
newText: `m:contextMenu`,
},
],
replacedText: "m:te",
},
{
label: "items",
tagName: "items",
additionalTextEdits: [
{
range: ranges[0],
newText: `m:items`,
},
],
replacedText: "m:te",
},
{
label: "swipeContent",
tagName: "swipeContent",
additionalTextEdits: [
{
range: ranges[0],
newText: `m:swipeContent`,
},
],
replacedText: "m:te",
},
]);

expect(suggestionKinds).to.deep.equal([CompletionItemKind.Field]);
});

it("will not replace the aggregation closing tag name when the tag is closed and has a different name from the opening tag", () => {
const xmlSnippet = `<mvc:View
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m">
Expand Down Expand Up @@ -382,7 +500,7 @@ describe("the UI5 language assistant Code Completion Services", () => {
expect(suggestionKinds).to.deep.equal([CompletionItemKind.Field]);
});

it("will replace the class closing tag name when the tag is closed and does not have a name", () => {
it("will replace the aggregation closing tag name when the tag is closed and does not have a name", () => {
const xmlSnippet = `<mvc:View
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m">
Expand Down
Loading