@sap/cds-compiler 2.10.4 → 2.11.0
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 +50 -0
- package/bin/cdsc.js +42 -25
- package/bin/cdsse.js +1 -0
- package/doc/CHANGELOG_BETA.md +4 -0
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +9 -23
- package/lib/api/options.js +12 -4
- package/lib/api/validate.js +23 -2
- package/lib/backends.js +9 -8
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/message-registry.js +10 -2
- package/lib/base/messages.js +23 -9
- package/lib/base/model.js +5 -4
- package/lib/base/optionProcessorHelper.js +56 -22
- package/lib/checks/selectItems.js +4 -0
- package/lib/checks/unknownMagic.js +6 -3
- package/lib/compiler/assert-consistency.js +7 -0
- package/lib/compiler/base.js +65 -0
- package/lib/compiler/builtins.js +28 -1
- package/lib/compiler/checks.js +2 -1
- package/lib/compiler/definer.js +58 -91
- package/lib/compiler/index.js +16 -4
- package/lib/compiler/propagator.js +5 -2
- package/lib/compiler/resolver.js +93 -34
- package/lib/compiler/shared.js +29 -202
- package/lib/compiler/utils.js +173 -0
- package/lib/edm/annotations/genericTranslation.js +1 -1
- package/lib/edm/csn2edm.js +3 -2
- package/lib/edm/edmPreprocessor.js +31 -36
- package/lib/edm/edmUtils.js +3 -3
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +17 -1
- package/lib/gen/language.tokens +79 -73
- package/lib/gen/languageLexer.interp +19 -1
- package/lib/gen/languageLexer.js +779 -731
- package/lib/gen/languageLexer.tokens +71 -65
- package/lib/gen/languageParser.js +4668 -4072
- package/lib/json/from-csn.js +10 -10
- package/lib/json/to-csn.js +169 -34
- package/lib/language/antlrParser.js +11 -0
- package/lib/language/genericAntlrParser.js +72 -14
- package/lib/language/language.g4 +73 -0
- package/lib/main.d.ts +136 -17
- package/lib/main.js +3 -1
- package/lib/model/api.js +2 -2
- package/lib/model/csnRefs.js +108 -31
- package/lib/model/csnUtils.js +63 -29
- package/lib/model/enrichCsn.js +36 -9
- package/lib/model/revealInternalProperties.js +20 -4
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +29 -18
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/toCdl.js +9 -3
- package/lib/render/toHdbcds.js +16 -36
- package/lib/render/toSql.js +23 -5
- package/lib/transform/db/constraints.js +278 -119
- package/lib/transform/db/draft.js +3 -2
- package/lib/transform/db/expansion.js +6 -4
- package/lib/transform/db/flattening.js +17 -1
- package/lib/transform/db/transformExists.js +61 -2
- package/lib/transform/db/views.js +438 -0
- package/lib/transform/forHanaNew.js +56 -435
- package/lib/transform/forOdataNew.js +9 -2
- package/lib/transform/localized.js +2 -0
- package/lib/transform/transformUtilsNew.js +10 -0
- package/lib/transform/translateAssocsToJoins.js +5 -13
- package/lib/utils/file.js +5 -3
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +48 -26
- package/package.json +1 -1
package/lib/compiler/shared.js
CHANGED
|
@@ -1,86 +1,33 @@
|
|
|
1
1
|
// Compiler functions and utilities shared across all phases
|
|
2
|
-
|
|
2
|
+
// TODO: rename to paths.js and move non resolve-paths functions to somewhere else
|
|
3
3
|
|
|
4
4
|
'use strict';
|
|
5
5
|
|
|
6
6
|
const { searchName } = require('../base/messages');
|
|
7
|
-
const {
|
|
7
|
+
const { dictAddArray } = require('../base/dictionaries');
|
|
8
8
|
const { setProp } = require('../base/model');
|
|
9
9
|
|
|
10
|
-
const
|
|
11
|
-
definitions: 'absolute',
|
|
12
|
-
elements: 'element',
|
|
13
|
-
enum: 'enum',
|
|
14
|
-
foreignKeys: 'key',
|
|
15
|
-
actions: 'action',
|
|
16
|
-
params: 'param',
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const kindProperties = {
|
|
20
|
-
// TODO: also foreignKeys ?
|
|
21
|
-
namespace: { artifacts: true }, // on-the-fly context
|
|
22
|
-
context: { artifacts: true, normalized: 'namespace' },
|
|
23
|
-
service: { artifacts: true, normalized: 'namespace' },
|
|
24
|
-
entity: { elements: true, actions: true, params: () => false },
|
|
25
|
-
select: { normalized: 'select', elements: true },
|
|
26
|
-
$join: { normalized: 'select' },
|
|
27
|
-
$tableAlias: { normalized: 'alias' }, // table alias in select
|
|
28
|
-
$self: { normalized: 'alias' }, // table alias in select
|
|
29
|
-
$navElement: { normalized: 'element' },
|
|
30
|
-
$inline: { normalized: 'element' }, // column with inline property
|
|
31
|
-
event: { elements: true },
|
|
32
|
-
type: { elements: propExists, enum: propExists },
|
|
33
|
-
aspect: { elements: propExists },
|
|
34
|
-
annotation: { elements: propExists, enum: propExists },
|
|
35
|
-
enum: { normalized: 'element' },
|
|
36
|
-
element: { elements: propExists, enum: propExists, dict: 'elements' },
|
|
37
|
-
mixin: { normalized: 'alias' },
|
|
38
|
-
action: {
|
|
39
|
-
params: () => false, elements: () => false, enum: () => false, dict: 'actions',
|
|
40
|
-
}, // no extend params, only annotate
|
|
41
|
-
function: {
|
|
42
|
-
params: () => false, elements: () => false, enum: () => false, normalized: 'action',
|
|
43
|
-
}, // no extend params, only annotate
|
|
44
|
-
key: { normalized: 'element' },
|
|
45
|
-
param: { elements: () => false, enum: () => false, dict: 'params' },
|
|
46
|
-
source: { artifacts: true }, // TODO -> $source
|
|
47
|
-
using: {},
|
|
48
|
-
extend: {
|
|
49
|
-
isExtension: true,
|
|
50
|
-
noDep: 'special',
|
|
51
|
-
elements: true, /* only for parse-cdl */
|
|
52
|
-
actions: true, /* only for parse-cdl */
|
|
53
|
-
},
|
|
54
|
-
annotate: {
|
|
55
|
-
isExtension: true, noDep: 'special', elements: true, enum: true, actions: true, params: true,
|
|
56
|
-
},
|
|
57
|
-
builtin: {}, // = CURRENT_DATE, TODO: improve
|
|
58
|
-
$parameters: {}, // $parameters in query entities
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
function propExists( prop, parent ) {
|
|
62
|
-
const obj = parent.returns || parent;
|
|
63
|
-
return (obj.items || obj.targetAspect || obj)[prop];
|
|
64
|
-
}
|
|
10
|
+
const { setLink, dependsOn } = require('./utils');
|
|
65
11
|
|
|
66
12
|
function artifactsEnv( art ) {
|
|
67
13
|
return art._subArtifacts || Object.create(null);
|
|
68
14
|
}
|
|
69
15
|
|
|
70
16
|
/**
|
|
71
|
-
* Main export function of this file.
|
|
72
|
-
* "define" and "resolve"
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
17
|
+
* Main export function of this file. Attach "resolve" functions shared for phase
|
|
18
|
+
* "define" and "resolve" to `model.$functions`, where argument `model` is the XSN.
|
|
19
|
+
*
|
|
20
|
+
* Before calling these functions, make sure that the following function
|
|
21
|
+
* in model.$volatileFunctions is set:
|
|
22
|
+
* - `environment`: a function which returns the search environment defined by
|
|
23
|
+
* its argument, e.g. a function which returns the dictionary of subartifacts.
|
|
76
24
|
*
|
|
77
25
|
* @param {XSN.Model} model
|
|
78
|
-
* @param {(a, b?, c?) => any} environment
|
|
79
|
-
* @returns {object} Commonly used "resolve" functions.
|
|
80
26
|
*/
|
|
81
|
-
|
|
27
|
+
// TODO: yes, this function will be renamed
|
|
28
|
+
function fns( model ) {
|
|
82
29
|
/** @type {CSN.Options} */
|
|
83
|
-
const options = model
|
|
30
|
+
const { options } = model;
|
|
84
31
|
const {
|
|
85
32
|
info, warning, error, message,
|
|
86
33
|
} = model.$messageFunctions;
|
|
@@ -204,13 +151,15 @@ function fns( model, environment = artifactsEnv ) {
|
|
|
204
151
|
},
|
|
205
152
|
};
|
|
206
153
|
|
|
207
|
-
|
|
154
|
+
const VolatileFns = model.$volatileFunctions;
|
|
155
|
+
Object.assign( model.$functions, {
|
|
208
156
|
resolveUncheckedPath,
|
|
209
157
|
resolvePath,
|
|
210
158
|
resolveTypeArguments,
|
|
211
159
|
defineAnnotations,
|
|
212
160
|
attachAndEmitValidNames,
|
|
213
|
-
};
|
|
161
|
+
} );
|
|
162
|
+
return;
|
|
214
163
|
|
|
215
164
|
function checkConstRef( art ) {
|
|
216
165
|
return ![ 'builtin', 'param' ].includes( art.kind );
|
|
@@ -257,7 +206,7 @@ function fns( model, environment = artifactsEnv ) {
|
|
|
257
206
|
// TODO: better error location if error for main
|
|
258
207
|
if (elem._main.kind !== 'entity' )
|
|
259
208
|
return true; // elem not starting at entity
|
|
260
|
-
environment( art ); // sets _effectiveType on art
|
|
209
|
+
VolatileFns.environment( art ); // sets _effectiveType on art
|
|
261
210
|
return !(art._effectiveType || art).target;
|
|
262
211
|
}
|
|
263
212
|
|
|
@@ -345,7 +294,7 @@ function fns( model, environment = artifactsEnv ) {
|
|
|
345
294
|
// first step: only use _combined of real query - TODO:
|
|
346
295
|
// reject if not visible, but not allow more (!)
|
|
347
296
|
(query._combined || query._parent._combined) ||
|
|
348
|
-
environment( user._main ? user._parent : user );
|
|
297
|
+
VolatileFns.environment( user._main ? user._parent : user );
|
|
349
298
|
}
|
|
350
299
|
}
|
|
351
300
|
|
|
@@ -439,7 +388,7 @@ function fns( model, environment = artifactsEnv ) {
|
|
|
439
388
|
}
|
|
440
389
|
else {
|
|
441
390
|
dependsOn( user, art._main, location );
|
|
442
|
-
environment( art, location, user );
|
|
391
|
+
VolatileFns.environment( art, location, user );
|
|
443
392
|
// Without on-demand resolve, we can simply signal 'undefined "x"'
|
|
444
393
|
// instead of 'illegal cycle' in the following case:
|
|
445
394
|
// element elem: type of elem.x;
|
|
@@ -513,7 +462,7 @@ function fns( model, environment = artifactsEnv ) {
|
|
|
513
462
|
function getPathRoot( path, spec, user, env, extDict, msgArt ) {
|
|
514
463
|
if (!spec.envFn && user._pathHead) {
|
|
515
464
|
// TODO: not necessarily for explicit ON condition in expand
|
|
516
|
-
environment( user._pathHead ); // make sure _origin is set
|
|
465
|
+
VolatileFns.environment( user._pathHead ); // make sure _origin is set
|
|
517
466
|
return user._pathHead._origin;
|
|
518
467
|
}
|
|
519
468
|
const head = path[0];
|
|
@@ -570,8 +519,10 @@ function fns( model, environment = artifactsEnv ) {
|
|
|
570
519
|
if (extDict && (!spec.dollar || head.id[0] !== '$')) {
|
|
571
520
|
const r = extDict[head.id];
|
|
572
521
|
if (Array.isArray(r)) {
|
|
573
|
-
if (r[0].kind === '$navElement') {
|
|
574
|
-
|
|
522
|
+
if (r[0].kind === '$navElement' && r.every( e => !e._parent.$duplicates )) {
|
|
523
|
+
// only complain about ambiguous source elements if we do not have
|
|
524
|
+
// duplicate table aliases, only mention non-ambiguous source elems
|
|
525
|
+
const names = r.filter( e => !e.$duplicates )
|
|
575
526
|
.map( e => `${ e.name.alias }.${ e.name.element }` );
|
|
576
527
|
if (names.length) {
|
|
577
528
|
error( 'ref-ambiguous', [ head.location, user ], { id: head.id, names },
|
|
@@ -658,7 +609,7 @@ function fns( model, environment = artifactsEnv ) {
|
|
|
658
609
|
continue;
|
|
659
610
|
}
|
|
660
611
|
|
|
661
|
-
const fn = (spec.envFn && artItemsCount >= 0) ? spec.envFn : environment;
|
|
612
|
+
const fn = (spec.envFn && artItemsCount >= 0) ? spec.envFn : VolatileFns.environment;
|
|
662
613
|
const env = fn( art, item.location, user, spec.assoc );
|
|
663
614
|
|
|
664
615
|
// do not check any elements of the path, e.g. $session - but still don't return path-head
|
|
@@ -847,7 +798,9 @@ function fns( model, environment = artifactsEnv ) {
|
|
|
847
798
|
}
|
|
848
799
|
// TODO: block should be construct._block
|
|
849
800
|
if (construct.$annotations && construct.$annotations.doc )
|
|
850
|
-
art.doc = construct.$annotations.doc;
|
|
801
|
+
art.doc = construct.$annotations.doc; // e.g. through `annotate` statement in CDL
|
|
802
|
+
else if (construct.doc)
|
|
803
|
+
art.doc = construct.doc; // e.g. through `extensions` array in CSN
|
|
851
804
|
if (!construct.$annotations) {
|
|
852
805
|
if (!block || block.$frontend !== 'json')
|
|
853
806
|
return; // namespace, or in CDL source without @annos:
|
|
@@ -923,132 +876,6 @@ function pathName(path) {
|
|
|
923
876
|
return (path.broken) ? '' : path.map( id => id.id ).join('.');
|
|
924
877
|
}
|
|
925
878
|
|
|
926
|
-
// The link (_artifact,_effectiveType,...) usually has the artifact as value.
|
|
927
|
-
// Falsy values are:
|
|
928
|
-
// - undefined: not computed yet, parse error, no ref
|
|
929
|
-
// - null: no valid reference, param:true if that is not allowed
|
|
930
|
-
// - false (only complete ref): multiple definitions, rejected
|
|
931
|
-
// - 0 (for _effectiveType only): circular reference
|
|
932
|
-
function setLink( obj, value = null, prop = '_artifact' ) {
|
|
933
|
-
Object.defineProperty( obj, prop, { value, configurable: true, writable: true } );
|
|
934
|
-
return value;
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
function linkToOrigin( origin, name, parent, prop, location, silentDep ) {
|
|
938
|
-
const elem = {
|
|
939
|
-
name: { location: location || origin.name.location, id: name },
|
|
940
|
-
kind: origin.kind,
|
|
941
|
-
location: location || origin.location,
|
|
942
|
-
};
|
|
943
|
-
if (origin.name.$inferred)
|
|
944
|
-
elem.name.$inferred = origin.name.$inferred;
|
|
945
|
-
if (parent)
|
|
946
|
-
setMemberParent( elem, name, parent, prop ); // TODO: redef in template
|
|
947
|
-
setProp( elem, '_origin', origin );
|
|
948
|
-
// TODO: should we use silent dependencies also for other things, like
|
|
949
|
-
// included elements? (Currently for $inferred: 'expand-element' only)
|
|
950
|
-
if (silentDep)
|
|
951
|
-
dependsOnSilent( elem, origin );
|
|
952
|
-
else
|
|
953
|
-
dependsOn( elem, origin, location );
|
|
954
|
-
return elem;
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
function setMemberParent( elem, name, parent, prop ) {
|
|
958
|
-
if (prop) { // extension or structure include
|
|
959
|
-
// TODO: consider nested ARRAY OF and RETURNS, COMPOSITION OF type
|
|
960
|
-
const p = parent.items || parent.targetAspect || parent;
|
|
961
|
-
if (!(prop in p))
|
|
962
|
-
p[prop] = Object.create(null);
|
|
963
|
-
dictAdd( p[prop], name, elem );
|
|
964
|
-
}
|
|
965
|
-
if (parent._outer)
|
|
966
|
-
parent = parent._outer;
|
|
967
|
-
setProp( elem, '_parent', parent );
|
|
968
|
-
setProp( elem, '_main', parent._main || parent );
|
|
969
|
-
elem.name.absolute = elem._main.name.absolute;
|
|
970
|
-
if (name == null)
|
|
971
|
-
return;
|
|
972
|
-
const normalized = kindProperties[elem.kind].normalized || elem.kind;
|
|
973
|
-
[ 'element', 'alias', 'select', 'param', 'action' ].forEach( ( kind ) => {
|
|
974
|
-
if (normalized === kind)
|
|
975
|
-
elem.name[kind] = (parent.name[kind] != null && kind !== 'select' && kind !== 'alias') ? `${ parent.name[kind] }.${ name }` : name;
|
|
976
|
-
|
|
977
|
-
else if (parent.name[kind] != null)
|
|
978
|
-
elem.name[kind] = parent.name[kind];
|
|
979
|
-
|
|
980
|
-
else
|
|
981
|
-
delete elem.name[kind];
|
|
982
|
-
});
|
|
983
|
-
// try { throw new Error('Foo') } catch (e) { elem.name.stack = e; };
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
/**
|
|
987
|
-
* Adds a dependency user -> art with the given location.
|
|
988
|
-
*
|
|
989
|
-
* @param {XSN.Artifact} user
|
|
990
|
-
* @param {XSN.Artifact} art
|
|
991
|
-
* @param {XSN.Location} location
|
|
992
|
-
*/
|
|
993
|
-
function dependsOn( user, art, location ) {
|
|
994
|
-
if (!user._deps)
|
|
995
|
-
setProp( user, '_deps', [] );
|
|
996
|
-
user._deps.push( { art, location } );
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
/**
|
|
1000
|
-
* Same as "dependsOn" but the dependency from user -> art is silent,
|
|
1001
|
-
* i.e. not reported to the user.
|
|
1002
|
-
*
|
|
1003
|
-
* @param {XSN.Artifact} user
|
|
1004
|
-
* @param {XSN.Artifact} art
|
|
1005
|
-
*/
|
|
1006
|
-
function dependsOnSilent( user, art ) {
|
|
1007
|
-
if (!user._deps)
|
|
1008
|
-
setProp( user, '_deps', [] );
|
|
1009
|
-
user._deps.push( { art } );
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
function storeExtension( elem, name, prop, parent, block ) {
|
|
1013
|
-
if (prop === 'enum')
|
|
1014
|
-
prop = 'elements';
|
|
1015
|
-
setProp( elem, '_block', block );
|
|
1016
|
-
const kind = `_${ elem.kind }`; // _extend or _annotate
|
|
1017
|
-
if (!parent[kind])
|
|
1018
|
-
setProp( parent, kind, {} );
|
|
1019
|
-
// if (name === '' && prop === 'params') {
|
|
1020
|
-
// pushToDict( parent[kind], 'returns', elem ); // not really a dict
|
|
1021
|
-
// return;
|
|
1022
|
-
// }
|
|
1023
|
-
if (!parent[kind][prop])
|
|
1024
|
-
parent[kind][prop] = Object.create(null);
|
|
1025
|
-
pushToDict( parent[kind][prop], name, elem );
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
/** @type {(a: any, b: any) => boolean} */
|
|
1029
|
-
const testFunctionPlaceholder = () => true;
|
|
1030
|
-
|
|
1031
|
-
// Return path step if the path navigates along an association whose final type
|
|
1032
|
-
// satisfies function `test`; "navigates along" = last path item not considered
|
|
1033
|
-
// without truthy optional argument `alsoTestLast`.
|
|
1034
|
-
function withAssociation( ref, test = testFunctionPlaceholder, alsoTestLast = false ) {
|
|
1035
|
-
for (const item of ref.path || []) {
|
|
1036
|
-
const art = item && item._artifact; // item can be null with parse error
|
|
1037
|
-
if (art && art._effectiveType && art._effectiveType.target && test( art._effectiveType, item ))
|
|
1038
|
-
return (alsoTestLast || item !== ref.path[ref.path.length - 1]) && item;
|
|
1039
|
-
}
|
|
1040
|
-
return false;
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
879
|
module.exports = {
|
|
1044
|
-
dictKinds,
|
|
1045
|
-
kindProperties,
|
|
1046
880
|
fns,
|
|
1047
|
-
setLink,
|
|
1048
|
-
linkToOrigin,
|
|
1049
|
-
dependsOn,
|
|
1050
|
-
dependsOnSilent,
|
|
1051
|
-
setMemberParent,
|
|
1052
|
-
storeExtension,
|
|
1053
|
-
withAssociation,
|
|
1054
881
|
};
|
package/lib/compiler/utils.js
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
|
+
const { dictAdd, pushToDict } = require('../base/dictionaries');
|
|
12
|
+
const { kindProperties } = require('./base');
|
|
13
|
+
|
|
11
14
|
// for links, i.e., properties starting with an underscore '_':
|
|
12
15
|
|
|
13
16
|
function pushLink( obj, prop, value ) {
|
|
@@ -38,10 +41,180 @@ function annotateWith( art, anno, location = art.location, val = true, literal =
|
|
|
38
41
|
};
|
|
39
42
|
}
|
|
40
43
|
|
|
44
|
+
// TODO: define setLink() like the current setProp(), we might have setArtifactLink()
|
|
45
|
+
// Do not share this function with CSN processors!
|
|
46
|
+
|
|
47
|
+
// The link (_artifact,_effectiveType,...) usually has the artifact as value.
|
|
48
|
+
// Falsy values are:
|
|
49
|
+
// - undefined: not computed yet, parse error, no ref
|
|
50
|
+
// - null: no valid reference, param:true if that is not allowed
|
|
51
|
+
// - false (only complete ref): multiple definitions, rejected
|
|
52
|
+
// - 0 (for _effectiveType only): circular reference
|
|
53
|
+
function setLink( obj, value = null, prop = '_artifact' ) {
|
|
54
|
+
Object.defineProperty( obj, prop, { value, configurable: true, writable: true } );
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Like `obj.prop = value`, but not contained in JSON / CSN
|
|
60
|
+
* It's important to set enumerable explicitly to false (although 'false' is the default),
|
|
61
|
+
* as else, if the property already exists, it keeps the old setting for enumerable.
|
|
62
|
+
*
|
|
63
|
+
* @param {object} obj
|
|
64
|
+
* @param {string} prop
|
|
65
|
+
* @param {any} value
|
|
66
|
+
*/
|
|
67
|
+
function setProp(obj, prop, value) {
|
|
68
|
+
const descriptor = {
|
|
69
|
+
value,
|
|
70
|
+
configurable: true,
|
|
71
|
+
writable: true,
|
|
72
|
+
enumerable: false,
|
|
73
|
+
};
|
|
74
|
+
Object.defineProperty( obj, prop, descriptor );
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function linkToOrigin( origin, name, parent, prop, location, silentDep ) {
|
|
79
|
+
const elem = {
|
|
80
|
+
name: { location: location || origin.name.location, id: name },
|
|
81
|
+
kind: origin.kind,
|
|
82
|
+
location: location || origin.location,
|
|
83
|
+
};
|
|
84
|
+
if (origin.name.$inferred)
|
|
85
|
+
elem.name.$inferred = origin.name.$inferred;
|
|
86
|
+
if (parent)
|
|
87
|
+
setMemberParent( elem, name, parent, prop ); // TODO: redef in template
|
|
88
|
+
setProp( elem, '_origin', origin );
|
|
89
|
+
// TODO: should we use silent dependencies also for other things, like
|
|
90
|
+
// included elements? (Currently for $inferred: 'expand-element' only)
|
|
91
|
+
if (silentDep)
|
|
92
|
+
dependsOnSilent( elem, origin );
|
|
93
|
+
else
|
|
94
|
+
dependsOn( elem, origin, location );
|
|
95
|
+
return elem;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function setMemberParent( elem, name, parent, prop ) {
|
|
99
|
+
if (prop) { // extension or structure include
|
|
100
|
+
// TODO: consider nested ARRAY OF and RETURNS, COMPOSITION OF type
|
|
101
|
+
const p = parent.items || parent.targetAspect || parent;
|
|
102
|
+
if (!(prop in p))
|
|
103
|
+
p[prop] = Object.create(null);
|
|
104
|
+
dictAdd( p[prop], name, elem );
|
|
105
|
+
}
|
|
106
|
+
if (parent._outer)
|
|
107
|
+
parent = parent._outer;
|
|
108
|
+
setProp( elem, '_parent', parent );
|
|
109
|
+
setProp( elem, '_main', parent._main || parent );
|
|
110
|
+
elem.name.absolute = elem._main.name.absolute;
|
|
111
|
+
if (name == null)
|
|
112
|
+
return;
|
|
113
|
+
const normalized = kindProperties[elem.kind].normalized || elem.kind;
|
|
114
|
+
[ 'element', 'alias', 'select', 'param', 'action' ].forEach( ( kind ) => {
|
|
115
|
+
if (normalized === kind)
|
|
116
|
+
elem.name[kind] = (parent.name[kind] != null && kind !== 'select' && kind !== 'alias') ? `${ parent.name[kind] }.${ name }` : name;
|
|
117
|
+
|
|
118
|
+
else if (parent.name[kind] != null)
|
|
119
|
+
elem.name[kind] = parent.name[kind];
|
|
120
|
+
|
|
121
|
+
else
|
|
122
|
+
delete elem.name[kind];
|
|
123
|
+
});
|
|
124
|
+
// try { throw new Error('Foo') } catch (e) { elem.name.stack = e; };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Adds a dependency user -> art with the given location.
|
|
129
|
+
*
|
|
130
|
+
* @param {XSN.Artifact} user
|
|
131
|
+
* @param {XSN.Artifact} art
|
|
132
|
+
* @param {XSN.Location} location
|
|
133
|
+
*/
|
|
134
|
+
function dependsOn( user, art, location ) {
|
|
135
|
+
if (!user._deps)
|
|
136
|
+
setProp( user, '_deps', [] );
|
|
137
|
+
user._deps.push( { art, location } );
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Same as "dependsOn" but the dependency from user -> art is silent,
|
|
142
|
+
* i.e. not reported to the user.
|
|
143
|
+
*
|
|
144
|
+
* @param {XSN.Artifact} user
|
|
145
|
+
* @param {XSN.Artifact} art
|
|
146
|
+
*/
|
|
147
|
+
function dependsOnSilent( user, art ) {
|
|
148
|
+
if (!user._deps)
|
|
149
|
+
setProp( user, '_deps', [] );
|
|
150
|
+
user._deps.push( { art } );
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function storeExtension( elem, name, prop, parent, block ) {
|
|
154
|
+
if (prop === 'enum')
|
|
155
|
+
prop = 'elements';
|
|
156
|
+
setProp( elem, '_block', block );
|
|
157
|
+
const kind = `_${ elem.kind }`; // _extend or _annotate
|
|
158
|
+
if (!parent[kind])
|
|
159
|
+
setProp( parent, kind, {} );
|
|
160
|
+
// if (name === '' && prop === 'params') {
|
|
161
|
+
// pushToDict( parent[kind], 'returns', elem ); // not really a dict
|
|
162
|
+
// return;
|
|
163
|
+
// }
|
|
164
|
+
if (!parent[kind][prop])
|
|
165
|
+
parent[kind][prop] = Object.create(null);
|
|
166
|
+
pushToDict( parent[kind][prop], name, elem );
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** @type {(a: any, b: any) => boolean} */
|
|
170
|
+
const testFunctionPlaceholder = () => true;
|
|
171
|
+
|
|
172
|
+
// Return path step if the path navigates along an association whose final type
|
|
173
|
+
// satisfies function `test`; "navigates along" = last path item not considered
|
|
174
|
+
// without truthy optional argument `alsoTestLast`.
|
|
175
|
+
function withAssociation( ref, test = testFunctionPlaceholder, alsoTestLast = false ) {
|
|
176
|
+
for (const item of ref.path || []) {
|
|
177
|
+
const art = item && item._artifact; // item can be null with parse error
|
|
178
|
+
if (art && art._effectiveType && art._effectiveType.target && test( art._effectiveType, item ))
|
|
179
|
+
return (alsoTestLast || item !== ref.path[ref.path.length - 1]) && item;
|
|
180
|
+
}
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Generates an XSN path out of the given name. Path segments are delimited by a dot.
|
|
186
|
+
* Each segment will have the given location assigned.
|
|
187
|
+
*
|
|
188
|
+
* @param {CSN.Location} location
|
|
189
|
+
* @param {string} name
|
|
190
|
+
* @returns {XSN.Path}
|
|
191
|
+
*/
|
|
192
|
+
function splitIntoPath( location, name ) {
|
|
193
|
+
return name.split('.').map( id => ({ id, location }) );
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @param {CSN.Location} location
|
|
198
|
+
* @param {...any} args
|
|
199
|
+
*/
|
|
200
|
+
function augmentPath( location, ...args ) {
|
|
201
|
+
return { path: args.map( id => ({ id, location }) ), location };
|
|
202
|
+
}
|
|
203
|
+
|
|
41
204
|
|
|
42
205
|
module.exports = {
|
|
43
206
|
pushLink,
|
|
44
207
|
annotationVal,
|
|
45
208
|
annotationIsFalse,
|
|
46
209
|
annotateWith,
|
|
210
|
+
setLink,
|
|
211
|
+
setProp,
|
|
212
|
+
linkToOrigin,
|
|
213
|
+
dependsOn,
|
|
214
|
+
dependsOnSilent,
|
|
215
|
+
setMemberParent,
|
|
216
|
+
storeExtension,
|
|
217
|
+
withAssociation,
|
|
218
|
+
augmentPath,
|
|
219
|
+
splitIntoPath,
|
|
47
220
|
};
|
|
@@ -1119,7 +1119,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
|
|
|
1119
1119
|
let dictPropertyTypeName = null;
|
|
1120
1120
|
if (dictProperties) {
|
|
1121
1121
|
dictPropertyTypeName = dictProperties[i];
|
|
1122
|
-
if (!dictPropertyTypeName){
|
|
1122
|
+
if (!dictPropertyTypeName && !getDictType(actualTypeName).OpenType){
|
|
1123
1123
|
message(warning, context, `record type '${ actualTypeName }' doesn't have a property '${ i }'`);
|
|
1124
1124
|
}
|
|
1125
1125
|
}
|
package/lib/edm/csn2edm.js
CHANGED
|
@@ -142,7 +142,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
142
142
|
definitions: Object.create(null)
|
|
143
143
|
}
|
|
144
144
|
};
|
|
145
|
-
|
|
145
|
+
|
|
146
146
|
if(options.isV4()) {
|
|
147
147
|
// tunnel schema xref and servicename in options to edm.Typebase to rectify
|
|
148
148
|
// type references that are eventually also prefixed with the service schema name.
|
|
@@ -304,6 +304,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
304
304
|
warning('odata-spec-violation-namespace',
|
|
305
305
|
[ 'definitions', schema.name ], { names: reservedNames });
|
|
306
306
|
}
|
|
307
|
+
/** @type {any} */
|
|
307
308
|
const Schema = new Edm.Schema(v, schema.name, undefined /* unset alias */, schema._csn, /* annotations */ [], schema.container);
|
|
308
309
|
const EntityContainer = Schema._ec || (LeadSchema && LeadSchema._ec);
|
|
309
310
|
// now namespace and alias are used to create the fullQualified(name)
|
|
@@ -390,7 +391,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
390
391
|
if(properties.length === 0) {
|
|
391
392
|
warning(null, loc, { type }, 'EDM EntityType $(TYPE) has no properties');
|
|
392
393
|
} else if(entityCsn.$edmKeyPaths.length === 0) {
|
|
393
|
-
|
|
394
|
+
message('odata-spec-violation-no-key', loc);
|
|
394
395
|
}
|
|
395
396
|
properties.forEach(p => {
|
|
396
397
|
const pLoc = [...loc, 'elements', p.Name];
|
|
@@ -54,7 +54,7 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
54
54
|
let options = validateOptions(_options);
|
|
55
55
|
|
|
56
56
|
// Fetch service definitions
|
|
57
|
-
const serviceRoots = Object.keys(csn.definitions).reduce((serviceRoots, artName) => {
|
|
57
|
+
const serviceRoots = Object.keys(csn.definitions || {}).reduce((serviceRoots, artName) => {
|
|
58
58
|
const art = csn.definitions[artName];
|
|
59
59
|
if(art.kind === 'service') {
|
|
60
60
|
serviceRoots[artName] = Object.assign(art, { name: artName });
|
|
@@ -67,6 +67,9 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
67
67
|
function whatsMyServiceRootName(n, self=true) {
|
|
68
68
|
return serviceRootNames.reduce((rc, sn) => !rc && n && n.startsWith(sn + '.') || (n === sn && self) ? sn : rc, undefined);
|
|
69
69
|
}
|
|
70
|
+
if(serviceRootNames.length === 0) {
|
|
71
|
+
return [serviceRoots, Object.create(null), whatsMyServiceRootName, options];
|
|
72
|
+
}
|
|
70
73
|
|
|
71
74
|
// Structural CSN inbound QA checks
|
|
72
75
|
inboundQualificationChecks();
|
|
@@ -594,6 +597,16 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
594
597
|
if(element['@cds.valid.from']) {
|
|
595
598
|
validFrom.push(element);
|
|
596
599
|
}
|
|
600
|
+
//forward annotations from managed association element to its foreign keys
|
|
601
|
+
const elements = construct.items && construct.items.elements || construct.elements;
|
|
602
|
+
forAll(elements[element['@odata.foreignKey4']], (attr, attrName) => {
|
|
603
|
+
if(attrName[0] === '@') {
|
|
604
|
+
element[attrName] = attr;
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
// and eventually remove some afterwards:)
|
|
608
|
+
if(options.isV2())
|
|
609
|
+
setSAPSpecificV2AnnotationsToAssociation(element);
|
|
597
610
|
|
|
598
611
|
// initialize an association
|
|
599
612
|
if(isAssociationOrComposition(element)) {
|
|
@@ -602,20 +615,6 @@ function initializeModel(csn, _options, messageFunctions)
|
|
|
602
615
|
assignProp(element._target, '$proxies', []);
|
|
603
616
|
// $abspath is used as partner path
|
|
604
617
|
assignProp(element, '$abspath', $path2path(element.$path));
|
|
605
|
-
|
|
606
|
-
//forward annotations from managed association element to its foreign keys
|
|
607
|
-
if(element.keys && options.isFlatFormat) {
|
|
608
|
-
const elements = construct.items && construct.items.elements || construct.elements;
|
|
609
|
-
for(let fk of element.keys) {
|
|
610
|
-
forAll(element, (attr, attrName) => {
|
|
611
|
-
if(attrName[0] === '@' && fk.$generatedFieldName && elements && elements[fk.$generatedFieldName]) {
|
|
612
|
-
elements[fk.$generatedFieldName][attrName] = attr;
|
|
613
|
-
}
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
// and afterwards eventually remove some :)
|
|
618
|
-
setSAPSpecificV2AnnotationsToAssociation(options, element, def);
|
|
619
618
|
}
|
|
620
619
|
|
|
621
620
|
// Collect keys
|
|
@@ -2159,19 +2158,17 @@ function setSAPSpecificV2AnnotationsToEntitySet(options, carrier) {
|
|
|
2159
2158
|
}
|
|
2160
2159
|
}
|
|
2161
2160
|
|
|
2162
|
-
function setSAPSpecificV2AnnotationsToAssociation(
|
|
2163
|
-
if(!options.isV2())
|
|
2164
|
-
return;
|
|
2161
|
+
function setSAPSpecificV2AnnotationsToAssociation(carrier) {
|
|
2165
2162
|
// documented in https://wiki.scn.sap.com/wiki/display/EmTech/SAP+Annotations+for+OData+Version+2.0
|
|
2166
2163
|
const SetAttributes = {
|
|
2167
2164
|
// Applicable to NavProp and foreign keys, add to AssociationSet
|
|
2168
|
-
'@sap.creatable' : (
|
|
2165
|
+
'@sap.creatable' : (c, pn, pv) => { addToAssociationSet(c, pn, pv, false); },
|
|
2169
2166
|
// Not applicable to NavProp, applicable to foreign keys, add to AssociationSet
|
|
2170
|
-
'@sap.updatable' :
|
|
2167
|
+
'@sap.updatable' : addToAssociationSet,
|
|
2171
2168
|
// Not applicable to NavProp, not applicable to foreign key, add to AssociationSet
|
|
2172
|
-
'@sap.deletable': (
|
|
2173
|
-
|
|
2174
|
-
removeFromForeignKey(
|
|
2169
|
+
'@sap.deletable': (c, pn, pv) => {
|
|
2170
|
+
addToAssociationSet(c, pn, pv);
|
|
2171
|
+
removeFromForeignKey(c, pn);
|
|
2175
2172
|
},
|
|
2176
2173
|
// applicable to NavProp, not applicable to foreign keys, not applicable to AssociationSet
|
|
2177
2174
|
'@sap.creatable.path': removeFromForeignKey,
|
|
@@ -2179,24 +2176,22 @@ function setSAPSpecificV2AnnotationsToAssociation(options, carrier, struct) {
|
|
|
2179
2176
|
};
|
|
2180
2177
|
|
|
2181
2178
|
Object.entries(carrier).forEach(([p, v]) => {
|
|
2182
|
-
(SetAttributes[p] || function() {/* no-op */})(
|
|
2179
|
+
(SetAttributes[p] || function() {/* no-op */})(carrier, p, v);
|
|
2183
2180
|
});
|
|
2184
2181
|
|
|
2185
|
-
function
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2182
|
+
function addToAssociationSet(carrier, propName, propValue, removeFromType=true) {
|
|
2183
|
+
if(isAssociationOrComposition(carrier)) {
|
|
2184
|
+
assignProp(carrier, '_SetAttributes', Object.create(null));
|
|
2185
|
+
assignAnnotation(carrier._SetAttributes, propName, propValue);
|
|
2186
|
+
if(removeFromType) {
|
|
2187
|
+
delete carrier[propName];
|
|
2188
|
+
}
|
|
2190
2189
|
}
|
|
2191
2190
|
}
|
|
2192
2191
|
|
|
2193
|
-
function removeFromForeignKey(
|
|
2194
|
-
if(carrier.
|
|
2195
|
-
|
|
2196
|
-
if(e['@odata.foreignKey4'] === carrier.name) {
|
|
2197
|
-
delete e[propName];
|
|
2198
|
-
}
|
|
2199
|
-
});
|
|
2192
|
+
function removeFromForeignKey(carrier, propName) {
|
|
2193
|
+
if(carrier['@odata.foreignKey4'] && carrier[propName] !== undefined) {
|
|
2194
|
+
delete carrier[propName];
|
|
2200
2195
|
}
|
|
2201
2196
|
}
|
|
2202
2197
|
}
|