@sap/cds-compiler 6.3.6 → 6.4.6

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 (62) hide show
  1. package/CHANGELOG.md +101 -3
  2. package/LICENSE +32 -0
  3. package/README.md +14 -2
  4. package/bin/cdsse.js +0 -3
  5. package/doc/CHANGELOG_BETA.md +1 -1
  6. package/doc/CHANGELOG_DEPRECATED.md +1 -1
  7. package/lib/base/message-registry.js +9 -2
  8. package/lib/base/messages.js +1 -1
  9. package/lib/base/model.js +2 -0
  10. package/lib/checks/existsExpressionsOnlyForeignKeys.js +16 -10
  11. package/lib/checks/existsMustEndInAssoc.js +1 -1
  12. package/lib/checks/existsMustNotStartWithDollarSelf.js +31 -0
  13. package/lib/checks/validator.js +4 -2
  14. package/lib/compiler/assert-consistency.js +3 -2
  15. package/lib/compiler/builtins.js +5 -6
  16. package/lib/compiler/checks.js +37 -26
  17. package/lib/compiler/define.js +1 -1
  18. package/lib/compiler/extend.js +39 -50
  19. package/lib/compiler/finalize-parse-cdl.js +1 -1
  20. package/lib/compiler/lsp-api.js +1 -1
  21. package/lib/compiler/populate.js +2 -2
  22. package/lib/compiler/propagator.js +29 -6
  23. package/lib/compiler/resolve.js +13 -3
  24. package/lib/compiler/shared.js +157 -133
  25. package/lib/compiler/tweak-assocs.js +87 -29
  26. package/lib/compiler/xpr-rewrite.js +164 -160
  27. package/lib/edm/annotations/edmJson.js +206 -37
  28. package/lib/edm/csn2edm.js +13 -0
  29. package/lib/edm/edmUtils.js +2 -2
  30. package/lib/gen/BaseParser.js +106 -72
  31. package/lib/gen/CdlGrammar.checksum +1 -1
  32. package/lib/gen/CdlParser.js +1501 -1509
  33. package/lib/json/to-csn.js +8 -5
  34. package/lib/language/genericAntlrParser.js +0 -0
  35. package/lib/main.js +19 -16
  36. package/lib/model/csnRefs.js +589 -521
  37. package/lib/model/csnUtils.js +8 -5
  38. package/lib/model/enrichCsn.js +1 -0
  39. package/lib/parsers/AstBuildingParser.js +73 -28
  40. package/lib/render/toCdl.js +2 -1
  41. package/lib/render/toHdbcds.js +6 -3
  42. package/lib/render/toSql.js +5 -0
  43. package/lib/transform/db/applyTransformations.js +1 -1
  44. package/lib/transform/db/assertUnique.js +4 -1
  45. package/lib/transform/db/assocsToQueries/transformExists.js +3 -10
  46. package/lib/transform/db/assocsToQueries/utils.js +0 -5
  47. package/lib/transform/db/cdsPersistence.js +17 -18
  48. package/lib/transform/db/expansion.js +179 -3
  49. package/lib/transform/db/flattening.js +16 -5
  50. package/lib/transform/db/rewriteCalculatedElements.js +79 -283
  51. package/lib/transform/effective/main.js +8 -1
  52. package/lib/transform/forOdata.js +1 -1
  53. package/lib/transform/forRelationalDB.js +21 -80
  54. package/lib/transform/localized.js +75 -127
  55. package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +89 -63
  56. package/lib/transform/transformUtils.js +23 -21
  57. package/lib/transform/translateAssocsToJoins.js +7 -5
  58. package/lib/transform/tupleExpansion.js +16 -3
  59. package/package.json +3 -3
  60. package/doc/DeprecatedOptions_v2.md +0 -150
  61. package/doc/NameResolution.md +0 -837
  62. package/lib/transform/parseExpr.js +0 -415
@@ -261,8 +261,7 @@ function extend( model ) {
261
261
  const { name } = ext;
262
262
  const { path } = name;
263
263
  if (name._artifact === undefined) {
264
- const refCtx = (name.id.startsWith( 'localized.' )) ? '_extensions' : ext.kind;
265
- resolvePath( name, refCtx, ext ); // for LSP
264
+ resolvePath( name, ext.kind, ext ); // induce error & for LSP
266
265
  }
267
266
  else if (model.options.lspMode && path?.[0]._artifact === undefined) {
268
267
  // we don't use resolvePath(…,'extend'), as that would add a dependency
@@ -487,13 +486,17 @@ function extend( model ) {
487
486
  return anno;
488
487
  const hasBase = previousAnno?.literal === 'array';
489
488
  if (!previousAnno) {
490
- const location = firstEllipsis.location || anno.name.location;
491
- message( 'anno-unexpected-ellipsis', [ location, art ], { code: '...' } );
489
+ const { location } = anno.name;
490
+ if (annoName !== '@extension.code') {
491
+ // Remark: we could allow that for all annotations which are not propagated
492
+ message( 'anno-unexpected-ellipsis', [ firstEllipsis.location || location, art ],
493
+ { code: '...' } );
494
+ }
492
495
  previousAnno = {
493
496
  kind: '$annotation',
494
497
  val: [],
495
498
  literal: 'array',
496
- name: { id: annoName.slice( 1 ) },
499
+ name: anno.name,
497
500
  location,
498
501
  };
499
502
  }
@@ -899,11 +902,9 @@ function extend( model ) {
899
902
  function createSuperAnnotate( annotate ) {
900
903
  const extensions = annotate._extensions;
901
904
  if (extensions && !annotate._main) {
902
- const { id } = annotate.name;
903
- const isLocalized = id.startsWith( 'localized.' ); // TODO: && anno
904
- const art = model.definitions[id];
905
+ const art = model.definitions[annotate.name.id];
905
906
  for (const ext of extensions)
906
- checkRemainingMainExtensions( art, ext, isLocalized );
907
+ checkRemainingMainExtensions( art, ext );
907
908
  if (art?.builtin && art.kind !== 'namespace') { // TODO: do not set `builtin` on cds, cds.hana
908
909
  setLink( annotate, '_extensions', art._extensions ); // for messages and member extensions
909
910
  // direct annotations on builtins or on the builtins for propagation, and
@@ -923,24 +924,14 @@ function extend( model ) {
923
924
  forEachMember( annotate, createSuperAnnotate );
924
925
  }
925
926
 
926
- function checkRemainingMainExtensions( art, ext, localized ) {
927
- const isExtend = ext.kind === 'extend';
928
- if (localized) {
929
- if (isExtend) {
930
- // In v5, reject any `extend` on localized.
931
- error( 'ref-undefined-art', [ ext.location || ext.name.location, ext ],
932
- { '#': 'localized', keyword: 'annotate' } );
933
- }
934
- return;
935
- }
936
-
927
+ function checkRemainingMainExtensions( art, ext ) {
937
928
  if (!resolvePath( ext.name, ext.kind, ext )) // error for extend, info for annotate
938
929
  return;
939
930
 
940
931
  if (art?.builtin) {
941
932
  info( 'anno-builtin', [ ext.name.location, ext ], {} ); // TODO: better location?
942
933
  }
943
- else if (isExtend && art?.kind === 'namespace') {
934
+ else if (ext.kind === 'extend' && art?.kind === 'namespace') {
944
935
  // `annotate` on namespaces already handled before
945
936
  const hasAnnotations = Object.keys(ext).find(a => a.charAt(0) === '@');
946
937
  const firstAnno = ext[hasAnnotations];
@@ -992,7 +983,7 @@ function extend( model ) {
992
983
  * includes, do so without includes.
993
984
  */
994
985
  function applyExtensions() {
995
- let noIncludes = false;
986
+ let cyclicIncludeNames = false;
996
987
  let extNames = Object.keys( extensionsDict ).sort();
997
988
 
998
989
  while (extNames.length) {
@@ -1000,35 +991,37 @@ function extend( model ) {
1000
991
  for (const name of extNames) {
1001
992
  const art = model.definitions[name];
1002
993
  if (art && art.kind !== 'namespace' &&
1003
- extendArtifact( extensionsDict[name], art, noIncludes ))
994
+ extendArtifact( extensionsDict[name], art, cyclicIncludeNames ))
1004
995
  delete extensionsDict[name];
1005
996
  }
1006
997
  extNames = Object.keys( extensionsDict ); // no sort() required anymore
1007
998
  if (extNames.length >= length)
1008
- noIncludes = Object.keys( extensionsDict ); // = no includes
999
+ cyclicIncludeNames = Object.keys( extensionsDict ); // = no includes
1009
1000
  }
1010
1001
  }
1011
1002
 
1012
1003
  /**
1013
- * Extend artifact `art` by `extensions`. `noIncludes` can have values:
1014
- * - false: includes are applied, extend and annotate is performed
1015
- * - true: includes are not applied, extend and annotate is performed
1016
- * - 'gen': no includes and no extensions allowed, annotate is performed
1004
+ * Extend artifact `art` by `extensions`. `cyclicIncludeNames` can have values:
1005
+ * - falsy: try to apply include, then perform extend and annotate
1006
+ * - an array of include names with cyclic dependencies: includes are not applied,
1007
+ * extend and annotate is performed
1008
+ * remark: we could have applied includes without cycle
1009
+ *
1010
+ * Returns true if extend and annotate are performed.
1017
1011
  *
1018
1012
  * @param {XSN.Extension[]} extensions
1019
1013
  * @param {XSN.Definition} art
1020
- * @param {boolean|'gen'} [noIncludes=false]
1014
+ * @param {String[]|false} [cyclicIncludeNames=false]
1021
1015
  */
1022
- function extendArtifact( extensions, art, noIncludes = false ) {
1023
- if (!noIncludes && !(canApplyIncludes( art, art ) &&
1016
+ function extendArtifact( extensions, art, cyclicIncludeNames = null ) {
1017
+ if (!cyclicIncludeNames && !(canApplyIncludes( art, art ) &&
1024
1018
  extensions.every( ext => canApplyIncludes( ext, art ) )))
1025
1019
  return false;
1026
- if (Array.isArray( noIncludes )) {
1027
- canApplyIncludes( art, art, noIncludes );
1028
- extensions.forEach( ext => canApplyIncludes( ext, art, noIncludes ) );
1020
+ if (cyclicIncludeNames) {
1021
+ canApplyIncludes( art, art, cyclicIncludeNames );
1022
+ extensions.forEach( ext => canApplyIncludes( ext, art, cyclicIncludeNames ) );
1029
1023
  }
1030
- else if (!noIncludes &&
1031
- !(canApplyIncludes( art, art ) &&
1024
+ else if (!(canApplyIncludes( art, art ) &&
1032
1025
  extensions.every( ext => canApplyIncludes( ext, art ) ))) {
1033
1026
  // console.log( 'FALSE:',art.name, extensions.map( e => e.name ) )
1034
1027
  return false;
@@ -1038,17 +1031,18 @@ function extend( model ) {
1038
1031
  art.$entity = ++model.$entity;
1039
1032
  }
1040
1033
  if (art.includes) {
1041
- if (!noIncludes) {
1034
+ if (!cyclicIncludeNames) {
1042
1035
  applyIncludes( art, art );
1043
1036
  }
1044
1037
  else {
1038
+ // resolve artifacts to induce errors: either ref-invalid-include or ref-cyclic
1045
1039
  for (const ref of art.includes)
1046
1040
  resolvePath( ref, 'include', art );
1047
1041
  }
1048
1042
  }
1049
1043
  // checkExtensionsKind( extensions, art );
1050
- extendMembers( extensions, art, noIncludes === 'gen' );
1051
- if (!noIncludes && art.includes) {
1044
+ extendMembers( extensions, art );
1045
+ if (!cyclicIncludeNames && art.includes) {
1052
1046
  // early propagation of specific annotation assignments
1053
1047
  propagateEarly( art, '@cds.autoexpose' );
1054
1048
  propagateEarly( art, '@fiori.draft.enabled' );
@@ -1057,7 +1051,7 @@ function extend( model ) {
1057
1051
  return true;
1058
1052
  }
1059
1053
 
1060
- function extendMembers( extensions, art, noExtend ) {
1054
+ function extendMembers( extensions, art ) {
1061
1055
  // TODO: do the whole extension stuff lazily if the elements are requested
1062
1056
  const elemExtensions = [];
1063
1057
  if (art._main) // extensions already sorted for main artifacts
@@ -1069,11 +1063,6 @@ function extend( model ) {
1069
1063
  // 'Info', 'EXT').toString())
1070
1064
  if (ext.name._artifact === undefined) { // not already applied
1071
1065
  setArtifactLink( ext.name, art );
1072
- if (noExtend && ext.kind === 'extend') {
1073
- error( 'extend-for-generated', [ ext.name.location, ext ], { art, keyword: 'extend' },
1074
- 'You can\'t use $(KEYWORD) on the generated $(ART)' );
1075
- continue;
1076
- }
1077
1066
  if (ext.includes) {
1078
1067
  // TODO: currently, re-compiling from gensrc does not give the exact
1079
1068
  // element sequence - we need something like
@@ -1207,17 +1196,17 @@ function extend( model ) {
1207
1196
  *
1208
1197
  * @param {XSN.Definition} art
1209
1198
  * @param {XSN.Artifact} target
1210
- * @param {string[]} [justResolveCyclic]
1199
+ * @param {string[]} [cyclicIncludeNames]
1211
1200
  * @returns {boolean}
1212
1201
  */
1213
- function canApplyIncludes( art, target, justResolveCyclic ) {
1202
+ function canApplyIncludes( art, target, cyclicIncludeNames ) {
1214
1203
  if (!art.includes)
1215
1204
  return true;
1216
1205
  for (const ref of art.includes) {
1217
1206
  const name = resolveUncheckedPath( ref, 'include', art );
1218
- // console.log('CAI:',justResolveCyclic, name, ref.path, Object.keys(extensionsDict))
1219
- if (justResolveCyclic) {
1220
- if (!justResolveCyclic.includes( name ))
1207
+ // console.log('CAI:',cyclicIncludeNames, name, ref.path, Object.keys(extensionsDict))
1208
+ if (cyclicIncludeNames) {
1209
+ if (!cyclicIncludeNames.includes( name ))
1221
1210
  continue;
1222
1211
  delete ref._artifact;
1223
1212
  }
@@ -40,7 +40,7 @@ function finalizeParseCdl( model ) {
40
40
  // TODO: why not just use the extensions as they are from the first source?
41
41
  for (const name in late) {
42
42
  for (const ext of late[name]._extensions) {
43
- ext.name.id = resolveUncheckedPath( ext.name, '_extensions', ext );
43
+ ext.name.id = resolveUncheckedPath( ext.name, '_uncheckedExtension', ext );
44
44
  // Initialize members and define annotations in sub-elements.
45
45
  initMembers( ext, ext, ext._block, true );
46
46
  extensions.push( ext );
@@ -7,7 +7,7 @@
7
7
  //
8
8
  // This files includes an iterator over "semantic tokens" in an XSN model.
9
9
  // "Semantic tokens" are identifiers, but also the "return" parameter.
10
- // See `internalDoc/lsp/IdentifierCrawling.md` for details.
10
+ // See <../../internalDoc/lsp/IdentifierCrawling.md> for details.
11
11
 
12
12
  const { CompilerAssertion } = require('../base/error');
13
13
  const $inferred = Symbol.for( 'cds.$inferred' );
@@ -882,7 +882,7 @@ function populate( model ) {
882
882
  for (const name in env) {
883
883
  const navElem = env[name];
884
884
  // TODO: remove all access to masked (use 'grep')
885
- if (excludingDict[name] || navElem.masked && navElem.masked.val)
885
+ if (excludingDict[name] || navElem.masked?.val)
886
886
  continue;
887
887
  const sibling = siblingElements[name];
888
888
  if (sibling) { // is explicitly provided (without duplicate)
@@ -917,7 +917,7 @@ function populate( model ) {
917
917
  const elemLocation = !query._main.$inferred && location;
918
918
  const origin = envParent ? navElem : navElem._origin;
919
919
  const elem = linkToOrigin( origin, name, query, null, elemLocation );
920
- if (origin.$calcDepElement) // TODO: this will be changed in the next PR
920
+ if (origin.$calcDepElement)
921
921
  dependsOn( elem, origin.$calcDepElement, location );
922
922
 
923
923
  // TODO: check assocToMany { * }
@@ -12,6 +12,7 @@ const {
12
12
  forEachDefinition,
13
13
  forEachMember,
14
14
  forEachGeneric,
15
+ isBetaEnabled,
15
16
  } = require( '../base/model');
16
17
  const {
17
18
  setLink,
@@ -34,7 +35,11 @@ function propagate( model ) {
34
35
  virtual,
35
36
  notNull,
36
37
  targetElement: onlyViaParent, // in foreign keys
37
- value: onlyViaParent, // enum symbol value, calculated element
38
+ value: enumOrCalcValue, // enum symbol value, calculated element
39
+ // `value` is also used for column expression
40
+ // TODO(!): think of having an extra XSN property for calculated elements,
41
+ // replacing `value:…`+`$syntax:'calc'` and `$calc:…
42
+ $calc: enumOrCalcValue,
38
43
  // masked: special = done in definer
39
44
  // key: special = done in resolver
40
45
  // actions: struct includes & primary source = in definer/resolver
@@ -59,7 +64,7 @@ function propagate( model ) {
59
64
  // enum: expensive,
60
65
  // params: expensive, // actually only with parent action
61
66
  // returns,
62
- $enclosed: annotation,
67
+ $enclosed: annotation, // TODO: hm
63
68
  };
64
69
  const ruleToFunction = {
65
70
  __proto__: null,
@@ -71,7 +76,7 @@ function propagate( model ) {
71
76
  for (const rule in propagationRules)
72
77
  props[rule] = ruleToFunction[propagationRules[rule]];
73
78
 
74
- const { rewriteAnnotationsRefs } = xprRewriteFns( model );
79
+ const { rewriteAnnotationsRefs, rewriteRefsInExpression } = xprRewriteFns( model );
75
80
 
76
81
  const { message, throwWithError } = model.$messageFunctions;
77
82
 
@@ -165,7 +170,9 @@ function propagate( model ) {
165
170
  // console.log('PROPS:',ref(source),'->',ref(target),keys.join('+'))
166
171
  for (const prop of keys) {
167
172
  // TODO: warning with competing props from multi-includes, but not in propagator.js
168
- if (target[prop] !== undefined || source[prop] === undefined)
173
+ if (target[prop] !== undefined &&
174
+ (prop !== 'value' || !source.$calcDepElement || !target._main?.query) ||
175
+ source[prop] === undefined)
169
176
  continue;
170
177
  const transformer = props[prop] || props[prop.charAt(0)];
171
178
  if (transformer)
@@ -282,6 +289,22 @@ function propagate( model ) {
282
289
  }
283
290
  }
284
291
 
292
+ function enumOrCalcValue( prop, destination, origin ) {
293
+ // Remark: with include, the calc expression has been copied early
294
+ if (prop === 'value' && !origin.$calcDepElement) {
295
+ onlyViaParent( prop, destination, origin ); // enum value
296
+ }
297
+ else if (destination.kind === 'element' &&
298
+ destination._main?.query && // query element
299
+ !destination.$calc && origin.$calc !== true &&
300
+ isBetaEnabled( model.options, '$calcForDraft' )) {
301
+ destination.$calc
302
+ = Object.assign( copyExpr( origin[prop] ), { $inferred: 'prop' } );
303
+ if (rewriteRefsInExpression( destination, origin, '$calc' ))
304
+ destination.$calc = true; // TODO: or { val: true }?
305
+ }
306
+ }
307
+
285
308
  function notWithExpand( prop, target, source ) {
286
309
  if (!target.expand || prop === 'type' && source.elements)
287
310
  always( prop, target, source );
@@ -296,13 +319,13 @@ function propagate( model ) {
296
319
  function annotation( prop, target, source ) {
297
320
  const anno = source[prop];
298
321
  if (anno.val !== null)
299
- withKind( prop, target, source );
322
+ withKind( prop, target, source ); // TODO: unfold
300
323
  }
301
324
 
302
325
  function docComment( prop, target, source ) {
303
326
  if (model.options.propagateDocComments)
304
327
  annotation( prop, target, source );
305
- else // TODO: Probably just "never"
328
+ else // TODO: or just "never"
306
329
  onlyViaParent( prop, target, source );
307
330
  }
308
331
 
@@ -1583,12 +1583,19 @@ function resolve( model ) {
1583
1583
  // (for code completion)
1584
1584
  const last = expr.path[expr.path.length - 1];
1585
1585
  if (!last || !(last.args || last.where || last.cardinality) ||
1586
- expr.$expected === 'approved-exists' ||
1587
1586
  user.expand || user.inline ||
1588
1587
  expWithFilter.includes( expected ) || // `from`, …
1589
1588
  last._navigation?.kind === '$tableAlias') // error already reported
1590
1589
  return ref;
1591
1590
 
1591
+ if (expr.$expected === 'approved-exists') {
1592
+ if (last.where?.args?.length === 0) {
1593
+ // at the moment, empty filter is not allowed on last path step
1594
+ reportUnexpectedArgsAndFilter( last, expected, user, null, 'last-empty-filter' );
1595
+ }
1596
+ return ref;
1597
+ }
1598
+
1592
1599
  const type = effectiveType( last._artifact );
1593
1600
  const art = type && (type.kind === 'entity' ? type : type.target?._artifact);
1594
1601
  if (!art)
@@ -1596,7 +1603,8 @@ function resolve( model ) {
1596
1603
  if (last.args || last.where || last.cardinality) {
1597
1604
  const unexpectedFilter = (expected !== 'annotation' && expected !== 'column' &&
1598
1605
  expected !== 'calc' && 'std') ||
1599
- isQuasiVirtualAssociation( type ) && 'model-only';
1606
+ isQuasiVirtualAssociation( type ) && 'model-only' ||
1607
+ last.where?.args?.length === 0 && 'last-empty-filter';
1600
1608
  reportUnexpectedArgsAndFilter( last, expected, user, art, unexpectedFilter );
1601
1609
  }
1602
1610
  // TODO: we should have different message-ids for the "last" stuff: adding
@@ -1696,7 +1704,9 @@ function resolve( model ) {
1696
1704
  error( 'expr-unexpected-argument', loc, { '#': variant } );
1697
1705
  }
1698
1706
  if ((step.where || step.cardinality) && variant) {
1699
- const location = combinedLocation( step.where, step.cardinality );
1707
+ const location = step.where?.location || step.cardinality?.location
1708
+ ? combinedLocation( step.where, step.cardinality )
1709
+ : step.location;
1700
1710
  // XSN TODO: filter$location including […]
1701
1711
  error( 'expr-unexpected-filter', [ location, user ], { '#': variant } );
1702
1712
  }