@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.
Files changed (70) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/bin/cdsc.js +42 -25
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +4 -0
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +9 -23
  7. package/lib/api/options.js +12 -4
  8. package/lib/api/validate.js +23 -2
  9. package/lib/backends.js +9 -8
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/message-registry.js +10 -2
  12. package/lib/base/messages.js +23 -9
  13. package/lib/base/model.js +5 -4
  14. package/lib/base/optionProcessorHelper.js +56 -22
  15. package/lib/checks/selectItems.js +4 -0
  16. package/lib/checks/unknownMagic.js +6 -3
  17. package/lib/compiler/assert-consistency.js +7 -0
  18. package/lib/compiler/base.js +65 -0
  19. package/lib/compiler/builtins.js +28 -1
  20. package/lib/compiler/checks.js +2 -1
  21. package/lib/compiler/definer.js +58 -91
  22. package/lib/compiler/index.js +16 -4
  23. package/lib/compiler/propagator.js +5 -2
  24. package/lib/compiler/resolver.js +93 -34
  25. package/lib/compiler/shared.js +29 -202
  26. package/lib/compiler/utils.js +173 -0
  27. package/lib/edm/annotations/genericTranslation.js +1 -1
  28. package/lib/edm/csn2edm.js +3 -2
  29. package/lib/edm/edmPreprocessor.js +31 -36
  30. package/lib/edm/edmUtils.js +3 -3
  31. package/lib/gen/language.checksum +1 -1
  32. package/lib/gen/language.interp +17 -1
  33. package/lib/gen/language.tokens +79 -73
  34. package/lib/gen/languageLexer.interp +19 -1
  35. package/lib/gen/languageLexer.js +779 -731
  36. package/lib/gen/languageLexer.tokens +71 -65
  37. package/lib/gen/languageParser.js +4668 -4072
  38. package/lib/json/from-csn.js +10 -10
  39. package/lib/json/to-csn.js +169 -34
  40. package/lib/language/antlrParser.js +11 -0
  41. package/lib/language/genericAntlrParser.js +72 -14
  42. package/lib/language/language.g4 +73 -0
  43. package/lib/main.d.ts +136 -17
  44. package/lib/main.js +3 -1
  45. package/lib/model/api.js +2 -2
  46. package/lib/model/csnRefs.js +108 -31
  47. package/lib/model/csnUtils.js +63 -29
  48. package/lib/model/enrichCsn.js +36 -9
  49. package/lib/model/revealInternalProperties.js +20 -4
  50. package/lib/modelCompare/compare.js +2 -1
  51. package/lib/optionProcessor.js +29 -18
  52. package/lib/render/DuplicateChecker.js +1 -1
  53. package/lib/render/toCdl.js +9 -3
  54. package/lib/render/toHdbcds.js +16 -36
  55. package/lib/render/toSql.js +23 -5
  56. package/lib/transform/db/constraints.js +278 -119
  57. package/lib/transform/db/draft.js +3 -2
  58. package/lib/transform/db/expansion.js +6 -4
  59. package/lib/transform/db/flattening.js +17 -1
  60. package/lib/transform/db/transformExists.js +61 -2
  61. package/lib/transform/db/views.js +438 -0
  62. package/lib/transform/forHanaNew.js +56 -435
  63. package/lib/transform/forOdataNew.js +9 -2
  64. package/lib/transform/localized.js +2 -0
  65. package/lib/transform/transformUtilsNew.js +10 -0
  66. package/lib/transform/translateAssocsToJoins.js +5 -13
  67. package/lib/utils/file.js +5 -3
  68. package/lib/utils/term.js +65 -42
  69. package/lib/utils/timetrace.js +48 -26
  70. package/package.json +1 -1
@@ -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 { dictAdd, dictAddArray, pushToDict } = require('../base/dictionaries');
7
+ const { dictAddArray } = require('../base/dictionaries');
8
8
  const { setProp } = require('../base/model');
9
9
 
10
- const dictKinds = {
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. Return "resolve" functions shared for phase
72
- * "define" and "resolve". Argument `model` is the augmented CSN. Optional
73
- * argument `environment` is a function which returns the search environment
74
- * defined by its argument - it defaults to the dictionary of subartifacts of
75
- * the argument.
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
- function fns( model, environment = artifactsEnv ) {
27
+ // TODO: yes, this function will be renamed
28
+ function fns( model ) {
82
29
  /** @type {CSN.Options} */
83
- const options = model.options || {};
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
- return {
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
- const names = r.filter( e => !e.$duplicates)
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
  };
@@ -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
  }
@@ -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
- warning(null, loc, { type }, 'EDM EntityType $(TYPE) has no primary key');
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(options, carrier, struct) {
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' : (struct, c,pn, pv) => { addToSetAttr(struct, c, pn, pv, false); },
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' : addToSetAttr,
2167
+ '@sap.updatable' : addToAssociationSet,
2171
2168
  // Not applicable to NavProp, not applicable to foreign key, add to AssociationSet
2172
- '@sap.deletable': (struct, c, pn, pv) => {
2173
- addToSetAttr(struct, c, pn, pv);
2174
- removeFromForeignKey(struct, c, pn);
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 */})(struct, carrier, p, v);
2179
+ (SetAttributes[p] || function() {/* no-op */})(carrier, p, v);
2183
2180
  });
2184
2181
 
2185
- function addToSetAttr(struct, carrier, propName, propValue, removeFromType=true) {
2186
- assignProp(carrier, '_SetAttributes', Object.create(null));
2187
- assignAnnotation(carrier._SetAttributes, propName, propValue);
2188
- if(removeFromType) {
2189
- delete carrier[propName];
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(struct, carrier, propName) {
2194
- if(carrier.target && carrier.keys) {
2195
- struct.elements && Object.values(struct.elements).forEach(e => {
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
  }