@sap/cds-compiler 6.9.3 → 7.0.1

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 (69) hide show
  1. package/CHANGELOG.md +76 -2
  2. package/bin/cdsc.js +4 -33
  3. package/doc/IncompatibleChanges_v7.md +639 -0
  4. package/lib/api/main.js +4 -56
  5. package/lib/api/options.js +5 -15
  6. package/lib/api/validate.js +1 -0
  7. package/lib/base/builtins.js +1 -2
  8. package/lib/base/csnRefs.js +2 -6
  9. package/lib/base/message-registry.js +82 -76
  10. package/lib/base/messages.js +8 -5
  11. package/lib/base/optionProcessor.js +2 -72
  12. package/lib/base/specialOptions.js +20 -17
  13. package/lib/checks/defaultValues.js +1 -39
  14. package/lib/checks/hasPersistedElements.js +19 -3
  15. package/lib/checks/parameters.js +0 -34
  16. package/lib/checks/selectItems.js +2 -38
  17. package/lib/checks/typeParameters.js +162 -0
  18. package/lib/checks/validator.js +5 -8
  19. package/lib/compiler/assert-consistency.js +19 -5
  20. package/lib/compiler/checks.js +47 -43
  21. package/lib/compiler/define.js +6 -6
  22. package/lib/compiler/extend.js +102 -111
  23. package/lib/compiler/generate.js +4 -8
  24. package/lib/compiler/populate.js +4 -7
  25. package/lib/compiler/propagator.js +9 -9
  26. package/lib/compiler/resolve.js +205 -7
  27. package/lib/compiler/shared.js +76 -82
  28. package/lib/compiler/tweak-assocs.js +102 -22
  29. package/lib/compiler/utils.js +57 -12
  30. package/lib/compiler/xpr-rewrite.js +2 -15
  31. package/lib/edm/annotations/edmJson.js +14 -10
  32. package/lib/edm/annotations/genericTranslation.js +3 -1
  33. package/lib/edm/annotations/preprocessAnnotations.js +9 -26
  34. package/lib/edm/csn2edm.js +27 -20
  35. package/lib/edm/edmUtils.js +25 -0
  36. package/lib/gen/CdlGrammar.checksum +1 -1
  37. package/lib/gen/CdlParser.js +2237 -2241
  38. package/lib/gen/Dictionary.json +17 -2
  39. package/lib/json/from-csn.js +67 -52
  40. package/lib/json/to-csn.js +28 -25
  41. package/lib/language/textUtils.js +0 -13
  42. package/lib/main.d.ts +22 -59
  43. package/lib/main.js +1 -1
  44. package/lib/model/csnUtils.js +9 -8
  45. package/lib/parsers/AstBuildingParser.js +45 -55
  46. package/lib/parsers/Lexer.js +2 -0
  47. package/lib/parsers/identifiers.js +0 -9
  48. package/lib/render/toCdl.js +41 -40
  49. package/lib/render/toSql.js +8 -1
  50. package/lib/render/utils/common.js +1 -1
  51. package/lib/render/utils/sql.js +2 -3
  52. package/lib/tool-lib/enrichCsn.js +1 -2
  53. package/lib/transform/db/applyTransformations.js +7 -5
  54. package/lib/transform/db/assertUnique.js +8 -51
  55. package/lib/transform/db/associations.js +1 -1
  56. package/lib/transform/db/cdsPersistence.js +1 -15
  57. package/lib/transform/db/expansion.js +9 -12
  58. package/lib/transform/db/flattening.js +1 -1
  59. package/lib/transform/db/groupByOrderBy.js +0 -16
  60. package/lib/transform/db/views.js +57 -161
  61. package/lib/transform/draft/db.js +2 -2
  62. package/lib/transform/forOdata.js +25 -14
  63. package/lib/transform/forRelationalDB.js +93 -301
  64. package/lib/transform/localized.js +33 -102
  65. package/lib/transform/odata/flattening.js +11 -2
  66. package/lib/transform/transformUtils.js +25 -3
  67. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -2
  68. package/package.json +2 -2
  69. package/lib/render/toHdbcds.js +0 -1810
@@ -4,7 +4,6 @@
4
4
 
5
5
  const { weakRefLocation } = require('../base/location');
6
6
  const { searchName } = require('../base/messages');
7
- const { isDeprecatedEnabled } = require('../base/specialOptions');
8
7
  const { dictAdd, pushToDict, dictForEach } = require('./dictionaries');
9
8
  const { kindProperties, dictKinds } = require('./base');
10
9
  const {
@@ -17,7 +16,6 @@ const {
17
16
  setMemberParent,
18
17
  createAndLinkCalcDepElement,
19
18
  initExprAnnoBlock,
20
- initDollarSelf,
21
19
  initBoundSelfParam,
22
20
  dependsOnSilent,
23
21
  pathName,
@@ -81,9 +79,6 @@ function extend( model ) {
81
79
  applyIncludes, // TODO: re-check
82
80
  } );
83
81
 
84
- const includesNonShadowedFirst
85
- = isDeprecatedEnabled( model.options, '_includesNonShadowedFirst' );
86
-
87
82
  const includeCollisions = [];
88
83
 
89
84
  forEachGeneric( model, 'definitions', tagCompositionTargets );
@@ -186,6 +181,7 @@ function extend( model ) {
186
181
  for (const prop in art._extensions) {
187
182
  if (Object.hasOwn( art._extensions, prop ) &&
188
183
  // remark: if we change the array, consider whether to delete the artifact
184
+ // TODO: what is the `$gen`? `$add` is handled...
189
185
  ![ 'elements', 'actions', 'params', '$gen' ].includes( prop ))
190
186
  applyPropertyExtensions( art, prop );
191
187
  }
@@ -292,7 +288,7 @@ function extend( model ) {
292
288
 
293
289
  /**
294
290
  * Applying extensions is handled in extendArtifactAfter(). And only afterward,
295
- * an effective sequence number is set. Meaning that if a sub-artifact already
291
+ * an effective sequence number is set. Meaning that if a sub artifact already
296
292
  * has a sequence number, then extensions would be lost.
297
293
  *
298
294
  * A special case are foreign keys, see extendForeignKeys().
@@ -425,6 +421,8 @@ function extend( model ) {
425
421
  }
426
422
  else if (prop === 'returns') {
427
423
  pushToDict( dict, 'elements', ext );
424
+ if (hasSecurityAnno( ext.returns ))
425
+ checkReturnsExtension( ext, ext );
428
426
  // create 'returns' for the super annotate, store in elements anyway
429
427
  if (!art.returns && art.kind === 'annotate')
430
428
  annotateCreate( art, '', art, 'returns' );
@@ -498,10 +496,12 @@ function extend( model ) {
498
496
  }
499
497
  // Now apply the relevant extensions
500
498
  scheduled.reverse();
499
+ art.$postponeInclude ??= 0; // add elems of includes before direct ones
501
500
  if (prop === 'includes' && !art.includes?.$original) {
502
501
  const $original = art.includes ?? [];
503
502
  art.includes = [ ...$original ];
504
503
  art.includes.$original = $original;
504
+ // console.log('APE:',art.name.id,art.includes.map( r => r._artifact?.name.id ))
505
505
  }
506
506
  if (prop === '$add') {
507
507
  extensions[prop] = scheduled;
@@ -703,7 +703,7 @@ function extend( model ) {
703
703
  result.push( item );
704
704
  }
705
705
  else {
706
- let upToSpec = item.upTo && checkUpToSpec( item.upTo, art, annoName, true );
706
+ let upToSpec = !item.$errorReported && item.upTo;
707
707
  while (prevPos < previousValue.length) {
708
708
  const prevItem = previousValue[prevPos++];
709
709
  result.push( prevItem );
@@ -728,40 +728,18 @@ function extend( model ) {
728
728
  location: previousAnno.location,
729
729
  };
730
730
  }
731
- // function se(a) { return a.upTo ? [a.val,a.upTo.val] : a.val ; }
732
731
 
733
- function checkUpToSpec( upToSpec, art, annoName, isFullUpTo ) {
734
- const { literal } = upToSpec;
735
- if (!isFullUpTo) { // inside struct of UP TO
736
- if (literal !== 'struct' && literal !== 'array' )
737
- return true;
738
- }
739
- else if (literal === 'struct') {
740
- return Object.values( upToSpec.struct ).every( v => checkUpToSpec( v, art, annoName ) );
741
- }
742
- else if (literal !== 'array' && literal !== 'boolean' && literal !== 'null') {
743
- return true;
744
- }
745
- error( null, [ upToSpec.location, art ],
746
- { anno: annoName, code: '... up to', '#': literal },
747
- {
748
- std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
749
- array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
750
- // eslint-disable-next-line @stylistic/max-len
751
- struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
752
- boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
753
- null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
754
- } );
755
- return false;
756
- }
732
+ // For the `up to` specification, see ../../test3/Annotations/EllipsisUpTo/
757
733
 
758
734
  function equalUpTo( previousItem, upToSpec ) {
759
735
  if (!previousItem)
760
736
  return false;
761
- if ('val' in upToSpec) {
737
+
738
+ if (upToSpec.sym) // enum symbol (literal: 'enum')
739
+ return previousItem.sym?.id === upToSpec.sym.id;
740
+ if (upToSpec.val !== undefined) { // not struct, ref or non-val expression
762
741
  if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
763
742
  return true;
764
- // TODO v6: delete the special UP TO comparison?
765
743
  const upToVal = upToSpec.val;
766
744
  const prevVal = previousItem.val;
767
745
  // eslint-disable-next-line eqeqeq
@@ -769,17 +747,27 @@ function extend( model ) {
769
747
  ( typeof upToVal === 'number' && stringCouldHaveBeenCdlNumber( prevVal ) ||
770
748
  typeof prevVal === 'number' && stringCouldHaveBeenCdlNumber( upToVal ) );
771
749
  }
772
- else if (upToSpec.path) {
773
- return previousItem.path && normalizeRef( previousItem ) === normalizeRef( upToSpec );
774
- }
775
- else if (upToSpec.sym) {
776
- return previousItem.sym && previousItem.sym.id === upToSpec.sym.id;
750
+
751
+ if (upToSpec.path) {
752
+ // console.log(upToSpec.location+'',normalizeRef( upToSpec ),previousItem.struct?.['='])
753
+ return (previousItem.path)
754
+ ? normalizeRef( upToSpec ) === normalizeRef( previousItem )
755
+ : normalizeRef( upToSpec ) === previousItem.struct?.['=']?.val &&
756
+ (!upToSpec.$tokenTexts || Object.keys( previousItem.struct ).length === 1);
777
757
  }
778
- else if (upToSpec.struct && previousItem.struct) {
779
- return Object.entries( upToSpec.struct )
780
- .every( ([ n, v ]) => equalUpTo( previousItem.struct[n], v ) );
758
+ if (!upToSpec.struct)
759
+ return false;
760
+
761
+ const { struct } = previousItem;
762
+ if (struct) {
763
+ const entries = Object.entries( upToSpec.struct );
764
+ // {} does not match { "=": "ref" }, but { prop: … } and { "=": "ref", more: … }
765
+ return (entries.length)
766
+ ? entries.every( ([ n, v ]) => equalUpTo( previousItem.struct[n], v ) )
767
+ : !struct['='] || Object.keys( struct ).length > 1;
781
768
  }
782
- return false;
769
+ return previousItem.path && Object.keys( upToSpec.struct ).length === 1 &&
770
+ normalizeRef( previousItem ) === upToSpec.struct['=']?.val;
783
771
  }
784
772
 
785
773
  // We only compare a string by number if the string is not empty, and could have
@@ -794,7 +782,7 @@ function extend( model ) {
794
782
 
795
783
  function normalizeRef( node ) { // see to-csn.js
796
784
  const ref = pathName( node.path );
797
- // TODO: get rid of name.variant (induces a wrong structure anyway)
785
+ // TODO XSN 7.1: get rid of name.variant (induces a wrong structure anyway)
798
786
  return node.variant ? `${ ref }#${ pathName( node.variant.path ) }` : ref;
799
787
  }
800
788
 
@@ -886,11 +874,10 @@ function extend( model ) {
886
874
  return;
887
875
 
888
876
  for (const ext of extensions) {
889
- let dictCheck = (art.kind !== 'annotate'); // no check in super annotate statement
890
877
  forEachGeneric( ext, extProp || (ext.enum ? 'enum' : 'elements'), ( elemExt, name ) => {
891
878
  if (elemExt.kind !== 'annotate' && elemExt.kind !== 'extend') // TODO: specified elems
892
879
  return; // definitions inside extend, already handled
893
- dictCheck = dictCheck && checkRemainingMemberExtensions( art, elemExt, artProp, name );
880
+ checkRemainingMemberExtensions( art, elemExt, artProp, name );
894
881
  const elem = art[artProp]?.[name] || annotateFor( art, extProp || 'elements', name );
895
882
  setLink( elemExt.name, '_artifact', (elem.kind !== 'annotate' ? elem : null ) );
896
883
  // TODO: why null for annotate?
@@ -948,13 +935,7 @@ function extend( model ) {
948
935
  ? 'ext-unexpected-returns-sec'
949
936
  : 'ext-unexpected-returns';
950
937
  message( msgId, [ ext.returns.location, ext ],
951
- { '#': art.kind, keyword: 'returns' },
952
- {
953
- std: 'Unexpected $(KEYWORD); only actions and functions have return parameters',
954
- action: 'Unexpected $(KEYWORD) for action without return parameter',
955
- // function without `returns` can happen via CSN input! TODO: check in parser
956
- function: 'Unexpected $(KEYWORD) for function without return parameter',
957
- } );
938
+ { '#': art.kind, keyword: 'returns' } );
958
939
  // Do not put completely wrong returns into a “super annotate” statement;
959
940
  // this could induce consequential errors with [..., …]:
960
941
  return art.kind === 'action' || art.kind === 'function';
@@ -983,7 +964,10 @@ function extend( model ) {
983
964
  // (i.e. not put into the "super annotate").
984
965
  const dict = parent[prop];
985
966
  const securityRelevant = hasSecurityAnno( ext ) ? '-sec' : '';
967
+ const inSuperAnnotate = (parent.kind === 'annotate');
986
968
  if (!dict && !securityRelevant) {
969
+ if (inSuperAnnotate) // no check in super annotate statement
970
+ return;
987
971
  // TODO: check - for each name? - better locations
988
972
  const location = ext._parent?.[prop]?.[$location] || ext.name.location;
989
973
  // Remark: no `elements` dict location with `annotate Main:elem`
@@ -1007,7 +991,7 @@ function extend( model ) {
1007
991
  break;
1008
992
  case 'actions':
1009
993
  if (canBeDraftMember( name, parent, draftBoundActions ))
1010
- return true;
994
+ return;
1011
995
  // TODO: use extra text variant and location of dictionary - no
1012
996
  notFound( 'ext-undefined-action', ext.name.location, ext,
1013
997
  { '#': 'action', art: parent, name } );
@@ -1016,9 +1000,10 @@ function extend( model ) {
1016
1000
  if (model.options.testMode)
1017
1001
  throw new CompilerAssertion(`Missing case for prop: ${ prop }`);
1018
1002
  }
1019
- return false;
1020
1003
  }
1021
1004
  else if (!dict?.[name]) {
1005
+ if (inSuperAnnotate && !securityRelevant)
1006
+ return; // there was already a warning for the parent
1022
1007
  // TODO: make variant `returns` an auto-variant for ($ART) ?
1023
1008
  const inReturns = parent._parent?.returns;
1024
1009
  switch (prop) {
@@ -1055,7 +1040,6 @@ function extend( model ) {
1055
1040
  throw new CompilerAssertion(`Missing case for prop: ${ prop }`);
1056
1041
  }
1057
1042
  }
1058
- return true;
1059
1043
  }
1060
1044
 
1061
1045
  function notFound( msgId, location, address, args, validDict ) {
@@ -1080,10 +1064,10 @@ function extend( model ) {
1080
1064
  annotate[prop] = art[prop];
1081
1065
  }
1082
1066
  }
1083
- if (extensions.length === 1) { // i.e. no proper location if from more than one extension
1084
- annotate.location = extensions[0].location;
1085
- annotate.name.location = extensions[0].name.location;
1086
- }
1067
+ }
1068
+ if (extensions?.length === 1) { // i.e. no proper location if from more than one extension
1069
+ annotate.location = extensions[0].location;
1070
+ annotate.name.location = extensions[0].name.location;
1087
1071
  }
1088
1072
  extendArtifactBefore( annotate );
1089
1073
  extendArtifactAfter( annotate );
@@ -1158,13 +1142,26 @@ function extend( model ) {
1158
1142
 
1159
1143
  // extend, mainly old-style ---------------------------------------------------
1160
1144
 
1145
+ function addedElements( art ) {
1146
+ return Object.values( art.elements )
1147
+ .reduce( ( acc, elem ) => (elem.kind === 'extend' ? acc : acc + 1), 0 );
1148
+ }
1149
+
1150
+ /**
1151
+ * Extend artifact with additional elements and actions: via includes and/or
1152
+ * extensions. This happens _after_ `populateArtifact`, i.e. after eventually
1153
+ * having inferred the elements/... from an "origin".
1154
+ */
1161
1155
  function extendArtifactAdd( art ) {
1162
1156
  const { includes } = art;
1163
1157
  if (includes) {
1164
1158
  if (includes.$original) // if extensions with includes:
1165
1159
  art.includes = includes.$original; // original includes have been stored
1166
1160
  if (art.includes?.length)
1167
- applyIncludes( art, art );
1161
+ applyIncludes( art, art ); // apply includes provided with definition
1162
+ else if (art.elements) // next include only after elements so far
1163
+ art.$postponeInclude += addedElements( art );
1164
+
1168
1165
  art.includes = includes;
1169
1166
  // early propagation of specific annotation assignments
1170
1167
  // TODO: propagate in effectiveType() ?
@@ -1172,7 +1169,7 @@ function extend( model ) {
1172
1169
  propagateEarly( art, '@fiori.draft.enabled' );
1173
1170
  }
1174
1171
  if (art._extensions?.$add)
1175
- extendArtifact( art._extensions.$add, art );
1172
+ extendArtifact( art._extensions.$add, art ); // apply extensions
1176
1173
  checkRedefinitionThroughIncludes( art, 'elements' );
1177
1174
  checkRedefinitionThroughIncludes( art, 'actions' );
1178
1175
  }
@@ -1225,6 +1222,7 @@ function extend( model ) {
1225
1222
  }
1226
1223
 
1227
1224
  function extendMembers( extensions, art ) {
1225
+ // only called in extendArtifact()
1228
1226
  // TODO: do the whole extension stuff lazily if the elements are requested
1229
1227
  const elemExtensions = [];
1230
1228
  // if (art._main) // extensions already sorted for main artifacts
@@ -1246,7 +1244,9 @@ function extend( model ) {
1246
1244
  // 'Info', 'EXT').toString())
1247
1245
  setArtifactLink( ext.name, art ); // TODO: probably already done
1248
1246
  if (ext.includes)
1249
- applyIncludes( ext, art );
1247
+ applyIncludes( ext, art ); // moves included elem to extension(! TODO: recheck)
1248
+ else if (ext.elements)
1249
+ art.$postponeInclude += addedElements( ext );
1250
1250
 
1251
1251
  // console.log(ext,art)
1252
1252
  checkAnnotate( ext, art );
@@ -1364,19 +1364,21 @@ function extend( model ) {
1364
1364
  * Set property `_parent` for all elements in `parent` to `parent` and do so
1365
1365
  * recursively for all sub elements.
1366
1366
  *
1367
- * If not for extensions: construct === parent
1367
+ * If `construct !== parent`, also copy elements to `parent`.
1368
+ *
1369
+ * (Only) if called recursively: `construct === parent`
1368
1370
  *
1369
1371
  * TODO: separate extension!
1370
1372
  */
1371
1373
  function initMembers( construct, parent, block ) {
1374
+ // only called by extendMembers() and recursively
1372
1375
  // TODO: split extend from init
1376
+ const obj = initItemsLinks( construct, block );
1377
+ initExprAnnoBlock( construct, block, error );
1378
+ if (parent.targetAspect?.elements)
1379
+ parent = parent.targetAspect;
1373
1380
  const main = parent._main || parent;
1374
1381
  const isQueryExtension = construct.kind === 'extend' && main.query;
1375
- let obj = initItemsLinks( construct, block );
1376
- initExprAnnoBlock( construct, block );
1377
- const { targetAspect } = obj;
1378
- if (targetAspect?.elements)
1379
- initAnonymousAspect();
1380
1382
 
1381
1383
  if (obj !== parent && obj.elements && parent.enum) { // applying the extension
1382
1384
  initElementsAsEnum();
@@ -1436,42 +1438,6 @@ function extend( model ) {
1436
1438
  forEachGeneric( { enum: obj.elements }, 'enum', init );
1437
1439
  }
1438
1440
 
1439
- function initAnonymousAspect() {
1440
- // TODO: main?
1441
- const inEntity = parent._main?.kind === 'entity';
1442
- // TODO: also allow indirectly (component in component in entity)?
1443
- setLink( targetAspect, '_outer', obj );
1444
- setLink( targetAspect, '_parent', parent._parent );
1445
- setLink( targetAspect, '_main', null ); // for name resolution
1446
-
1447
- parent = targetAspect;
1448
- construct = parent; // avoid extension behavior
1449
- targetAspect.kind = 'aspect'; // TODO: probably '$aspect' to detect
1450
- setLink( targetAspect, '_block', block );
1451
- initDollarSelf( targetAspect );
1452
- // allow ref of up_ in anonymous aspect inside entity
1453
- // (TODO: complain if used and the managed composition is included into
1454
- // another entity - might induce auto-redirection):
1455
- if (inEntity && !targetAspect.elements.up_) {
1456
- const up = {
1457
- name: { id: 'up_' },
1458
- kind: '$navElement',
1459
- location: obj.location,
1460
- };
1461
- setLink( up, '_parent', targetAspect );
1462
- setLink( up, '_main', targetAspect ); // used on main artifact
1463
- // recompilation case: both target and targetAspect → allow up_ in that case, too:
1464
- const name = obj.target && resolveUncheckedPath( obj.target, 'target', obj );
1465
- const entity = name && model.definitions[name];
1466
- if (entity && entity.elements)
1467
- setLink( up, '_origin', entity.elements.up_ );
1468
- // processAspectComposition/expand() sets _origin to element of
1469
- // generated target entity
1470
- targetAspect.$tableAliases.up_ = up;
1471
- }
1472
- obj = targetAspect;
1473
- }
1474
-
1475
1441
  function init( elem, name, prop ) {
1476
1442
  if (!elem.name && !elem._outer) {
1477
1443
  const ref = elem.targetElement || elem.kind === 'element' && elem.value;
@@ -1670,6 +1636,7 @@ function extend( model ) {
1670
1636
  // ...ext.includes.map( r => r._artifact?.name.id ));
1671
1637
  if (!art.query && art.elements) // do not set art.elements and art.enums with query entity!
1672
1638
  includeMembers( ext, art, 'elements' );
1639
+
1673
1640
  if (art.kind !== 'type') {
1674
1641
  includeMembers( ext, art, 'actions' );
1675
1642
  }
@@ -1696,7 +1663,7 @@ function extend( model ) {
1696
1663
  // TODO two kind of messages:
1697
1664
  // Error 'More than one include defines element "A"' (at include ref)
1698
1665
  // Warning 'Overwrites definition from include "I" (at elem def)
1699
- const propagateKeys = art.kind !== 'type' || !model.options.v7KeyPropagation;
1666
+ const propagateKeys = art.kind !== 'type' || model.options.v6KeyPropagation;
1700
1667
  const parent = ext === art && art;
1701
1668
  const members = ext[prop];
1702
1669
  // if (members)console.log( 'EXT:', prop, art.kind, art.name.id, ...Object.keys(members));
@@ -1704,9 +1671,29 @@ function extend( model ) {
1704
1671
  ext[prop] = Object.create( null );
1705
1672
  ext[prop][$location] = members[$location];
1706
1673
  }
1674
+ let elementsAdded = 0;
1707
1675
  let hasNewElement = false;
1708
-
1709
1676
  for (const ref of ext.includes) {
1677
+ // Elements of include are added after previously provided elements:
1678
+ if (prop === 'elements') {
1679
+ if (ref.$postponeInclude == null) {
1680
+ if (ref._artifact?.elements)
1681
+ ref.$postponeInclude = art.$postponeInclude || 0;
1682
+ }
1683
+ else if (ref.$postponeInclude > 0 && members) {
1684
+ let added = elementsAdded;
1685
+ let postpone = ref.$postponeInclude;
1686
+ elementsAdded += postpone;
1687
+ forEachInOrder( { [prop]: members }, prop, ( elem, name ) => {
1688
+ // The element could have been added in the previous loop (includes) to keep
1689
+ // the element order.
1690
+ if (--added < 0 && --postpone >= 0 && ext[prop][name] !== elem )
1691
+ dictAdd( ext[prop], name, elem );
1692
+ } );
1693
+ }
1694
+ if (ref.$postponeInclude != null) // not reset after bare aspect
1695
+ art.$postponeInclude = 0; // elements of next include directly after this one
1696
+ }
1710
1697
  const template = ref._artifact; // already resolved
1711
1698
  if (template) { // be robust
1712
1699
  if (template[prop] && !ext[prop])
@@ -1724,7 +1711,7 @@ function extend( model ) {
1724
1711
  if (checkForLocalized && (name === 'texts' || name === 'localized'))
1725
1712
  return;
1726
1713
  if (members && members[name]) {
1727
- if (!includesNonShadowedFirst && !ext[prop][name])
1714
+ if (!ext[prop][name])
1728
1715
  dictAdd( ext[prop], name, members[name] ); // to keep order
1729
1716
  return;
1730
1717
  }
@@ -1734,8 +1721,6 @@ function extend( model ) {
1734
1721
  if (!parent) // not yet set for EXTEND foo WITH bar => linkToOrigin() did not add it
1735
1722
  dictAdd( ext[prop], name, elem );
1736
1723
  elem.$inferred = 'include';
1737
- if (origin.masked) // TODO(v6): remove 'masked'
1738
- elem.masked = Object.assign( { $inferred: 'include' }, origin.masked );
1739
1724
  if (origin.key && propagateKeys)
1740
1725
  elem.key = Object.assign( { $inferred: 'include' }, origin.key );
1741
1726
  if (origin.value && origin.$syntax === 'calc') {
@@ -1754,14 +1739,20 @@ function extend( model ) {
1754
1739
  }
1755
1740
  if (!hasNewElement && members) {
1756
1741
  ext[prop] = members;
1742
+ if (prop === 'elements')
1743
+ art.$postponeInclude += addedElements( ext );
1757
1744
  }
1758
1745
  else if (members) {
1746
+ let added = elementsAdded;
1759
1747
  // TODO: expand elements having direct elements (if needed)
1760
1748
  forEachInOrder( { [prop]: members }, prop, ( elem, name ) => {
1761
1749
  // The element could have been added in the previous loop (includes) to keep
1762
1750
  // the element order.
1763
- if (ext[prop][name] !== elem )
1751
+ if (--added < 0 && ext[prop][name] !== elem ) {
1764
1752
  dictAdd( ext[prop], name, elem );
1753
+ if (prop === 'elements' && elem.kind !== 'extend')
1754
+ ++art.$postponeInclude;
1755
+ }
1765
1756
  } );
1766
1757
  }
1767
1758
  }
@@ -47,6 +47,7 @@ function generate( model ) {
47
47
  const textsAspect = model.definitions['sap.common.TextsAspect'];
48
48
  let enableLanguageAssoc = null; // → addLanguageAssoc()
49
49
  let enableTextsAspect = null; // → useTextsAspect()
50
+ const copyJournal = !isDeprecatedEnabled( options, '_noPersistenceJournalForGeneratedEntities' );
50
51
  return;
51
52
 
52
53
  /**
@@ -725,7 +726,7 @@ function generate( model ) {
725
726
  // annotations (remark: adding elements is not allowed for generated artifacts):
726
727
  extendArtifactBefore( art );
727
728
  setGenExtensions( art, { _composition: elem } );
728
- setLink( art, '_origin', target ); // TODO: does this hurt?
729
+ setLink( art, '_origin', target ); // needed especially with anonymous target aspect
729
730
  return art;
730
731
  }
731
732
 
@@ -746,7 +747,7 @@ function generate( model ) {
746
747
  }
747
748
 
748
749
  if (targetAspect.name) { // named target aspect
749
- if (!isDeprecatedEnabled( options, 'noCompositionIncludes' )) {
750
+ if (!isDeprecatedEnabled( options, '_noCompositionIncludes' )) {
750
751
  art.includes = [ createInclude( targetAspect.name.id, location ) ];
751
752
  art.includes.$origin = []; // included elements after up_
752
753
  // TODO: propagate in effectiveType()
@@ -790,8 +791,6 @@ function generate( model ) {
790
791
  setLink( proxy, '_block', origin._block );
791
792
  if (location)
792
793
  proxy.$inferred = inferred;
793
- if (origin.masked)
794
- proxy.masked = Object.assign( { $inferred: 'include' }, origin.masked );
795
794
  if (origin.key)
796
795
  proxy.key = Object.assign( { $inferred: 'include' }, origin.key );
797
796
  if (origin.value && origin.$syntax === 'calc') {
@@ -824,13 +823,10 @@ function generate( model ) {
824
823
  return;
825
824
 
826
825
  // Copied since v6
827
- const copyJournal = !isDeprecatedEnabled( options, 'noPersistenceJournalForGeneratedEntities' );
828
826
  if (copyJournal)
829
827
  copy( '@cds.persistence.journal' );
830
828
 
831
- const copyExists = !isDeprecatedEnabled( options, '_eagerPersistenceForGeneratedEntities' );
832
- if (copyExists)
833
- copy( '@cds.persistence.exists' );
829
+ copy( '@cds.persistence.exists' );
834
830
  copy( '@cds.persistence.skip' );
835
831
  copy( '@cds.tenant.independent' );
836
832
 
@@ -459,8 +459,7 @@ function populate( model ) {
459
459
  const location = weakRefLocation( ref ) || weakLocation( art.location );
460
460
  // console.log( message( null, location, art, {target:struct,art}, 'Info','EXPAND-ELEM')
461
461
  // .toString(), Object.keys(struct.elements))
462
- proxyCopyMembers( art, 'elements', struct.elements, location,
463
- null, isDeprecatedEnabled( options, '_noKeyPropagationWithExpansions' ) );
462
+ proxyCopyMembers( art, 'elements', struct.elements, location, null );
464
463
  // Set elements expansion status (the if condition is always true, as no
465
464
  // elements expansion will take place on artifact with existing other
466
465
  // member property):
@@ -724,9 +723,8 @@ function populate( model ) {
724
723
  // (we might have those already)
725
724
  if (alias.kind === 'mixin' || alias.kind === '$self')
726
725
  return;
727
- if (!alias.elements) // could be false in hierarchical JOIN - TODO: necessary?
728
- effectiveType( alias ); // element → $navElement expansion for $tableAlias
729
-
726
+ if (!effectiveType( alias )) // not found or cyclic
727
+ return;
730
728
  forEachGeneric( { elements: alias.elements }, 'elements', ( elem, name ) => {
731
729
  if (elem.$duplicates !== true)
732
730
  dictAddArray( query._combined, name, elem, null ); // not dictAdd()
@@ -887,8 +885,7 @@ function populate( model ) {
887
885
 
888
886
  for (const name in env) {
889
887
  const navElem = env[name];
890
- // TODO: remove all access to masked (use 'grep')
891
- if (excludingDict[name] || navElem.masked?.val)
888
+ if (excludingDict[name])
892
889
  continue;
893
890
  const sibling = siblingElements[name];
894
891
  if (sibling) { // is explicitly provided (without duplicate)
@@ -37,7 +37,6 @@ function propagate( model ) {
37
37
  // TODO(!): think of having an extra XSN property for calculated elements,
38
38
  // replacing `value:…`+`$syntax:'calc'` and `$calc:…
39
39
  $calc: enumOrCalcValue,
40
- // masked: special = done in definer
41
40
  // key: special = done in resolver
42
41
  // actions: struct includes & primary source = in definer/resolver
43
42
  type: notWithExpand,
@@ -65,7 +64,7 @@ function propagate( model ) {
65
64
  };
66
65
  const ruleToFunction = {
67
66
  __proto__: null,
68
- never,
67
+ never: onlyViaParent,
69
68
  onlyViaArtifact,
70
69
  onlyViaParent,
71
70
  notWithPersistenceTable,
@@ -165,10 +164,11 @@ function propagate( model ) {
165
164
  const viaType = target.type && // TODO: falsy $inferred value instead of 'cast'?
166
165
  (!target.type.$inferred || target.type.$inferred === 'cast');
167
166
  const keys = Object.keys( source );
168
- // console.log('PROPS:',ref(source),'->',ref(target),keys.join('+'))
167
+ // console.log('PROPS:',source.name.id,'->',target.name.id,keys.join('+'))
169
168
  for (const prop of keys) {
170
169
  // TODO: warning with competing props from multi-includes, but not in propagator.js
171
- if (target[prop] !== undefined &&
170
+ if ((target[prop] !== undefined && target[prop]?.$inferred !== 'prop-null') &&
171
+ // do not propagate into calculated element from calc expression:
172
172
  (prop !== 'value' || !source.$calcDepElement || !target._main?.query) ||
173
173
  source[prop] === undefined)
174
174
  continue;
@@ -178,8 +178,6 @@ function propagate( model ) {
178
178
  }
179
179
  }
180
180
 
181
- function never() { /* no-op: don't propagate */ }
182
-
183
181
  function always( prop, target, source ) {
184
182
  const val = source[prop];
185
183
  if (Array.isArray( val )) {
@@ -261,6 +259,7 @@ function propagate( model ) {
261
259
  }
262
260
 
263
261
  // Only propagate if parent object (which is not necessarily `_parent`) was propagated.
262
+ // (part of a deep-copy)
264
263
  function onlyViaParent( prop, target, source ) {
265
264
  if (target.$inferred === 'proxy' || target.$inferred === 'expanded')
266
265
  // assocs and enums do not have 'include'
@@ -316,14 +315,15 @@ function propagate( model ) {
316
315
 
317
316
  function annotation( prop, target, source ) {
318
317
  const anno = source[prop];
319
- if (anno.val !== null)
320
- withKind( prop, target, source ); // TODO: unfold
318
+ withKind( prop, target, source );
319
+ if (anno.val === null && target[prop]?.$inferred === 'prop')
320
+ target[prop].$inferred = 'prop-null';
321
321
  }
322
322
 
323
323
  function docComment( prop, target, source ) {
324
324
  if (model.options.propagateDocComments)
325
325
  annotation( prop, target, source );
326
- else // TODO: or just "never"
326
+ else
327
327
  onlyViaParent( prop, target, source );
328
328
  }
329
329