@sap/cds-compiler 5.9.2 → 6.0.10

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 (111) hide show
  1. package/CHANGELOG.md +109 -319
  2. package/README.md +1 -1
  3. package/bin/cds_update_identifiers.js +3 -5
  4. package/bin/cdsc.js +22 -8
  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 +190 -96
  13. package/lib/base/messages.js +29 -20
  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 +30 -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 +12 -9
  27. package/lib/compiler/checks.js +18 -50
  28. package/lib/compiler/define.js +6 -6
  29. package/lib/compiler/extend.js +2 -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 +35 -22
  34. package/lib/compiler/shared.js +7 -1
  35. package/lib/compiler/tweak-assocs.js +1 -1
  36. package/lib/compiler/utils.js +1 -1
  37. package/lib/edm/annotations/edmJson.js +20 -15
  38. package/lib/edm/annotations/genericTranslation.js +7 -8
  39. package/lib/edm/csn2edm.js +46 -50
  40. package/lib/edm/edm.js +8 -7
  41. package/lib/edm/edmPreprocessor.js +37 -85
  42. package/lib/edm/edmUtils.js +2 -2
  43. package/lib/gen/BaseParser.js +55 -44
  44. package/lib/gen/CdlGrammar.checksum +1 -1
  45. package/lib/gen/CdlParser.js +1133 -1150
  46. package/lib/json/from-csn.js +70 -43
  47. package/lib/json/to-csn.js +6 -8
  48. package/lib/language/multiLineStringParser.js +3 -2
  49. package/lib/main.d.ts +58 -24
  50. package/lib/model/csnUtils.js +28 -39
  51. package/lib/model/xprAsTree.js +23 -9
  52. package/lib/modelCompare/compare.js +5 -4
  53. package/lib/optionProcessor.js +21 -17
  54. package/lib/parsers/AstBuildingParser.js +63 -11
  55. package/lib/parsers/XprTree.js +57 -3
  56. package/lib/parsers/identifiers.js +1 -1
  57. package/lib/parsers/index.js +0 -3
  58. package/lib/render/manageConstraints.js +25 -25
  59. package/lib/render/toCdl.js +173 -170
  60. package/lib/render/toHdbcds.js +126 -128
  61. package/lib/render/toRename.js +7 -7
  62. package/lib/render/toSql.js +128 -125
  63. package/lib/render/utils/common.js +47 -22
  64. package/lib/render/utils/delta.js +25 -25
  65. package/lib/render/utils/operators.js +2 -2
  66. package/lib/render/utils/pretty.js +5 -5
  67. package/lib/render/utils/sql.js +13 -13
  68. package/lib/render/utils/standardDatabaseFunctions.js +115 -103
  69. package/lib/render/utils/unique.js +4 -4
  70. package/lib/transform/db/applyTransformations.js +1 -1
  71. package/lib/transform/db/assertUnique.js +2 -2
  72. package/lib/transform/db/associations.js +6 -7
  73. package/lib/transform/db/assocsToQueries/utils.js +4 -5
  74. package/lib/transform/db/backlinks.js +12 -9
  75. package/lib/transform/db/cdsPersistence.js +8 -7
  76. package/lib/transform/db/constraints.js +13 -10
  77. package/lib/transform/db/expansion.js +7 -3
  78. package/lib/transform/db/flattening.js +4 -14
  79. package/lib/transform/db/processSqlServices.js +2 -1
  80. package/lib/transform/db/temporal.js +5 -7
  81. package/lib/transform/db/views.js +2 -4
  82. package/lib/transform/draft/db.js +8 -8
  83. package/lib/transform/draft/odata.js +10 -7
  84. package/lib/transform/forOdata.js +10 -5
  85. package/lib/transform/forRelationalDB.js +5 -75
  86. package/lib/transform/localized.js +1 -1
  87. package/lib/transform/odata/createForeignKeys.js +11 -10
  88. package/lib/transform/odata/flattening.js +8 -4
  89. package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +96 -0
  90. package/lib/transform/odata/typesExposure.js +3 -3
  91. package/lib/transform/transformUtils.js +4 -8
  92. package/lib/transform/translateAssocsToJoins.js +14 -7
  93. package/lib/transform/universalCsn/universalCsnEnricher.js +10 -4
  94. package/lib/utils/objectUtils.js +0 -17
  95. package/package.json +10 -13
  96. package/share/messages/def-upcoming-virtual-change.md +1 -1
  97. package/LICENSE +0 -37
  98. package/bin/cds_remove_invalid_whitespace.js +0 -138
  99. package/doc/CHANGELOG_ARCHIVE.md +0 -3604
  100. package/lib/gen/genericAntlrParser.js +0 -3
  101. package/lib/gen/language.checksum +0 -1
  102. package/lib/gen/language.interp +0 -456
  103. package/lib/gen/language.tokens +0 -180
  104. package/lib/gen/languageLexer.interp +0 -439
  105. package/lib/gen/languageLexer.js +0 -1483
  106. package/lib/gen/languageLexer.tokens +0 -167
  107. package/lib/gen/languageParser.js +0 -24941
  108. package/lib/language/antlrParser.js +0 -205
  109. package/lib/language/errorStrategy.js +0 -646
  110. package/lib/language/genericAntlrParser.js +0 -1572
  111. 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 ) {
@@ -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;
@@ -982,11 +982,11 @@ function define( model ) {
982
982
  col: 'You have provided a $(PROP) already at line $(LINE), column $(COL)',
983
983
  } );
984
984
  // TODO: extra text variants for expand/inline? - probably not
985
- col.val = null; // do not consider it for expandWildcard()
985
+ col.val = '**'; // do not consider it for expandWildcard()
986
986
  }
987
987
  }
988
- // Either expression (value), expand or new association (target && type)
989
- else if (col.value || col.expand || (col.target && col.type)) {
988
+ // Either expression (value), expand, new virtual or new association
989
+ else if (col.value || col.name) {
990
990
  if (!col._block)
991
991
  setLink( col, '_block', parent._block );
992
992
  if (col.inline) { // `@anno elem.{ * }` does not work
@@ -1096,7 +1096,7 @@ function define( model ) {
1096
1096
  forEachGeneric( obj, 'enum', init );
1097
1097
  }
1098
1098
 
1099
- if (obj.foreignKeys) // cannot be extended or annotated - TODO: check anyway?
1099
+ if (obj.foreignKeys)
1100
1100
  forEachInOrder( obj, 'foreignKeys', init );
1101
1101
  if (checkDefinitions( construct, parent, 'actions' ))
1102
1102
  forEachGeneric( construct, 'actions', init );
@@ -1136,8 +1136,8 @@ function define( model ) {
1136
1136
  }
1137
1137
  if (hasElement) {
1138
1138
  // 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.
1139
+ // turn this warning into an error, remove `$syntax: 'element',
1140
+ // and use the above `ext-unexpected-element` only for CSN input.
1141
1141
  warning( 'ext-expecting-enum', [ obj.elements[$location], construct ],
1142
1142
  { code: 'extend … with enum' }, 'Use $(CODE) when extending enums' );
1143
1143
  }
@@ -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
@@ -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;
@@ -143,10 +143,6 @@ function resolve( model ) {
143
143
  const msg = semanticLoc && 'target';
144
144
  error( 'ref-cyclic', [ location, semanticLoc || user ], {
145
145
  art, '#': msg,
146
- }, {
147
- std: 'Illegal circular reference to $(ART)',
148
- element: 'Illegal circular reference to element $(MEMBER) of $(ART)',
149
- target: 'Illegal circular reference to target $(ART)',
150
146
  } );
151
147
  }
152
148
  } );
@@ -1172,12 +1168,24 @@ function resolve( model ) {
1172
1168
  return [ null, '$navElement', '$tableAlias' ][path.length] === (kind || true) && elem._origin;
1173
1169
  }
1174
1170
 
1175
- function addImplicitForeignKeys( art, obj, target ) {
1171
+ function isQuasiVirtualAssociation( art ) {
1172
+ if (art.on)
1173
+ return false;
1174
+ if (art.foreignKeys != null)
1175
+ return !art.foreignKeys;
1176
1176
  const max = art.cardinality?.targetMax;
1177
- if (max && (typeof max.val !== 'number' || max.val > 1) &&
1178
- !art.$inferred && !art.virtual?.val) {
1179
- warning( 'assoc-incomplete-to-many', [ max.location, art ], null,
1180
- 'Provide an ON-condition or foreign keys to this to-many association' );
1177
+ return max && (typeof max.val !== 'number' || max.val > 1);
1178
+ }
1179
+
1180
+ function addImplicitForeignKeys( art, obj, target ) {
1181
+ if (!art.$inferred && !art.virtual?.val && isQuasiVirtualAssociation( obj )) {
1182
+ if (!isDeprecatedEnabled( options, 'noQuasiVirtualAssocs' )) {
1183
+ // TODO: set `foreignKeys`, `ON` or `virtual` to false or similar to
1184
+ // indicate that it is already handled (and that we might not be able to
1185
+ // follow the assoc)? Let us set foreignKeys to false
1186
+ obj.foreignKeys = false;
1187
+ return; // no foreign keys for `Association to many Target`;
1188
+ }
1181
1189
  }
1182
1190
  obj.foreignKeys = Object.create( null );
1183
1191
  forEachInOrder( target, 'elements', ( elem, name ) => {
@@ -1306,6 +1314,8 @@ function resolve( model ) {
1306
1314
  'The redirected target is the original $(ART)' );
1307
1315
  }
1308
1316
 
1317
+ // No check with user-provided ON/fKeys (remark: compiler deduceds ON/fKeys
1318
+ // _after_ redirecting the target = no $inferred test necessary):
1309
1319
  if (elem.foreignKeys || elem.on)
1310
1320
  return; // TODO: or should we still bring an msg if nothing in common?
1311
1321
 
@@ -1562,9 +1572,10 @@ function resolve( model ) {
1562
1572
  const art = type && (type.kind === 'entity' ? type : type.target?._artifact);
1563
1573
  if (!art)
1564
1574
  return ref; // error already reported via resolvePathItem()
1565
- const unexpectedFilter = expected !== 'column' && expected !== 'calc';
1575
+ const unexpectedFilter = expected !== 'column' && expected !== 'calc' && 'std' ||
1576
+ isQuasiVirtualAssociation( type ) && 'model-only';
1566
1577
  if (last.args || last.where || last.cardinality)
1567
- reportUnexpectedArgsAndFilter( last, expected, user, art, unexpectedFilter && 'std' );
1578
+ reportUnexpectedArgsAndFilter( last, expected, user, art, unexpectedFilter );
1568
1579
  // TODO: we should have different message-ids for the "last" stuff: adding
1569
1580
  // `.item` likely corrects the ref, probably with location at end of ref
1570
1581
  return ref;
@@ -1622,12 +1633,12 @@ function resolve( model ) {
1622
1633
  function resolveEnumSymbol( expr, expected, user, type ) {
1623
1634
  const { sym } = expr;
1624
1635
  // CSN input with both '#'+val (recompilation) → do not resolve
1625
- if (!sym || expr.val !== undefined || !type)
1636
+ if (!sym || expr.val !== undefined)
1626
1637
  return;
1627
1638
  // The parameter def for an argument, and the annotation def for an assignment
1628
1639
  // is in `type._artifact`, it is not `type` itself:
1629
- type = effectiveType( type.id ? type._artifact : type );
1630
- // Remark: do not use `?.`, type could be 0 for cyclic parameter type
1640
+ type = type && effectiveType( type.id ? type._artifact : type );
1641
+ // Remark: type could be 0 for cyclic parameter type
1631
1642
  if (type && type.foreignKeys) {
1632
1643
  const keys = Object.values( type.foreignKeys );
1633
1644
  if (keys.length === 1) {
@@ -1636,11 +1647,14 @@ function resolve( model ) {
1636
1647
  }
1637
1648
  }
1638
1649
  const symbols = type && type.enum;
1639
- if (!symbols)
1640
- return;
1641
-
1642
- // TODO: or warning if enum symbol but non-enum type?
1643
- if (symbols[sym.id]) {
1650
+ if (!symbols) {
1651
+ if (user.kind !== '$annotation') { // TODO: better type deduction for annotations
1652
+ const msg = (user.kind === 'enum') ? 'symbolDef' : type && 'invalidType';
1653
+ warning( 'ref-unexpected-enum', [ expr.location, user ],
1654
+ { '#': msg || 'untyped', enum: sym.id, type: type || '' } );
1655
+ }
1656
+ }
1657
+ else if (symbols[sym.id]) {
1644
1658
  setLink( sym, '_artifact', symbols[sym.id] );
1645
1659
  }
1646
1660
  else {
@@ -1649,8 +1663,7 @@ function resolve( model ) {
1649
1663
  type = getOrigin( type );
1650
1664
  const err = message( 'ref-undefined-enum', [ sym.location, user ],
1651
1665
  { id: sym.id, type } );
1652
- if (options.newParser || options.newparser)
1653
- attachAndEmitValidNames( err, symbols );
1666
+ attachAndEmitValidNames( err, symbols );
1654
1667
  }
1655
1668
  }
1656
1669
 
@@ -1662,7 +1675,7 @@ function resolve( model ) {
1662
1675
  if ((step.where || step.cardinality) && variant) {
1663
1676
  const location = combinedLocation( step.where, step.cardinality );
1664
1677
  // XSN TODO: filter$location including […]
1665
- message( 'expr-unexpected-filter', [ location, user ], { '#': variant } );
1678
+ error( 'expr-unexpected-filter', [ location, user ], { '#': variant } );
1666
1679
  }
1667
1680
  return variant && traverseExpr.SKIP;
1668
1681
  }
@@ -1286,6 +1286,8 @@ function fns( model ) {
1286
1286
  if (!user.elements && !user.actions && !user.enum && !user.params &&
1287
1287
  couldBeDraftsEntity( item.id, valid, prev, path ))
1288
1288
  return;
1289
+ if (couldBeDraftAdminDataEntity( item ) )
1290
+ return;
1289
1291
  signalNotFound( (valid.length > 1 ? 'ext-undefined-art' : 'ext-undefined-def'),
1290
1292
  // TODO: ext-undefined-xyz
1291
1293
  [ item.location, user ], valid, { art } );
@@ -1293,11 +1295,15 @@ function fns( model ) {
1293
1295
 
1294
1296
  function couldBeDraftsEntity( id, valid, prev, path ) {
1295
1297
  const entity = prev
1296
- ? prev === path[path.length - 2]._artifact && prev
1298
+ ? prev === path[path.length - 2]._artifact && prev // TODO: Should check for '.drafts'?
1297
1299
  : path.length === 1 && id.endsWith( '.drafts' ) && model.definitions[id.slice( 0, -7 )];
1298
1300
  return entity?.kind === 'entity' && !!entity._service;
1299
1301
  }
1300
1302
 
1303
+ function couldBeDraftAdminDataEntity( item ) {
1304
+ return item.id === 'DraftAdministrativeData' && !item._artifact;
1305
+ }
1306
+
1301
1307
  function undefinedParam( user, head, valid, _dict, _art, _path, semantics ) {
1302
1308
  // TODO: text variant if there are no parameters, or in artifactParameters()
1303
1309
  // TODO: use prepared message variants
@@ -274,7 +274,7 @@ function tweakAssocs( model ) {
274
274
  // target -> avoid infloop ourselves with _status.
275
275
  // TODO: this should be good now
276
276
  const chain = [];
277
- while (!elem.on && !elem.foreignKeys) {
277
+ while (!elem.on && elem.foreignKeys == null) {
278
278
  chain.push( elem );
279
279
  if (elem._status === 'rewrite') { // circular dependency (already reported)
280
280
  for (const e of chain)
@@ -126,7 +126,7 @@ function proxyCopyMembers( art, dictProp, originDict, location, kind, tmpDepreca
126
126
  if (kind)
127
127
  member.kind = kind;
128
128
  else if (origin.key && !tmpDeprecated)
129
- // TODO(v6): remove tmpDeprecated once `noKeyPropagationWithExpansions` is removed
129
+ // TODO(v6): remove tmpDeprecated once `_noKeyPropagationWithExpansions` is removed
130
130
  member.key = Object.assign( { $inferred: 'expanded' }, origin.key );
131
131
  if (kind && origin.masked) // TODO: remove!
132
132
  member.masked = Object.assign( { $inferred: 'nav' }, origin.masked );