@sap-ux/annotation-converter
Advanced tools
Comparing version 0.6.7 to 0.6.8
# @sap-ux/annotation-converter | ||
## 0.6.8 | ||
### Patch Changes | ||
- 6c325c5: We have improved the handling of aliases | ||
## 0.6.7 | ||
@@ -4,0 +10,0 @@ |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.convert = void 0; | ||
const VocabularyReferences_1 = require("@sap-ux/vocabularies-types/vocabularies/VocabularyReferences"); | ||
const utils_1 = require("./utils"); | ||
const VocabularyReferences_1 = require("@sap-ux/vocabularies-types/vocabularies/VocabularyReferences"); | ||
/** | ||
@@ -125,3 +125,3 @@ * Symbol to extend an annotation with the reference to its target. | ||
const thisElement = current.target; | ||
if (segment === '' || segment === thisElement.fullyQualifiedName) { | ||
if (segment === '' || converter.unalias(segment) === thisElement.fullyQualifiedName) { | ||
return current; | ||
@@ -175,3 +175,4 @@ } | ||
} | ||
const action = thisElement.actions[segment]; | ||
const actionName = (0, utils_1.substringBeforeFirst)(converter.unalias(segment), '('); | ||
const action = thisElement.actions[actionName]; | ||
if (action) { | ||
@@ -428,44 +429,26 @@ current.target = action; | ||
function parseRecord(converter, currentTerm, currentTarget, currentProperty, currentSource, annotationRecord, currentFQN) { | ||
var _a; | ||
const annotationTerm = { | ||
const record = { | ||
$Type: parseRecordType(converter, currentTerm, currentTarget, currentProperty, annotationRecord), | ||
fullyQualifiedName: currentFQN, | ||
[ANNOTATION_TARGET]: currentTarget | ||
[ANNOTATION_TARGET]: currentTarget, | ||
__source: currentSource | ||
}; | ||
for (const propertyValue of annotationRecord.propertyValues) { | ||
(0, utils_1.lazy)(record, propertyValue.name, () => parseValue(converter, currentTarget, currentTerm, propertyValue.name, currentSource, propertyValue.value, `${currentFQN}/${propertyValue.name}`)); | ||
} | ||
// annotations on the record | ||
(0, utils_1.lazy)(annotationTerm, 'annotations', () => { | ||
var _a; | ||
// be graceful when resolving annotations on annotations: Sometimes they are referenced directly, sometimes they | ||
// are part of the global annotations list | ||
let annotations; | ||
if (annotationRecord.annotations && annotationRecord.annotations.length > 0) { | ||
annotations = annotationRecord.annotations; | ||
} | ||
else { | ||
annotations = (_a = converter.rawAnnotationsPerTarget[currentFQN]) === null || _a === void 0 ? void 0 : _a.annotations; | ||
} | ||
annotations === null || annotations === void 0 ? void 0 : annotations.forEach((annotation) => { | ||
annotation.target = currentFQN; | ||
annotation.__source = currentSource; | ||
annotation[ANNOTATION_TARGET] = currentTarget; | ||
annotation.fullyQualifiedName = `${currentFQN}@${annotation.term}`; | ||
}); | ||
return createAnnotationsObject(converter, annotationTerm, annotations !== null && annotations !== void 0 ? annotations : []); | ||
}); | ||
const annotationContent = (_a = annotationRecord.propertyValues) === null || _a === void 0 ? void 0 : _a.reduce((annotationContent, propertyValue) => { | ||
(0, utils_1.lazy)(annotationContent, propertyValue.name, () => parseValue(converter, currentTarget, currentTerm, propertyValue.name, currentSource, propertyValue.value, `${currentFQN}/${propertyValue.name}`)); | ||
return annotationContent; | ||
}, annotationTerm); | ||
if (isDataFieldWithForAction(annotationContent)) { | ||
(0, utils_1.lazy)(annotationContent, 'ActionTarget', () => { | ||
var _a, _b; | ||
// try to resolve to a bound action of the annotation target | ||
let actionTarget = (_a = currentTarget.actions) === null || _a === void 0 ? void 0 : _a[annotationContent.Action]; | ||
(0, utils_1.lazy)(record, 'annotations', resolveAnnotationsOnAnnotation(converter, annotationRecord, record)); | ||
if (isDataFieldWithForAction(record)) { | ||
(0, utils_1.lazy)(record, 'ActionTarget', () => { | ||
var _a, _b, _c; | ||
const actionTargetFQN = converter.unalias((_a = record.Action) === null || _a === void 0 ? void 0 : _a.toString()); | ||
// (1) Bound action of the annotation target? | ||
let actionTarget = (_b = currentTarget.actions) === null || _b === void 0 ? void 0 : _b[actionTargetFQN]; | ||
if (!actionTarget) { | ||
// try to find a corresponding unbound action | ||
actionTarget = (_b = converter.getConvertedActionImport(annotationContent.Action)) === null || _b === void 0 ? void 0 : _b.action; | ||
// (2) ActionImport (= unbound action)? | ||
actionTarget = (_c = converter.getConvertedActionImport(actionTargetFQN)) === null || _c === void 0 ? void 0 : _c.action; | ||
} | ||
if (!actionTarget) { | ||
// try to find a corresponding bound (!) action | ||
actionTarget = converter.getConvertedAction(annotationContent.Action); | ||
// (3) Bound action of a different EntityType | ||
actionTarget = converter.getConvertedAction(actionTargetFQN); | ||
if (!(actionTarget === null || actionTarget === void 0 ? void 0 : actionTarget.isBound)) { | ||
@@ -476,3 +459,3 @@ actionTarget = undefined; | ||
if (!actionTarget) { | ||
converter.logError(`Unable to resolve the action '${annotationContent.Action}' defined for '${annotationTerm.fullyQualifiedName}'`); | ||
converter.logError(`${record.fullyQualifiedName}: Unable to resolve '${record.Action}' ('${actionTargetFQN}')`); | ||
} | ||
@@ -482,3 +465,3 @@ return actionTarget; | ||
} | ||
return annotationContent; | ||
return record; | ||
} | ||
@@ -643,26 +626,7 @@ /** | ||
const [vocAlias, vocTerm] = converter.splitTerm(rawAnnotation.term); | ||
annotation.term = converter.unalias(`${vocAlias}.${vocTerm}`); | ||
annotation.term = converter.unalias(`${vocAlias}.${vocTerm}`, VocabularyReferences_1.VocabularyReferences); | ||
annotation.qualifier = rawAnnotation.qualifier; | ||
annotation.__source = rawAnnotation.__source; | ||
try { | ||
(0, utils_1.lazy)(annotation, 'annotations', () => { | ||
var _a; | ||
const annotationFQN = annotation.fullyQualifiedName; | ||
// be graceful when resolving annotations on annotations: Sometimes they are referenced directly, sometimes they | ||
// are part of the global annotations list | ||
let annotations; | ||
if (rawAnnotation.annotations && rawAnnotation.annotations.length > 0) { | ||
annotations = rawAnnotation.annotations; | ||
} | ||
else { | ||
annotations = (_a = converter.rawAnnotationsPerTarget[annotationFQN]) === null || _a === void 0 ? void 0 : _a.annotations; | ||
} | ||
annotations === null || annotations === void 0 ? void 0 : annotations.forEach((rawSubAnnotation) => { | ||
rawSubAnnotation.target = annotationFQN; | ||
rawSubAnnotation.__source = annotation.__source; | ||
rawSubAnnotation[ANNOTATION_TARGET] = target; | ||
rawSubAnnotation.fullyQualifiedName = `${annotationFQN}@${rawSubAnnotation.term}`; | ||
}); | ||
return createAnnotationsObject(converter, annotation, annotations !== null && annotations !== void 0 ? annotations : []); | ||
}); | ||
(0, utils_1.lazy)(annotation, 'annotations', resolveAnnotationsOnAnnotation(converter, rawAnnotation, annotation)); | ||
} | ||
@@ -674,60 +638,45 @@ catch (e) { | ||
} | ||
function getAnnotationFQN(currentTargetName, references, annotation) { | ||
const annotationFQN = `${currentTargetName}@${(0, utils_1.unalias)(references, annotation.term)}`; | ||
if (annotation.qualifier) { | ||
return `${annotationFQN}#${annotation.qualifier}`; | ||
} | ||
else { | ||
return annotationFQN; | ||
} | ||
} | ||
/** | ||
* Merge annotation from different source together by overwriting at the term level. | ||
* | ||
* @param rawMetadata | ||
* @param converter | ||
* @returns the resulting merged annotations | ||
*/ | ||
function mergeAnnotations(rawMetadata) { | ||
const annotationListPerTarget = {}; | ||
Object.keys(rawMetadata.schema.annotations).forEach((annotationSource) => { | ||
rawMetadata.schema.annotations[annotationSource].forEach((annotationList) => { | ||
const currentTargetName = (0, utils_1.unalias)(rawMetadata.references, annotationList.target); | ||
annotationList.__source = annotationSource; | ||
if (!annotationListPerTarget[currentTargetName]) { | ||
annotationListPerTarget[currentTargetName] = { | ||
annotations: annotationList.annotations.map((annotation) => { | ||
annotation.fullyQualifiedName = getAnnotationFQN(currentTargetName, rawMetadata.references, annotation); | ||
annotation.__source = annotationSource; | ||
return annotation; | ||
}), | ||
target: currentTargetName | ||
}; | ||
annotationListPerTarget[currentTargetName].__source = annotationSource; | ||
function mergeAnnotations(converter) { | ||
return Object.keys(converter.rawSchema.annotations).reduceRight((annotationsPerTarget, annotationSource) => { | ||
for (const { target: rawTarget, annotations: rawAnnotations } of converter.rawSchema.annotations[annotationSource]) { | ||
const target = converter.unalias(rawTarget); | ||
if (!annotationsPerTarget[target]) { | ||
annotationsPerTarget[target] = []; | ||
} | ||
else { | ||
annotationList.annotations.forEach((annotation) => { | ||
const findIndex = annotationListPerTarget[currentTargetName].annotations.findIndex((referenceAnnotation) => { | ||
return (referenceAnnotation.term === annotation.term && | ||
referenceAnnotation.qualifier === annotation.qualifier); | ||
}); | ||
annotation.__source = annotationSource; | ||
annotation.fullyQualifiedName = getAnnotationFQN(currentTargetName, rawMetadata.references, annotation); | ||
if (findIndex !== -1) { | ||
annotationListPerTarget[currentTargetName].annotations.splice(findIndex, 1, annotation); | ||
} | ||
else { | ||
annotationListPerTarget[currentTargetName].annotations.push(annotation); | ||
} | ||
}); | ||
} | ||
}); | ||
}); | ||
return annotationListPerTarget; | ||
annotationsPerTarget[target].push(...rawAnnotations | ||
.filter((rawAnnotation) => !annotationsPerTarget[target].some((existingAnnotation) => existingAnnotation.term === rawAnnotation.term && | ||
existingAnnotation.qualifier === rawAnnotation.qualifier)) | ||
.map((rawAnnotation) => { | ||
let annotationFQN = `${target}@${converter.unalias(rawAnnotation.term)}`; | ||
if (rawAnnotation.qualifier) { | ||
annotationFQN = `${annotationFQN}#${rawAnnotation.qualifier}`; | ||
} | ||
const annotation = rawAnnotation; | ||
annotation.fullyQualifiedName = annotationFQN; | ||
annotation.__source = annotationSource; | ||
return annotation; | ||
})); | ||
} | ||
return annotationsPerTarget; | ||
}, {}); | ||
} | ||
class Converter { | ||
get rawAnnotationsPerTarget() { | ||
if (this._rawAnnotationsPerTarget === undefined) { | ||
this._rawAnnotationsPerTarget = mergeAnnotations(this.rawMetadata); | ||
/** | ||
* Get preprocessed annotations on the specified target. | ||
* | ||
* @param target The annotation target | ||
* @returns An array of annotations | ||
*/ | ||
getAnnotations(target) { | ||
var _a; | ||
if (this.annotationyByTarget === undefined) { | ||
this.annotationyByTarget = mergeAnnotations(this); | ||
} | ||
return this._rawAnnotationsPerTarget; | ||
return (_a = this.annotationyByTarget[target]) !== null && _a !== void 0 ? _a : []; | ||
} | ||
@@ -816,5 +765,5 @@ getConvertedEntityContainer() { | ||
} | ||
unalias(value) { | ||
unalias(value, references = this.rawMetadata.references) { | ||
var _a; | ||
return (_a = (0, utils_1.unalias)(this.rawMetadata.references, value)) !== null && _a !== void 0 ? _a : ''; | ||
return (_a = (0, utils_1.unalias)(references, value)) !== null && _a !== void 0 ? _a : ''; | ||
} | ||
@@ -854,5 +803,23 @@ } | ||
const nestedAnnotations = rawAnnotationTarget.annotations; | ||
return () => createAnnotationsObject(converter, rawAnnotationTarget, nestedAnnotations !== null && nestedAnnotations !== void 0 ? nestedAnnotations : converter.getAnnotations(rawAnnotationTarget.fullyQualifiedName)); | ||
} | ||
function resolveAnnotationsOnAnnotation(converter, annotationRecord, annotationTerm) { | ||
return () => { | ||
var _a, _b; | ||
return createAnnotationsObject(converter, rawAnnotationTarget, (_b = nestedAnnotations !== null && nestedAnnotations !== void 0 ? nestedAnnotations : (_a = converter.rawAnnotationsPerTarget[rawAnnotationTarget.fullyQualifiedName]) === null || _a === void 0 ? void 0 : _a.annotations) !== null && _b !== void 0 ? _b : []); | ||
const currentFQN = annotationTerm.fullyQualifiedName; | ||
// be graceful when resolving annotations on annotations: Sometimes they are referenced directly, sometimes they | ||
// are part of the global annotations list | ||
let annotations; | ||
if (annotationRecord.annotations && annotationRecord.annotations.length > 0) { | ||
annotations = annotationRecord.annotations; | ||
} | ||
else { | ||
annotations = converter.getAnnotations(currentFQN); | ||
} | ||
annotations === null || annotations === void 0 ? void 0 : annotations.forEach((annotation) => { | ||
annotation.target = currentFQN; | ||
annotation.__source = annotationTerm.__source; | ||
annotation[ANNOTATION_TARGET] = annotationTerm[ANNOTATION_TARGET]; | ||
annotation.fullyQualifiedName = `${currentFQN}@${annotation.term}`; | ||
}); | ||
return createAnnotationsObject(converter, annotationTerm, annotations !== null && annotations !== void 0 ? annotations : []); | ||
}; | ||
@@ -1032,20 +999,13 @@ } | ||
(0, utils_1.lazy)(convertedAction, 'annotations', () => { | ||
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l; | ||
// this.is.the.action(on.this.type) --> action: 'this.is.the.action', overload: 'on.this.type' | ||
// this.is.the.action() --> action: 'this.is.the.action', overload: undefined | ||
// this.is.the.action --> action: 'this.is.the.action', overload: undefined | ||
const actionAndOverload = rawAction.fullyQualifiedName.match(/(?<action>[^()]+)(?:\((?<overload>.*)\))?/); | ||
let rawAnnotations = []; | ||
if (actionAndOverload) { | ||
if ((_a = actionAndOverload.groups) === null || _a === void 0 ? void 0 : _a.overload) { | ||
rawAnnotations = (_c = (_b = converter.rawAnnotationsPerTarget[rawAction.fullyQualifiedName]) === null || _b === void 0 ? void 0 : _b.annotations) !== null && _c !== void 0 ? _c : []; | ||
const action = (0, utils_1.substringBeforeFirst)(rawAction.fullyQualifiedName, '('); | ||
// if the action is unbound (e.g. "myAction"), the annotation target is "myAction()" | ||
const annotationTargetFQN = rawAction.isBound | ||
? rawAction.fullyQualifiedName | ||
: `${rawAction.fullyQualifiedName}()`; | ||
const rawAnnotations = converter.getAnnotations(annotationTargetFQN); | ||
const baseAnnotations = converter.getAnnotations(action); | ||
for (const baseAnnotation of baseAnnotations) { | ||
if (!rawAnnotations.some((annotation) => annotation.term === baseAnnotation.term && annotation.qualifier === baseAnnotation.qualifier)) { | ||
rawAnnotations.push(baseAnnotation); | ||
} | ||
else { | ||
rawAnnotations = | ||
(_f = (_e = converter.rawAnnotationsPerTarget[`${(_d = actionAndOverload.groups) === null || _d === void 0 ? void 0 : _d.action}()`]) === null || _e === void 0 ? void 0 : _e.annotations) !== null && _f !== void 0 ? _f : []; | ||
} | ||
if (((_g = actionAndOverload.groups) === null || _g === void 0 ? void 0 : _g.action) && ((_h = actionAndOverload.groups) === null || _h === void 0 ? void 0 : _h.action) !== rawAction.fullyQualifiedName) { | ||
const baseAnnotations = (_l = (_k = converter.rawAnnotationsPerTarget[(_j = actionAndOverload.groups) === null || _j === void 0 ? void 0 : _j.action]) === null || _k === void 0 ? void 0 : _k.annotations) !== null && _l !== void 0 ? _l : []; | ||
rawAnnotations = rawAnnotations.concat(baseAnnotations); | ||
} | ||
} | ||
@@ -1052,0 +1012,0 @@ return createAnnotationsObject(converter, rawAction, rawAnnotations); |
export * from './converter'; | ||
export * from './utils'; | ||
export * from './writeback'; | ||
export * from './utils'; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -18,4 +18,4 @@ "use strict"; | ||
__exportStar(require("./converter"), exports); | ||
__exportStar(require("./utils"), exports); | ||
__exportStar(require("./writeback"), exports); | ||
__exportStar(require("./utils"), exports); | ||
//# sourceMappingURL=index.js.map |
@@ -1,5 +0,5 @@ | ||
import type { ComplexType, Reference, TypeDefinition, ArrayWithIndex } from '@sap-ux/vocabularies-types'; | ||
export { VocabularyReferences as defaultReferences } from '@sap-ux/vocabularies-types/vocabularies/VocabularyReferences'; | ||
import type { ArrayWithIndex, ComplexType, Reference, TypeDefinition } from '@sap-ux/vocabularies-types'; | ||
export { EnumIsFlag } from '@sap-ux/vocabularies-types/vocabularies/EnumIsFlag'; | ||
export { TermToTypes } from '@sap-ux/vocabularies-types/vocabularies/TermToTypes'; | ||
export { VocabularyReferences as defaultReferences } from '@sap-ux/vocabularies-types/vocabularies/VocabularyReferences'; | ||
export type ReferencesWithMap = Reference[] & { | ||
@@ -50,7 +50,7 @@ referenceMap?: Record<string, Reference>; | ||
/** | ||
* Transform an aliased string representation annotation to the unaliased version. | ||
* Transform an aliased string to its unaliased version given a set of references. | ||
* | ||
* @param references currentReferences for the project | ||
* @param aliasedValue the aliased value | ||
* @returns the unaliased string representing the same | ||
* @param references The references to use for unaliasing. | ||
* @param aliasedValue The aliased value | ||
* @returns The equal unaliased string. | ||
*/ | ||
@@ -57,0 +57,0 @@ export declare function unalias(references: ReferencesWithMap, aliasedValue: string | undefined): string | undefined; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.addGetByValue = exports.createIndexedFind = exports.lazy = exports.Decimal = exports.isComplexTypeDefinition = exports.unalias = exports.alias = exports.substringBeforeLast = exports.substringBeforeFirst = exports.splitAtLast = exports.splitAtFirst = exports.TermToTypes = exports.EnumIsFlag = exports.defaultReferences = void 0; | ||
var VocabularyReferences_1 = require("@sap-ux/vocabularies-types/vocabularies/VocabularyReferences"); | ||
Object.defineProperty(exports, "defaultReferences", { enumerable: true, get: function () { return VocabularyReferences_1.VocabularyReferences; } }); | ||
exports.addGetByValue = exports.createIndexedFind = exports.lazy = exports.Decimal = exports.isComplexTypeDefinition = exports.unalias = exports.alias = exports.substringBeforeLast = exports.substringBeforeFirst = exports.splitAtLast = exports.splitAtFirst = exports.defaultReferences = exports.TermToTypes = exports.EnumIsFlag = void 0; | ||
var EnumIsFlag_1 = require("@sap-ux/vocabularies-types/vocabularies/EnumIsFlag"); | ||
@@ -10,2 +8,4 @@ Object.defineProperty(exports, "EnumIsFlag", { enumerable: true, get: function () { return EnumIsFlag_1.EnumIsFlag; } }); | ||
Object.defineProperty(exports, "TermToTypes", { enumerable: true, get: function () { return TermToTypes_1.TermToTypes; } }); | ||
var VocabularyReferences_1 = require("@sap-ux/vocabularies-types/vocabularies/VocabularyReferences"); | ||
Object.defineProperty(exports, "defaultReferences", { enumerable: true, get: function () { return VocabularyReferences_1.VocabularyReferences; } }); | ||
function splitAt(string, index) { | ||
@@ -94,9 +94,13 @@ return index < 0 ? [string, ''] : [string.substring(0, index), string.substring(index + 1)]; | ||
/** | ||
* Transform an aliased string representation annotation to the unaliased version. | ||
* Transform an aliased string to its unaliased version given a set of references. | ||
* | ||
* @param references currentReferences for the project | ||
* @param aliasedValue the aliased value | ||
* @returns the unaliased string representing the same | ||
* @param references The references to use for unaliasing. | ||
* @param aliasedValue The aliased value | ||
* @returns The equal unaliased string. | ||
*/ | ||
function unalias(references, aliasedValue) { | ||
var _a, _b; | ||
if (!aliasedValue) { | ||
return aliasedValue; | ||
} | ||
if (!references.referenceMap) { | ||
@@ -108,18 +112,21 @@ references.referenceMap = references.reduce((map, ref) => { | ||
} | ||
if (!aliasedValue) { | ||
return aliasedValue; | ||
const separators = ['@', '/', '(']; | ||
const unaliased = []; | ||
let start = 0; | ||
for (let end = 0, maybeAlias = true; end < aliasedValue.length; end++) { | ||
const char = aliasedValue[end]; | ||
if (maybeAlias && char === '.') { | ||
const alias = aliasedValue.substring(start, end); | ||
unaliased.push((_b = (_a = references.referenceMap[alias]) === null || _a === void 0 ? void 0 : _a.namespace) !== null && _b !== void 0 ? _b : alias); | ||
start = end; | ||
maybeAlias = false; | ||
} | ||
if (separators.includes(char)) { | ||
unaliased.push(aliasedValue.substring(start, end + 1)); | ||
start = end + 1; | ||
maybeAlias = true; | ||
} | ||
} | ||
const [vocAlias, value] = splitAtFirst(aliasedValue, '.'); | ||
const reference = references.referenceMap[vocAlias]; | ||
if (reference) { | ||
return `${reference.namespace}.${value}`; | ||
} | ||
else if (aliasedValue.includes('@')) { | ||
// Try to see if it's an annotation Path like to_SalesOrder/@UI.LineItem | ||
const [preAlias, postAlias] = splitAtFirst(aliasedValue, '@'); | ||
return `${preAlias}@${unalias(references, postAlias)}`; | ||
} | ||
else { | ||
return aliasedValue; | ||
} | ||
unaliased.push(aliasedValue.substring(start)); | ||
return unaliased.join(''); | ||
} | ||
@@ -126,0 +133,0 @@ exports.unalias = unalias; |
{ | ||
"name": "@sap-ux/annotation-converter", | ||
"version": "0.6.7", | ||
"version": "0.6.8", | ||
"description": "SAP Fiori OData - Annotation converter", | ||
@@ -25,3 +25,3 @@ "repository": { | ||
"clean": "rimraf dist", | ||
"format": "prettier --write '**/*.{js,json,ts,yaml,yml}' --ignore-path ../../.prettierignore", | ||
"format": "prettier **/* --write --ignore-unknown --ignore-path ../../.prettierignore", | ||
"lint": "eslint . --ext .ts", | ||
@@ -28,0 +28,0 @@ "lint:fix": "eslint . --ext .ts --fix", |
@@ -6,3 +6,2 @@ import type { | ||
Annotation, | ||
AnnotationList, | ||
AnnotationRecord, | ||
@@ -35,3 +34,2 @@ ArrayWithIndex, | ||
RawV4NavigationProperty, | ||
Reference, | ||
RemoveAnnotationAndType, | ||
@@ -42,2 +40,3 @@ ResolutionTarget, | ||
} from '@sap-ux/vocabularies-types'; | ||
import { VocabularyReferences } from '@sap-ux/vocabularies-types/vocabularies/VocabularyReferences'; | ||
import { | ||
@@ -56,3 +55,2 @@ addGetByValue, | ||
} from './utils'; | ||
import { VocabularyReferences } from '@sap-ux/vocabularies-types/vocabularies/VocabularyReferences'; | ||
@@ -200,3 +198,3 @@ /** | ||
if (segment === '' || segment === thisElement.fullyQualifiedName) { | ||
if (segment === '' || converter.unalias(segment) === thisElement.fullyQualifiedName) { | ||
return current; | ||
@@ -265,3 +263,4 @@ } | ||
const action = thisElement.actions[segment]; | ||
const actionName = substringBeforeFirst(converter.unalias(segment), '('); | ||
const action = thisElement.actions[actionName]; | ||
if (action) { | ||
@@ -606,31 +605,11 @@ current.target = action; | ||
) { | ||
const annotationTerm: any = { | ||
const record: any = { | ||
$Type: parseRecordType(converter, currentTerm, currentTarget, currentProperty, annotationRecord), | ||
fullyQualifiedName: currentFQN, | ||
[ANNOTATION_TARGET]: currentTarget | ||
[ANNOTATION_TARGET]: currentTarget, | ||
__source: currentSource | ||
}; | ||
// annotations on the record | ||
lazy(annotationTerm, 'annotations', () => { | ||
// be graceful when resolving annotations on annotations: Sometimes they are referenced directly, sometimes they | ||
// are part of the global annotations list | ||
let annotations; | ||
if (annotationRecord.annotations && annotationRecord.annotations.length > 0) { | ||
annotations = annotationRecord.annotations; | ||
} else { | ||
annotations = converter.rawAnnotationsPerTarget[currentFQN]?.annotations; | ||
} | ||
annotations?.forEach((annotation: any) => { | ||
annotation.target = currentFQN; | ||
annotation.__source = currentSource; | ||
annotation[ANNOTATION_TARGET] = currentTarget; | ||
annotation.fullyQualifiedName = `${currentFQN}@${annotation.term}`; | ||
}); | ||
return createAnnotationsObject(converter, annotationTerm, annotations ?? []); | ||
}); | ||
const annotationContent = annotationRecord.propertyValues?.reduce((annotationContent, propertyValue) => { | ||
lazy(annotationContent, propertyValue.name, () => | ||
for (const propertyValue of annotationRecord.propertyValues) { | ||
lazy(record, propertyValue.name, () => | ||
parseValue( | ||
@@ -646,19 +625,22 @@ converter, | ||
); | ||
} | ||
return annotationContent; | ||
}, annotationTerm); | ||
// annotations on the record | ||
lazy(record, 'annotations', resolveAnnotationsOnAnnotation(converter, annotationRecord, record)); | ||
if (isDataFieldWithForAction(annotationContent)) { | ||
lazy(annotationContent, 'ActionTarget', () => { | ||
// try to resolve to a bound action of the annotation target | ||
let actionTarget = currentTarget.actions?.[annotationContent.Action]; | ||
if (isDataFieldWithForAction(record)) { | ||
lazy(record, 'ActionTarget', () => { | ||
const actionTargetFQN = converter.unalias(record.Action?.toString()); | ||
// (1) Bound action of the annotation target? | ||
let actionTarget = currentTarget.actions?.[actionTargetFQN]; | ||
if (!actionTarget) { | ||
// try to find a corresponding unbound action | ||
actionTarget = converter.getConvertedActionImport(annotationContent.Action)?.action; | ||
// (2) ActionImport (= unbound action)? | ||
actionTarget = converter.getConvertedActionImport(actionTargetFQN)?.action; | ||
} | ||
if (!actionTarget) { | ||
// try to find a corresponding bound (!) action | ||
actionTarget = converter.getConvertedAction(annotationContent.Action); | ||
// (3) Bound action of a different EntityType | ||
actionTarget = converter.getConvertedAction(actionTargetFQN); | ||
if (!actionTarget?.isBound) { | ||
@@ -671,3 +653,3 @@ actionTarget = undefined; | ||
converter.logError( | ||
`Unable to resolve the action '${annotationContent.Action}' defined for '${annotationTerm.fullyQualifiedName}'` | ||
`${record.fullyQualifiedName}: Unable to resolve '${record.Action}' ('${actionTargetFQN}')` | ||
); | ||
@@ -678,3 +660,3 @@ } | ||
} | ||
return annotationContent; | ||
return record; | ||
} | ||
@@ -922,3 +904,3 @@ | ||
annotation.term = converter.unalias(`${vocAlias}.${vocTerm}`); | ||
annotation.term = converter.unalias(`${vocAlias}.${vocTerm}`, VocabularyReferences); | ||
annotation.qualifier = rawAnnotation.qualifier; | ||
@@ -928,23 +910,3 @@ annotation.__source = (rawAnnotation as any).__source; | ||
try { | ||
lazy(annotation, 'annotations', () => { | ||
const annotationFQN = annotation.fullyQualifiedName; | ||
// be graceful when resolving annotations on annotations: Sometimes they are referenced directly, sometimes they | ||
// are part of the global annotations list | ||
let annotations; | ||
if (rawAnnotation.annotations && rawAnnotation.annotations.length > 0) { | ||
annotations = rawAnnotation.annotations; | ||
} else { | ||
annotations = converter.rawAnnotationsPerTarget[annotationFQN]?.annotations; | ||
} | ||
annotations?.forEach((rawSubAnnotation: any) => { | ||
rawSubAnnotation.target = annotationFQN; | ||
rawSubAnnotation.__source = annotation.__source; | ||
rawSubAnnotation[ANNOTATION_TARGET] = target; | ||
rawSubAnnotation.fullyQualifiedName = `${annotationFQN}@${rawSubAnnotation.term}`; | ||
}); | ||
return createAnnotationsObject(converter, annotation, annotations ?? []); | ||
}); | ||
lazy(annotation, 'annotations', resolveAnnotationsOnAnnotation(converter, rawAnnotation, annotation)); | ||
} catch (e) { | ||
@@ -957,73 +919,62 @@ // not an error: parseRecord() already adds annotations, but the other parseXXX functions don't, so this can happen | ||
function getAnnotationFQN(currentTargetName: string, references: Reference[], annotation: RawAnnotation) { | ||
const annotationFQN = `${currentTargetName}@${unalias(references, annotation.term)}`; | ||
if (annotation.qualifier) { | ||
return `${annotationFQN}#${annotation.qualifier}`; | ||
} else { | ||
return annotationFQN; | ||
} | ||
} | ||
/** | ||
* Merge annotation from different source together by overwriting at the term level. | ||
* | ||
* @param rawMetadata | ||
* @param converter | ||
* @returns the resulting merged annotations | ||
*/ | ||
function mergeAnnotations(rawMetadata: RawMetadata): Record<string, AnnotationList> { | ||
const annotationListPerTarget: Record<string, AnnotationList> = {}; | ||
Object.keys(rawMetadata.schema.annotations).forEach((annotationSource) => { | ||
rawMetadata.schema.annotations[annotationSource].forEach((annotationList: AnnotationList) => { | ||
const currentTargetName = unalias(rawMetadata.references, annotationList.target) as string; | ||
(annotationList as any).__source = annotationSource; | ||
if (!annotationListPerTarget[currentTargetName]) { | ||
annotationListPerTarget[currentTargetName] = { | ||
annotations: annotationList.annotations.map((annotation: RawAnnotation) => { | ||
(annotation as Annotation).fullyQualifiedName = getAnnotationFQN( | ||
currentTargetName, | ||
rawMetadata.references, | ||
annotation | ||
); | ||
(annotation as any).__source = annotationSource; | ||
function mergeAnnotations(converter: Converter): Record<string, Annotation[]> { | ||
return Object.keys(converter.rawSchema.annotations).reduceRight((annotationsPerTarget, annotationSource) => { | ||
for (const { target: rawTarget, annotations: rawAnnotations } of converter.rawSchema.annotations[ | ||
annotationSource | ||
]) { | ||
const target = converter.unalias(rawTarget); | ||
if (!annotationsPerTarget[target]) { | ||
annotationsPerTarget[target] = []; | ||
} | ||
annotationsPerTarget[target].push( | ||
...rawAnnotations | ||
.filter( | ||
(rawAnnotation) => | ||
!annotationsPerTarget[target].some( | ||
(existingAnnotation) => | ||
existingAnnotation.term === rawAnnotation.term && | ||
existingAnnotation.qualifier === rawAnnotation.qualifier | ||
) | ||
) | ||
.map((rawAnnotation) => { | ||
let annotationFQN = `${target}@${converter.unalias(rawAnnotation.term)}`; | ||
if (rawAnnotation.qualifier) { | ||
annotationFQN = `${annotationFQN}#${rawAnnotation.qualifier}`; | ||
} | ||
const annotation = rawAnnotation as Annotation & { __source: string }; | ||
annotation.fullyQualifiedName = annotationFQN; | ||
annotation.__source = annotationSource; | ||
return annotation; | ||
}), | ||
target: currentTargetName | ||
}; | ||
(annotationListPerTarget[currentTargetName] as any).__source = annotationSource; | ||
} else { | ||
annotationList.annotations.forEach((annotation: RawAnnotation) => { | ||
const findIndex = annotationListPerTarget[currentTargetName].annotations.findIndex( | ||
(referenceAnnotation: RawAnnotation) => { | ||
return ( | ||
referenceAnnotation.term === annotation.term && | ||
referenceAnnotation.qualifier === annotation.qualifier | ||
); | ||
} | ||
); | ||
(annotation as any).__source = annotationSource; | ||
(annotation as Annotation).fullyQualifiedName = getAnnotationFQN( | ||
currentTargetName, | ||
rawMetadata.references, | ||
annotation | ||
); | ||
if (findIndex !== -1) { | ||
annotationListPerTarget[currentTargetName].annotations.splice(findIndex, 1, annotation); | ||
} else { | ||
annotationListPerTarget[currentTargetName].annotations.push(annotation); | ||
} | ||
}); | ||
} | ||
}); | ||
}); | ||
return annotationListPerTarget; | ||
}) | ||
); | ||
} | ||
return annotationsPerTarget; | ||
}, {} as Record<string, Annotation[]>); | ||
} | ||
class Converter { | ||
private _rawAnnotationsPerTarget: Record<FullyQualifiedName, AnnotationList>; | ||
get rawAnnotationsPerTarget(): Record<FullyQualifiedName, AnnotationList> { | ||
if (this._rawAnnotationsPerTarget === undefined) { | ||
this._rawAnnotationsPerTarget = mergeAnnotations(this.rawMetadata); | ||
private annotationyByTarget: Record<FullyQualifiedName, Annotation[]>; | ||
/** | ||
* Get preprocessed annotations on the specified target. | ||
* | ||
* @param target The annotation target | ||
* @returns An array of annotations | ||
*/ | ||
getAnnotations(target: FullyQualifiedName): Annotation[] { | ||
if (this.annotationyByTarget === undefined) { | ||
this.annotationyByTarget = mergeAnnotations(this); | ||
} | ||
return this._rawAnnotationsPerTarget; | ||
return this.annotationyByTarget[target] ?? []; | ||
} | ||
@@ -1149,3 +1100,3 @@ | ||
toDefaultAlias(value: string) { | ||
toDefaultAlias(value: string | undefined) { | ||
const unaliased = unalias(this.rawMetadata.references, value) ?? ''; | ||
@@ -1155,4 +1106,4 @@ return alias(VocabularyReferences, unaliased); | ||
unalias(value: string | undefined) { | ||
return unalias(this.rawMetadata.references, value) ?? ''; | ||
unalias(value: string | undefined, references = this.rawMetadata.references) { | ||
return unalias(references, value) ?? ''; | ||
} | ||
@@ -1210,8 +1161,34 @@ } | ||
rawAnnotationTarget, | ||
nestedAnnotations ?? | ||
converter.rawAnnotationsPerTarget[rawAnnotationTarget.fullyQualifiedName]?.annotations ?? | ||
[] | ||
nestedAnnotations ?? converter.getAnnotations(rawAnnotationTarget.fullyQualifiedName) | ||
); | ||
} | ||
function resolveAnnotationsOnAnnotation( | ||
converter: Converter, | ||
annotationRecord: AnnotationRecord | RawAnnotation, | ||
annotationTerm: any | ||
) { | ||
return () => { | ||
const currentFQN = annotationTerm.fullyQualifiedName; | ||
// be graceful when resolving annotations on annotations: Sometimes they are referenced directly, sometimes they | ||
// are part of the global annotations list | ||
let annotations; | ||
if (annotationRecord.annotations && annotationRecord.annotations.length > 0) { | ||
annotations = annotationRecord.annotations; | ||
} else { | ||
annotations = converter.getAnnotations(currentFQN); | ||
} | ||
annotations?.forEach((annotation: any) => { | ||
annotation.target = currentFQN; | ||
annotation.__source = annotationTerm.__source; | ||
annotation[ANNOTATION_TARGET] = annotationTerm[ANNOTATION_TARGET]; | ||
annotation.fullyQualifiedName = `${currentFQN}@${annotation.term}`; | ||
}); | ||
return createAnnotationsObject(converter, annotationTerm, annotations ?? []); | ||
}; | ||
} | ||
function createAnnotationsObject(converter: Converter, target: any, rawAnnotations: RawAnnotation[]) { | ||
@@ -1475,20 +1452,20 @@ return rawAnnotations.reduce((vocabularyAliases, annotation) => { | ||
lazy(convertedAction, 'annotations', () => { | ||
// this.is.the.action(on.this.type) --> action: 'this.is.the.action', overload: 'on.this.type' | ||
// this.is.the.action() --> action: 'this.is.the.action', overload: undefined | ||
// this.is.the.action --> action: 'this.is.the.action', overload: undefined | ||
const actionAndOverload = rawAction.fullyQualifiedName.match(/(?<action>[^()]+)(?:\((?<overload>.*)\))?/); | ||
const action = substringBeforeFirst(rawAction.fullyQualifiedName, '('); | ||
let rawAnnotations: RawAnnotation[] = []; | ||
if (actionAndOverload) { | ||
if (actionAndOverload.groups?.overload) { | ||
rawAnnotations = converter.rawAnnotationsPerTarget[rawAction.fullyQualifiedName]?.annotations ?? []; | ||
} else { | ||
rawAnnotations = | ||
converter.rawAnnotationsPerTarget[`${actionAndOverload.groups?.action}()`]?.annotations ?? []; | ||
} | ||
// if the action is unbound (e.g. "myAction"), the annotation target is "myAction()" | ||
const annotationTargetFQN = rawAction.isBound | ||
? rawAction.fullyQualifiedName | ||
: `${rawAction.fullyQualifiedName}()`; | ||
if (actionAndOverload.groups?.action && actionAndOverload.groups?.action !== rawAction.fullyQualifiedName) { | ||
const baseAnnotations = | ||
converter.rawAnnotationsPerTarget[actionAndOverload.groups?.action]?.annotations ?? []; | ||
rawAnnotations = rawAnnotations.concat(baseAnnotations); | ||
const rawAnnotations = converter.getAnnotations(annotationTargetFQN); | ||
const baseAnnotations = converter.getAnnotations(action); | ||
for (const baseAnnotation of baseAnnotations) { | ||
if ( | ||
!rawAnnotations.some( | ||
(annotation) => | ||
annotation.term === baseAnnotation.term && annotation.qualifier === baseAnnotation.qualifier | ||
) | ||
) { | ||
rawAnnotations.push(baseAnnotation); | ||
} | ||
@@ -1495,0 +1472,0 @@ } |
export * from './converter'; | ||
export * from './utils'; | ||
export * from './writeback'; | ||
export * from './utils'; |
@@ -1,5 +0,6 @@ | ||
import type { Index, ComplexType, Reference, TypeDefinition, ArrayWithIndex } from '@sap-ux/vocabularies-types'; | ||
export { VocabularyReferences as defaultReferences } from '@sap-ux/vocabularies-types/vocabularies/VocabularyReferences'; | ||
import type { ArrayWithIndex, ComplexType, Index, Reference, TypeDefinition } from '@sap-ux/vocabularies-types'; | ||
export { EnumIsFlag } from '@sap-ux/vocabularies-types/vocabularies/EnumIsFlag'; | ||
export { TermToTypes } from '@sap-ux/vocabularies-types/vocabularies/TermToTypes'; | ||
export { VocabularyReferences as defaultReferences } from '@sap-ux/vocabularies-types/vocabularies/VocabularyReferences'; | ||
@@ -94,9 +95,13 @@ export type ReferencesWithMap = Reference[] & { | ||
/** | ||
* Transform an aliased string representation annotation to the unaliased version. | ||
* Transform an aliased string to its unaliased version given a set of references. | ||
* | ||
* @param references currentReferences for the project | ||
* @param aliasedValue the aliased value | ||
* @returns the unaliased string representing the same | ||
* @param references The references to use for unaliasing. | ||
* @param aliasedValue The aliased value | ||
* @returns The equal unaliased string. | ||
*/ | ||
export function unalias(references: ReferencesWithMap, aliasedValue: string | undefined): string | undefined { | ||
if (!aliasedValue) { | ||
return aliasedValue; | ||
} | ||
if (!references.referenceMap) { | ||
@@ -108,16 +113,23 @@ references.referenceMap = references.reduce((map: Record<string, Reference>, ref) => { | ||
} | ||
if (!aliasedValue) { | ||
return aliasedValue; | ||
const separators = ['@', '/', '(']; | ||
const unaliased: string[] = []; | ||
let start = 0; | ||
for (let end = 0, maybeAlias = true; end < aliasedValue.length; end++) { | ||
const char = aliasedValue[end]; | ||
if (maybeAlias && char === '.') { | ||
const alias = aliasedValue.substring(start, end); | ||
unaliased.push(references.referenceMap[alias]?.namespace ?? alias); | ||
start = end; | ||
maybeAlias = false; | ||
} | ||
if (separators.includes(char)) { | ||
unaliased.push(aliasedValue.substring(start, end + 1)); | ||
start = end + 1; | ||
maybeAlias = true; | ||
} | ||
} | ||
const [vocAlias, value] = splitAtFirst(aliasedValue, '.'); | ||
const reference = references.referenceMap[vocAlias]; | ||
if (reference) { | ||
return `${reference.namespace}.${value}`; | ||
} else if (aliasedValue.includes('@')) { | ||
// Try to see if it's an annotation Path like to_SalesOrder/@UI.LineItem | ||
const [preAlias, postAlias] = splitAtFirst(aliasedValue, '@'); | ||
return `${preAlias}@${unalias(references, postAlias)}`; | ||
} else { | ||
return aliasedValue; | ||
} | ||
unaliased.push(aliasedValue.substring(start)); | ||
return unaliased.join(''); | ||
} | ||
@@ -124,0 +136,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
220380
3639