@sap/cds-compiler 4.4.4 → 4.5.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 (59) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/bin/cdsc.js +5 -0
  3. package/bin/cdsv2m.js +7 -5
  4. package/doc/CHANGELOG_BETA.md +16 -0
  5. package/lib/api/main.js +68 -47
  6. package/lib/api/options.js +10 -6
  7. package/lib/api/validate.js +1 -1
  8. package/lib/base/message-registry.js +28 -6
  9. package/lib/base/messages.js +18 -13
  10. package/lib/base/model.js +3 -0
  11. package/lib/checks/annotationsOData.js +49 -0
  12. package/lib/checks/validator.js +6 -4
  13. package/lib/compiler/assert-consistency.js +38 -16
  14. package/lib/compiler/builtins.js +10 -49
  15. package/lib/compiler/checks.js +16 -8
  16. package/lib/compiler/cycle-detector.js +1 -4
  17. package/lib/compiler/define.js +4 -1
  18. package/lib/compiler/extend.js +21 -7
  19. package/lib/compiler/generate.js +3 -0
  20. package/lib/compiler/populate.js +5 -1
  21. package/lib/compiler/propagator.js +46 -9
  22. package/lib/compiler/resolve.js +68 -14
  23. package/lib/compiler/shared.js +44 -27
  24. package/lib/compiler/tweak-assocs.js +158 -37
  25. package/lib/compiler/utils.js +9 -0
  26. package/lib/edm/annotations/edmJson.js +35 -61
  27. package/lib/edm/annotations/genericTranslation.js +13 -5
  28. package/lib/edm/annotations/preprocessAnnotations.js +2 -3
  29. package/lib/edm/csn2edm.js +4 -1
  30. package/lib/edm/edmInboundChecks.js +59 -15
  31. package/lib/edm/edmPreprocessor.js +1 -7
  32. package/lib/gen/Dictionary.json +8 -0
  33. package/lib/gen/language.checksum +1 -1
  34. package/lib/gen/language.interp +12 -2
  35. package/lib/gen/languageParser.js +6095 -5195
  36. package/lib/json/from-csn.js +4 -5
  37. package/lib/json/to-csn.js +22 -3
  38. package/lib/language/errorStrategy.js +7 -3
  39. package/lib/language/genericAntlrParser.js +120 -24
  40. package/lib/language/textUtils.js +16 -0
  41. package/lib/model/csnUtils.js +9 -8
  42. package/lib/model/revealInternalProperties.js +5 -2
  43. package/lib/optionProcessor.js +2 -3
  44. package/lib/render/toCdl.js +31 -13
  45. package/lib/render/toHdbcds.js +20 -30
  46. package/lib/render/toSql.js +33 -54
  47. package/lib/render/utils/common.js +24 -6
  48. package/lib/transform/db/applyTransformations.js +59 -2
  49. package/lib/transform/db/backlinks.js +13 -1
  50. package/lib/transform/db/expansion.js +24 -3
  51. package/lib/transform/db/flattening.js +2 -2
  52. package/lib/transform/db/killAnnotations.js +37 -0
  53. package/lib/transform/db/rewriteCalculatedElements.js +46 -6
  54. package/lib/transform/forOdata.js +13 -46
  55. package/lib/transform/forRelationalDB.js +2 -1
  56. package/lib/transform/translateAssocsToJoins.js +13 -4
  57. package/lib/transform/universalCsn/coreComputed.js +1 -1
  58. package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
  59. package/package.json +7 -6
@@ -4,6 +4,7 @@
4
4
 
5
5
  const {
6
6
  forEachGeneric,
7
+ forEachMember,
7
8
  forEachInOrder,
8
9
  } = require('../base/model');
9
10
  const { dictLocation, weakLocation, weakRefLocation } = require('../base/location');
@@ -32,6 +33,7 @@ function tweakAssocs( model ) {
32
33
  info, warning, error,
33
34
  } = model.$messageFunctions;
34
35
  const {
36
+ resolvePath,
35
37
  traverseExpr,
36
38
  checkExpr,
37
39
  checkOnCondition,
@@ -62,8 +64,6 @@ function tweakAssocs( model ) {
62
64
  function rewriteArtifact( art ) {
63
65
  // return;
64
66
  if (!art.query) {
65
- // console.log(message( null, art.location, art, {target:art._target},
66
- // 'Info','RAS').toString())
67
67
  rewriteAssociation( art );
68
68
  forEachGeneric( art, 'elements', rewriteAssociation );
69
69
  }
@@ -78,6 +78,8 @@ function tweakAssocs( model ) {
78
78
  traverseQueryPost( art.query, false, ( query ) => {
79
79
  forEachGeneric( query, 'elements', rewriteAssociationCheck );
80
80
  } );
81
+
82
+ checkForAnnotationRefs( art );
81
83
  }
82
84
 
83
85
  // function rewriteView( view ) {
@@ -122,6 +124,79 @@ function tweakAssocs( model ) {
122
124
  }
123
125
  }
124
126
 
127
+ function checkForAnnotationRefs( art ) {
128
+ // TODO: no check for type inheritance yet
129
+ const origin = art._origin ||
130
+ art.includes && art.includes[art.includes.length - 1]._artifact;
131
+ // Make sure not to waste time if no inherited annotation has checked refs:
132
+ if (!origin?.$contains?.$annotation)
133
+ return;
134
+ for (const prop in origin) {
135
+ const anno = prop.charAt(0) === '@' && !art[prop] && origin[prop];
136
+ // Remark: to be on the academic safe side, we should consider the
137
+ // annotations which are not propagated, but they never have references as
138
+ // value. So “no” for the moment. We also do not perform these checks in
139
+ // propagator.js, as it should go away in compiler v6.
140
+ if (anno.kind) { // i.e. with values refs
141
+ art[prop] = { ...anno, $inferred: 'prop' };
142
+ setLink( art[prop], '_outer', art );
143
+ const errorRef = checkAnnotationForRefs( art[prop], art );
144
+ if (errorRef) {
145
+ const valid = errorRef.path[errorRef.path.length - 1]._artifact;
146
+ error( 'anno-missing-rewrite', [ weakLocation( art.location ), art ], {
147
+ '#': (valid ? 'unrelated' : 'std'),
148
+ anno: prop,
149
+ art: origin,
150
+ elemref: errorRef,
151
+ } );
152
+ }
153
+ art.$contains ??= {};
154
+ art.$contains.$annotation = true;
155
+ }
156
+ }
157
+ forEachMember( art, checkForAnnotationRefs );
158
+ }
159
+
160
+ function checkAnnotationForRefs( expr, user ) {
161
+ if (expr.$tokenTexts)
162
+ return traverseExpr( expr, 'annoRewrite', user, checkAnnotationRef );
163
+ if (expr.literal === 'array')
164
+ return expr.val.find( val => checkAnnotationForRefs( val, user ) );
165
+ if (expr.literal !== 'struct')
166
+ return null;
167
+ const struct = Object.values( expr.struct );
168
+ return struct.find( val => checkAnnotationForRefs( val, user ) );
169
+ }
170
+
171
+ function checkAnnotationRef( ref, refCtx, user ) {
172
+ const origPath = ref.path;
173
+ if (!origPath[origPath.length - 1]._artifact) // already wrong in original
174
+ return null;
175
+ ref = { ...ref, path: [ ...origPath.map( item => ({ ...item }) ) ] };
176
+ if (!resolvePath( ref, refCtx, user ))
177
+ return ref;
178
+ return ref.path.some( isUnrelated ) && ref;
179
+
180
+ function isUnrelated( item, idx ) {
181
+ let elem = item._artifact;
182
+ const orig = origPath[idx]._artifact;
183
+ // With includes, we allow shadowing: an included element might seem to be
184
+ // unrelated.
185
+ if (elem._main?.includes && elem._main === (user._main || user))
186
+ return false;
187
+ if (!elem._effectiveType) // safety
188
+ return false;
189
+ // With redirections, the originally referred object might not be the same
190
+ // or even the direct _origin
191
+ do {
192
+ if (elem === orig)
193
+ return false;
194
+ elem = elem._origin;
195
+ } while (elem);
196
+ return true;
197
+ }
198
+ }
199
+
125
200
  function rewriteAssociationCheck( element ) {
126
201
  const elem = element.items || element; // TODO v5: nested items
127
202
  if (elem.elements)
@@ -287,7 +362,7 @@ function tweakAssocs( model ) {
287
362
  elem[prop] = { $inferred: 'NULL', val: undefined, location };
288
363
  break;
289
364
  }
290
- origin = origin._origin;
365
+ origin = getOrigin( origin );
291
366
  }
292
367
  }
293
368
  }
@@ -340,9 +415,9 @@ function tweakAssocs( model ) {
340
415
  setExpandStatus( elem, 'target' );
341
416
  if (elem._parent?.kind === 'element') {
342
417
  // managed association as sub element not supported yet
343
- error( null, [ elem.location, elem ], {},
344
- // eslint-disable-next-line max-len
345
- 'Rewriting the ON-condition of unmanaged association in sub element is not supported' );
418
+ // TODO: Only report once for multi-include chains, see
419
+ // Associations/SubElements/UnmanagedInSubElement.err.cds
420
+ error( 'type-unsupported-rewrite', [ elem.location, elem ], { '#': 'sub-element' } );
346
421
  return;
347
422
  }
348
423
  const nav = (elem._main?.query && elem.value)
@@ -388,23 +463,28 @@ function tweakAssocs( model ) {
388
463
  * The added condition (filter) is already rewritten relative to `elem`.
389
464
  */
390
465
  function addConditionFromAssocPublishing( elem, assoc ) {
391
- const publishAssoc = (elem._main?.query && elem.value?.path?.length > 0);
466
+ const publishAssoc = (elem._main?.query || elem.$syntax === 'calc') &&
467
+ elem.value?.path?.length > 0;
392
468
  if (!publishAssoc)
393
469
  return;
394
470
 
395
471
  const { location } = elem.name;
472
+ let isRestricted = false;
396
473
  const lastStep = elem.value.path[elem.value.path.length - 1];
474
+ if (!lastStep)
475
+ return;
397
476
 
398
- if (lastStep?.cardinality) {
477
+ if (lastStep.cardinality) {
478
+ isRestricted = true;
399
479
  if (!elem.cardinality)
400
- elem.cardinality = assoc.cardinality || { location };
480
+ elem.cardinality = { ...assoc.cardinality } || { location };
401
481
  for (const card of [ 'sourceMin', 'targetMin', 'targetMax' ]) {
402
482
  if (lastStep.cardinality[card])
403
483
  elem.cardinality[card] = copyExpr( lastStep.cardinality[card], location );
404
484
  }
405
485
  }
406
486
 
407
- if (lastStep?.where) {
487
+ if (lastStep.where) {
408
488
  // If there are foreign keys, transform them into an ON-condition first.
409
489
  if (assoc.foreignKeys) {
410
490
  const cond = foreignKeysToOnCondition( elem );
@@ -415,6 +495,7 @@ function tweakAssocs( model ) {
415
495
  }
416
496
 
417
497
  if (elem.on) {
498
+ isRestricted = true;
418
499
  elem.on = {
419
500
  op: { val: 'and', location },
420
501
  args: [
@@ -426,6 +507,14 @@ function tweakAssocs( model ) {
426
507
  $inferred: 'copy',
427
508
  };
428
509
  }
510
+ if (isRestricted) {
511
+ elem.$filtered = {
512
+ val: true,
513
+ literal: 'boolean',
514
+ location,
515
+ $inferred: '$generated',
516
+ };
517
+ }
429
518
  }
430
519
  }
431
520
 
@@ -468,11 +557,13 @@ function tweakAssocs( model ) {
468
557
 
469
558
  // Caller must ensure ON-condition correctness via rewriteExpr()!
470
559
  function foreignKeysToOnCondition( elem ) {
471
- const nav = pathNavigation( elem.value );
472
- if (!nav.tableAlias && model.options.testMode && !elem._pathHead)
473
- throw new CompilerAssertion('rewriting keys to ON-condition: no tableAlias but not inline');
560
+ const nav = elem.$syntax === 'calc'
561
+ ? calcPathNavigation( elem.value )
562
+ : pathNavigation( elem.value );
563
+ if (model.options.testMode && !nav.tableAlias && !elem._pathHead && elem.$syntax !== 'calc')
564
+ throw new CompilerAssertion('rewriting keys to cond: no tableAlias but not inline/calc');
474
565
 
475
- if (!nav.tableAlias || elem._parent?.kind === 'element' ||
566
+ if ((!nav.tableAlias && elem.$syntax !== 'calc') || elem._parent?.kind === 'element' ||
476
567
  (nav && nav.item !== elem.value.path[elem.value.path.length - 1])) {
477
568
  // - no nav.tableAlias for mixins or inside inline; mixins can't have managed assocs, though.
478
569
  // - _parent is element for expand
@@ -501,21 +592,27 @@ function tweakAssocs( model ) {
501
592
  if (elem.name.id.charAt(0) === '$')
502
593
  prependSelfToPath( lhs.path, elem );
503
594
 
595
+ const elemOrigin = getOrigin( elem );
504
596
  const rhs = {
505
597
  path: [
506
598
  // use origin's name; elem could have alias
507
- { id: elem._origin.name.id, location: elem._origin.name.location },
599
+ { id: elemOrigin.name.id, location: elemOrigin.name.location },
508
600
  ...copyExpr( fKey.targetElement.path ),
509
601
  ],
510
602
  location: elem.name.location,
511
603
  };
512
- setLink( rhs.path[0], '_artifact', elem._origin ); // different to lhs!
604
+ setLink( rhs.path[0], '_artifact', elemOrigin ); // different to lhs!
513
605
  setLink( rhs, '_artifact', rhs.path[rhs.path.length - 1] );
514
606
 
515
- // Can't use rewriteExpr as that would use `assoc[…]` itself as well.
516
- const projectedFk = firstProjectionForPath( rhs.path, nav.tableAlias, elem );
517
- rewritePath( rhs, projectedFk.item, elem, projectedFk.elem, elem.value.location );
518
-
607
+ if (elem.$syntax !== 'calc') {
608
+ // Can't use rewriteExpr as that would use `assoc[…]` itself as well.
609
+ const projectedFk = firstProjectionForPath( rhs.path, nav.tableAlias, elem );
610
+ rewritePath( rhs, projectedFk.item, elem, projectedFk.elem, elem.value.location );
611
+ }
612
+ else {
613
+ // TODO: calc elements: Do we need to rewrite? Shouldn't all elements exist, since
614
+ // the calc element is right next to all others?
615
+ }
519
616
  const fkCond = {
520
617
  op: { val: 'ixpr', location: elem.name.location },
521
618
  args: [
@@ -593,31 +690,39 @@ function tweakAssocs( model ) {
593
690
  nav.item ? nav.item.location : expr.path[0].location );
594
691
  }
595
692
  }
596
- else { // from ON cond of element in included structure
693
+ else { // from ON cond of element that was included (i.e. from included structure)
597
694
  const root = expr.path[0]._navigation || expr.path[0]._artifact;
598
695
  if (root.builtin || root.kind !== '$self' && root.kind !== 'element')
599
696
  return;
600
697
  const item = expr.path[root.kind === '$self' ? 1 : 0];
601
698
  if (!item)
602
- return; // just $self
603
- // corresponding elem in including structure
604
- const elem = (assoc._main.items || assoc._main).elements[item.id];
699
+ return; // just $self
700
+ // corresponding elem in including structure or…
701
+ let elem = (assoc._main.items || assoc._main).elements[item.id];
702
+ if (elem !== assoc && assoc.$syntax === 'calc') {
703
+ // … special case for calc elements where "elem" points to the referenced
704
+ // (possibly included) association. For _included_ calc elements of the form,
705
+ // `calc = assoc[…]` it holds: `elem === assoc`
706
+ // TODO: expr could be the transformed fkey->ON-condition, where this statement is not true
707
+ // And for the reference to the _foreign key_, we must _not_ rewrite it.
708
+ const calcRoot = assoc.value.path[0]?._navigation;
709
+ if (assoc.value.path[calcRoot?.kind === '$self' ? 1 : 0]?._artifact === elem)
710
+ elem = assoc;
711
+ }
605
712
  if (!elem)
606
713
  return; // See #11755
607
- if (!(Array.isArray( elem ) || // no msg for redefs
608
- elem === item._artifact || // redirection for explicit def
714
+ if (!(elem === item._artifact || // redirection for explicit def
609
715
  elem._origin === item._artifact)) {
610
716
  const art = assoc._origin;
611
- warning( 'rewrite-shadowed', [ elem.name.location, elem ],
612
- { art: art && effectiveType( art ) },
613
- {
614
- // eslint-disable-next-line max-len
615
- std: 'This element is not originally referred to in the ON-condition of association $(ART)',
616
- // eslint-disable-next-line max-len
617
- element: 'This element is not originally referred to in the ON-condition of association $(MEMBER) of $(ART)',
618
- } );
717
+ // eslint-disable-next-line max-len
718
+ warning( 'rewrite-shadowed', [ elem.name.location, elem ], { art: art && effectiveType( art ) }, {
719
+ // eslint-disable-next-line max-len
720
+ std: 'This element is not originally referred to in the ON-condition of association $(ART)',
721
+ // eslint-disable-next-line max-len
722
+ element: 'This element is not originally referred to in the ON-condition of association $(MEMBER) of $(ART)',
723
+ } );
619
724
  }
620
- rewritePath( expr, item, assoc, (Array.isArray( elem ) ? false : elem), null );
725
+ rewritePath( expr, item, assoc, elem, null );
621
726
  }
622
727
  }
623
728
 
@@ -719,12 +824,12 @@ function tweakAssocs( model ) {
719
824
  env = env.target._artifact?._effectiveType;
720
825
  elem = setArtifactLink( item, env?.elements?.[name] );
721
826
 
722
- if (elem && !Array.isArray( elem ))
827
+ if (elem)
723
828
  return elem;
724
829
  // TODO: better (extra message), TODO: do it
725
830
  error( 'query-undefined-element', [ item.location, assoc ],
726
831
  { id: name || item.id, '#': 'redirected' } );
727
- return (elem) ? false : null;
832
+ return null;
728
833
  }
729
834
  }
730
835
 
@@ -826,4 +931,20 @@ function pathNavigation( ref ) {
826
931
  return { navigation: root.elements[item.id], item, tableAlias: root };
827
932
  }
828
933
 
934
+ // TODO: Maybe unify with pathNavigation?
935
+ function calcPathNavigation( ref ) {
936
+ if (!ref._artifact)
937
+ return {};
938
+ let item = ref.path && ref.path[0];
939
+ const root = item && item._artifact;
940
+ if (!root)
941
+ return {};
942
+ if (root.kind === 'element')
943
+ return { navigation: root, item, tableAlias: root._parent };
944
+ item = ref.path[1];
945
+ if (root.kind === '$self')
946
+ return { item, tableAlias: root };
947
+ return { navigation: root.elements[item.id], item, tableAlias: root };
948
+ }
949
+
829
950
  module.exports = tweakAssocs;
@@ -90,6 +90,7 @@ function setArtifactLink( obj, value ) {
90
90
  }
91
91
 
92
92
  function linkToOrigin( origin, name, parent, prop, location, silentDep ) {
93
+ // TODO: should `key` propagation be part of this?
93
94
  location ||= weakLocation( origin.name.location ); // not ??=
94
95
  const elem = {
95
96
  name: { location, id: origin.name.id },
@@ -153,12 +154,19 @@ function setMemberParent( elem, name, parent, prop ) {
153
154
  setLink( elem, '_main', parent._main || parent );
154
155
  }
155
156
 
157
+ function createAndLinkCalcDepElement( elem ) {
158
+ const r = { kind: '$calculation' }; // no name, like /items
159
+ elem.$calcDepElement = r;
160
+ setLink( r, '_outer', elem );
161
+ }
162
+
156
163
  /**
157
164
  * Adds a dependency user -> art with the given location.
158
165
  *
159
166
  * @param {XSN.Artifact} user
160
167
  * @param {XSN.Artifact} art
161
168
  * @param {XSN.Location} location
169
+ * @param {XSN.Artifact} [semanticLoc]
162
170
  */
163
171
  function dependsOn( user, art, location, semanticLoc = undefined ) {
164
172
  while (user._outer && !user.kind)
@@ -683,6 +691,7 @@ module.exports = {
683
691
  dependsOn,
684
692
  dependsOnSilent,
685
693
  setMemberParent,
694
+ createAndLinkCalcDepElement,
686
695
  storeExtension,
687
696
  withAssociation,
688
697
  pathName,
@@ -7,8 +7,9 @@ const {
7
7
  EdmTypeFacetNames,
8
8
  EdmPrimitiveTypeMap,
9
9
  } = require('../EdmPrimitiveTypeDefinitions.js');
10
- const { mapCdsToEdmType } = require('../edmUtils.js');
11
10
  const { isBuiltinType } = require('../../model/csnUtils');
11
+ const { transformExpression } = require('../../transform/db/applyTransformations.js');
12
+ const { xprInAnnoProperties } = require('../../compiler/builtins');
12
13
 
13
14
  /**
14
15
  * Translate a given token stream expression into an edmJson representation
@@ -77,7 +78,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
77
78
  };
78
79
  //----------------------------------
79
80
  // Error transformer
80
- const notADynExpr = (parent, op, xpr, parentparent, parentprop, txt) => {
81
+ const notADynExpr = (parent, op, xpr, csnPath, parentparent, parentprop, txt) => {
81
82
  error('odata-anno-xpr', location, {
82
83
  anno, op: txt ??= op, '#': 'notadynexpr',
83
84
  });
@@ -93,27 +94,27 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
93
94
  //----------------------------------
94
95
  // operators not supported as dynamic expression
95
96
  '.': notADynExpr,
96
- isNull: (p, o) => notADynExpr(p, o, null, null, null, 'is null'),
97
- isNotNull: (p, o) => notADynExpr(p, o, null, null, null, 'is not null'),
97
+ isNull: (p, o) => notADynExpr(p, o, null, null, null, null, 'is null'),
98
+ isNotNull: (p, o) => notADynExpr(p, o, null, null, null, null, 'is not null'),
98
99
  exists: notADynExpr,
99
100
  '#': notADynExpr,
100
101
  SELECT: notADynExpr,
101
- SET: (p, o) => notADynExpr(p, o, null, null, null, 'UNION'),
102
+ SET: (p, o) => notADynExpr(p, o, null, null, null, null, 'UNION'),
102
103
  like: notADynExpr,
103
104
  new: notADynExpr,
104
105
  };
105
106
 
106
107
  //----------------------------------
107
108
  // list is a $Collection => []
108
- transform.list = (parent, prop, xpr, parentparent, parentprop) => {
109
+ transform.list = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
109
110
  parentparent[parentprop] = xpr.filter(a => a);
110
- applyTransformations(parentparent, transform);
111
+ transformExpression(parentparent, transform);
111
112
  };
112
113
  // XPR
113
- transform.xpr = (parent, prop, xpr, parentparent, parentprop) => {
114
+ transform.xpr = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
114
115
  // eliminate 'xpr' node by pulling up xpr node to its parent
115
116
  parentparent[parentprop] = xpr;
116
- applyTransformations(parentparent, transform);
117
+ transformExpression(parentparent, transform);
117
118
  };
118
119
  //----------------------------------
119
120
  // CASE
@@ -144,12 +145,12 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
144
145
  curIf.$If.push(caseExpr[i]);
145
146
  parent.$If = edmIf.$If;
146
147
  delete parent.case;
147
- applyTransformations(parent, transform);
148
+ transformExpression(parent, transform);
148
149
  };
149
150
  transform.$If = noOp;
150
151
  //----------------------------------
151
152
  // Cast => $Cast
152
- transform.cast = (parent, prop, castExpr, parentparent, parentprop) => {
153
+ transform.cast = (parent, prop, castExpr, csnPath, parentparent, parentprop) => {
153
154
  const csnType = castExpr[0];
154
155
  // try to resolve to final scalar base type and use that instead of derived type
155
156
  if (!isBuiltinType(csnType.type)) {
@@ -161,7 +162,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
161
162
  });
162
163
  }
163
164
  }
164
- const edmTypeName = mapCdsToEdmType(csnType, messageFunctions, options.isV2(), false, location);
165
+ const edmTypeName = edmUtils.mapCdsToEdmType(csnType, messageFunctions, options.isV2(), false, location);
165
166
  const typeFunc = { func: 'Type', args: [ { val: edmTypeName } ] };
166
167
  const castFunc = { func: '$Cast', args: [ typeFunc, castExpr[1] ] };
167
168
 
@@ -184,7 +185,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
184
185
 
185
186
 
186
187
  parentparent[parentprop] = castFunc;
187
- applyTransformations(parentparent, transform);
188
+ transformExpression(parentparent, transform);
188
189
  };
189
190
  //----------------------------------
190
191
  const evalArgs = (argDef, args, propName) => {
@@ -220,7 +221,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
220
221
  evalArgs({ exact }, xpr, prop);
221
222
  parent[opStr] = xpr;
222
223
  delete parent[prop];
223
- applyTransformations(parent, transform);
224
+ transformExpression(parent, transform);
224
225
  };
225
226
  //----------------------------------
226
227
  // LOGICAL
@@ -249,12 +250,12 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
249
250
  evalArgs({ min: 1 }, xpr[1].list, prop);
250
251
  parent.$In = [ xpr[0], ...xpr[1].list ];
251
252
  delete parent[prop];
252
- applyTransformations(parent, transform);
253
+ transformExpression(parent, transform);
253
254
  };
254
255
  transform.$In = noOp;
255
- transform.between = (parent, prop, xpr, parentparent, parentprop) => {
256
+ transform.between = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
256
257
  evalArgs({ exact: 2 }, xpr.slice(1), prop);
257
- applyTransformations(xpr, transform);
258
+ transformExpression(xpr, transform);
258
259
  delete parent[prop];
259
260
  parentparent[parentprop]
260
261
  = {
@@ -264,22 +265,22 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
264
265
  ],
265
266
  };
266
267
  };
267
- transform['||'] = (parent, prop, xpr, parentparent, parentprop) => {
268
+ transform['||'] = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
268
269
  evalArgs({ exact: 2 }, xpr, prop);
269
- applyTransformations(xpr, transform);
270
+ transformExpression(xpr, transform);
270
271
  delete parent[prop];
271
272
  parentparent[parentprop].$Apply = [ { $Function: 'odata.concat' }, ...xpr ];
272
273
  };
273
274
  //----------------------------------
274
275
  // ARITHMETICAL AND UNARY
275
- transform['+'] = (parent, prop, xpr, parentparent, parentprop) => {
276
+ transform['+'] = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
276
277
  if (Array.isArray(xpr)) {
277
278
  op('$Add')(parent, prop, xpr);
278
279
  }
279
280
  else {
280
281
  delete parent[prop];
281
282
  parentparent[parentprop] = xpr;
282
- applyTransformations(parentparent, transform);
283
+ transformExpression(parentparent, transform);
283
284
  }
284
285
  };
285
286
  transform.$Add = noOp;
@@ -295,13 +296,13 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
295
296
  // $DivBy, $Mod are functions
296
297
  //----------------------------------
297
298
  // LITERALS
298
- transform.val = (parent, prop, xpr, parentparent, parentprop) => {
299
+ transform.val = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
299
300
  if (xpr === null)
300
301
  parent.$Null = true;
301
302
  else
302
303
  parentparent[parentprop] = xpr;
303
304
  };
304
- transform.ref = (parent, prop, xpr, parentparent, parentprop) => {
305
+ transform.ref = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
305
306
  if (xpr.some(ps => ps.args || ps.where)) {
306
307
  error('odata-anno-xpr-args', location, {
307
308
  anno, elemref: parent, '#': 'wrongref',
@@ -311,7 +312,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
311
312
  };
312
313
  //----------------------------------
313
314
  // Functions
314
- transform.func = (parent, prop, xpr, parentparent, parentprop) => {
315
+ transform.func = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
315
316
  const rewriteArgs = (argDefs, evalVal = true) => {
316
317
  Object.entries(argDefs).forEach(([ argName, argDef ]) => {
317
318
  const [ foundProps, newArgs ]
@@ -694,7 +695,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
694
695
  // $Record ???
695
696
  $Collection: () => {
696
697
  standard(parentparent, parentprop);
697
- applyTransformations(parentparent, transform);
698
+ transformExpression(parentparent, transform);
698
699
  },
699
700
  $Path: () => {
700
701
  oneArg(parent, xpr);
@@ -704,7 +705,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
704
705
  anno, op: `${xpr}(…)`, meta: 'string', '#': 'wrongval_meta',
705
706
  });
706
707
  }
707
- applyTransformations(parentparent, transform);
708
+ transformExpression(parentparent, transform);
708
709
  },
709
710
  $Null: () => {
710
711
  parent[xpr] = true;
@@ -724,7 +725,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
724
725
  funcDef.forEach(f => f());
725
726
  else
726
727
  funcDef();
727
- applyTransformations(parent, transform);
728
+ transformExpression(parent, transform);
728
729
  }
729
730
  else {
730
731
  const funcName = xpr.startsWith('odata.') ? xpr : `odata.${xpr}`;
@@ -740,7 +741,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
740
741
  parentparent[parentprop].$Apply = [ { $Function: funcName }, ...(parent.args || []) ];
741
742
  delete parentparent[parentprop].func;
742
743
  delete parentparent[parentprop].args;
743
- applyTransformations(parentparent, transform);
744
+ transformExpression(parentparent, transform);
744
745
  }
745
746
  }
746
747
  else {
@@ -752,41 +753,14 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
752
753
  delete parent[prop];
753
754
  };
754
755
 
755
- return applyTransformations({ annoVal }, {
756
- '=': (parent, prop, xpr, parentparent, parentprop) => {
757
- delete parent['='];
758
- parentparent[parentprop] = applyTransformations({ $edmJson: parseExpr( parent, { array: false }) }, transform);
756
+ return transformExpression({ annoVal }, {
757
+ '=': (parent, prop, xpr, csnPath, parentparent, parentprop) => {
758
+ if (parent?.['='] !== undefined && xprInAnnoProperties.some(xProp => parent[xProp] !== undefined)) {
759
+ delete parent['='];
760
+ parentparent[parentprop] = transformExpression({ $edmJson: parseExpr( parent, { array: false }) }, transform);
761
+ }
759
762
  },
760
763
  }).annoVal;
761
-
762
- function applyTransformations( parent, customTransformers ) {
763
- function standard( _parent, _prop, node ) {
764
- if (!node || typeof node !== 'object' ||
765
- !{}.propertyIsEnumerable.call( _parent, _prop ) ||
766
- (typeof _prop === 'string' && _prop.startsWith('@')))
767
- return;
768
-
769
-
770
- if (Array.isArray(node)) {
771
- node.forEach( (n, i) => standard( node, i, n ) );
772
- }
773
- else {
774
- for (const name of Object.getOwnPropertyNames( node )) {
775
- const ct = customTransformers[name];
776
- if (ct) {
777
- if (Array.isArray(ct))
778
- ct.forEach(cti => cti(node, name, node[name], _parent, _prop));
779
- else
780
- ct(node, name, node[name], _parent, _prop);
781
- }
782
- standard(node, name, node[name]);
783
- }
784
- }
785
- }
786
- for (const name of Object.getOwnPropertyNames( parent ))
787
- standard( parent, name, parent[name] );
788
- return parent;
789
- }
790
764
  }
791
765
 
792
766
  // Not everything that can occur in OData annotations can be expressed with
@@ -302,7 +302,7 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
302
302
  if (knownAnnos.length === 0)
303
303
  return;
304
304
  }
305
- if (isBetaEnabled(options, 'annotationExpressions')) {
305
+ if (isBetaEnabled(options, 'odataAnnotationExpressions')) {
306
306
  knownAnnos.forEach((knownAnno) => {
307
307
  if (knownAnno.search(/\.\$edmJson\./g) < 0)
308
308
  carrier[knownAnno] = xpr2edmJson(carrier, knownAnno, location, options, messageFunctions);
@@ -841,8 +841,9 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
841
841
  else {
842
842
  // regular record
843
843
  if (dTypeIsACollection) {
844
- message('odata-anno-value', msg.location,
845
- { anno: msg.anno(), str: 'structured', '#': 'incompval' });
844
+ message('odata-anno-value', msg.location, {
845
+ anno: msg.anno(), str: 'structured', type: dTypeName, '#': 'incompval',
846
+ });
846
847
  }
847
848
  oTarget.append(generateRecord(cAnnoValue, oTermName, dTypeName, dTypeIsACollection, msg));
848
849
  }
@@ -1100,8 +1101,15 @@ function csn2annotationEdm( reqDefs, csnVocabularies, serviceName,
1100
1101
  }
1101
1102
  }
1102
1103
 
1103
- if ( [ 'Edm.AnnotationPath', 'Edm.ModelElementPath', 'Edm.NavigationPropertyPath', 'Edm.PropertyPath', 'Edm.Path' ].includes(resolvedType) )
1104
- resolvedType = resolvedType.split('.')[1];
1104
+ if ( EdmPathTypeMap[resolvedType] ) {
1105
+ if (resolvedType === 'Edm.AnyPropertyPath') {
1106
+ resolvedType = 'PropertyPath';
1107
+ typeName = resolvedType;
1108
+ }
1109
+ else {
1110
+ resolvedType = resolvedType.split('.')[1];
1111
+ }
1112
+ }
1105
1113
 
1106
1114
  return {
1107
1115
  name: typeName,
@@ -40,11 +40,10 @@ function preprocessAnnotations( csn, serviceName, options ) {
40
40
  const keyNames = Object.keys(target.elements).filter(x => target.elements[x].key && !target.elements[x].target);
41
41
  if (keyNames.length === 0) {
42
42
  keyNames.push('MISSING');
43
- message('odata-anno-preproc', null, { anno, name: targetName, '#': 'nokey' },
44
- 'target $(NAME) has no key');
43
+ message('odata-anno-preproc', assoc.$path, { anno, name: targetName, '#': 'nokey' } );
45
44
  }
46
45
  else if (keyNames.length > 1) {
47
- message('odata-anno-preproc', null, { anno, name: targetName, '#': 'multkeys' });
46
+ message('odata-anno-preproc', assoc.$path, { anno, name: targetName, '#': 'multkeys' });
48
47
  }
49
48
 
50
49
  return keyNames[0];