@sap/cds-compiler 4.8.0 → 4.9.2

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 (95) hide show
  1. package/CHANGELOG.md +38 -4
  2. package/bin/cds_remove_invalid_whitespace.js +135 -0
  3. package/bin/cds_update_annotations.js +180 -0
  4. package/bin/cds_update_identifiers.js +3 -4
  5. package/bin/cdsc.js +30 -17
  6. package/doc/CHANGELOG_BETA.md +19 -0
  7. package/lib/api/main.js +59 -24
  8. package/lib/api/options.js +12 -1
  9. package/lib/api/validate.js +1 -5
  10. package/lib/base/builtins.js +27 -0
  11. package/lib/base/message-registry.js +38 -21
  12. package/lib/base/messages.js +51 -20
  13. package/lib/base/model.js +4 -5
  14. package/lib/checks/actionsFunctions.js +2 -2
  15. package/lib/checks/annotationsOData.js +3 -0
  16. package/lib/checks/defaultValues.js +5 -2
  17. package/lib/checks/queryNoDbArtifacts.js +3 -2
  18. package/lib/checks/validator.js +2 -34
  19. package/lib/compiler/assert-consistency.js +10 -3
  20. package/lib/compiler/checks.js +44 -18
  21. package/lib/compiler/define.js +38 -30
  22. package/lib/compiler/extend.js +33 -10
  23. package/lib/compiler/index.js +0 -1
  24. package/lib/compiler/lsp-api.js +5 -0
  25. package/lib/compiler/populate.js +0 -2
  26. package/lib/compiler/propagator.js +23 -19
  27. package/lib/compiler/resolve.js +48 -29
  28. package/lib/compiler/shared.js +60 -20
  29. package/lib/compiler/tweak-assocs.js +72 -116
  30. package/lib/compiler/xpr-rewrite.js +762 -0
  31. package/lib/edm/annotations/edmJson.js +24 -7
  32. package/lib/edm/annotations/genericTranslation.js +81 -61
  33. package/lib/edm/edm.js +4 -4
  34. package/lib/edm/edmInboundChecks.js +33 -0
  35. package/lib/edm/edmPreprocessor.js +9 -6
  36. package/lib/gen/Dictionary.json +129 -14
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +1 -1
  39. package/lib/gen/languageParser.js +1523 -1518
  40. package/lib/json/from-csn.js +13 -4
  41. package/lib/json/to-csn.js +12 -12
  42. package/lib/language/genericAntlrParser.js +14 -6
  43. package/lib/main.d.ts +67 -14
  44. package/lib/main.js +1 -0
  45. package/lib/model/cloneCsn.js +6 -3
  46. package/lib/model/csnRefs.js +23 -11
  47. package/lib/model/csnUtils.js +13 -7
  48. package/lib/model/enrichCsn.js +3 -1
  49. package/lib/model/revealInternalProperties.js +2 -1
  50. package/lib/model/sortViews.js +14 -6
  51. package/lib/modelCompare/compare.js +33 -34
  52. package/lib/optionProcessor.js +27 -2
  53. package/lib/render/DuplicateChecker.js +6 -6
  54. package/lib/render/manageConstraints.js +1 -0
  55. package/lib/render/toCdl.js +3 -1
  56. package/lib/transform/db/applyTransformations.js +33 -0
  57. package/lib/transform/db/constraints.js +75 -28
  58. package/lib/transform/db/expansion.js +8 -3
  59. package/lib/transform/db/flattening.js +2 -2
  60. package/lib/transform/db/groupByOrderBy.js +2 -2
  61. package/lib/transform/db/temporal.js +6 -3
  62. package/lib/transform/db/transformExists.js +2 -2
  63. package/lib/transform/effective/annotations.js +194 -0
  64. package/lib/transform/effective/main.js +6 -8
  65. package/lib/transform/effective/misc.js +31 -10
  66. package/lib/transform/forOdata.js +23 -7
  67. package/lib/transform/forRelationalDB.js +3 -3
  68. package/lib/transform/localized.js +7 -6
  69. package/lib/transform/odata/flattening.js +221 -124
  70. package/lib/transform/odata/toFinalBaseType.js +1 -1
  71. package/lib/transform/odata/typesExposure.js +15 -12
  72. package/lib/transform/parseExpr.js +4 -4
  73. package/lib/transform/transformUtils.js +47 -42
  74. package/lib/transform/translateAssocsToJoins.js +47 -47
  75. package/lib/transform/universalCsn/universalCsnEnricher.js +16 -19
  76. package/package.json +1 -1
  77. package/share/messages/anno-missing-rewrite.md +45 -0
  78. package/share/messages/message-explanations.json +1 -0
  79. package/bin/.eslintrc.json +0 -17
  80. package/lib/api/.eslintrc.json +0 -37
  81. package/lib/checks/.eslintrc.json +0 -31
  82. package/lib/compiler/.eslintrc.json +0 -8
  83. package/lib/edm/.eslintrc.json +0 -46
  84. package/lib/inspect/.eslintrc.json +0 -4
  85. package/lib/json/.eslintrc.json +0 -4
  86. package/lib/language/.eslintrc.json +0 -4
  87. package/lib/model/.eslintrc.json +0 -13
  88. package/lib/modelCompare/utils/.eslintrc.json +0 -22
  89. package/lib/render/.eslintrc.json +0 -22
  90. package/lib/transform/.eslintrc.json +0 -13
  91. package/lib/transform/db/.eslintrc.json +0 -41
  92. package/lib/transform/draft/.eslintrc.json +0 -4
  93. package/lib/transform/effective/.eslintrc.json +0 -4
  94. package/lib/transform/universalCsn/.eslintrc.json +0 -37
  95. package/lib/utils/.eslintrc.json +0 -7
@@ -4,8 +4,8 @@
4
4
 
5
5
  const {
6
6
  forEachGeneric,
7
- forEachMember,
8
7
  forEachInOrder,
8
+ isBetaEnabled,
9
9
  } = require('../base/model');
10
10
  const { dictLocation, weakLocation, weakRefLocation } = require('../base/location');
11
11
 
@@ -19,6 +19,7 @@ const {
19
19
  traverseQueryPost,
20
20
  traverseQueryExtra,
21
21
  setExpandStatus,
22
+ getUnderlyingBuiltinType,
22
23
  } = require('./utils');
23
24
  const { Location } = require('../base/location');
24
25
  const { CompilerAssertion } = require('../base/error');
@@ -33,7 +34,6 @@ function tweakAssocs( model ) {
33
34
  info, warning, error,
34
35
  } = model.$messageFunctions;
35
36
  const {
36
- resolvePath,
37
37
  traverseExpr,
38
38
  checkExpr,
39
39
  checkOnCondition,
@@ -41,6 +41,12 @@ function tweakAssocs( model ) {
41
41
  getOrigin,
42
42
  } = model.$functions;
43
43
 
44
+ Object.assign(model.$functions, {
45
+ firstProjectionForPath,
46
+ });
47
+
48
+ const isV5preview = isBetaEnabled( model.options, 'v5preview' );
49
+
44
50
  // Phase 5: rewrite associations
45
51
  model._entities.forEach( rewriteArtifact );
46
52
  // Think hard whether an on condition rewrite can lead to a new cyclic
@@ -78,8 +84,6 @@ function tweakAssocs( model ) {
78
84
  traverseQueryPost( art.query, false, ( query ) => {
79
85
  forEachGeneric( query, 'elements', rewriteAssociationCheck );
80
86
  } );
81
-
82
- checkForAnnotationRefs( art );
83
87
  }
84
88
 
85
89
  // function rewriteView( view ) {
@@ -124,90 +128,6 @@ function tweakAssocs( model ) {
124
128
  }
125
129
  }
126
130
 
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
- for (const val of expr.val) {
165
- const found = checkAnnotationForRefs( val, user );
166
- if (found)
167
- return found;
168
- }
169
- return null;
170
- }
171
- if (expr.literal !== 'struct')
172
- return null;
173
- const struct = Object.values( expr.struct );
174
- for (const val of struct) {
175
- const found = checkAnnotationForRefs( val, user );
176
- if (found)
177
- return found;
178
- }
179
- return null;
180
- }
181
-
182
- function checkAnnotationRef( ref, refCtx, user ) {
183
- const origPath = ref.path;
184
- if (!origPath[origPath.length - 1]._artifact) // already wrong in original
185
- return null;
186
- ref = { ...ref, path: [ ...origPath.map( item => ({ ...item }) ) ] };
187
- if (!resolvePath( ref, refCtx, user ))
188
- return ref;
189
- return ref.path.some( isUnrelated ) && ref;
190
-
191
- function isUnrelated( item, idx ) {
192
- let elem = item._artifact;
193
- const orig = origPath[idx]._artifact;
194
- // With includes, we allow shadowing: an included element might seem to be
195
- // unrelated.
196
- if (elem._main?.includes && elem._main === (user._main || user))
197
- return false;
198
- if (!elem._effectiveType) // safety
199
- return false;
200
- // With redirections, the originally referred object might not be the same
201
- // or even the direct _origin
202
- do {
203
- if (elem === orig)
204
- return false;
205
- elem = elem._origin;
206
- } while (elem);
207
- return true;
208
- }
209
- }
210
-
211
131
  function rewriteAssociationCheck( element ) {
212
132
  const elem = element.items || element; // TODO v5: nested items
213
133
  if (elem.elements)
@@ -524,14 +444,39 @@ function tweakAssocs( model ) {
524
444
  $inferred: 'copy',
525
445
  };
526
446
 
527
- elem.$filtered = {
528
- val: true,
529
- literal: 'boolean',
447
+ // Published paths with filters are always associations, never
448
+ // compositions, hence we need to change the type to avoid type propagation.
449
+ const assocType = { id: 'cds.Association', location };
450
+ setArtifactLink( assocType, model.definitions['cds.Association'] );
451
+ elem.type = {
452
+ path: [ assocType ],
453
+ scope: 'global',
530
454
  location,
531
455
  $inferred: '$generated',
532
456
  };
457
+ setArtifactLink( elem.type, assocType._artifact );
458
+
459
+ if (!isV5preview) { // TODO(v5): Remove, only use $enclosed
460
+ elem.$filtered = {
461
+ val: true,
462
+ literal: 'boolean',
463
+ location,
464
+ $inferred: '$generated',
465
+ };
466
+ }
467
+
468
+ const isComp = (getUnderlyingBuiltinType( assoc )?.name?.id === 'cds.Composition');
469
+ if (isComp) {
470
+ elem.$enclosed = {
471
+ val: true,
472
+ literal: 'boolean',
473
+ location,
474
+ $inferred: '$generated',
475
+ };
476
+ }
533
477
  }
534
478
 
479
+
535
480
  /**
536
481
  * Transform a filter on `assocPathStep` into an ON-condition.
537
482
  * Paths inside the filter are rewritten relative to `assoc`, so they can be redirected
@@ -617,7 +562,7 @@ function tweakAssocs( model ) {
617
562
  setLink( rhs, '_artifact', rhs.path[rhs.path.length - 1]._artifact );
618
563
 
619
564
  if (elem.$syntax !== 'calc') { // different to lhs!
620
- const projectedFk = firstProjectionForPath( rhs.path, nav.tableAlias, elem );
565
+ const projectedFk = firstProjectionForPath( rhs.path, 0, nav.tableAlias, elem );
621
566
  rewritePath( rhs, projectedFk.item, elem, projectedFk.elem, elem.value.location );
622
567
  }
623
568
 
@@ -672,7 +617,8 @@ function tweakAssocs( model ) {
672
617
  return; // not $self or source element
673
618
  if (expr.scope === 'param' || root.kind === '$parameters')
674
619
  return; // are not allowed anyway - there was an error before
675
- const result = firstProjectionForPath( expr.path, tableAlias, assoc );
620
+ const startIndex = (root.kind === '$self' ? 1 : 0);
621
+ const result = firstProjectionForPath( expr.path, startIndex, tableAlias, assoc );
676
622
  // For `assoc[…]`, ensure that we don't rewrite to another projection on `assoc`.
677
623
  if (result.item && assoc._origin === result.item._artifact)
678
624
  result.elem = assoc;
@@ -820,6 +766,7 @@ function tweakAssocs( model ) {
820
766
  return null;
821
767
  }
822
768
  item.id = name;
769
+ // TODO: Why not break here? Test test3/scenarios/AFC/db/view/consumption/C_ScopedRole.cds
823
770
  }
824
771
  }
825
772
  let env = name && elem._effectiveType; // should have been computed
@@ -857,51 +804,60 @@ function navProjection( navigation, preferred ) {
857
804
 
858
805
 
859
806
  /**
860
- * For a path `a.b.c.d`, return a projection for the first path item that is projected.
807
+ * For a path `a.b.c.d`, return a projection for the first path item that is projected,
808
+ * starting at `startIndex` in this path using the given navigation (table alias or
809
+ * navigation element).
861
810
  * For example, if a query has multiple projections such as `a.b, a, a.b.c`, the
862
811
  * _first_ possible projection will be used and the caller can rewrite `a.b.c.d` to `b.c.d`.
863
- * This avoids that `extend`s affect the ON-condition.
812
+ * This avoids `extend`s affect the ON-condition.
864
813
  *
865
- * The returned object `ret` has `ret.item`, which is the path item that is projected.
866
- * `ret.elem` is the element projection.
814
+ * The returned object `ret` has `ret.item`, which is the path item at index `ret.index`
815
+ * that is projected. `ret.elem` is the element projection.
867
816
  *
868
817
  * @param {any[]} path
869
- * @param {object} tableAlias
870
- * @param {object} assoc Preferred association that should be used if projected.
818
+ * @param {number} startIndex
819
+ * @param {object} nav
820
+ * @param {object} elem Preferred association/element that should be used if projected.
871
821
  * @return {{elem: object, item: object}|null}
872
822
  */
873
- function firstProjectionForPath( path, tableAlias, assoc ) {
874
- const viaSelf = (path[0]._navigation || path[0]._artifact).kind === '$self';
875
- const root = viaSelf ? 1 : 0;
876
- if (root >= path.length) // e.g. just `$self` path item
823
+ function firstProjectionForPath( path, startIndex, nav, elem ) {
824
+ if (startIndex >= path.length) // e.g. just `$self` path item
877
825
  return { item: undefined, elem: {} };
878
826
 
827
+ let tableAlias = nav;
828
+ while (tableAlias.kind === '$navElement')
829
+ tableAlias = tableAlias._parent;
830
+
879
831
  // We want to use the _first_ valid projection that is written by the user (if the preferred
880
- // `assoc` is not directly projected). To achieve that, look into the table alias' elements.
832
+ // `assoc` is not directly projected). To achieve that, look into the query's elements.
881
833
  const selectedElements = Object.values(tableAlias._parent.elements);
882
- const proj = [];
883
- let navItem = tableAlias;
884
- for (const item of path.slice(root)) {
834
+
835
+ let proj = null;
836
+ let navItem = nav;
837
+ for (let i = startIndex; i < path.length; ++i) {
838
+ const item = path[i];
885
839
  navItem = item?.id && navItem.elements?.[item.id];
886
840
  if (!navItem) {
887
841
  break;
888
842
  }
889
843
  else if (navItem._projections) {
890
- const elem = navProjection( navItem, assoc );
891
- if (elem && elem === assoc) {
844
+ const projElem = navProjection( navItem, elem );
845
+ if (projElem && projElem === elem) {
892
846
  // in case the specified association is found, _always_ use it.
893
- return { item, elem };
847
+ return { index: i, item, elem };
894
848
  }
895
- else if (elem) {
896
- const index = selectedElements.indexOf(elem);
897
- proj.push({ item, elem, index });
849
+ else if (projElem) {
850
+ const queryIndex = selectedElements.indexOf(projElem);
851
+ if (!proj || queryIndex < proj.queryIndex) {
852
+ proj = {
853
+ index: i, item, elem: projElem, queryIndex,
854
+ };
855
+ }
898
856
  }
899
857
  }
900
858
  }
901
859
 
902
- return (proj.length === 0)
903
- ? { item: path[root], elem: null }
904
- : proj.reduce( (acc, curr) => (acc.index > curr.index ? curr : acc), proj[0] ); // first
860
+ return proj || { index: startIndex, item: path[startIndex], elem: null };
905
861
  }
906
862
 
907
863
  /**