@sap/cds-compiler 3.6.2 → 3.8.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 (89) hide show
  1. package/CHANGELOG.md +109 -1
  2. package/README.md +3 -0
  3. package/bin/cdsc.js +12 -5
  4. package/doc/CHANGELOG_ARCHIVE.md +6 -6
  5. package/doc/CHANGELOG_BETA.md +35 -2
  6. package/doc/CHANGELOG_DEPRECATED.md +2 -2
  7. package/doc/DeprecatedOptions_v2.md +1 -1
  8. package/doc/NameResolution.md +1 -1
  9. package/lib/api/main.js +63 -23
  10. package/lib/api/options.js +1 -0
  11. package/lib/api/validate.js +5 -0
  12. package/lib/base/dictionaries.js +15 -3
  13. package/lib/base/keywords.js +2 -0
  14. package/lib/base/message-registry.js +120 -34
  15. package/lib/base/messages.js +51 -27
  16. package/lib/base/model.js +4 -2
  17. package/lib/base/shuffle.js +2 -1
  18. package/lib/checks/arrayOfs.js +1 -1
  19. package/lib/checks/defaultValues.js +1 -1
  20. package/lib/checks/elements.js +29 -1
  21. package/lib/checks/{emptyOrOnlyVirtual.js → hasPersistedElements.js} +10 -6
  22. package/lib/checks/invalidTarget.js +1 -1
  23. package/lib/checks/nonexpandableStructured.js +1 -1
  24. package/lib/checks/onConditions.js +15 -9
  25. package/lib/checks/sql-snippets.js +2 -2
  26. package/lib/checks/types.js +5 -1
  27. package/lib/checks/validator.js +7 -3
  28. package/lib/compiler/assert-consistency.js +42 -26
  29. package/lib/compiler/base.js +50 -4
  30. package/lib/compiler/builtins.js +17 -8
  31. package/lib/compiler/checks.js +241 -246
  32. package/lib/compiler/define.js +113 -146
  33. package/lib/compiler/extend.js +889 -383
  34. package/lib/compiler/finalize-parse-cdl.js +5 -58
  35. package/lib/compiler/index.js +1 -1
  36. package/lib/compiler/kick-start.js +7 -8
  37. package/lib/compiler/populate.js +297 -293
  38. package/lib/compiler/propagator.js +27 -18
  39. package/lib/compiler/resolve.js +146 -463
  40. package/lib/compiler/shared.js +36 -79
  41. package/lib/compiler/tweak-assocs.js +30 -28
  42. package/lib/compiler/utils.js +31 -5
  43. package/lib/edm/annotations/genericTranslation.js +131 -59
  44. package/lib/edm/annotations/preprocessAnnotations.js +3 -0
  45. package/lib/edm/csn2edm.js +22 -5
  46. package/lib/edm/edm.js +6 -4
  47. package/lib/edm/edmAnnoPreprocessor.js +1 -0
  48. package/lib/edm/edmPreprocessor.js +42 -26
  49. package/lib/gen/Dictionary.json +38 -2
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +3 -1
  52. package/lib/gen/languageLexer.js +1 -1
  53. package/lib/gen/languageParser.js +4828 -4472
  54. package/lib/inspect/inspectPropagation.js +20 -34
  55. package/lib/json/from-csn.js +140 -44
  56. package/lib/json/to-csn.js +114 -122
  57. package/lib/language/errorStrategy.js +2 -0
  58. package/lib/language/genericAntlrParser.js +156 -36
  59. package/lib/language/language.g4 +100 -58
  60. package/lib/language/textUtils.js +13 -0
  61. package/lib/main.d.ts +43 -3
  62. package/lib/main.js +4 -2
  63. package/lib/model/csnRefs.js +15 -3
  64. package/lib/model/csnUtils.js +12 -74
  65. package/lib/model/revealInternalProperties.js +4 -2
  66. package/lib/modelCompare/compare.js +2 -1
  67. package/lib/optionProcessor.js +3 -0
  68. package/lib/render/manageConstraints.js +5 -2
  69. package/lib/render/toCdl.js +216 -104
  70. package/lib/render/toHdbcds.js +2 -9
  71. package/lib/render/toRename.js +14 -51
  72. package/lib/render/toSql.js +4 -3
  73. package/lib/render/utils/common.js +9 -5
  74. package/lib/transform/braceExpression.js +6 -0
  75. package/lib/transform/db/assertUnique.js +2 -1
  76. package/lib/transform/db/expansion.js +2 -0
  77. package/lib/transform/db/flattening.js +37 -36
  78. package/lib/transform/db/rewriteCalculatedElements.js +600 -0
  79. package/lib/transform/db/transformExists.js +4 -0
  80. package/lib/transform/db/views.js +40 -37
  81. package/lib/transform/forOdataNew.js +20 -15
  82. package/lib/transform/forRelationalDB.js +58 -41
  83. package/lib/transform/odata/typesExposure.js +50 -15
  84. package/lib/transform/parseExpr.js +16 -8
  85. package/lib/transform/transformUtilsNew.js +42 -14
  86. package/lib/transform/translateAssocsToJoins.js +60 -37
  87. package/lib/transform/universalCsn/coreComputed.js +15 -7
  88. package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
  89. package/package.json +2 -1
@@ -55,7 +55,7 @@ const transformers = {
55
55
  kind,
56
56
  id: n => n, // in path item
57
57
  doc: value,
58
- '@': value,
58
+ '@': anno,
59
59
  virtual: value,
60
60
  key: value,
61
61
  unique: value,
@@ -73,6 +73,17 @@ const transformers = {
73
73
  // type properties (without 'elements') ------------------------------------
74
74
  localized: value,
75
75
  type,
76
+ $typeArgs: (node, csn, xsn) => {
77
+ const typeArgs = xsn.$typeArgs;
78
+ // One or two arguments are interpreted as either length or precision/scale.
79
+ if (typeArgs?.length === 1) {
80
+ csn.length = value(typeArgs[0]);
81
+ }
82
+ else if (typeArgs?.length === 2) {
83
+ csn.precision = value(typeArgs[0]);
84
+ csn.scale = value(typeArgs[1]);
85
+ }
86
+ },
76
87
  length: value,
77
88
  precision: value,
78
89
  scale: value,
@@ -190,6 +201,7 @@ const typeProperties = [
190
201
  'cardinality', // for association publishing in views
191
202
  'type', 'length', 'precision', 'scale', 'srid', 'localized', 'notNull',
192
203
  'foreignKeys', 'on', // for explicit ON/keys with REDIRECTED
204
+ '$typeArgs', // for unresolved type arguments, e.g. through parseCql
193
205
  ];
194
206
 
195
207
  const csnDictionaries = [
@@ -243,6 +255,12 @@ function sortCsn( csn, cloneOptions = false ) {
243
255
  setHidden( r, 'elements', csnDictionary( csn.elements, false, cloneOptions ) );
244
256
  if (hasNonEnumerable( csn, '$tableConstraints' ) && !r.$tableConstraints)
245
257
  setHidden( r, '$tableConstraints', csn.$tableConstraints );
258
+ if (cloneOptions.hiddenPropertiesToClone) {
259
+ cloneOptions.hiddenPropertiesToClone.forEach((property) => {
260
+ if ({}.hasOwnProperty.call( csn, property )) // used in generic reference flattener
261
+ setHidden( r, property, csn[property] );
262
+ });
263
+ }
246
264
  }
247
265
  return r;
248
266
  }
@@ -382,109 +400,19 @@ function extensions( node, csn, model ) {
382
400
  if (model.kind && model.kind !== 'source')
383
401
  return undefined;
384
402
  const exts = node.map( definition );
385
-
386
- // builtins are non-enumerable for smaller display
387
- for (const name of Object.getOwnPropertyNames( model.definitions || {} ).sort()) {
388
- const art = model.definitions[name];
389
-
390
- // For namespaces and builtins: Extract annotations since they cannot be represented
391
- // in CSN. For all other artifacts, check whether they may be auto-exposed,
392
- // $inferred, etc. and extract their annotations.
393
- // In parseCdl mode extensions were already put into "extensions".
394
- if (!model.options.parseCdl && (art.kind === 'namespace' || art.builtin)) {
395
- extractAnnotationsToExtension( art );
396
- }
397
- else if (gensrcFlavor) {
403
+ if (gensrcFlavor) {
404
+ for (const name of Object.getOwnPropertyNames( model.definitions || {} ).sort()) {
405
+ const art = model.definitions[name];
398
406
  // From definitions (without redefinitions) with potential inferred elements:
399
407
  const result = { annotate: Object.create(null) };
400
- attachAnnotations(result, 'annotate', { [name]: art }, art.$inferred );
408
+ attachAnnotations( result, 'annotate', { [name]: art }, art.$inferred );
401
409
  if (result.annotate[name])
402
- exts.push({ annotate: name, ...result.annotate[name] } );
410
+ exts.push( { annotate: name, ...result.annotate[name] } );
403
411
  }
404
412
  }
405
-
406
- return exts.sort(
413
+ return exts.sort( // TODO: really sort with parse.cdl?
407
414
  (a, b) => (a.annotate || a.extend).localeCompare( b.annotate || b.extend )
408
415
  );
409
-
410
- /*
411
- function attachElementAnnos( annotate, art ) {
412
- while (art.items)
413
- art = art.items;
414
- if (art.elements) {
415
- const elems = inferred( art.elements, art.$inferred );
416
- if (Object.keys( elems ).length)
417
- annotate.elements = elems;
418
- }
419
- }
420
-
421
- function attachParamAnnos( annotate, art ) {
422
- const inferredParent = art.$inferred;
423
- if (art.params) {
424
- const ext = Object.create( dictionaryPrototype );
425
- for (const name in art.params) {
426
- const par = art.params[name];
427
- if (!inferredParent && !par.$inferred && par.$expand !== 'annotate')
428
- continue;
429
- const render = annotationsAndDocComment( par );
430
- const subElems = par.$expand !== 'origin' && (par.items || par).elements;
431
- if (subElems) {
432
- const sub = inferred( subElems, par.$inferred );
433
- if (Object.keys( sub ).length)
434
- render.elements = sub;
435
- }
436
- if (Object.keys(render).length)
437
- ext[name] = render;
438
- }
439
- if (obj.keys( ext ))
440
- annotate.params = ext;
441
- }
442
- if (art.returns) {
443
- const par = art.returns;
444
- if (!inferredParent && !par.$inferred && par.$expand !== 'annotate')
445
- return;
446
- const render = annotationsAndDocComment( par );
447
- const subElems = par.$expand !== 'origin' && (par.items || par).elements;
448
- if (subElems) {
449
- const sub = inferred( subElems, par.$inferred );
450
- if (Object.keys( sub ).length)
451
- render.elements = sub;
452
- }
453
- if (Object.keys(render).length)
454
- const sub = inferred( subElems, par.$inferred );
455
- if (Object.keys( sub ).length)
456
- render.elements = sub;
457
- }
458
- }
459
- return ext;
460
- */
461
-
462
- // extract namespace/builtin annotations
463
- function extractAnnotationsToExtension( art ) {
464
- const name = art.name.absolute;
465
- // 'true' because annotations on namespaces and builtins can only
466
- // happen through extensions.
467
- const annos = annotationsAndDocComment( art );
468
- const annotate = Object.assign( { annotate: name }, annos );
469
- if (Object.keys( annotate ).length > 1) {
470
- const loc = locationForAnnotationExtension();
471
- if (loc)
472
- location( loc, annotate, art );
473
- exts.push( annotate );
474
- }
475
-
476
- // Either the artifact's name's location or (for builtin types) the location
477
- // of its first annotation.
478
- function locationForAnnotationExtension() {
479
- if (art.location)
480
- return art.location;
481
- for (const key in art) {
482
- if (key.charAt(0) === '@' && art[key].name)
483
- return art[key].name.location;
484
- }
485
- return null;
486
- }
487
- }
488
416
  }
489
417
 
490
418
  /**
@@ -522,7 +450,10 @@ function sources( srcDict, csn ) {
522
450
 
523
451
  function attachAnnotations( annotate, prop, dict, inferred, insideReturns = false ) {
524
452
  const annoDict = Object.create( dictionaryPrototype );
525
- for (const name in dict) {
453
+ const names = Object.keys( dict );
454
+ if (strictMode)
455
+ names.sort();
456
+ for (const name of names) {
526
457
  const entry = dict[name];
527
458
  const inf = inferred || entry.$inferred; // is probably always inferred if parent was
528
459
  const sub = (inf) ? annotationsAndDocComment( entry ) : {};
@@ -531,7 +462,7 @@ function attachAnnotations( annotate, prop, dict, inferred, insideReturns = fals
531
462
  attachAnnotations( sub, 'actions', entry.actions, inf );
532
463
  else if (entry.params)
533
464
  attachAnnotations( sub, 'params', entry.params, inf );
534
- const obj = entry.returns || entry;
465
+ const obj = entry.returns || entry; // TODO: create returns !
535
466
  const many = obj.items || obj;
536
467
  const elems = (many.targetAspect || many).elements;
537
468
  if (elems)
@@ -631,9 +562,11 @@ function elements( dict, csn, node ) {
631
562
  // no 'elements' with SELECT or inferred elements with gensrc;
632
563
  // hidden or visible 'elements' will be set in query()
633
564
  return undefined;
634
- if (dict !== 0)
635
- return insertOrderDict( dict );
636
- return undefined;
565
+ // TODO(!): inside `annotate`, use sorted with --test-mode
566
+ if (dict === 0)
567
+ return undefined;
568
+ // In "super annotate" statements, use sorted dictionary
569
+ return (node.$inferred === '') ? sortedDict( dict ) : insertOrderDict( dict );
637
570
  }
638
571
 
639
572
  function enumDict( dict, csn, node ) {
@@ -748,7 +681,7 @@ function location( loc, csn, xsn ) {
748
681
  * @param {object} csn
749
682
  */
750
683
  function addLocation( loc, csn ) {
751
- if (loc) {
684
+ if (loc?.file) {
752
685
  // Remove endLine/endCol:
753
686
  // Reasoning: $location is mostly attached to definitions/members but the name
754
687
  // is often not the reason for an error or warning. So we gain little benefit for
@@ -773,8 +706,10 @@ function sortedDict( dict ) {
773
706
  return dictionary( dict, keys );
774
707
  }
775
708
 
776
- function actions( dict ) {
709
+ function actions( dict, _csn, node ) {
777
710
  const keys = Object.keys( dict );
711
+ if (strictMode && node.kind === 'annotate')
712
+ keys.sort(); // TODO: always sort with --test-mode ?
778
713
  return (keys.length)
779
714
  ? dictionary( dict, keys, 'actions' )
780
715
  : undefined;
@@ -812,12 +747,12 @@ function foreignKeys( dict, csn, node ) {
812
747
  csn.keys.push( definition( dict[n] ) );
813
748
  }
814
749
 
815
- function returns( art, csn, _node, prop ) {
750
+ function returns( art, csn, node, prop ) {
816
751
  // TODO: currently, the `returns` structure might just have been created by the propagator
817
752
  // if that is the case, there should be no reason to store it in universal CSN
818
- if (universalCsn && art.$inferred === 'proxy')
753
+ if (universalCsn && (art.$inferred === 'proxy' || node.$expand === 'origin'))
819
754
  return undefined;
820
- return definition( art, csn, _node, prop );
755
+ return definition( art, csn, node, prop );
821
756
  }
822
757
 
823
758
  function definition( art, _csn, _node, prop ) {
@@ -872,16 +807,35 @@ function includesOrigin( includes, art ) {
872
807
  for (const prop in aspect) {
873
808
  if ((prop.charAt(0) === '@' || prop === 'doc') &&
874
809
  (!art[prop] || art[prop].$inferred)) {
875
- const anno = aspect[prop];
876
- if (anno.val !== null)
877
- // matererialize non-null annos (whether direct or inherited)
878
- result[prop] = value( Object.create( anno, { $inferred: { value: null } } ) );
810
+ const annoVal = aspect[prop];
811
+ if (annoVal.val !== null)
812
+ // materialize non-null annos (whether direct or inherited)
813
+ result[prop] = value( Object.create( annoVal, { $inferred: { value: null } } ) );
879
814
  }
880
815
  }
881
816
  }
882
817
  return (Object.keys( result ).length === 1) ? $origin : result;
883
818
  }
884
819
 
820
+ /**
821
+ * Calculated elements via `includes` can inherit annotations from sibling elements.
822
+ * These annotations need to be put into `$origin`, because `$origin` points to
823
+ * the calculated element, not the simple ref's artifact.
824
+ */
825
+ function calculatedElementOrigin( csn, xsn, origin ) {
826
+ const $origin = originRef( origin );
827
+ const result = { $origin };
828
+ for (const prop in xsn) {
829
+ if ((prop.charAt(0) === '@' || prop === 'doc') && !origin[prop] && xsn[prop].$inferred) {
830
+ const annoVal = xsn[prop];
831
+ if (annoVal.val !== null)
832
+ // materialize non-null annos (whether direct or inherited)
833
+ result[prop] = value( Object.create( annoVal, { $inferred: { value: null } } ) );
834
+ }
835
+ }
836
+ return (Object.keys( result ).length === 1) ? undefined : result;
837
+ }
838
+
885
839
  function addOrigin( csn, xsn, node ) {
886
840
  if (!universalCsn)
887
841
  return;
@@ -926,18 +880,32 @@ function addOrigin( csn, xsn, node ) {
926
880
  return;
927
881
  }
928
882
  // from here on: member:
883
+ // TODO: write a xsnNode._csnOrigin, which is useful to decide whether to write
884
+ // $origins for its members
929
885
  const parent = getParent( xsn );
930
886
  const parentOrigin = getOrigin( parent );
887
+ // console.log( 'X:',xsn, origin, parent, parentOrigin, getParent( origin ) );
931
888
  if (!origin) {
932
- if (parentOrigin && !(parent.enum && !parent.$origin && parent.type))
889
+ if (parent && parentOrigin &&
890
+ (parent.kind !== 'select' || parent === parent._main._leadingQuery) &&
891
+ !(parent.enum && !parent.$origin && parent.type))
933
892
  csn.$origin = null;
934
893
  return;
935
894
  }
936
- if (parentOrigin === getParent( origin )) {
895
+ if (parent?.kind !== 'select' && parentOrigin?.kind !== 'select' &&
896
+ parentOrigin === getParent( origin )) {
937
897
  // implicit prototype or shortened reference
938
898
  const { id } = origin.name || {};
939
- if (id && xsn.name && id !== xsn.name.id)
899
+
900
+ if (id && xsn.name && id !== xsn.name.id) {
940
901
  csn.$origin = id;
902
+ }
903
+ else if (xsn._calcOrigin) {
904
+ const calcOrigin = calculatedElementOrigin( csn, xsn, origin );
905
+ if (calcOrigin)
906
+ csn.$origin = calcOrigin;
907
+ }
908
+
941
909
  return;
942
910
  }
943
911
  if (origin.kind === 'mixin') {
@@ -1034,13 +1002,13 @@ function getOrigin( art ) {
1034
1002
  if (art.$noOrigin)
1035
1003
  return undefined;
1036
1004
  const { _origin } = art;
1037
- if (_origin)
1038
- return (_origin.kind === 'builtin') ? undefined : _origin; // not $dollarVariable
1005
+ if (_origin) // also for query entities
1006
+ return (_origin.kind === 'builtin') ? undefined : _origin;
1007
+
1039
1008
  if (hasExplicitProp( art.type, 'cast' ))
1040
1009
  return art.type._artifact;
1041
- if (art._from)
1042
- return art._from[0]._origin;
1043
- // must come after _from, since queries can have includes as well.
1010
+ // must come after checking _origin, since entities can have both queries and
1011
+ // includes as well -> the query wins
1044
1012
  if (art.includes)
1045
1013
  return art.includes[0]._artifact;
1046
1014
  return undefined;
@@ -1052,7 +1020,7 @@ function hasExplicitProp( ref, alsoLikeExplicit ) {
1052
1020
 
1053
1021
  /**
1054
1022
  * @param art
1055
- * @param user
1023
+ * @param [user]
1056
1024
  * @return {boolean|string[]}
1057
1025
  */
1058
1026
  function originRef( art, user ) {
@@ -1230,6 +1198,12 @@ function args( node ) {
1230
1198
  return dict;
1231
1199
  }
1232
1200
 
1201
+ function anno( node ) {
1202
+ if (node.$tokenTexts) // expressions in annotation values
1203
+ return Object.assign({ '=': node.$tokenTexts }, expression( node ));
1204
+ return value(node);
1205
+ }
1206
+
1233
1207
  function value( node ) {
1234
1208
  // "Short" value form, e.g. for annotation assignments
1235
1209
  if (!node)
@@ -1243,6 +1217,8 @@ function value( node ) {
1243
1217
  }
1244
1218
  if (node.$inferred && gensrcFlavor)
1245
1219
  return undefined;
1220
+ if (node.$tokenTexts)
1221
+ return Object.assign({ '=': node.$tokenTexts }, expression( node ));
1246
1222
  if (node.path) {
1247
1223
  const ref = pathName( node.path );
1248
1224
  return extra( { '=': node.variant ? `${ ref }#${ pathName(node.variant.path) }` : ref }, node );
@@ -1300,7 +1276,7 @@ function expression( node ) {
1300
1276
  function exprInternal( node, xprParens ) {
1301
1277
  if (typeof node === 'string')
1302
1278
  return node;
1303
- if (!node) // make to-csn robst
1279
+ if (!node) // make to-csn robust
1304
1280
  return {};
1305
1281
  if (node.scope === 'param') {
1306
1282
  if (node.path)
@@ -1343,6 +1319,8 @@ function exprInternal( node, xprParens ) {
1343
1319
  case 'ixpr':
1344
1320
  case 'xpr':
1345
1321
  break;
1322
+ case '?:':
1323
+ return ternaryOperator( node );
1346
1324
  case 'cast':
1347
1325
  return cast( expression( node.args[0] ), node );
1348
1326
  case 'list':
@@ -1379,6 +1357,20 @@ function flattenInternalXpr( array, op ) {
1379
1357
  return left;
1380
1358
  }
1381
1359
 
1360
+ function ternaryOperator( node ) {
1361
+ const rargs = [
1362
+ 'case',
1363
+ 'when', exprInternal(node.args[0]),
1364
+ 'then', exprInternal(node.args[2]),
1365
+ 'else', exprInternal(node.args[4]),
1366
+ 'end',
1367
+ ];
1368
+
1369
+ if (node.$parens?.length)
1370
+ return { xpr: flattenInternalXpr( rargs, 'xpr' ) };
1371
+ return flattenInternalXpr( rargs, 'xpr' );
1372
+ }
1373
+
1382
1374
  function query( node, csn, xsn, _prop, expectedParens = 0 ) {
1383
1375
  if (node.op.val === 'SELECT') {
1384
1376
  if (xsn && xsn.query === node && xsn.$syntax === 'projection' &&
@@ -1548,7 +1540,7 @@ function setHidden( obj, prop, val ) {
1548
1540
  }
1549
1541
 
1550
1542
  function addExplicitAs( node, name, implicit ) {
1551
- if (name && name.id &&
1543
+ if (name?.id && name.$inferred !== '$internal' &&
1552
1544
  (!name.$inferred || !node.ref && !node.func || implicit && implicit(name.id) ))
1553
1545
  node.as = name.id;
1554
1546
  return node;
@@ -101,6 +101,7 @@ Object.assign( KeywordErrorStrategy.prototype, {
101
101
 
102
102
  // Attempt to recover from problems in subrules, except if rule has defined a
103
103
  // local variable `_sync` with value 'nop'
104
+ // TODO: consider performance - see #8800
104
105
  function sync( recognizer ) {
105
106
  // If already recovering, don't try to sync
106
107
  if (this.inErrorRecoveryMode(recognizer))
@@ -406,6 +407,7 @@ function intervalSetToArray( recognizer, expected, excludesForNextToken ) {
406
407
  return names;
407
408
  }
408
409
 
410
+ // Used for sorting in messages
409
411
  const token1sort = {
410
412
  // 0: Identifier, Number, ...
411
413
  // 1: separators: