@sap/cds-compiler 2.5.2 → 2.11.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 (102) hide show
  1. package/CHANGELOG.md +235 -9
  2. package/bin/cdsc.js +44 -27
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +37 -3
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +37 -123
  7. package/lib/api/options.js +27 -15
  8. package/lib/api/validate.js +34 -9
  9. package/lib/backends.js +9 -89
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/keywords.js +32 -2
  12. package/lib/base/message-registry.js +73 -11
  13. package/lib/base/messages.js +86 -30
  14. package/lib/base/model.js +6 -6
  15. package/lib/base/optionProcessorHelper.js +56 -22
  16. package/lib/checks/defaultValues.js +27 -2
  17. package/lib/checks/elements.js +1 -6
  18. package/lib/checks/foreignKeys.js +0 -6
  19. package/lib/checks/managedWithoutKeys.js +17 -0
  20. package/lib/checks/nonexpandableStructured.js +38 -0
  21. package/lib/checks/onConditions.js +9 -45
  22. package/lib/checks/queryNoDbArtifacts.js +25 -7
  23. package/lib/checks/selectItems.js +29 -2
  24. package/lib/checks/types.js +26 -2
  25. package/lib/checks/unknownMagic.js +41 -0
  26. package/lib/checks/utils.js +61 -0
  27. package/lib/checks/validator.js +60 -7
  28. package/lib/compiler/assert-consistency.js +23 -7
  29. package/lib/compiler/base.js +65 -0
  30. package/lib/compiler/builtins.js +30 -1
  31. package/lib/compiler/checks.js +8 -5
  32. package/lib/compiler/definer.js +157 -133
  33. package/lib/compiler/index.js +89 -31
  34. package/lib/compiler/propagator.js +5 -2
  35. package/lib/compiler/resolver.js +375 -185
  36. package/lib/compiler/shared.js +49 -202
  37. package/lib/compiler/utils.js +173 -0
  38. package/lib/edm/annotations/genericTranslation.js +183 -187
  39. package/lib/edm/csn2edm.js +104 -108
  40. package/lib/edm/edm.js +18 -21
  41. package/lib/edm/edmPreprocessor.js +388 -146
  42. package/lib/edm/edmUtils.js +104 -34
  43. package/lib/gen/Dictionary.json +22 -0
  44. package/lib/gen/language.checksum +1 -1
  45. package/lib/gen/language.interp +28 -1
  46. package/lib/gen/language.tokens +79 -69
  47. package/lib/gen/languageLexer.interp +28 -1
  48. package/lib/gen/languageLexer.js +879 -805
  49. package/lib/gen/languageLexer.tokens +71 -62
  50. package/lib/gen/languageParser.js +5330 -4300
  51. package/lib/json/from-csn.js +110 -52
  52. package/lib/json/to-csn.js +434 -120
  53. package/lib/language/antlrParser.js +15 -3
  54. package/lib/language/errorStrategy.js +1 -0
  55. package/lib/language/genericAntlrParser.js +93 -26
  56. package/lib/language/language.g4 +172 -31
  57. package/lib/main.d.ts +216 -19
  58. package/lib/main.js +32 -7
  59. package/lib/model/api.js +78 -0
  60. package/lib/model/csnRefs.js +413 -149
  61. package/lib/model/csnUtils.js +286 -75
  62. package/lib/model/enrichCsn.js +50 -6
  63. package/lib/model/revealInternalProperties.js +22 -5
  64. package/lib/modelCompare/compare.js +39 -21
  65. package/lib/optionProcessor.js +35 -18
  66. package/lib/render/.eslintrc.json +4 -1
  67. package/lib/render/DuplicateChecker.js +9 -6
  68. package/lib/render/toCdl.js +121 -36
  69. package/lib/render/toHdbcds.js +148 -98
  70. package/lib/render/toSql.js +114 -43
  71. package/lib/render/utils/common.js +8 -13
  72. package/lib/render/utils/sql.js +3 -3
  73. package/lib/sql-identifier.js +6 -1
  74. package/lib/transform/db/assertUnique.js +5 -6
  75. package/lib/transform/db/constraints.js +281 -106
  76. package/lib/transform/db/draft.js +11 -8
  77. package/lib/transform/db/expansion.js +584 -0
  78. package/lib/transform/db/flattening.js +341 -0
  79. package/lib/transform/db/groupByOrderBy.js +2 -2
  80. package/lib/transform/db/transformExists.js +345 -65
  81. package/lib/transform/db/views.js +438 -0
  82. package/lib/transform/forHanaNew.js +131 -793
  83. package/lib/transform/forOdataNew.js +30 -24
  84. package/lib/transform/localized.js +39 -10
  85. package/lib/transform/odata/attachPath.js +19 -4
  86. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  87. package/lib/transform/odata/referenceFlattener.js +60 -39
  88. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  89. package/lib/transform/odata/structuralPath.js +72 -0
  90. package/lib/transform/odata/structureFlattener.js +19 -18
  91. package/lib/transform/odata/typesExposure.js +22 -12
  92. package/lib/transform/transformUtilsNew.js +144 -78
  93. package/lib/transform/translateAssocsToJoins.js +22 -27
  94. package/lib/transform/universalCsnEnricher.js +67 -0
  95. package/lib/utils/file.js +5 -14
  96. package/lib/utils/moduleResolve.js +6 -8
  97. package/lib/utils/term.js +65 -42
  98. package/lib/utils/timetrace.js +48 -26
  99. package/package.json +1 -1
  100. package/lib/json/walker.js +0 -26
  101. package/lib/transform/sqlite +0 -0
  102. package/lib/utils/string.js +0 -17
@@ -12,20 +12,31 @@
12
12
  'use strict';
13
13
 
14
14
  const { locationString } = require('../base/messages');
15
- const { isDeprecatedEnabled } = require('../base/model');
15
+ const { isBetaEnabled, isDeprecatedEnabled } = require('../base/model');
16
16
 
17
17
  const compilerVersion = require('../../package.json').version;
18
18
  const creator = `CDS Compiler v${ compilerVersion }`;
19
19
  const csnVersion = '2.0';
20
20
 
21
+ const normalizedKind = {
22
+ param: 'param',
23
+ action: 'action',
24
+ function: 'action',
25
+ };
26
+
21
27
  /** @type {boolean|string} */
22
28
  let gensrcFlavor = true; // good enough here...
29
+ let universalCsn = false;
23
30
  let strictMode = false; // whether to dump with unknown properties (in standard)
24
31
  let parensAsStrings = false;
25
32
  let projectionAsQuery = false;
26
33
  let withLocations = false;
27
34
  let dictionaryPrototype = null;
28
35
 
36
+ // Properties for dictionaries, set in compileX() and TODO: parseX(), must be
37
+ // stored with symbols as keys, as we do not want to disallow any key name:
38
+ const $inferred = Symbol.for('cds.$inferred');
39
+
29
40
  // IMPORTANT: the order of these properties determine the order of properties
30
41
  // in the resulting CSN !!! Also check const `csnPropertyNames`.
31
42
  const transformers = {
@@ -50,7 +61,7 @@ const transformers = {
50
61
  all: ignore, // XSN TODO use quantifier
51
62
  // type properties (without 'elements') ------------------------------------
52
63
  localized: value,
53
- type: t => artifactRef( t, !t.$extra ),
64
+ type,
54
65
  length: value,
55
66
  precision: value,
56
67
  scale: value,
@@ -59,7 +70,7 @@ const transformers = {
59
70
  targetAspect,
60
71
  target,
61
72
  foreignKeys,
62
- enum: insertOrderDict,
73
+ enum: enumDict,
63
74
  items,
64
75
  includes: arrayOf( artifactRef ), // also entities
65
76
  // late expressions / query properties -------------------------------------
@@ -72,6 +83,7 @@ const transformers = {
72
83
  where: condition, // also pathItem after 'cardinality' before 'args'
73
84
  having: condition,
74
85
  args, // also pathItem after 'where', before 'on'/'orderBy'
86
+ suffix: node => [].concat( ...node.suffix.map( xprArg ) ),
75
87
  orderBy: arrayOf( orderBy ), // TODO XSN: make `sort` and `nulls` sibling properties
76
88
  sort: value,
77
89
  nulls: value,
@@ -80,14 +92,14 @@ const transformers = {
80
92
  offset: expression,
81
93
  on: onCondition,
82
94
  // definitions, extensions, members ----------------------------------------
83
- returns: standard, // storing the return type of actions
95
+ returns: definition, // storing the return type of actions
84
96
  notNull: value,
85
97
  default: expression,
86
98
  // targetElement: ignore, // special display of foreign key, renameTo: select
87
99
  value: enumValue, // do not list for select items as elements
88
100
  query,
89
101
  elements,
90
- actions: nonEmptyDict, // TODO: just normal dictionary
102
+ actions, // TODO: just normal dictionary
91
103
  // special: top-level, cardinality -----------------------------------------
92
104
  sources,
93
105
  definitions: sortedDict,
@@ -128,7 +140,7 @@ const transformers = {
128
140
  // which should appear at that place in order.
129
141
  const csnPropertyNames = {
130
142
  virtual: [ 'abstract' ], // abstract is compiler v1 CSN property
131
- kind: [ 'annotate', 'extend' ],
143
+ kind: [ 'annotate', 'extend', '$origin' ],
132
144
  op: [ 'join', 'func', 'xpr' ], // TODO: 'func','xpr' into 'quantifier'? TODO: 'global'(scope)?
133
145
  quantifier: [
134
146
  'some', 'any', 'distinct', // 'all' explicitly listed
@@ -148,7 +160,7 @@ const csnPropertyNames = {
148
160
  name: [ 'as', 'cast' ],
149
161
  location: [ '$env', '$location' ], // --enrich-csn
150
162
  expectedKind: [
151
- '_type', '_targetAspect', '_target', '_includes', '_links', '_art', '_scope',
163
+ '_origin', '_type', '_targetAspect', '_target', '_includes', '_links', '_art', '_scope',
152
164
  ], // --enrich-csn
153
165
  };
154
166
 
@@ -183,6 +195,22 @@ const operators = {
183
195
  notLike: ternary( [ 'not', 'like' ], [ 'escape' ] ),
184
196
  when: exprs => [ 'when', ...exprs[0], 'then', ...exprs[1] ],
185
197
  case: exprs => [ 'case' ].concat( ...exprs, [ 'end' ] ),
198
+ over: exprs => [ 'over', { xpr: [].concat( ...exprs ) } ],
199
+ orderBy: exprs => [
200
+ 'order', 'by', ...exprs[0].concat( ...exprs.slice(1).map( e => [ ',', ...e ] ) ),
201
+ ],
202
+ partitionBy: exprs => [
203
+ 'partition', 'by', ...exprs[0].concat( ...exprs.slice(1).map( e => [ ',', ...e ] ) ),
204
+ ],
205
+ rows: exprs => [
206
+ 'rows', ...exprs[0].concat( ...exprs.slice(1).map( e => [ ',', ...e ] ) ),
207
+ ],
208
+ preceding: postfix( [ 'preceding' ] ),
209
+ unboundedPreceding: [ 'unbounded', 'preceding' ],
210
+ currentRow: [ 'current', 'row' ],
211
+ unboundedFollowing: [ 'unbounded', 'following' ],
212
+ following: postfix( [ 'following' ] ),
213
+ frameBetween: exprs => [ 'between', ...exprs[0], 'and', ...exprs[1] ],
186
214
  // xpr: (exprs) => [].concat( ...exprs ), see below - handled extra
187
215
  };
188
216
 
@@ -194,7 +222,7 @@ const csnDirectValues = [ 'val' ]; // + all starting with '@' - TODO: still rele
194
222
  /**
195
223
  * Sort property names of CSN according to sequence which is also used by the compactModel function
196
224
  * Only returns enumerable properties, except for certain hidden properties
197
- * if requested (cloneOptions != false): $location, $env, elements.
225
+ * if requested (cloneOptions != false): $location, elements.
198
226
  *
199
227
  * If cloneOptions is false or if either cloneOptions.testMode or cloneOptions.testSortCsn
200
228
  * are set, definitions are also sorted.
@@ -224,24 +252,36 @@ function sortCsn( csn, cloneOptions = false ) {
224
252
  r[n] = sortCsn(val, cloneOptions);
225
253
  }
226
254
  if (cloneOptions && typeof csn === 'object') {
227
- if (csn.$sources && !r.$sources)
228
- setHidden(r, '$sources', csn.$sources);
229
- if (csn.$location && !r.$location)
230
- setHidden(r, '$location', csn.$location);
231
- if (csn.$env)
232
- setHidden(r, '$env', csn.$env);
233
- if (csn.$path) // used in generic reference flattener
234
- setHidden(r, '$path', csn.$path);
235
- if (csn.$paths) // used in generic reference flattener
236
- setHidden(r, '$paths', csn.$paths);
237
- if (csn.elements && !r.elements) // non-enumerable 'elements'
238
- setHidden(r, 'elements', csnDictionary( csn.elements, false, cloneOptions ) );
239
- if (csn.$tableConstraints && !r.$tableConstraints)
240
- setHidden(r, '$tableConstraints', csn.$tableConstraints);
255
+ if ({}.hasOwnProperty.call( csn, '$sources' ) && !r.$sources)
256
+ setHidden( r, '$sources', csn.$sources );
257
+ if ({}.hasOwnProperty.call( csn, '$location' ) && !r.$location)
258
+ setHidden( r, '$location', csn.$location );
259
+ if ({}.hasOwnProperty.call( csn, '$path' )) // used in generic reference flattener
260
+ setHidden( r, '$path', csn.$path );
261
+ if ({}.hasOwnProperty.call( csn, '$paths' )) // used in generic reference flattener
262
+ setHidden( r, '$paths', csn.$paths );
263
+ if (hasNonEnumerable( csn, 'elements' ) && !r.elements) // non-enumerable 'elements'
264
+ setHidden( r, 'elements', csnDictionary( csn.elements, false, cloneOptions ) );
265
+ if (hasNonEnumerable( csn, '$tableConstraints' ) && !r.$tableConstraints)
266
+ setHidden( r, '$tableConstraints', csn.$tableConstraints );
241
267
  }
242
268
  return r;
243
269
  }
244
270
 
271
+ /**
272
+ * Check wether the given object has non enumerable property.
273
+ * Ensure that we don't take it from the prototype, only "directly" - we accidentally
274
+ * cloned elements with a cds.linked input otherwise.
275
+ *
276
+ * @param {object} object
277
+ * @param {string} property
278
+ * @returns
279
+ */
280
+ function hasNonEnumerable(object, property) {
281
+ return {}.hasOwnProperty.call( object, property ) &&
282
+ !{}.propertyIsEnumerable.call( object, property );
283
+ }
284
+
245
285
  /**
246
286
  * @param {object} csn
247
287
  * @param {boolean} sort
@@ -353,10 +393,12 @@ function usings( srcDict ) {
353
393
  * @param {object} csn
354
394
  * @param {object} model
355
395
  */
396
+
397
+
356
398
  function extensions( node, csn, model ) {
357
399
  if (model.kind && model.kind !== 'source')
358
400
  return undefined;
359
- const exts = node.map( standard );
401
+ const exts = node.map( definition );
360
402
 
361
403
  // builtins are non-enumerable for smaller display
362
404
  for (const name of Object.getOwnPropertyNames( model.definitions || {} ).sort()) {
@@ -371,17 +413,21 @@ function extensions( node, csn, model ) {
371
413
  }
372
414
  else if (gensrcFlavor) {
373
415
  // From definitions (without redefinitions) with potential inferred elements:
374
- if (!Array.isArray(art) && art.elements &&
375
- (art.query || art.includes || art.$inferred)) {
376
- const annos = art.$inferred && annotationsAndDocComment( art, true );
377
- const elems = inferred( art.elements, art.$inferred );
378
- /** @type {object} */
379
- const annotate = Object.assign( { annotate: name }, annos );
380
- if (Object.keys( elems ).length)
381
- annotate.elements = elems;
382
- if (Object.keys( annotate ).length > 1)
383
- exts.push( annotate );
416
+ const annotate = { annotate: name };
417
+ if (art.$inferred)
418
+ Object.assign( annotate, annotationsAndDocComment( art, true ) );
419
+ if (art.$expand === 'annotate') {
420
+ if (art.actions)
421
+ attachAnnotations( annotate, 'actions', art.actions, art.$inferred );
422
+ else if (art.params)
423
+ attachAnnotations( annotate, 'params', art.params, art.$inferred );
424
+ const obj = art.returns || art;
425
+ const elems = (obj.items || obj).elements; // no targetAspect here
426
+ if (elems)
427
+ attachAnnotations( annotate, 'elements', elems, art.$inferred, art.returns );
384
428
  }
429
+ if (Object.keys( annotate ).length > 1)
430
+ exts.push( annotate );
385
431
  }
386
432
  }
387
433
 
@@ -389,6 +435,58 @@ function extensions( node, csn, model ) {
389
435
  (a, b) => (a.annotate || a.extend).localeCompare( b.annotate || b.extend )
390
436
  );
391
437
 
438
+ /*
439
+ function attachElementAnnos( annotate, art ) {
440
+ while (art.items)
441
+ art = art.items;
442
+ if (art.elements) {
443
+ const elems = inferred( art.elements, art.$inferred );
444
+ if (Object.keys( elems ).length)
445
+ annotate.elements = elems;
446
+ }
447
+ }
448
+
449
+ function attachParamAnnos( annotate, art ) {
450
+ const inferredParent = art.$inferred;
451
+ if (art.params) {
452
+ const ext = Object.create( dictionaryPrototype );
453
+ for (const name in art.params) {
454
+ const par = art.params[name];
455
+ if (!inferredParent && !par.$inferred && par.$expand !== 'annotate')
456
+ continue;
457
+ const render = annotationsAndDocComment( par, true );
458
+ const subElems = par.$expand !== 'origin' && (par.items || par).elements;
459
+ if (subElems) {
460
+ const sub = inferred( subElems, par.$inferred );
461
+ if (Object.keys( sub ).length)
462
+ render.elements = sub;
463
+ }
464
+ if (Object.keys(render).length)
465
+ ext[name] = render;
466
+ }
467
+ if (obj.keys( ext ))
468
+ annotate.params = ext;
469
+ }
470
+ if (art.returns) {
471
+ const par = art.returns;
472
+ if (!inferredParent && !par.$inferred && par.$expand !== 'annotate')
473
+ return;
474
+ const render = annotationsAndDocComment( par, true );
475
+ const subElems = par.$expand !== 'origin' && (par.items || par).elements;
476
+ if (subElems) {
477
+ const sub = inferred( subElems, par.$inferred );
478
+ if (Object.keys( sub ).length)
479
+ render.elements = sub;
480
+ }
481
+ if (Object.keys(render).length)
482
+ const sub = inferred( subElems, par.$inferred );
483
+ if (Object.keys( sub ).length)
484
+ render.elements = sub;
485
+ }
486
+ }
487
+ return ext;
488
+ */
489
+
392
490
  // extract namespace/builtin annotations
393
491
  function extractAnnotationsToExtension( art ) {
394
492
  const name = art.name.absolute;
@@ -450,17 +548,29 @@ function sources( srcDict, csn ) {
450
548
  }
451
549
  }
452
550
 
453
- function inferred( elems, inferredParent ) {
454
- const ext = Object.create( dictionaryPrototype );
455
- for (const name in elems) {
456
- const elem = elems[name];
457
- if (Array.isArray(elem) || !inferredParent && !elem.$inferred)
458
- continue;
459
- const csn = annotationsAndDocComment( elem, true );
460
- if (Object.keys(csn).length)
461
- ext[name] = csn;
551
+ function attachAnnotations( annotate, prop, dict, inferred, returns = false ) {
552
+ const annoDict = Object.create( dictionaryPrototype );
553
+ for (const name in dict) {
554
+ const elem = dict[name];
555
+ const inf = inferred || elem.$inferred; // is probably always inferred if parent was
556
+ const sub = (inf) ? annotationsAndDocComment( elem, true ) : {};
557
+ if (elem.$expand === 'annotate') {
558
+ if (elem.params)
559
+ attachAnnotations( sub, 'params', elem.params, inf );
560
+ const obj = elem.returns || elem;
561
+ const elems = (obj.items || obj.targetAspect || obj).elements;
562
+ if (elems)
563
+ attachAnnotations( sub, 'elements', elems, inf, elem.returns );
564
+ }
565
+ if (Object.keys( sub ).length)
566
+ annoDict[name] = sub;
567
+ }
568
+ if (Object.keys( annoDict ).length) {
569
+ if (returns)
570
+ annotate.returns = { elements: annoDict };
571
+ else
572
+ annotate[prop] = annoDict;
462
573
  }
463
- return ext;
464
574
  }
465
575
 
466
576
  function standard( node ) {
@@ -499,48 +609,96 @@ function set( prop, csn, node ) {
499
609
  }
500
610
 
501
611
  function targetAspect( val, csn, node ) {
612
+ const ta = (val.elements)
613
+ ? addLocation( val.location, standard( val ) )
614
+ : artifactRef( val, true );
502
615
  if (!gensrcFlavor || node.target && !node.target.$inferred)
503
- return (val.elements) ? standard( val ) : artifactRef( val, true );
616
+ return ta;
504
617
  // For compatibility, put aspect in 'target' with parse.cdl and csn flavor 'gensrc'
505
- csn.target = (val.elements) ? standard( val ) : artifactRef( val, true );
618
+ csn.target = ta;
506
619
  return undefined;
507
620
  }
508
621
 
509
622
  function target( val, _csn, node ) {
510
623
  if (gensrcFlavor && node._origin && node._origin.$inferred === 'REDIRECTED')
511
624
  val = node._origin.target;
512
- if (!val.elements)
625
+ if (val.elements)
626
+ return standard( val ); // elements in target (parse-cdl)
627
+ if (!universalCsn || node.on)
513
628
  return artifactRef( val, true );
514
- return standard( val ); // elements in target (parse-cdl)
629
+ const tref = artifactRef( val, true );
630
+ const proto = node.type && !node.type.$inferred ? node.type._artifact : node._origin;
631
+ return (proto && proto.target && artifactRef( proto.target, true ) === tref)
632
+ ? undefined
633
+ : tref;
515
634
  }
516
635
 
517
636
  function items( obj, csn, node ) {
518
- if (!keepElements( node ) && node.type && node.kind !== 'type')
519
- // no 'elements' with SELECT or inferred elements with gensrc;
520
- // hidden 'elements' will be set in query()
637
+ if (!keepElements( node, obj ))
521
638
  return undefined;
522
- return standard( obj );
639
+ return standard( obj ); // no 'elements' with inferred elements with gensrc
523
640
  }
524
641
 
525
642
  function elements( dict, csn, node ) {
526
- if (csn.from ||
643
+ if (node.from || // do not directly show query elements here
527
644
  gensrcFlavor && (node.query || node.type) ||
528
- node.type && node.kind !== 'type' && !keepElements( node ))
645
+ !keepElements( node ))
646
+ // no 'elements' with SELECT or inferred elements with gensrc;
647
+ // hidden or visible 'elements' will be set in query()
648
+ return undefined;
649
+ return insertOrderDict( dict );
650
+ }
651
+
652
+ function enumDict( dict, csn, node ) {
653
+ if (gensrcFlavor && dict[$inferred] ||
654
+ !keepElements( node ))
529
655
  // no 'elements' with SELECT or inferred elements with gensrc;
530
- // hidden 'elements' will be set in query()
656
+ // hidden or visible 'elements' will be set in query()
531
657
  return undefined;
658
+ if (universalCsn && node.type && !node.type.$inferred && node.$expand === 'annotate')
659
+ // derived type of enum type with individual annotations: also set $origin
660
+ csn.$origin = originRef( node.type._artifact );
532
661
  return insertOrderDict( dict );
533
662
  }
534
663
 
535
- // We do not optimize away elements which are potentially adapted during their
536
- // way from the original structure type definition to the current usage
537
- function keepElements( node ) {
664
+ function enumerableQueryElements( select ) {
665
+ if (!universalCsn || select === select._main._leadingQuery)
666
+ return false;
667
+ if (select.orderBy || select.$orderBy)
668
+ return true;
669
+ const alias = select._parent;
670
+ return alias.query && (alias.query._leadingQuery || alias.query) === select;
671
+ }
672
+
673
+ // Should we render the elements? (and items?)
674
+ function keepElements( node, line ) {
675
+ if (universalCsn)
676
+ // $expand = null/undefined: not elements not via expansion
677
+ // $expand = 'target'/'annotate': with redirections / individual annotations
678
+ return node.$expand !== 'origin';
679
+ if (!node.type || node.kind === 'type')
680
+ return true;
681
+ // keep many SimpleType/Entity
682
+ if (line) {
683
+ if (!node.type)
684
+ return true;
685
+ const array = node.type._artifact; // see function items() in propagator.js
686
+ const ltype = line.type && line.type._artifact;
687
+ if (!array || // reference errors
688
+ array._main && !line.elements && !line.enum && !line.items && !line.notNull &&
689
+ (!ltype || !ltype._main)) // many Foo:bar -> not SimpleType
690
+ return true;
691
+ }
692
+ // even if expanded elements have no new target or direct annotation,
693
+ // they might have got one via propagation – any new target/annos during their
694
+ // way from the original structure type definition to the current usage
538
695
  while (node) {
539
696
  if (node.$expand !== 'origin')
540
697
  return true;
541
698
  node = node._origin;
542
699
  }
543
- return false;
700
+ // all in _origin chain only have expanded elements with 'origin':
701
+ return false; // no need to render elements
544
702
  }
545
703
 
546
704
  // for gensrcFlavor and namespace/builtin annotation extraction:
@@ -626,17 +784,17 @@ function sortedDict( dict ) {
626
784
  return dictionary( dict, keys );
627
785
  }
628
786
 
629
- function nonEmptyDict( dict ) {
787
+ function actions( dict ) {
630
788
  const keys = Object.keys( dict );
631
789
  return (keys.length)
632
- ? dictionary( dict, keys )
790
+ ? dictionary( dict, keys, 'actions' )
633
791
  : undefined;
634
792
  }
635
793
 
636
- function dictionary( dict, keys ) {
794
+ function dictionary( dict, keys, prop ) {
637
795
  const csn = Object.create( dictionaryPrototype );
638
796
  for (const name of keys) {
639
- const def = definition( dict[name] );
797
+ const def = definition( dict[name], null, null, prop );
640
798
  if (def !== undefined)
641
799
  csn[name] = def;
642
800
  }
@@ -644,6 +802,8 @@ function dictionary( dict, keys ) {
644
802
  }
645
803
 
646
804
  function foreignKeys( dict, csn, node ) {
805
+ if (universalCsn && !target( node.target, csn, node ))
806
+ return;
647
807
  if (gensrcFlavor && node._origin && node._origin.$inferred === 'REDIRECTED')
648
808
  dict = node._origin.foreignKeys;
649
809
  const keys = [];
@@ -657,7 +817,7 @@ function foreignKeys( dict, csn, node ) {
657
817
  csn.keys = keys;
658
818
  }
659
819
 
660
- function definition( art ) {
820
+ function definition( art, _csn, _node, prop ) {
661
821
  if (!art || typeof art !== 'object')
662
822
  return undefined; // TODO: complain with strict
663
823
  // Do not include namespace definitions or inferred construct (in gensrc):
@@ -669,22 +829,176 @@ function definition( art ) {
669
829
  addLocation( art.targetElement.location, key );
670
830
  return extra( key, art );
671
831
  }
672
- return standard( art );
832
+ const c = standard( art );
833
+ // The XSN of actions in extensions do not contain a returns yet - TODO?
834
+ const elems = c.elements;
835
+ if (elems && (prop === 'actions' || art.$syntax === 'returns')) {
836
+ delete c.elements;
837
+ c.returns = { elements: elems };
838
+ }
839
+ if (kind && kind !== 'key')
840
+ addOrigin( c, art, art._origin );
841
+ return c;
842
+ }
843
+
844
+ // create $origin specification for `includes` of `art`
845
+ function includesOrigin( includes, art ) {
846
+ const $origin = originRef( includes[0]._artifact );
847
+ if (includes.length === 1)
848
+ return $origin;
849
+ const result = { $origin };
850
+ for (const incl of includes.slice(1)) {
851
+ const aspect = incl._artifact;
852
+ for (const prop in aspect) {
853
+ if (prop.charAt(0) === '@' && (!art[prop] || art[prop].$inferred)) {
854
+ const anno = aspect[prop];
855
+ if (anno.val !== null)
856
+ // matererialize non-null annos (whether direct or inherited)
857
+ result[prop] = value( Object.create( anno, { $inferred: { value: null } } ) );
858
+ }
859
+ }
860
+ }
861
+ return (Object.keys( result ).length === 1) ? $origin : result;
862
+ }
863
+
864
+ function addOrigin( csn, xsn, origin ) {
865
+ if (!universalCsn || hasExplicitProp( xsn.type ))
866
+ return;
867
+ if (xsn._from) {
868
+ const source = xsn._from[0]._origin;
869
+ csn.$origin = originRef( source );
870
+ if (source.params && !xsn.params)
871
+ csn.params = null; // discontinue `params` inheritance
872
+ if (source.actions && !xsn.actions)
873
+ csn.actions = null; // discontinue `actions` inheritance
874
+ return;
875
+ }
876
+ else if (xsn.includes) {
877
+ csn.$origin = includesOrigin( xsn.includes, xsn );
878
+ return;
879
+ }
880
+ else if (!xsn._main || xsn.kind === 'select') {
881
+ return;
882
+ }
883
+ const parent = getParent( xsn );
884
+ const parentOrigin = getOrigin( parent );
885
+ if (!xsn._origin || xsn._origin.kind === 'builtin') { // or $dollarVariable
886
+ if (parentOrigin && (!parent.enum || parent.$origin || !parent.type))
887
+ csn.$origin = null;
888
+ return;
889
+ }
890
+ // Skip all proxies which do not make it into the CSN, as there are no
891
+ // individual annotations or redirection targets on it:
892
+ while (origin._parent && origin._parent.$expand === 'origin')
893
+ origin = origin._origin || origin.type._artifact;
894
+ // The while loop is not only for the else case below: when setting implicit
895
+ // prototypes, it is important that we do not have to follow the prototypes of
896
+ // other object; we would need to ensure a right order to avoid issues otherwise.
897
+ if (parentOrigin === getParent( origin )) {
898
+ // implicit prototype or shortened reference
899
+ const { id } = origin.name || {};
900
+ if (id && xsn.name && id !== xsn.name.id)
901
+ csn.$origin = id;
902
+ return;
903
+ }
904
+ if (origin.kind === 'mixin') {
905
+ // currently, target and on are always set - nothing to do here, just set type
906
+ csn.type = 'cds.Association';
907
+ return;
908
+ }
909
+ const ref = originRef( origin, xsn );
910
+ if (ref) {
911
+ csn.$origin = ref;
912
+ return;
913
+ }
914
+ // An element of a query with a query in FROM:
915
+ const anon = definition( origin ); // use $origin: {...} if necessary
916
+ // as there are no implicit $origin prototypes on sub query elements (yet),
917
+ // we do not have to care about $origin not being set
918
+ const { $origin } = anon;
919
+ if ($origin && typeof $origin === 'object' && !Array.isArray( $origin )) {
920
+ // repeated anon: flatten
921
+ csn.$origin = Object.assign( $origin, anon );
922
+ }
923
+ else if (Object.keys( anon )
924
+ // (we can use the properties in `csn`, because addOrigin() is called last)
925
+ .every( p => p in csn || p === '$origin' || p === '$location')) {
926
+ // nothing new in $origin: {...}
927
+ addOrigin( csn, xsn, origin._origin );
928
+ }
929
+ else {
930
+ csn.$origin = anon;
931
+ }
932
+ }
933
+
934
+ function getParent( art ) {
935
+ const parent = art._parent;
936
+ const main = parent._main;
937
+ return (main && parent === main._leadingQuery) ? main : parent;
938
+ }
939
+
940
+ function getOrigin( art ) {
941
+ if (art._origin)
942
+ return art._origin;
943
+ if (hasExplicitProp( art.type ))
944
+ return art.type._artifact;
945
+ if (art.includes)
946
+ return art.includes[0]._artifact;
947
+ if (art._from)
948
+ return art._from[0]._origin;
949
+ return undefined;
950
+ }
951
+
952
+ function hasExplicitProp( ref ) {
953
+ return ref && !ref.$inferred;
954
+ }
955
+
956
+ function originRef( art, user ) {
957
+ const r = [];
958
+ // do not use name.element, as we allow `.`s in name
959
+ let parent = art;
960
+ while (parent._main && parent.kind !== 'select') {
961
+ const nkind = normalizedKind[parent.kind];
962
+ if (parent.name.id || !r.length)
963
+ // Return parameter is in XSN - kind: 'param', name.id: ''
964
+ // eslint-disable-next-line no-nested-ternary, max-len
965
+ r.push( !nkind ? parent.name.id : parent.name.id ? { [nkind]: parent.name.id } : { return: true } );
966
+ parent = parent._parent;
967
+ }
968
+ if (user && parent._main && parent._main === user._main && parent !== user._main._leadingQuery)
969
+ // well, an element of an query in FROM (TODO: try with sub elem), but not the leading query
970
+ return null; // probably use $origin: {...}
971
+ // for sub query in FROM in sub query in FROM, we could condense the info
972
+
973
+ // Now the ref, with ["absolute", "action"] instead of ["absolute", {action:"action"}]
974
+ if (r.length === 1 && normalizedKind[art.kind] === 'action')
975
+ return [ art.name.absolute, art.name.id ];
976
+ r.push( art.name.absolute );
977
+ r.reverse();
978
+ return r;
673
979
  }
674
980
 
675
981
  function kind( k, csn, node ) {
676
- if (!node._main && [ 'annotate', 'extend' ].includes( k )) {
982
+ if (k === 'annotate' || k === 'extend') {
677
983
  // We just use `name.absolute` because it is very likely a "constructed"
678
984
  // extensions. The CSN parser must produce name.path like for other refs.
679
- csn[k] = node.name.absolute || artifactRef( node.name, true );
680
- return undefined;
985
+ if (!node._main)
986
+ csn[k] = node.name.absolute || artifactRef( node.name, true );
987
+ else if (k === 'extend')
988
+ csn.kind = k;
681
989
  }
682
- if ([
683
- 'element', 'key', 'param', 'enum', 'annotate', 'select', '$join',
990
+ else if (![
991
+ 'element', 'key', 'param', 'enum', 'select', '$join',
684
992
  '$tableAlias', 'annotation', 'mixin',
685
- ].includes(k))
993
+ ].includes(k)) {
994
+ csn.kind = k;
995
+ }
996
+ }
997
+
998
+ function type( node, csn, xsn ) {
999
+ if (universalCsn && node.$inferred && xsn._origin)
686
1000
  return undefined;
687
- return k;
1001
+ return artifactRef( node, !node.$extra );
688
1002
  }
689
1003
 
690
1004
  function artifactRef( node, terse ) {
@@ -786,10 +1100,12 @@ function args( node ) {
786
1100
  return dict;
787
1101
  }
788
1102
 
789
- // "Short" value form, e.g. for annotation assignments
790
1103
  function value( node ) {
1104
+ // "Short" value form, e.g. for annotation assignments
791
1105
  if (!node)
792
1106
  return true; // `@aBool` short for `@aBool: true`
1107
+ if (universalCsn && node.$inferred === 'prop') // via propagator.js
1108
+ return undefined;
793
1109
  if (node.$inferred && gensrcFlavor)
794
1110
  return undefined;
795
1111
  if (node.path) {
@@ -813,7 +1129,10 @@ function value( node ) {
813
1129
 
814
1130
  function enumValue( v, csn, node ) {
815
1131
  // Enums can have values but if enums are extended, their kind is 'element',
816
- // so we check whether the node is inside an extension.
1132
+ // so we check whether the node is inside an extension. (TODO: still?)
1133
+ if (universalCsn && v.$inferred)
1134
+ return;
1135
+ // (with gensrc, the symbol itself would not make it into the CSN)
817
1136
  if (node.kind === 'enum' || node._parent && node._parent.kind === 'extend')
818
1137
  Object.assign( csn, expression( v, true ) );
819
1138
  }
@@ -832,23 +1151,7 @@ function onCondition( cond, csn, node ) {
832
1151
  function condition( node ) {
833
1152
  const expr = expression( node );
834
1153
  // we do not set a hidden $parens on array - we could still do it if requested
835
- return !expr.cast && expr.xpr || [ expr ];
836
- }
837
-
838
- // TODO: quoted magic names like $now should be complained about in the compiler
839
-
840
- function pathRef( path ) {
841
- const ref = { ref: path.map( pathItem ) };
842
- const nav = path[0]._navigation;
843
- if (nav && nav.kind !== '$self' && nav.kind !== 'element' && nav.name.select != null) {
844
- setHidden( ref, '$env', (nav.kind === '$navElement')
845
- ? nav.name.alias
846
- : nav.name.select );
847
- }
848
- else if ( path[0]._artifact && path[0]._artifact.query ) {
849
- setHidden( ref, '$env', true );
850
- }
851
- return ref;
1154
+ return !expr.cast && !expr.func && expr.xpr || [ expr ];
852
1155
  }
853
1156
 
854
1157
  function expression( node, dollarExtra ) {
@@ -863,8 +1166,8 @@ function expression( node, dollarExtra ) {
863
1166
  return { ref: [ node.param.val ], param: true }; // CDL rule for runtimes
864
1167
  }
865
1168
  if (node.path) {
866
- // TODO: global
867
- return extra( pathRef( node.path ), dollarExtraNode );
1169
+ // we would need to consider node.global here if we introduce that
1170
+ return extra( { ref: node.path.map( pathItem ) }, dollarExtraNode );
868
1171
  }
869
1172
  if (node.literal) {
870
1173
  if (typeof node.val === node.literal || node.val === null)
@@ -888,10 +1191,12 @@ function expression( node, dollarExtra ) {
888
1191
  arg0.xpr.unshift( quantifier.val );
889
1192
  }
890
1193
  }
1194
+ if (node.suffix)
1195
+ call.xpr = [].concat( ...node.suffix.map( xprArg ) );
891
1196
  return extra( call, dollarExtraNode );
892
1197
  }
893
1198
  if (node.query)
894
- return query( node.query, null, null, 1 );
1199
+ return query( node.query, null, null, null, 1 );
895
1200
  if (!node.op) // parse error
896
1201
  return { xpr: [] };
897
1202
  else if (node.op.val === 'xpr')
@@ -901,7 +1206,7 @@ function expression( node, dollarExtra ) {
901
1206
  return cast( expression( node.args[0] ), dollarExtraNode );
902
1207
  // from here on: CDL input (no $extra possible - but $parens)
903
1208
  else if (node.op.val !== ',')
904
- return extra( { xpr: xpr( node ) }, dollarExtraNode, 1 );
1209
+ return extra( { xpr: xpr( node ) }, dollarExtraNode, (dollarExtra === 'sub-xpr' ? 1 : 0) );
905
1210
  return (parensAsStrings)
906
1211
  ? { xpr: [ '(', ...xpr( node ), ')' ] }
907
1212
  // the inner parens belong to the tuple construct, i.e. won't count as parens
@@ -911,15 +1216,7 @@ function expression( node, dollarExtra ) {
911
1216
  function xpr( node ) {
912
1217
  // if (!node.op) console.log(node)
913
1218
  const op = operators[node.op.val] || node.op.val.split(' ');
914
- const exprs = node.args.map( ( sub ) => {
915
- const expr = expression( sub );
916
- // return !sub.$parens && !expr.cast && expr.xpr || [ expr ]; if parensAsStrings is gone
917
- if (expr.cast || !expr.xpr || sub.$parens && !parensAsStrings)
918
- return [ expr ];
919
- else if (sub.$parens && sub.op.val !== ',')
920
- return [ '(', ...expr.xpr, ')' ];
921
- return expr.xpr;
922
- } );
1219
+ const exprs = node.args.map( xprArg );
923
1220
  if (op instanceof Function)
924
1221
  return op( exprs );
925
1222
  if (node.quantifier)
@@ -929,6 +1226,27 @@ function xpr( node ) {
929
1226
  return exprs[0].concat( ...exprs.slice(1).map( a => [ ...op, ...a ] ) );
930
1227
  }
931
1228
 
1229
+ function xprArg( sub ) {
1230
+ const realXpr = sub.op && sub.op.val === 'xpr';
1231
+ const expr = expression( sub, 'sub-xpr' );
1232
+ // `sort`/`nulls` will be attached to arguments of orderBy
1233
+ // which might be either `path`s or `xpr`s
1234
+ const sortAndNulls = [];
1235
+ if (sub.sort)
1236
+ sortAndNulls.push( sub.sort.val );
1237
+ if (sub.nulls)
1238
+ sortAndNulls.push( ...[ 'nulls', sub.nulls.val ] );
1239
+ // return !sub.$parens && !expr.cast && !expr.func && expr.xpr || [ expr ];
1240
+ // if parensAsStrings is gone
1241
+ if (realXpr || expr.cast || expr.func || !expr.xpr || sub.$parens && !parensAsStrings)
1242
+ return [ expr, ...sortAndNulls ];
1243
+ else if (sub.$parens && sub.op.val !== ',')
1244
+ return [ '(', ...expr.xpr, ')' ];
1245
+
1246
+ expr.xpr.push( ...sortAndNulls );
1247
+ return expr.xpr;
1248
+ }
1249
+
932
1250
  function ternary( op1, op2 ) {
933
1251
  return function ternaryOp( exprs ) {
934
1252
  return (exprs[2])
@@ -952,7 +1270,7 @@ function binaryRightParen( op ) {
952
1270
  };
953
1271
  }
954
1272
 
955
- function query( node, csn, xsn, expectedParens = 0 ) {
1273
+ function query( node, csn, xsn, _prop, expectedParens = 0 ) {
956
1274
  if (node.op.val === 'SELECT') {
957
1275
  if (xsn && xsn.query === node && xsn.$syntax === 'projection' &&
958
1276
  node.from && node.from.path && !projectionAsQuery) {
@@ -969,7 +1287,10 @@ function query( node, csn, xsn, expectedParens = 0 ) {
969
1287
  const gensrcSaved = gensrcFlavor;
970
1288
  try {
971
1289
  gensrcFlavor = false;
972
- setHidden( select.SELECT, 'elements', insertOrderDict( elems ) );
1290
+ if (enumerableQueryElements( node ))
1291
+ select.SELECT.elements = insertOrderDict( elems );
1292
+ else
1293
+ setHidden( select.SELECT, 'elements', insertOrderDict( elems ) );
973
1294
  }
974
1295
  finally {
975
1296
  gensrcFlavor = gensrcSaved;
@@ -999,7 +1320,7 @@ function columns( xsnColumns, csn, xsn ) {
999
1320
  addElementAsColumn( col, csnColumns );
1000
1321
  }
1001
1322
  }
1002
- else { // null = use elements
1323
+ else { // null = use elements - TODO: still used by A2J? -> remove
1003
1324
  for (const name in xsn.elements)
1004
1325
  addElementAsColumn( xsn.elements[name], csnColumns );
1005
1326
  }
@@ -1021,7 +1342,7 @@ function from( node ) {
1021
1342
  return extra( join, node );
1022
1343
  }
1023
1344
  else if (node.query) {
1024
- return addExplicitAs( query( node.query, null, null, 1 ), node.name );
1345
+ return addExplicitAs( query( node.query, null, null, null, 1 ), node.name );
1025
1346
  }
1026
1347
  else if (!node._artifact || node._artifact._main) { // CQL or follow assoc
1027
1348
  return extra( addExplicitAs( artifactRef( node, false ), node.name ), node );
@@ -1057,25 +1378,15 @@ function addElementAsColumn( elem, cols ) {
1057
1378
  col.excluding = Object.keys( elem.excludingDict );
1058
1379
  // yes, the AS comes after the EXPAND
1059
1380
  addExplicitAs( col, elem.name, neqPath( elem.value ) );
1060
- // $env and elements (sub queries) in expr are hidden (not set via Object.assign):
1061
- if (!expr.cast) {
1062
- if (expr.$env)
1063
- setHidden( col, '$env', expr.$env );
1064
- if (expr.elements)
1065
- setHidden( col, 'elements', expr.elements );
1066
- }
1381
+ // elements of sub queries (in expr) are hidden (not set via Object.assign):
1382
+ if (!expr.cast && expr.elements)
1383
+ setHidden( col, 'elements', expr.elements );
1067
1384
  if (elem.type && !elem.type.$inferred || elem.target && !elem.target.$inferred)
1068
1385
  cast( col, elem );
1069
1386
  }
1070
1387
  finally {
1071
1388
  gensrcFlavor = gensrcSaved;
1072
1389
  }
1073
- // FIXME: Currently toHana requires that an '_ignore' property on the elem is
1074
- // also visible on the column. Don't ignore virtual columns, let the
1075
- // renderer decide how to render that column.
1076
- if (!elem.virtual && elem._ignore)
1077
- col._ignore = true;
1078
-
1079
1390
  if (elem.value && !elem.$inferred) {
1080
1391
  const parens = elem.value.$parens;
1081
1392
  if (parens)
@@ -1162,6 +1473,9 @@ function compactExpr( e ) { // TODO: options
1162
1473
  function initModuleVars( options = { csnFlavor: 'gensrc' } ) {
1163
1474
  gensrcFlavor = options.parseCdl || options.csnFlavor === 'gensrc' ||
1164
1475
  options.toCsn && options.toCsn.flavor === 'gensrc';
1476
+ universalCsn = (options.csnFlavor === 'universal' ||
1477
+ options.toCsn && options.toCsn.flavor === 'universal' ) &&
1478
+ isBetaEnabled( options, 'enableUniversalCsn' ) && !options.parseCdl;
1165
1479
  strictMode = options.testMode;
1166
1480
  const proto = options.dictionaryPrototype;
1167
1481
  // eslint-disable-next-line no-nested-ternary