@sap/cds-compiler 3.1.2 → 3.3.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 (100) hide show
  1. package/CHANGELOG.md +80 -3
  2. package/bin/cdsc.js +1 -1
  3. package/doc/CHANGELOG_BETA.md +18 -0
  4. package/lib/api/main.js +8 -13
  5. package/lib/base/error.js +2 -2
  6. package/lib/base/keywords.js +2 -24
  7. package/lib/base/message-registry.js +43 -14
  8. package/lib/base/messages.js +20 -10
  9. package/lib/base/model.js +1 -1
  10. package/lib/checks/actionsFunctions.js +1 -1
  11. package/lib/checks/annotationsOData.js +2 -2
  12. package/lib/checks/arrayOfs.js +15 -7
  13. package/lib/checks/cdsPersistence.js +1 -1
  14. package/lib/checks/checkForTypes.js +48 -0
  15. package/lib/checks/defaultValues.js +2 -2
  16. package/lib/checks/elements.js +81 -6
  17. package/lib/checks/foreignKeys.js +12 -13
  18. package/lib/checks/invalidTarget.js +10 -11
  19. package/lib/checks/managedInType.js +21 -15
  20. package/lib/checks/nullableKeys.js +1 -1
  21. package/lib/checks/onConditions.js +9 -9
  22. package/lib/checks/parameters.js +21 -0
  23. package/lib/checks/selectItems.js +1 -1
  24. package/lib/checks/types.js +2 -2
  25. package/lib/checks/utils.js +17 -7
  26. package/lib/checks/validator.js +26 -14
  27. package/lib/compiler/assert-consistency.js +13 -6
  28. package/lib/compiler/builtins.js +8 -0
  29. package/lib/compiler/checks.js +40 -33
  30. package/lib/compiler/define.js +50 -44
  31. package/lib/compiler/extend.js +303 -37
  32. package/lib/compiler/kick-start.js +2 -35
  33. package/lib/compiler/populate.js +83 -62
  34. package/lib/compiler/propagator.js +1 -1
  35. package/lib/compiler/resolve.js +61 -104
  36. package/lib/compiler/shared.js +16 -6
  37. package/lib/compiler/tweak-assocs.js +25 -12
  38. package/lib/compiler/utils.js +2 -2
  39. package/lib/edm/annotations/genericTranslation.js +3 -3
  40. package/lib/edm/csn2edm.js +10 -10
  41. package/lib/edm/edm.js +17 -9
  42. package/lib/edm/edmPreprocessor.js +53 -30
  43. package/lib/edm/edmUtils.js +7 -2
  44. package/lib/gen/Dictionary.json +14 -0
  45. package/lib/gen/language.checksum +1 -1
  46. package/lib/gen/language.interp +3 -2
  47. package/lib/gen/languageParser.js +4205 -4100
  48. package/lib/inspect/inspectModelStatistics.js +1 -1
  49. package/lib/inspect/inspectPropagation.js +23 -9
  50. package/lib/json/csnVersion.js +1 -1
  51. package/lib/json/from-csn.js +26 -19
  52. package/lib/json/to-csn.js +47 -5
  53. package/lib/language/antlrParser.js +1 -1
  54. package/lib/language/genericAntlrParser.js +29 -13
  55. package/lib/language/language.g4 +28 -8
  56. package/lib/main.d.ts +3 -6
  57. package/lib/model/.eslintrc.json +13 -0
  58. package/lib/model/api.js +4 -2
  59. package/lib/model/csnRefs.js +74 -47
  60. package/lib/model/csnUtils.js +236 -218
  61. package/lib/model/enrichCsn.js +41 -31
  62. package/lib/model/revealInternalProperties.js +61 -57
  63. package/lib/model/sortViews.js +31 -31
  64. package/lib/modelCompare/compare.js +6 -6
  65. package/lib/optionProcessor.js +5 -0
  66. package/lib/render/manageConstraints.js +2 -2
  67. package/lib/render/toCdl.js +31 -44
  68. package/lib/render/toHdbcds.js +7 -5
  69. package/lib/render/toRename.js +4 -4
  70. package/lib/render/toSql.js +11 -5
  71. package/lib/render/utils/common.js +20 -9
  72. package/lib/render/utils/sql.js +5 -5
  73. package/lib/transform/db/applyTransformations.js +32 -3
  74. package/lib/transform/db/expansion.js +81 -37
  75. package/lib/transform/db/flattening.js +1 -1
  76. package/lib/transform/db/temporal.js +1 -1
  77. package/lib/transform/db/transformExists.js +1 -1
  78. package/lib/transform/forOdataNew.js +10 -7
  79. package/lib/transform/{forHanaNew.js → forRelationalDB.js} +7 -7
  80. package/lib/transform/localized.js +28 -19
  81. package/lib/transform/odata/toFinalBaseType.js +8 -11
  82. package/lib/transform/odata/typesExposure.js +1 -1
  83. package/lib/transform/transformUtilsNew.js +101 -39
  84. package/lib/transform/translateAssocsToJoins.js +5 -4
  85. package/lib/utils/moduleResolve.js +5 -5
  86. package/lib/utils/objectUtils.js +3 -3
  87. package/package.json +2 -2
  88. package/share/messages/anno-duplicate-unrelated-layer.md +6 -6
  89. package/share/messages/check-proper-type-of.md +4 -4
  90. package/share/messages/check-proper-type.md +2 -2
  91. package/share/messages/duplicate-autoexposed.md +4 -4
  92. package/share/messages/extend-repeated-intralayer.md +4 -5
  93. package/share/messages/extend-unrelated-layer.md +4 -4
  94. package/share/messages/message-explanations.json +3 -1
  95. package/share/messages/redirected-to-ambiguous.md +7 -6
  96. package/share/messages/redirected-to-complex.md +63 -0
  97. package/share/messages/redirected-to-unrelated.md +6 -5
  98. package/share/messages/rewrite-not-supported.md +4 -4
  99. package/share/messages/syntax-expected-integer.md +3 -3
  100. package/share/messages/wildcard-excluding-one.md +37 -0
@@ -22,7 +22,8 @@ const {
22
22
  augmentPath,
23
23
  splitIntoPath,
24
24
  } = require('./utils');
25
- const { compareLayer, layer } = require('./moduleLayers');
25
+ const layers = require('./moduleLayers');
26
+ const { typeParameters } = require('./builtins');
26
27
 
27
28
  function extend( model ) {
28
29
  const { options } = model;
@@ -44,6 +45,9 @@ function extend( model ) {
44
45
 
45
46
  Object.assign( model.$functions, {
46
47
  lateExtensions,
48
+ layeredAssignments,
49
+ assignmentsOfHighestLayers,
50
+ applyTypeExtensions,
47
51
  } );
48
52
 
49
53
  applyExtensions();
@@ -74,6 +78,9 @@ function extend( model ) {
74
78
  }
75
79
 
76
80
  /**
81
+ * Propagate the given `prop` (e.g. annotation) early, i.e. copy it from all `.includes`
82
+ * if they have the property.
83
+ *
77
84
  * @param {XSN.Definition} art
78
85
  * @param {string} prop
79
86
  */
@@ -163,10 +170,10 @@ function extend( model ) {
163
170
  if (ext.expectedKind === 'context' || ext.expectedKind === 'service') {
164
171
  const loc = ext.name.location;
165
172
  // TODO: warning is enough
166
- error( 'extend-with-artifacts', [ loc, ext ], { name, '#': ext.expectedKind }, {
167
- std: 'Cannot extend non-context / non-service $(NAME) with artifacts',
168
- service: 'Cannot extend non-service $(NAME) with artifacts',
169
- context: 'Cannot extend non-context $(NAME) with artifacts',
173
+ error( 'extend-with-artifacts', [ loc, ext ], { name, '#': ext.expectedKind, keyword: `EXTEND ${ ext.expectedKind }` }, {
174
+ std: 'Can\'t extend non-context / non-service $(NAME) with $(KEYWORD)',
175
+ service: 'Can\'t extend non-service $(NAME) with $(KEYWORD)',
176
+ context: 'Can\'t extend non-context $(NAME) with $(KEYWORD)',
170
177
  });
171
178
  }
172
179
  }
@@ -182,7 +189,7 @@ function extend( model ) {
182
189
  checkDefinitions( ext, art, 'columns');
183
190
  if (ext.includes)
184
191
  applyIncludes( ext, art ); // emits error if `includes` is set
185
- if (ext.kind === 'annotate')
192
+ if (ext.kind === 'annotate' || ext.kind === 'extend')
186
193
  checkAnnotate( ext, art );
187
194
  defineAnnotations( ext, art, ext._block, ext.kind );
188
195
  }
@@ -221,7 +228,8 @@ function extend( model ) {
221
228
  function extendMembers( extensions, art, noExtend ) {
222
229
  // TODO: do the whole extension stuff lazily if the elements are requested
223
230
  const elemExtensions = [];
224
- extensions.sort( compareLayer );
231
+ extensions.sort( layers.compareLayer );
232
+ // TODO: use same sequence as in chooseAssignment() - better: use common code with that fn
225
233
  for (const ext of extensions) {
226
234
  // console.log(message( 'id', [ext.location, ext], { art: ext.name._artifact },
227
235
  // 'Info', 'EXT').toString())
@@ -243,18 +251,19 @@ function extend( model ) {
243
251
  art.includes = [ ...ext.includes ];
244
252
  applyIncludes( ext, art );
245
253
  }
246
- if (ext.kind === 'annotate')
254
+ if (ext.kind === 'annotate' || ext.kind === 'extend')
247
255
  checkAnnotate( ext, art );
248
256
  defineAnnotations( ext, art, ext._block, ext.kind );
249
257
  // TODO: do we allow to add elements with array of {...}? If yes, adapt
250
258
  initMembers( ext, art, ext._block ); // might set _extend, _annotate
259
+ storeTypeExtension( ext, art );
251
260
  dependsOnSilent(art, ext); // art depends silently on ext (inverse to normal dep!)
252
261
  }
253
262
  for (const name in ext.elements) {
254
263
  const elem = ext.elements[name];
255
264
  if (elem.kind === 'element') { // i.e. not extend or annotate
256
265
  elemExtensions.push( elem );
257
- break;
266
+ break; // more than one elem in same EXTEND is fine
258
267
  }
259
268
  }
260
269
 
@@ -263,6 +272,8 @@ function extend( model ) {
263
272
  }
264
273
  if (elemExtensions.length > 1)
265
274
  reportUnstableExtensions( elemExtensions );
275
+ if (art._extendType && art._extendType.length > 0)
276
+ reportTypeExtensionsInSameLayer( art._extendType );
266
277
 
267
278
  [ 'elements', 'actions' ].forEach( (prop) => {
268
279
  const dict = art._extend && art._extend[prop];
@@ -315,14 +326,115 @@ function extend( model ) {
315
326
  }
316
327
  }
317
328
 
329
+ /**
330
+ * Similar to chooseAssignment for annotations, this function applies type extensions in order,
331
+ * such that hierarchies are respected.
332
+ * Order already set in `extendMembers()` using `compareLayers()`.
333
+ *
334
+ * @param art
335
+ */
336
+ function applyTypeExtensions(art) {
337
+ /**
338
+ * Contains the previous extension for each property that was applied
339
+ * successfully.
340
+ */
341
+ const previousSuccess = Object.create(null);
342
+ const allowedBuiltinCategories = [ 'string', 'decimal', 'integer', 'binary' ];
343
+ const artType = art.type?._artifact;
344
+
345
+ for (const ext of art._extendType) {
346
+ if (art.$inferred) {
347
+ // Only report first extension for $inferred artifact. Reduces noise.
348
+ error('ref-expected-scalar-type', [ ext.name.location, ext ], { '#': 'inferred' } );
349
+ break;
350
+ }
351
+
352
+ const baseType = art._effectiveType; // may be an ENUM
353
+ if (!artType || (art.kind !== 'type' && art.kind !== 'element') || !baseType?.builtin) {
354
+ // Only report first extension for non-scalar base type. Reduces noise.
355
+ error('ref-expected-scalar-type', [ ext.name.location, ext ], { } );
356
+ break;
357
+ }
358
+ else if (!allowedBuiltinCategories.includes(baseType.category)) {
359
+ // Only report first extension for non-scalar type. Reduces noise.
360
+ error('ref-expected-scalar-type', [ ext.name.location, ext ],
361
+ { '#': 'unsupported', prop: baseType.category });
362
+ continue;
363
+ }
364
+
365
+ for (const prop of typeParameters.list) {
366
+ if (!ext[prop])
367
+ continue;
368
+
369
+ if (!baseType.parameters?.includes(prop)) {
370
+ // For `extend T with type (length:10)` where T does not expect a length.
371
+ error( 'type-unexpected-argument', [ ext[prop].location, ext ], {
372
+ '#': 'type', prop, art, type: baseType,
373
+ });
374
+ break; // one error for first property is enough
375
+ }
376
+ else if (!art[prop]) {
377
+ // Can only extend properties that exist directly on the artifact.
378
+ error('ext-invalid-type-property', [ ext[prop].location, ext ],
379
+ { prop, '#': 'new-prop' });
380
+ break; // one error for first property is enough
381
+ }
382
+ else if (art[prop].val === ext[prop].val) {
383
+ // Ignore extensions with same value
384
+ }
385
+ else if (art[prop].literal === 'string' || ext[prop].literal === 'string' ) {
386
+ // Users can't change from/to string value for property,
387
+ // e.g. `variable`/`floating` for Decimal
388
+ error( 'ext-invalid-type-property', [ ext[prop].location, ext ],
389
+ { '#': 'string', prop } );
390
+ }
391
+ else if (art[prop].val > ext[prop].val) {
392
+ // TODO: Future: Sub-message that points to previous extension?
393
+ error( 'ext-invalid-type-property', [ ext[prop].location, ext ], {
394
+ prop,
395
+ literal: ext[prop].val,
396
+ number: art[prop].val,
397
+ '#': previousSuccess[prop] ? 'ext-smaller' : 'smaller',
398
+ } );
399
+ break; // one error for first property is enough
400
+ }
401
+ else if (art[prop].val < ext[prop].val) {
402
+ art[prop] = ext[prop];
403
+ previousSuccess[prop] = ext;
404
+ }
405
+ }
406
+
407
+ // If `scale` is increased, but `precision` is not, data may be lost in a migration.
408
+ // e.g. `Decimal(4,2)` allows `12.34`. Increasing scale to 3 would make this value
409
+ // invalid and only allow `1.234`.
410
+ // TODO: Should we actually check that the increase is correct?
411
+ if (ext.scale && !ext.precision) {
412
+ error('ext-invalid-type-property', [ ext.scale.location, ext ], {
413
+ prop: 'scale',
414
+ otherprop: 'precision',
415
+ '#': 'scale',
416
+ });
417
+ }
418
+ }
419
+ }
420
+
421
+ /**
422
+ * Report 'Warning: Unstable element order due to repeated extensions'
423
+ * except if all extensions are in the same file.
424
+ *
425
+ * @param {XSN.Extension[]} extensions
426
+ */
318
427
  function reportUnstableExtensions( extensions ) {
319
- // Report 'Warning: Unstable element order due to repeated extensions'.
428
+ // No message if all extensions are in the same file:
429
+ const file = layers.realname( extensions[0] );
430
+ if (extensions.every( ( ext, i ) => !i || file === layers.realname( ext ) ))
431
+ return;
320
432
  // Similar to chooseAssignment(), TODO there: also extra intralayer message
321
433
  // as this is a modeling error
322
434
  let lastExt = null;
323
435
  let open = []; // the "highest" layers
324
436
  for (const ext of extensions) {
325
- const extLayer = layer( ext ) || { realname: '', _layerExtends: Object.create(null) };
437
+ const extLayer = layers.layer( ext ) || { realname: '', _layerExtends: Object.create(null) };
326
438
  if (!open.length) {
327
439
  lastExt = ext;
328
440
  open = [ extLayer.realname ];
@@ -346,6 +458,28 @@ function extend( model ) {
346
458
  }
347
459
  }
348
460
  }
461
+
462
+ /**
463
+ * Report type extensions in same layer, similar mechanism to chooseAssignment()
464
+ * for annotations.
465
+ *
466
+ * @param {XSN.Extension[]} extensions
467
+ */
468
+ function reportTypeExtensionsInSameLayer( extensions ) {
469
+ // Group assignments by layer
470
+ const extLayers = layeredAssignments( extensions );
471
+ // We only care about the highest layer
472
+ const { assignments, issue } = assignmentsOfHighestLayers( extLayers );
473
+
474
+ if (issue) {
475
+ const id = (issue === 'unrelated')
476
+ ? 'ext-duplicate-extend-type-unrelated-layer'
477
+ : 'ext-duplicate-extend-type';
478
+ for (const ext of assignments)
479
+ message( id, [ ext.name.location, ext ], { type: ext.name._artifact } );
480
+ }
481
+ }
482
+
349
483
  /**
350
484
  * @param {XSN.Extension[]} extensions
351
485
  * @param {string} prop
@@ -354,12 +488,14 @@ function extend( model ) {
354
488
  * @param {object} validDict
355
489
  */
356
490
  function extendNothing( extensions, prop, name, art, validDict ) {
491
+ const artName = searchName( art, name, dictKinds[prop] );
357
492
  for (const ext of extensions) {
358
493
  // TODO: use shared functionality with notFound in resolver.js
359
494
  const { location } = ext.name;
495
+ const extName = { ...artName, kind: ext.kind };
360
496
  const msg
361
- = error( 'extend-undefined', [ location, ext ],
362
- { art: searchName( art, name, dictKinds[prop] ) },
497
+ = error( 'extend-undefined', [ location, extName ],
498
+ { art: artName },
363
499
  {
364
500
  std: 'Unknown $(ART) - nothing to extend',
365
501
  // eslint-disable-next-line max-len
@@ -444,7 +580,11 @@ function extend( model ) {
444
580
  // includes ----------------------------------------------------------------
445
581
 
446
582
  /**
583
+ * Returns true, if `includes` can be applied. They can't be applied if
584
+ * any of the artifacts referenced in `includes` are yet to be extended.
585
+ *
447
586
  * @param {XSN.Definition} art
587
+ * @returns {boolean}
448
588
  */
449
589
  function canApplyIncludes( art ) {
450
590
  if (art.includes) {
@@ -458,6 +598,15 @@ function extend( model ) {
458
598
  }
459
599
 
460
600
  /**
601
+ * Apply all includes of `ext` on `ext`. Checks that `art` allows includes.
602
+ * If `ext === art`, then includes of the artifact itself are applied.
603
+ * If `ext !== art`, applies includes on the extensions, not artifact.
604
+ * Sets `_ancestor` links on `art`.
605
+ *
606
+ * Examples:
607
+ * ext === art: `entity E : F {}` => add elements of F to E
608
+ * ext !== art: `extend E with F` => add elements of F to extension on E
609
+ *
461
610
  * @param {XSN.Extension} ext
462
611
  * @param {XSN.Artifact} art
463
612
  */
@@ -471,37 +620,42 @@ function extend( model ) {
471
620
  if (!art._ancestors)
472
621
  setLink( art, '_ancestors', [] ); // recursive array of includes
473
622
  for (const ref of ext.includes) {
474
- const template = ref._artifact; // already resolved
623
+ const template = ref._artifact;
624
+ // !template -> non-includable, e.g. scalar type, or cyclic
475
625
  if (template) {
476
626
  if (template._ancestors)
477
627
  art._ancestors.push( ...template._ancestors );
478
628
  art._ancestors.push( template );
479
629
  }
480
630
  }
481
- includeMembers( ext, 'elements', forEachInOrder, ext === art && art );
482
- includeMembers( ext, 'actions', forEachGeneric, ext === art && art );
631
+ includeMembers( ext, art, 'elements' );
632
+ includeMembers( ext, art, 'actions' );
483
633
  }
484
634
 
485
635
  /**
636
+ * Add all members (e.g. elements or actions) of `ext.includes` to `ext[prop]`
637
+ * if `parent` is `false` or to `parent` with appropriate _origin links otherwise.
638
+ * Included members are prepended to existing ones.
639
+ *
486
640
  * @param {XSN.Extension} ext
641
+ * @param {XSN.Artifact} art
487
642
  * @param {string} prop
488
- * @param {function} forEach
489
- * @param {XSN.Artifact} parent
490
643
  */
491
- function includeMembers( ext, prop, forEach, parent ) {
644
+ function includeMembers( ext, art, prop ) {
492
645
  // TODO two kind of messages:
493
646
  // Error 'More than one include defines element "A"' (at include ref)
494
647
  // Warning 'Overwrites definition from include "I" (at elem def)
648
+ const parent = ext === art && art;
495
649
  const members = ext[prop];
496
650
  ext[prop] = Object.create(null); // TODO: do not set actions property if there are none
497
651
  for (const ref of ext.includes) {
498
652
  const template = ref._artifact; // already resolved
499
653
  if (template) { // be robust
500
- forEach( template, prop, ( origin, name ) => {
654
+ forEachInOrder( template, prop, ( origin, name ) => {
501
655
  if (members && name in members)
502
656
  return; // TODO: warning for overwritten element
503
657
  const elem = linkToOrigin( origin, name, parent, prop, weakLocation( ref.location ) );
504
- if (!parent) // not yet set for EXTEND foo WITH bar
658
+ if (!parent) // not yet set for EXTEND foo WITH bar => linkToOrigin() did not add it
505
659
  dictAdd( ext[prop], name, elem );
506
660
  elem.$inferred = 'include';
507
661
  if (origin.masked)
@@ -514,7 +668,7 @@ function extend( model ) {
514
668
  }
515
669
  // TODO: expand elements having direct elements (if needed)
516
670
  if (members) {
517
- forEach( { [prop]: members }, prop, ( elem, name ) => {
671
+ forEachInOrder( { [prop]: members }, prop, ( elem, name ) => {
518
672
  dictAdd( ext[prop], name, elem );
519
673
  });
520
674
  }
@@ -523,6 +677,9 @@ function extend( model ) {
523
677
  // localized texts entities
524
678
 
525
679
  /**
680
+ * Process localized data for `art`. This includes creating `.texts` entities
681
+ * and `locale` associations.
682
+ *
526
683
  * @param {XSN.Artifact} art
527
684
  */
528
685
  function processLocalizedData( art ) {
@@ -542,9 +699,13 @@ function extend( model ) {
542
699
  }
543
700
 
544
701
  /**
702
+ * Returns `false`, if there is no localized data or an array of elements
703
+ * that are required for `.texts` entities such as keys and localized elements.
704
+ *
545
705
  * @param {XSN.Artifact} art
546
706
  * @param {XSN.Artifact|undefined} textsEntity
547
707
  * @param {boolean} fioriEnabled
708
+ * @returns {false|XSN.Element[]}
548
709
  */
549
710
  function localizedData( art, textsEntity, fioriEnabled ) {
550
711
  let keys = 0;
@@ -626,7 +787,7 @@ function extend( model ) {
626
787
  }
627
788
 
628
789
  /**
629
- * TODO: set _parent also for main artifacts!
790
+ * Create the `.texts` entity for the given base artifact.
630
791
  *
631
792
  * @param {XSN.Artifact} base
632
793
  * @param {string} absolute
@@ -772,16 +933,20 @@ function extend( model ) {
772
933
  }
773
934
 
774
935
  /**
936
+ * Returns whether `art` directly or indirectly has the property 'prop',
937
+ * following the 'origin' and the 'type' (not involving elements).
938
+ *
939
+ * DON'T USE FOR ANNOTATIONS (see TODO below)
940
+ *
941
+ * TODO: we should issue a warning if we get localized via TYPE OF
942
+ * TODO: XSN: for anno short form, use { val: true, location, <no literal prop> }
943
+ * ...then this function also works with annotations
944
+ *
775
945
  * @param {XSN.Artifact} art
776
946
  * @param {string} prop
947
+ * @returns {boolean}
777
948
  */
778
949
  function hasTruthyProp( art, prop ) {
779
- // Returns whether art directly or indirectly has the property 'prop',
780
- // following the 'origin' and the 'type' (not involving elements).
781
- //
782
- // TODO: we should issue a warning if we get localized via TYPE OF
783
- // TODO XSN: for anno short form, use { val: true, location, <no literal prop> }
784
- // ...then this function also works with annotations
785
950
  const processed = Object.create(null); // avoid infloops with circular refs
786
951
  let name = art.name.absolute; // is ok, since no recursive type possible
787
952
  while (art && !processed[name]) {
@@ -1008,6 +1173,88 @@ function extend( model ) {
1008
1173
  }
1009
1174
  }
1010
1175
 
1176
+ /**
1177
+ * Group assignments by their layers. An assignment provided with a definition
1178
+ * is considered to be provided in a layer named '', the lowest layer.
1179
+ *
1180
+ * TODO: make this usable for extend (elements), too =
1181
+ * do not use $priority, make assignments on define do not have own _block
1182
+ *
1183
+ * @param {object[]} assignments Array of assignments, e.g. extensions.
1184
+ * @returns {Record<string, object>} key: layer name, value: {name, layer, assignments[]}`
1185
+ */
1186
+ function layeredAssignments( assignments ) {
1187
+ const layered = Object.create(null);
1188
+ for (const a of assignments) {
1189
+ const layer = a.$priority !== false && layers.layer( a );
1190
+ // just consider layer if Extend/Annotate, not Define
1191
+ const name = (layer) ? layer.realname : '';
1192
+ const done = layered[name];
1193
+ if (done)
1194
+ done.assignments.push( a );
1195
+ else
1196
+ layered[name] = { name, layer, assignments: [ a ] };
1197
+ // TODO: file - if set: unique in layer
1198
+ }
1199
+ return layered;
1200
+ }
1201
+
1202
+ /**
1203
+ * Return assignments of the highest layers.
1204
+ * Also returns whether there could be an issue:
1205
+ * - false: there is just one assignment
1206
+ * - 'unrelated': there is just one assignment per layer
1207
+ * - true: there is at least one layer with two or more assignments
1208
+ * TODO: make this usable for extend (elements), too.
1209
+ *
1210
+ * @param {Record<string, object>} layeredAnnos Structure as returned by layeredAssignments()
1211
+ * @returns {{assignments, issue: boolean|string}}
1212
+ */
1213
+ function assignmentsOfHighestLayers( layeredAnnos ) {
1214
+ const layerNames = Object.keys( layeredAnnos );
1215
+ // console.log('HIB:',layerNames)
1216
+ if (layerNames.length <= 1) {
1217
+ const name = layerNames[0];
1218
+ const { assignments } = layeredAnnos[name] || { assignments: [] };
1219
+ delete layeredAnnos[name];
1220
+ return { assignments, issue: assignments.length > 1 };
1221
+ }
1222
+
1223
+ // collect all layers which are lower than another layer
1224
+ const allExtends = Object.create(null);
1225
+ allExtends[''] = {}; // the "Define" layer
1226
+ for (const name of layerNames) {
1227
+ if (name) // not the "Define" layer
1228
+ Object.assign( allExtends, layeredAnnos[name].layer._layerExtends );
1229
+ }
1230
+ // console.log('HIE:',Object.keys(allExtends))
1231
+ const assignments = [];
1232
+ const highest = [];
1233
+ for (const name of layerNames) {
1234
+ if (!(name in allExtends)) {
1235
+ const layer = layeredAnnos[name];
1236
+ delete layeredAnnos[name];
1237
+ highest.push( layer );
1238
+ assignments.push( ...layer.assignments );
1239
+ }
1240
+ }
1241
+ assignments.sort( compareAssignments );
1242
+ const good = highest.every( layer => layer.assignments.length === 1 );
1243
+ // TODO: use layer.file instead
1244
+ const issue = !good || highest.length > 1 && 'unrelated';
1245
+ // console.log('HI:',highest.map(l=>l.name),issue,issue&&assignments)
1246
+ return { assignments, issue };
1247
+ }
1248
+
1249
+ function compareAssignments( a, b ) {
1250
+ const fileA = layers.realname( a._block );
1251
+ const fileB = layers.realname( b._block );
1252
+ if (fileA !== fileB)
1253
+ return (fileA > fileB) ? 1 : -1;
1254
+ return (a?.location?.line || 0) - (b?.location?.line || 0) ||
1255
+ (a?.location?.col || 0) - (b?.location?.col || 0);
1256
+ }
1257
+
1011
1258
  /**
1012
1259
  * Copy the annotations `@cds.persistence.skip`/`@cds.persistence.exists` from
1013
1260
  * source to target if present on source but not target.
@@ -1019,14 +1266,16 @@ function extend( model ) {
1019
1266
  function copyPersistenceAnnotations(target, source, options) {
1020
1267
  if (!source)
1021
1268
  return;
1022
- // Copy @cds.persistence.skip/exists annotation.
1023
- const noCopyExists = isDeprecatedEnabled( options, 'eagerPersistenceForGeneratedEntities' );
1024
- const existsAnno = '@cds.persistence.exists';
1025
- const skipAnno = '@cds.persistence.skip';
1026
- if (!noCopyExists && source[existsAnno] && !target[existsAnno])
1027
- target[existsAnno] = source[existsAnno];
1028
- if (source[skipAnno] && !target[skipAnno])
1029
- target[skipAnno] = source[skipAnno];
1269
+
1270
+ const copyExists = !isDeprecatedEnabled( options, 'eagerPersistenceForGeneratedEntities' );
1271
+ if (copyExists)
1272
+ copy( '@cds.persistence.exists' );
1273
+ copy( '@cds.persistence.skip' );
1274
+
1275
+ function copy(anno) {
1276
+ if ( source[anno] && !target[anno] )
1277
+ target[anno] = { ...source[anno], $inferred: 'parent-origin' };
1278
+ }
1030
1279
  }
1031
1280
 
1032
1281
  function augmentEqual( location, assocname, relations, prefix = '' ) {
@@ -1054,4 +1303,21 @@ function augmentEqual( location, assocname, relations, prefix = '' ) {
1054
1303
  }
1055
1304
  }
1056
1305
 
1306
+ /**
1307
+ * If the given extension is a `EXTEND <def> WITH TYPE` extension, store
1308
+ * it in the given artifact. resolve.js will resolve types and call
1309
+ * `typeExtensions()` later.
1310
+ *
1311
+ * @param {XSN.Extension} ext
1312
+ * @param {object} art
1313
+ */
1314
+ function storeTypeExtension( ext, art ) {
1315
+ // If there are no parameters to apply, don't store the extension.
1316
+ if (!typeParameters.list.some( prop => ext[prop] !== undefined ))
1317
+ return;
1318
+ else if (!art._extendType)
1319
+ setLink( art, '_extendType', [] );
1320
+ art._extendType.push( ext );
1321
+ }
1322
+
1057
1323
  module.exports = extend;
@@ -10,7 +10,6 @@ function kickStart( model ) {
10
10
  const { message } = model.$messageFunctions;
11
11
 
12
12
  const { resolveUncheckedPath, resolvePath } = model.$functions;
13
- Object.assign( model.$functions, { projectionAncestor } );
14
13
 
15
14
  // Set _service link (sorted to set it on parent first). Could be set
16
15
  // directly, but beware a namespace becoming a service later.
@@ -68,7 +67,7 @@ function kickStart( model ) {
68
67
  // no redirection target for E if Service2.E = projection on Service1.E and
69
68
  // Service1.E = projection on E
70
69
  const chain = [];
71
- const autoexposed = art.$generated || annotationVal( art['@cds.autoexposed'] );
70
+ const autoexposed = annotationVal( art['@cds.autoexposed'] );
72
71
  const preferredRedirectionTarget = annotationVal( art['@cds.redirection.target'] );
73
72
  // no need to set preferredRedirectionTarget in the while loop as we would
74
73
  // use the projection having @cds.redirection.target anyhow instead of
@@ -80,7 +79,7 @@ function kickStart( model ) {
80
79
  chain.push( art );
81
80
  setLink( art, '_ancestors', null ); // avoid infloop with cyclic from
82
81
  const name = resolveUncheckedPath( art._from[0], 'include', art ); // TODO: 'include'?
83
- art = name && projectionAncestor( model.definitions[name], art.params );
82
+ art = name && model.definitions[name];
84
83
  if (autoexposed)
85
84
  break; // only direct projection for auto-exposed
86
85
  }
@@ -93,38 +92,6 @@ function kickStart( model ) {
93
92
  }
94
93
  }
95
94
 
96
- // Return argument `source` if entity `source` has parameters like `params`
97
- // - same parameters, although `params` can contain a new optional one (with DEFAULT)
98
- // - a parameter in `params` can be optional which is not in `source.params`, but not vice versa
99
- // - exactly the same types (type argument do not matter)
100
- function projectionAncestor( source, params ) {
101
- if (!source)
102
- return source;
103
- if (!params) // proj has no params => ok if source has no params
104
- return !source.params && source;
105
- const sourceParams = source.params || Object.create(null);
106
- for (const n in sourceParams) {
107
- if (!(n in params)) // source param is not projection param
108
- return null; // -> can't be used as implicit redirection target
109
- }
110
- for (const n in params) {
111
- const pp = params[n];
112
- const sp = sourceParams[n];
113
- if (sp) {
114
- if (sp.default && !pp.default) // param DEFAULT clause not supported yet
115
- return null; // param is not optional anymore
116
- const pt = pp.type && resolveUncheckedPath( pp.type, 'type', pp );
117
- const st = sp.type && resolveUncheckedPath( sp.type, 'type', sp );
118
- if ((pt || null) !== (st || null))
119
- return null; // params have different type
120
- }
121
- else if (!pp.default) {
122
- return null;
123
- } // non-optional param in projection, but not source
124
- }
125
- return source;
126
- }
127
-
128
95
  function postProcessArtifact( art ) {
129
96
  tagCompositionTargets( art );
130
97
  if (art.$queries) {