@sap/cds-compiler 3.0.0 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +104 -9
- package/bin/.eslintrc.json +2 -1
- package/bin/cdsc.js +28 -16
- package/doc/API.md +11 -0
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +24 -2
- package/doc/CHANGELOG_DEPRECATED.md +21 -1
- package/lib/api/main.js +92 -40
- package/lib/api/options.js +2 -3
- package/lib/base/keywords.js +64 -1
- package/lib/base/message-registry.js +33 -5
- package/lib/base/messages.js +54 -65
- package/lib/base/model.js +2 -0
- package/lib/base/optionProcessorHelper.js +53 -21
- package/lib/checks/actionsFunctions.js +8 -7
- package/lib/checks/selectItems.js +96 -14
- package/lib/checks/types.js +5 -8
- package/lib/checks/validator.js +1 -2
- package/lib/compiler/assert-consistency.js +65 -13
- package/lib/compiler/base.js +6 -4
- package/lib/compiler/builtins.js +93 -4
- package/lib/compiler/checks.js +1 -1
- package/lib/compiler/define.js +28 -23
- package/lib/compiler/extend.js +20 -11
- package/lib/compiler/finalize-parse-cdl.js +5 -9
- package/lib/compiler/index.js +2 -0
- package/lib/compiler/populate.js +37 -32
- package/lib/compiler/propagator.js +11 -6
- package/lib/compiler/resolve.js +15 -19
- package/lib/compiler/shared.js +54 -18
- package/lib/compiler/tweak-assocs.js +5 -11
- package/lib/compiler/utils.js +15 -6
- package/lib/edm/annotations/genericTranslation.js +12 -2
- package/lib/edm/annotations/preprocessAnnotations.js +18 -15
- package/lib/edm/csn2edm.js +18 -17
- package/lib/edm/edm.js +22 -13
- package/lib/edm/edmAnnoPreprocessor.js +349 -0
- package/lib/edm/edmInboundChecks.js +85 -0
- package/lib/edm/edmPreprocessor.js +336 -665
- package/lib/edm/edmUtils.js +86 -45
- package/lib/gen/Dictionary.json +29 -9
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -2
- package/lib/gen/languageLexer.js +3 -0
- package/lib/gen/languageParser.js +4332 -4496
- package/lib/inspect/.eslintrc.json +4 -0
- package/lib/inspect/index.js +14 -0
- package/lib/inspect/inspectModelStatistics.js +81 -0
- package/lib/inspect/inspectPropagation.js +189 -0
- package/lib/inspect/inspectUtils.js +44 -0
- package/lib/json/from-csn.js +19 -20
- package/lib/json/to-csn.js +11 -8
- package/lib/language/genericAntlrParser.js +150 -92
- package/lib/language/language.g4 +47 -74
- package/lib/main.d.ts +1 -0
- package/lib/model/api.js +1 -1
- package/lib/model/csnRefs.js +56 -29
- package/lib/model/csnUtils.js +29 -14
- package/lib/model/revealInternalProperties.js +6 -4
- package/lib/modelCompare/compare.js +3 -0
- package/lib/optionProcessor.js +81 -38
- package/lib/render/toCdl.js +57 -32
- package/lib/render/toHdbcds.js +1 -1
- package/lib/render/toSql.js +31 -11
- package/lib/render/utils/common.js +3 -4
- package/lib/transform/db/associations.js +43 -35
- package/lib/transform/db/cdsPersistence.js +0 -1
- package/lib/transform/db/flattening.js +3 -4
- package/lib/transform/db/transformExists.js +7 -5
- package/lib/transform/draft/db.js +1 -1
- package/lib/transform/forHanaNew.js +11 -2
- package/lib/transform/forOdataNew.js +4 -4
- package/lib/transform/localized.js +15 -11
- package/lib/transform/odata/typesExposure.js +14 -5
- package/lib/utils/file.js +28 -18
- package/lib/utils/moduleResolve.js +0 -1
- package/package.json +3 -4
- package/share/messages/syntax-expected-integer.md +9 -8
- package/lib/checks/unknownMagic.js +0 -41
package/lib/compiler/shared.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
const { searchName } = require('../base/messages');
|
|
7
7
|
const { dictAddArray } = require('../base/dictionaries');
|
|
8
|
+
const { isDeprecatedEnabled } = require('../base/model');
|
|
8
9
|
|
|
9
10
|
const {
|
|
10
11
|
setLink,
|
|
@@ -144,6 +145,13 @@ function fns( model ) {
|
|
|
144
145
|
rewrite: {
|
|
145
146
|
next: '_$next', dollar: true, escape: 'param', noDep: true, rewrite: true,
|
|
146
147
|
}, // TODO: assertion that there is no next/escape used
|
|
148
|
+
'order-by': {
|
|
149
|
+
next: '_$next',
|
|
150
|
+
dollar: true,
|
|
151
|
+
escape: 'param',
|
|
152
|
+
assoc: 'nav',
|
|
153
|
+
deprecatedSourceRefs: true,
|
|
154
|
+
},
|
|
147
155
|
'order-by-union': {
|
|
148
156
|
next: '_$next', dollar: true, escape: 'param', noDep: true, noExt: true,
|
|
149
157
|
},
|
|
@@ -160,6 +168,7 @@ function fns( model ) {
|
|
|
160
168
|
resolveUncheckedPath,
|
|
161
169
|
resolveTypeArgumentsUnchecked,
|
|
162
170
|
resolvePath,
|
|
171
|
+
checkAnnotate,
|
|
163
172
|
defineAnnotations,
|
|
164
173
|
attachAndEmitValidNames,
|
|
165
174
|
} );
|
|
@@ -602,6 +611,21 @@ function fns( model ) {
|
|
|
602
611
|
else if (r) {
|
|
603
612
|
return setArtifactLink( head, r );
|
|
604
613
|
}
|
|
614
|
+
else if (spec.deprecatedSourceRefs && env._combined &&
|
|
615
|
+
isDeprecatedEnabled( options, 'autoCorrectOrderBySourceRefs' )) {
|
|
616
|
+
// User has provided a source element without table alias where a query
|
|
617
|
+
// element is expected. Possible on many DBs (and compiler v1), in CAP
|
|
618
|
+
// only with table alias. Auto-correct it if no duplicate.
|
|
619
|
+
// TODO: we could use that info also in messages when the deprecated flag is not set
|
|
620
|
+
const s = env._combined[head.id];
|
|
621
|
+
if (s && !Array.isArray(s)) {
|
|
622
|
+
path.$prefix = s.name.alias; // pushing it to path directly could be problematic
|
|
623
|
+
warning( null, [ head.location, user ],
|
|
624
|
+
{ id: head.id, newcode: `${ s.name.alias }.${ head.id }` },
|
|
625
|
+
'Replace source element reference $(ID) by $(NEWCODE); auto-corrected' );
|
|
626
|
+
return setArtifactLink( head, s );
|
|
627
|
+
}
|
|
628
|
+
}
|
|
605
629
|
}
|
|
606
630
|
if (spec.noMessage || msgArt === true && extDict === model.definitions)
|
|
607
631
|
return null;
|
|
@@ -861,29 +885,41 @@ function fns( model ) {
|
|
|
861
885
|
}
|
|
862
886
|
}
|
|
863
887
|
|
|
888
|
+
// Issue messages for annotations on namespaces and builtins
|
|
889
|
+
// (TODO: really here?, probably split main artifacts vs returns)
|
|
890
|
+
// see also lateExtensions() where similar messages are reported
|
|
891
|
+
function checkAnnotate( construct, art ) {
|
|
892
|
+
// Namespaces cannot be annotated in CSN but because they exist as XSN artifacts
|
|
893
|
+
// they can still be applied. Namespace annotations are extracted in to-csn.js
|
|
894
|
+
// In parseCdl mode USINGs and other unknown references are generated as
|
|
895
|
+
// namespaces which would lead to false positives.
|
|
896
|
+
// TODO: should this really be different to annotate-unknown?
|
|
897
|
+
if (art.kind === 'namespace') {
|
|
898
|
+
info( 'anno-namespace', [ construct.name.location, construct ], {},
|
|
899
|
+
'Namespaces can\'t be annotated' );
|
|
900
|
+
}
|
|
901
|
+
// Builtin annotations would also get lost. Same as for namespaces:
|
|
902
|
+
// extracted in to-csn.js
|
|
903
|
+
else if (art.builtin === true) {
|
|
904
|
+
info( 'anno-builtin', [ construct.name.location, construct ], {},
|
|
905
|
+
'Builtin types should not be annotated. Use custom type instead' );
|
|
906
|
+
}
|
|
907
|
+
else if (construct.$syntax === 'returns' && art._block && art.kind !== 'action' &&
|
|
908
|
+
art.kind !== 'function' ) {
|
|
909
|
+
// `annotate ABC with returns {}` is handled just like `elements`. Warn if it is used
|
|
910
|
+
// for non-actions. We can't only check for !art.returns, because `action A();` is valid.
|
|
911
|
+
// `art._block` ensures that `art` is a defined def.
|
|
912
|
+
warning('anno-unexpected-returns', [ construct.name.location, construct ],
|
|
913
|
+
{ keyword: 'returns', kind: art.kind }, 'Unexpected $(KEYWORD) for $(KIND)');
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
864
917
|
// Set _block links for annotations (necessary for layering).
|
|
865
|
-
// Issue messages for annotations on namespaces and builtins (TODO: really here?)
|
|
866
918
|
// Also copy annotations from `construct` to `art` (TODO: separate that functionality).
|
|
867
919
|
function defineAnnotations( construct, art, block, priority = false ) {
|
|
868
|
-
if (!options.parseCdl && construct.kind === 'annotate') {
|
|
869
|
-
// Namespaces cannot be annotated in CSN but because they exist as XSN artifacts
|
|
870
|
-
// they can still be applied. Namespace annotations are extracted in to-csn.js
|
|
871
|
-
// In parseCdl mode USINGs and other unknown references are generated as
|
|
872
|
-
// namespaces which would lead to false positives.
|
|
873
|
-
// TODO: should this really be different to annotate-unknown?
|
|
874
|
-
if (art.kind === 'namespace') {
|
|
875
|
-
info( 'anno-namespace', [ construct.name.location, construct ], {},
|
|
876
|
-
'Namespaces can\'t be annotated' );
|
|
877
|
-
}
|
|
878
|
-
// Builtin annotations would also get lost. Same as for namespaces:
|
|
879
|
-
// extracted in to-csn.js
|
|
880
|
-
else if (art.builtin === true) {
|
|
881
|
-
info( 'anno-builtin', [ construct.name.location, construct ], {},
|
|
882
|
-
'Builtin types should not be annotated. Use custom type instead' );
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
920
|
if (construct.doc)
|
|
886
921
|
art.doc = construct.doc; // e.g. through `extensions` array in CSN
|
|
922
|
+
|
|
887
923
|
// set _block (for layering) and $priority, shallow-copy from extension
|
|
888
924
|
// TODO: think of removing $priority, then
|
|
889
925
|
// no _block: define, _block: annotate/extend/edmx
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
5
|
const {
|
|
6
|
-
isDeprecatedEnabled,
|
|
7
6
|
forEachDefinition,
|
|
8
7
|
forEachGeneric,
|
|
9
8
|
forEachInOrder,
|
|
@@ -26,7 +25,6 @@ const $location = Symbol.for('cds.$location');
|
|
|
26
25
|
|
|
27
26
|
// Export function of this file.
|
|
28
27
|
function tweakAssocs( model ) {
|
|
29
|
-
const { options } = model;
|
|
30
28
|
// Get shared functionality and the message function:
|
|
31
29
|
const {
|
|
32
30
|
info, warning, error,
|
|
@@ -38,11 +36,6 @@ function tweakAssocs( model ) {
|
|
|
38
36
|
} = model.$functions;
|
|
39
37
|
const { environment } = model.$volatileFunctions;
|
|
40
38
|
|
|
41
|
-
// behavior depending on option `deprecated`:
|
|
42
|
-
const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
|
|
43
|
-
// TODO: we should get rid of noElementsExpansion soon; both
|
|
44
|
-
// beta.nestedProjections and beta.universalCsn do not work with it.
|
|
45
|
-
|
|
46
39
|
// Phase 5: rewrite associations
|
|
47
40
|
forEachDefinition( model, rewriteSimple );
|
|
48
41
|
// TODO: sequence not good enough with derived type of structure with
|
|
@@ -113,7 +106,7 @@ function tweakAssocs( model ) {
|
|
|
113
106
|
|
|
114
107
|
function rewriteAssociationCheck( element ) {
|
|
115
108
|
const elem = element.items || element; // TODO v2: nested items
|
|
116
|
-
if (elem.elements
|
|
109
|
+
if (elem.elements)
|
|
117
110
|
forEachGeneric( elem, 'elements', rewriteAssociationCheck );
|
|
118
111
|
if (!elem.target)
|
|
119
112
|
return;
|
|
@@ -207,7 +200,7 @@ function tweakAssocs( model ) {
|
|
|
207
200
|
|
|
208
201
|
function rewriteAssociation( element ) {
|
|
209
202
|
let elem = element.items || element; // TODO v2: nested items
|
|
210
|
-
if (elem.elements
|
|
203
|
+
if (elem.elements)
|
|
211
204
|
forEachGeneric( elem, 'elements', rewriteAssociation );
|
|
212
205
|
if (!originTarget( elem ))
|
|
213
206
|
return;
|
|
@@ -286,7 +279,7 @@ function tweakAssocs( model ) {
|
|
|
286
279
|
// same (TODO later: set status whether rewrite changes anything),
|
|
287
280
|
// especially problematic are refs starting with $self:
|
|
288
281
|
setExpandStatus( elem, 'target' );
|
|
289
|
-
if (
|
|
282
|
+
if (elem._parent && elem._parent.kind === 'element') {
|
|
290
283
|
// managed association as sub element not supported yet
|
|
291
284
|
error( null, [ elem.location, elem ], {},
|
|
292
285
|
// eslint-disable-next-line max-len
|
|
@@ -369,7 +362,8 @@ function tweakAssocs( model ) {
|
|
|
369
362
|
const item = expr.path[root.kind === '$self' ? 1 : 0];
|
|
370
363
|
if (!item)
|
|
371
364
|
return; // just $self
|
|
372
|
-
|
|
365
|
+
// corresponding elem in including structure
|
|
366
|
+
const elem = (assoc._main.items || assoc._main).elements[item.id];
|
|
373
367
|
if (!(Array.isArray(elem) || // no msg for redefs
|
|
374
368
|
elem === item._artifact || // redirection for explicit def
|
|
375
369
|
elem._origin === item._artifact)) {
|
package/lib/compiler/utils.js
CHANGED
|
@@ -95,6 +95,14 @@ function linkToOrigin( origin, name, parent, prop, location, silentDep ) {
|
|
|
95
95
|
return elem;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Set the member `elem` to have a _parent link to `parent` and a corresponding
|
|
100
|
+
* _main link. Also set the member's name accordingly, where argument `name`
|
|
101
|
+
* is most often the property `elem.name.id`.
|
|
102
|
+
*
|
|
103
|
+
* If argument `prop` is provided, add `elem` to the dictionary of that name,
|
|
104
|
+
* e.g. `elements`.
|
|
105
|
+
*/
|
|
98
106
|
function setMemberParent( elem, name, parent, prop ) {
|
|
99
107
|
if (prop) { // extension or structure include
|
|
100
108
|
// TODO: consider nested ARRAY OF and RETURNS, COMPOSITION OF type
|
|
@@ -107,9 +115,10 @@ function setMemberParent( elem, name, parent, prop ) {
|
|
|
107
115
|
parent = parent._outer;
|
|
108
116
|
setLink( elem, '_parent', parent );
|
|
109
117
|
setLink( elem, '_main', parent._main || parent );
|
|
110
|
-
const parentName = parent.name || parent._outer
|
|
111
|
-
|
|
112
|
-
|
|
118
|
+
const parentName = parent.name || parent._outer?.name;
|
|
119
|
+
if (parentName) // may not be available in e.g. cast() - TODO recheck (#9503)
|
|
120
|
+
elem.name.absolute = parentName.absolute;
|
|
121
|
+
if (!parentName || name == null)
|
|
113
122
|
return;
|
|
114
123
|
const normalized = kindProperties[elem.kind].normalized || elem.kind;
|
|
115
124
|
[ 'element', 'alias', 'select', 'param', 'action' ].forEach( ( kind ) => {
|
|
@@ -188,8 +197,8 @@ function withAssociation( ref, test = testFunctionPlaceholder, alsoTestLast = fa
|
|
|
188
197
|
*
|
|
189
198
|
* @param {XSN.Path} path
|
|
190
199
|
*/
|
|
191
|
-
function pathName(path) {
|
|
192
|
-
return (path.broken) ?
|
|
200
|
+
function pathName( path ) {
|
|
201
|
+
return (path && !path.broken) ? path.map( id => id.id ).join('.') : '';
|
|
193
202
|
}
|
|
194
203
|
|
|
195
204
|
/**
|
|
@@ -357,7 +366,7 @@ function traverseQueryExtra( main, callback ) {
|
|
|
357
366
|
// that value is only on elements, types, and params -> no other members
|
|
358
367
|
// when set, only on elem/art with expanded elements
|
|
359
368
|
// - 'target': all expanded (sub) elements might only have new target/on, but
|
|
360
|
-
// no
|
|
369
|
+
// no individual annotations on any (sub) member
|
|
361
370
|
// when set, traverse all parents where the value has been 'origin' before
|
|
362
371
|
// - 'annotate': at least one inferred (sub) member has an individual annotation,
|
|
363
372
|
// not counting propagated ones; set up to the definition (main artifact)
|
|
@@ -401,8 +401,6 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
401
401
|
params.push(action['@cds.odata.bindingparameter.collection'] ? 'Collection(' + bindingParam + ')' : bindingParam);
|
|
402
402
|
}
|
|
403
403
|
if (action.kind === 'function') {
|
|
404
|
-
let mapType = (p) => (isBuiltinType(p.type)) ?
|
|
405
|
-
edmUtils.mapCdsToEdmType(p, messageFunctions, false /*is only called for v4*/) : p.type;
|
|
406
404
|
if(action.params) {
|
|
407
405
|
action.params && Object.values(action.params).forEach(p => {
|
|
408
406
|
let isArrayType = !p.type && p.items && p.items.type;
|
|
@@ -411,6 +409,18 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
411
409
|
}
|
|
412
410
|
}
|
|
413
411
|
return '(' + params.join(',') + ')';
|
|
412
|
+
|
|
413
|
+
function mapType(p) {
|
|
414
|
+
if(isBuiltinType(p.type))
|
|
415
|
+
return edmUtils.mapCdsToEdmType(p, messageFunctions, false /*is only called for v4*/)
|
|
416
|
+
else if(options.whatsMySchemaName) {
|
|
417
|
+
const schemaName = options.whatsMySchemaName(p.type);
|
|
418
|
+
// strip the service namespace of from a parameter type
|
|
419
|
+
if(schemaName && schemaName !== options.serviceName)
|
|
420
|
+
return p.type.replace(options.serviceName + '.', '');
|
|
421
|
+
}
|
|
422
|
+
return p.type;
|
|
423
|
+
}
|
|
414
424
|
}
|
|
415
425
|
|
|
416
426
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const edmUtils = require('../edmUtils.js');
|
|
4
3
|
const { makeMessageFunction } = require('../../base/messages.js');
|
|
4
|
+
const { forEachDefinition } = require('../../model/csnUtils.js');
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
/**************************************************************************************************
|
|
@@ -37,15 +37,14 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
37
37
|
let targetName = (typeof assoc.target === 'object') ? assoc.target.name : assoc.target;
|
|
38
38
|
let target = (typeof assoc.target === 'object') ? assoc.target : csn.definitions[assoc.target];
|
|
39
39
|
|
|
40
|
-
let keyNames = Object.keys(target.elements).filter(x => target.elements[x].key);
|
|
40
|
+
let keyNames = Object.keys(target.elements).filter(x => target.elements[x].key && !target.elements[x].target);
|
|
41
41
|
if (keyNames.length === 0) {
|
|
42
42
|
keyNames.push('MISSING');
|
|
43
43
|
warning(null, null, `in annotation preprocessing: target ${targetName} has no key`);
|
|
44
44
|
}
|
|
45
45
|
else if (keyNames.length > 1)
|
|
46
46
|
warning(null, null, `in annotation preprocessing: target ${targetName} has multiple key elements`);
|
|
47
|
-
|
|
48
|
-
// TODO: what happens if key of target is itself a managed association?
|
|
47
|
+
|
|
49
48
|
return keyNames[0];
|
|
50
49
|
}
|
|
51
50
|
|
|
@@ -58,15 +57,15 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
58
57
|
function resolveShortcuts() {
|
|
59
58
|
let art = null;
|
|
60
59
|
|
|
61
|
-
|
|
62
|
-
if(artifactName
|
|
60
|
+
forEachDefinition(csn, (artifact, artifactName) => {
|
|
61
|
+
if(artifactName === serviceName || artifactName.startsWith(serviceName + '.')) {
|
|
63
62
|
art = artifactName;
|
|
64
63
|
handleAnnotations(artifactName, artifact);
|
|
65
|
-
|
|
64
|
+
artifact.elements && Object.entries(artifact.elements).forEach(([elementName, element]) => {
|
|
66
65
|
handleAnnotations(elementName, element);
|
|
67
66
|
});
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
artifact.actions && Object.values(artifact.actions).forEach(action => {
|
|
68
|
+
action.params && Object.entries(action.params).forEach(([paramName, param]) => {
|
|
70
69
|
handleAnnotations(paramName, param);
|
|
71
70
|
});
|
|
72
71
|
});
|
|
@@ -155,7 +154,7 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
155
154
|
return false;
|
|
156
155
|
}
|
|
157
156
|
let assoc = csn.definitions[art].elements[assocName];
|
|
158
|
-
if (!assoc || !
|
|
157
|
+
if (!assoc || !assoc.target) {
|
|
159
158
|
warning(null, null, `in annotation preprocessing/${aNameWithoutQualifier}: there is no association "${assocName}", ${ctx}`);
|
|
160
159
|
return false;
|
|
161
160
|
}
|
|
@@ -165,7 +164,7 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
165
164
|
}
|
|
166
165
|
else if (aNameWithoutQualifier === '@Common.ValueList.entity') {
|
|
167
166
|
// if both annotations are present, ignore 'entity' and raise a message
|
|
168
|
-
if (annoNames.map(x=>x.split('#')[0]).find(x=>(x
|
|
167
|
+
if (annoNames.map(x=>x.split('#')[0]).find(x=>(x==='@Common.ValueList.viaAssociation'))) {
|
|
169
168
|
warning(null, null, `in annotation preprocessing/@Common.ValueList: 'entity' is ignored, as 'viaAssociation' is present, ${ctx}`);
|
|
170
169
|
return false;
|
|
171
170
|
}
|
|
@@ -196,7 +195,7 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
196
195
|
// name of the element carrying the value help annotation
|
|
197
196
|
// if this is a managed assoc, use fk field instead (if there is a single one)
|
|
198
197
|
let localDataProp = carrierName.split('/').pop();
|
|
199
|
-
if (
|
|
198
|
+
if (carrier.target && carrier.on === undefined) {
|
|
200
199
|
localDataProp = localDataProp + fkSeparator + getKeyOfTargetOfManagedAssoc(carrier);
|
|
201
200
|
}
|
|
202
201
|
|
|
@@ -212,7 +211,7 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
212
211
|
// valueListProp: the (single) key field of the value list entity
|
|
213
212
|
// if no key or multiple keys -> warning
|
|
214
213
|
let valueListProp = null;
|
|
215
|
-
let keys = Object.keys(vlEntity.elements).filter( x => vlEntity.elements[x].key );
|
|
214
|
+
let keys = Object.keys(vlEntity.elements).filter( x => vlEntity.elements[x].key && !vlEntity.elements[x].target );
|
|
216
215
|
if (keys.length === 0) {
|
|
217
216
|
warning(null, null, `in annotation preprocessing/value help shortcut: entity "${enameFull}" has no key, ${ctx}`);
|
|
218
217
|
return false;
|
|
@@ -297,8 +296,12 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
297
296
|
|
|
298
297
|
//change the scalar anno into a "pseudo-structured" one
|
|
299
298
|
// TODO should be flattened, but then alphabetical order is destroyed
|
|
300
|
-
|
|
301
|
-
|
|
299
|
+
|
|
300
|
+
// Do not overwrite existing nested annotation values, instead give existing
|
|
301
|
+
// nested annotation precedence and remove outer annotation (always)
|
|
302
|
+
if(!carrier['@Common.Text.@UI.TextArrangement'] && textAnno) {
|
|
303
|
+
carrier['@Common.Text'] = { '$value': textAnno, '@UI.TextArrangement': value };
|
|
304
|
+
}
|
|
302
305
|
delete carrier[aName];
|
|
303
306
|
}
|
|
304
307
|
}
|
package/lib/edm/csn2edm.js
CHANGED
|
@@ -7,7 +7,7 @@ const NAVPROP_TRENNER = '_';
|
|
|
7
7
|
const VALUELIST_NAVPROP_PREFIX = '';
|
|
8
8
|
|
|
9
9
|
const edmUtils = require('./edmUtils.js')
|
|
10
|
-
const { initializeModel
|
|
10
|
+
const { initializeModel } = require('./edmPreprocessor.js');
|
|
11
11
|
const translate = require('./annotations/genericTranslation.js');
|
|
12
12
|
const { setProp } = require('../base/model');
|
|
13
13
|
const { cloneCsnNonDict, isEdmPropertyRendered, isBuiltinType } = require('../model/csnUtils');
|
|
@@ -326,15 +326,16 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
326
326
|
|
|
327
327
|
/* create the entitytypes and sets
|
|
328
328
|
Do not create an entity set if:
|
|
329
|
-
V4 containment:
|
|
329
|
+
V4 containment: $containerNames is set and not equal with the artifact name
|
|
330
330
|
Entity starts with 'localserviceNameized.' or ends with '_localized'
|
|
331
331
|
*/
|
|
332
332
|
edmUtils.foreach(schemaCsn.definitions,
|
|
333
|
-
a =>
|
|
333
|
+
a => a.kind === 'entity' && !a.abstract && a.name.startsWith(schemaNamePrefix),
|
|
334
334
|
createEntityTypeAndSet
|
|
335
335
|
);
|
|
336
336
|
// create unbound actions/functions
|
|
337
|
-
edmUtils.foreach(schemaCsn.definitions,
|
|
337
|
+
edmUtils.foreach(schemaCsn.definitions,
|
|
338
|
+
a => (a.kind === 'action' || a.kind === 'function') && a.name.startsWith(schemaNamePrefix),
|
|
338
339
|
(options.isV4()) ? createActionV4 : createActionV2);
|
|
339
340
|
|
|
340
341
|
// create the complex types
|
|
@@ -346,7 +347,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
346
347
|
{
|
|
347
348
|
edmUtils.foreach(schemaCsn.definitions,
|
|
348
349
|
artifact => edmUtils.isDerivedType(artifact) &&
|
|
349
|
-
!
|
|
350
|
+
!artifact.target &&
|
|
350
351
|
artifact.name.startsWith(schemaNamePrefix),
|
|
351
352
|
createTypeDefinition);
|
|
352
353
|
}
|
|
@@ -414,7 +415,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
414
415
|
if(p._edmAttributes.Name === EntityTypeName)
|
|
415
416
|
warning('odata-spec-violation-property-name', pLoc, { kind: entityCsn.kind });
|
|
416
417
|
|
|
417
|
-
if(options.isV2() && p._isCollection && !
|
|
418
|
+
if(options.isV2() && p._isCollection && !p._csn.target)
|
|
418
419
|
warning('odata-spec-violation-array', pLoc, { version: '2.0' });
|
|
419
420
|
|
|
420
421
|
if(!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
|
|
@@ -432,7 +433,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
432
433
|
// CDXCORE-CDXCORE-173
|
|
433
434
|
if(options.isV2() && hasStream) {
|
|
434
435
|
attributes['m:HasStream'] = true;
|
|
435
|
-
assignAnnotation(entityCsn, '@Core.MediaType', hasStream);
|
|
436
|
+
edmUtils.assignAnnotation(entityCsn, '@Core.MediaType', hasStream);
|
|
436
437
|
}
|
|
437
438
|
|
|
438
439
|
Schema.append(new Edm.EntityType(v, attributes, properties, entityCsn));
|
|
@@ -461,7 +462,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
461
462
|
}
|
|
462
463
|
|
|
463
464
|
// put actions behind entity types in Schema/EntityContainer
|
|
464
|
-
|
|
465
|
+
entityCsn.actions && Object.entries(entityCsn.actions).forEach(([ n, a ]) => {
|
|
465
466
|
(options.isV4()) ? createActionV4(a, n, entityCsn)
|
|
466
467
|
: createActionV2(a, n, entityCsn)
|
|
467
468
|
});
|
|
@@ -522,7 +523,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
522
523
|
}
|
|
523
524
|
|
|
524
525
|
// Parameter Nodes
|
|
525
|
-
|
|
526
|
+
actionCsn.params && Object.entries(actionCsn.params).forEach(([parameterName, parameterCsn]) => {
|
|
526
527
|
const p = new Edm.Parameter(v, { Name: parameterName }, parameterCsn );
|
|
527
528
|
const pLoc = [ ...loc, 'params', p._edmAttributes.Name ];
|
|
528
529
|
if(!edmUtils.isODataSimpleIdentifier(parameterName))
|
|
@@ -573,7 +574,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
573
574
|
if(rt) // add EntitySet attribute only if return type is an entity
|
|
574
575
|
{
|
|
575
576
|
const defintion = schemaCsn.definitions[rt];
|
|
576
|
-
if(defintion &&
|
|
577
|
+
if(defintion && defintion.kind === 'entity')
|
|
577
578
|
{
|
|
578
579
|
functionImport.setEdmAttribute('EntitySet', rt.replace(schemaNamePrefix, ''));
|
|
579
580
|
}
|
|
@@ -597,7 +598,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
597
598
|
// Binding Parameter: Primary Keys at first position in sequence, this is decisive!
|
|
598
599
|
// V2 XML: Nullable=false is set because we reuse the primary key property for the parameter
|
|
599
600
|
edmUtils.foreach(entityCsn.elements,
|
|
600
|
-
elementCsn => elementCsn.key && !
|
|
601
|
+
elementCsn => elementCsn.key && !elementCsn.target,
|
|
601
602
|
(elementCsn, elementName) => {
|
|
602
603
|
functionImport.append(new Edm.Parameter(v, { Name: elementName }, elementCsn, 'In' ));
|
|
603
604
|
}
|
|
@@ -605,7 +606,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
605
606
|
}
|
|
606
607
|
|
|
607
608
|
// is this still required?
|
|
608
|
-
|
|
609
|
+
Object.entries(actionCsn).forEach(([p, v]) => {
|
|
609
610
|
if (p.match(/^@sap\./))
|
|
610
611
|
functionImport.setXml( { ['sap:' + p.slice(5).replace(/\./g, '-')] : v });
|
|
611
612
|
});
|
|
@@ -613,7 +614,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
613
614
|
// V2 XML: Parameters that are not explicitly marked as Nullable or NotNullable in the CSN must become Nullable=true
|
|
614
615
|
// V2 XML spec does only mention default Nullable=true for Properties not for Parameters so omitting Nullable=true let
|
|
615
616
|
// the client assume that Nullable is false.... Correct Nullable Handling is done inside Parameter constructor
|
|
616
|
-
|
|
617
|
+
actionCsn.params && Object.entries(actionCsn.params).forEach(([parameterName, parameterCsn]) => {
|
|
617
618
|
const pLoc = [...loc, 'params', parameterName];
|
|
618
619
|
const param = new Edm.Parameter(v, { Name: parameterName }, parameterCsn, 'In' );
|
|
619
620
|
edmTypeCompatibilityCheck(param, pLoc);
|
|
@@ -683,12 +684,12 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
683
684
|
let hasStream = false;
|
|
684
685
|
const streamProps = [];
|
|
685
686
|
|
|
686
|
-
|
|
687
|
+
elementsCsn.elements && Object.entries(elementsCsn.elements).forEach(([elementName, elementCsn]) =>
|
|
687
688
|
{
|
|
688
689
|
if(elementCsn._edmParentCsn == undefined)
|
|
689
690
|
setProp(elementCsn, '_edmParentCsn', edmParentCsn);
|
|
690
691
|
|
|
691
|
-
if(
|
|
692
|
+
if(elementCsn.target) {
|
|
692
693
|
// Foreign keys are part of the generic elementCsn.elements property creation
|
|
693
694
|
|
|
694
695
|
// This is the V4 edmx:NavigationProperty
|
|
@@ -769,10 +770,10 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
769
770
|
message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
|
|
770
771
|
|
|
771
772
|
if(options.isV2()) {
|
|
772
|
-
if(p._isCollection && !
|
|
773
|
+
if(p._isCollection && !p._csn.target)
|
|
773
774
|
warning('odata-spec-violation-array', pLoc, { version: '2.0' });
|
|
774
775
|
|
|
775
|
-
if(
|
|
776
|
+
if(p._csn.target)
|
|
776
777
|
warning('odata-spec-violation-assoc', pLoc, { version: '2.0' });
|
|
777
778
|
}
|
|
778
779
|
});
|
package/lib/edm/edm.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const edmUtils = require('./edmUtils.js');
|
|
4
4
|
const { isBuiltinType } = require('../model/csnUtils.js');
|
|
5
5
|
const { forEach } = require("../utils/objectUtils");
|
|
6
|
+
const { isBetaEnabled } = require('../base/model.js');
|
|
6
7
|
|
|
7
8
|
// facet definitions, optional could either be true or array of edm types
|
|
8
9
|
// remove indicates wether or not the canonic facet shall be removed when applying @odata.Type
|
|
@@ -239,7 +240,7 @@ function getEdm(options, messageFunctions) {
|
|
|
239
240
|
if(csn)
|
|
240
241
|
{
|
|
241
242
|
const attr = (useSetAttributes ? csn._SetAttributes : csn);
|
|
242
|
-
|
|
243
|
+
attr && Object.entries(attr).forEach(([p, v]) => {
|
|
243
244
|
if (p.match(/^@sap./))
|
|
244
245
|
this.setXml( { ['sap:' + p.slice(5).replace(/\./g, '-')] : v } );
|
|
245
246
|
});
|
|
@@ -332,7 +333,7 @@ function getEdm(options, messageFunctions) {
|
|
|
332
333
|
if(what==='metadata' || what==='all')
|
|
333
334
|
{
|
|
334
335
|
xml += super.innerXML(indent);
|
|
335
|
-
|
|
336
|
+
this._actions && Object.values(this._actions).forEach(actionArray => {
|
|
336
337
|
actionArray.forEach(action => {
|
|
337
338
|
xml += action.toXML(indent, what) + '\n'; });
|
|
338
339
|
});
|
|
@@ -350,7 +351,7 @@ function getEdm(options, messageFunctions) {
|
|
|
350
351
|
// no $Namespace
|
|
351
352
|
toJSONattributes(json)
|
|
352
353
|
{
|
|
353
|
-
|
|
354
|
+
this._edmAttributes && Object.entries(this._edmAttributes).forEach(([p, v]) => {
|
|
354
355
|
if (p !== 'Name' && p !== 'Namespace')
|
|
355
356
|
json[p[0] === '@' ? p : '$' + p] = v;
|
|
356
357
|
});
|
|
@@ -371,7 +372,7 @@ function getEdm(options, messageFunctions) {
|
|
|
371
372
|
if(Object.keys(json_Annotations).length)
|
|
372
373
|
json['$Annotations'] = json_Annotations;
|
|
373
374
|
}
|
|
374
|
-
|
|
375
|
+
this._actions && Object.entries(this._actions).forEach(([actionName, actionArray]) => {
|
|
375
376
|
json[actionName] = [];
|
|
376
377
|
actionArray.forEach(action => {
|
|
377
378
|
json[actionName].push(action.toJSON());
|
|
@@ -737,7 +738,7 @@ function getEdm(options, messageFunctions) {
|
|
|
737
738
|
if(this._type !== 'Edm.String' && this._type) // Edm.String is default)
|
|
738
739
|
json['$'+this._typeName] = this._type;
|
|
739
740
|
|
|
740
|
-
|
|
741
|
+
this._edmAttributes && Object.entries(this._edmAttributes).forEach(([p, v]) => {
|
|
741
742
|
if (p !== 'Name' && p !== this._typeName
|
|
742
743
|
// remove this line if Nullable=true becomes default
|
|
743
744
|
&& !(p === 'Nullable' && v == false))
|
|
@@ -754,7 +755,14 @@ function getEdm(options, messageFunctions) {
|
|
|
754
755
|
}
|
|
755
756
|
}
|
|
756
757
|
|
|
757
|
-
class ComplexType extends TypeBase {
|
|
758
|
+
class ComplexType extends TypeBase {
|
|
759
|
+
constructor(v, details, csn) {
|
|
760
|
+
super(v, details, csn);
|
|
761
|
+
if(this.v4 && !!csn['@open'] && isBetaEnabled(options, 'odataOpenType')) {
|
|
762
|
+
this._edmAttributes['OpenType'] = true;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
758
766
|
class EntityType extends ComplexType
|
|
759
767
|
{
|
|
760
768
|
constructor(v, details, properties, csn)
|
|
@@ -840,8 +848,8 @@ function getEdm(options, messageFunctions) {
|
|
|
840
848
|
super(v, attributes, csn);
|
|
841
849
|
|
|
842
850
|
// array of enum not yet allowed
|
|
843
|
-
|
|
844
|
-
|
|
851
|
+
const enumValues = /*(csn.items && csn.items.enum) ||*/ csn.enum;
|
|
852
|
+
enumValues && Object.entries(enumValues).forEach(([en, e]) => {
|
|
845
853
|
this.append(new Member(v, { Name: en, Value: e.val } ));
|
|
846
854
|
});
|
|
847
855
|
}
|
|
@@ -1177,9 +1185,10 @@ function getEdm(options, messageFunctions) {
|
|
|
1177
1185
|
_constraints = this._csn._constraints._partnerCsn._constraints;
|
|
1178
1186
|
[i,j] = [1,0];
|
|
1179
1187
|
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1188
|
+
_constraints.constraints && Object.values(_constraints.constraints).forEach(c =>
|
|
1189
|
+
this.append(new ReferentialConstraint(this._v,
|
|
1190
|
+
{ Property: c[i].join(options.pathDelimiter), ReferencedProperty: c[j].join(options.pathDelimiter) } ) )
|
|
1191
|
+
);
|
|
1183
1192
|
}
|
|
1184
1193
|
}
|
|
1185
1194
|
|
|
@@ -1502,7 +1511,7 @@ function getEdm(options, messageFunctions) {
|
|
|
1502
1511
|
}
|
|
1503
1512
|
toJSONattributes(json) {
|
|
1504
1513
|
super.toJSONattributes(json);
|
|
1505
|
-
|
|
1514
|
+
this._jsonOnlyAttributes && Object.entries(this._jsonOnlyAttributes).forEach(([p, v]) => {
|
|
1506
1515
|
json[p[0] === '@' ? p : '$' + p] = v;
|
|
1507
1516
|
});
|
|
1508
1517
|
return json;
|
|
@@ -1606,7 +1615,7 @@ function getEdm(options, messageFunctions) {
|
|
|
1606
1615
|
node._d = new Dependent(v, { Role: from } );
|
|
1607
1616
|
node._p = new Principal(v, { Role: to } );
|
|
1608
1617
|
|
|
1609
|
-
|
|
1618
|
+
c && Object.values(c).forEach(cv => {
|
|
1610
1619
|
node._d.append(new PropertyRef(v, cv[0].join(options.pathDelimiter)));
|
|
1611
1620
|
node._p.append(new PropertyRef(v, cv[1].join(options.pathDelimiter)));
|
|
1612
1621
|
});
|