Skip to content

Commit

Permalink
fix: support cc and diagnostics for absolute path in meta path (#710)
Browse files Browse the repository at this point in the history
* fix: support cc for absolute path in meta path

* fix: validation - draft 02

* fix: add diagnostic support for absolute path

* fix: adapt test case and adress sonar cloud issue

* fix: add change set

* fix: diagnostic message

* fix: readme update

---------

Co-authored-by: vadson71 <vadims.koblovs@sap.com>
  • Loading branch information
marufrasully and vadson71 committed Apr 30, 2024
1 parent 1dfdebf commit fddc24b
Show file tree
Hide file tree
Showing 21 changed files with 1,369 additions and 730 deletions.
7 changes: 7 additions & 0 deletions .changeset/eight-pots-protect.md
@@ -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
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
@@ -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
);
}

0 comments on commit fddc24b

Please sign in to comment.