@sap/cds-compiler 4.8.0 → 4.9.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 (92) hide show
  1. package/CHANGELOG.md +29 -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 +14 -1
  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 +32 -19
  12. package/lib/base/messages.js +50 -19
  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 +8 -2
  20. package/lib/compiler/checks.js +44 -18
  21. package/lib/compiler/define.js +34 -22
  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/propagator.js +21 -18
  26. package/lib/compiler/resolve.js +44 -28
  27. package/lib/compiler/shared.js +60 -20
  28. package/lib/compiler/tweak-assocs.js +13 -88
  29. package/lib/compiler/xpr-rewrite.js +689 -0
  30. package/lib/edm/annotations/genericTranslation.js +80 -60
  31. package/lib/edm/edm.js +4 -4
  32. package/lib/edm/edmInboundChecks.js +33 -0
  33. package/lib/edm/edmPreprocessor.js +9 -6
  34. package/lib/gen/Dictionary.json +129 -14
  35. package/lib/gen/language.checksum +1 -1
  36. package/lib/gen/language.interp +1 -1
  37. package/lib/gen/languageParser.js +1523 -1518
  38. package/lib/json/from-csn.js +13 -4
  39. package/lib/json/to-csn.js +10 -11
  40. package/lib/language/genericAntlrParser.js +14 -6
  41. package/lib/main.d.ts +67 -14
  42. package/lib/main.js +1 -0
  43. package/lib/model/cloneCsn.js +6 -3
  44. package/lib/model/csnRefs.js +12 -7
  45. package/lib/model/csnUtils.js +13 -7
  46. package/lib/model/enrichCsn.js +3 -1
  47. package/lib/model/revealInternalProperties.js +2 -1
  48. package/lib/model/sortViews.js +14 -6
  49. package/lib/modelCompare/compare.js +33 -34
  50. package/lib/optionProcessor.js +27 -2
  51. package/lib/render/DuplicateChecker.js +6 -6
  52. package/lib/render/manageConstraints.js +1 -0
  53. package/lib/render/toCdl.js +3 -1
  54. package/lib/transform/db/applyTransformations.js +33 -0
  55. package/lib/transform/db/constraints.js +1 -1
  56. package/lib/transform/db/expansion.js +8 -3
  57. package/lib/transform/db/groupByOrderBy.js +2 -2
  58. package/lib/transform/db/temporal.js +6 -3
  59. package/lib/transform/db/transformExists.js +2 -2
  60. package/lib/transform/effective/annotations.js +194 -0
  61. package/lib/transform/effective/main.js +6 -8
  62. package/lib/transform/effective/misc.js +31 -10
  63. package/lib/transform/forOdata.js +23 -7
  64. package/lib/transform/forRelationalDB.js +1 -1
  65. package/lib/transform/localized.js +7 -6
  66. package/lib/transform/odata/flattening.js +189 -106
  67. package/lib/transform/odata/toFinalBaseType.js +1 -1
  68. package/lib/transform/odata/typesExposure.js +15 -12
  69. package/lib/transform/parseExpr.js +4 -4
  70. package/lib/transform/transformUtils.js +40 -37
  71. package/lib/transform/translateAssocsToJoins.js +47 -47
  72. package/lib/transform/universalCsn/universalCsnEnricher.js +12 -16
  73. package/package.json +1 -1
  74. package/share/messages/anno-missing-rewrite.md +45 -0
  75. package/share/messages/message-explanations.json +1 -0
  76. package/bin/.eslintrc.json +0 -17
  77. package/lib/api/.eslintrc.json +0 -37
  78. package/lib/checks/.eslintrc.json +0 -31
  79. package/lib/compiler/.eslintrc.json +0 -8
  80. package/lib/edm/.eslintrc.json +0 -46
  81. package/lib/inspect/.eslintrc.json +0 -4
  82. package/lib/json/.eslintrc.json +0 -4
  83. package/lib/language/.eslintrc.json +0 -4
  84. package/lib/model/.eslintrc.json +0 -13
  85. package/lib/modelCompare/utils/.eslintrc.json +0 -22
  86. package/lib/render/.eslintrc.json +0 -22
  87. package/lib/transform/.eslintrc.json +0 -13
  88. package/lib/transform/db/.eslintrc.json +0 -41
  89. package/lib/transform/draft/.eslintrc.json +0 -4
  90. package/lib/transform/effective/.eslintrc.json +0 -4
  91. package/lib/transform/universalCsn/.eslintrc.json +0 -37
  92. package/lib/utils/.eslintrc.json +0 -7
@@ -19,6 +19,7 @@ const {
19
19
  isAssocToPrimaryKeys,
20
20
  artifactRefLocation,
21
21
  } = require('./utils');
22
+ const { isBetaEnabled } = require('../base/model');
22
23
 
23
24
  const $inferred = Symbol.for( 'cds.$inferred' );
24
25
  const $location = Symbol.for( 'cds.$location' );
@@ -133,14 +134,14 @@ function fns( model ) {
133
134
  param: paramSemantics,
134
135
  },
135
136
  filter: {
136
- lexical: justDollarSelf,
137
+ lexical: justDollarAliases,
137
138
  dollar: true,
138
139
  dynamic: targetElements,
139
140
  notFound: undefinedTargetElement,
140
141
  param: paramSemantics,
141
142
  },
142
143
  'calc-filter': {
143
- lexical: justDollarSelf,
144
+ lexical: justDollarAliases,
144
145
  dollar: true,
145
146
  dynamic: targetElements,
146
147
  navigation: calcElemNavigation,
@@ -181,7 +182,7 @@ function fns( model ) {
181
182
  check: checkColumnRef,
182
183
  param: paramSemantics,
183
184
  nestedColumn: () => ({ // in expand and inline
184
- lexical: justDollarSelf,
185
+ lexical: justDollarAliases,
185
186
  dollar: true,
186
187
  dynamic: nestedElements,
187
188
  notFound: undefinedNestedElement,
@@ -197,7 +198,7 @@ function fns( model ) {
197
198
  param: paramSemantics,
198
199
  },
199
200
  calc: {
200
- lexical: justDollarSelf,
201
+ lexical: justDollarAliases,
201
202
  dollar: true,
202
203
  dynamic: parentElements,
203
204
  navigation: calcElemNavigation,
@@ -213,7 +214,7 @@ function fns( model ) {
213
214
  param: paramSemantics,
214
215
  },
215
216
  on: { // unmanaged assoc: outside query, redirected or new assoc in column
216
- lexical: justDollarSelf,
217
+ lexical: justDollarAliases,
217
218
  dollar: true,
218
219
  dynamic: parentElements,
219
220
  navigation: assocOnNavigation,
@@ -222,7 +223,7 @@ function fns( model ) {
222
223
  check: checkAssocOn,
223
224
  param: paramUnsupported,
224
225
  nestedColumn: () => ({ // in expand and inline
225
- lexical: justDollarSelf,
226
+ lexical: justDollarAliases,
226
227
  dollar: true,
227
228
  dynamic: parentElements,
228
229
  navigation: assocOnNavigation,
@@ -276,7 +277,7 @@ function fns( model ) {
276
277
  param: paramSemantics,
277
278
  },
278
279
  annotation: { // annotation assignments
279
- lexical: justDollarSelf, // TODO: forbid $projection
280
+ lexical: justDollarAliases,
280
281
  dollar: true,
281
282
  dynamic: parentElements,
282
283
  navigation: assocOnNavigation,
@@ -287,8 +288,8 @@ function fns( model ) {
287
288
  'ref-undefined-param': 'anno-undefined-param',
288
289
  },
289
290
  param: paramSemantics,
290
- nestedColumn: () => ({ // in expand and inline - TODO
291
- lexical: justDollarSelf,
291
+ nestedColumn: () => ({
292
+ lexical: justDollarAliases,
292
293
  dollar: true,
293
294
  dynamic: parentElements,
294
295
  navigation: assocOnNavigation,
@@ -298,14 +299,21 @@ function fns( model ) {
298
299
  },
299
300
  // TODO: introduce some kind of inheritance
300
301
  annoRewrite: { // annotation assignments
301
- lexical: justDollarSelf, // TODO: forbid $projection
302
+ lexical: justDollarAliases,
302
303
  dollar: true,
303
304
  dynamic: parentElements,
304
305
  navigation: assocOnNavigation,
305
306
  noDep: true,
306
307
  notFound: null, // no error, just falsy links
307
308
  param: paramSemantics,
308
- // nestedColumn: // in expand and inline - TODO
309
+ nestedColumn: () => ({
310
+ lexical: justDollarAliases,
311
+ dollar: true,
312
+ dynamic: parentElements,
313
+ navigation: assocOnNavigation,
314
+ notFound: undefinedParentElement,
315
+ rewriteProjectionToSelf: true,
316
+ }),
309
317
  },
310
318
  };
311
319
 
@@ -313,9 +321,11 @@ function fns( model ) {
313
321
  traverseExpr,
314
322
  resolveUncheckedPath,
315
323
  resolveTypeArgumentsUnchecked, // TODO: move to some other file
324
+ resolvePathRoot,
316
325
  resolvePath,
317
326
  checkExpr,
318
327
  checkOnCondition,
328
+ navigationEnv,
319
329
  nestedElements,
320
330
  attachAndEmitValidNames,
321
331
  } );
@@ -354,7 +364,6 @@ function fns( model ) {
354
364
  const { path } = ref;
355
365
  if (!path || path.broken) // incomplete type AST
356
366
  return undefined;
357
-
358
367
  const semantics = referenceSemantics[refCtx];
359
368
  if (!semantics.isMainRef)
360
369
  throw new CompilerAssertion( `resolveUncheckedPath() called for reference ctx '${ refCtx }'` );
@@ -628,6 +637,22 @@ function fns( model ) {
628
637
  return art;
629
638
  }
630
639
 
640
+ /**
641
+ * Resolve the _path-root_ only. Used for rewriting annotation paths.
642
+ *
643
+ * @param ref
644
+ * @param {string} expected
645
+ * @param user
646
+ */
647
+ function resolvePathRoot( ref, expected, user ) {
648
+ if (ref == null || !ref.path) // no references -> nothing to do
649
+ return undefined;
650
+ const s = referenceSemantics[expected];
651
+ const semantics = (typeof s === 'string') ? referenceSemantics[s] : s;
652
+ const r = getPathRoot( ref, semantics, user );
653
+ return r && acceptPathRoot( r, ref, semantics, user );
654
+ }
655
+
631
656
  // Helper functions for resolve[Unchecked]Path, getPath{Root,Item}: -----------
632
657
 
633
658
  function acceptLexical( art, path, semantics, user ) {
@@ -687,9 +712,14 @@ function fns( model ) {
687
712
  // TODO: remove again, should be easy enough in to-csn without.
688
713
  if (path.length === 1 && art.kind === '$tableAlias')
689
714
  (user._user || user).$noOrigin = true;
715
+ if (head.id === '$projection' && user.kind === '$annotation') {
716
+ error( 'ref-unsupported-projection', [ head.location, user ],
717
+ { code: '$projection', newcode: '$self' },
718
+ '$(CODE) is not supported in annotations; replace by $(NEWCODE)' );
719
+ }
690
720
  return art;
691
721
  }
692
- case '$parameters': { // TODO: remove from CC
722
+ case '$parameters': {
693
723
  // TODO: if ref.scope='param' is handled, test that here, too ?
694
724
  const { id } = path[1];
695
725
  message( 'ref-obsolete-parameters', [ head.location, user ],
@@ -756,10 +786,10 @@ function fns( model ) {
756
786
  function userBlock( user ) {
757
787
  return definedViaCdl( user ) && user._block;
758
788
  }
759
- function justDollarSelf( user ) {
789
+ function justDollarAliases( user ) {
760
790
  const query = userQuery( user );
761
791
  if (!query)
762
- return user._main || user;
792
+ return user._main || user; // TODO: also contains `up_` for aspects; remove
763
793
  // query.$tableAliases contains both aliases and $self/$projection
764
794
  const aliases = query.$tableAliases;
765
795
  const r = Object.create( null );
@@ -783,7 +813,7 @@ function fns( model ) {
783
813
  const aliases = userQuery( user ).$tableAliases;
784
814
  user.$extended = Object.keys( aliases )[0];
785
815
  }
786
- return justDollarSelf( user );
816
+ return justDollarAliases( user );
787
817
  }
788
818
 
789
819
  // Functions called via semantics.dynamic: ------------------------------------
@@ -829,7 +859,13 @@ function fns( model ) {
829
859
  return query._combined; // TODO: do we need query._parent._combined ?
830
860
  }
831
861
  function parentElements( user ) {
832
- return environment( user._main && user.kind !== 'select' ? user._parent : user );
862
+ // Note: We could have `$self` in bound actions refer to its entity, but reject it now.
863
+ // If users request it, we can either allow it later or point them to binding parameters.
864
+ const useParent = user._main &&
865
+ user.kind !== 'select' &&
866
+ user.kind !== 'action' &&
867
+ user.kind !== 'function';
868
+ return environment( useParent ? user._parent : user );
833
869
  }
834
870
 
835
871
  function queryElements( user ) {
@@ -1144,7 +1180,7 @@ function fns( model ) {
1144
1180
  // corresponding column expression; this might have references to elements
1145
1181
  // of invisible table aliases; at least one stakeholder uses this,
1146
1182
  // so it can't be an error (yet).
1147
- warning( 'ref-deprecated-self-element', [ ref.path[0].location, user._user ], {},
1183
+ message( 'ref-deprecated-self-element', [ ref.path[0].location, user._user ], {},
1148
1184
  // eslint-disable-next-line max-len
1149
1185
  'Referring to the query\'s own elements here might lead to invalid SQL references; use source elements only' );
1150
1186
  return false;
@@ -1199,8 +1235,12 @@ function fns( model ) {
1199
1235
  }
1200
1236
 
1201
1237
  function acceptRealArtifact( art, user, ref ) {
1202
- // For compatibility, we accept `extend Unknown` without elements/actions/includes
1203
- if (art.kind !== 'namespace' || !(user.elements || user.actions || user.includes))
1238
+ if (art.kind !== 'namespace')
1239
+ return art;
1240
+ // For compatibility (≤v4), we accept `extend Unknown` without elements/actions/includes
1241
+ // In v5, only allow `extend with definitions`.
1242
+ if (!isBetaEnabled( model.options, 'v5preview' ) &&
1243
+ !(user.elements || user.actions || user.includes))
1204
1244
  return art;
1205
1245
  const { location } = ref.path[ref.path.length - 1];
1206
1246
  signalNotFound( 'ref-undefined-def', [ location, user ], null, { art } );
@@ -4,7 +4,6 @@
4
4
 
5
5
  const {
6
6
  forEachGeneric,
7
- forEachMember,
8
7
  forEachInOrder,
9
8
  } = require('../base/model');
10
9
  const { dictLocation, weakLocation, weakRefLocation } = require('../base/location');
@@ -33,7 +32,6 @@ function tweakAssocs( model ) {
33
32
  info, warning, error,
34
33
  } = model.$messageFunctions;
35
34
  const {
36
- resolvePath,
37
35
  traverseExpr,
38
36
  checkExpr,
39
37
  checkOnCondition,
@@ -78,8 +76,6 @@ function tweakAssocs( model ) {
78
76
  traverseQueryPost( art.query, false, ( query ) => {
79
77
  forEachGeneric( query, 'elements', rewriteAssociationCheck );
80
78
  } );
81
-
82
- checkForAnnotationRefs( art );
83
79
  }
84
80
 
85
81
  // function rewriteView( view ) {
@@ -124,90 +120,6 @@ function tweakAssocs( model ) {
124
120
  }
125
121
  }
126
122
 
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
123
  function rewriteAssociationCheck( element ) {
212
124
  const elem = element.items || element; // TODO v5: nested items
213
125
  if (elem.elements)
@@ -524,6 +436,18 @@ function tweakAssocs( model ) {
524
436
  $inferred: 'copy',
525
437
  };
526
438
 
439
+ // Published paths with filters are always associations, never
440
+ // compositions, hence we need to change the type to avoid type propagation.
441
+ const assocType = { id: 'cds.Association', location };
442
+ setArtifactLink( assocType, model.definitions['cds.Association'] );
443
+ elem.type = {
444
+ path: [ assocType ],
445
+ scope: 'global',
446
+ location,
447
+ $inferred: '$generated',
448
+ };
449
+ setArtifactLink( elem.type, assocType._artifact );
450
+
527
451
  elem.$filtered = {
528
452
  val: true,
529
453
  literal: 'boolean',
@@ -820,6 +744,7 @@ function tweakAssocs( model ) {
820
744
  return null;
821
745
  }
822
746
  item.id = name;
747
+ // TODO: Why not break here? Test test3/scenarios/AFC/db/view/consumption/C_ScopedRole.cds
823
748
  }
824
749
  }
825
750
  let env = name && elem._effectiveType; // should have been computed