@sap/cds-compiler 2.12.0 → 2.15.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.
Files changed (128) hide show
  1. package/CHANGELOG.md +221 -15
  2. package/bin/cdsc.js +125 -50
  3. package/bin/cdsse.js +2 -2
  4. package/doc/CHANGELOG_BETA.md +13 -6
  5. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  6. package/doc/NameResolution.md +21 -16
  7. package/lib/api/main.js +47 -84
  8. package/lib/api/options.js +5 -6
  9. package/lib/api/validate.js +6 -11
  10. package/lib/backends.js +15 -23
  11. package/lib/base/dictionaries.js +0 -8
  12. package/lib/base/error.js +26 -0
  13. package/lib/base/keywords.js +7 -17
  14. package/lib/base/location.js +9 -4
  15. package/lib/base/message-registry.js +114 -18
  16. package/lib/base/messages.js +101 -90
  17. package/lib/base/model.js +2 -63
  18. package/lib/base/optionProcessorHelper.js +177 -123
  19. package/lib/checks/annotationsOData.js +12 -33
  20. package/lib/checks/arrayOfs.js +1 -34
  21. package/lib/checks/cdsPersistence.js +2 -1
  22. package/lib/checks/enricher.js +17 -1
  23. package/lib/checks/invalidTarget.js +3 -1
  24. package/lib/checks/managedWithoutKeys.js +3 -1
  25. package/lib/checks/selectItems.js +4 -4
  26. package/lib/checks/sql-snippets.js +27 -26
  27. package/lib/checks/types.js +1 -1
  28. package/lib/checks/validator.js +6 -11
  29. package/lib/compiler/assert-consistency.js +6 -3
  30. package/lib/compiler/base.js +1 -0
  31. package/lib/compiler/builtins.js +19 -6
  32. package/lib/compiler/checks.js +23 -60
  33. package/lib/compiler/cycle-detector.js +1 -1
  34. package/lib/compiler/define.js +1151 -0
  35. package/lib/compiler/extend.js +1000 -0
  36. package/lib/compiler/finalize-parse-cdl.js +237 -0
  37. package/lib/compiler/index.js +107 -39
  38. package/lib/compiler/kick-start.js +190 -0
  39. package/lib/compiler/moduleLayers.js +4 -4
  40. package/lib/compiler/populate.js +1227 -0
  41. package/lib/compiler/propagator.js +114 -46
  42. package/lib/compiler/resolve.js +1521 -0
  43. package/lib/compiler/shared.js +126 -65
  44. package/lib/compiler/tweak-assocs.js +535 -0
  45. package/lib/compiler/utils.js +197 -33
  46. package/lib/edm/.eslintrc.json +5 -0
  47. package/lib/edm/annotations/genericTranslation.js +38 -24
  48. package/lib/edm/annotations/preprocessAnnotations.js +2 -2
  49. package/lib/edm/csn2edm.js +219 -100
  50. package/lib/edm/edm.js +302 -230
  51. package/lib/edm/edmPreprocessor.js +554 -419
  52. package/lib/edm/edmUtils.js +138 -44
  53. package/lib/gen/Dictionary.json +100 -19
  54. package/lib/gen/language.checksum +1 -1
  55. package/lib/gen/language.interp +11 -1
  56. package/lib/gen/language.tokens +86 -83
  57. package/lib/gen/languageLexer.interp +10 -1
  58. package/lib/gen/languageLexer.js +860 -833
  59. package/lib/gen/languageLexer.tokens +78 -75
  60. package/lib/gen/languageParser.js +5765 -4480
  61. package/lib/json/csnVersion.js +10 -11
  62. package/lib/json/from-csn.js +15 -3
  63. package/lib/json/to-csn.js +126 -68
  64. package/lib/language/docCommentParser.js +4 -4
  65. package/lib/language/genericAntlrParser.js +123 -5
  66. package/lib/language/language.g4 +355 -156
  67. package/lib/language/multiLineStringParser.js +5 -5
  68. package/lib/main.d.ts +486 -59
  69. package/lib/main.js +41 -9
  70. package/lib/model/api.js +3 -1
  71. package/lib/model/csnRefs.js +252 -156
  72. package/lib/model/csnUtils.js +384 -297
  73. package/lib/model/enrichCsn.js +71 -29
  74. package/lib/model/revealInternalProperties.js +29 -8
  75. package/lib/model/sortViews.js +2 -1
  76. package/lib/modelCompare/compare.js +23 -18
  77. package/lib/optionProcessor.js +63 -26
  78. package/lib/render/manageConstraints.js +35 -32
  79. package/lib/render/toCdl.js +897 -947
  80. package/lib/render/toHdbcds.js +205 -257
  81. package/lib/render/toSql.js +264 -225
  82. package/lib/render/utils/common.js +136 -25
  83. package/lib/render/utils/sql.js +4 -3
  84. package/lib/render/utils/stringEscapes.js +111 -0
  85. package/lib/sql-identifier.js +1 -1
  86. package/lib/transform/.eslintrc.json +5 -0
  87. package/lib/transform/db/.eslintrc.json +3 -1
  88. package/lib/transform/db/applyTransformations.js +35 -12
  89. package/lib/transform/db/assertUnique.js +1 -1
  90. package/lib/transform/db/associations.js +104 -306
  91. package/lib/transform/db/cdsPersistence.js +2 -2
  92. package/lib/transform/db/constraints.js +58 -53
  93. package/lib/transform/db/expansion.js +60 -33
  94. package/lib/transform/db/flattening.js +582 -104
  95. package/lib/transform/db/groupByOrderBy.js +3 -1
  96. package/lib/transform/db/transformExists.js +66 -13
  97. package/lib/transform/db/views.js +11 -7
  98. package/lib/transform/draft/.eslintrc.json +38 -0
  99. package/lib/transform/{db/draft.js → draft/db.js} +6 -5
  100. package/lib/transform/draft/odata.js +227 -0
  101. package/lib/transform/forHanaNew.js +109 -208
  102. package/lib/transform/forOdataNew.js +59 -212
  103. package/lib/transform/localized.js +46 -26
  104. package/lib/transform/odata/toFinalBaseType.js +85 -11
  105. package/lib/transform/odata/typesExposure.js +147 -199
  106. package/lib/transform/odata/utils.js +2 -2
  107. package/lib/transform/transformUtilsNew.js +44 -33
  108. package/lib/transform/translateAssocsToJoins.js +3 -20
  109. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  110. package/lib/transform/universalCsn/coreComputed.js +172 -0
  111. package/lib/transform/universalCsn/universalCsnEnricher.js +737 -0
  112. package/lib/transform/universalCsn/utils.js +63 -0
  113. package/lib/utils/moduleResolve.js +13 -6
  114. package/lib/utils/objectUtils.js +30 -0
  115. package/package.json +1 -1
  116. package/share/messages/README.md +26 -0
  117. package/share/messages/message-explanations.json +2 -1
  118. package/share/messages/syntax-expected-integer.md +37 -0
  119. package/lib/compiler/definer.js +0 -2361
  120. package/lib/compiler/resolver.js +0 -3079
  121. package/lib/transform/odata/attachPath.js +0 -96
  122. package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
  123. package/lib/transform/odata/generateForeignKeyElements.js +0 -261
  124. package/lib/transform/odata/referenceFlattener.js +0 -290
  125. package/lib/transform/odata/sortByAssociationDependency.js +0 -105
  126. package/lib/transform/odata/structuralPath.js +0 -72
  127. package/lib/transform/odata/structureFlattener.js +0 -171
  128. package/lib/transform/universalCsnEnricher.js +0 -237
@@ -1,9 +1,10 @@
1
1
  'use strict';
2
2
 
3
- const { csnRefs } = require('../model/csnRefs');
3
+ const { csnRefs, implicitAs } = require('../model/csnRefs');
4
4
  const { applyTransformations, applyTransformationsOnNonDictionary } = require('../transform/db/applyTransformations');
5
5
  const { isBuiltinType } = require('../compiler/builtins.js')
6
6
  const { sortCsn, cloneCsnDictionary: _cloneCsnDictionary } = require('../json/to-csn');
7
+ const { ModelError } = require("../base/error");
7
8
  const version = require('../../package.json').version;
8
9
 
9
10
  // Low-level utility functions to work with compact CSN.
@@ -36,15 +37,15 @@ const version = require('../../package.json').version;
36
37
  * Get utility functions for a given CSN.
37
38
  * @param {CSN.Model} model (Compact) CSN model
38
39
  */
39
- function getUtils(model) {
40
- const { artifactRef, inspectRef, effectiveType, getOrigin } = csnRefs(model);
40
+ function getUtils(model, universalReady) {
41
+ const { artifactRef, inspectRef, effectiveType, getOrigin, targetAspect, getColumn, getElement, initDefinition } = csnRefs(model, universalReady);
41
42
 
42
43
  return {
43
44
  getCsnDef,
44
45
  isStructured,
45
46
  getFinalType,
46
47
  getFinalTypeDef,
47
- isManagedAssociationElement,
48
+ isManagedAssociation,
48
49
  isAssocOrComposition,
49
50
  isAssociation,
50
51
  isComposition,
@@ -62,6 +63,10 @@ function getUtils(model) {
62
63
  get$combined,
63
64
  getOrigin,
64
65
  getQueryPrimarySource,
66
+ targetAspect,
67
+ getColumn,
68
+ getElement,
69
+ initDefinition
65
70
  };
66
71
 
67
72
  /**
@@ -71,125 +76,125 @@ function getUtils(model) {
71
76
  * @returns {object}
72
77
  */
73
78
  function get$combined(query) {
74
- const sources = getSources(query);
75
- return sources;
79
+ return getSources(query);
80
+ }
76
81
 
77
- /**
78
- * Get the union of all elements from the from clause
79
- * - descend into unions, following the lead query
80
- * - merge all queries in case of joins
81
- * - follow subqueries
82
- *
83
- * @param {CSN.Query} query Query to check
84
- * @returns {object} Map of sources
85
- */
86
- function getSources(query, isSubquery=false) {
87
- // Remark CW: better just a while along query.SET.args[0]
88
- if (query.SET) {
89
- if (query.SET.args[0].SELECT && query.SET.args[0].SELECT.elements)
90
- return mergeElementsIntoMap(Object.create(null), query.SET.args[0].SELECT.elements, query.SET.args[0].$location);
82
+ /**
83
+ * Get the union of all elements from the from clause
84
+ * - descend into unions, following the lead query
85
+ * - merge all queries in case of joins
86
+ * - follow subqueries
87
+ *
88
+ * @param {CSN.Query} query Query to check
89
+ * @param {boolean} [isSubquery]
90
+ * @returns {object} Map of sources
91
+ */
92
+ function getSources(query, isSubquery=false) {
93
+ // Remark CW: better just a while along query.SET.args[0]
94
+ if (query.SET) {
95
+ if (query.SET.args[0].SELECT && query.SET.args[0].SELECT.elements)
96
+ return mergeElementsIntoMap(Object.create(null), query.SET.args[0].SELECT.elements, query.SET.args[0].$location);
91
97
 
92
- return getSources(query.SET.args[0], isSubquery);
98
+ return getSources(query.SET.args[0], isSubquery);
99
+ }
100
+ else if (query.SELECT) {
101
+ if (query.SELECT.from.args) {
102
+ return walkArgs(query.SELECT.from.args);
93
103
  }
94
- else if (query.SELECT) {
95
- if (query.SELECT.from.args) {
96
- return walkArgs(query.SELECT.from.args);
97
- }
98
- else if (query.SELECT.from.ref) {
99
- let art = artifactRef(query.SELECT.from);
104
+ else if (query.SELECT.from.ref) {
105
+ let art = artifactRef(query.SELECT.from);
100
106
 
101
- if(art.target)
102
- art = artifactRef(art.target);
107
+ if(art.target)
108
+ art = artifactRef(art.target);
103
109
 
104
- if(isSubquery && !query.SELECT.elements)
105
- throw new Error('Expected subquery to have .elements');
110
+ if(isSubquery && !query.SELECT.elements)
111
+ throw new ModelError('Expected subquery to have .elements');
106
112
 
107
- return mergeElementsIntoMap(Object.create(null), isSubquery ? query.SELECT.elements : art.elements, art.$location,
108
- query.SELECT.from.as || query.SELECT.from.ref[query.SELECT.from.ref.length - 1],
109
- query.SELECT.from.ref[query.SELECT.from.ref.length - 1] || query.SELECT.from.as );
110
- }
111
- else if (query.SELECT.from.SET || query.SELECT.from.SELECT) {
112
- return getSources(query.SELECT.from, true);
113
- }
113
+ return mergeElementsIntoMap(Object.create(null), isSubquery ? query.SELECT.elements : art.elements, art.$location,
114
+ query.SELECT.from.as || query.SELECT.from.ref[query.SELECT.from.ref.length - 1],
115
+ query.SELECT.from.ref[query.SELECT.from.ref.length - 1] || query.SELECT.from.as );
114
116
  }
115
-
116
- function walkArgs(args) {
117
- let elements = Object.create(null);
118
- for (const arg of args) {
119
- if (arg.args) {
120
- elements = mergeElementMaps(elements, walkArgs(arg.args));
121
- }
122
- else if (arg.ref) {
123
- const art = artifactRef(arg);
124
- elements = mergeElementsIntoMap(elements, art.elements, art.$location, arg.as || arg.ref[arg.ref.length - 1], arg.ref[arg.ref.length - 1] || arg.as);
125
- }
126
- else if (arg.SELECT || arg.SET) {
127
- elements = mergeElementMaps(elements, getSources(arg));
128
- }
129
- }
130
-
131
- return elements;
117
+ else if (query.SELECT.from.SET || query.SELECT.from.SELECT) {
118
+ return getSources(query.SELECT.from, true);
132
119
  }
120
+ }
133
121
 
134
- return {};
135
-
136
- /**
137
- * Merge two maps of elements together
138
- *
139
- * @param {object} mapA Map a - will be returned
140
- * @param {object} mapB Map b - will not be returned
141
- * @returns {object} mapA
142
- */
143
- function mergeElementMaps(mapA, mapB) {
144
- for (const elementName in mapB) {
145
- if (!mapA[elementName])
146
- mapA[elementName] = [];
147
-
148
- mapB[elementName].forEach(e => mapA[elementName].push(e));
149
- }
122
+ return {};
123
+ }
150
124
 
151
- return mapA;
125
+ function walkArgs(args) {
126
+ let elements = Object.create(null);
127
+ for (const arg of args) {
128
+ if (arg.args) {
129
+ elements = mergeElementMaps(elements, walkArgs(arg.args));
130
+ }
131
+ else if (arg.ref) {
132
+ const art = artifactRef(arg);
133
+ elements = mergeElementsIntoMap(elements, art.elements, art.$location, arg.as || arg.ref[arg.ref.length - 1], arg.ref[arg.ref.length - 1] || arg.as);
134
+ }
135
+ else if (arg.SELECT || arg.SET) {
136
+ elements = mergeElementMaps(elements, getSources(arg));
152
137
  }
138
+ }
153
139
 
154
- /**
155
- * Merge elements into an existing map
156
- *
157
- * @param {any} existingMap map to merge into - will be returned
158
- * @param {object} elements elements to merge into the map
159
- * @param {CSN.Location} $location $location of the elements - where they come from
160
- * @param {any} [parent] Name of the parent of the elements, alias before ref
161
- * @param {any} [error_parent] Parent name to use for error messages, ref before alias
162
- * @returns {object} existingMap
163
- */
164
- function mergeElementsIntoMap(existingMap, elements, $location, parent, error_parent) {
165
- for (const elementName in elements) {
166
- const element = elements[elementName];
167
- if (!existingMap[elementName])
168
- existingMap[elementName] = [];
169
-
170
-
171
- existingMap[elementName].push({
172
- element, name: elementName, source: $location, parent: getBaseName(parent), error_parent,
173
- });
174
- }
140
+ return elements;
141
+ }
175
142
 
176
- return existingMap;
177
- }
143
+ /**
144
+ * Merge two maps of elements together
145
+ *
146
+ * @param {object} mapA Map a - will be returned
147
+ * @param {object} mapB Map b - will not be returned
148
+ * @returns {object} mapA
149
+ */
150
+ function mergeElementMaps(mapA, mapB) {
151
+ for (const elementName in mapB) {
152
+ if (!mapA[elementName])
153
+ mapA[elementName] = [];
154
+
155
+ mapB[elementName].forEach(e => mapA[elementName].push(e));
178
156
  }
179
157
 
180
- /**
181
- * Return the name part of the artifact name - no namespace etc.
182
- * @param {string|object} name Absolute name of the artifact
183
- */
184
- function getBaseName(name) {
185
- if (!name)
186
- return name;
158
+ return mapA;
159
+ }
187
160
 
188
- if (name.id)
189
- return name.id.substring( name.id.lastIndexOf('.')+1 );
161
+ /**
162
+ * Merge elements into an existing map
163
+ *
164
+ * @param {any} existingMap map to merge into - will be returned
165
+ * @param {object} elements elements to merge into the map
166
+ * @param {CSN.Location} $location $location of the elements - where they come from
167
+ * @param {any} [parent] Name of the parent of the elements, alias before ref
168
+ * @param {any} [error_parent] Parent name to use for error messages, ref before alias
169
+ * @returns {object} existingMap
170
+ */
171
+ function mergeElementsIntoMap(existingMap, elements, $location, parent, error_parent) {
172
+ for (const elementName in elements) {
173
+ const element = elements[elementName];
174
+ if (!existingMap[elementName])
175
+ existingMap[elementName] = [];
190
176
 
191
- return name.substring( name.lastIndexOf('.')+1 )
177
+
178
+ existingMap[elementName].push({
179
+ element, name: elementName, source: $location, parent: getBaseName(parent), error_parent,
180
+ });
192
181
  }
182
+
183
+ return existingMap;
184
+ }
185
+
186
+ /**
187
+ * Return the name part of the artifact name - no namespace etc.
188
+ * @param {string|object} name Absolute name of the artifact
189
+ */
190
+ function getBaseName(name) {
191
+ if (!name)
192
+ return name;
193
+
194
+ if (name.id)
195
+ return name.id.substring( name.id.lastIndexOf('.')+1 );
196
+
197
+ return name.substring( name.lastIndexOf('.')+1 )
193
198
  }
194
199
 
195
200
  /**
@@ -216,11 +221,11 @@ function getUtils(model) {
216
221
 
217
222
  /**
218
223
  * Create an object to track visited objects identified by a unique string.
219
- * @param {string} [id] Initial entry (optional)
224
+ * @param {string} [initialId] Initial entry (optional)
220
225
  */
221
- function createVisited(id) {
226
+ function createVisited(initialId) {
222
227
  let visited = Object.create(null);
223
- check(id);
228
+ check(initialId);
224
229
  return { check };
225
230
 
226
231
  /**
@@ -231,7 +236,7 @@ function getUtils(model) {
231
236
  function check(id) {
232
237
  if (!id) return;
233
238
  if (visited[id]) {
234
- throw new Error('Circular dependency');
239
+ throw new ModelError('Circular dependency');
235
240
  }
236
241
  visited[id] = true;
237
242
  }
@@ -245,7 +250,7 @@ function getUtils(model) {
245
250
  if (model.definitions[defName])
246
251
  return model.definitions[defName]
247
252
  else
248
- throw new Error(`Nonexistent definition in the model: '${defName}'`);
253
+ throw new ModelError(`Nonexistent definition in the model: '${defName}'`);
249
254
  }
250
255
 
251
256
  /**
@@ -298,7 +303,7 @@ function getUtils(model) {
298
303
 
299
304
  // Return true if 'node' is a managed association element
300
305
  // TODO: what about elements having a type, which (finally) is an assoc?
301
- function isManagedAssociationElement(node) {
306
+ function isManagedAssociation(node) {
302
307
  return node.target !== undefined && node.on === undefined && node.keys;
303
308
  }
304
309
 
@@ -442,13 +447,13 @@ function getUtils(model) {
442
447
  * The transformer functions are called with the following signature:
443
448
  * transformer(value, node, resultNode, key)
444
449
  *
445
- * @param {any} node Node to transform
450
+ * @param {any} rootNode Node to transform
446
451
  * @param {any} transformers Object defining transformer functions
447
452
  * @returns {object}
448
453
  */
449
- function cloneWithTransformations(node, transformers) {
454
+ function cloneWithTransformations(rootNode, transformers) {
450
455
 
451
- return transformNode(node);
456
+ return transformNode(rootNode);
452
457
 
453
458
  // This general transformation function will be applied to each node recursively
454
459
  function transformNode(node) {
@@ -536,7 +541,7 @@ function getUtils(model) {
536
541
  if (cycleCheck) {
537
542
  let visited = path.length? type + ':' + path.join('.') : type;
538
543
  if (cycleCheck[visited])
539
- throw new Error('Circular type chain on type ' + type);
544
+ throw new ModelError('Circular type chain on type ' + type);
540
545
  else
541
546
  cycleCheck[visited] = true;
542
547
  }
@@ -583,7 +588,7 @@ function getUtils(model) {
583
588
  * @param {object} csn Top-level CSN. You can pass non-dictionary values.
584
589
  * @param {CSN.Options} options CSN Options, only used for `dictionaryPrototype`, `testMode`, and `testSortCsn`
585
590
  */
586
- function cloneCsn(csn, options) {
591
+ function cloneCsnNonDict(csn, options) {
587
592
  return sortCsn(csn, options);
588
593
  }
589
594
 
@@ -591,7 +596,7 @@ function cloneCsn(csn, options) {
591
596
  * Deeply clone the given CSN dictionary and return it.
592
597
  * Note that annotations are only copied shallowly.
593
598
  * This function does _not_ sort the given dictionary.
594
- * See cloneCsn() if you want sorted definitions.
599
+ * See cloneCsnNonDict() if you want sorted definitions.
595
600
  *
596
601
  * @param {object} csn
597
602
  * @param {CSN.Options} options Only cloneOptions.dictionaryPrototype is
@@ -627,8 +632,10 @@ function forEachDefinition( csn, callback, iterateOptions = {} ) {
627
632
  * @param {CSN.Path} [path]
628
633
  * @param {boolean} [ignoreIgnore]
629
634
  * @param {object} iterateOptions can be used to skip certain kinds from being iterated
635
+ * @param constructCallback
630
636
  */
631
- function forEachMember( construct, callback, path=[], ignoreIgnore=true, iterateOptions = {}) {
637
+ function forEachMember( construct, callback, path=[], ignoreIgnore=true, iterateOptions = {},
638
+ constructCallback = (_construct, _prop, _path) => {}) {
632
639
  // Allow processing _ignored elements if requested
633
640
  if (ignoreIgnore && construct._ignore) {
634
641
  return;
@@ -636,8 +643,7 @@ function forEachMember( construct, callback, path=[], ignoreIgnore=true, iterate
636
643
 
637
644
  // `items` itself is a structure that can contain "elements", and more.
638
645
  if (construct.items) {
639
- // TODO: Should we go to the deepest items.items?
640
- forEachMember( construct.items, callback, [...path, 'items'], ignoreIgnore, iterateOptions );
646
+ forEachMember( construct.items, callback, [...path, 'items'], ignoreIgnore, iterateOptions, constructCallback );
641
647
  }
642
648
 
643
649
  // Unlike XSN, we don't make "returns" a "params" in the callback.
@@ -645,12 +651,43 @@ function forEachMember( construct, callback, path=[], ignoreIgnore=true, iterate
645
651
  // `elements` of the return type (if structured).
646
652
  // TODO: `returns` should be handled like a parameter just like XSN (maybe with different prop name)
647
653
  if (construct.returns && !iterateOptions.elementsOnly) {
648
- forEachMember( construct.returns, callback, [...path, 'returns'], ignoreIgnore, iterateOptions );
654
+ forEachMember( construct.returns, callback, [...path, 'returns'], ignoreIgnore, iterateOptions, constructCallback );
649
655
  }
650
656
 
651
657
  path = [...path]; // Copy
652
- const propsWithMembers = (iterateOptions.elementsOnly ? ['elements'] : ['elements', 'enum', 'foreignKeys', 'actions', 'params']);
653
- propsWithMembers.forEach((prop) => forEachGeneric( construct, prop, callback, path, iterateOptions ));
658
+ const propsWithMembers = (iterateOptions.elementsOnly ? ['elements'] : ['elements', 'enum', 'actions', 'params']);
659
+ propsWithMembers.forEach((prop) => {
660
+ forEachGeneric( construct, prop, callback, path, iterateOptions );
661
+ if (construct[prop]) {
662
+ if (Array.isArray(constructCallback))
663
+ constructCallback.forEach(cb => cb(construct, prop, path));
664
+ else
665
+ constructCallback(construct, prop, path);
666
+ }
667
+ });
668
+ }
669
+
670
+ /**
671
+ * Call `forEachMember` and then apply `forEachMember` on queries.
672
+ *
673
+ * @param {CSN.Artifact} construct
674
+ * @param {genericCallback|genericCallback[]} callback
675
+ * @param {CSN.Path} [path]
676
+ * @param {boolean} [ignoreIgnore]
677
+ * @param {object} iterateOptions can be used to skip certain kinds from being iterated
678
+ * @param {constructCallback|constructCallback[]} callback
679
+ */
680
+ function forEachMemberWithQuery( construct, callback, path=[], ignoreIgnore=true, iterateOptions = {},
681
+ constructCallback = (_construct, _prop, _path) => {}) {
682
+ forEachMember(construct, callback, path, ignoreIgnore, iterateOptions, constructCallback);
683
+ if (construct.query) {
684
+ forAllQueries(construct.query, (q, p) => {
685
+ const s = q.SELECT;
686
+ if(s) {
687
+ forEachMember(s, callback, p, ignoreIgnore, iterateOptions);
688
+ }
689
+ }, [ ...path, 'query' ]);
690
+ }
654
691
  }
655
692
 
656
693
  /**
@@ -662,16 +699,43 @@ function forEachMember( construct, callback, path=[], ignoreIgnore=true, iterate
662
699
  * @param {CSN.Path} [path]
663
700
  * @param {boolean} [ignoreIgnore]
664
701
  * @param {object} iterateOptions can be used to skip certain kinds from being iterated
702
+ * @param {constructCallback|constructCallback[]} callback
665
703
  */
666
- function forEachMemberRecursively( construct, callback, path=[], ignoreIgnore=true, iterateOptions = {}) {
667
- forEachMember( construct, ( member, memberName, prop, subpath ) => {
704
+ function forEachMemberRecursively( construct, callback, path=[], ignoreIgnore=true, iterateOptions = {},
705
+ constructCallback = (_construct, _prop, _path) => {}) {
706
+ forEachMember( construct, ( member, memberName, prop, subpath, parent ) => {
668
707
  if(Array.isArray(callback))
669
- callback.forEach(cb => cb( member, memberName, prop, subpath, construct ));
708
+ callback.forEach(cb => cb( member, memberName, prop, subpath, parent ));
670
709
  else
671
- callback( member, memberName, prop, subpath, construct );
710
+ callback( member, memberName, prop, subpath, parent );
672
711
  // Descend into nested members, too
673
- forEachMemberRecursively( member, callback, subpath, ignoreIgnore, iterateOptions);
674
- }, path, ignoreIgnore, iterateOptions);
712
+ forEachMemberRecursively( member, callback, subpath, ignoreIgnore, iterateOptions, constructCallback);
713
+ }, path, ignoreIgnore, iterateOptions, constructCallback);
714
+ }
715
+
716
+ /**
717
+ * Apply function `callback(member, memberName)` to each member in `construct`,
718
+ * recursively (i.e. also for sub-elements of elements).
719
+ * Recursively iterate over elements of `construct` query.
720
+ *
721
+ * @param {CSN.Artifact} construct
722
+ * @param {genericCallback|genericCallback[]} callback
723
+ * @param {CSN.Path} [path]
724
+ * @param {boolean} [ignoreIgnore]
725
+ * @param {object} iterateOptions can be used to skip certain kinds from being iterated
726
+ * @param {constructCallback|constructCallback[]} callback
727
+ */
728
+ function forEachMemberRecursivelyWithQuery( construct, callback, path=[], ignoreIgnore=true, iterateOptions = {},
729
+ constructCallback = (_construct, _prop, _path) => {}) {
730
+ forEachMemberRecursively(construct, callback, path, ignoreIgnore, iterateOptions, constructCallback);
731
+ if(construct.query) {
732
+ forAllQueries(construct.query, (q, p) => {
733
+ const s = q.SELECT;
734
+ if(s) {
735
+ forEachMemberRecursively(s, callback, p, ignoreIgnore, iterateOptions);
736
+ }
737
+ }, [ ...path, 'query' ]);
738
+ }
675
739
  }
676
740
 
677
741
  /**
@@ -680,14 +744,14 @@ function forEachMemberRecursively( construct, callback, path=[], ignoreIgnore=tr
680
744
  * the following arguments: the object, the name, and -if it is a duplicate-
681
745
  * the array index and the array containing all duplicates.
682
746
  *
683
- * @param {object} obj
747
+ * @param {object} construct
684
748
  * @param {string} prop
685
749
  * @param {genericCallback|genericCallback[]} callback
686
750
  * @param {CSN.Path} path
687
751
  * @param {object} iterateOptions can be used to skip certain kinds from being iterated
688
752
  */
689
- function forEachGeneric( obj, prop, callback, path = [], iterateOptions = {}) {
690
- const dict = obj[prop];
753
+ function forEachGeneric( construct, prop, callback, path = [], iterateOptions = {}) {
754
+ const dict = construct[prop];
691
755
  for (const name in dict) {
692
756
  if (!Object.prototype.hasOwnProperty.call(dict, name))
693
757
  continue;
@@ -696,65 +760,26 @@ function forEachGeneric( obj, prop, callback, path = [], iterateOptions = {}) {
696
760
  || (iterateOptions.skipArtifact && typeof iterateOptions.skipArtifact === 'function'
697
761
  && iterateOptions.skipArtifact(dictObj, name)))
698
762
  continue;
699
- cb( dictObj, name );
763
+ executeCallbacks( dictObj, name );
700
764
  }
701
- function cb(o, name ) {
765
+ function executeCallbacks(o, name ) {
702
766
  if (Array.isArray(callback))
703
- callback.forEach(cb => cb( o, name, prop, path.concat([prop, name])));
767
+ callback.forEach(cb => cb( o, name, prop, path.concat([prop, name]), construct ));
704
768
  else
705
- callback( o, name, prop, path.concat([prop, name]))
706
- }
707
- }
708
-
709
- /**
710
- * For each property named 'ref' in 'node' (recursively), call callback(ref, node, path)
711
- *
712
- * @param {object} node
713
- * @param {refCallback|refCallback[]} callback
714
- * @param {CSN.Path} path
715
- */
716
- function forEachRef(node, callback, path = []) {
717
- if (node === null || typeof node !== 'object') {
718
- // Primitive node
719
- return;
720
- }
721
-
722
- if(node._ignore){
723
- return;
724
- }
725
-
726
- if(Array.isArray(node)){
727
- for (let i = 0; i < node.length; i++) {
728
- // Descend recursively
729
- forEachRef(node[i], callback, path.concat([i]));
730
- }
731
- } else {
732
- for (let name in node) {
733
- if (!Object.hasOwnProperty.call( node, name ))
734
- continue;
735
- // If ref found within a non-dictionary, call callback
736
- if (name === 'ref' && Object.getPrototypeOf(node)) {
737
- if(Array.isArray(callback))
738
- callback.forEach(cb => cb( node.ref, node, path ));
739
- else
740
- callback( node.ref, node, path );
741
- }
742
- // Descend recursively
743
- forEachRef(node[name], callback, path.concat([name]));
744
- }
769
+ callback( o, name, prop, path.concat([prop, name]), construct )
745
770
  }
746
771
  }
747
772
 
748
773
  // Like Object.assign() but copies also non enumerable properties
749
774
  function assignAll(target, ...sources) {
750
775
  sources.forEach(source => {
751
- let descriptors = Object.getOwnPropertyNames(source).reduce((descriptors, key) => {
752
- descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
753
- return descriptors;
776
+ const descriptors = Object.getOwnPropertyNames(source).reduce((propertyDescriptors, current) => {
777
+ propertyDescriptors[current] = Object.getOwnPropertyDescriptor(source, current);
778
+ return propertyDescriptors;
754
779
  }, {});
755
780
  // by default, Object.assign copies enumerable Symbols too
756
781
  Object.getOwnPropertySymbols(source).forEach(sym => {
757
- let descriptor = Object.getOwnPropertyDescriptor(source, sym);
782
+ const descriptor = Object.getOwnPropertyDescriptor(source, sym);
758
783
  if (descriptor.enumerable) {
759
784
  descriptors[sym] = descriptor;
760
785
  }
@@ -765,101 +790,68 @@ function assignAll(target, ...sources) {
765
790
  }
766
791
 
767
792
  /**
768
- * @param {CSN.Query} query
769
- * @param {queryCallback|queryCallback[]} callback
793
+ * @param {CSN.Query} mainQuery
794
+ * @param {queryCallback|queryCallback[]} queryCallback
770
795
  * @param {CSN.Path} path
771
796
  */
772
- function forAllQueries(query, callback, path = []){
773
- return traverseQuery(query, callback, path);
774
- function traverseQuery( q, callback, p ) {
775
- if (q.SELECT) {
797
+ function forAllQueries(mainQuery, queryCallback, path = []){
798
+ return traverseQuery(mainQuery, queryCallback, path);
799
+ function traverseQuery( query, callback, queryPath ) {
800
+ if (query.SELECT) {
776
801
  // The projection is turned into a normalized query - there
777
802
  // is no real SELECT, it is fake
778
803
  if(!(path.length === 3 && path[2] === 'projection'))
779
- p.push('SELECT');
780
- cb( q, p );
781
- q = q.SELECT;
804
+ queryPath.push('SELECT');
805
+ executeCallbacks();
806
+ query = query.SELECT;
782
807
  }
783
- else if (q.SET) {
784
- p.push('SET');
785
- cb( q, p );
786
- q = q.SET;
808
+ else if (query.SET) {
809
+ queryPath.push('SET');
810
+ executeCallbacks();
811
+ query = query.SET;
787
812
  }
788
813
 
789
- if (q.from)
790
- traverseFrom( q.from, callback, p.concat(['from']) );
814
+ if (query.from)
815
+ traverseFrom( query.from, callback, queryPath.concat(['from']) );
791
816
 
792
817
  for (const prop of ['args', 'xpr', 'columns', 'where', 'having']) {
793
818
  // all properties which could have sub queries (directly or indirectly)
794
- const expr = q[prop];
819
+ const expr = query[prop];
795
820
  if (expr && typeof expr === 'object') {
796
821
  if(Array.isArray(expr)){
797
822
  for(let i = 0; i < expr.length; i++){
798
- traverseQuery(expr[i], callback, p.concat([prop, i]));
823
+ traverseQuery(expr[i], callback, queryPath.concat([prop, i]));
799
824
  }
800
825
  } else {
801
826
  for(const argName of Object.keys( expr )){
802
- traverseQuery(expr[argName], callback, p.concat([prop, argName]))
827
+ traverseQuery(expr[argName], callback, queryPath.concat([prop, argName]))
803
828
  }
804
829
  }
805
830
  }
806
831
  }
807
- function cb(q, p) {
832
+ function executeCallbacks() {
808
833
  if(Array.isArray(callback))
809
- callback.forEach(cb => cb( q, p ));
834
+ callback.forEach(cb => cb( query, queryPath ));
810
835
  else
811
- callback( q, p );
836
+ callback( query, queryPath );
812
837
  }
813
838
  }
814
839
 
815
840
  /**
816
841
  * @param {CSN.QueryFrom} from
817
842
  * @param {Function} callback
818
- * @param {CSN.Path} path
843
+ * @param {CSN.Path} csnPath
819
844
  */
820
- function traverseFrom( from, callback, path = [] ) {
845
+ function traverseFrom( from, callback, csnPath = [] ) {
821
846
  if (from.ref) // ignore
822
847
  return;
823
848
  else if (from.args){ // join
824
849
  for(let i = 0; i < from.args.length; i++){
825
- traverseFrom(from.args[i], callback, path.concat(['args', i]));
850
+ traverseFrom(from.args[i], callback, csnPath.concat(['args', i]));
826
851
  }
827
852
  }
828
853
  else
829
- traverseQuery( from, callback, path ); // sub query in FROM
830
- }
831
- }
832
-
833
- function forAllElements(artifact, artifactName, cb, includeActions = false){
834
- if(artifact.elements) {
835
- cb(artifact, artifact.elements, ['definitions', artifactName, 'elements']);
836
- }
837
-
838
- if(includeActions && artifact.actions) {
839
- Object.entries(artifact.actions).forEach( ([actionName, action]) => {
840
- const path = ['definitions', artifactName, 'actions', actionName];
841
- if(action.params) {
842
- Object.entries(action.params).forEach( ([paramName, param]) => {
843
- if(param.elements)
844
- cb(param, param.elements, path.concat(['params', paramName, 'elements']));
845
- });
846
- }
847
- if(action.returns && action.returns.elements)
848
- cb(action.returns, action.returns.elements,path.concat(['returns', 'elements']));
849
- });
850
- }
851
-
852
- if(artifact.query) {
853
- forAllQueries(artifact.query, (q, p) => {
854
- const s = q.SELECT;
855
- if(s) {
856
- if(s.elements) {
857
- cb(s, s.elements, [...p, 'elements']);
858
- } else if(s.$elements) { // huh?, is just refloc output
859
- cb(s, s.$elements, [...p, '$elements']);
860
- }
861
- }
862
- }, ['definitions', artifactName, 'query'])
854
+ traverseQuery( from, callback, csnPath ); // sub query in FROM
863
855
  }
864
856
  }
865
857
 
@@ -925,48 +917,48 @@ function isEdmPropertyRendered(elementCsn, options) {
925
917
 
926
918
  /**
927
919
  * Return the resulting database name for (absolute) 'artifactName', depending on the current naming
928
- * convention.
920
+ * mode.
929
921
  *
930
- * - For the 'hdbcds' naming convention, this means converting '.' to '::' on
922
+ * - For the 'hdbcds' naming mode, this means converting '.' to '::' on
931
923
  * the border between namespace and top-level artifact and correctly replacing some '.' with '_'.
932
- * - For the 'plain' naming convention, it means converting all '.' to '_' and uppercasing.
933
- * - For the 'quoted' naming convention, this means correctly replacing some '.' with '_'.
924
+ * - For the 'plain' naming mode, it means converting all '.' to '_' and upper-casing.
925
+ * - For the 'quoted' naming mode, this means correctly replacing some '.' with '_'.
934
926
  *
935
927
  * If the old function signature is used - with a namespace as the third argument - the result might be wrong,
936
928
  * since the '.' -> '_' conversion for quoted/hdbcds is missing.
937
929
  *
938
- * @param {string} artifactName The name of the artifact
939
- * @param {('plain'|'quoted'|'hdbcds')} namingConvention The naming convention to use
930
+ * @param {string} artifactName The fully qualified name of the artifact
931
+ * @param {('plain'|'quoted'|'hdbcds')} sqlMapping The naming mode to use
940
932
  * @param {CSN.Model|string|undefined} csn
941
- * @returns {string} The resulting database name for (absolute) 'artifactName', depending on the current naming convention.
933
+ * @returns {string} The resulting database name for (absolute) 'artifactName', depending on the current naming mode.
942
934
  */
943
- function getArtifactDatabaseNameOf(artifactName, namingConvention, csn) {
935
+ function getArtifactDatabaseNameOf(artifactName, sqlMapping, csn) {
944
936
  if(csn && typeof csn === 'object' && csn.definitions)
945
- if (namingConvention === 'quoted' || namingConvention === 'hdbcds') {
946
- return getResultingName(csn, namingConvention, artifactName);
937
+ if (sqlMapping === 'quoted' || sqlMapping === 'hdbcds') {
938
+ return getResultingName(csn, sqlMapping, artifactName);
947
939
  }
948
- else if (namingConvention === 'plain') {
940
+ else if (sqlMapping === 'plain') {
949
941
  return artifactName.replace(/\./g, '_').toUpperCase();
950
942
  } else {
951
- throw new Error('Unknown naming convention: ' + namingConvention);
943
+ throw new Error('Unknown naming mode: ' + sqlMapping);
952
944
  }
953
945
  else {
954
946
  console.error(`This invocation of "getArtifactCdsPersistenceName" is deprecated, as it doesn't produce correct output with definition names containing dots - please provide a CSN as the third parameter.`);
955
- if (namingConvention === 'hdbcds') {
947
+ if (sqlMapping === 'hdbcds') {
956
948
  if (csn) {
957
949
  const namespace = String(csn);
958
950
  return `${namespace}::${artifactName.substring(namespace.length + 1)}`;
959
951
  }
960
952
  return artifactName;
961
953
  }
962
- else if (namingConvention === 'plain') {
954
+ else if (sqlMapping === 'plain') {
963
955
  return artifactName.replace(/\./g, '_').toUpperCase();
964
956
  }
965
- else if (namingConvention === 'quoted') {
957
+ else if (sqlMapping === 'quoted') {
966
958
  return artifactName;
967
959
  }
968
960
  else {
969
- throw new Error('Unknown naming convention: ' + namingConvention);
961
+ throw new Error('Unknown naming mode: ' + sqlMapping);
970
962
  }
971
963
  }
972
964
  }
@@ -1034,7 +1026,7 @@ function getUnderscoredName(startIndex, parts, csn) {
1034
1026
 
1035
1027
  return result;
1036
1028
  } else if(art && art.kind === 'service') {
1037
- // inside services, we immediatly turn . into _
1029
+ // inside services, we immediately turn . into _
1038
1030
  const prefix = parts.slice(0, i).join('.');
1039
1031
  const suffix = parts.slice(i).join('_');
1040
1032
  const result = [];
@@ -1052,29 +1044,29 @@ function getUnderscoredName(startIndex, parts, csn) {
1052
1044
 
1053
1045
 
1054
1046
  /**
1055
- * Return the resulting database element name for 'elemName', depending on the current naming
1056
- * convention.
1057
- * - For the 'hdbcds' naming convention, this is just 'elemName'.
1058
- * - For the 'plain' naming convention, it means converting all '.' to '_' and uppercasing.
1059
- * - For the 'quoted' naming convention, it means converting all '.' to '_'.
1060
- * No other naming conventions are accepted
1047
+ * Return the resulting database element name for 'elemName', depending on the current
1048
+ * naming mode.
1049
+ * - For the 'hdbcds' naming mode, this is just 'elemName'.
1050
+ * - For the 'plain' naming mode, it means converting all '.' to '_' and upper-casing.
1051
+ * - For the 'quoted' naming mode, it means converting all '.' to '_'.
1052
+ * No other naming modes are accepted!
1061
1053
  *
1062
- * @param {string} elemName Name of the element
1063
- * @param {('plain'|'quoted'|'hdbcds')} namingConvention The naming convention to use
1064
- * @returns {string} The resulting database element name for 'elemName', depending on the current naming convention.
1054
+ * @param {string} elemName The name of the element
1055
+ * @param {('plain'|'quoted'|'hdbcds')} sqlMapping The naming mode to use
1056
+ * @returns {string} The resulting database element name for 'elemName', depending on the current naming mode.
1065
1057
  */
1066
- function getElementDatabaseNameOf(elemName, namingConvention) {
1067
- if (namingConvention === 'hdbcds') {
1058
+ function getElementDatabaseNameOf(elemName, sqlMapping) {
1059
+ if (sqlMapping === 'hdbcds') {
1068
1060
  return elemName;
1069
1061
  }
1070
- else if (namingConvention === 'plain') {
1062
+ else if (sqlMapping === 'plain') {
1071
1063
  return elemName.replace(/\./g, '_').toUpperCase();
1072
1064
  }
1073
- else if (namingConvention === 'quoted') {
1065
+ else if (sqlMapping === 'quoted') {
1074
1066
  return elemName.replace(/\./g, '_');
1075
1067
  }
1076
1068
  else {
1077
- throw new Error('Unknown naming convention: ' + namingConvention);
1069
+ throw new Error('Unknown naming mode: ' + sqlMapping);
1078
1070
  }
1079
1071
  }
1080
1072
 
@@ -1152,7 +1144,7 @@ function setDependencies( csn ) {
1152
1144
  * @returns {boolean}
1153
1145
  */
1154
1146
  function isPersistedOnDatabase(art) {
1155
- return !([ 'entity', 'view' ].includes(art.kind) && (art.abstract || hasAnnotationValue(art, '@cds.persistence.skip')));
1147
+ return !('entity' === art.kind && (art.abstract || hasAnnotationValue(art, '@cds.persistence.skip')));
1156
1148
  }
1157
1149
 
1158
1150
  /**
@@ -1206,44 +1198,44 @@ function mergeOptions(...optionsObjects) {
1206
1198
 
1207
1199
  // Recursively used for scalars, too
1208
1200
  function mergeTwo(left, right, name) {
1209
- let result;
1201
+ let intermediateResult;
1210
1202
  // Copy left as far as required
1211
1203
  if (Array.isArray(left)) {
1212
1204
  // Shallow-copy left array
1213
- result = left.slice();
1205
+ intermediateResult = left.slice();
1214
1206
  } else if (isObject(left)) {
1215
1207
  // Deep-copy left object (unless empty)
1216
- result = Object.keys(left).length ? mergeTwo({}, left, name) : {};
1208
+ intermediateResult = Object.keys(left).length ? mergeTwo({}, left, name) : {};
1217
1209
  } else {
1218
1210
  // Just use left scalar
1219
- result = left;
1211
+ intermediateResult = left;
1220
1212
  }
1221
1213
  // Check against improper overwriting
1222
1214
  if (isObject(left) && !Array.isArray(left) && (Array.isArray(right) || isScalar(right))) {
1223
- throw new Error(`Cannot overwrite structured option "${name}" with array or scalar value`);
1215
+ throw new ModelError(`Cannot overwrite structured option "${name}" with array or scalar value`);
1224
1216
  }
1225
1217
  if ((isScalar(left) && typeof left !== 'boolean' || Array.isArray(left)) && isObject(right) && !Array.isArray(right)) {
1226
- throw new Error(`Cannot overwrite non-boolean scalar or array option "${name}" with structured value`);
1218
+ throw new ModelError(`Cannot overwrite non-boolean scalar or array option "${name}" with structured value`);
1227
1219
  }
1228
1220
 
1229
1221
  // Copy or overwrite properties from right to left
1230
1222
  if (Array.isArray(right)) {
1231
1223
  // Shallow-copy right array
1232
- result = right.slice();
1224
+ intermediateResult = right.slice();
1233
1225
  } else if (isObject(right)) {
1234
1226
  // Object overwrites undefined, scalars and arrays
1235
- if (result === undefined || isScalar(result) || Array.isArray(result)) {
1236
- result = {};
1227
+ if (intermediateResult === undefined || isScalar(intermediateResult) || Array.isArray(intermediateResult)) {
1228
+ intermediateResult = {};
1237
1229
  }
1238
1230
  // Deep-copy right object into result
1239
1231
  for (let key of Object.keys(right)) {
1240
- result[key] = mergeTwo(result[key], right[key], `${name}.${key}`);
1232
+ intermediateResult[key] = mergeTwo(intermediateResult[key], right[key], `${name}.${key}`);
1241
1233
  }
1242
1234
  } else {
1243
1235
  // Right scalar wins (unless undefined)
1244
- result = (right !== undefined) ? right : result;
1236
+ intermediateResult = (right !== undefined) ? right : intermediateResult;
1245
1237
  }
1246
- return result;
1238
+ return intermediateResult;
1247
1239
  }
1248
1240
 
1249
1241
  // Return true if 'o' is a non-null object or array
@@ -1331,19 +1323,73 @@ function getParentNamesOf(name) {
1331
1323
  }
1332
1324
 
1333
1325
 
1334
- // Copy all annotations from 'fromNode' to 'toNode'. Overwrite existing ones only if 'overwrite' is true
1335
- function copyAnnotations(fromNode, toNode, overwrite=false) {
1326
+ /**
1327
+ * Copy all annotations from 'fromNode' to 'toNode'.
1328
+ *
1329
+ * Overwrite existing ones only if 'overwrite' is true.
1330
+ *
1331
+ * @param {object} fromNode
1332
+ * @param {object} toNode
1333
+ * @param {boolean} [overwrite]
1334
+ */
1335
+ function copyAnnotations(fromNode, toNode, overwrite = false) {
1336
+ // Ignore if no toNode (in case of errors)
1337
+ if (!toNode)
1338
+ return;
1339
+
1340
+ const annotations = Object.keys(fromNode).filter(key => key.startsWith('@'));
1341
+
1342
+ for (const anno of annotations) {
1343
+ if (toNode[anno] === undefined || overwrite) {
1344
+ toNode[anno] = fromNode[anno];
1345
+ }
1346
+ }
1347
+ }
1348
+
1349
+
1350
+ /**
1351
+ * Same as `copyAnnotations()` but also copies the
1352
+ * annotation-like property `doc`.
1353
+ *
1354
+ * Overwrite existing ones only if 'overwrite' is true.
1355
+ *
1356
+ * @param {object} fromNode
1357
+ * @param {object} toNode
1358
+ * @param {boolean} [overwrite]
1359
+ */
1360
+ function copyAnnotationsAndDoc(fromNode, toNode, overwrite = false) {
1336
1361
  // Ignore if no toNode (in case of errors)
1337
- if (!toNode) {
1362
+ if (!toNode)
1338
1363
  return;
1364
+
1365
+ const annotations = Object.keys(fromNode)
1366
+ .filter(key => key.startsWith('@') || key === 'doc');
1367
+
1368
+ for (const anno of annotations) {
1369
+ if (toNode[anno] === undefined || overwrite) {
1370
+ toNode[anno] = fromNode[anno];
1371
+ }
1339
1372
  }
1340
- for (let prop in fromNode) {
1341
- if (!Object.hasOwnProperty.call( fromNode, prop ))
1342
- continue;
1343
- if (prop.startsWith('@')) {
1344
- if (toNode[prop] === undefined || overwrite) {
1345
- toNode[prop] = fromNode[prop];
1346
- }
1373
+ }
1374
+
1375
+ /**
1376
+ * Applies annotations from `csn.extensions` to definitions, i.e. top-level artifacts.
1377
+ * Does _not_ apply element/param/action/... annotations.
1378
+ * `config.filter` can be used to only copy annotations for those definitions,
1379
+ * for which the filter returns true.
1380
+ *
1381
+ * @param {CSN.Model} csn
1382
+ * @param {{overwrite?: boolean, filter?: (name: string) => boolean}} config
1383
+ */
1384
+ function applyDefinitionAnnotationsFromExtensions(csn, config) {
1385
+ if (!csn.extensions)
1386
+ return;
1387
+
1388
+ const filter = config.filter || ((_name) => true);
1389
+ for (const ext of csn.extensions) {
1390
+ const name = ext.annotate || ext.extend;
1391
+ if (name && csn.definitions[name] && filter(name)) {
1392
+ copyAnnotationsAndDoc(ext, csn.definitions[name], config.overwrite);
1347
1393
  }
1348
1394
  }
1349
1395
  }
@@ -1378,7 +1424,7 @@ function forEachPath(node, callback) {
1378
1424
  * @returns {boolean}
1379
1425
  */
1380
1426
  function hasValidSkipOrExists(artifact) {
1381
- return (artifact.kind === 'entity' || artifact.kind === 'view') &&
1427
+ return artifact.kind === 'entity' &&
1382
1428
  (hasAnnotationValue(artifact, '@cds.persistence.exists', true) || hasAnnotationValue(artifact, '@cds.persistence.skip', true))
1383
1429
 
1384
1430
  }
@@ -1447,7 +1493,7 @@ function getServiceNames(csn) {
1447
1493
  }
1448
1494
 
1449
1495
  /**
1450
- * Check wether the artifact is @cds.persistence.skip
1496
+ * Check whether the artifact is @cds.persistence.skip
1451
1497
  *
1452
1498
  * @param {CSN.Artifact} artifact
1453
1499
  * @returns {Boolean}
@@ -1501,19 +1547,57 @@ function getVariableReplacement(ref, options) {
1501
1547
  }
1502
1548
  }
1503
1549
 
1550
+ /**
1551
+ *
1552
+ * @param {object} obj
1553
+ * @param {*} other
1554
+ * @param {boolean} noExtendedProps
1555
+ * @returns {boolean} returns equality
1556
+ *
1557
+ * noExtendedProps remove '$', '_' and '@' properties from
1558
+ * the comparison. This eliminates false negatives such as
1559
+ * mismatching $locations or @odata.foreignKey4.
1560
+ */
1561
+ function isDeepEqual(obj, other, noExtendedProps) {
1562
+ let objectKeys = Object.keys(obj);
1563
+ let otherKeys = Object.keys(other);
1564
+
1565
+ if(noExtendedProps) {
1566
+ objectKeys = objectKeys.filter(k => !['@', '$', '_'].includes(k[0]));
1567
+ otherKeys = otherKeys.filter(k => !['@', '$', '_'].includes(k[0]));
1568
+ }
1569
+ if (objectKeys.length !== otherKeys.length)
1570
+ return false;
1571
+
1572
+ for (let key of objectKeys) {
1573
+ const areValuesObjects = (obj[key] != null && typeof obj[key] === 'object')
1574
+ && (other[key] !== null && typeof other[key] === 'object');
1575
+
1576
+ if (areValuesObjects) {
1577
+ if (!isDeepEqual(obj[key], other[key], noExtendedProps))
1578
+ return false;
1579
+ } else if (obj[key] !== other[key]) {
1580
+ return false;
1581
+ }
1582
+ }
1583
+ return true;
1584
+ }
1585
+
1504
1586
  module.exports = {
1505
1587
  getUtils,
1506
- cloneCsn,
1588
+ cloneCsn: cloneCsnNonDict, // Umbrella relies on this name
1589
+ cloneCsnNonDict,
1507
1590
  cloneCsnDictionary,
1508
1591
  isBuiltinType,
1509
1592
  assignAll,
1593
+ applyDefinitionAnnotationsFromExtensions,
1510
1594
  forEachGeneric,
1511
1595
  forEachDefinition,
1512
1596
  forEachMember,
1597
+ forEachMemberWithQuery,
1513
1598
  forEachMemberRecursively,
1514
- forEachRef,
1599
+ forEachMemberRecursivelyWithQuery,
1515
1600
  forAllQueries,
1516
- forAllElements,
1517
1601
  hasAnnotationValue,
1518
1602
  isEdmPropertyRendered,
1519
1603
  getArtifactDatabaseNameOf,
@@ -1533,6 +1617,7 @@ module.exports = {
1533
1617
  getParentNameOf,
1534
1618
  getLastPartOf,
1535
1619
  copyAnnotations,
1620
+ copyAnnotationsAndDoc,
1536
1621
  isAspect,
1537
1622
  forEachPath,
1538
1623
  hasValidSkipOrExists,
@@ -1541,5 +1626,7 @@ module.exports = {
1541
1626
  getServiceNames,
1542
1627
  isSkipped,
1543
1628
  walkCsnPath,
1544
- getVariableReplacement
1629
+ getVariableReplacement,
1630
+ implicitAs,
1631
+ isDeepEqual,
1545
1632
  };