@sap/cds-compiler 2.7.0 → 2.10.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 (63) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/lib/api/main.js +8 -10
  3. package/lib/api/options.js +13 -9
  4. package/lib/api/validate.js +11 -8
  5. package/lib/base/keywords.js +32 -2
  6. package/lib/base/message-registry.js +16 -0
  7. package/lib/base/messages.js +2 -0
  8. package/lib/base/model.js +1 -0
  9. package/lib/checks/onConditions.js +5 -0
  10. package/lib/checks/types.js +26 -2
  11. package/lib/checks/unknownMagic.js +38 -0
  12. package/lib/checks/validator.js +7 -2
  13. package/lib/compiler/assert-consistency.js +11 -5
  14. package/lib/compiler/builtins.js +2 -0
  15. package/lib/compiler/checks.js +3 -1
  16. package/lib/compiler/definer.js +87 -29
  17. package/lib/compiler/resolver.js +75 -16
  18. package/lib/compiler/shared.js +29 -9
  19. package/lib/edm/annotations/genericTranslation.js +182 -186
  20. package/lib/edm/csn2edm.js +93 -98
  21. package/lib/edm/edm.js +16 -20
  22. package/lib/edm/edmPreprocessor.js +274 -83
  23. package/lib/edm/edmUtils.js +29 -10
  24. package/lib/gen/language.checksum +1 -1
  25. package/lib/gen/language.interp +12 -1
  26. package/lib/gen/language.tokens +57 -53
  27. package/lib/gen/languageLexer.interp +10 -1
  28. package/lib/gen/languageLexer.js +770 -744
  29. package/lib/gen/languageLexer.tokens +49 -46
  30. package/lib/gen/languageParser.js +4727 -4323
  31. package/lib/json/from-csn.js +52 -23
  32. package/lib/json/to-csn.js +185 -71
  33. package/lib/language/errorStrategy.js +1 -0
  34. package/lib/language/genericAntlrParser.js +9 -0
  35. package/lib/language/language.g4 +90 -31
  36. package/lib/main.js +4 -0
  37. package/lib/model/api.js +78 -0
  38. package/lib/model/csnRefs.js +7 -1
  39. package/lib/model/csnUtils.js +5 -4
  40. package/lib/optionProcessor.js +7 -1
  41. package/lib/render/.eslintrc.json +3 -1
  42. package/lib/render/toCdl.js +45 -9
  43. package/lib/render/toHdbcds.js +100 -34
  44. package/lib/render/toSql.js +12 -4
  45. package/lib/render/utils/common.js +5 -9
  46. package/lib/sql-identifier.js +6 -1
  47. package/lib/transform/db/draft.js +6 -4
  48. package/lib/transform/db/expansion.js +14 -4
  49. package/lib/transform/db/flattening.js +13 -5
  50. package/lib/transform/db/transformExists.js +252 -58
  51. package/lib/transform/forHanaNew.js +7 -1
  52. package/lib/transform/forOdataNew.js +12 -8
  53. package/lib/transform/odata/attachPath.js +19 -4
  54. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  55. package/lib/transform/odata/referenceFlattener.js +44 -38
  56. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  57. package/lib/transform/odata/structuralPath.js +76 -0
  58. package/lib/transform/odata/structureFlattener.js +13 -10
  59. package/lib/transform/odata/typesExposure.js +22 -12
  60. package/lib/transform/transformUtilsNew.js +33 -1
  61. package/lib/transform/translateAssocsToJoins.js +6 -4
  62. package/lib/transform/universalCsnEnricher.js +67 -0
  63. package/package.json +1 -1
@@ -145,7 +145,7 @@ const schemaClasses = {
145
145
  type: natnumOrStar,
146
146
  msgId: 'syntax-csn-expected-cardinality',
147
147
  },
148
- column: {
148
+ columns: {
149
149
  arrayOf: selectItem,
150
150
  msgId: 'syntax-csn-expected-column',
151
151
  defaultKind: '$column',
@@ -238,15 +238,15 @@ const schema = compileSchema( {
238
238
  validKinds: [],
239
239
  },
240
240
  columns: {
241
- class: 'column',
241
+ class: 'columns',
242
242
  inKind: [ 'extend' ], // only valid in extend and SELECT/projection
243
243
  },
244
244
  expand: {
245
- class: 'column',
245
+ class: 'columns',
246
246
  inKind: [ '$column' ], // only valid in $column
247
247
  },
248
248
  inline: {
249
- class: 'column',
249
+ class: 'columns',
250
250
  inKind: [ '$column' ], // only valid in $column
251
251
  },
252
252
  keys: {
@@ -272,7 +272,7 @@ const schema = compileSchema( {
272
272
  },
273
273
  annotate: {
274
274
  type: kindAndName,
275
- inKind: (kind => kind === 'annotate'), // using array would test 'entity' for extensions[]
275
+ inKind: [ 'annotate' ],
276
276
  },
277
277
  extend: {
278
278
  type: kindAndName,
@@ -326,7 +326,7 @@ const schema = compileSchema( {
326
326
  inKind: [ 'element', 'type', 'param', 'annotation' ],
327
327
  },
328
328
  scale: {
329
- type: natnum,
329
+ type: scalenum,
330
330
  inKind: [ 'element', 'type', 'param', 'annotation' ],
331
331
  },
332
332
  srid: {
@@ -609,10 +609,10 @@ const schema = compileSchema( {
609
609
  inKind: [ 'entity', 'type', 'aspect', 'event', 'extend' ],
610
610
  },
611
611
  returns: {
612
- type: definition,
612
+ type: returnsDefinition,
613
613
  defaultKind: 'param',
614
614
  validKinds: [ 'param' ],
615
- inKind: [ 'action', 'function' ],
615
+ inKind: [ 'action', 'function', 'annotate' ],
616
616
  },
617
617
  technicalConfig: { // treat it like external_property
618
618
  type: extra,
@@ -739,14 +739,15 @@ function compileSchema( specs, proto = null) {
739
739
  throw new Error( `Missing type specification for property "${ p }"` );
740
740
  }
741
741
  }
742
- if (proto)
743
- return r;
742
+ // Set property 'xorGroup' in main and sub schema:
744
743
  for (const group in xorGroups) {
745
744
  for (const prop of xorGroups[group]) {
746
745
  if (r[prop].xorGroup === undefined)
747
746
  r[prop].xorGroup = group;
748
747
  }
749
748
  }
749
+ if (proto)
750
+ return r;
750
751
  for (const prop of exprProperties) {
751
752
  if (r[prop].inValue === undefined)
752
753
  r[prop].inValue = true;
@@ -944,7 +945,10 @@ function definition( def, spec, xsn, csn, name ) {
944
945
  return s.inKind && s.inKind( kind, spec );
945
946
  return s.inKind.includes( kind ) &&
946
947
  // for an 'annotate', both 'annotate' and the "host" kind must be expected
947
- (!inExtensions || s.inKind.includes( inExtensions ));
948
+ (!inExtensions || s.inKind.includes( inExtensions ) ||
949
+ // extending elements in returns can be without 'returns' in CSN
950
+ // TODO: with warning/info?
951
+ inExtensions === 'action' && p === 'elements');
948
952
  }
949
953
  }
950
954
 
@@ -992,12 +996,23 @@ function keys( array, spec, xsn ) {
992
996
  }
993
997
 
994
998
  function selectItem( def, spec, xsn, csn ) {
995
- if (def === '*')
999
+ if (def === '*') // compile() will complain about repeated '*'s
996
1000
  return { val: '*', location: location() };
997
1001
 
998
1002
  return definition( def, spec, xsn, csn, null ); // definer sets name
999
1003
  }
1000
1004
 
1005
+ function returnsDefinition( def, spec, xsn, csn, name ) {
1006
+ // TODO: be stricter in what is allowed inside returns
1007
+ if (!inExtensions)
1008
+ return definition( def, spec, xsn, csn, name );
1009
+ // for the moment, flatten elements in returns in an annotate
1010
+ // TODO: bigger Core Compiler changes would have to be done otherwise
1011
+ xsn.elements = definition( def, spec, xsn, csn, name ).elements;
1012
+ xsn.$syntax = 'returns';
1013
+ return undefined;
1014
+ }
1015
+
1001
1016
  // For v1 CSNs with annotation definitions
1002
1017
  function attachVocabInDefinitions( csn ) {
1003
1018
  if (!csn.vocabularies) {
@@ -1124,6 +1139,12 @@ function stringValOrNull( val, spec ) {
1124
1139
  return stringVal(val, spec);
1125
1140
  }
1126
1141
 
1142
+ function scalenum( val, spec ) {
1143
+ if ([ 'floating', 'variable' ].includes(val))
1144
+ return { val, literal: 'string', location: location() };
1145
+ return natnum(val, spec );
1146
+ }
1147
+
1127
1148
  function natnum( val, spec ) {
1128
1149
  if (typeof val === 'number' && val >= 0)
1129
1150
  // XSN TODO: do not require literal
@@ -1248,9 +1269,14 @@ function func( val, spec, xsn ) {
1248
1269
  return { path: [ { id: val, location: location() } ], location: location() };
1249
1270
  }
1250
1271
 
1251
- function xpr( exprs, spec, xsn ) {
1252
- xsn.op = { val: 'xpr', location: location() };
1253
- xsn.args = exists( exprs, spec, xsn );
1272
+ function xpr( exprs, spec, xsn, csn ) {
1273
+ if (csn.func) {
1274
+ xsn.suffix = exprArgs( exprs, spec );
1275
+ }
1276
+ else {
1277
+ xsn.op = { val: 'xpr', location: location() };
1278
+ xsn.args = exprArgs( exprs, spec, xsn );
1279
+ }
1254
1280
  }
1255
1281
 
1256
1282
  function list( exprs, spec, xsn ) {
@@ -1258,15 +1284,15 @@ function list( exprs, spec, xsn ) {
1258
1284
  xsn.args = arrayOf( exprOrString )( exprs, spec, xsn );
1259
1285
  }
1260
1286
 
1261
- function xprInValue( exprs, spec, xsn ) {
1287
+ function xprInValue( exprs, spec, xsn, csn ) {
1262
1288
  // if the top-level xpr is just for a cast:
1263
1289
  if (exprs.length === 1 && exprs[0].cast) {
1264
1290
  const x = {};
1265
- xpr( exprs, spec, x );
1291
+ xpr( exprs, spec, x, csn );
1266
1292
  Object.assign( xsn, x.args[0] );
1267
1293
  }
1268
1294
  else {
1269
- xpr( exprs, spec, xsn );
1295
+ xpr( exprs, spec, xsn, csn );
1270
1296
  }
1271
1297
  }
1272
1298
 
@@ -1315,9 +1341,9 @@ function exprOrString( e, spec ) {
1315
1341
  }
1316
1342
 
1317
1343
  // mark path argument of 'exits' predicate with $expected:'exists'
1318
- function exists( cond, spec, xsn, csn ) {
1319
- const rxsn = arrayOf( exprOrString )(cond, spec, xsn, csn);
1320
- if (Array.isArray(rxsn) && rxsn.some(x => x === 'exists')) {
1344
+ function exprArgs( cond, spec, xsn, csn ) {
1345
+ const rxsn = arrayOf( exprOrString )( cond, spec, xsn, csn );
1346
+ if (Array.isArray( rxsn ) && rxsn.some( x => x === 'exists' )) {
1321
1347
  for (let i = 0; i < rxsn.length - 1; i++) {
1322
1348
  if (rxsn[i] === 'exists' && rxsn[i + 1].path)
1323
1349
  rxsn[++i].$expected = 'exists';
@@ -1330,7 +1356,7 @@ function condition( cond, spec ) {
1330
1356
  const loc = location();
1331
1357
  const x = {
1332
1358
  op: { val: 'xpr', location: loc },
1333
- args: exists( cond, spec ),
1359
+ args: exprArgs( cond, spec ),
1334
1360
  location: loc,
1335
1361
  };
1336
1362
  return x;
@@ -1500,7 +1526,7 @@ function calculateKind( def, spec ) {
1500
1526
  return 'annotate';
1501
1527
  }
1502
1528
  if (spec.prop === 'extensions') {
1503
- inExtensions = (def.extend) ? '' : 'entity';
1529
+ inExtensions = (def.extend) ? '' : 'annotate';
1504
1530
  return (def.extend) ? 'extend' : 'annotate';
1505
1531
  }
1506
1532
  const kind = (def.kind === 'view') ? 'entity' : def.kind; // 'view' is CSN v0.1.0
@@ -1556,6 +1582,9 @@ function checkAndSetXorGroup( group, prop, xor ) {
1556
1582
  xor[group] = prop;
1557
1583
  return true;
1558
1584
  }
1585
+ if (prop === 'func' && xor[group] === 'xpr' ||
1586
+ prop === 'xpr' && xor[group] === 'func')
1587
+ return true; // hack for window function: both func and xpr is allowed
1559
1588
  error( 'syntax-csn-excluded-property', location(true),
1560
1589
  { prop, otherprop: xor[group] },
1561
1590
  'CSN property $(PROP) can only be used alternatively to $(OTHERPROP)');
@@ -80,6 +80,7 @@ const transformers = {
80
80
  where: condition, // also pathItem after 'cardinality' before 'args'
81
81
  having: condition,
82
82
  args, // also pathItem after 'where', before 'on'/'orderBy'
83
+ suffix: node => [].concat( ...node.suffix.map( xprArg ) ),
83
84
  orderBy: arrayOf( orderBy ), // TODO XSN: make `sort` and `nulls` sibling properties
84
85
  sort: value,
85
86
  nulls: value,
@@ -95,7 +96,7 @@ const transformers = {
95
96
  value: enumValue, // do not list for select items as elements
96
97
  query,
97
98
  elements,
98
- actions: nonEmptyDict, // TODO: just normal dictionary
99
+ actions, // TODO: just normal dictionary
99
100
  // special: top-level, cardinality -----------------------------------------
100
101
  sources,
101
102
  definitions: sortedDict,
@@ -191,6 +192,13 @@ const operators = {
191
192
  notLike: ternary( [ 'not', 'like' ], [ 'escape' ] ),
192
193
  when: exprs => [ 'when', ...exprs[0], 'then', ...exprs[1] ],
193
194
  case: exprs => [ 'case' ].concat( ...exprs, [ 'end' ] ),
195
+ over: exprs => [ 'over', { xpr: [].concat( ...exprs ) } ],
196
+ orderBy: exprs => [
197
+ 'order', 'by', ...exprs[0].concat( ...exprs.slice(1).map( e => [ ',', ...e ] ) ),
198
+ ],
199
+ partitionBy: exprs => [
200
+ 'partition', 'by', ...exprs[0].concat( ...exprs.slice(1).map( e => [ ',', ...e ] ) ),
201
+ ],
194
202
  // xpr: (exprs) => [].concat( ...exprs ), see below - handled extra
195
203
  };
196
204
 
@@ -232,22 +240,36 @@ function sortCsn( csn, cloneOptions = false ) {
232
240
  r[n] = sortCsn(val, cloneOptions);
233
241
  }
234
242
  if (cloneOptions && typeof csn === 'object') {
235
- if (csn.$sources && !r.$sources)
236
- setHidden(r, '$sources', csn.$sources);
237
- if (csn.$location && !r.$location)
238
- setHidden(r, '$location', csn.$location);
239
- if (csn.$path) // used in generic reference flattener
240
- setHidden(r, '$path', csn.$path);
241
- if (csn.$paths) // used in generic reference flattener
242
- setHidden(r, '$paths', csn.$paths);
243
- if (csn.elements && !r.elements) // non-enumerable 'elements'
244
- setHidden(r, 'elements', csnDictionary( csn.elements, false, cloneOptions ) );
245
- if (csn.$tableConstraints && !r.$tableConstraints)
246
- setHidden(r, '$tableConstraints', csn.$tableConstraints);
243
+ if ({}.hasOwnProperty.call( csn, '$sources' ) && !r.$sources)
244
+ setHidden( r, '$sources', csn.$sources );
245
+ if ({}.hasOwnProperty.call( csn, '$location' ) && !r.$location)
246
+ setHidden( r, '$location', csn.$location );
247
+ if ({}.hasOwnProperty.call( csn, '$path' )) // used in generic reference flattener
248
+ setHidden( r, '$path', csn.$path );
249
+ if ({}.hasOwnProperty.call( csn, '$paths' )) // used in generic reference flattener
250
+ setHidden( r, '$paths', csn.$paths );
251
+ if (hasNonEnumerable( csn, 'elements' ) && !r.elements) // non-enumerable 'elements'
252
+ setHidden( r, 'elements', csnDictionary( csn.elements, false, cloneOptions ) );
253
+ if (hasNonEnumerable( csn, '$tableConstraints' ) && !r.$tableConstraints)
254
+ setHidden( r, '$tableConstraints', csn.$tableConstraints );
247
255
  }
248
256
  return r;
249
257
  }
250
258
 
259
+ /**
260
+ * Check wether the given object has non enumerable property.
261
+ * Ensure that we don't take it from the prototype, only "directly" - we accidentally
262
+ * cloned elements with a cds.linked input otherwise.
263
+ *
264
+ * @param {object} object
265
+ * @param {string} property
266
+ * @returns
267
+ */
268
+ function hasNonEnumerable(object, property) {
269
+ return {}.hasOwnProperty.call( object, property ) &&
270
+ !{}.propertyIsEnumerable.call( object, property );
271
+ }
272
+
251
273
  /**
252
274
  * @param {object} csn
253
275
  * @param {boolean} sort
@@ -359,10 +381,12 @@ function usings( srcDict ) {
359
381
  * @param {object} csn
360
382
  * @param {object} model
361
383
  */
384
+
385
+
362
386
  function extensions( node, csn, model ) {
363
387
  if (model.kind && model.kind !== 'source')
364
388
  return undefined;
365
- const exts = node.map( standard );
389
+ const exts = node.map( definition );
366
390
 
367
391
  // builtins are non-enumerable for smaller display
368
392
  for (const name of Object.getOwnPropertyNames( model.definitions || {} ).sort()) {
@@ -377,17 +401,21 @@ function extensions( node, csn, model ) {
377
401
  }
378
402
  else if (gensrcFlavor) {
379
403
  // From definitions (without redefinitions) with potential inferred elements:
380
- if (!Array.isArray(art) && art.elements &&
381
- (art.query || art.includes || art.$inferred)) {
382
- const annos = art.$inferred && annotationsAndDocComment( art, true );
383
- const elems = inferred( art.elements, art.$inferred );
384
- /** @type {object} */
385
- const annotate = Object.assign( { annotate: name }, annos );
386
- if (Object.keys( elems ).length)
387
- annotate.elements = elems;
388
- if (Object.keys( annotate ).length > 1)
389
- exts.push( annotate );
404
+ const annotate = { annotate: name };
405
+ if (art.$inferred)
406
+ Object.assign( annotate, annotationsAndDocComment( art, true ) );
407
+ if (art.$expand === 'annotate') {
408
+ if (art.actions)
409
+ attachAnnotations( annotate, 'actions', art.actions, art.$inferred );
410
+ else if (art.params)
411
+ attachAnnotations( annotate, 'params', art.params, art.$inferred );
412
+ const obj = art.returns || art;
413
+ const elems = (obj.items || obj).elements; // no targetAspect here
414
+ if (elems)
415
+ attachAnnotations( annotate, 'elements', elems, art.$inferred, art.returns );
390
416
  }
417
+ if (Object.keys( annotate ).length > 1)
418
+ exts.push( annotate );
391
419
  }
392
420
  }
393
421
 
@@ -395,6 +423,58 @@ function extensions( node, csn, model ) {
395
423
  (a, b) => (a.annotate || a.extend).localeCompare( b.annotate || b.extend )
396
424
  );
397
425
 
426
+ /*
427
+ function attachElementAnnos( annotate, art ) {
428
+ while (art.items)
429
+ art = art.items;
430
+ if (art.elements) {
431
+ const elems = inferred( art.elements, art.$inferred );
432
+ if (Object.keys( elems ).length)
433
+ annotate.elements = elems;
434
+ }
435
+ }
436
+
437
+ function attachParamAnnos( annotate, art ) {
438
+ const inferredParent = art.$inferred;
439
+ if (art.params) {
440
+ const ext = Object.create( dictionaryPrototype );
441
+ for (const name in art.params) {
442
+ const par = art.params[name];
443
+ if (!inferredParent && !par.$inferred && par.$expand !== 'annotate')
444
+ continue;
445
+ const render = annotationsAndDocComment( par, true );
446
+ const subElems = par.$expand !== 'origin' && (par.items || par).elements;
447
+ if (subElems) {
448
+ const sub = inferred( subElems, par.$inferred );
449
+ if (Object.keys( sub ).length)
450
+ render.elements = sub;
451
+ }
452
+ if (Object.keys(render).length)
453
+ ext[name] = render;
454
+ }
455
+ if (obj.keys( ext ))
456
+ annotate.params = ext;
457
+ }
458
+ if (art.returns) {
459
+ const par = art.returns;
460
+ if (!inferredParent && !par.$inferred && par.$expand !== 'annotate')
461
+ return;
462
+ const render = annotationsAndDocComment( par, true );
463
+ const subElems = par.$expand !== 'origin' && (par.items || par).elements;
464
+ if (subElems) {
465
+ const sub = inferred( subElems, par.$inferred );
466
+ if (Object.keys( sub ).length)
467
+ render.elements = sub;
468
+ }
469
+ if (Object.keys(render).length)
470
+ const sub = inferred( subElems, par.$inferred );
471
+ if (Object.keys( sub ).length)
472
+ render.elements = sub;
473
+ }
474
+ }
475
+ return ext;
476
+ */
477
+
398
478
  // extract namespace/builtin annotations
399
479
  function extractAnnotationsToExtension( art ) {
400
480
  const name = art.name.absolute;
@@ -456,17 +536,29 @@ function sources( srcDict, csn ) {
456
536
  }
457
537
  }
458
538
 
459
- function inferred( elems, inferredParent ) {
460
- const ext = Object.create( dictionaryPrototype );
461
- for (const name in elems) {
462
- const elem = elems[name];
463
- if (Array.isArray(elem) || !inferredParent && !elem.$inferred)
464
- continue;
465
- const csn = annotationsAndDocComment( elem, true );
466
- if (Object.keys(csn).length)
467
- ext[name] = csn;
539
+ function attachAnnotations( annotate, prop, dict, inferred, returns = false ) {
540
+ const annoDict = Object.create( dictionaryPrototype );
541
+ for (const name in dict) {
542
+ const elem = dict[name];
543
+ const inf = inferred || elem.$inferred; // is probably always inferred if parent was
544
+ const sub = (inf) ? annotationsAndDocComment( elem, true ) : {};
545
+ if (elem.$expand === 'annotate') {
546
+ if (elem.params)
547
+ attachAnnotations( sub, 'params', elem.params, inf );
548
+ const obj = elem.returns || elem;
549
+ const elems = (obj.items || obj.targetAspect || obj).elements;
550
+ if (elems)
551
+ attachAnnotations( sub, 'elements', elems, inf, elem.returns );
552
+ }
553
+ if (Object.keys( sub ).length)
554
+ annoDict[name] = sub;
555
+ }
556
+ if (Object.keys( annoDict ).length) {
557
+ if (returns)
558
+ annotate.returns = { elements: annoDict };
559
+ else
560
+ annotate[prop] = annoDict;
468
561
  }
469
- return ext;
470
562
  }
471
563
 
472
564
  function standard( node ) {
@@ -505,10 +597,13 @@ function set( prop, csn, node ) {
505
597
  }
506
598
 
507
599
  function targetAspect( val, csn, node ) {
600
+ const ta = (val.elements)
601
+ ? addLocation( val.location, standard( val ) )
602
+ : artifactRef( val, true );
508
603
  if (!gensrcFlavor || node.target && !node.target.$inferred)
509
- return (val.elements) ? standard( val ) : artifactRef( val, true );
604
+ return ta;
510
605
  // For compatibility, put aspect in 'target' with parse.cdl and csn flavor 'gensrc'
511
- csn.target = (val.elements) ? standard( val ) : artifactRef( val, true );
606
+ csn.target = ta;
512
607
  return undefined;
513
608
  }
514
609
 
@@ -528,10 +623,8 @@ function target( val, _csn, node ) {
528
623
 
529
624
  function items( obj, csn, node ) {
530
625
  if (!keepElements( node ))
531
- // no 'elements' with SELECT or inferred elements with gensrc;
532
- // hidden 'elements' will be set in query()
533
626
  return undefined;
534
- return standard( obj );
627
+ return standard( obj ); // no 'elements' with inferred elements with gensrc
535
628
  }
536
629
 
537
630
  function elements( dict, csn, node ) {
@@ -553,21 +646,24 @@ function enumerableQueryElements( select ) {
553
646
  return alias.query && (alias.query._leadingQuery || alias.query) === select;
554
647
  }
555
648
 
556
- // We do not optimize away elements which are potentially adapted during their
557
- // way from the original structure type definition to the current usage
649
+ // Should we render the elements? (and items?)
558
650
  function keepElements( node ) {
559
651
  if (universalCsn)
560
- // if there are neither _origin, nor type, $expand won't be set and this
561
- // function returns true automatically
652
+ // $expand = null/undefined: not elements not via expansion
653
+ // $expand = 'target'/'annotate': with redirections / individual annotations
562
654
  return node.$expand !== 'origin';
563
655
  if (!node.type || node.kind === 'type')
564
656
  return true;
657
+ // even if expanded elements have no new target or direct annotation,
658
+ // they might have got one via propagation – any new target/annos during their
659
+ // way from the original structure type definition to the current usage
565
660
  while (node) {
566
661
  if (node.$expand !== 'origin')
567
662
  return true;
568
663
  node = node._origin;
569
664
  }
570
- return false;
665
+ // all in _origin chain only have expanded elements with 'origin':
666
+ return false; // no need to render elements
571
667
  }
572
668
 
573
669
  // for gensrcFlavor and namespace/builtin annotation extraction:
@@ -653,17 +749,17 @@ function sortedDict( dict ) {
653
749
  return dictionary( dict, keys );
654
750
  }
655
751
 
656
- function nonEmptyDict( dict ) {
752
+ function actions( dict ) {
657
753
  const keys = Object.keys( dict );
658
754
  return (keys.length)
659
- ? dictionary( dict, keys )
755
+ ? dictionary( dict, keys, 'actions' )
660
756
  : undefined;
661
757
  }
662
758
 
663
- function dictionary( dict, keys ) {
759
+ function dictionary( dict, keys, prop ) {
664
760
  const csn = Object.create( dictionaryPrototype );
665
761
  for (const name of keys) {
666
- const def = definition( dict[name] );
762
+ const def = definition( dict[name], null, null, prop );
667
763
  if (def !== undefined)
668
764
  csn[name] = def;
669
765
  }
@@ -686,7 +782,7 @@ function foreignKeys( dict, csn, node ) {
686
782
  csn.keys = keys;
687
783
  }
688
784
 
689
- function definition( art ) {
785
+ function definition( art, _csn, _node, prop ) {
690
786
  if (!art || typeof art !== 'object')
691
787
  return undefined; // TODO: complain with strict
692
788
  // Do not include namespace definitions or inferred construct (in gensrc):
@@ -698,7 +794,14 @@ function definition( art ) {
698
794
  addLocation( art.targetElement.location, key );
699
795
  return extra( key, art );
700
796
  }
701
- return standard( art );
797
+ const c = standard( art );
798
+ // The XSN of actions in extensions do not contain a returns yet - TODO?
799
+ const elems = c.elements;
800
+ if (elems && (prop === 'actions' || art.$syntax === 'returns')) {
801
+ delete c.elements;
802
+ c.returns = { elements: elems };
803
+ }
804
+ return c;
702
805
  }
703
806
 
704
807
  function addOrigin( csn, xsn ) {
@@ -865,10 +968,12 @@ function args( node ) {
865
968
  return dict;
866
969
  }
867
970
 
868
- // "Short" value form, e.g. for annotation assignments
869
971
  function value( node ) {
972
+ // "Short" value form, e.g. for annotation assignments
870
973
  if (!node)
871
974
  return true; // `@aBool` short for `@aBool: true`
975
+ if (universalCsn && node.$inferred === 'prop') // via propagator.js
976
+ return undefined;
872
977
  if (node.$inferred && gensrcFlavor)
873
978
  return undefined;
874
979
  if (node.path) {
@@ -911,7 +1016,7 @@ function onCondition( cond, csn, node ) {
911
1016
  function condition( node ) {
912
1017
  const expr = expression( node );
913
1018
  // we do not set a hidden $parens on array - we could still do it if requested
914
- return !expr.cast && expr.xpr || [ expr ];
1019
+ return !expr.cast && !expr.func && expr.xpr || [ expr ];
915
1020
  }
916
1021
 
917
1022
  function expression( node, dollarExtra ) {
@@ -951,10 +1056,12 @@ function expression( node, dollarExtra ) {
951
1056
  arg0.xpr.unshift( quantifier.val );
952
1057
  }
953
1058
  }
1059
+ if (node.suffix)
1060
+ call.xpr = [].concat( ...node.suffix.map( xprArg ) );
954
1061
  return extra( call, dollarExtraNode );
955
1062
  }
956
1063
  if (node.query)
957
- return query( node.query, null, null, 1 );
1064
+ return query( node.query, null, null, null, 1 );
958
1065
  if (!node.op) // parse error
959
1066
  return { xpr: [] };
960
1067
  else if (node.op.val === 'xpr')
@@ -964,7 +1071,7 @@ function expression( node, dollarExtra ) {
964
1071
  return cast( expression( node.args[0] ), dollarExtraNode );
965
1072
  // from here on: CDL input (no $extra possible - but $parens)
966
1073
  else if (node.op.val !== ',')
967
- return extra( { xpr: xpr( node ) }, dollarExtraNode, 1 );
1074
+ return extra( { xpr: xpr( node ) }, dollarExtraNode, (dollarExtra === 'sub-xpr' ? 1 : 0) );
968
1075
  return (parensAsStrings)
969
1076
  ? { xpr: [ '(', ...xpr( node ), ')' ] }
970
1077
  // the inner parens belong to the tuple construct, i.e. won't count as parens
@@ -974,15 +1081,7 @@ function expression( node, dollarExtra ) {
974
1081
  function xpr( node ) {
975
1082
  // if (!node.op) console.log(node)
976
1083
  const op = operators[node.op.val] || node.op.val.split(' ');
977
- const exprs = node.args.map( ( sub ) => {
978
- const expr = expression( sub );
979
- // return !sub.$parens && !expr.cast && expr.xpr || [ expr ]; if parensAsStrings is gone
980
- if (expr.cast || !expr.xpr || sub.$parens && !parensAsStrings)
981
- return [ expr ];
982
- else if (sub.$parens && sub.op.val !== ',')
983
- return [ '(', ...expr.xpr, ')' ];
984
- return expr.xpr;
985
- } );
1084
+ const exprs = node.args.map( xprArg );
986
1085
  if (op instanceof Function)
987
1086
  return op( exprs );
988
1087
  if (node.quantifier)
@@ -992,6 +1091,27 @@ function xpr( node ) {
992
1091
  return exprs[0].concat( ...exprs.slice(1).map( a => [ ...op, ...a ] ) );
993
1092
  }
994
1093
 
1094
+ function xprArg( sub ) {
1095
+ const realXpr = sub.op && sub.op.val === 'xpr';
1096
+ const expr = expression( sub, 'sub-xpr' );
1097
+ // `sort`/`nulls` will be attached to arguments of orderBy
1098
+ // which might be either `path`s or `xpr`s
1099
+ const sortAndNulls = [];
1100
+ if (sub.sort)
1101
+ sortAndNulls.push( sub.sort.val );
1102
+ if (sub.nulls)
1103
+ sortAndNulls.push( ...[ 'nulls', sub.nulls.val ] );
1104
+ // return !sub.$parens && !expr.cast && !expr.func && expr.xpr || [ expr ];
1105
+ // if parensAsStrings is gone
1106
+ if (realXpr || expr.cast || expr.func || !expr.xpr || sub.$parens && !parensAsStrings)
1107
+ return [ expr, ...sortAndNulls ];
1108
+ else if (sub.$parens && sub.op.val !== ',')
1109
+ return [ '(', ...expr.xpr, ')' ];
1110
+
1111
+ expr.xpr.push( ...sortAndNulls );
1112
+ return expr.xpr;
1113
+ }
1114
+
995
1115
  function ternary( op1, op2 ) {
996
1116
  return function ternaryOp( exprs ) {
997
1117
  return (exprs[2])
@@ -1015,7 +1135,7 @@ function binaryRightParen( op ) {
1015
1135
  };
1016
1136
  }
1017
1137
 
1018
- function query( node, csn, xsn, expectedParens = 0 ) {
1138
+ function query( node, csn, xsn, _prop, expectedParens = 0 ) {
1019
1139
  if (node.op.val === 'SELECT') {
1020
1140
  if (xsn && xsn.query === node && xsn.$syntax === 'projection' &&
1021
1141
  node.from && node.from.path && !projectionAsQuery) {
@@ -1087,7 +1207,7 @@ function from( node ) {
1087
1207
  return extra( join, node );
1088
1208
  }
1089
1209
  else if (node.query) {
1090
- return addExplicitAs( query( node.query, null, null, 1 ), node.name );
1210
+ return addExplicitAs( query( node.query, null, null, null, 1 ), node.name );
1091
1211
  }
1092
1212
  else if (!node._artifact || node._artifact._main) { // CQL or follow assoc
1093
1213
  return extra( addExplicitAs( artifactRef( node, false ), node.name ), node );
@@ -1132,12 +1252,6 @@ function addElementAsColumn( elem, cols ) {
1132
1252
  finally {
1133
1253
  gensrcFlavor = gensrcSaved;
1134
1254
  }
1135
- // FIXME: Currently toHana requires that an '_ignore' property on the elem is
1136
- // also visible on the column. Don't ignore virtual columns, let the
1137
- // renderer decide how to render that column.
1138
- if (!elem.virtual && elem._ignore)
1139
- col._ignore = true;
1140
-
1141
1255
  if (elem.value && !elem.$inferred) {
1142
1256
  const parens = elem.value.$parens;
1143
1257
  if (parens)
@@ -245,6 +245,7 @@ function reportIgnoredWith( recognizer, t ) {
245
245
  }
246
246
 
247
247
  function consumeUntil( recognizer, set ) {
248
+ // TODO: add trace
248
249
  if (SEMI == null)
249
250
  SEMI = recognizer.literalNames.indexOf( "';'" );
250
251
  if (RBRACE == null)
@@ -77,6 +77,7 @@ GenericAntlrParser.prototype = Object.assign(
77
77
  noAssignmentInSameLine,
78
78
  noSemicolonHere,
79
79
  setLocalToken,
80
+ setLocalTokenIfBefore,
80
81
  excludeExpected,
81
82
  isStraightBefore,
82
83
  meltKeywordToIdentifier,
@@ -183,6 +184,14 @@ function setLocalToken( string, tokenName, notBefore, inSameLine ) {
183
184
  ll1.type = this.constructor[tokenName];
184
185
  }
185
186
 
187
+ function setLocalTokenIfBefore( string, tokenName, before, inSameLine ) {
188
+ const ll1 = this.getCurrentToken();
189
+ if (ll1.text.toUpperCase() === string &&
190
+ (!inSameLine || this._input.LT(-1).line === ll1.line) &&
191
+ (!before || before && before.test( this._input.LT(2).text )))
192
+ ll1.type = this.constructor[tokenName];
193
+ }
194
+
186
195
  // // Special function for rule `requiredSemi` before return $ctx
187
196
  // function braceForSemi() {
188
197
  // if (RBRACE == null)