@sap/cds-compiler 5.9.4 → 6.0.12

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 (114) hide show
  1. package/CHANGELOG.md +117 -319
  2. package/README.md +1 -1
  3. package/bin/cds_update_identifiers.js +3 -5
  4. package/bin/cdsc.js +24 -9
  5. package/bin/cdshi.js +1 -1
  6. package/bin/cdsse.js +4 -4
  7. package/doc/CHANGELOG_BETA.md +11 -0
  8. package/doc/CHANGELOG_DEPRECATED.md +29 -0
  9. package/lib/api/main.js +8 -5
  10. package/lib/api/options.js +12 -10
  11. package/lib/base/builtins.js +1 -0
  12. package/lib/base/message-registry.js +191 -99
  13. package/lib/base/messages.js +35 -21
  14. package/lib/base/model.js +14 -24
  15. package/lib/checks/actionsFunctions.js +10 -20
  16. package/lib/checks/annotationsOData.js +1 -1
  17. package/lib/checks/elements.js +35 -10
  18. package/lib/checks/enums.js +31 -0
  19. package/lib/checks/foreignKeys.js +2 -2
  20. package/lib/checks/hasPersistedElements.js +5 -0
  21. package/lib/checks/invalidTarget.js +1 -1
  22. package/lib/checks/managedWithoutKeys.js +5 -4
  23. package/lib/checks/queryNoDbArtifacts.js +10 -8
  24. package/lib/checks/types.js +5 -5
  25. package/lib/checks/validator.js +6 -4
  26. package/lib/compiler/assert-consistency.js +13 -9
  27. package/lib/compiler/checks.js +20 -52
  28. package/lib/compiler/define.js +31 -6
  29. package/lib/compiler/extend.js +5 -1
  30. package/lib/compiler/generate.js +14 -17
  31. package/lib/compiler/populate.js +8 -31
  32. package/lib/compiler/propagator.js +21 -35
  33. package/lib/compiler/resolve.js +64 -29
  34. package/lib/compiler/shared.js +16 -4
  35. package/lib/compiler/tweak-assocs.js +1 -1
  36. package/lib/compiler/utils.js +1 -1
  37. package/lib/edm/annotations/edmJson.js +23 -20
  38. package/lib/edm/annotations/genericTranslation.js +12 -10
  39. package/lib/edm/csn2edm.js +50 -56
  40. package/lib/edm/edm.js +33 -28
  41. package/lib/edm/edmInboundChecks.js +2 -2
  42. package/lib/edm/edmPreprocessor.js +54 -88
  43. package/lib/edm/edmUtils.js +9 -12
  44. package/lib/gen/BaseParser.js +63 -52
  45. package/lib/gen/CdlGrammar.checksum +1 -1
  46. package/lib/gen/CdlParser.js +1153 -1165
  47. package/lib/gen/Dictionary.json +21 -1
  48. package/lib/json/from-csn.js +70 -43
  49. package/lib/json/to-csn.js +6 -8
  50. package/lib/language/multiLineStringParser.js +3 -2
  51. package/lib/main.d.ts +58 -24
  52. package/lib/model/cloneCsn.js +3 -0
  53. package/lib/model/csnUtils.js +28 -39
  54. package/lib/model/xprAsTree.js +23 -9
  55. package/lib/modelCompare/compare.js +5 -4
  56. package/lib/optionProcessor.js +24 -17
  57. package/lib/parsers/AstBuildingParser.js +81 -25
  58. package/lib/parsers/XprTree.js +57 -3
  59. package/lib/parsers/identifiers.js +1 -1
  60. package/lib/parsers/index.js +0 -3
  61. package/lib/render/manageConstraints.js +25 -25
  62. package/lib/render/toCdl.js +173 -170
  63. package/lib/render/toHdbcds.js +126 -128
  64. package/lib/render/toRename.js +7 -7
  65. package/lib/render/toSql.js +128 -125
  66. package/lib/render/utils/common.js +47 -22
  67. package/lib/render/utils/delta.js +25 -25
  68. package/lib/render/utils/operators.js +2 -2
  69. package/lib/render/utils/pretty.js +5 -5
  70. package/lib/render/utils/sql.js +13 -13
  71. package/lib/render/utils/standardDatabaseFunctions.js +115 -103
  72. package/lib/render/utils/unique.js +4 -4
  73. package/lib/transform/db/applyTransformations.js +1 -1
  74. package/lib/transform/db/assertUnique.js +2 -2
  75. package/lib/transform/db/associations.js +6 -7
  76. package/lib/transform/db/assocsToQueries/utils.js +4 -5
  77. package/lib/transform/db/backlinks.js +12 -9
  78. package/lib/transform/db/cdsPersistence.js +8 -7
  79. package/lib/transform/db/constraints.js +13 -10
  80. package/lib/transform/db/expansion.js +7 -3
  81. package/lib/transform/db/flattening.js +4 -14
  82. package/lib/transform/db/processSqlServices.js +2 -1
  83. package/lib/transform/db/temporal.js +5 -7
  84. package/lib/transform/db/views.js +2 -4
  85. package/lib/transform/draft/db.js +8 -8
  86. package/lib/transform/draft/odata.js +10 -7
  87. package/lib/transform/forOdata.js +10 -5
  88. package/lib/transform/forRelationalDB.js +5 -75
  89. package/lib/transform/localized.js +1 -1
  90. package/lib/transform/odata/createForeignKeys.js +11 -10
  91. package/lib/transform/odata/flattening.js +8 -4
  92. package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +96 -0
  93. package/lib/transform/odata/typesExposure.js +3 -3
  94. package/lib/transform/transformUtils.js +4 -8
  95. package/lib/transform/translateAssocsToJoins.js +14 -7
  96. package/lib/transform/universalCsn/universalCsnEnricher.js +10 -4
  97. package/lib/utils/objectUtils.js +0 -17
  98. package/package.json +10 -13
  99. package/share/messages/def-upcoming-virtual-change.md +1 -1
  100. package/LICENSE +0 -37
  101. package/bin/cds_remove_invalid_whitespace.js +0 -138
  102. package/doc/CHANGELOG_ARCHIVE.md +0 -3604
  103. package/lib/gen/genericAntlrParser.js +0 -3
  104. package/lib/gen/language.checksum +0 -1
  105. package/lib/gen/language.interp +0 -456
  106. package/lib/gen/language.tokens +0 -180
  107. package/lib/gen/languageLexer.interp +0 -439
  108. package/lib/gen/languageLexer.js +0 -1483
  109. package/lib/gen/languageLexer.tokens +0 -167
  110. package/lib/gen/languageParser.js +0 -24941
  111. package/lib/language/antlrParser.js +0 -205
  112. package/lib/language/errorStrategy.js +0 -646
  113. package/lib/language/genericAntlrParser.js +0 -1572
  114. package/lib/parsers/CdlGrammar.g4 +0 -2070
@@ -16,12 +16,10 @@ const {
16
16
  forEachMember,
17
17
  forEachMemberRecursively,
18
18
  isDeprecatedEnabled,
19
- isBetaEnabled,
20
19
  } = require('../base/model');
21
20
  const { typeParameters } = require('./builtins');
22
21
  const { propagationRules } = require('../base/builtins');
23
22
  const { annotationVal } = require('./utils');
24
- const { functionsWithoutParentheses } = require('../parsers/identifiers');
25
23
 
26
24
  const $location = Symbol.for( 'cds.$location' );
27
25
 
@@ -95,8 +93,6 @@ function check( model ) {
95
93
  checkTypeStructure( art );
96
94
  checkAssociation( art ); // type def could be assoc
97
95
  checkDefaultValue( art );
98
- if (art.kind === 'enum')
99
- checkEnum( art );
100
96
  checkEnumType( art );
101
97
  }
102
98
 
@@ -332,26 +328,6 @@ function check( model ) {
332
328
  }
333
329
  }
334
330
 
335
- /**
336
- * The enumNode is a single enum element and not the whole type.
337
- *
338
- * @param {XSN.Artifact} enumNode
339
- */
340
- function checkEnum( enumNode ) {
341
- if (!enumNode.value)
342
- return;
343
-
344
- const type = enumNode.value.literal;
345
- const loc = enumNode.value.location;
346
-
347
- // Special handling to print a more detailed error message.
348
- // Other cases like `null` as enum value are handled in `checkEnumValueType()`
349
- if (type === 'enum') {
350
- warning( 'ref-unexpected-enum', [ loc, enumNode ], {},
351
- 'References to other values are not allowed as enum values' );
352
- }
353
- }
354
-
355
331
  function checkEnumType( enumNode ) {
356
332
  // Either the type is an enum or an arrayed enum. We are only interested in
357
333
  // the enum and don't care whether the enum is arrayed.
@@ -563,8 +539,7 @@ function check( model ) {
563
539
 
564
540
  // Check that "not null" artifacts don't have `null` default values.
565
541
  // At least one property must be written explicitly to avoid reporting on inferred elements.
566
- if (isBetaEnabled( model.options, 'v6preview' ) &&
567
- (art.default?.val === null || art.notNull?.val)) {
542
+ if (art.default?.val === null || art.notNull?.val) {
568
543
  const notNullValue = getInheritedProp( art, 'notNull' );
569
544
  if (notNullValue?.val && defaultValue?.val === null) {
570
545
  const loc = (art.default || art.notNull)?.location || art.location;
@@ -585,6 +560,7 @@ function check( model ) {
585
560
  } );
586
561
  }
587
562
  else if (art._effectiveType?.elements) {
563
+ // TODO: error for v7
588
564
  warning( 'type-unexpected-default-struct', [ defaultValue.location, art ], {
589
565
  '#': art.kind, keyword: 'default',
590
566
  }, {
@@ -779,24 +755,29 @@ function check( model ) {
779
755
  * Let users know about it.
780
756
  *
781
757
  * @param elem
758
+ *
759
+ * TODO: simplify if the old parser is gone (query-invalid-virtual-struct stays)
782
760
  */
783
761
  function checkVirtualSelectItemChangeForV6( elem ) {
784
762
  if (
785
763
  !elem.virtual?.val || elem.virtual.$inferred || // not explicitly marked virtual
786
- elem.type && !elem.type.$inferred || // has explicit type
787
- elem.value.path?.length > 1 || // multi-path step, i.e. no new definition
788
- !elem.name.$inferred // has explicit alias
764
+ !elem.name.$inferred || // has explicit alias
765
+ elem._columnParent || // virtual inside expand/inline is already error
766
+ elem._parent.kind === 'element' || // dito (expand without ref)
767
+ !elem.value?.path && !elem.value?.func || // neither ref nor function call
768
+ elem.value.args || // arguments (with function call)
769
+ elem.value.path?.length > 1 || // multi-path step, i.e. no new definition
770
+ elem.value.path?.some( ps => ps.args || ps.where ) // not a simple reference
789
771
  )
790
772
  return;
791
773
 
792
- if ((!elem.value.path || !elem.value._artifact) &&
793
- !isFunctionWithoutParentheses( elem ))
794
- return; // not a reference nor function without parentheses
795
-
796
- if (elem.value.path?.some(ps => ps.args || ps.where))
797
- return; // not a simple reference
798
-
799
- warning('def-upcoming-virtual-change', [ elem.virtual.location, elem ], { name: elem.name.id });
774
+ if (elem.expand) {
775
+ warning( 'query-invalid-virtual-struct', [ elem.expand[$location], elem ],
776
+ { code: `as ${ elem.name.id }` } );
777
+ }
778
+ else {
779
+ error( 'def-upcoming-virtual-change', [ elem.virtual.location, elem ] );
780
+ }
800
781
  }
801
782
 
802
783
  function checkCalculatedElementValue( elem ) {
@@ -991,7 +972,7 @@ function check( model ) {
991
972
 
992
973
  function checkAnnotationAssignment1( art, anno ) {
993
974
  const name = anno.name?.id;
994
- if (art.$contains?.$annotation && anno.kind === '$annotation') {
975
+ if (art.$contains?.$annotation && anno.kind === '$annotation' && anno._outer) {
995
976
  if (checkAnnotationAcceptsExpressions( anno, art ))
996
977
  checkAnnotationExpressions( anno, art );
997
978
  }
@@ -1144,7 +1125,7 @@ function check( model ) {
1144
1125
  return;
1145
1126
  }
1146
1127
 
1147
- // Struct expected (can only happen within arrays)?
1128
+ // Struct expected (can only happen within arrays, or CSN input)?
1148
1129
  if (elementDecl._effectiveType.elements) {
1149
1130
  if (value.literal !== 'struct') {
1150
1131
  warning( null, loc, { anno }, 'A struct value is required here for annotation $(ANNO)' );
@@ -1385,17 +1366,4 @@ function isComplexView( art ) {
1385
1366
  return (!art.query.from?._artifact || art.query.from._artifact.kind === 'element');
1386
1367
  }
1387
1368
 
1388
- /**
1389
- * @param {XSN.Element} elem
1390
- * @returns {boolean}
1391
- */
1392
- function isFunctionWithoutParentheses( elem ) {
1393
- const func = elem.value?.func;
1394
- if (!func?.path || elem.value.args)
1395
- return false;
1396
- if (func.path.length !== 1)
1397
- return false;
1398
- return functionsWithoutParentheses.includes(func.path[0].id.toUpperCase());
1399
- }
1400
-
1401
1369
  module.exports = check;
@@ -298,6 +298,7 @@ function define( model ) {
298
298
  return;
299
299
  }
300
300
  setLink( art, '_block', block );
301
+ initExprAnnoBlock( art, block );
301
302
  // dictAdd might set $duplicates
302
303
  dictAdd( model.definitions, absolute, art );
303
304
  }
@@ -393,6 +394,7 @@ function define( model ) {
393
394
 
394
395
  function addExtension( ext, block ) {
395
396
  setLink( ext, '_block', block );
397
+ initExprAnnoBlock( ext, block );
396
398
  const absolute = ext.name && resolveUncheckedPath( ext.name, '_extensions', ext );
397
399
  if (!absolute) // broken path
398
400
  return;
@@ -429,6 +431,7 @@ function define( model ) {
429
431
  if (prop === 'params' && name === '') // RETURNS
430
432
  sub.name = { id: '', location: sub.location };
431
433
  setLink( sub, '_block', parent._block );
434
+ initExprAnnoBlock( sub, parent._block );
432
435
  setLink( sub, '_parent', parent );
433
436
  setLink( sub, '_main', parent._main || parent );
434
437
  initExtension( sub );
@@ -446,6 +449,26 @@ function define( model ) {
446
449
  }
447
450
  }
448
451
 
452
+ function initExprAnnoBlock( art, block ) {
453
+ // remark: `art` could also be the extension
454
+ for (const prop in art) {
455
+ if (prop.charAt(0) !== '@')
456
+ continue;
457
+ const anno = art[prop];
458
+ // _block links needed for `cast( … as Type )`, `[ ..., cast( … as Type ) ]`
459
+ if (anno.literal === 'array') {
460
+ anno.kind = '$annotation';
461
+ for (const item of anno.val)
462
+ setLink( item, '_block', block );
463
+ }
464
+ else if (anno.$tokenTexts) {
465
+ // remark: it wouldn't hurt to set it always...
466
+ anno.kind = '$annotation';
467
+ setLink( anno, '_block', block );
468
+ }
469
+ }
470
+ }
471
+
449
472
  function addVocabulary( vocab, block, prefix ) {
450
473
  setLink( vocab, '_block', block );
451
474
  const { name } = vocab;
@@ -982,11 +1005,11 @@ function define( model ) {
982
1005
  col: 'You have provided a $(PROP) already at line $(LINE), column $(COL)',
983
1006
  } );
984
1007
  // TODO: extra text variants for expand/inline? - probably not
985
- col.val = null; // do not consider it for expandWildcard()
1008
+ col.val = '**'; // do not consider it for expandWildcard()
986
1009
  }
987
1010
  }
988
- // Either expression (value), expand or new association (target && type)
989
- else if (col.value || col.expand || (col.target && col.type)) {
1011
+ // Either expression (value), expand, new virtual or new association
1012
+ else if (col.value || col.name) {
990
1013
  if (!col._block)
991
1014
  setLink( col, '_block', parent._block );
992
1015
  if (col.inline) { // `@anno elem.{ * }` does not work
@@ -1009,6 +1032,7 @@ function define( model ) {
1009
1032
  }
1010
1033
 
1011
1034
  initItemsLinks( col, parent._block );
1035
+ initExprAnnoBlock( col, parent._block );
1012
1036
  }
1013
1037
 
1014
1038
  if (hasItems && !wildcard && parent.excludingDict && !options.$recompile) {
@@ -1069,6 +1093,7 @@ function define( model ) {
1069
1093
  const main = parent._main || parent;
1070
1094
  const isQueryExtension = construct.kind === 'extend' && main.query;
1071
1095
  let obj = initItemsLinks( construct, block );
1096
+ initExprAnnoBlock( construct, block );
1072
1097
  if (obj.target && targetIsTargetAspect( obj )) {
1073
1098
  obj.targetAspect = obj.target;
1074
1099
  delete obj.target;
@@ -1096,7 +1121,7 @@ function define( model ) {
1096
1121
  forEachGeneric( obj, 'enum', init );
1097
1122
  }
1098
1123
 
1099
- if (obj.foreignKeys) // cannot be extended or annotated - TODO: check anyway?
1124
+ if (obj.foreignKeys)
1100
1125
  forEachInOrder( obj, 'foreignKeys', init );
1101
1126
  if (checkDefinitions( construct, parent, 'actions' ))
1102
1127
  forEachGeneric( construct, 'actions', init );
@@ -1136,8 +1161,8 @@ function define( model ) {
1136
1161
  }
1137
1162
  if (hasElement) {
1138
1163
  // This message is similar to the one above. In v6, we could probably
1139
- // turn this warning into an error, remove `$syntax: 'element' (also in
1140
- // language.g4), and use the above `ext-unexpected-element` only for CSN input.
1164
+ // turn this warning into an error, remove `$syntax: 'element',
1165
+ // and use the above `ext-unexpected-element` only for CSN input.
1141
1166
  warning( 'ext-expecting-enum', [ obj.elements[$location], construct ],
1142
1167
  { code: 'extend … with enum' }, 'Use $(CODE) when extending enums' );
1143
1168
  }
@@ -74,7 +74,8 @@ function extend( model ) {
74
74
  applyIncludes, // TODO: re-check
75
75
  } );
76
76
 
77
- const includesNonShadowedFirst = isDeprecatedEnabled( model.options, 'includesNonShadowedFirst' );
77
+ const includesNonShadowedFirst
78
+ = isDeprecatedEnabled( model.options, '_includesNonShadowedFirst' );
78
79
 
79
80
  sortModelSources();
80
81
  const extensionsDict = Object.create( null ); // TODO TMP
@@ -489,6 +490,7 @@ function extend( model ) {
489
490
  const location = firstEllipsis.location || anno.name.location;
490
491
  message( 'anno-unexpected-ellipsis', [ location, art ], { code: '...' } );
491
492
  previousAnno = {
493
+ kind: '$annotation',
492
494
  val: [],
493
495
  literal: 'array',
494
496
  name: { id: annoName.slice( 1 ) },
@@ -499,6 +501,7 @@ function extend( model ) {
499
501
  // TODO: If we introduce sub-messages, point to the non-array base value.
500
502
  error( 'anno-mismatched-ellipsis', [ anno.name.location, art ], { code: '...' } );
501
503
  previousAnno = {
504
+ kind: '$annotation',
502
505
  val: [],
503
506
  literal: 'array',
504
507
  name: previousAnno.name,
@@ -532,6 +535,7 @@ function extend( model ) {
532
535
  }
533
536
  // console.log('TP:',previousValue.map(se),anno.val.map(se),'->',result.map(se))
534
537
  return {
538
+ kind: '$annotation',
535
539
  val: result,
536
540
  literal: 'array',
537
541
  name: previousAnno.name,
@@ -696,7 +696,7 @@ function generate( model ) {
696
696
  $inferred: 'composition-entity',
697
697
  };
698
698
  if (target.name) { // named target aspect
699
- if (options.compositionIncludes !== false) // default is 'true' since v5.9
699
+ if (!isDeprecatedEnabled( options, 'noCompositionIncludes' ))
700
700
  art.includes = [ createInclude( target.name.id, location ) ];
701
701
  setLink( art, '_origin', target );
702
702
  setLink( art, '_upperAspects', [ target, ...(elem._main._upperAspects || []) ] );
@@ -721,20 +721,11 @@ function generate( model ) {
721
721
  location,
722
722
  },
723
723
  };
724
- // By default, 'up_' is a managed primary key association.
725
- // If 'up_' shall be rendered unmanaged, infer the parent
726
- // primary keys and add the ON condition
727
- if (isDeprecatedEnabled( options, '_unmanagedUpInComponent' )) {
728
- addProxyElements( art, keys, 'aspect-composition', target.name && location,
729
- 'up__', '@odata.containment.ignore' );
730
- up.on = augmentEqual( location, 'up_', Object.values( keys ), 'up__' );
731
- }
732
- else {
733
- up.key = { location, val: true };
734
- // managed associations must be explicitly set to not null
735
- // even if target cardinality is 1..1
736
- up.notNull = { location, val: true };
737
- }
724
+
725
+ up.key = { location, val: true };
726
+ // managed associations must be explicitly set to not null
727
+ // even if target cardinality is 1..1
728
+ up.notNull = { location, val: true };
738
729
 
739
730
  dictAdd( art.elements, 'up_', up );
740
731
  // Only for named aspects, use a new location; otherwise use the origin's one.
@@ -753,7 +744,7 @@ function generate( model ) {
753
744
  // Copy persistence annotations from aspect.
754
745
  copyPersistenceAnnotations( art, target ); // after extendArtifactBefore()
755
746
 
756
- if (options.compositionIncludes !== false && art.includes)
747
+ if (!isDeprecatedEnabled( options, 'noCompositionIncludes' ) && art.includes)
757
748
  applyIncludes( art, art ); // for actions
758
749
  return art;
759
750
  }
@@ -798,12 +789,18 @@ function generate( model ) {
798
789
  if (!source)
799
790
  return;
800
791
 
801
- const copyExists = !isDeprecatedEnabled( options, 'eagerPersistenceForGeneratedEntities' );
792
+ // Copied since v6
793
+ const copyJournal = !isDeprecatedEnabled( options, 'noPersistenceJournalForGeneratedEntities' );
794
+ if (copyJournal)
795
+ copy( '@cds.persistence.journal' );
796
+
797
+ const copyExists = !isDeprecatedEnabled( options, '_eagerPersistenceForGeneratedEntities' );
802
798
  if (copyExists)
803
799
  copy( '@cds.persistence.exists' );
804
800
  copy( '@cds.persistence.skip' );
805
801
  copy( '@cds.tenant.independent' );
806
802
 
803
+ /** @param {string} anno */
807
804
  function copy( anno ) {
808
805
  if ( source[anno] && !target[anno] )
809
806
  target[anno] = { ...source[anno], $inferred: 'parent-origin' };
@@ -91,16 +91,6 @@ function populate( model ) {
91
91
  /** @type {any} may also be a boolean */
92
92
  let newAutoExposed = [];
93
93
 
94
- const scopedRedirections
95
- = !isDeprecatedEnabled( options, '_shortAutoexposed' ) &&
96
- !isDeprecatedEnabled( options, '_longAutoexposed' ) &&
97
- !isDeprecatedEnabled( options, '_noInheritedAutoexposeViaComposition' ) &&
98
- !isDeprecatedEnabled( options, '_noScopedRedirections' );
99
- const autoexposeViaComposition
100
- = (isDeprecatedEnabled( options, '_noInheritedAutoexposeViaComposition' ))
101
- ? 'Composition'
102
- : true;
103
- const redirectInSubQueries = isDeprecatedEnabled( options, '_redirectInSubQueries' );
104
94
  const ignoreSpecifiedElements
105
95
  = isDeprecatedEnabled( model.options, 'ignoreSpecifiedQueryElements' );
106
96
 
@@ -413,7 +403,7 @@ function populate( model ) {
413
403
  // console.log( message( null, location, art, {target:struct,art}, 'Info','EXPAND-ELEM')
414
404
  // .toString(), Object.keys(struct.elements))
415
405
  proxyCopyMembers( art, 'elements', struct.elements, location,
416
- null, isDeprecatedEnabled( options, 'noKeyPropagationWithExpansions' ) );
406
+ null, isDeprecatedEnabled( options, '_noKeyPropagationWithExpansions' ) );
417
407
  // Set elements expansion status (the if condition is always true, as no
418
408
  // elements expansion will take place on artifact with existing other
419
409
  // member property):
@@ -750,8 +740,8 @@ function populate( model ) {
750
740
  const siblings = wildcardSiblings( columns, query );
751
741
  expandWildcard( col, siblings, inlineHead, query );
752
742
  }
753
- // If neither expression (value), expand nor new association.
754
- if (!col.value && !col.expand && !(col.target && col.type))
743
+ // If neither expression (value), expand, new virtual nor new association.
744
+ if (!col.value && !col.name)
755
745
  continue; // error should have been reported by parser
756
746
  if (col.inline) {
757
747
  const q = userQuery( query );
@@ -794,7 +784,7 @@ function populate( model ) {
794
784
  function ensureColumnName( col, colIndex, query, insideExpand ) {
795
785
  if (col.name)
796
786
  return col.name.id;
797
- if (col.inline || col.val === '*')
787
+ if (col.inline || col.val === '*' || col.val === '**') // '**' = duplicate '*'
798
788
  return '';
799
789
  const path = col.value &&
800
790
  (col.value.path || !col.value.args && col.value.func?.path);
@@ -1085,7 +1075,7 @@ function populate( model ) {
1085
1075
  if (assoc.kind === 'mixin')
1086
1076
  return false;
1087
1077
  const query = userQuery( assoc );
1088
- return redirectInSubQueries || !query || query._main._leadingQuery === query;
1078
+ return !query || query._main._leadingQuery === query;
1089
1079
  }
1090
1080
 
1091
1081
  function redirectImplicitlyDo( elem, assoc, target, service ) {
@@ -1096,8 +1086,7 @@ function populate( model ) {
1096
1086
  // the current main artifact is a suitable auto-redirection target → return it
1097
1087
  return elem._main;
1098
1088
  }
1099
- const elemScope = scopedRedirections && // null if no scoped redirections
1100
- preferredElemScope( target, service, elem, assoc._main || assoc );
1089
+ const elemScope = preferredElemScope( target, service, elem, assoc._main || assoc );
1101
1090
  const exposed = minimalExposure( target, service, elemScope );
1102
1091
 
1103
1092
  if (!exposed.length) {
@@ -1304,24 +1293,12 @@ function populate( model ) {
1304
1293
  return false;
1305
1294
  }
1306
1295
  // no @cds.autoexpose or @cds.autoexpose:null
1307
- // TODO: introduce deprecated._noInheritedAutoexposeViaComposition
1308
- art.$autoexpose = model.$compositionTargets[art.name.id]
1309
- ? autoexposeViaComposition
1310
- : null;
1296
+ art.$autoexpose = model.$compositionTargets[art.name.id] ? true : null;
1311
1297
  return true; // still check for inherited @cds.autoexpose
1312
1298
  }
1313
1299
 
1314
1300
  function autoExposedName( target, service, elemScope ) {
1315
1301
  const absolute = target.name.id;
1316
- if (isDeprecatedEnabled( options, '_shortAutoexposed' )) {
1317
- const parent = definitionScope( target )._parent;
1318
- const name = (parent) ? absolute.substring( parent.name.id.length + 1 ) : absolute;
1319
- // no need for dedot here (as opposed to deprecated._longAutoexposed), as
1320
- // the name for dependent entities have already been created using `_` then
1321
- return `${ service.name.id }.${ name }`;
1322
- }
1323
- if (isDeprecatedEnabled( options, '_longAutoexposed' ))
1324
- return `${ service.name.id }.${ absolute }`;
1325
1302
  const base = definitionScope( target );
1326
1303
  if (base === target)
1327
1304
  return `${ service.name.id }.${ absolute.substring( absolute.lastIndexOf( '.' ) + 1 ) }`;
@@ -1338,7 +1315,7 @@ function populate( model ) {
1338
1315
  function createAutoExposed( target, service, elemScope ) {
1339
1316
  const absolute = autoExposedName( target, service, elemScope );
1340
1317
  const autoexposed = model.definitions[absolute];
1341
- if (autoexposed && (autoexposed.kind !== 'namespace' || !scopedRedirections)) {
1318
+ if (autoexposed && (autoexposed.kind !== 'namespace')) {
1342
1319
  if (isDirectProjection( autoexposed, target )) {
1343
1320
  const anno = autoexposed['@cds.redirection.target'];
1344
1321
  if (annotationIsFalse( anno )) {
@@ -12,7 +12,6 @@ const {
12
12
  forEachDefinition,
13
13
  forEachMember,
14
14
  forEachGeneric,
15
- isDeprecatedEnabled,
16
15
  } = require( '../base/model');
17
16
  const {
18
17
  setLink,
@@ -30,7 +29,7 @@ const { xprRewriteFns } = require('./xpr-rewrite');
30
29
  function propagate( model ) {
31
30
  const props = {
32
31
  '@': annotation, // always except in 'items' (and parameters for entity return types)
33
- doc: annotation, // always except in 'items' (and parameters for entity return types)
32
+ doc: docComment, // like annotations, but guarded by option `propagateDocComments`
34
33
  default: withKind, // always except in 'items'
35
34
  virtual,
36
35
  notNull,
@@ -72,19 +71,15 @@ function propagate( model ) {
72
71
  for (const rule in propagationRules)
73
72
  props[rule] = ruleToFunction[propagationRules[rule]];
74
73
 
75
- const { options } = model;
76
74
  const { rewriteAnnotationsRefs } = xprRewriteFns( model );
77
- // eslint-disable-next-line @stylistic/js/max-len
78
- const oldVirtualNotNullPropagation = isDeprecatedEnabled( options, '_oldVirtualNotNullPropagation' );
79
- const { warning, throwWithError } = model.$messageFunctions;
75
+
76
+ const { message, throwWithError } = model.$messageFunctions;
80
77
 
81
78
  forEachDefinition( model, run );
82
79
  forEachGeneric( model, 'vocabularies', run );
83
80
 
84
- // TODO: move 'virtual' handling/checks to resolver if
85
- // 'deprecated.oldVirtualNotNullPropagation' is gone
86
- if (!oldVirtualNotNullPropagation) // check would always be right, but to be ultra compatible…
87
- forEachDefinition( model, checkVirtual );
81
+ // TODO: move 'virtual' handling/checks to resolver
82
+ forEachDefinition( model, checkVirtual );
88
83
  throwWithError();
89
84
  return model;
90
85
 
@@ -176,22 +171,6 @@ function propagate( model ) {
176
171
  if (transformer)
177
172
  transformer( prop, target, source, viaType );
178
173
  }
179
- // propagate NOT NULL and VIRTUAL from sub elements with
180
- // 'deprecated.oldVirtualNotNullPropagation':
181
- if (oldVirtualNotNullPropagation &&
182
- target.$inferred !== 'proxy' &&
183
- target.kind === 'element' && source.kind === 'element') {
184
- let elem = source; // the outer element
185
- while (elem._parent.kind === 'element')
186
- elem = elem._parent;
187
- if (elem !== source) {
188
- if (target.notNull === undefined && elem.notNull !== undefined)
189
- props.notNull( 'notNull', target, elem );
190
- if (target.virtual === undefined && elem.virtual !== undefined)
191
- props.virtual( 'virtual', target, elem );
192
- }
193
- }
194
- // setLink( target, '_status', 'shallow-propagated' );
195
174
  }
196
175
 
197
176
  function never() { /* no-op: don't propagate */ }
@@ -252,12 +231,14 @@ function propagate( model ) {
252
231
  return;
253
232
  if (prop === 'params' && target.$inferred !== 'proxy' && target.$inferred !== 'include')
254
233
  return;
255
- if (prop === 'foreignKeys' && target.on)
256
- return; // e.g. published associations with filters
234
+ // Remark: occurrences of `foreignKeys` which are not propagated already in
235
+ // tweak-assocs.js: inside `targetAspect` and parameters
236
+ const dict = source[prop];
237
+ if (prop === 'foreignKeys' && (!dict || target.on))
238
+ return; // e.g. published associations with filters, or `Association to many …`
257
239
  const location = target.type && !target.type.$inferred && target.type.location ||
258
240
  target.location ||
259
241
  target._outer && target._outer.location;
260
- const dict = source[prop];
261
242
  target[prop] = Object.create( null ); // also propagate empty elements
262
243
  const propagateKey = target.kind === 'aspect'; // anonymous aspect
263
244
  for (const name in dict) {
@@ -318,6 +299,13 @@ function propagate( model ) {
318
299
  withKind( prop, target, source );
319
300
  }
320
301
 
302
+ function docComment( prop, target, source ) {
303
+ if (model.options.propagateDocComments)
304
+ annotation( prop, target, source );
305
+ else // TODO: Probably just "never"
306
+ onlyViaParent( prop, target, source );
307
+ }
308
+
321
309
  function onlyViaArtifact( prop, target, source ) {
322
310
  const from = viewFromPrimary( target )?.path;
323
311
  // do not propagate from member / if follow assoc in from or into `returns` of actions (v4)
@@ -333,10 +321,8 @@ function propagate( model ) {
333
321
  always( prop, target, source ); // not in 'items'
334
322
  }
335
323
 
336
- function notNull( prop, target, source, viaType ) {
324
+ function notNull( prop, target, source, _viaType ) {
337
325
  // Really "reset" NOT NULL when ref has assoc with cardinality min: 0 (TODO: Universal CSN)
338
- if (oldVirtualNotNullPropagation && viaType)
339
- return; // strange propagation not supported with Universal CSN
340
326
  if (target.value && withAssociation( target.value, targetMinZero ))
341
327
  target[prop] = { $inferred: 'NULL', val: undefined }; // set null value in Universal CSN
342
328
  // $inferred: 'NULL' is only an issue for sub elements with a 'value' property;
@@ -348,7 +334,7 @@ function propagate( model ) {
348
334
  function virtual( prop, target, source, viaType ) {
349
335
  if (!viaType)
350
336
  always( prop, target, source );
351
- else if (!oldVirtualNotNullPropagation) // NULL would block strange propagation to sub element
337
+ else // NULL would block strange propagation to sub element
352
338
  target[prop] = { $inferred: 'NULL', val: undefined }; // set null value in Universal CSN
353
339
  }
354
340
 
@@ -360,14 +346,14 @@ function propagate( model ) {
360
346
  function checkNonVirtualElement( elem ) {
361
347
  // Not enough at all, but so are the current checks - a complete expression
362
348
  // must be checked. Here we just check what might have worked before.
363
- // TODO: Propagate 'virtual' in resolver if 'deprecated.oldVirtualNotNullPropagation' is gone.
349
+ // TODO: Propagate 'virtual' in resolver.
364
350
  const path = !elem.virtual && elem.value && elem.value.path;
365
351
  if (!path || path.broken)
366
352
  return;
367
353
  for (const item of path) {
368
354
  const art = item && item._artifact;
369
355
  if (art?.virtual?.val) {
370
- warning( 'def-missing-virtual', [ item.location, elem ], { art, keyword: 'virtual' },
356
+ message( 'def-missing-virtual', [ item.location, elem ], { art, keyword: 'virtual' },
371
357
  // eslint-disable-next-line @stylistic/js/max-len
372
358
  'Prepend $(KEYWORD) to current select item - containing element $(ART) is virtual' );
373
359
  return;