@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
@@ -0,0 +1,737 @@
1
+ 'use strict';
2
+
3
+ const { setProp } = require('../../base/model');
4
+ const { setAnnotationIfNotDefined, makeClientCompatible } = require('./utils');
5
+ const {
6
+ forEachDefinition,
7
+ getUtils,
8
+ applyTransformations,
9
+ implicitAs,
10
+ isBuiltinType,
11
+ } = require('../../model/csnUtils');
12
+ const {
13
+ forEachValue, forEach,
14
+ } = require('../../utils/objectUtils');
15
+ const { setCoreComputedOnViews } = require('./coreComputed');
16
+
17
+ /**
18
+ * Loop through a universal CSN and enrich it with the properties/annotations
19
+ * from the source definition - modifies the input model in-place
20
+ *
21
+ * @param {CSN.Model} csn
22
+ * @param {CSN.Options} options
23
+ */
24
+ module.exports = (csn, options) => {
25
+ const {
26
+ initDefinition, getOrigin, getQueryPrimarySource, artifactRef, getColumn,
27
+ } = getUtils(csn, 'init-all');
28
+ // Properties on definition level that we treat specially.
29
+ const definitionPropagationRules = {
30
+ '@cds.autoexpose': onlyViaArtifact,
31
+ '@fiori.draft.enabled': onlyViaArtifact,
32
+ '@': (prop, target, source) => {
33
+ if (source[prop] !== null)
34
+ target[prop] = source[prop];
35
+ },
36
+ // Example: `type E : F;` does not have `elements`, but they are required for e.g. OData.
37
+ elements: onlyTypeDef,
38
+ '@cds.persistence.exists': skip,
39
+ '@cds.persistence.table': skip,
40
+ '@cds.persistence.calcview': skip,
41
+ '@cds.persistence.udf': skip,
42
+ '@cds.persistence.skip': notWithPersistenceTable,
43
+ '@sql.append': skip,
44
+ '@sql.prepend': skip,
45
+ '@sql.replace': skip,
46
+ '@Analytics.hidden': skip,
47
+ '@Analytics.visible': skip,
48
+ '@cds.autoexposed': skip,
49
+ '@cds.redirection.target': skip,
50
+ type: always,
51
+ doc: always,
52
+ length: always,
53
+ precision: always,
54
+ scale: always,
55
+ srid: always,
56
+ localized: always,
57
+ target: always,
58
+ targetAspect: always,
59
+ cardinality: always,
60
+ enum: always,
61
+ items: always,
62
+ params: skip, // TODO: (comment is from propagator.js) actually only with parent action
63
+ returns: always,
64
+ notNull: always,
65
+ keys: always,
66
+ };
67
+
68
+ // Properties on member level that we treat specially
69
+ const memberPropagationRules = {
70
+ key: skip,
71
+ enum: notWithTypeOrigin,
72
+ masked: skip,
73
+ virtual: notWithTypeRef,
74
+ items: specialItemsRules,
75
+ elements: (prop, target, source) => {
76
+ if (source.kind === 'type')
77
+ return;
78
+ if (!target.type || target.type && !target.type.ref && hasAnnotationOnSubelement(source.elements)) {
79
+ let needsInitialization = !target[prop];
80
+ if (needsInitialization)
81
+ target[prop] = Object.create(null);
82
+ // Propagate elements thing by thing, applying the appropriate rules
83
+ // to not propagate key in subelements for example
84
+ forEach(source[prop], (name) => {
85
+ if (!target[prop][name]) {
86
+ target[prop][name] = {};
87
+ needsInitialization = true;
88
+ }
89
+ copyProperties(source[prop][name], target[prop][name], getMemberPropagationRuleFor);
90
+ });
91
+ if (needsInitialization) // make it safe to call getOrigin
92
+ initDefinition(target);
93
+ }
94
+ }, // overwrite from defProps
95
+ kind: skip,
96
+ val: always,
97
+ };
98
+
99
+ generate();
100
+
101
+ propagateOnMemberLevel();
102
+
103
+ // In this first loop through the model, missing properties in universal CSN
104
+ // are propagated so the CSN can become client one
105
+ forEachDefinition(csn, propagateOnArtifactLevel);
106
+
107
+ // The $origin properties need to be removed separately
108
+ // as the values are used in csnRef::getOrigin that is used during
109
+ // the propagation above.
110
+ // Currently testMode-only for comparison against client CSN.
111
+ if (options.testMode)
112
+ makeClientCompatible(csn);
113
+
114
+ /**
115
+ * Before we can start propagating stuff along $origin chains,
116
+ * we have to perform some pre-processing on the csn:
117
+ * - @Core.Computed annotation is not set in the Universal CSN and must be calculated
118
+ * - Annotations on built-in types in the `csn.extensions` must be applied
119
+ * - Annotations for localized and auto-exposed are not set in Universal CSN, the compiler
120
+ * helps us and attaches a `$generated` property which indicates a compiler generated
121
+ * entity for which we must attach the annotations in this pre-processing step
122
+ * - setting properties coming from $origin object (anonymous prototype)
123
+ */
124
+ function generate() {
125
+ /**
126
+ * `@Core.Computed' must be calculated manually as this annotation
127
+ * is not set in the universal csn flavor.
128
+ */
129
+ setCoreComputedOnViews( csn );
130
+ /**
131
+ * Construct an extensions object which maps a built-in type to it's annotations
132
+ */
133
+ const extensions = Object.create( null );
134
+ if (csn.extensions) {
135
+ for ( const extension of csn.extensions ) {
136
+ const annotations = Object.create( null );
137
+ forEach( extension, ( key, val ) => {
138
+ if (!key.startsWith( '@' ))
139
+ return;
140
+ annotations[key] = val;
141
+ } );
142
+ extensions[extension.annotate] = annotations;
143
+ }
144
+ }
145
+ applyTransformations(csn, {
146
+ virtual: ( parent, prop, virtual, path) => {
147
+ // if we are not in columns, we must add `@Core.Computed`,
148
+ // even for `virtual: null`, this is e.g. the case for parameters.
149
+ // This strange behavior of `@Core.Computed` has historic reasons (due to annotation propagation).
150
+ if (path[path.length - 2] !== 'columns')
151
+ setAnnotationIfNotDefined( parent, '@Core.Computed', true );
152
+ },
153
+ target: (parent, prop, target) => {
154
+ if (!(parent.type && parent.type === 'cds.Composition'))
155
+ return;
156
+ if (typeof target === 'string') {
157
+ const artifact = artifactRef(target);
158
+ if (artifact.kind === 'aspect') {
159
+ parent.targetAspect = target;
160
+ if (parent.targetAspect === target)
161
+ delete parent.target;
162
+ return;
163
+ }
164
+ }
165
+
166
+ setTargetAspectIfRequired(parent);
167
+ },
168
+ type: ( parent, prop, type, path, grandParent, parentProp ) => {
169
+ if (parentProp === 'returns') // annos are not propagated to `returns` and `items`
170
+ return;
171
+ const annotationsForBuiltinType = extensions[type];
172
+ Object.assign( parent, annotationsForBuiltinType );
173
+ },
174
+ $generated: ( parent, prop, $generated, path ) => {
175
+ const rootArtifact = csn.definitions[path[1].slice(0, -6)];
176
+ if ( $generated === 'exposed' ) {
177
+ setAnnotationIfNotDefined( parent, '@cds.autoexposed', true );
178
+ const origin = getOrigin( parent );
179
+ if ( origin.$generated === 'localized' && origin.kind === 'entity' ) // generated .texts entity that was then autoexposed
180
+ attachAnnosForTextsTable( parent, rootArtifact );
181
+ }
182
+ else if ( $generated === 'localized' && parent.kind === 'entity' ) { // generated .texts entity
183
+ attachAnnosForTextsTable( parent, rootArtifact );
184
+ }
185
+ },
186
+ $origin: ( parent, prop, $origin ) => {
187
+ if (typeof $origin === 'string' || Array.isArray($origin))
188
+ return;
189
+ // if $origin is an object, we have to check
190
+ // if there are properties in this object
191
+ // which are not directly set on the `parent`
192
+ // and if not -> assign them
193
+ forEach($origin, (key, val) => {
194
+ if (key !== '$origin' && !parent[key])
195
+ parent[key] = val;
196
+ });
197
+ },
198
+
199
+ });
200
+
201
+ /**
202
+ * Get the thing we are supposed to use for setting targetAspect
203
+ *
204
+ * @param {object} root
205
+ * @returns {object|string|null}
206
+ */
207
+ function getTargetAspectBase(root) {
208
+ if (root.target && root.target.elements) {
209
+ return root.target;
210
+ }
211
+ else if (root.$origin) {
212
+ if (Array.isArray(root.$origin) && root.$origin[root.$origin.length - 1].target)
213
+ return getOrigin(root);
214
+ else if (root.$origin.target)
215
+ return root.$origin.target;
216
+ return null;
217
+ }
218
+ return null;
219
+ }
220
+ /**
221
+ * Set targetAspect on root and on any subelements of targetAspect if required.
222
+ *
223
+ * We can detect that via
224
+ * - root.$origin either directly or as part of an array has target
225
+ * - root.target has .elements
226
+ *
227
+ * @see getTargetAspectBase for details on how we find our "start"
228
+ * @param {object} root
229
+ */
230
+ function setTargetAspectIfRequired(root) {
231
+ if (root.$origin || root.target && root.target.elements) {
232
+ const base = getTargetAspectBase(root);
233
+ if (base && (base.elements || typeof base === 'string' && csn.definitions[base].kind === 'aspect')) {
234
+ root.targetAspect = base;
235
+ if (root.target && root.target.elements)
236
+ delete root.target;
237
+ if (base.elements) {
238
+ // anonymous aspect - we need to set targetAspect on subnodes
239
+ // things are simpler in here, no need to check the origin
240
+ const stack = [ root.targetAspect.elements ];
241
+ while (stack.length > 0) {
242
+ const elements = stack.pop();
243
+ forEach(elements, (name, element) => {
244
+ if (element.target) {
245
+ if (typeof element.target === 'string') {
246
+ const art = artifactRef(element.target);
247
+ if (art.kind === 'aspect') {
248
+ element.targetAspect = element.target;
249
+ delete element.target;
250
+ }
251
+ }
252
+ else if (element.target.elements) {
253
+ element.targetAspect = element.target;
254
+ delete element.target;
255
+ stack.push(element.targetAspect.elements);
256
+ }
257
+ }
258
+ });
259
+ }
260
+ }
261
+ }
262
+ }
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Walk over properties on member level and propagate all relevant properties
268
+ * from it's prototype.
269
+ */
270
+ function propagateOnMemberLevel() {
271
+ applyTransformations(csn, {
272
+ actions: (parent, prop, actions) => {
273
+ forEachValue(actions, (action) => {
274
+ if (!action.kind) // bound actions might not have a kind, only functions
275
+ action.kind = 'action';
276
+ propagateMemberPropsFromOrigin(action);
277
+ });
278
+ },
279
+ params: (parent, prop, params) => {
280
+ forEachValue(params, (param) => {
281
+ propagateMemberPropsFromOrigin(param, {
282
+ items: true, elements: true, enum: true, virtual: true,
283
+ });
284
+ });
285
+ },
286
+ returns: (parent, prop, returns) => {
287
+ propagateMemberPropsFromOrigin(returns, { items: true, '@': true, elements: true });
288
+ if (returns.target)
289
+ calculateForeignKeys(returns);
290
+ },
291
+ items: (parent, prop, items) => {
292
+ // items in items must be propagated
293
+ // `specialItemsRule()` does not cover this case
294
+ propagateMemberPropsFromOrigin(items, { '@': true, doc: true }, { items: onlyWithTypeRef });
295
+ },
296
+ elements: (parent, prop, elements) => {
297
+ forEachValue(elements, (e) => {
298
+ // within query elements we have to propagate the `enum` prop
299
+ const skipEnum = !parent.query && !parent.projection ? { enum: true } : null;
300
+ propagateMemberPropsFromOrigin(e, skipEnum);
301
+ });
302
+ },
303
+ enum: (parent, prop, enumProp) => forEachValue( enumProp, e => propagateMemberPropsFromOrigin(e) ),
304
+ target: (parent) => {
305
+ if (parent.type && typeof parent.target === 'string' && !parent.keys && !parent.on)
306
+ calculateForeignKeys(parent);
307
+ },
308
+ SELECT: (parent, prop, SELECT, path, artifact) => {
309
+ if ( SELECT.mixin && artifact.query )
310
+ propagateToPublishedMixin(artifact.query, artifact);
311
+ },
312
+ }, []);
313
+
314
+ /**
315
+ * Propagate properties to the `member` from it's prototype.
316
+ * For that to work we calculate the prototype chain of the member and
317
+ * propagate properties along this prototype chain until we reach the `member`
318
+ * passed to this function.
319
+ *
320
+ * @param {CSN.Element} member
321
+ * @param {object} [except=null] List of properties which should not be propagated along the origin chain
322
+ * of the `member`
323
+ * @param {object} [force=null] Overwrite any member propagation rules or any except and always propagate the corresponding keys
324
+ */
325
+ function propagateMemberPropsFromOrigin(member, except = null, force = null) {
326
+ const memberChain = getOriginChain(member);
327
+ const virtualOrigin = Object.create(null); // To collect stuff across the origin chain - currently only for .items via type-of
328
+
329
+ if (memberChain.length) {
330
+ for (let i = memberChain.length - 1; i >= 0; i--) { // start from the bottom and propagate member props upwards
331
+ const { origin, target } = memberChain[i];
332
+ if (target._status !== 'propagated' && !skipMemberPropagation(origin)) {
333
+ copyProperties(origin, target, getMemberPropagationRuleFor, except, force);
334
+
335
+ // For a `type of` with .items, we want to take stuff from types (which we skip for "normal" propagation, see specialItemsRules).
336
+ // So for a type of we also propagate stuff from the virtual origin (which we don't give a "kind", therefore skipping that part of the check)
337
+ if (target.type && target.type.ref)
338
+ copyProperties(virtualOrigin, target, getMemberPropagationRuleFor, except);
339
+
340
+ if (!target.kind)
341
+ setProp(target, '_status', 'propagated');
342
+ }
343
+
344
+ if (i > 0) // function needs to be adapted if we need more info
345
+ copyProperties(origin, virtualOrigin, key => (key === 'items' ? always : skip));
346
+ }
347
+ }
348
+ // If $origin is an object, it is the anonymous prototype of `member`
349
+ // e.g. if outer query element has an element of a subquery as it's prototype
350
+ // annotations are part of the $origin object and must be copied over to the `member`
351
+ // --> annotations on anonymous prototypes have precedence over those coming from base type
352
+ if (member.$origin && Object.keys(member.$origin).length > 0)
353
+ copyProperties(member.$origin, member, getMemberPropagationRuleFor, except);
354
+
355
+ /**
356
+ * @param {CSN.Element} origin
357
+ * @returns {boolean} whether props from the members origin should be propagated
358
+ * @todo check if still necessary
359
+ */
360
+ function skipMemberPropagation(origin) {
361
+ // For empty members (`{}`), the origin was set in a previous call to `getOrigin(definition)`.
362
+ return !origin;
363
+ }
364
+ }
365
+
366
+ /**
367
+ * Some properties must only be copied over if the target
368
+ * is a type reference pointing to an element of a type or
369
+ * an aspect.
370
+ *
371
+ * @param {string} prop
372
+ * @param {CSN.Element} target
373
+ * @param {CSN.Element} source
374
+ */
375
+ function onlyWithTypeRef(prop, target, source) {
376
+ const typeIsTypeRef = Boolean(typeof target.type === 'object' && target.type.ref);
377
+ if (typeIsTypeRef) {
378
+ const referencedArtifact = csn.definitions[target.type.ref[0]];
379
+ if (referencedArtifact.kind in { type: true, aspect: true })
380
+ target[prop] = source[prop];
381
+ }
382
+ }
383
+ }
384
+
385
+ /**
386
+ * Identify the sources of the passed object and propagate the relevant
387
+ * properties/annotations along it's $origin chain.
388
+ *
389
+ * @param {object} art Target object for propagation
390
+ */
391
+ function propagateOnArtifactLevel(art) {
392
+ // check if art was already processed by the status flag
393
+ // TODO: clean up later on, together with validator clean up probably or
394
+ // when this module is meant to be used standalone -> use internal cache to store already processed definitions?
395
+ if (art._status === 'propagated')
396
+ return;
397
+
398
+ const chain = getOriginChain(art);
399
+
400
+ if (chain.length)
401
+ chain.reverse().forEach(chainLink => definitionPropagation(chainLink.target, chainLink.origin, chain[0].origin));
402
+
403
+ /**
404
+ * @param {CSN.Element} targetDefinition
405
+ * @param {CSN.Element} targetsOrigin
406
+ * @param {CSN.Element} rootOrigin
407
+ */
408
+ function definitionPropagation(targetDefinition, targetsOrigin, rootOrigin) {
409
+ // if target was already processed -> continue
410
+ if (targetDefinition._status === 'propagated')
411
+ return;
412
+ // propagate relevant definition level properties
413
+ // we check for kind as in the future the function should be
414
+ // generic and work for parts of CSN
415
+ if (targetDefinition.kind) {
416
+ propagateDefProps(targetDefinition, targetsOrigin, rootOrigin);
417
+ if (targetDefinition.target && !targetDefinition.keys && !targetDefinition.on) // Association/Composition type
418
+ calculateForeignKeys(targetDefinition);
419
+ }
420
+
421
+ setProp(targetDefinition, '_status', 'propagated');
422
+
423
+ /**
424
+ * Propagate from 'source' to 'target' the relevant properties
425
+ * for CSN definitions. For type definitions, also walk up the origin-chain if needed to get .elements
426
+ *
427
+ * @param {CSN.Definition} definition
428
+ * @param {CSN.Definition|CSN.Element} source
429
+ * @param {CSN.Definition|CSN.Element} root
430
+ */
431
+ function propagateDefProps(definition, source, root) {
432
+ copyProperties(source, definition, getDefinitionPropagationRuleFor);
433
+ // If $origin is an object, it is the anonymous prototype of `definition`
434
+ // e.g. for structure includes annotations are part of the $origin object and must be copied over to the `definition`
435
+ // --> annotations on anonymous prototypes have precedence over those coming from base type
436
+ if (definition.$origin && Object.keys(definition.$origin).length > 0)
437
+ copyProperties(definition.$origin, definition, getDefinitionPropagationRuleFor);
438
+
439
+ // We need to propagate .elements to type artifacts - but our direct origin might not have .elements,
440
+ // because they are not propagated to members. We check if our root had elements (so we know that we should have some as well)
441
+ // and then walk the origin-chain until we find the first .elements
442
+ if (definition.kind === 'type' && root.elements && !definition.elements) {
443
+ const firstOriginWithElements = getFirstOriginWithElements(source);
444
+ definition.elements = firstOriginWithElements.elements;
445
+ }
446
+ }
447
+ }
448
+
449
+ /**
450
+ * Walk the origin-chain until we find the first origin with .elements and return it
451
+ *
452
+ * @param {CSN.Artifact|CSN.Element} start
453
+ * @returns {CSN.Artifact|CSN.Element|null} Null if no origin with .elements was found
454
+ */
455
+ function getFirstOriginWithElements(start) {
456
+ let target = start;
457
+ let firstOriginWithElements;
458
+ do {
459
+ firstOriginWithElements = getOrigin(target);
460
+ if (firstOriginWithElements && firstOriginWithElements.elements)
461
+ return firstOriginWithElements;
462
+
463
+ target = firstOriginWithElements;
464
+ } while (firstOriginWithElements);
465
+
466
+ return null;
467
+ }
468
+ }
469
+
470
+ /**
471
+ * Walk the origin-chain until we find the first origin with .elements and return it
472
+ *
473
+ * collect chain of origins and propagate
474
+ * from the farthest to the nearest one to the target
475
+ *
476
+ * @param {CSN.Artifact|CSN.Element} start
477
+ * @returns {object[]} chain of origin - target
478
+ * @todo Optimize: Only return the chain until the first propagated thing?
479
+ */
480
+ function getOriginChain(start) {
481
+ const chain = [];
482
+ let target = start;
483
+ let origin;
484
+ do {
485
+ origin = getOrigin(target);
486
+ if (origin) {
487
+ chain.push({ target, origin });
488
+ target = origin;
489
+ }
490
+ } while (origin);
491
+
492
+ return chain;
493
+ }
494
+
495
+ /**
496
+ * Propagate type properties like cardinality from the mixin definition to the published mixin element.
497
+ * To do that, we scan the elements and mark all associations, we then build a mapping from element name -> column
498
+ * and use that to check if we have a matching mixin element.
499
+ *
500
+ * If we find a match, we propagate the properties.
501
+ *
502
+ * @param {CSN.Query} query
503
+ * @param {CSN.Artifact} artifact
504
+ */
505
+ function propagateToPublishedMixin(query, artifact) {
506
+ const elements = query.SELECT.elements || artifact.elements;
507
+ forEachValue(elements, (element) => {
508
+ if (element.target) {
509
+ const column = getColumn(element);
510
+ if (column) {
511
+ const mixin = query.SELECT.mixin[implicitAs(column.ref)] || {};
512
+ copyProperties(mixin, element, getMemberPropagationRuleFor);
513
+ }
514
+ }
515
+ });
516
+ }
517
+
518
+ /**
519
+ * @param {CSN.Element} member
520
+ */
521
+ function calculateForeignKeys(member) {
522
+ // managed assocs in universal CSN have no longer keys
523
+ // if they are not explicitly defined - PR#8064
524
+ const target = artifactRef(member.target);
525
+ const targetKeys = Object.keys(target.elements).filter(key => target.elements[key].key);
526
+ member.keys = targetKeys.map(
527
+ keyName => ({ ref: [ keyName ] })
528
+ );
529
+ }
530
+
531
+ /**
532
+ * `@cds.autoexposed` for example, is propagated only if at definition level and only if
533
+ * the primary source (left-most) does not follow an association.
534
+ *
535
+ * @param {string} prop
536
+ * @param {CSN.Definition} target
537
+ * @param {CSN.Definition} source
538
+ */
539
+ function onlyViaArtifact(prop, target, source) {
540
+ if (!target.kind)
541
+ return;
542
+ const primarySourceRef = getQueryPrimarySource(target.query || target.projection);
543
+ const artRef = primarySourceRef ? artifactRef(primarySourceRef) : source;
544
+ if (!artRef.target)
545
+ target[prop] = source[prop];
546
+ }
547
+
548
+ /**
549
+ * Get the custom rule from "memberProps" (or default to "defProps") for the property copying
550
+ *
551
+ * @param {string} key identifier of the csn prop we are looking for
552
+ * @returns {Function} which can be used to apply custom propagation rules for certain props
553
+ */
554
+ function getMemberPropagationRuleFor(key) {
555
+ return memberPropagationRules[key] || memberPropagationRules[key.charAt(0)] || getDefinitionPropagationRuleFor(key);
556
+ }
557
+
558
+ /**
559
+ * Get the custom rule from "defProps" for the property copying
560
+ *
561
+ * @param {string} key identifier of the csn prop we are looking for
562
+ * @returns {Function} which can be used to apply custom propagation rules for certain props
563
+ */
564
+ function getDefinitionPropagationRuleFor(key) {
565
+ return definitionPropagationRules[key] || definitionPropagationRules[key.charAt(0)];
566
+ }
567
+
568
+ /**
569
+ * Set the annotations for a localized `rootArtifact` on it's `parent`.
570
+ *
571
+ * @param {CSN.Artifact} parent
572
+ * @param {CSN.Artifact} rootArtifact The artifact that had the localized
573
+ */
574
+ function attachAnnosForTextsTable(parent, rootArtifact) {
575
+ const isFioriDraftEnabled = rootArtifact && (rootArtifact['@fiori.draft.enabled'] === true || getOriginChain(rootArtifact).some(({ origin }) => origin['@fiori.draft.enabled'] === true));
576
+ if (isFioriDraftEnabled) {
577
+ setAnnotationIfNotDefined(parent, '@assert.unique.locale', [ { '=': 'locale' } ]);
578
+ forEach(rootArtifact.elements, (name, element) => {
579
+ if (element.key)
580
+ parent['@assert.unique.locale'].push({ '=': name });
581
+ });
582
+ }
583
+ else {
584
+ setAnnotationIfNotDefined(parent, '@odata.draft.enabled', false);
585
+
586
+ // key elements (except for locale) must get "@odata.containment.ignore": true
587
+ forEach(parent.elements, (name, element) => {
588
+ if (name !== 'locale' && element.key)
589
+ setAnnotationIfNotDefined(element, '@odata.containment.ignore', true);
590
+ });
591
+ }
592
+ }
593
+ };
594
+
595
+ /**
596
+ * Simply copy the properties of "from" to "to" - but don't overwrite existing properties.
597
+ *
598
+ * Apply the custom rules from "memberProps" and "defProps" for the copying!
599
+ *
600
+ * @param {object} from
601
+ * @param {object} to
602
+ * @param {Function} getCustomRule getter for the `memberProps` or `defProps`
603
+ * which shall be used for retrieving custom rules
604
+ * @param {object} [except=null] array of properties which should not be propagated
605
+ * @param {object} [force=null] Force propagation of the contained keys via a custom rule.
606
+ */
607
+ function copyProperties(from, to, getCustomRule, except = null, force = null) {
608
+ const keys = Object.keys(from);
609
+ // Copy over properties from the origin element to the target.
610
+ for (const key of keys) {
611
+ if (except && !(force && force[key]) && (key.charAt(0) in except || key in except))
612
+ continue;
613
+ if (!(key in to)) {
614
+ const func = force && force[key] ? force[key] : getCustomRule(key);
615
+ if (func)
616
+ func(key, to, from);
617
+ }
618
+ }
619
+ }
620
+
621
+ /**
622
+ * Recursively check if some element in the elements has an annotation.
623
+ *
624
+ * @param {object} elements
625
+ * @returns {boolean} whether some element in the elements has an annotation
626
+ */
627
+ function hasAnnotationOnSubelement(elements) {
628
+ for (const element of Object.values(elements)) {
629
+ if (Object.keys(element).some(key => key.startsWith('@')))
630
+ return true;
631
+ else if (element.elements)
632
+ return hasAnnotationOnSubelement(element.elements);
633
+ }
634
+
635
+ return false;
636
+ }
637
+
638
+
639
+ /**
640
+ * Does nothing. Is used as a placeholder
641
+ * in our member- and definition propagation
642
+ * rules.
643
+ */
644
+ function skip() {
645
+ // Do nothing
646
+ }
647
+
648
+ /**
649
+ * Always copy `prop` from `source` to `target`
650
+ *
651
+ * @param {string} prop
652
+ * @param {object} target
653
+ * @param {object} source
654
+ */
655
+ function always(prop, target, source) {
656
+ const val = source[prop];
657
+ if (Array.isArray(val))
658
+ target[prop] = [ ...val ];
659
+ else
660
+ target[prop] = val;
661
+ }
662
+
663
+ /**
664
+ * Execute only if the target definition is a user-defined type.
665
+ *
666
+ * @param {string} prop
667
+ * @param {CSN.Definition} target
668
+ * @param {CSN.Definition} source
669
+ */
670
+ function onlyTypeDef(prop, target, source) {
671
+ if (target.kind !== 'type')
672
+ return;
673
+ target[prop] = source[prop];
674
+ }
675
+
676
+ /**
677
+ * Copy `prop` from `source` to `target`
678
+ * If the `target` is annotated with `@cds.persistence.table`,
679
+ * this function does nothing.
680
+ *
681
+ * @param {string} prop
682
+ * @param {object} target
683
+ * @param {object} source
684
+ */
685
+ function notWithPersistenceTable(prop, target, source) {
686
+ const tableAnno = target['@cds.persistence.table'];
687
+ if (tableAnno === undefined || tableAnno === null)
688
+ target[prop] = source[prop];
689
+ }
690
+
691
+ /**
692
+ * Copy `prop` from `source` to `target`
693
+ * If the `source` has `source.kind === 'type'`
694
+ * this function does nothing.
695
+ *
696
+ * @param {string} prop
697
+ * @param {CSN.Element} target
698
+ * @param {CSN.Element} source
699
+ */
700
+ function notWithTypeOrigin(prop, target, source) {
701
+ if (source.kind !== 'type')
702
+ target[prop] = source[prop];
703
+ }
704
+
705
+ /**
706
+ * Special propagation rules for .items - depending on the exact type of .items and the
707
+ * way it was referenced (type of, direct type, direct many), we need to propagate (or not).
708
+ *
709
+ * We do not propagate it
710
+ * - from a type
711
+ * - from a type ref
712
+ * - from a custom type
713
+ *
714
+ * In a projection/simple view, our target and source will not have a type - we need to copy the .items there regardless of the type stuff.
715
+ *
716
+ * @param {string} prop
717
+ * @param {CSN.Element} target
718
+ * @param {CSN.Element} source
719
+ */
720
+ function specialItemsRules(prop, target, source) {
721
+ if (source.kind !== 'type' && ((!source.type && !target.type) || !(source[prop].type && source[prop].type.ref || !isBuiltinType(source[prop].type))))
722
+ target[prop] = source[prop];
723
+ }
724
+
725
+ /**
726
+ * Some properties must not be copied over if the type of this member
727
+ * is a reference to another element.
728
+ *
729
+ * @param {string} prop
730
+ * @param {CSN.Element} target
731
+ * @param {CSN.Element} source
732
+ */
733
+ function notWithTypeRef(prop, target, source) {
734
+ const typeIsTypeRef = Boolean(typeof target.type === 'object' && target.type.ref);
735
+ if (!typeIsTypeRef)
736
+ target[prop] = source[prop];
737
+ }