@sap/cds-compiler 3.7.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 (70) hide show
  1. package/CHANGELOG.md +63 -4
  2. package/bin/cdsc.js +3 -0
  3. package/doc/CHANGELOG_ARCHIVE.md +6 -6
  4. package/doc/CHANGELOG_BETA.md +15 -0
  5. package/doc/DeprecatedOptions_v2.md +1 -1
  6. package/doc/NameResolution.md +1 -1
  7. package/lib/api/main.js +61 -22
  8. package/lib/api/options.js +1 -0
  9. package/lib/api/validate.js +5 -0
  10. package/lib/base/dictionaries.js +5 -3
  11. package/lib/base/keywords.js +2 -0
  12. package/lib/base/message-registry.js +64 -22
  13. package/lib/base/messages.js +12 -7
  14. package/lib/base/model.js +3 -2
  15. package/lib/checks/arrayOfs.js +1 -1
  16. package/lib/checks/defaultValues.js +1 -1
  17. package/lib/checks/hasPersistedElements.js +1 -1
  18. package/lib/checks/invalidTarget.js +1 -1
  19. package/lib/checks/onConditions.js +9 -6
  20. package/lib/checks/sql-snippets.js +2 -2
  21. package/lib/checks/types.js +1 -2
  22. package/lib/compiler/assert-consistency.js +24 -5
  23. package/lib/compiler/base.js +49 -2
  24. package/lib/compiler/builtins.js +15 -6
  25. package/lib/compiler/checks.js +4 -4
  26. package/lib/compiler/define.js +59 -80
  27. package/lib/compiler/extend.js +701 -498
  28. package/lib/compiler/finalize-parse-cdl.js +4 -3
  29. package/lib/compiler/index.js +1 -1
  30. package/lib/compiler/kick-start.js +2 -2
  31. package/lib/compiler/populate.js +17 -9
  32. package/lib/compiler/propagator.js +12 -5
  33. package/lib/compiler/resolve.js +26 -173
  34. package/lib/compiler/shared.js +12 -53
  35. package/lib/compiler/tweak-assocs.js +1 -1
  36. package/lib/compiler/utils.js +2 -2
  37. package/lib/edm/annotations/genericTranslation.js +124 -46
  38. package/lib/edm/csn2edm.js +22 -1
  39. package/lib/edm/edmPreprocessor.js +41 -21
  40. package/lib/gen/Dictionary.json +4 -0
  41. package/lib/gen/language.checksum +1 -1
  42. package/lib/gen/language.interp +3 -1
  43. package/lib/gen/languageLexer.js +1 -1
  44. package/lib/gen/languageParser.js +4810 -4482
  45. package/lib/inspect/inspectPropagation.js +20 -36
  46. package/lib/json/from-csn.js +55 -5
  47. package/lib/json/to-csn.js +71 -110
  48. package/lib/language/errorStrategy.js +1 -0
  49. package/lib/language/genericAntlrParser.js +47 -8
  50. package/lib/language/language.g4 +88 -62
  51. package/lib/language/textUtils.js +13 -0
  52. package/lib/main.d.ts +43 -3
  53. package/lib/main.js +4 -2
  54. package/lib/model/csnRefs.js +14 -2
  55. package/lib/model/csnUtils.js +11 -74
  56. package/lib/model/revealInternalProperties.js +3 -0
  57. package/lib/optionProcessor.js +3 -0
  58. package/lib/render/toCdl.js +203 -104
  59. package/lib/render/toHdbcds.js +0 -1
  60. package/lib/render/toRename.js +14 -51
  61. package/lib/transform/braceExpression.js +6 -0
  62. package/lib/transform/db/rewriteCalculatedElements.js +55 -14
  63. package/lib/transform/forOdataNew.js +20 -15
  64. package/lib/transform/forRelationalDB.js +21 -14
  65. package/lib/transform/parseExpr.js +2 -0
  66. package/lib/transform/transformUtilsNew.js +36 -9
  67. package/lib/transform/translateAssocsToJoins.js +11 -4
  68. package/lib/transform/universalCsn/coreComputed.js +15 -7
  69. package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
  70. package/package.json +2 -1
@@ -4,7 +4,13 @@ const { createMessageFunctions } = require('../base/messages');
4
4
  const { locationString } = require('../base/location');
5
5
  const { findArtifact, stringRefToPath } = require('./inspectUtils');
6
6
  const { term } = require('../utils/term');
7
- const { CompilerAssertion } = require('../base/error');
7
+
8
+ const inferredNiceOutput = {
9
+ '*': 'wildcard',
10
+ 'aspect-composition': 'composition',
11
+ prop: 'propagation',
12
+ $generated: 'generated',
13
+ };
8
14
 
9
15
  /**
10
16
  * @param {XSN.Model} xsn
@@ -77,29 +83,16 @@ function _inspectAnnotations( artifactXsn ) {
77
83
  for (const anno of annos) {
78
84
  const annoXsn = artifactXsn[anno];
79
85
  const loc = locationString(annoXsn.name.location);
86
+
80
87
  let origin;
81
- switch (annoXsn.$priority) {
82
- case false:
83
- if (annoXsn.$inferred)
84
- origin = 'propagation';
85
- else
86
- origin = 'direct';
87
- break;
88
-
89
- case 'extend':
90
- case 'annotate':
91
- origin = annoXsn.$priority;
92
- break;
93
-
94
- case undefined:
95
- if (annoXsn.$inferred === '$generated') {
96
- origin = 'generated';
97
- break;
98
- }
99
- // fallthrough
100
- default:
101
- throw new CompilerAssertion(`inspect anno: Unhandled Case: ${ annoXsn.$priority }`);
102
- }
88
+ if (annoXsn.$inferred === '$generated')
89
+ origin = 'generated';
90
+ else if (annoXsn.$inferred)
91
+ origin = inferredNiceOutput[annoXsn.$inferred] || annoXsn.$inferred;
92
+ else if (isContainedInParentLocation(annoXsn.name, artifactXsn))
93
+ origin = 'direct';
94
+ else
95
+ origin = 'annotate'; // ...or `extend`
103
96
 
104
97
  maxAnnoLength = Math.max(maxAnnoLength, anno.length);
105
98
 
@@ -122,11 +115,6 @@ function _inspectElements( artifactXsn ) {
122
115
  const result = [];
123
116
  const elements = Object.keys(artifactXsn.elements);
124
117
 
125
- const inferredNiceOutput = {
126
- '*': 'wildcard',
127
- 'aspect-composition': 'composition',
128
- };
129
-
130
118
  let maxElemLength = 12;
131
119
  let maxOriginLength = 6;
132
120
 
@@ -153,7 +141,7 @@ function _inspectElements( artifactXsn ) {
153
141
  origin = elementXsn.$inferred;
154
142
  }
155
143
  else if (!isContainedInParentLocation(elementXsn, artifactXsn)) {
156
- // just a heuristic
144
+ // just a heuristic - a good enough one
157
145
  origin = 'extend';
158
146
  }
159
147
  else {
@@ -192,13 +180,9 @@ function isContainedInParentLocation( art, parent ) {
192
180
  const parentLoc = parent.location;
193
181
  if (artLoc.file !== parentLoc.file)
194
182
  return false;
195
- // Warning is correct, but we have a "TODO" below.
196
- // eslint-disable-next-line sonarjs/prefer-single-boolean-return
197
- if (artLoc.line < parentLoc.line || artLoc.line > parentLoc.endLine)
198
- return false;
199
- // Good enough for now
200
- // TODO: Check columns
201
- return true;
183
+ const startDiff = artLoc.line - parentLoc.line || artLoc.col - parentLoc.col;
184
+ const endDiff = artLoc.endLine - parentLoc.endLine || artLoc.endCol - parentLoc.endCol;
185
+ return startDiff >= 0 && endDiff <= 0;
202
186
  }
203
187
 
204
188
  module.exports = {
@@ -75,6 +75,8 @@
75
75
  * it.
76
76
  * @property {string} [xorException] A property name that is allowed besides another property
77
77
  * of an xorGroup (as an exception to the rule).
78
+ * @property {boolean} [ignoreExtra] Whether extra properties are ignored and not put
79
+ * into $extra.
78
80
  */
79
81
 
80
82
  /**
@@ -303,10 +305,20 @@ const schema = compileSchema( {
303
305
  },
304
306
  // type properties (except: elements, enum, keys, on): ---------------------
305
307
  type: {
306
- type: artifactRef,
308
+ type: typeArtifactRef,
307
309
  msgVariant: 'or-object', // for 'syntax-expecting-string',
308
310
  optional: [ 'ref', 'global' ],
309
311
  inKind: [ 'element', 'type', 'param', 'mixin', 'event', 'annotation', 'extend' ],
312
+ schema: {
313
+ ref: {
314
+ arrayOf: typeRefItem,
315
+ type: renameTo( 'path', typeRef ),
316
+ minLength: 1,
317
+ requires: 'id',
318
+ optional: [ 'id' ],
319
+ ignoreExtra: true, // custom properties inside `ref` ignored.
320
+ },
321
+ },
310
322
  },
311
323
  targetAspect: {
312
324
  type: artifactRef,
@@ -429,6 +441,7 @@ const schema = compileSchema( {
429
441
  list: {
430
442
  class: 'condition',
431
443
  type: list,
444
+ inKind: [ '$column' ],
432
445
  },
433
446
  val: {
434
447
  type: value,
@@ -604,7 +617,7 @@ const schema = compileSchema( {
604
617
  optional: [ '=', '#', 'xpr', 'ref', 'val', 'list', 'literal', 'func', 'args' ],
605
618
  schema: {
606
619
  '=': {
607
- type: ignore,
620
+ type: renameTo( '$tokenTexts', string ),
608
621
  xorGroups: null, // reset xorGroup; allow '=' for all :expr
609
622
  },
610
623
  },
@@ -625,7 +638,7 @@ const schema = compileSchema( {
625
638
  },
626
639
  notNull: {
627
640
  type: boolOrNull,
628
- inKind: [ 'element', 'param' ], // TODO: $column - or if so: in 'cast'?
641
+ inKind: [ 'element', 'param', 'type' ], // TODO: $column - or if so: in 'cast'?
629
642
  },
630
643
  virtual: {
631
644
  type: boolOrNull,
@@ -1133,6 +1146,14 @@ function validKind( val, spec, xsn ) {
1133
1146
  return ignore( val );
1134
1147
  }
1135
1148
 
1149
+ function typeArtifactRef( ref, spec ) {
1150
+ if (ref && typeof ref === 'object' && !Array.isArray( ref )) {
1151
+ if (ref.ref?.length === 1)
1152
+ return artifactRef( ref, { ...spec, ignoreExtra: true } );
1153
+ }
1154
+ return artifactRef( ref, spec );
1155
+ }
1156
+
1136
1157
  // Use with spec.msgVariant: 'or-object'
1137
1158
  function artifactRef( ref, spec ) {
1138
1159
  if (!ref || typeof ref !== 'string') {
@@ -1253,6 +1274,32 @@ function symbol( id, spec, xsn ) { // for CSN property '#'
1253
1274
  xsn.sym = { id, location: location() };
1254
1275
  }
1255
1276
 
1277
+ /**
1278
+ * Wrapper around the default `ref` spec: Don't allow references of length 1 for types.
1279
+ */
1280
+ function typeRef( val, spec, xsn, csn ) {
1281
+ // e.g. { ref: [ 'T' ] }
1282
+ if (Array.isArray(val) && val.length <= 1)
1283
+ warning( 'syntax-deprecated-type-ref', location(true), { '#': 'std', prop: 'type' });
1284
+
1285
+ return arrayOf(spec.arrayOf)(val, spec, xsn, csn);
1286
+ }
1287
+
1288
+ /**
1289
+ * Similar to refItem(), but warns that the item should be a string if `id` is the only CSN
1290
+ * property inside the ref-item.
1291
+ */
1292
+ function typeRefItem( val, spec, xsn, csn ) {
1293
+ // e.g. [ 'T', { id: 'elem', other_prop: true } ]
1294
+ // avoid duplicate messages for single-item reference, see typeRef()
1295
+ if (val && csn.ref?.length > 1 && typeof val === 'object' && val.id) {
1296
+ const ownKeysCount = Object.keys(val).filter(key => ourpropsRegex.test(key)).length;
1297
+ if (ownKeysCount === 1)
1298
+ warning('syntax-deprecated-type-ref', location(true), { '#': 'ref-item', prop: 'ref[]' });
1299
+ }
1300
+ return refItem(val, spec);
1301
+ }
1302
+
1256
1303
  /**
1257
1304
  * returns:
1258
1305
  * - false = no "...",
@@ -1311,7 +1358,7 @@ function annoValue( val, spec ) {
1311
1358
  if (valKeys.length > 1 && isBetaEnabled(userOptions, 'annotationExpressions')) {
1312
1359
  const s = schema['@'].schema['-expr'];
1313
1360
  const r = { location: location() };
1314
- r.value = object(val, s);
1361
+ Object.assign(r, object(val, s));
1315
1362
  return r;
1316
1363
  }
1317
1364
  else if (valKeys.length === 1) {
@@ -1643,8 +1690,11 @@ function getSpec( parentSpec, csn, prop, xor, expected, kind ) {
1643
1690
  const p0 = schema[prop] ? prop : prop.charAt(0);
1644
1691
  const s = (parentSpec.schema || schema)[p0];
1645
1692
  if (!s || s.noPrefix === (prop !== p0) ) {
1646
- if (prop && !ourpropsRegex.test( prop ))
1693
+ if (prop && !ourpropsRegex.test( prop )) {
1694
+ if (parentSpec.ignoreExtra)
1695
+ return { prop, type: ignore };
1647
1696
  return { prop, type: extra };
1697
+ }
1648
1698
  // TODO v4: No warning with --sloppy
1649
1699
  warning( 'syntax-unknown-property', location(true), { prop },
1650
1700
  'Unknown CSN property $(PROP)' );
@@ -400,109 +400,19 @@ function extensions( node, csn, model ) {
400
400
  if (model.kind && model.kind !== 'source')
401
401
  return undefined;
402
402
  const exts = node.map( definition );
403
-
404
- // builtins are non-enumerable for smaller display
405
- for (const name of Object.getOwnPropertyNames( model.definitions || {} ).sort()) {
406
- const art = model.definitions[name];
407
-
408
- // For namespaces and builtins: Extract annotations since they cannot be represented
409
- // in CSN. For all other artifacts, check whether they may be auto-exposed,
410
- // $inferred, etc. and extract their annotations.
411
- // In parseCdl mode extensions were already put into "extensions".
412
- if (!model.options.parseCdl && (art.kind === 'namespace' || art.builtin)) {
413
- extractAnnotationsToExtension( art );
414
- }
415
- else if (gensrcFlavor) {
403
+ if (gensrcFlavor) {
404
+ for (const name of Object.getOwnPropertyNames( model.definitions || {} ).sort()) {
405
+ const art = model.definitions[name];
416
406
  // From definitions (without redefinitions) with potential inferred elements:
417
407
  const result = { annotate: Object.create(null) };
418
- attachAnnotations(result, 'annotate', { [name]: art }, art.$inferred );
408
+ attachAnnotations( result, 'annotate', { [name]: art }, art.$inferred );
419
409
  if (result.annotate[name])
420
- exts.push({ annotate: name, ...result.annotate[name] } );
410
+ exts.push( { annotate: name, ...result.annotate[name] } );
421
411
  }
422
412
  }
423
-
424
- return exts.sort(
413
+ return exts.sort( // TODO: really sort with parse.cdl?
425
414
  (a, b) => (a.annotate || a.extend).localeCompare( b.annotate || b.extend )
426
415
  );
427
-
428
- /*
429
- function attachElementAnnos( annotate, art ) {
430
- while (art.items)
431
- art = art.items;
432
- if (art.elements) {
433
- const elems = inferred( art.elements, art.$inferred );
434
- if (Object.keys( elems ).length)
435
- annotate.elements = elems;
436
- }
437
- }
438
-
439
- function attachParamAnnos( annotate, art ) {
440
- const inferredParent = art.$inferred;
441
- if (art.params) {
442
- const ext = Object.create( dictionaryPrototype );
443
- for (const name in art.params) {
444
- const par = art.params[name];
445
- if (!inferredParent && !par.$inferred && par.$expand !== 'annotate')
446
- continue;
447
- const render = annotationsAndDocComment( par );
448
- const subElems = par.$expand !== 'origin' && (par.items || par).elements;
449
- if (subElems) {
450
- const sub = inferred( subElems, par.$inferred );
451
- if (Object.keys( sub ).length)
452
- render.elements = sub;
453
- }
454
- if (Object.keys(render).length)
455
- ext[name] = render;
456
- }
457
- if (obj.keys( ext ))
458
- annotate.params = ext;
459
- }
460
- if (art.returns) {
461
- const par = art.returns;
462
- if (!inferredParent && !par.$inferred && par.$expand !== 'annotate')
463
- return;
464
- const render = annotationsAndDocComment( par );
465
- const subElems = par.$expand !== 'origin' && (par.items || par).elements;
466
- if (subElems) {
467
- const sub = inferred( subElems, par.$inferred );
468
- if (Object.keys( sub ).length)
469
- render.elements = sub;
470
- }
471
- if (Object.keys(render).length)
472
- const sub = inferred( subElems, par.$inferred );
473
- if (Object.keys( sub ).length)
474
- render.elements = sub;
475
- }
476
- }
477
- return ext;
478
- */
479
-
480
- // extract namespace/builtin annotations
481
- function extractAnnotationsToExtension( art ) {
482
- const name = art.name.absolute;
483
- // 'true' because annotations on namespaces and builtins can only
484
- // happen through extensions.
485
- const annos = annotationsAndDocComment( art );
486
- const annotate = Object.assign( { annotate: name }, annos );
487
- if (Object.keys( annotate ).length > 1) {
488
- const loc = locationForAnnotationExtension();
489
- if (loc)
490
- location( loc, annotate, art );
491
- exts.push( annotate );
492
- }
493
-
494
- // Either the artifact's name's location or (for builtin types) the location
495
- // of its first annotation.
496
- function locationForAnnotationExtension() {
497
- if (art.location)
498
- return art.location;
499
- for (const key in art) {
500
- if (key.charAt(0) === '@' && art[key].name)
501
- return art[key].name.location;
502
- }
503
- return null;
504
- }
505
- }
506
416
  }
507
417
 
508
418
  /**
@@ -540,7 +450,10 @@ function sources( srcDict, csn ) {
540
450
 
541
451
  function attachAnnotations( annotate, prop, dict, inferred, insideReturns = false ) {
542
452
  const annoDict = Object.create( dictionaryPrototype );
543
- for (const name in dict) {
453
+ const names = Object.keys( dict );
454
+ if (strictMode)
455
+ names.sort();
456
+ for (const name of names) {
544
457
  const entry = dict[name];
545
458
  const inf = inferred || entry.$inferred; // is probably always inferred if parent was
546
459
  const sub = (inf) ? annotationsAndDocComment( entry ) : {};
@@ -549,7 +462,7 @@ function attachAnnotations( annotate, prop, dict, inferred, insideReturns = fals
549
462
  attachAnnotations( sub, 'actions', entry.actions, inf );
550
463
  else if (entry.params)
551
464
  attachAnnotations( sub, 'params', entry.params, inf );
552
- const obj = entry.returns || entry;
465
+ const obj = entry.returns || entry; // TODO: create returns !
553
466
  const many = obj.items || obj;
554
467
  const elems = (many.targetAspect || many).elements;
555
468
  if (elems)
@@ -649,9 +562,11 @@ function elements( dict, csn, node ) {
649
562
  // no 'elements' with SELECT or inferred elements with gensrc;
650
563
  // hidden or visible 'elements' will be set in query()
651
564
  return undefined;
652
- if (dict !== 0)
653
- return insertOrderDict( dict );
654
- 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 );
655
570
  }
656
571
 
657
572
  function enumDict( dict, csn, node ) {
@@ -766,7 +681,7 @@ function location( loc, csn, xsn ) {
766
681
  * @param {object} csn
767
682
  */
768
683
  function addLocation( loc, csn ) {
769
- if (loc) {
684
+ if (loc?.file) {
770
685
  // Remove endLine/endCol:
771
686
  // Reasoning: $location is mostly attached to definitions/members but the name
772
687
  // is often not the reason for an error or warning. So we gain little benefit for
@@ -791,8 +706,10 @@ function sortedDict( dict ) {
791
706
  return dictionary( dict, keys );
792
707
  }
793
708
 
794
- function actions( dict ) {
709
+ function actions( dict, _csn, node ) {
795
710
  const keys = Object.keys( dict );
711
+ if (strictMode && node.kind === 'annotate')
712
+ keys.sort(); // TODO: always sort with --test-mode ?
796
713
  return (keys.length)
797
714
  ? dictionary( dict, keys, 'actions' )
798
715
  : undefined;
@@ -900,6 +817,25 @@ function includesOrigin( includes, art ) {
900
817
  return (Object.keys( result ).length === 1) ? $origin : result;
901
818
  }
902
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
+
903
839
  function addOrigin( csn, xsn, node ) {
904
840
  if (!universalCsn)
905
841
  return;
@@ -945,7 +881,7 @@ function addOrigin( csn, xsn, node ) {
945
881
  }
946
882
  // from here on: member:
947
883
  // TODO: write a xsnNode._csnOrigin, which is useful to decide whether to write
948
- // $origins for its members
884
+ // $origins for its members
949
885
  const parent = getParent( xsn );
950
886
  const parentOrigin = getOrigin( parent );
951
887
  // console.log( 'X:',xsn, origin, parent, parentOrigin, getParent( origin ) );
@@ -960,8 +896,16 @@ function addOrigin( csn, xsn, node ) {
960
896
  parentOrigin === getParent( origin )) {
961
897
  // implicit prototype or shortened reference
962
898
  const { id } = origin.name || {};
963
- if (id && xsn.name && id !== xsn.name.id)
899
+
900
+ if (id && xsn.name && id !== xsn.name.id) {
964
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
+
965
909
  return;
966
910
  }
967
911
  if (origin.kind === 'mixin') {
@@ -1076,7 +1020,7 @@ function hasExplicitProp( ref, alsoLikeExplicit ) {
1076
1020
 
1077
1021
  /**
1078
1022
  * @param art
1079
- * @param user
1023
+ * @param [user]
1080
1024
  * @return {boolean|string[]}
1081
1025
  */
1082
1026
  function originRef( art, user ) {
@@ -1255,9 +1199,8 @@ function args( node ) {
1255
1199
  }
1256
1200
 
1257
1201
  function anno( node ) {
1258
- if (node.value) // expressions in annotation values
1259
- // TODO: actual string representation and not placeholder "42"
1260
- return Object.assign({ '=': '42' }, expression( node.value ));
1202
+ if (node.$tokenTexts) // expressions in annotation values
1203
+ return Object.assign({ '=': node.$tokenTexts }, expression( node ));
1261
1204
  return value(node);
1262
1205
  }
1263
1206
 
@@ -1274,6 +1217,8 @@ function value( node ) {
1274
1217
  }
1275
1218
  if (node.$inferred && gensrcFlavor)
1276
1219
  return undefined;
1220
+ if (node.$tokenTexts)
1221
+ return Object.assign({ '=': node.$tokenTexts }, expression( node ));
1277
1222
  if (node.path) {
1278
1223
  const ref = pathName( node.path );
1279
1224
  return extra( { '=': node.variant ? `${ ref }#${ pathName(node.variant.path) }` : ref }, node );
@@ -1374,6 +1319,8 @@ function exprInternal( node, xprParens ) {
1374
1319
  case 'ixpr':
1375
1320
  case 'xpr':
1376
1321
  break;
1322
+ case '?:':
1323
+ return ternaryOperator( node );
1377
1324
  case 'cast':
1378
1325
  return cast( expression( node.args[0] ), node );
1379
1326
  case 'list':
@@ -1410,6 +1357,20 @@ function flattenInternalXpr( array, op ) {
1410
1357
  return left;
1411
1358
  }
1412
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
+
1413
1374
  function query( node, csn, xsn, _prop, expectedParens = 0 ) {
1414
1375
  if (node.op.val === 'SELECT') {
1415
1376
  if (xsn && xsn.query === node && xsn.$syntax === 'projection' &&
@@ -1579,7 +1540,7 @@ function setHidden( obj, prop, val ) {
1579
1540
  }
1580
1541
 
1581
1542
  function addExplicitAs( node, name, implicit ) {
1582
- if (name && name.id &&
1543
+ if (name?.id && name.$inferred !== '$internal' &&
1583
1544
  (!name.$inferred || !node.ref && !node.func || implicit && implicit(name.id) ))
1584
1545
  node.as = name.id;
1585
1546
  return node;
@@ -407,6 +407,7 @@ function intervalSetToArray( recognizer, expected, excludesForNextToken ) {
407
407
  return names;
408
408
  }
409
409
 
410
+ // Used for sorting in messages
410
411
  const token1sort = {
411
412
  // 0: Identifier, Number, ...
412
413
  // 1: separators:
@@ -20,6 +20,7 @@ const {
20
20
  const { pathName } = require('../compiler/utils');
21
21
  const { isBetaEnabled } = require('../base/model');
22
22
  const { weakLocation } = require('../base/messages');
23
+ const { normalizeNewLine } = require('./textUtils');
23
24
 
24
25
  const $location = Symbol.for('cds.$location');
25
26
 
@@ -78,6 +79,7 @@ Object.assign(GenericAntlrParser.prototype, {
78
79
  attachLocation,
79
80
  assignAnnotation,
80
81
  addAnnotation,
82
+ expressionAsAnnotationValue,
81
83
  checkExtensionDict,
82
84
  handleDuplicateExtension,
83
85
  startLocation,
@@ -88,6 +90,7 @@ Object.assign(GenericAntlrParser.prototype, {
88
90
  previousTokenAtLocation,
89
91
  combinedLocation,
90
92
  surroundByParens,
93
+ tokensToStringRepresentation,
91
94
  secureParens,
92
95
  unaryOpForParens,
93
96
  leftAssocBinaryOp,
@@ -526,18 +529,24 @@ function fixMultiLineTokenEndLocation( token, location ) {
526
529
  }
527
530
 
528
531
  /**
529
- * Return `val` with the location of `token`. If `endToken` is provided, use its end
530
- * location as end location in the result.
532
+ * Return `val` with a location; if `val` and `endToken` are not provided, use the
533
+ * lower-cased token string of `startToken` as `val`. As location, use the
534
+ * location covered by `startToken` and `endToken`, or only `startToken` if no
535
+ * `endToken` is provided. The `startToken` defaults to the previous token.
531
536
  *
532
537
  * @param {object} startToken
533
538
  * @param {object} endToken
534
539
  * @param {any} val
535
540
  */
536
- function valueWithTokenLocation( val, startToken, endToken = null ) {
537
- if (!startToken)
538
- return undefined;
541
+ function valueWithTokenLocation( val = undefined, startToken = this._input.LT(-1),
542
+ endToken = undefined ) {
543
+ // if (!startToken)
544
+ // startToken = this._input.LT(-1);
539
545
  const loc = this.tokenLocation( startToken, endToken );
540
- return { location: loc, val };
546
+ return {
547
+ location: loc,
548
+ val: (endToken || val !== undefined) ? val : startToken.text.toLowerCase(),
549
+ };
541
550
  }
542
551
 
543
552
  function previousTokenAtLocation( location ) {
@@ -585,6 +594,23 @@ function surroundByParens( expr, open, close, asQuery = false ) {
585
594
  return (asQuery) ? { query: expr, location } : expr;
586
595
  }
587
596
 
597
+
598
+ function tokensToStringRepresentation( matchedRule ) {
599
+ const tokens = this._input.getTokens(
600
+ matchedRule.start.tokenIndex,
601
+ matchedRule.stop.tokenIndex + 1, null
602
+ ).filter(tok => tok.channel === antlr4.Token.DEFAULT_CHANNEL);
603
+ if (tokens.length === 0)
604
+ return '';
605
+
606
+ let result = tokens[0].text;
607
+ for (let i = 1; i < tokens.length; ++i) {
608
+ const str = normalizeNewLine(tokens[i].text);
609
+ result += (tokens[i].start > tokens[i - 1].stop + 1) ? ` ${ str }` : str;
610
+ }
611
+ return result;
612
+ }
613
+
588
614
  function unaryOpForParens( query, val ) {
589
615
  const parens = query?.$parens;
590
616
  if (!parens)
@@ -697,8 +723,11 @@ function argsExpression( args, nary, location ) {
697
723
  location: undefined,
698
724
  } );
699
725
  }
726
+ // eslint-disable-next-line no-nested-ternary
727
+ const val = nary === '?:' ? nary
728
+ : (nary && nary !== '=' ? 'nary' : 'ixpr');
700
729
  const op = {
701
- val: (nary && nary !== '=' ? 'nary' : 'ixpr'), // there is no n-ary in rule conditionTerm
730
+ val, // there is no n-ary in rule conditionTerm
702
731
  location: this.startLocation(),
703
732
  };
704
733
  return this.attachLocation( { op, args, location: location && { ...location } } );
@@ -814,6 +843,16 @@ function fixNewKeywordPlacement( args ) {
814
843
  args.push(ixpr);
815
844
  }
816
845
 
846
+ function expressionAsAnnotationValue( assignment, cond ) {
847
+ if (!cond.cond) // parse error
848
+ return;
849
+ Object.assign(assignment, cond.cond);
850
+ assignment.$tokenTexts = this.tokensToStringRepresentation(cond);
851
+ if (!this.isBetaEnabled(this.options, 'annotationExpressions')) {
852
+ this.error( 'syntax-unsupported-expression', [ cond.cond.location ], {},
853
+ 'Expressions in annotation values are not supported' );
854
+ }
855
+ }
817
856
 
818
857
  // If a '-' is directly before an unsigned number, consider it part of the number;
819
858
  // otherwise (including for '+'), represent it as extra unary prefix operator.
@@ -1063,7 +1102,7 @@ function aspectWithoutElements( art ) {
1063
1102
  }
1064
1103
  }
1065
1104
 
1066
- // must be in action directly after having parsed '{' or '(`
1105
+ // must be in action directly after having parsed '{', '(`, or a keyword before
1067
1106
  function createDict() {
1068
1107
  const dict = Object.create(null);
1069
1108
  dict[$location] = this.startLocation( this._input.LT(-1) );