@sap/cds-compiler 5.1.0 → 5.2.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 (51) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/bin/cdsc.js +2 -2
  3. package/bin/cdshi.js +24 -17
  4. package/bin/cdsse.js +17 -18
  5. package/lib/api/main.js +19 -2
  6. package/lib/api/options.js +4 -1
  7. package/lib/base/builtins.js +1 -0
  8. package/lib/base/message-registry.js +16 -3
  9. package/lib/base/model.js +0 -10
  10. package/lib/checks/actionsFunctions.js +0 -12
  11. package/lib/checks/structuredAnnoExpressions.js +10 -14
  12. package/lib/compiler/assert-consistency.js +19 -11
  13. package/lib/compiler/builtins.js +1 -1
  14. package/lib/compiler/define.js +6 -4
  15. package/lib/compiler/extend.js +5 -5
  16. package/lib/compiler/populate.js +9 -9
  17. package/lib/compiler/propagator.js +1 -0
  18. package/lib/compiler/resolve.js +29 -34
  19. package/lib/compiler/shared.js +7 -8
  20. package/lib/compiler/tweak-assocs.js +155 -64
  21. package/lib/compiler/utils.js +1 -1
  22. package/lib/compiler/xpr-rewrite.js +4 -3
  23. package/lib/edm/annotations/genericTranslation.js +13 -9
  24. package/lib/edm/csn2edm.js +26 -2
  25. package/lib/edm/edm.js +23 -8
  26. package/lib/edm/edmInboundChecks.js +5 -7
  27. package/lib/edm/edmPreprocessor.js +43 -30
  28. package/lib/gen/BaseParser.js +720 -0
  29. package/lib/gen/CdlParser.js +4421 -0
  30. package/lib/gen/language.checksum +1 -1
  31. package/lib/gen/language.interp +1 -1
  32. package/lib/gen/languageParser.js +4006 -4001
  33. package/lib/language/antlrParser.js +62 -0
  34. package/lib/language/genericAntlrParser.js +28 -0
  35. package/lib/model/csnUtils.js +2 -0
  36. package/lib/model/revealInternalProperties.js +2 -0
  37. package/lib/modelCompare/utils/filter.js +70 -42
  38. package/lib/optionProcessor.js +9 -3
  39. package/lib/parsers/AstBuildingParser.js +1172 -0
  40. package/lib/parsers/CdlGrammar.g4 +1940 -0
  41. package/lib/parsers/Lexer.js +239 -0
  42. package/lib/render/toCdl.js +23 -27
  43. package/lib/render/toSql.js +5 -5
  44. package/lib/transform/db/applyTransformations.js +54 -16
  45. package/lib/transform/draft/odata.js +10 -11
  46. package/lib/transform/effective/flattening.js +10 -14
  47. package/lib/transform/odata/flattening.js +42 -31
  48. package/lib/transform/odata/toFinalBaseType.js +7 -6
  49. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
  50. package/package.json +2 -2
  51. package/share/messages/redirected-to-ambiguous.md +5 -4
@@ -73,7 +73,6 @@ function tweakAssocs( model ) {
73
73
  // Only top-level queries and sub queries in FROM
74
74
 
75
75
  function rewriteArtifact( art ) {
76
- // return;
77
76
  if (!art.query) {
78
77
  rewriteAssociation( art );
79
78
  }
@@ -333,27 +332,62 @@ function tweakAssocs( model ) {
333
332
 
334
333
  // TODO: split this function: create foreign keys without `targetElement`
335
334
  // already in Phase 2: redirectImplicitly()
336
- // console.log(message( null, elem.location, elem, {art:assoc,target:assoc.target},
337
- // 'Info','FK').toString())
338
335
  elem.foreignKeys = Object.create(null); // set already here (also for zero foreign keys)
339
336
  forEachInOrder( assoc, 'foreignKeys', ( orig, name ) => {
340
337
  const location = weakRefLocation( elem.target );
341
338
  const fk = linkToOrigin( orig, name, elem, 'foreignKeys', location );
342
339
  fk.$inferred = 'rewrite'; // Override existing value; TODO: other $inferred value?
343
340
  setLink( fk, '_effectiveType', fk );
344
- const te = copyExpr( orig.targetElement, location );
345
- if (elem._redirected) {
346
- const i = te.path[0]; // TODO: or also follow path like for ON?
347
- const state = rewriteItem( elem, i, i.id, elem, true );
348
- if (state && state !== true && te.path.length === 1)
349
- setArtifactLink( te, state );
350
- }
351
- fk.targetElement = te;
341
+ fk.targetElement = copyExpr( orig.targetElement, location );
342
+ if (elem._redirected)
343
+ rewriteKey( elem, fk.targetElement );
352
344
  } );
353
345
  if (elem.foreignKeys) // Possibly no fk was set
354
346
  elem.foreignKeys[$inferred] = 'rewrite';
355
347
  }
356
348
 
349
+ function rewriteKey( elem, targetElement ) {
350
+ let projectedKey = null;
351
+ // rewrite along redirection chain
352
+ for (const alias of elem._redirected) {
353
+ if (alias.kind !== '$tableAlias')
354
+ continue;
355
+
356
+ projectedKey = firstProjectionForPath( targetElement.path, 0, alias, null );
357
+ if (projectedKey.elem) {
358
+ const item = targetElement.path[projectedKey.index];
359
+ item.id = projectedKey.elem.name.id;
360
+ if (projectedKey.index > 0)
361
+ targetElement.path.splice(0, projectedKey.index);
362
+ }
363
+ else {
364
+ setArtifactLink( targetElement.path[0], null );
365
+ setArtifactLink( targetElement, null );
366
+
367
+ const culprit = !elem.target.$inferred && elem.target ||
368
+ elem.value?.path?.[elem.value.path.length - 1] ||
369
+ elem;
370
+ // TODO: probably better to collect the non-projected foreign keys
371
+ // and have one message for all
372
+ error('rewrite-undefined-key', [ weakLocation( culprit.location ), elem ], {
373
+ '#': 'std',
374
+ id: targetElement.path.map(p => p.id).join('.'),
375
+ target: alias._main,
376
+ name: elem.name.id,
377
+ });
378
+ return null;
379
+ }
380
+ }
381
+
382
+ if (projectedKey?.elem) {
383
+ const item = targetElement.path[0];
384
+ setArtifactLink( item, projectedKey.elem );
385
+ setArtifactLink( targetElement, projectedKey.elem );
386
+ return projectedKey.elem;
387
+ }
388
+ return null;
389
+ }
390
+
357
391
  // TODO: there is no need to rewrite the on condition of non-leading queries,
358
392
  // i.e. we could just have on = {…}
359
393
  // TODO: re-check $self rewrite (with managed composition of aspects),
@@ -380,27 +414,35 @@ function tweakAssocs( model ) {
380
414
  elem.on.$inferred = 'copy';
381
415
 
382
416
  const { navigation } = nav;
383
- if (!navigation) // TODO: what about $projection.assoc as myAssoc ?
417
+ if (!navigation) { // TODO: what about $projection.assoc as myAssoc ?
418
+ if (elem._columnParent)
419
+ error( 'rewrite-not-supported', [ elem.target.location, elem ], { '#': 'inline-expand' } );
384
420
  return; // should not happen: $projection, $magic, or ref to const
385
-
386
- // Currently, having an unmanaged association inside a struct is not
387
- // supported by this function:
388
- if (navigation !== assoc && navigation._origin !== assoc) { // TODO: re-check
389
- // For "assoc1.assoc2" and "struct.elem1.assoc2"
390
- if (elem._redirected !== null) // null = already reported
391
- error( 'rewrite-not-supported', [ elem.target.location, elem ] );
392
421
  }
393
- else if (!nav.tableAlias || nav.tableAlias.path) {
422
+ const isAssocInStruct = (navigation !== assoc && navigation._origin !== assoc);
423
+ if (isAssocInStruct) {
424
+ // For "[sub.]assoc1.assoc2": not supported, yet (#3977)
425
+ const multipleAssoc = elem.value.path.slice(0, -1).some(segment => segment._artifact?.target);
426
+ if (multipleAssoc && elem._redirected !== null) { // null = already reported
427
+ error('rewrite-not-supported', [ elem.target.location, elem ], { '#': 'secondary' });
428
+ return;
429
+ }
430
+ }
431
+
432
+ if (!nav.tableAlias || nav.tableAlias.path) {
433
+ const navEnv = followNavigationPath( elem.value?.path, nav ) || nav.tableAlias;
394
434
  traverseExpr( elem.on, 'rewrite-on', elem,
395
- expr => rewriteExpr( expr, elem, nav.tableAlias ) );
435
+ expr => rewriteExpr( expr, elem, nav.tableAlias, navEnv ) );
396
436
  }
397
- else if (elem._pathHead) {
398
- error( 'rewrite-not-supported', [ elem.target.location, elem ] );
437
+ else if (elem._columnParent) {
438
+ error( 'rewrite-not-supported', [ elem.target.location, elem ], { '#': 'inline-expand' } );
439
+ return;
399
440
  }
400
441
  else {
401
442
  // TODO: support that, now that the ON condition is rewritten in the right order
402
443
  error( null, [ elem.value.location, elem ], {},
403
444
  'Selecting unmanaged associations from a sub query is not supported' );
445
+ return;
404
446
  }
405
447
 
406
448
  addConditionFromAssocPublishing( elem, assoc, nav );
@@ -528,7 +570,7 @@ function tweakAssocs( model ) {
528
570
 
529
571
  // Caller must ensure ON-condition correctness via rewriteExpr()!
530
572
  function foreignKeysToOnCondition( elem, assoc, nav ) {
531
- if (model.options.testMode && !nav.tableAlias && !elem._pathHead && elem.$syntax !== 'calc')
573
+ if (model.options.testMode && !nav.tableAlias && !elem._columnParent && elem.$syntax !== 'calc')
532
574
  throw new CompilerAssertion('rewriting keys to cond: no tableAlias but not inline/calc');
533
575
 
534
576
  if ((!nav.tableAlias && elem.$syntax !== 'calc') || elem._parent?.kind === 'element' ||
@@ -571,8 +613,11 @@ function tweakAssocs( model ) {
571
613
  setLink( rhs.path[0], '_artifact', assoc );
572
614
  setLink( rhs, '_artifact', rhs.path[rhs.path.length - 1]._artifact );
573
615
 
574
- if (elem.$syntax !== 'calc') { // different to lhs!
575
- const projectedFk = firstProjectionForPath( rhs.path, 0, nav.tableAlias, elem );
616
+ if (elem.$syntax !== 'calc') {
617
+ // Not passing an element, as we don't want to use our own filtered association here!
618
+ // That's done for lhs.
619
+ const projectedFk = firstProjectionForPath( rhs.path, 0, nav.tableAlias, null );
620
+ // different to lhs!
576
621
  rewritePath( rhs, projectedFk.item, elem, projectedFk.elem, elem.value.location );
577
622
  }
578
623
 
@@ -607,7 +652,13 @@ function tweakAssocs( model ) {
607
652
  return cond;
608
653
  }
609
654
 
610
- function rewriteExpr( expr, assoc, tableAlias ) {
655
+ /**
656
+ * @param expr
657
+ * @param assoc
658
+ * @param tableAlias
659
+ * @param navEnv Navigation element / table alias, used to traverse/rewrite the path.
660
+ */
661
+ function rewriteExpr( expr, assoc, tableAlias, navEnv = tableAlias ) {
611
662
  // Rewrite ON condition (resulting in outside perspective) for association
612
663
  // 'assoc' in query or including entity from ON cond of mixin element /
613
664
  // element in included structure / element in source ref/d by table alias.
@@ -620,7 +671,7 @@ function tweakAssocs( model ) {
620
671
  return;
621
672
  if (!assoc._main)
622
673
  return;
623
- if (tableAlias) { // from ON cond of element in source ref/d by table alias
674
+ if (navEnv) { // from ON cond of element in source ref/d by table alias
624
675
  const source = tableAlias._origin;
625
676
  const root = expr.path[0]._navigation || expr.path[0]._artifact;
626
677
  if (!root || root._main !== source)
@@ -628,7 +679,8 @@ function tweakAssocs( model ) {
628
679
  if (expr.scope === 'param' || root.kind === '$parameters')
629
680
  return; // are not allowed anyway - there was an error before
630
681
  const startIndex = (root.kind === '$self' ? 1 : 0);
631
- const result = firstProjectionForPath( expr.path, startIndex, tableAlias, assoc );
682
+ const exprNavigation = (root.kind === '$self' ? tableAlias : navEnv);
683
+ const result = firstProjectionForPath( expr.path, startIndex, exprNavigation, assoc );
632
684
  // For `assoc[…]`, ensure that we don't rewrite to another projection on `assoc`.
633
685
  if (result.item && assoc._origin === result.item._artifact)
634
686
  result.elem = assoc;
@@ -647,8 +699,8 @@ function tweakAssocs( model ) {
647
699
  }
648
700
  return;
649
701
  }
650
- const nav = pathNavigation( expr );
651
- if (nav.navigation || nav.tableAlias) { // rewrite src elem, mixin, $self[.elem]
702
+ if (expr.path[0]._navigation) { // rewrite src elem, mixin, $self[.elem]
703
+ const nav = pathNavigation( expr );
652
704
  const elem = (assoc._origin === root) ? assoc : navProjection( nav.navigation, assoc );
653
705
  rewritePath( expr, nav.item, assoc, elem,
654
706
  nav.item ? nav.item.location : expr.path[0].location );
@@ -691,8 +743,9 @@ function tweakAssocs( model ) {
691
743
  if (!elem) {
692
744
  if (location) {
693
745
  const elemref = root._navigation?.kind === '$self' ? path.slice(1) : path;
746
+ // TODO: Fix message for sub-elements: `s: { a: Association on x=1, x: Integer};` for x
694
747
  error( 'rewrite-not-projected', [ location, assoc ], {
695
- name: assoc.name.id, art: item._artifact, elemref: { ref: elemref },
748
+ name: assoc.name.id, art: elemref[0]._artifact, elemref: { ref: elemref },
696
749
  }, {
697
750
  std: 'Projected association $(NAME) uses non-projected element $(ELEMREF)',
698
751
  element: 'Projected association $(NAME) uses non-projected element $(ELEMREF) of $(ART)',
@@ -729,14 +782,11 @@ function tweakAssocs( model ) {
729
782
  if (i === item)
730
783
  state = setArtifactLink( i, elem );
731
784
  }
732
- else if (i) {
733
- state = rewriteItem( state, i, i.id, assoc, false );
785
+ else {
786
+ state = rewriteItem( state, i, assoc );
734
787
  if (!state || state === true)
735
788
  break;
736
789
  }
737
- else {
738
- return;
739
- }
740
790
  }
741
791
  if (state !== true)
742
792
  setArtifactLink( ref, state );
@@ -749,9 +799,15 @@ function tweakAssocs( model ) {
749
799
  path.unshift( root );
750
800
  }
751
801
 
752
- function rewriteItem( elem, item, name, assoc, forKeys ) {
802
+ /**
803
+ * @param elem "Navigation environment" (element) for `item`.
804
+ * @param item Path segment to rewrite.
805
+ * @param assoc Published association of query.
806
+ */
807
+ function rewriteItem( elem, item, assoc ) {
753
808
  if (!elem._redirected)
754
809
  return true;
810
+ let name = item.id;
755
811
  for (const alias of elem._redirected) {
756
812
  // TODO: a message for the same situation as msg 'rewrite-shadowed'?
757
813
  if (alias.kind === '$tableAlias') { // _redirected also contains structures for includes
@@ -760,21 +816,8 @@ function tweakAssocs( model ) {
760
816
  // but its origins, too.
761
817
  const proj = navProjection( alias.elements[name], assoc );
762
818
  name = proj?.name?.id;
763
- if (!name) {
764
- if (!forKeys)
765
- break;
766
- setArtifactLink( item, null );
767
- const culprit = elem.target && !elem.target.$inferred && elem.target ||
768
- elem.value?.path?.[elem.value.path.length - 1] ||
769
- elem;
770
- // TODO: probably better to collect the non-projected foreign keys
771
- // and have one message for all
772
- error( 'rewrite-undefined-key', [ weakLocation( culprit.location ), assoc ], {
773
- '#': 'std', id: item.id, target: alias._main, name: assoc.name.id,
774
- });
775
- // ''
776
- return null;
777
- }
819
+ if (!name)
820
+ break;
778
821
  item.id = name;
779
822
  // TODO: Why not break here? Test test3/scenarios/AFC/db/view/consumption/C_ScopedRole.cds
780
823
  }
@@ -802,14 +845,20 @@ function tweakAssocs( model ) {
802
845
 
803
846
  function navProjection( navigation, preferred ) {
804
847
  // TODO: Info if more than one possibility?
805
- // console.log(navigation,navigation._projections)
806
848
  if (!navigation)
807
849
  return {};
808
- else if (!navigation._projections)
850
+
851
+ if (!navigation._projections && !navigation._complexProjections)
809
852
  return null;
810
- return (preferred && navigation._projections.includes( preferred ))
811
- ? preferred
812
- : navigation._projections[0] || null;
853
+
854
+ // _complexProjections contains projections that are not "simple",
855
+ // i.e. contain a filter or arguments. Only used if it contains our
856
+ // preferred association.
857
+ if (preferred && ( navigation._complexProjections?.includes( preferred ) ||
858
+ navigation._projections?.includes( preferred )))
859
+ return preferred;
860
+
861
+ return navigation._projections?.[0] || null;
813
862
  }
814
863
 
815
864
 
@@ -824,6 +873,9 @@ function navProjection( navigation, preferred ) {
824
873
  * The returned object `ret` has `ret.item`, which is the path item at index `ret.index`
825
874
  * that is projected. `ret.elem` is the element projection.
826
875
  *
876
+ * If nothing was found, `ret.elem` is null, and `ret.item` is the last segment for which
877
+ * there was a $navElement.
878
+ *
827
879
  * @param {any[]} path
828
880
  * @param {number} startIndex
829
881
  * @param {object} nav
@@ -844,30 +896,69 @@ function firstProjectionForPath( path, startIndex, nav, elem ) {
844
896
 
845
897
  let proj = null;
846
898
  let navItem = nav;
847
- for (let i = startIndex; i < path.length; ++i) {
848
- const item = path[i];
899
+ let navIndex = startIndex;
900
+ for (; navIndex < path.length; ++navIndex) {
901
+ const item = path[navIndex];
849
902
  navItem = item?.id && navItem.elements?.[item.id];
850
903
  if (!navItem) {
851
904
  break;
852
905
  }
853
- else if (navItem._projections) {
906
+ else if (navItem._projections || navItem._complexProjections) {
854
907
  const projElem = navProjection( navItem, elem );
855
908
  if (projElem && projElem === elem) {
856
909
  // in case the specified association is found, _always_ use it.
857
- return { index: i, item, elem };
910
+ return { index: navIndex, item, elem };
858
911
  }
859
912
  else if (projElem) {
860
913
  const queryIndex = selectedElements.indexOf(projElem);
861
914
  if (!proj || queryIndex < proj.queryIndex) {
862
915
  proj = {
863
- index: i, item, elem: projElem, queryIndex,
916
+ index: navIndex, item, elem: projElem, queryIndex,
864
917
  };
865
918
  }
866
919
  }
867
920
  }
868
921
  }
922
+ if (proj)
923
+ return proj;
924
+
925
+ const index = (navIndex - 1) <= startIndex ? startIndex : (navIndex - 1);
926
+ return { index, item: path[index], elem: null };
927
+ }
928
+
929
+ /**
930
+ * Follow the navigation along the given path to its N-1 path step, so
931
+ * that the last step can be resolved against the returned navigation like
932
+ * `returnValue.elements[last.id]`.
933
+ *
934
+ * @param {XSN.Path} path
935
+ * @param {object} nav
936
+ * @returns {object|null}
937
+ */
938
+ function followNavigationPath( path, nav ) {
939
+ if (!nav.item || !path || path.length === 1)
940
+ return nav.tableAlias;
941
+
942
+ const startIndex = path.indexOf(nav.item);
943
+ if (startIndex === -1)
944
+ return null;
945
+
946
+ // navigation is already at last path step
947
+ if (startIndex === path.length - 1) {
948
+ return nav.navigation?.kind === '$navElement'
949
+ ? nav.navigation._parent
950
+ : nav.tableAlias;
951
+ }
952
+
953
+ let navItem = nav.navigation || nav.tableAlias;
954
+ for (let i = startIndex + 1; i < path.length - 1; ++i) {
955
+ const item = path[i];
956
+ navItem = item?.id && navItem.elements?.[item.id];
957
+ if (!navItem)
958
+ return null;
959
+ }
869
960
 
870
- return proj || { index: startIndex, item: path[startIndex], elem: null };
961
+ return navItem;
871
962
  }
872
963
 
873
964
  /**
@@ -607,7 +607,7 @@ function pathStartsWithSelf( ref ) {
607
607
  }
608
608
 
609
609
  function columnRefStartsWithSelf( col ) {
610
- for (; col; col = col._pathHead) {
610
+ for (; col; col = col._columnParent) {
611
611
  const ref = col.value;
612
612
  const head = ref && !ref.scope && ref.path?.[0];
613
613
  if (head?._navigation?.kind === '$self')
@@ -355,7 +355,7 @@ function xprRewriteFns( model ) {
355
355
  // On select items, use navigation elements or table alias
356
356
  // TODO: Expand/inline paths don't have a `_navigation` property on their last
357
357
  // path step, yet. We need to implement expand/inline.
358
- const isSimpleSelectItem = target.value?.path && target._main?.query && !target._pathHead;
358
+ const isSimpleSelectItem = target.value?.path && target._main?.query && !target._columnParent;
359
359
  if (isSimpleSelectItem) {
360
360
  const isSelfPath = (expr.path[0]?._navigation?.kind === '$self');
361
361
  if (isSelfPath) {
@@ -665,8 +665,9 @@ function xprRewriteFns( model ) {
665
665
  }
666
666
 
667
667
  const item = expr.path[index];
668
- // Not a query -> no $navElement -> use `elements`
669
- if (!env.query && env.kind !== 'select') {
668
+ // If the artifact is already in the same definition, we must not check the query.
669
+ // Or if it is not a query -> no $navElement -> use `elements`
670
+ if (item._artifact._main === env || !env.query && env.kind !== 'select') {
670
671
  if (env.elements?.[item.id])
671
672
  return [ env.elements[item.id], index ];
672
673
  return [ null, expr.path.length ];
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { isEdmPropertyRendered, transformExpression } = require('../../model/csnUtils');
3
+ const { isEdmPropertyRendered, transformAnnotationExpression } = require('../../model/csnUtils');
4
4
  const { isBuiltinType, isMagicVariable } = require('../../base/builtins');
5
5
  const edmUtils = require('../edmUtils.js');
6
6
  const oDataDictionary = require('../../gen/Dictionary.json');
@@ -131,7 +131,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
131
131
  const knownAnnos = filterKnownAnnotations(carrier);
132
132
  knownAnnos.forEach((pn) => {
133
133
  scopeCheck.anno = pn;
134
- transformExpression(carrier, pn, scopeCheck, carrier.$path);
134
+ transformAnnotationExpression(carrier, pn, scopeCheck, carrier.$path);
135
135
  });
136
136
  });
137
137
  }
@@ -141,7 +141,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
141
141
  const knownAnnos = filterKnownAnnotations(obj);
142
142
  knownAnnos.forEach((pn) => {
143
143
  scopeCheck.anno = pn;
144
- transformExpression(obj, pn, scopeCheck, obj.$path);
144
+ transformAnnotationExpression(obj, pn, scopeCheck, obj.$path);
145
145
  });
146
146
  };
147
147
  if (def.$isParamEntity && def._origin) {
@@ -171,7 +171,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
171
171
  if (innerAnnotation)
172
172
  newAnno += `.@${innerAnnotation}`;
173
173
  edmUtils.assignAnnotation(def, newAnno, def._origin[attr]);
174
- transformExpression(def._origin, attr, scopeCheck, def._origin.$path);
174
+ transformAnnotationExpression(def._origin, attr, scopeCheck, def._origin.$path);
175
175
  if (paramAnnoParts.length > 1)
176
176
  delete def._origin[attr];
177
177
  }
@@ -419,7 +419,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
419
419
 
420
420
  knownAnnos.forEach((knownAnno) => {
421
421
  if (knownAnno.search(/\.\$edmJson\./g) < 0) {
422
- transformExpression(carrier, knownAnno, {
422
+ transformAnnotationExpression(carrier, knownAnno, {
423
423
  ref: (elemref, prop, xpr, csnPath) => {
424
424
  if (options.isV2() && elemref.$bparam) {
425
425
  error('odata-anno-xpr-ref', ctx.location, {
@@ -842,8 +842,8 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
842
842
  * */
843
843
  let newAnno;
844
844
  const omissions = { 'Aggregation.default': 1 };
845
- const nullList = { 'Core.OperationAvailable': 1 };
846
- if (annoValue !== null && !omissions[termName] || nullList[termName]) {
845
+ const nullList = { 'Core.OperationAvailable': 1, 'Core.OptionalParameter': 1 };
846
+ if (annoValue != null && !omissions[termName] || nullList[termName]) {
847
847
  // termName may contain a qualifier: @UI.FieldGroup#shippingStatus
848
848
  // -> remove qualifier from termName and set Qualifier attribute in newAnno
849
849
  const i = termName.indexOf('#');
@@ -989,6 +989,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
989
989
  else {
990
990
  const res = handleSimpleValue(cAnnoValue, dTypeName, msg);
991
991
  if (((oTermName === 'Core.OperationAvailable' && dTypeName === 'Edm.Boolean') ||
992
+ (oTermName === 'Core.OptionalParameter' && dTypeName === 'Edm.String') ||
992
993
  (oTermName === 'Validation.AllowedValues' && dTypeName === 'Edm.PrimitiveType')) &&
993
994
  cAnnoValue === null) {
994
995
  oTarget.append(new Edm.ValueThing(v, 'Null'));
@@ -1230,7 +1231,10 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
1230
1231
  }
1231
1232
  }
1232
1233
  else if (value === null) {
1233
- if ((resolvedType == null || resolvedType === 'Edm.PrimitiveType') && typeName === 'String') {
1234
+ if ((resolvedType == null ||
1235
+ resolvedType === 'Edm.PrimitiveType' ||
1236
+ resolvedType === 'Edm.String') &&
1237
+ typeName === 'String') {
1234
1238
  resolvedType = 'Edm.String';
1235
1239
  }
1236
1240
  else {
@@ -1589,7 +1593,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
1589
1593
 
1590
1594
  function filterKnownAnnotations( carrier ) {
1591
1595
  const annoNames = Object.keys(carrier).filter( x => x[0] === '@' );
1592
- const nullWhitelist = [ '@Core.OperationAvailable' ];
1596
+ const nullWhitelist = [ '@Core.OperationAvailable', '@Core.OptionalParameter.DefaultValue' ];
1593
1597
  const knownAnnosP = annoNames.filter((n) => {
1594
1598
  const tns = whatsMyTermNamespace(n);
1595
1599
  return tns &&
@@ -266,8 +266,19 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
266
266
  });
267
267
  }
268
268
  });
269
+ if (!options.odataNoCreator) {
270
+ // remove unqualified @Core.Links and #CAP
271
+ Object.keys(serviceCsn).forEach((key) => {
272
+ if (key === '@Core.Links' || key.startsWith('@Core.Links.') ||
273
+ key === '@Core.Links#CAP' || key.startsWith('@Core.Links#CAP.'))
274
+ delete serviceCsn[key];
275
+ });
276
+ }
277
+
269
278
  // Create annotations and distribute into Schemas, merge vocabulary cross refs into xServiceRefs
270
- addAnnotations2XServiceRefs();
279
+ addAnnotationsAndXServiceRefs();
280
+ if (!options.odataNoCreator)
281
+ LeadSchema.prepend(waterMark());
271
282
 
272
283
  // Finally add cross service references into the EDM and extract the targetSchemaNames
273
284
  // for the type cross check
@@ -308,6 +319,18 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
308
319
 
309
320
  return edm;
310
321
 
322
+ function waterMark() {
323
+ const rel = new Edm.PropertyValue(v, 'rel');
324
+ rel._xmlOnlyAttributes.String = 'author';
325
+ rel._jsonOnlyAttributes['Edm.String'] = 'author';
326
+ const href = new Edm.PropertyValue(v, 'href');
327
+ href._xmlOnlyAttributes.String = 'https://cap.cloud.sap';
328
+ href._jsonOnlyAttributes['Edm.String'] = 'https://cap.cloud.sap';
329
+ const watermark = new Edm.Annotation(v, 'Core.Links', new Edm.Collection(v, new Edm.Record(v, rel, href)));
330
+ // watermark._edmAttributes['Qualifier'] = 'CAP';
331
+ return watermark;
332
+ }
333
+
311
334
  // Sort definitions into their schema container
312
335
  function populateSchemas( schemas ) {
313
336
  forEach(reqDefs.definitions, ( fqName, art ) => {
@@ -1099,8 +1122,9 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
1099
1122
  }
1100
1123
 
1101
1124
  // generate the Edm.Annotations tree and append it to the corresponding schema
1102
- function addAnnotations2XServiceRefs( ) {
1125
+ function addAnnotationsAndXServiceRefs( ) {
1103
1126
  options.getFinalTypeInfo = csnUtils.getFinalTypeInfo;
1127
+
1104
1128
  const { annos, usedVocabularies, xrefs } = translate.csn2annotationEdm(reqDefs, csnUtils, csn.vocabularies, serviceCsn.name, Edm, options, messageFunctions, mergedVocabularies);
1105
1129
  // distribute edm:Annotations into the schemas
1106
1130
  // Distribute each anno into Schema
package/lib/edm/edm.js CHANGED
@@ -111,8 +111,7 @@ function getEdm( options, messageFunctions ) {
111
111
  json.$Kind = this.kind;
112
112
 
113
113
  this.toJSONattributes(json);
114
- this.toJSONchildren(json);
115
- return json;
114
+ return this.toJSONchildren(json);
116
115
  }
117
116
 
118
117
  // virtual
@@ -131,6 +130,7 @@ function getEdm( options, messageFunctions ) {
131
130
  this._children.filter(c => c._edmAttributes.Name).forEach((c) => {
132
131
  json[c._edmAttributes.Name] = c.toJSON();
133
132
  });
133
+ return json;
134
134
  }
135
135
 
136
136
  // virtual
@@ -325,7 +325,12 @@ function getEdm( options, messageFunctions ) {
325
325
 
326
326
  toJSONchildren(json) {
327
327
  // 'edmx:DataServices' should not appear in JSON
328
- super.toJSONchildren(json);
328
+ // Annotations first
329
+ this._children.filter(c => c._edmAttributes.Term).forEach((c) => {
330
+ json = { ...json, ...c.toJSON() };
331
+ });
332
+
333
+ json = super.toJSONchildren(json);
329
334
  if (this._annotations.length > 0) {
330
335
  this._annotations.filter(a => a._edmAttributes.Term).forEach((a) => {
331
336
  Object.entries(a.toJSON()).forEach(([ n, v ]) => {
@@ -445,8 +450,7 @@ function getEdm( options, messageFunctions ) {
445
450
  json.$Reference = referenceJson;
446
451
 
447
452
  this._service.toJSONattributes(json);
448
- this._service.toJSONchildren(json);
449
- return json;
453
+ return this._service.toJSONchildren(json);
450
454
  }
451
455
 
452
456
  // all(default), metadata, annotations
@@ -1154,8 +1158,7 @@ function getEdm( options, messageFunctions ) {
1154
1158
  toJSON() {
1155
1159
  const json = Object.create(null);
1156
1160
  this.toJSONattributes(json);
1157
- this.toJSONchildren(json);
1158
- return json;
1161
+ return this.toJSONchildren(json);
1159
1162
  }
1160
1163
 
1161
1164
  getConstantExpressionValue() {
@@ -1226,6 +1229,7 @@ function getEdm( options, messageFunctions ) {
1226
1229
  json[n] = v;
1227
1230
  });
1228
1231
  });
1232
+ return json;
1229
1233
  }
1230
1234
  }
1231
1235
 
@@ -1242,8 +1246,9 @@ function getEdm( options, messageFunctions ) {
1242
1246
  // since it was discovered, that in JSON the EnumMember type must be
1243
1247
  // transported this is no longer the case....
1244
1248
  class Annotation extends AnnotationBase {
1245
- constructor(version, termName) {
1249
+ constructor(version, termName, ...children) {
1246
1250
  super(version, { Term: termName } );
1251
+ this.append(...children);
1247
1252
  }
1248
1253
 
1249
1254
  toJSON() {
@@ -1263,6 +1268,11 @@ function getEdm( options, messageFunctions ) {
1263
1268
  }
1264
1269
 
1265
1270
  class Collection extends AnnotationBase {
1271
+ constructor(version, ...children) {
1272
+ super(version);
1273
+ this.append(...children);
1274
+ }
1275
+
1266
1276
  toJSON() {
1267
1277
  // EDM JSON doesn't mention annotations on collections
1268
1278
  return this._children.map(a => a.toJSON());
@@ -1270,6 +1280,10 @@ function getEdm( options, messageFunctions ) {
1270
1280
  }
1271
1281
 
1272
1282
  class Record extends AnnotationBase {
1283
+ constructor(version, ...children) {
1284
+ super(version);
1285
+ this.append(...children);
1286
+ }
1273
1287
  toJSONattributes(json) {
1274
1288
  if (this._jsonOnlyAttributes.Type)
1275
1289
  json['@type'] = this._jsonOnlyAttributes.Type;
@@ -1300,6 +1314,7 @@ function getEdm( options, messageFunctions ) {
1300
1314
  error(null, `Pease debug me: Unhandled Record child: ${c.kind}`);
1301
1315
  }
1302
1316
  });
1317
+ return json;
1303
1318
  }
1304
1319
  }
1305
1320
 
@@ -3,7 +3,7 @@
3
3
  const { setProp, isBetaEnabled } = require('../base/model');
4
4
  const {
5
5
  forEachDefinition, forEachMemberRecursively, getUtils,
6
- transformExpression, findAnnotationExpression,
6
+ transformAnnotationExpression,
7
7
  } = require('../model/csnUtils');
8
8
  const { isBuiltinType } = require('../base/builtins');
9
9
  const { assignAnnotation } = require('./edmUtils.js');
@@ -144,14 +144,12 @@ function inboundQualificationChecks( csn, options, messageFunctions,
144
144
  parent.$bparam = true;
145
145
  },
146
146
  };
147
- let exprAnnos = Object.keys(action).filter(pn => findAnnotationExpression(action, pn));
148
- exprAnnos.forEach((pn) => {
149
- transformExpression(action, pn, markBindingParam, loc);
147
+ Object.keys(action).filter(pn => pn[0] === '@').forEach((pn) => {
148
+ transformAnnotationExpression(action, pn, markBindingParam, loc);
150
149
  });
151
150
  forEachMemberRecursively(action, (member, _memberName, _prop, path, _parent) => {
152
- exprAnnos = Object.keys(member).filter(pn => findAnnotationExpression(member, pn));
153
- exprAnnos.forEach((pn) => {
154
- transformExpression(member, pn, markBindingParam, path);
151
+ Object.keys(member).filter(pn => pn[0] === '@').forEach((pn) => {
152
+ transformAnnotationExpression(member, pn, markBindingParam, path);
155
153
  });
156
154
  }, loc);
157
155
  }