@sap/cds-compiler 3.1.2 → 3.4.0

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 (117) hide show
  1. package/CHANGELOG.md +101 -3
  2. package/bin/cdsc.js +4 -2
  3. package/doc/CHANGELOG_BETA.md +35 -0
  4. package/lib/api/main.js +153 -29
  5. package/lib/api/validate.js +8 -3
  6. package/lib/base/dictionaries.js +6 -6
  7. package/lib/base/error.js +2 -2
  8. package/lib/base/keywords.js +106 -24
  9. package/lib/base/message-registry.js +177 -79
  10. package/lib/base/messages.js +78 -57
  11. package/lib/base/model.js +2 -1
  12. package/lib/checks/actionsFunctions.js +1 -1
  13. package/lib/checks/annotationsOData.js +2 -2
  14. package/lib/checks/arrayOfs.js +15 -7
  15. package/lib/checks/cdsPersistence.js +1 -1
  16. package/lib/checks/checkForTypes.js +53 -0
  17. package/lib/checks/defaultValues.js +4 -2
  18. package/lib/checks/elements.js +81 -6
  19. package/lib/checks/foreignKeys.js +12 -13
  20. package/lib/checks/invalidTarget.js +10 -11
  21. package/lib/checks/managedInType.js +21 -15
  22. package/lib/checks/nullableKeys.js +1 -1
  23. package/lib/checks/onConditions.js +9 -9
  24. package/lib/checks/parameters.js +23 -0
  25. package/lib/checks/queryNoDbArtifacts.js +1 -1
  26. package/lib/checks/selectItems.js +1 -1
  27. package/lib/checks/sql-snippets.js +12 -10
  28. package/lib/checks/types.js +2 -2
  29. package/lib/checks/utils.js +17 -7
  30. package/lib/checks/validator.js +36 -14
  31. package/lib/compiler/assert-consistency.js +21 -13
  32. package/lib/compiler/builtins.js +8 -0
  33. package/lib/compiler/checks.js +57 -40
  34. package/lib/compiler/define.js +139 -69
  35. package/lib/compiler/extend.js +319 -50
  36. package/lib/compiler/finalize-parse-cdl.js +14 -9
  37. package/lib/compiler/kick-start.js +2 -35
  38. package/lib/compiler/populate.js +111 -68
  39. package/lib/compiler/propagator.js +5 -3
  40. package/lib/compiler/resolve.js +71 -108
  41. package/lib/compiler/shared.js +82 -54
  42. package/lib/compiler/tweak-assocs.js +26 -14
  43. package/lib/compiler/utils.js +13 -2
  44. package/lib/edm/annotations/genericTranslation.js +10 -7
  45. package/lib/edm/csn2edm.js +11 -11
  46. package/lib/edm/edm.js +17 -9
  47. package/lib/edm/edmPreprocessor.js +53 -30
  48. package/lib/edm/edmUtils.js +7 -2
  49. package/lib/gen/Dictionary.json +14 -0
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +3 -2
  52. package/lib/gen/languageParser.js +4312 -4186
  53. package/lib/inspect/inspectModelStatistics.js +1 -1
  54. package/lib/inspect/inspectPropagation.js +23 -9
  55. package/lib/json/csnVersion.js +13 -13
  56. package/lib/json/from-csn.js +161 -172
  57. package/lib/json/to-csn.js +70 -10
  58. package/lib/language/.eslintrc.json +4 -0
  59. package/lib/language/antlrParser.js +8 -11
  60. package/lib/language/docCommentParser.js +1 -2
  61. package/lib/language/errorStrategy.js +54 -27
  62. package/lib/language/genericAntlrParser.js +140 -93
  63. package/lib/language/language.g4 +57 -33
  64. package/lib/language/multiLineStringParser.js +75 -63
  65. package/lib/main.d.ts +3 -6
  66. package/lib/main.js +1 -0
  67. package/lib/model/.eslintrc.json +13 -0
  68. package/lib/model/api.js +4 -2
  69. package/lib/model/csnRefs.js +78 -50
  70. package/lib/model/csnUtils.js +272 -222
  71. package/lib/model/enrichCsn.js +41 -31
  72. package/lib/model/revealInternalProperties.js +61 -57
  73. package/lib/model/sortViews.js +35 -31
  74. package/lib/modelCompare/compare.js +52 -18
  75. package/lib/modelCompare/filter.js +83 -0
  76. package/lib/optionProcessor.js +10 -1
  77. package/lib/render/manageConstraints.js +11 -7
  78. package/lib/render/toCdl.js +151 -106
  79. package/lib/render/toHdbcds.js +8 -6
  80. package/lib/render/toRename.js +4 -4
  81. package/lib/render/toSql.js +17 -7
  82. package/lib/render/utils/common.js +27 -9
  83. package/lib/render/utils/sql.js +5 -5
  84. package/lib/sql-identifier.js +7 -0
  85. package/lib/transform/db/applyTransformations.js +32 -3
  86. package/lib/transform/db/assertUnique.js +27 -38
  87. package/lib/transform/db/expansion.js +92 -41
  88. package/lib/transform/db/flattening.js +1 -1
  89. package/lib/transform/db/temporal.js +3 -1
  90. package/lib/transform/db/transformExists.js +8 -2
  91. package/lib/transform/db/views.js +42 -13
  92. package/lib/transform/draft/db.js +2 -2
  93. package/lib/transform/forOdataNew.js +10 -7
  94. package/lib/transform/{forHanaNew.js → forRelationalDB.js} +18 -12
  95. package/lib/transform/localized.js +29 -20
  96. package/lib/transform/odata/toFinalBaseType.js +8 -11
  97. package/lib/transform/odata/typesExposure.js +2 -1
  98. package/lib/transform/parseExpr.js +245 -0
  99. package/lib/transform/transformUtilsNew.js +122 -51
  100. package/lib/transform/translateAssocsToJoins.js +17 -16
  101. package/lib/utils/moduleResolve.js +5 -5
  102. package/lib/utils/objectUtils.js +3 -3
  103. package/lib/utils/term.js +5 -5
  104. package/package.json +2 -2
  105. package/share/messages/anno-duplicate-unrelated-layer.md +6 -6
  106. package/share/messages/check-proper-type-of.md +4 -4
  107. package/share/messages/check-proper-type.md +2 -2
  108. package/share/messages/duplicate-autoexposed.md +4 -4
  109. package/share/messages/extend-repeated-intralayer.md +4 -5
  110. package/share/messages/extend-unrelated-layer.md +4 -4
  111. package/share/messages/message-explanations.json +3 -1
  112. package/share/messages/redirected-to-ambiguous.md +7 -6
  113. package/share/messages/redirected-to-complex.md +63 -0
  114. package/share/messages/redirected-to-unrelated.md +6 -5
  115. package/share/messages/rewrite-not-supported.md +4 -4
  116. package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +4 -4
  117. package/share/messages/wildcard-excluding-one.md +37 -0
@@ -69,7 +69,6 @@ const {
69
69
  } = require('./utils');
70
70
 
71
71
  const detectCycles = require('./cycle-detector');
72
- const layers = require('./moduleLayers');
73
72
 
74
73
  const $location = Symbol.for('cds.$location');
75
74
 
@@ -88,14 +87,18 @@ function resolve( model ) {
88
87
  const {
89
88
  resolvePath,
90
89
  checkAnnotate,
91
- defineAnnotations,
90
+ initAnnotations,
91
+ copyAnnotationsForExtensions,
92
92
  attachAndEmitValidNames,
93
93
  lateExtensions,
94
+ applyTypeExtensions,
94
95
  effectiveType,
95
96
  directType,
96
97
  resolveType,
97
98
  resolveTypeArgumentsUnchecked,
98
99
  populateQuery,
100
+ layeredAssignments,
101
+ assignmentsOfHighestLayers,
99
102
  } = model.$functions;
100
103
  const { environment } = model.$volatileFunctions;
101
104
  Object.assign( model.$functions, {
@@ -168,15 +171,7 @@ function resolve( model ) {
168
171
  for (const name in query.elements) {
169
172
  const elem = query.elements[name];
170
173
  // no key prop for duplicate elements or additional specified elements:
171
- if (elem.$duplicates || !elem.value)
172
- continue;
173
- const nav = pathNavigation( elem.value );
174
- if (!nav.navigation)
175
- continue; // undefined, expr, $magic, :const, $self (!), $self.elem
176
- const { item } = nav;
177
- if (item !== elem.value.path[elem.value.path.length - 1])
178
- continue; // having selected a sub elem / navigated along assoc
179
- const { key } = item._artifact;
174
+ const key = !elem.$duplicates && !elem.expand && inheritedSourceKeyProp( elem );
180
175
  if (key) {
181
176
  if (!doIt)
182
177
  return true;
@@ -186,6 +181,20 @@ function resolve( model ) {
186
181
  return false;
187
182
  }
188
183
 
184
+ function inheritedSourceKeyProp( { value, _pathHead } ) {
185
+ if (!value || !value.path)
186
+ return null;
187
+ const nav = pathNavigation( value );
188
+ const item = value.path[value.path.length - 1];
189
+ if (nav.navigation && nav.item === item)
190
+ return item._artifact?.key;
191
+ if (value.path.length !== 1 || _pathHead?.kind !== '$inline')
192
+ return null;
193
+ const hpath = _pathHead.value?.path;
194
+ const head = hpath?.length === 1 && hpath[0]._navigation;
195
+ return head?.kind === '$tableAlias' && item._artifact?.key;
196
+ }
197
+
189
198
  function primarySourceNavigation( aliases ) {
190
199
  for (const name in aliases)
191
200
  return aliases[name].elements;
@@ -240,6 +249,7 @@ function resolve( model ) {
240
249
  function selectTest( expr ) {
241
250
  const art = withAssociation( expr, targetMaxNotOne );
242
251
  if (art) {
252
+ // ID published! Used in stakeholder project; if renamed, add to oldMessageIds
243
253
  info( 'query-navigate-many', [ art.location, query ], { art },
244
254
  {
245
255
  // eslint-disable-next-line max-len
@@ -314,9 +324,10 @@ function resolve( model ) {
314
324
  // TODO: shouldn't be this part of populate.js ?
315
325
  const items = {
316
326
  location: weakLocation( (obj.type || obj).location ),
317
- $inferred: 'expand-items',
327
+ $inferred: 'expanded',
318
328
  };
319
329
  setLink( items, '_outer', obj );
330
+ setLink( items, '_parent', obj._parent );
320
331
  setLink( items, '_origin', type.items );
321
332
  obj.items = items;
322
333
  obj.$expand = 'origin';
@@ -361,7 +372,7 @@ function resolve( model ) {
361
372
  }
362
373
  }
363
374
  if (obj.target) {
364
- // console.log(obj.name,obj._origin.name)
375
+ // console.log(obj.name,obj._origin?.name,obj)
365
376
  if (obj._origin && obj._origin.$inferred === 'REDIRECTED')
366
377
  resolveTarget( art, obj._origin );
367
378
  // console.log(error( 'test-target', [ obj.location, obj ],
@@ -511,9 +522,9 @@ function resolve( model ) {
511
522
  setArtifactLink( ext.name, art );
512
523
 
513
524
  if (art) {
514
- if (art.kind === 'annotate')
515
- checkAnnotate( ext, art );
516
- defineAnnotations( ext, art, ext._block, ext.kind );
525
+ checkAnnotate( ext, art );
526
+ initAnnotations( ext, ext._block, ext.kind ); // TODO: do in define.js
527
+ copyAnnotationsForExtensions( ext, art );
517
528
  // eslint-disable-next-line no-shadow
518
529
  forEachMember( ext, ( elem, name, prop ) => {
519
530
  storeExtension( elem, name, prop, art, ext._block );
@@ -555,7 +566,7 @@ function resolve( model ) {
555
566
  }
556
567
  }
557
568
  }
558
- if (art && art._annotate) {
569
+ if (art?._annotate) {
559
570
  if (art.kind === 'action' || art.kind === 'function') {
560
571
  expandParameters( art );
561
572
  if (art.returns)
@@ -579,6 +590,15 @@ function resolve( model ) {
579
590
  // annotateMembers( env && env[n], dict[n], 'elements', n, parent, 'element' );
580
591
  // }
581
592
  }
593
+
594
+ if (art?._extendType) {
595
+ // Only works because annotateMembers is called in resolveRefs() _after_ `.type` was resolved.
596
+ // TODO: If we allow extending included elements, we may need custom $expand,
597
+ // similar to annotate above.
598
+ art._extendType.forEach(resolveRefs);
599
+ applyTypeExtensions(art);
600
+ }
601
+
582
602
  return;
583
603
 
584
604
  function notFound( msgId, location, address, args, validDict ) {
@@ -632,7 +652,7 @@ function resolve( model ) {
632
652
  // not to create proxies
633
653
  const orig = origin.params[name];
634
654
  linkToOrigin( orig, name, art, 'params', weakLocation( orig.location ), true )
635
- .$inferred = 'expand-param';
655
+ .$inferred = 'expanded';
636
656
  }
637
657
  }
638
658
  if (!art.returns && origin.returns) {
@@ -642,7 +662,7 @@ function resolve( model ) {
642
662
  name: Object.assign( {}, art.name, { id: '', param: '', location } ),
643
663
  kind: 'param',
644
664
  location,
645
- $inferred: 'expand-param',
665
+ $inferred: 'expanded',
646
666
  };
647
667
  setLink( art.returns, '_parent', art );
648
668
  setLink( art.returns, '_main', art._main || art );
@@ -751,90 +771,21 @@ function resolve( model ) {
751
771
  : Object.assign( {}, scheduledAssignments[scheduledAssignments.length - 1], result );
752
772
  }
753
773
 
754
- // Group assignments by their layers. An assignment provided with a definition
755
- // is considered to be provided in a layer named '', the lowest layer.
756
- // TODO: make this usable for extend (elements), too =
757
- // do not use $priority, make assignments on define do not have own _block
758
- function layeredAssignments( assignment ) {
759
- const layered = Object.create(null);
760
- for (const a of assignment) {
761
- const layer = a.$priority && layers.layer( a );
762
- // just consider layer if Extend/Annotate, not Define
763
- const name = (layer) ? layer.realname : '';
764
- const done = layered[name];
765
- if (done)
766
- done.assignments.push( a );
767
- else
768
- layered[name] = { name, layer, assignments: [ a ] };
769
- // TODO: file - if set: unique in layer
770
- }
771
- return layered;
772
- }
773
-
774
- // Return assignments of the highest layers.
775
- // Also return whether there could be an issue:
776
- // - false: there is just one assignment
777
- // - 'unrelated': there is just one assignment per layer
778
- // - true: there is at least one layer with two or more assignments
779
- // TODO: make this usable for extend (elements), too
780
- function assignmentsOfHighestLayers( layeredAnnos ) {
781
- const layerNames = Object.keys( layeredAnnos );
782
- // console.log('HIB:',layerNames)
783
- if (layerNames.length <= 1) {
784
- const name = layerNames[0];
785
- const { assignments } = layeredAnnos[name] || { assignments: [] };
786
- delete layeredAnnos[name];
787
- return { assignments, issue: assignments.length > 1 };
788
- }
789
-
790
- // collect all layers which are lower than another layer
791
- const allExtends = Object.create(null);
792
- allExtends[''] = {}; // the "Define" layer
793
- for (const name of layerNames) {
794
- if (name) // not the "Define" layer
795
- Object.assign( allExtends, layeredAnnos[name].layer._layerExtends );
796
- }
797
- // console.log('HIE:',Object.keys(allExtends))
798
- const assignments = [];
799
- const highest = [];
800
- for (const name of layerNames) {
801
- if (!(name in allExtends)) {
802
- const layer = layeredAnnos[name];
803
- delete layeredAnnos[name];
804
- highest.push( layer );
805
- assignments.push( ...layer.assignments );
806
- }
807
- }
808
- assignments.sort( compareAssignments );
809
- const good = highest.every( layer => layer.assignments.length === 1 );
810
- // TODO: use layer.file instead
811
- const issue = !good || highest.length > 1 && 'unrelated';
812
- // console.log('HI:',highest.map(l=>l.name),issue,issue&&assignments)
813
- return { assignments, issue };
814
- }
815
-
816
- function compareAssignments( a, b ) {
817
- const fileA = layers.realname( a._block );
818
- const fileB = layers.realname( b._block );
819
- if (fileA !== fileB)
820
- return (fileA > fileB) ? 1 : -1;
821
- return (a?.location?.line || 0) - (b?.location?.line || 0) ||
822
- (a?.location?.col || 0) - (b?.location?.col || 0);
823
- }
824
-
825
774
  function applyAssignment( previousAnno, anno, art, annoName ) {
775
+ const hasBase = previousAnno?.literal === 'array';
826
776
  if (!previousAnno) {
827
- if (!annotationHasEllipsis( anno ))
777
+ const firstEllipsis = annotationHasEllipsis( anno );
778
+ if (!firstEllipsis)
828
779
  return anno;
829
780
  if (anno.$priority) { // already complained about with Define
830
- message( 'anno-unexpected-ellipsis-layers', // TODO: better location
831
- [ anno.name.location, art ], { code: '...' } );
781
+ const loc = firstEllipsis.location || anno.name.location;
782
+ message( 'anno-unexpected-ellipsis-layers', [ loc, art ], { code: '...' } );
832
783
  }
833
784
  previousAnno = { val: [] };
834
785
  }
835
786
  else if (previousAnno.literal !== 'array') {
836
- error( 'anno-mismatched-ellipsis', // TODO: better location
837
- [ anno.name.location, art ], { code: '...' } );
787
+ // TODO: If we introduce sub-messages, point to the non-array base value.
788
+ error( 'anno-mismatched-ellipsis', [ anno.name.location, art ], { code: '...' } );
838
789
  previousAnno = { val: [] };
839
790
  }
840
791
  const previousValue = previousAnno.val;
@@ -855,7 +806,8 @@ function resolve( model ) {
855
806
  break;
856
807
  }
857
808
  }
858
- if (upToSpec) { // non-matched UP TO
809
+ if (upToSpec && hasBase) {
810
+ // non-matched UP TO; if there is no base to apply to, there is already an error.
859
811
  warning( null, [ item.upTo.location, art ], { anno: annoName, code: '... up to' },
860
812
  'The $(CODE) value does not match any item in the base annotation $(ANNO)' );
861
813
  }
@@ -1045,7 +997,7 @@ function resolve( model ) {
1045
997
  else if (obj.type && !obj.type.$inferred && art._parent && art._parent.kind === 'select') {
1046
998
  // New association in views, i.e. parent is a query.
1047
999
  error( 'query-expected-on-condition', [ obj.target.location, art ], {},
1048
- 'Expected on-condition for published association' );
1000
+ 'Expected ON-condition for published association' );
1049
1001
  return; // avoid subsequent errors
1050
1002
  }
1051
1003
  else if (target && !obj.foreignKeys && target.kind === 'entity') {
@@ -1128,6 +1080,11 @@ function resolve( model ) {
1128
1080
  'Only an association can be redirected' );
1129
1081
  return;
1130
1082
  }
1083
+ else if ((elem.value || elem.expand) && elem.type && !elem.type.$inferred) {
1084
+ error( 'ref-unexpected-assoc', [ elem.type.location, elem ], {},
1085
+ 'Casting to an association is not supported' );
1086
+ return;
1087
+ }
1131
1088
  // console.log(message( null, elem.location, elem, {target,art:assoc}, 'Info','RE')
1132
1089
  // .toString(), elem.value)
1133
1090
  const nav = elem._main && elem._main.query && elem.value && pathNavigation( elem.value );
@@ -1135,7 +1092,7 @@ function resolve( model ) {
1135
1092
  if (!elem.on && origType.on) {
1136
1093
  error( 'rewrite-not-supported', [ elem.target.location, elem ], {},
1137
1094
  // TODO: Better text ?
1138
- 'The ON condition is not rewritten here - provide an explicit ON condition' );
1095
+ 'The ON-condition is not rewritten here - provide an explicit ON-condition' );
1139
1096
  return;
1140
1097
  }
1141
1098
  }
@@ -1148,6 +1105,7 @@ function resolve( model ) {
1148
1105
  if (!elem.target.$inferred && !elem.on && !elem.foreignKeys) {
1149
1106
  // Only a managed redirection gets this info message. Because otherwise
1150
1107
  // we'd have to check whether on-condition/foreignKeys are the same.
1108
+ // ID published! Used in stakeholder project; if renamed, add to oldMessageIds
1151
1109
  info( 'redirected-to-same', [ elem.target.location, elem ], { art: target },
1152
1110
  'The redirected target is the original $(ART)' );
1153
1111
  }
@@ -1162,12 +1120,17 @@ function resolve( model ) {
1162
1120
  if (!from)
1163
1121
  return; // parse error - TODO: or UNION?
1164
1122
  if (!from.path) {
1165
- warning( 'redirected-to-complex', [ elem.target.location, elem ],
1166
- { art: target, '#': target === elem.target._artifact ? 'target' : 'std' },
1167
- {
1168
- std: 'Redirection involves the complex view $(ART)',
1169
- target: 'The redirected target $(ART) is a complex view',
1170
- });
1123
+ const isTarget = target === elem.target._artifact;
1124
+ const op = from.op?.val || target.query.op?.val;
1125
+ const variant = (!isTarget && 'std') || (op && 'targetOp') || 'target';
1126
+ // ID published! Used in stakeholder project; if renamed, add to oldMessageIds
1127
+ info( 'redirected-to-complex', [ elem.target.location, elem ],
1128
+ { art: target, '#': variant, keyword: op || '' }, {
1129
+ std: 'Redirection involves the complex view $(ART)',
1130
+ target: 'The redirected target $(ART) is a complex view',
1131
+ // eslint-disable-next-line max-len
1132
+ targetOp: 'The redirected target $(ART) is a complex view with $(KEYWORD)',
1133
+ });
1171
1134
  break;
1172
1135
  }
1173
1136
  target = from._artifact;
@@ -1301,13 +1264,13 @@ function resolve( model ) {
1301
1264
 
1302
1265
  let variant;
1303
1266
  if (type.builtin)
1304
- // `.type` is already a builtin: use a nicer message.
1267
+ // `.type` is already a builtin: use a nicer message.
1305
1268
  variant = 'builtin';
1306
1269
  else if (effectiveTypeArt.builtin)
1307
- // base type is a builtin, i.e. a scalar
1270
+ // base type is a builtin, i.e. a scalar
1308
1271
  variant = 'type';
1309
1272
  else
1310
- // effectiveType is not a builtin -> array or structured
1273
+ // effectiveType is not a builtin -> array or structured
1311
1274
  variant = 'non-scalar';
1312
1275
 
1313
1276
  error('type-unexpected-argument', [ artWithType[param].location, artWithType ], {
@@ -1432,7 +1395,7 @@ function resolve( model ) {
1432
1395
  }
1433
1396
  const exp = (expected === 'from') ? 'expr' : expected;
1434
1397
  if (Array.isArray(dict)) {
1435
- message( 'args-expected-named', [ dict[0] && dict[0].location || stepLocation, user ],
1398
+ message( 'args-expected-named', [ dict[0] && dict[0].location || stepLocation, user ], {},
1436
1399
  'Named parameters must be provided for the entity' );
1437
1400
  for (const a of dict)
1438
1401
  resolveExpr( a, exp, user, extDict );
@@ -12,7 +12,6 @@ const {
12
12
  setArtifactLink,
13
13
  dependsOn,
14
14
  pathName,
15
- annotationHasEllipsis,
16
15
  } = require('./utils');
17
16
 
18
17
  function artifactsEnv( art ) {
@@ -87,6 +86,11 @@ function fns( model ) {
87
86
  expectedMsgId: 'expected-struct',
88
87
  envFn: artifactsEnv,
89
88
  },
89
+ viewInclude: {
90
+ check: checkViewIncludesRef,
91
+ expectedMsgId: 'ref-expecting-bare-aspect',
92
+ envFn: artifactsEnv,
93
+ },
90
94
  target: {
91
95
  check: checkEntityRef,
92
96
  expectedMsgId: 'expected-entity',
@@ -135,15 +139,22 @@ function fns( model ) {
135
139
  dollar: true,
136
140
  rootEnv: 'elements', // the final environment for the path root
137
141
  noDep: true, // do not set dependency for circular-check
142
+ allowSelf: true,
138
143
  }, // TODO: special assoc for only on user
139
144
  'mixin-on': {
140
145
  escape: 'param', // TODO: extra check that assocs containing param in ON is not published
141
146
  next: '_$next', // TODO: lexical: ... how to find the (next) lexical environment
142
147
  dollar: true,
143
148
  noDep: true, // do not set dependency for circular-check
149
+ allowSelf: true,
144
150
  }, // TODO: special assoc for only on user
145
151
  rewrite: {
146
- next: '_$next', dollar: true, escape: 'param', noDep: true, rewrite: true,
152
+ next: '_$next',
153
+ dollar: true,
154
+ escape: 'param',
155
+ noDep: true,
156
+ allowSelf: true,
157
+ rewrite: true,
147
158
  }, // TODO: assertion that there is no next/escape used
148
159
  'order-by': {
149
160
  next: '_$next',
@@ -153,7 +164,11 @@ function fns( model ) {
153
164
  deprecatedSourceRefs: true,
154
165
  },
155
166
  'order-by-union': {
156
- next: '_$next', dollar: true, escape: 'param', noDep: true, noExt: true,
167
+ next: '_$next',
168
+ dollar: true,
169
+ escape: 'param',
170
+ noDep: true,
171
+ noExt: true,
157
172
  },
158
173
  // expr TODO: better - on condition for assoc, other on
159
174
  // expr TODO: write dependency, but care for $self
@@ -169,7 +184,7 @@ function fns( model ) {
169
184
  resolveTypeArgumentsUnchecked,
170
185
  resolvePath,
171
186
  checkAnnotate,
172
- defineAnnotations,
187
+ copyAnnotationsForExtensions,
173
188
  attachAndEmitValidNames,
174
189
  } );
175
190
  return;
@@ -183,7 +198,22 @@ function fns( model ) {
183
198
  // - derived structure types: would have to follow type in extend/include;
184
199
  // - entities with params: clarify inheritance, use of param in ON/DEFAULT;
185
200
  // - query entities/events: difficult sequence of resolve steps
186
- return !(art.elements && !art.query && !art.type && !art.params);
201
+ // - aspect without elements (useful for actions/annotations)
202
+ return !(art.elements && !art.query && !art.type && !art.params) && art.kind !== 'aspect';
203
+ }
204
+
205
+ /**
206
+ * Returns true, if the given artifact can be included by a query entity / view.
207
+ *
208
+ * We currently allow:
209
+ * - aspects without elements (the aspect may have actions):
210
+ * either no `elements` property or empty dictionary
211
+ *
212
+ * @param {XSN.Artifact} art
213
+ * @return {boolean}
214
+ */
215
+ function checkViewIncludesRef( art ) {
216
+ return !(art.kind === 'aspect' && (!art.elements || Object.keys(art.elements).length === 0));
187
217
  }
188
218
 
189
219
  /**
@@ -331,7 +361,8 @@ function fns( model ) {
331
361
  return setArtifactLink( ref, art );
332
362
  }
333
363
  else if (!spec.envFn && user._pathHead) {
334
- // eslint-disable-next-line no-empty
364
+ if (art.kind === '$self')
365
+ rejectBareSelf( spec, path, user, extDict );
335
366
  }
336
367
  else if (art.kind === 'using') {
337
368
  const def = model.definitions[art.name.absolute];
@@ -374,7 +405,10 @@ function fns( model ) {
374
405
  // TODO: set art?
375
406
  }
376
407
  else if (art.kind === '$tableAlias' || art.kind === '$self') {
377
- if (spec.noAliasOrMixin && art.kind !== '$self') { // TODO: extra kind $self?
408
+ if (art.kind === '$self') {
409
+ rejectBareSelf( spec, path, user, extDict );
410
+ }
411
+ else if (spec.noAliasOrMixin) {
378
412
  // TODO: good enough for now - change later to not search for table aliases at all
379
413
  signalNotFound( 'ref-rejected-on', [ head.location, user ], extDict && [ extDict ],
380
414
  { '#': 'alias', id: head.id } );
@@ -447,6 +481,15 @@ function fns( model ) {
447
481
  return setArtifactLink( ref, art );
448
482
  }
449
483
 
484
+ function rejectBareSelf( spec, path, user, extDict ) {
485
+ if (path.length === 1 && !spec.allowSelf && !user.expand && !user.inline) {
486
+ const head = path[0];
487
+ // TODO: extra text variant for JOIN-ON (if we have an extra `expected`)
488
+ signalNotFound( 'ref-unexpected-self', [ head.location, user ], extDict && [ extDict ],
489
+ { id: head.id } );
490
+ }
491
+ }
492
+
450
493
  // Issue errors for "smart" element-in-artifact references
451
494
  // without a colon; and errors for misplaced colons in references.
452
495
  // This function likely disappears again in cds-compiler v2.x.
@@ -534,16 +577,21 @@ function fns( model ) {
534
577
  // if the artifact does not exist. Return a "fresh" artifact for
535
578
  // non-existing external using references if `unchecked` is truthy.
536
579
  function getPathRoot( path, spec, user, env, extDict, msgArt ) {
537
- if (!spec.envFn && user._pathHead) {
538
- // TODO: not necessarily for explicit ON condition in expand
580
+ const head = path[0];
581
+ if (!head || !head.id || !env)
582
+ return undefined; // parse error
583
+ if (!spec.envFn && user._pathHead && head.id.charAt(0) !== '$') {
584
+ if (spec.rootEnv === 'elements') { // ON condition in expand/inline
585
+ let root = user._pathHead;
586
+ while (root.kind === '$inline')
587
+ root = root._parent;
588
+ return root;
589
+ }
539
590
  VolatileFns.environment( user._pathHead ); // make sure _origin is set
540
591
  return user._pathHead._origin;
541
592
  // const { _origin } = user._pathHead;
542
593
  // return (_origin && _origin.kind === '$tableAlias') ? _origin._origin : _origin;
543
594
  }
544
- const head = path[0];
545
- if (!head || !head.id || !env)
546
- return undefined; // parse error
547
595
  // if (head.id === 'k') {console.log(Object.keys(user));throw Error(JSON.stringify(user.name))}
548
596
  // if head._artifact is set or is null then it was already computed once
549
597
  if ('_artifact' in head)
@@ -576,7 +624,7 @@ function fns( model ) {
576
624
  message( 'ref-obsolete-parameters', [ head.location, user ],
577
625
  { code: `$parameters.${ path[1].id }`, newcode: `:${ path[1].id }` },
578
626
  'Obsolete $(CODE) - replace by $(NEWCODE)' );
579
- // TODO: replace it in to-csn correspondingly
627
+ // TODO: replace it in to-csn correspondingly !!!
580
628
  return setArtifactLink( head, r );
581
629
  }
582
630
  }
@@ -665,8 +713,7 @@ function fns( model ) {
665
713
  { art: searchName( msgArt, head.id, 'element' ) } );
666
714
  }
667
715
  else {
668
- signalNotFound( 'ref-undefined-var', [ head.location, user ], valid, { id: head.id },
669
- 'Element or variable $(ID) has not been found' );
716
+ signalNotFound( 'ref-undefined-var', [ head.location, user ], valid, { id: head.id } );
670
717
  }
671
718
  }
672
719
  else if (env.$frontend && env.$frontend !== 'cdl' || spec.global) {
@@ -685,6 +732,9 @@ function fns( model ) {
685
732
  // search environment (for the first path item) is `arg`. For messages about
686
733
  // missing artifacts (as opposed to elements), provide the `head` (first
687
734
  // element item in the path)
735
+ // TODO - think about setting _navigation for all $navElement – the
736
+ // "ref: ['tabAlias']: inline: […]" handling might be easier
737
+ // (no _pathHead consultation for key prop and renaming support)
688
738
  function getPathItem( path, spec, user, artItemsCount, headArt ) {
689
739
  // let art = (headArt && headArt.kind === '$tableAlias') ? headArt._origin : headArt;
690
740
  let art = headArt;
@@ -796,13 +846,11 @@ function fns( model ) {
796
846
  // TODO: views elements are proxies to query-0 elements, not the same
797
847
  // TODO: better message text
798
848
  signalNotFound( 'query-undefined-element', [ item.location, user ],
799
- [ env ], { id: item.id },
800
- 'Element $(ID) has not been found in the elements of the query' );
849
+ [ env ], { id: item.id } );
801
850
  }
802
851
  else if (art.kind === '$parameters') {
803
852
  signalNotFound( 'ref-undefined-param', [ item.location, user ],
804
- [ env ], { art: searchName( art._main, item.id, 'param' ) },
805
- { param: 'Entity $(ART) has no parameter $(MEMBER)' } );
853
+ [ env ], { art: art._main, id: item.id } );
806
854
  }
807
855
  else {
808
856
  const variant = art.kind === 'aspect' && !art.name && 'aspect';
@@ -824,14 +872,13 @@ function fns( model ) {
824
872
  * @param {any} location
825
873
  * @param {object[]} valid
826
874
  * @param {object} [textParams]
827
- * @param {any} [texts]
828
875
  */
829
- function signalNotFound(msgId, location, valid, textParams, texts ) {
876
+ function signalNotFound(msgId, location, valid, textParams ) {
830
877
  if (location.$notFound)
831
878
  return;
832
879
  location.$notFound = true;
833
880
  /** @type {object} */
834
- const err = message( msgId, location, textParams, texts );
881
+ const err = message( msgId, location, textParams );
835
882
  if (valid)
836
883
  attachAndEmitValidNames(err, ...valid.reverse());
837
884
  }
@@ -889,6 +936,8 @@ function fns( model ) {
889
936
  // (TODO: really here?, probably split main artifacts vs returns)
890
937
  // see also lateExtensions() where similar messages are reported
891
938
  function checkAnnotate( construct, art ) {
939
+ // TODO: Handle extend statements properly: Different message for empty extend?
940
+
892
941
  // Namespaces cannot be annotated in CSN but because they exist as XSN artifacts
893
942
  // they can still be applied. Namespace annotations are extracted in to-csn.js
894
943
  // In parseCdl mode USINGs and other unknown references are generated as
@@ -914,44 +963,23 @@ function fns( model ) {
914
963
  }
915
964
  }
916
965
 
917
- // Set _block links for annotations (necessary for layering).
918
- // Also copy annotations from `construct` to `art` (TODO: separate that functionality).
919
- function defineAnnotations( construct, art, block, priority = false ) {
920
- if (construct.doc)
921
- art.doc = construct.doc; // e.g. through `extensions` array in CSN
922
-
923
- // set _block (for layering) and $priority, shallow-copy from extension
924
- // TODO: think of removing $priority, then
925
- // no _block: define, _block: annotate/extend/edmx
926
- // would fit with extending defs with props like length
927
- for (const annoProp in construct) {
966
+ // Copy annotations from `ext` to `art`, overwriting inferred ones.
967
+ // TODO: move to extend.js if not used anymore in define.js
968
+ function copyAnnotationsForExtensions( ext, art ) {
969
+ if (ext.doc)
970
+ art.doc = ext.doc; // e.g. through `extensions` array in CSN
971
+ for (const annoProp in ext) {
928
972
  if (annoProp.charAt(0) === '@') {
929
- let annos = construct[annoProp];
930
- if (!(Array.isArray(annos)))
931
- annos = [ annos ];
932
- for (const a of annos) {
933
- setLink( a, '_block', block );
934
- a.$priority = priority; // is now: undefined (auto-set) | false | 'annotate' | 'extend'
935
- if (construct !== art)
936
- addAnnotation( art, annoProp, a );
937
- if (!priority && annotationHasEllipsis( a )) {
938
- error( 'anno-unexpected-ellipsis',
939
- [ a.name.location, art ], { code: '...' } );
940
- }
941
- }
973
+ const extAnno = ext[annoProp];
974
+ if (art[annoProp]?.$inferred)
975
+ art[annoProp] = extAnno; // overwrite $inferred annos
976
+ else
977
+ dictAddArray( art, annoProp, extAnno );
942
978
  }
943
979
  }
944
980
  }
945
981
  }
946
982
 
947
- // Add annotation to definition - overwriting $inferred annos
948
- function addAnnotation( art, annoProp, anno ) {
949
- const old = art[annoProp];
950
- if (old && old.$inferred)
951
- delete art[annoProp];
952
- dictAddArray( art, annoProp, anno );
953
- }
954
-
955
983
  module.exports = {
956
984
  fns,
957
985
  };