Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: support cc and diagnostics for absolute path in meta path #710

Merged
merged 9 commits into from
Apr 30, 2024
7 changes: 7 additions & 0 deletions .changeset/eight-pots-protect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@ui5-language-assistant/vscode-ui5-language-assistant-bas-ext": patch
"vscode-ui5-language-assistant": patch
"@ui5-language-assistant/fe": patch
---

feat: add absolute path support for meta path
2 changes: 1 addition & 1 deletion packages/fe/src/i18n/i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"UNKNOWN_PATH": "Unknown path: \"{{value}}\"",
"INVALID_PROPERTY_PATH_MULTIPLE_1_TO_MANY": "Invalid property path value. Multiple 1:many association segments not allowed",

"ENTITY_SET_OR_CONTEXT_PATH_IS_MISSING_IN_MANIFEST": "EntitySet or contextPath for the current view are not defined in application manifest. Attribute value completion and diagnostics is not possible if EntitySet or contextPath are not defined or defined dynamically in controllers",
"ENTITY_SET_OR_CONTEXT_PATH_IS_MISSING_IN_MANIFEST": "Path cannot be identified: use absolute path or define contextPath",
"EMPTY_CONTEXT_PATH_IN_MANIFEST": "ContextPath in manifest is empty. Attribute value completion and diagnostics are disabled",
"RELATIVE_CONTEXT_PATH_IN_MANIFEST": "ContextPath in manifest \"{{value}}\" must be absolute. Attribute value completion and diagnostics are disabled",
"UNKNOWN_CONTEXT_PATH_IN_MANIFEST": "Unknown contextPath in manifest \"{{value}}\". Attribute value completion and diagnostics are disabled",
Expand Down
236 changes: 75 additions & 161 deletions packages/fe/src/services/completion/providers/context-path.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,17 @@
import { getUI5PropertyByXMLAttributeKey } from "@ui5-language-assistant/logic-utils";
import {
getPathConstraintsForControl,
getNextPossibleContextPathTargets,
getRootElements,
resolvePathTarget,
} from "../../../utils";
import { getPathConstraintsForControl } from "../../../utils";
import {
AnnotationTargetInXMLAttributeValueCompletion,
SAP_FE_MACROS,
} from "../../../types";

import { UI5AttributeValueCompletionOptions } from "./index";
import { CompletionSuggestion } from "../../../types/completion";
import {
EntityContainer,
EntitySet,
EntityType,
NavigationProperty,
Singleton,
} from "@sap-ux/vocabularies-types";
import { getAffectedRange } from "../utils";
import { AnnotationTargetInXMLAttributeValueTypeName } from "../../../types/completion";

type ApplicableMetadataElement =
| EntityContainer
| EntitySet
| EntityType
| Singleton
| NavigationProperty;

interface CompletionSuggestion {
element: ApplicableMetadataElement;
isLastSegment: boolean;
}
getNavigationSuggestion,
getRootElementSuggestions,
suggestionToTargetCompletion,
} from "./utils";

/**
* Suggests values for macros contextPath
Expand All @@ -46,150 +26,84 @@ export function contextPathSuggestions({
attribute,
context.ui5Model
);
if (!ui5Property) {
return [];
}
const metaPathStartsWithAbsolutePath = element.attributes.find(
(i) => i.key === "metaPath" && i.value?.startsWith("/")
);
// no CC for contextPath if metaPath starts with absolute path
if (metaPathStartsWithAbsolutePath) {
return [];
}

if (
ui5Property?.library === SAP_FE_MACROS &&
ui5Property.parent?.name === "Chart" &&
ui5Property.name === "contextPath"
!["contextPath"].includes(ui5Property.name) ||
ui5Property?.library !== SAP_FE_MACROS ||
ui5Property.parent?.name !== "Chart"
) {
const mainServicePath = context.manifestDetails.mainServicePath;
const service = mainServicePath
? context.services[mainServicePath]
: undefined;
if (!service) {
return [];
}
const metadata = service.convertedMetadata;
const { expectedAnnotations, expectedTypes } = getPathConstraintsForControl(
element.name,
ui5Property
);
const isPropertyPath = expectedTypes.includes("Property");
const suggestions: CompletionSuggestion[] = [];
const segments = (attribute.value || "").split("/");
const precedingSegments = (prefix || "").split("/");
const completionSegmentIndex = precedingSegments.length - 1;
precedingSegments.pop();
const completionSegmentOffset =
precedingSegments.join("/").length + (precedingSegments.length ? 1 : 0);
const isAbsolutePath = segments.length > 1 && !segments[0];
if (!isAbsolutePath && completionSegmentIndex > 0) {
// relative paths are not supported
return [];
}
if (expectedAnnotations.length + expectedTypes.length === 0) {
return [];
}
return [];
}

const isNextSegmentPossible = (
currentTarget: EntitySet | EntityType | Singleton | EntityContainer,
milestones: string[] = []
): boolean => {
return (
getNextPossibleContextPathTargets(
service.convertedMetadata,
currentTarget,
{
allowedTerms: expectedAnnotations,
allowedTargets: expectedTypes,
isPropertyPath,
},
[...milestones, currentTarget.fullyQualifiedName]
).length > 0
);
};
const mainServicePath = context.manifestDetails.mainServicePath;
const service = mainServicePath
? context.services[mainServicePath]
: undefined;
if (!service) {
return [];
}
const metadata = service.convertedMetadata;
const { expectedAnnotations, expectedTypes } = getPathConstraintsForControl(
element.name,
ui5Property
);
if (expectedAnnotations.length + expectedTypes.length === 0) {
return [];
}
const isPropertyPath = expectedTypes.includes("Property");
const suggestions: CompletionSuggestion[] = [];
const segments = (attribute.value || "").split("/");
const precedingSegments = (prefix || "").split("/");
const completionSegmentIndex = precedingSegments.length - 1;
precedingSegments.pop();
const completionSegmentOffset =
precedingSegments.join("/").length + (precedingSegments.length ? 1 : 0);
const isAbsolutePath = segments.length > 1 && !segments[0];
if (!isAbsolutePath && completionSegmentIndex > 0) {
// relative paths are not supported
return [];
}

if (completionSegmentIndex < 2) {
// completion for root element
const roots = getRootElements(
if (completionSegmentIndex < 2) {
// completion for root element
suggestions.push(
...getRootElementSuggestions(
metadata,
expectedAnnotations,
expectedTypes,
isPropertyPath
);
suggestions.push(
...roots.map((root) => ({
element: root,
isLastSegment: !isNextSegmentPossible(root),
}))
);
} else {
// completion for navigation property segment
const precedingPath = segments.slice(0, completionSegmentIndex).join("/");
const { target, isCollection, milestones } = resolvePathTarget(
service.convertedMetadata,
precedingPath
);
if (!target) {
// target not resolved or path leads to collection - no further segments possible
return [];
} else if (target._type === "Property") {
// no further segments possible after entity property, container is not supported
return [];
} else {
const possibleTargets = getNextPossibleContextPathTargets(
service.convertedMetadata,
target,
{
allowedTerms: expectedAnnotations,
allowedTargets: expectedTypes,
isPropertyPath,
isCollection: isCollection ? false : undefined,
},
milestones
);
suggestions.push(
...possibleTargets.map((t) => {
const entityType =
t._type === "NavigationProperty" ? t.targetType : t.entityType;
return {
element: t,
isLastSegment: !isNextSegmentPossible(entityType, milestones),
};
})
);
}
}

const sortMap: Record<string, string> = {
EntityContainer: "Z",
EntityType: "A",
EntitySet: "B",
Singleton: "C",
NavigationProperty: "N",
};

const getSuggestionText = (suggestion: CompletionSuggestion): string => {
const isFullyQualifiedName = [
"EntityContainer",
"EntitySet",
"Singleton",
].includes(suggestion.element._type);
return `${completionSegmentIndex === 0 ? "/" : ""}${
isFullyQualifiedName && completionSegmentIndex < 2
? suggestion.element.fullyQualifiedName
: suggestion.element.name
}`;
)
);
} else {
// completion for navigation property segment
const precedingPath = segments.slice(0, completionSegmentIndex).join("/");
const options = {
allowedTerms: expectedAnnotations,
allowedTargets: expectedTypes,
isPropertyPath,
};

return suggestions.map((suggestion) => {
const text = getSuggestionText(suggestion);
return {
type: AnnotationTargetInXMLAttributeValueTypeName,
node: {
kind: suggestion.element._type,
name: text,
text,
affectedRange: getAffectedRange(
attribute.syntax.value,
completionSegmentOffset
),
commitCharacters: suggestion.isLastSegment ? [] : ["/"],
commitCharactersRequired: true,
sortText: sortMap[suggestion.element._type] + text,
},
};
});
suggestions.push(
...getNavigationSuggestion(
service.convertedMetadata,
precedingPath,
options
)
);
}
return [];
return suggestionToTargetCompletion(
attribute,
suggestions,
completionSegmentIndex,
completionSegmentOffset
);
}
Loading
Loading