@sap/cds-compiler 2.11.2 → 2.13.6

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 (140) hide show
  1. package/CHANGELOG.md +175 -2
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +23 -17
  5. package/bin/cdsse.js +2 -2
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +25 -6
  9. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  10. package/doc/NameResolution.md +21 -16
  11. package/lib/api/main.js +32 -79
  12. package/lib/api/options.js +3 -2
  13. package/lib/api/validate.js +2 -1
  14. package/lib/backends.js +16 -26
  15. package/lib/base/dictionaries.js +0 -8
  16. package/lib/base/error.js +26 -0
  17. package/lib/base/keywords.js +10 -19
  18. package/lib/base/location.js +9 -4
  19. package/lib/base/message-registry.js +75 -9
  20. package/lib/base/messages.js +31 -35
  21. package/lib/base/model.js +2 -62
  22. package/lib/base/optionProcessorHelper.js +246 -183
  23. package/lib/checks/.eslintrc.json +2 -0
  24. package/lib/checks/actionsFunctions.js +2 -1
  25. package/lib/checks/annotationsOData.js +1 -1
  26. package/lib/checks/cdsPersistence.js +2 -1
  27. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  28. package/lib/checks/enricher.js +17 -1
  29. package/lib/checks/foreignKeys.js +4 -4
  30. package/lib/checks/invalidTarget.js +3 -1
  31. package/lib/checks/managedInType.js +4 -4
  32. package/lib/checks/managedWithoutKeys.js +3 -1
  33. package/lib/checks/queryNoDbArtifacts.js +1 -3
  34. package/lib/checks/selectItems.js +4 -4
  35. package/lib/checks/sql-snippets.js +94 -0
  36. package/lib/checks/types.js +1 -1
  37. package/lib/checks/unknownMagic.js +1 -1
  38. package/lib/checks/validator.js +12 -7
  39. package/lib/compiler/assert-consistency.js +12 -8
  40. package/lib/compiler/base.js +0 -1
  41. package/lib/compiler/builtins.js +42 -21
  42. package/lib/compiler/checks.js +46 -12
  43. package/lib/compiler/cycle-detector.js +1 -1
  44. package/lib/compiler/define.js +1103 -0
  45. package/lib/compiler/extend.js +983 -0
  46. package/lib/compiler/finalize-parse-cdl.js +231 -0
  47. package/lib/compiler/index.js +46 -39
  48. package/lib/compiler/kick-start.js +190 -0
  49. package/lib/compiler/moduleLayers.js +4 -4
  50. package/lib/compiler/populate.js +1226 -0
  51. package/lib/compiler/propagator.js +113 -47
  52. package/lib/compiler/resolve.js +1433 -0
  53. package/lib/compiler/shared.js +100 -65
  54. package/lib/compiler/tweak-assocs.js +529 -0
  55. package/lib/compiler/utils.js +215 -33
  56. package/lib/edm/.eslintrc.json +5 -0
  57. package/lib/edm/annotations/genericTranslation.js +38 -25
  58. package/lib/edm/annotations/preprocessAnnotations.js +3 -3
  59. package/lib/edm/csn2edm.js +10 -9
  60. package/lib/edm/edm.js +19 -20
  61. package/lib/edm/edmPreprocessor.js +166 -95
  62. package/lib/edm/edmUtils.js +127 -34
  63. package/lib/gen/Dictionary.json +92 -43
  64. package/lib/gen/language.checksum +1 -1
  65. package/lib/gen/language.interp +11 -1
  66. package/lib/gen/language.tokens +86 -82
  67. package/lib/gen/languageLexer.interp +18 -1
  68. package/lib/gen/languageLexer.js +925 -847
  69. package/lib/gen/languageLexer.tokens +78 -74
  70. package/lib/gen/languageParser.js +5434 -4298
  71. package/lib/json/from-csn.js +59 -17
  72. package/lib/json/to-csn.js +189 -71
  73. package/lib/language/antlrParser.js +3 -3
  74. package/lib/language/docCommentParser.js +3 -3
  75. package/lib/language/errorStrategy.js +26 -8
  76. package/lib/language/genericAntlrParser.js +144 -53
  77. package/lib/language/language.g4 +424 -200
  78. package/lib/language/multiLineStringParser.js +536 -0
  79. package/lib/main.d.ts +550 -61
  80. package/lib/main.js +38 -11
  81. package/lib/model/api.js +3 -1
  82. package/lib/model/csnRefs.js +322 -198
  83. package/lib/model/csnUtils.js +226 -370
  84. package/lib/model/enrichCsn.js +124 -69
  85. package/lib/model/revealInternalProperties.js +29 -7
  86. package/lib/model/sortViews.js +10 -2
  87. package/lib/modelCompare/compare.js +17 -12
  88. package/lib/optionProcessor.js +8 -3
  89. package/lib/render/.eslintrc.json +1 -2
  90. package/lib/render/DuplicateChecker.js +1 -1
  91. package/lib/render/manageConstraints.js +36 -33
  92. package/lib/render/toCdl.js +174 -275
  93. package/lib/render/toHdbcds.js +203 -122
  94. package/lib/render/toRename.js +7 -10
  95. package/lib/render/toSql.js +161 -82
  96. package/lib/render/utils/common.js +22 -8
  97. package/lib/render/utils/sql.js +10 -7
  98. package/lib/render/utils/stringEscapes.js +111 -0
  99. package/lib/sql-identifier.js +1 -1
  100. package/lib/transform/.eslintrc.json +5 -0
  101. package/lib/transform/braceExpression.js +4 -2
  102. package/lib/transform/db/.eslintrc.json +2 -0
  103. package/lib/transform/db/applyTransformations.js +212 -0
  104. package/lib/transform/db/assertUnique.js +1 -1
  105. package/lib/transform/db/associations.js +187 -0
  106. package/lib/transform/db/cdsPersistence.js +150 -0
  107. package/lib/transform/db/constraints.js +61 -56
  108. package/lib/transform/db/expansion.js +50 -29
  109. package/lib/transform/db/flattening.js +556 -106
  110. package/lib/transform/db/groupByOrderBy.js +3 -1
  111. package/lib/transform/db/temporal.js +236 -0
  112. package/lib/transform/db/transformExists.js +103 -28
  113. package/lib/transform/db/views.js +92 -44
  114. package/lib/transform/draft/.eslintrc.json +38 -0
  115. package/lib/transform/{db/draft.js → draft/db.js} +9 -7
  116. package/lib/transform/draft/odata.js +227 -0
  117. package/lib/transform/forHanaNew.js +98 -783
  118. package/lib/transform/forOdataNew.js +22 -175
  119. package/lib/transform/localized.js +36 -32
  120. package/lib/transform/odata/generateForeignKeyElements.js +3 -3
  121. package/lib/transform/odata/referenceFlattener.js +95 -89
  122. package/lib/transform/odata/structureFlattener.js +1 -1
  123. package/lib/transform/odata/toFinalBaseType.js +86 -12
  124. package/lib/transform/odata/typesExposure.js +5 -5
  125. package/lib/transform/odata/utils.js +2 -2
  126. package/lib/transform/transformUtilsNew.js +47 -33
  127. package/lib/transform/translateAssocsToJoins.js +13 -30
  128. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  129. package/lib/transform/universalCsn/coreComputed.js +170 -0
  130. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  131. package/lib/transform/universalCsn/utils.js +63 -0
  132. package/lib/utils/file.js +8 -3
  133. package/lib/utils/objectUtils.js +30 -0
  134. package/lib/utils/timetrace.js +8 -2
  135. package/package.json +1 -1
  136. package/share/messages/README.md +26 -0
  137. package/lib/compiler/definer.js +0 -2349
  138. package/lib/compiler/resolver.js +0 -2922
  139. package/lib/transform/db/helpers.js +0 -58
  140. package/lib/transform/universalCsnEnricher.js +0 -67
@@ -0,0 +1,715 @@
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) => propagateMemberPropsFromOrigin(items, { '@': true, doc: true }),
292
+ elements: (parent, prop, elements) => {
293
+ forEachValue(elements, (e) => {
294
+ // within query elements we have to propagate the `enum` prop
295
+ const skipEnum = !parent.query && !parent.projection ? { enum: true } : null;
296
+ propagateMemberPropsFromOrigin(e, skipEnum);
297
+ });
298
+ },
299
+ enum: (parent, prop, enumProp) => forEachValue( enumProp, e => propagateMemberPropsFromOrigin(e) ),
300
+ target: (parent) => {
301
+ if (parent.type && typeof parent.target === 'string' && !parent.keys && !parent.on)
302
+ calculateForeignKeys(parent);
303
+ },
304
+ SELECT: (parent, prop, SELECT, path, artifact) => {
305
+ if ( SELECT.mixin && artifact.query )
306
+ propagateToPublishedMixin(artifact.query, artifact);
307
+ },
308
+ }, []);
309
+
310
+ /**
311
+ * Propagate properties to the `member` from it's prototype.
312
+ * For that to work we calculate the prototype chain of the member and
313
+ * propagate properties along this prototype chain until we reach the `member`
314
+ * passed to this function.
315
+ *
316
+ * @param {CSN.Element} member
317
+ * @param {object} [except=null] List of properties which should not be propagated along the origin chain
318
+ * of the `member`
319
+ * @param {object} [force=null] Overwrite any member propagation rules or any except and always propagate the corresponding keys
320
+ */
321
+ function propagateMemberPropsFromOrigin(member, except = null, force = null) {
322
+ const memberChain = getOriginChain(member);
323
+ const virtualOrigin = Object.create(null); // To collect stuff across the origin chain - currently only for .items via type-of
324
+
325
+ if (memberChain.length) {
326
+ for (let i = memberChain.length - 1; i >= 0; i--) { // start from the bottom and propagate member props upwards
327
+ const { origin, target } = memberChain[i];
328
+ if (target._status !== 'propagated' && !skipMemberPropagation(origin)) {
329
+ copyProperties(origin, target, getMemberPropagationRuleFor, except, force);
330
+
331
+ // For a `type of` with .items, we want to take stuff from types (which we skip for "normal" propagation, see specialItemsRules).
332
+ // 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)
333
+ if (target.type && target.type.ref)
334
+ copyProperties(virtualOrigin, target, getMemberPropagationRuleFor, except);
335
+
336
+ if (!target.kind)
337
+ setProp(target, '_status', 'propagated');
338
+ }
339
+
340
+ if (i > 0) // function needs to be adapted if we need more info
341
+ copyProperties(origin, virtualOrigin, key => (key === 'items' ? always : skip));
342
+ }
343
+ }
344
+ // If $origin is an object, it is the anonymous prototype of `member`
345
+ // e.g. if outer query element has an element of a subquery as it's prototype
346
+ // annotations are part of the $origin object and must be copied over to the `member`
347
+ // --> annotations on anonymous prototypes have precedence over those coming from base type
348
+ if (member.$origin && Object.keys(member.$origin).length > 0)
349
+ copyProperties(member.$origin, member, getMemberPropagationRuleFor, except);
350
+
351
+ /**
352
+ * @param {CSN.Element} origin
353
+ * @returns {boolean} whether props from the members origin should be propagated
354
+ * @todo check if still necessary
355
+ */
356
+ function skipMemberPropagation(origin) {
357
+ // For empty members (`{}`), the origin was set in a previous call to `getOrigin(definition)`.
358
+ return !origin;
359
+ }
360
+ }
361
+ }
362
+
363
+ /**
364
+ * Identify the sources of the passed object and propagate the relevant
365
+ * properties/annotations along it's $origin chain.
366
+ *
367
+ * @param {Object} art Target object for propagation
368
+ */
369
+ function propagateOnArtifactLevel(art) {
370
+ // check if art was already processed by the status flag
371
+ // TODO: clean up later on, together with validator clean up probably or
372
+ // when this module is meant to be used standalone -> use internal cache to store already processed definitions?
373
+ if (art._status === 'propagated')
374
+ return;
375
+
376
+ const chain = getOriginChain(art);
377
+
378
+ if (chain.length)
379
+ chain.reverse().forEach(chainLink => definitionPropagation(chainLink.target, chainLink.origin, chain[0].origin));
380
+
381
+ /**
382
+ * @param {CSN.Element} targetDefinition
383
+ * @param {CSN.Element} targetsOrigin
384
+ * @param {CSN.Element} rootOrigin
385
+ */
386
+ function definitionPropagation(targetDefinition, targetsOrigin, rootOrigin) {
387
+ // if target was already processed -> continue
388
+ if (targetDefinition._status === 'propagated')
389
+ return;
390
+ // propagate relevant definition level properties
391
+ // we check for kind as in the future the function should be
392
+ // generic and work for parts of CSN
393
+ if (targetDefinition.kind) {
394
+ propagateDefProps(targetDefinition, targetsOrigin, rootOrigin);
395
+ if (targetDefinition.target && !targetDefinition.keys && !targetDefinition.on) // Association/Composition type
396
+ calculateForeignKeys(targetDefinition);
397
+ }
398
+
399
+ setProp(targetDefinition, '_status', 'propagated');
400
+
401
+ /**
402
+ * Propagate from 'source' to 'target' the relevant properties
403
+ * for CSN definitions. For type definitions, also walk up the origin-chain if needed to get .elements
404
+ *
405
+ * @param {CSN.Definition} definition
406
+ * @param {CSN.Definition|CSN.Element} source
407
+ * @param {CSN.Definition|CSN.Element} root
408
+ */
409
+ function propagateDefProps(definition, source, root) {
410
+ copyProperties(source, definition, getDefinitionPropagationRuleFor);
411
+ // If $origin is an object, it is the anonymous prototype of `definition`
412
+ // e.g. for structure includes annotations are part of the $origin object and must be copied over to the `definition`
413
+ // --> annotations on anonymous prototypes have precedence over those coming from base type
414
+ if (definition.$origin && Object.keys(definition.$origin).length > 0)
415
+ copyProperties(definition.$origin, definition, getDefinitionPropagationRuleFor);
416
+
417
+ // We need to propagate .elements to type artifacts - but our direct origin might not have .elements,
418
+ // because they are not propagated to members. We check if our root had elements (so we know that we should have some aswell)
419
+ // and then walk the origin-chain until we find the first .elements
420
+ if (definition.kind === 'type' && root.elements && !definition.elements) {
421
+ const firstOriginWithElements = getFirstOriginWithElements(source);
422
+ definition.elements = firstOriginWithElements.elements;
423
+ }
424
+ }
425
+ }
426
+
427
+ /**
428
+ * Walk the origin-chain until we find the first origin with .elements and return it
429
+ *
430
+ * @param {CSN.Artifact|CSN.Element} start
431
+ * @returns {CSN.Artifact|CSN.Element|null} Null if no origin with .elements was found
432
+ */
433
+ function getFirstOriginWithElements(start) {
434
+ let target = start;
435
+ let firstOriginWithElements;
436
+ do {
437
+ firstOriginWithElements = getOrigin(target);
438
+ if (firstOriginWithElements && firstOriginWithElements.elements)
439
+ return firstOriginWithElements;
440
+
441
+ target = firstOriginWithElements;
442
+ } while (firstOriginWithElements);
443
+
444
+ return null;
445
+ }
446
+ }
447
+
448
+ /**
449
+ * Walk the origin-chain until we find the first origin with .elements and return it
450
+ *
451
+ * collect chain of origins and propagate
452
+ * from the farthest to the nearest one to the target
453
+ *
454
+ * @param {CSN.Artifact|CSN.Element} start
455
+ * @returns {object[]} chain of origin - target
456
+ * @todo Optimize: Only return the chain until the first propagated thing?
457
+ */
458
+ function getOriginChain(start) {
459
+ const chain = [];
460
+ let target = start;
461
+ let origin;
462
+ do {
463
+ origin = getOrigin(target);
464
+ if (origin) {
465
+ chain.push({ target, origin });
466
+ target = origin;
467
+ }
468
+ } while (origin);
469
+
470
+ return chain;
471
+ }
472
+
473
+ /**
474
+ * Propagate type properties like cardinality from the mixin definition to the published mixin element.
475
+ * To do that, we scan the elements and mark all associations, we then build a mapping from element name -> column
476
+ * and use that to check if we have a matching mixin element.
477
+ *
478
+ * If we find a match, we propagate the properties.
479
+ *
480
+ * @param {CSN.Query} query
481
+ * @param {CSN.Artifact} artifact
482
+ */
483
+ function propagateToPublishedMixin(query, artifact) {
484
+ const elements = query.SELECT.elements || artifact.elements;
485
+ forEachValue(elements, (element) => {
486
+ if (element.target) {
487
+ const column = getColumn(element);
488
+ if (column) {
489
+ const mixin = query.SELECT.mixin[implicitAs(column.ref)] || {};
490
+ copyProperties(mixin, element, getMemberPropagationRuleFor);
491
+ }
492
+ }
493
+ });
494
+ }
495
+
496
+ /**
497
+ * @param {CSN.Element} member
498
+ */
499
+ function calculateForeignKeys(member) {
500
+ // managed assocs in universal CSN have no longer keys
501
+ // if they are not explicitly defined - PR#8064
502
+ const target = artifactRef(member.target);
503
+ const targetKeys = Object.keys(target.elements).filter(key => target.elements[key].key);
504
+ member.keys = targetKeys.map(
505
+ keyName => ({ ref: [ keyName ] })
506
+ );
507
+ }
508
+
509
+ /**
510
+ * `@cds.autoexposed` for example, is propagated only if at definition level and only if
511
+ * the primary source (left-most) does not follow an association.
512
+ *
513
+ * @param {string} prop
514
+ * @param {CSN.Definition} target
515
+ * @param {CSN.Definition} source
516
+ */
517
+ function onlyViaArtifact(prop, target, source) {
518
+ if (!target.kind)
519
+ return;
520
+ const primarySourceRef = getQueryPrimarySource(target.query || target.projection);
521
+ const artRef = primarySourceRef ? artifactRef(primarySourceRef) : source;
522
+ if (!artRef.target)
523
+ target[prop] = source[prop];
524
+ }
525
+
526
+ /**
527
+ * Get the custom rule from "memberProps" (or default to "defProps") for the property copying
528
+ *
529
+ * @param {string} key identifier of the csn prop we are looking for
530
+ * @returns {Function} which can be used to apply custom propagation rules for certain props
531
+ */
532
+ function getMemberPropagationRuleFor(key) {
533
+ return memberPropagationRules[key] || memberPropagationRules[key.charAt(0)] || getDefinitionPropagationRuleFor(key);
534
+ }
535
+
536
+ /**
537
+ * Get the custom rule from "defProps" for the property copying
538
+ *
539
+ * @param {string} key identifier of the csn prop we are looking for
540
+ * @returns {Function} which can be used to apply custom propagation rules for certain props
541
+ */
542
+ function getDefinitionPropagationRuleFor(key) {
543
+ return definitionPropagationRules[key] || definitionPropagationRules[key.charAt(0)];
544
+ }
545
+
546
+ /**
547
+ * Set the annotations for a localized `rootArtifact` on it's `parent`.
548
+ *
549
+ * @param {CSN.Artifact} parent
550
+ * @param {CSN.Artifact} rootArtifact The artifact that had the localized
551
+ */
552
+ function attachAnnosForTextsTable(parent, rootArtifact) {
553
+ const isFioriDraftEnabled = rootArtifact && (rootArtifact['@fiori.draft.enabled'] === true || getOriginChain(rootArtifact).some(({ origin }) => origin['@fiori.draft.enabled'] === true));
554
+ if (isFioriDraftEnabled) {
555
+ setAnnotationIfNotDefined(parent, '@assert.unique.locale', [ { '=': 'locale' } ]);
556
+ forEach(rootArtifact.elements, (name, element) => {
557
+ if (element.key)
558
+ parent['@assert.unique.locale'].push({ '=': name });
559
+ });
560
+ }
561
+ else {
562
+ setAnnotationIfNotDefined(parent, '@odata.draft.enabled', false);
563
+
564
+ // key elements (except for locale) must get "@odata.containment.ignore": true
565
+ forEach(parent.elements, (name, element) => {
566
+ if (name !== 'locale' && element.key)
567
+ setAnnotationIfNotDefined(element, '@odata.containment.ignore', true);
568
+ });
569
+ }
570
+ }
571
+ };
572
+
573
+ /**
574
+ * Simply copy the properties of "from" to "to" - but don't overwrite existing properties.
575
+ *
576
+ * Apply the custom rules from "memberProps" and "defProps" for the copying!
577
+ *
578
+ * @param {object} from
579
+ * @param {object} to
580
+ * @param {Function} getCustomRule getter for the `memberProps` or `defProps`
581
+ * which shall be used for retrieving custom rules
582
+ * @param {object} [except=null] array of properties which should not be propagated
583
+ * @param {object} [force=null] Force propagation of the contained keys via rule "always"
584
+ */
585
+ function copyProperties(from, to, getCustomRule, except = null, force = null) {
586
+ const keys = Object.keys(from);
587
+ // Copy over properties from the origin element to the target.
588
+ for (const key of keys) {
589
+ if (except && !(force && force[key]) && (key.charAt(0) in except || key in except))
590
+ continue;
591
+ if (!(key in to)) {
592
+ const func = force && force[key] ? always : getCustomRule(key);
593
+ if (func)
594
+ func(key, to, from);
595
+ }
596
+ }
597
+ }
598
+
599
+ /**
600
+ * Recursively check if some element in the elements has an annotation.
601
+ *
602
+ * @param {object} elements
603
+ * @returns {boolean} whether some element in the elements has an annotation
604
+ */
605
+ function hasAnnotationOnSubelement(elements) {
606
+ for (const element of Object.values(elements)) {
607
+ if (Object.keys(element).some(key => key.startsWith('@')))
608
+ return true;
609
+ else if (element.elements)
610
+ return hasAnnotationOnSubelement(element.elements);
611
+ }
612
+
613
+ return false;
614
+ }
615
+
616
+
617
+ /**
618
+ * Does nothing. Is used as a placeholder
619
+ * in our member- and definition propagation
620
+ * rules.
621
+ */
622
+ function skip() {
623
+ // Do nothing
624
+ }
625
+
626
+ /**
627
+ * Always copy `prop` from `source` to `target`
628
+ *
629
+ * @param {string} prop
630
+ * @param {object} target
631
+ * @param {object} source
632
+ */
633
+ function always(prop, target, source) {
634
+ const val = source[prop];
635
+ if (Array.isArray(val))
636
+ target[prop] = [ ...val ];
637
+ else
638
+ target[prop] = val;
639
+ }
640
+
641
+ /**
642
+ * Execute only if the target definition is a user-defined type.
643
+ *
644
+ * @param {string} prop
645
+ * @param {CSN.Definition} target
646
+ * @param {CSN.Definition} source
647
+ */
648
+ function onlyTypeDef(prop, target, source) {
649
+ if (target.kind !== 'type')
650
+ return;
651
+ target[prop] = source[prop];
652
+ }
653
+
654
+ /**
655
+ * Copy `prop` from `source` to `target`
656
+ * If the `target` is annotated with `@cds.persistence.table`,
657
+ * this function does nothing.
658
+ *
659
+ * @param {string} prop
660
+ * @param {object} target
661
+ * @param {object} source
662
+ */
663
+ function notWithPersistenceTable(prop, target, source) {
664
+ const tableAnno = target['@cds.persistence.table'];
665
+ if (tableAnno === undefined || tableAnno === null)
666
+ target[prop] = source[prop];
667
+ }
668
+
669
+ /**
670
+ * Copy `prop` from `source` to `target`
671
+ * If the `source` has `source.kind === 'type'`
672
+ * this function does nothing.
673
+ *
674
+ * @param {string} prop
675
+ * @param {CSN.Element} target
676
+ * @param {CSN.Element} source
677
+ */
678
+ function notWithTypeOrigin(prop, target, source) {
679
+ if (source.kind !== 'type')
680
+ target[prop] = source[prop];
681
+ }
682
+
683
+ /**
684
+ * Special propagation rules for .items - depending on the exact type of .items and the
685
+ * way it was referenced (type of, direct type, direct many), we need to propagate (or not).
686
+ *
687
+ * We do not propagate it
688
+ * - from a type
689
+ * - from a type ref
690
+ * - from a custom type
691
+ *
692
+ * 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.
693
+ *
694
+ * @param {string} prop
695
+ * @param {CSN.Element} target
696
+ * @param {CSN.Element} source
697
+ */
698
+ function specialItemsRules(prop, target, source) {
699
+ if (source.kind !== 'type' && ((!source.type && !target.type) || !(source[prop].type && source[prop].type.ref || !isBuiltinType(source[prop].type))))
700
+ target[prop] = source[prop];
701
+ }
702
+
703
+ /**
704
+ * Some properties must not be copied over if the type of this member
705
+ * is a reference to another element.
706
+ *
707
+ * @param {string} prop
708
+ * @param {CSN.Element} target
709
+ * @param {CSN.Element} source
710
+ */
711
+ function notWithTypeRef(prop, target, source) {
712
+ const typeIsTypeRef = Boolean(typeof target.type === 'object' && target.type.ref);
713
+ if (!typeIsTypeRef)
714
+ target[prop] = source[prop];
715
+ }