@sap/cds-compiler 3.4.2 → 3.5.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 (143) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +1 -0
  3. package/bin/cds_update_identifiers.js +5 -5
  4. package/bin/cdsc.js +15 -16
  5. package/bin/cdshi.js +19 -6
  6. package/doc/CHANGELOG_ARCHIVE.md +2 -2
  7. package/doc/CHANGELOG_BETA.md +9 -1
  8. package/doc/CHANGELOG_DEPRECATED.md +2 -0
  9. package/lib/api/main.js +61 -59
  10. package/lib/api/options.js +4 -2
  11. package/lib/api/validate.js +2 -2
  12. package/lib/base/cleanSymbols.js +2 -3
  13. package/lib/base/dictionaries.js +6 -6
  14. package/lib/base/error.js +2 -2
  15. package/lib/base/keywords.js +6 -6
  16. package/lib/base/location.js +11 -12
  17. package/lib/base/message-registry.js +177 -58
  18. package/lib/base/messages.js +252 -180
  19. package/lib/base/model.js +14 -11
  20. package/lib/base/node-helpers.js +9 -10
  21. package/lib/base/optionProcessorHelper.js +138 -129
  22. package/lib/checks/.eslintrc.json +2 -0
  23. package/lib/checks/actionsFunctions.js +5 -5
  24. package/lib/checks/annotationsOData.js +4 -4
  25. package/lib/checks/arrayOfs.js +1 -1
  26. package/lib/checks/cdsPersistence.js +1 -1
  27. package/lib/checks/checkForTypes.js +3 -3
  28. package/lib/checks/defaultValues.js +3 -3
  29. package/lib/checks/elements.js +7 -7
  30. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  31. package/lib/checks/foreignKeys.js +1 -1
  32. package/lib/checks/invalidTarget.js +4 -4
  33. package/lib/checks/managedInType.js +1 -1
  34. package/lib/checks/managedWithoutKeys.js +1 -1
  35. package/lib/checks/nonexpandableStructured.js +5 -3
  36. package/lib/checks/nullableKeys.js +1 -1
  37. package/lib/checks/onConditions.js +5 -6
  38. package/lib/checks/parameters.js +1 -1
  39. package/lib/checks/queryNoDbArtifacts.js +2 -2
  40. package/lib/checks/selectItems.js +4 -4
  41. package/lib/checks/sql-snippets.js +4 -4
  42. package/lib/checks/types.js +7 -7
  43. package/lib/checks/utils.js +4 -4
  44. package/lib/checks/validator.js +16 -13
  45. package/lib/compiler/.eslintrc.json +4 -1
  46. package/lib/compiler/assert-consistency.js +8 -7
  47. package/lib/compiler/builtins.js +14 -14
  48. package/lib/compiler/checks.js +123 -48
  49. package/lib/compiler/define.js +12 -13
  50. package/lib/compiler/extend.js +266 -60
  51. package/lib/compiler/finalize-parse-cdl.js +10 -5
  52. package/lib/compiler/index.js +17 -14
  53. package/lib/compiler/populate.js +14 -6
  54. package/lib/compiler/propagator.js +2 -0
  55. package/lib/compiler/resolve.js +2 -15
  56. package/lib/compiler/shared.js +27 -16
  57. package/lib/compiler/tweak-assocs.js +5 -6
  58. package/lib/compiler/utils.js +20 -0
  59. package/lib/edm/annotations/genericTranslation.js +604 -358
  60. package/lib/edm/annotations/preprocessAnnotations.js +39 -35
  61. package/lib/edm/csn2edm.js +275 -222
  62. package/lib/edm/edm.js +17 -3
  63. package/lib/edm/edmAnnoPreprocessor.js +6 -6
  64. package/lib/edm/edmInboundChecks.js +2 -2
  65. package/lib/edm/edmPreprocessor.js +107 -77
  66. package/lib/edm/edmUtils.js +44 -5
  67. package/lib/gen/Dictionary.json +210 -8
  68. package/lib/gen/language.checksum +1 -1
  69. package/lib/gen/language.interp +67 -63
  70. package/lib/gen/language.tokens +81 -81
  71. package/lib/gen/languageLexer.interp +4 -10
  72. package/lib/gen/languageLexer.js +854 -869
  73. package/lib/gen/languageLexer.tokens +79 -81
  74. package/lib/gen/languageParser.js +14309 -13832
  75. package/lib/inspect/inspectModelStatistics.js +2 -2
  76. package/lib/inspect/inspectPropagation.js +6 -6
  77. package/lib/inspect/inspectUtils.js +2 -2
  78. package/lib/json/from-csn.js +102 -55
  79. package/lib/json/to-csn.js +119 -198
  80. package/lib/language/antlrParser.js +5 -2
  81. package/lib/language/docCommentParser.js +6 -6
  82. package/lib/language/errorStrategy.js +43 -23
  83. package/lib/language/genericAntlrParser.js +113 -133
  84. package/lib/language/language.g4 +1550 -1506
  85. package/lib/language/multiLineStringParser.js +3 -3
  86. package/lib/language/textUtils.js +2 -2
  87. package/lib/main.js +3 -3
  88. package/lib/model/csnRefs.js +5 -0
  89. package/lib/model/csnUtils.js +130 -122
  90. package/lib/model/revealInternalProperties.js +1 -1
  91. package/lib/model/sortViews.js +4 -6
  92. package/lib/modelCompare/compare.js +2 -2
  93. package/lib/modelCompare/utils/.eslintrc.json +22 -0
  94. package/lib/modelCompare/utils/filter.js +100 -0
  95. package/lib/optionProcessor.js +5 -0
  96. package/lib/render/.eslintrc.json +1 -0
  97. package/lib/render/DuplicateChecker.js +1 -1
  98. package/lib/render/manageConstraints.js +12 -12
  99. package/lib/render/toCdl.js +311 -276
  100. package/lib/render/toHdbcds.js +97 -94
  101. package/lib/render/toRename.js +5 -5
  102. package/lib/render/toSql.js +127 -223
  103. package/lib/render/utils/common.js +141 -108
  104. package/lib/render/utils/delta.js +227 -0
  105. package/lib/render/utils/sql.js +22 -6
  106. package/lib/render/utils/stringEscapes.js +3 -3
  107. package/lib/transform/db/.eslintrc.json +2 -0
  108. package/lib/transform/db/applyTransformations.js +3 -3
  109. package/lib/transform/db/assertUnique.js +13 -12
  110. package/lib/transform/db/associations.js +5 -5
  111. package/lib/transform/db/cdsPersistence.js +10 -8
  112. package/lib/transform/db/constraints.js +14 -14
  113. package/lib/transform/db/expansion.js +20 -22
  114. package/lib/transform/db/flattening.js +24 -42
  115. package/lib/transform/db/groupByOrderBy.js +3 -3
  116. package/lib/transform/db/temporal.js +6 -6
  117. package/lib/transform/db/transformExists.js +23 -23
  118. package/lib/transform/db/views.js +16 -16
  119. package/lib/transform/draft/.eslintrc.json +1 -35
  120. package/lib/transform/draft/db.js +10 -10
  121. package/lib/transform/draft/odata.js +2 -2
  122. package/lib/transform/forOdataNew.js +8 -29
  123. package/lib/transform/forRelationalDB.js +16 -6
  124. package/lib/transform/localized.js +11 -10
  125. package/lib/transform/odata/toFinalBaseType.js +41 -27
  126. package/lib/transform/odata/typesExposure.js +113 -47
  127. package/lib/transform/parseExpr.js +209 -106
  128. package/lib/transform/transformUtilsNew.js +17 -10
  129. package/lib/transform/translateAssocsToJoins.js +24 -19
  130. package/lib/transform/universalCsn/coreComputed.js +10 -10
  131. package/lib/transform/universalCsn/universalCsnEnricher.js +26 -26
  132. package/lib/transform/universalCsn/utils.js +3 -3
  133. package/lib/utils/file.js +5 -5
  134. package/lib/utils/moduleResolve.js +13 -13
  135. package/lib/utils/objectUtils.js +6 -6
  136. package/lib/utils/term.js +5 -2
  137. package/lib/utils/timetrace.js +51 -24
  138. package/package.json +5 -8
  139. package/share/messages/check-proper-type-of.md +1 -1
  140. package/share/messages/message-explanations.json +1 -1
  141. package/share/messages/redirected-to-complex.md +4 -4
  142. package/share/messages/{syntax-expecting-integer.md → syntax-expecting-unsigned-int.md} +7 -4
  143. package/lib/modelCompare/filter.js +0 -83
@@ -23,15 +23,16 @@ const normalizedKind = {
23
23
  param: 'param',
24
24
  action: 'action',
25
25
  function: 'action',
26
+ enum: 'enum',
26
27
  };
27
28
 
28
29
  /** @type {boolean|string} */
29
30
  let gensrcFlavor = true; // good enough here...
30
31
  let universalCsn = false;
31
32
  let strictMode = false; // whether to dump with unknown properties (in standard)
32
- let parensAsStrings = false;
33
33
  let projectionAsQuery = false;
34
34
  let withLocations = false;
35
+ let structXpr = false;
35
36
  let dictionaryPrototype = null;
36
37
 
37
38
  // Properties for dictionaries, set in compileX() and TODO: parseX(), must be
@@ -92,7 +93,7 @@ const transformers = {
92
93
  where: condition, // also pathItem after 'cardinality' before 'args'
93
94
  having: condition,
94
95
  args, // also pathItem after 'where', before 'on'/'orderBy'
95
- suffix: node => [].concat( ...node.suffix.map( xprArg ) ),
96
+ suffix: ignore, // handled in exprInternal()
96
97
  orderBy: arrayOf( orderBy ), // TODO XSN: make `sort` and `nulls` sibling properties
97
98
  sort: value,
98
99
  nulls: value,
@@ -190,41 +191,6 @@ const typeProperties = [
190
191
  'foreignKeys', 'on', // for explicit ON/keys with REDIRECTED
191
192
  ];
192
193
 
193
- const operators = {
194
- // standard is: binary infix (and corresponding n-ary), unary prefix
195
- isNot: [ 'is', 'not' ], // TODO XSN: 'is not'
196
- isNull: postfix( [ 'is', 'null' ] ),
197
- isNotNull: postfix( [ 'is', 'not', 'null' ] ),
198
- in: binaryRightParen( [ 'in' ] ),
199
- notIn: binaryRightParen( [ 'not', 'in' ] ),
200
- between: ternary( [ 'between' ], [ 'and' ] ),
201
- notBetween: ternary( [ 'not', 'between' ], [ 'and' ] ),
202
- like: ternary( [ 'like' ], [ 'escape' ] ),
203
- notLike: ternary( [ 'not', 'like' ], [ 'escape' ] ),
204
- when: exprs => [ 'when', ...exprs[0], 'then', ...exprs[1] ],
205
- case: exprs => [ 'case' ].concat( ...exprs, [ 'end' ] ),
206
- over: exprs => [ 'over', { xpr: [].concat( ...exprs ) } ],
207
- orderBy: exprs => [ // ORDER BY in generic functions
208
- ...exprs[0], ...operators.overOrderBy(exprs.slice(1)),
209
- ],
210
- overOrderBy: exprs => [ // ORDER BY in OVER() clause
211
- 'order', 'by', ...exprs[0].concat( ...exprs.slice(1).map( e => [ ',', ...e ] ) ),
212
- ],
213
- partitionBy: exprs => [
214
- 'partition', 'by', ...exprs[0].concat( ...exprs.slice(1).map( e => [ ',', ...e ] ) ),
215
- ],
216
- rows: exprs => [
217
- 'rows', ...exprs[0].concat( ...exprs.slice(1).map( e => [ ',', ...e ] ) ),
218
- ],
219
- preceding: postfix( [ 'preceding' ] ),
220
- unboundedPreceding: [ 'unbounded', 'preceding' ],
221
- currentRow: [ 'current', 'row' ],
222
- unboundedFollowing: [ 'unbounded', 'following' ],
223
- following: postfix( [ 'following' ] ),
224
- frameBetween: exprs => [ 'between', ...exprs[0], 'and', ...exprs[1] ],
225
- ixpr: exprs => [].concat( ...exprs ), // xpr extra, due to extra parentheses
226
- };
227
-
228
194
  const csnDictionaries = [
229
195
  'args', 'params', 'enum', 'mixin', 'elements', 'actions', 'definitions',
230
196
  ];
@@ -280,7 +246,7 @@ function sortCsn( csn, cloneOptions = false ) {
280
246
  return r;
281
247
  }
282
248
 
283
- function cloneAnnotationValue(val) {
249
+ function cloneAnnotationValue( val ) {
284
250
  if (typeof val !== 'object') // scalar
285
251
  return val;
286
252
  return JSON.parse( JSON.stringify( val ) );
@@ -295,7 +261,7 @@ function cloneAnnotationValue(val) {
295
261
  * @param {string} property
296
262
  * @returns
297
263
  */
298
- function hasNonEnumerable(object, property) {
264
+ function hasNonEnumerable( object, property ) {
299
265
  return {}.hasOwnProperty.call( object, property ) &&
300
266
  !{}.propertyIsEnumerable.call( object, property );
301
267
  }
@@ -345,7 +311,7 @@ function compactModel( model, options = model.options || {} ) {
345
311
  for (const first in srcDict) {
346
312
  const { namespace } = srcDict[first];
347
313
  if (namespace && namespace.path)
348
- csn.namespace = namespace.path.map( i => i.id ).join('.');
314
+ csn.namespace = pathName( namespace.path );
349
315
  break;
350
316
  }
351
317
  set( 'definitions', csn, model );
@@ -429,21 +395,10 @@ function extensions( node, csn, model ) {
429
395
  }
430
396
  else if (gensrcFlavor) {
431
397
  // From definitions (without redefinitions) with potential inferred elements:
432
- const annotate = { annotate: name };
433
- if (art.$inferred)
434
- Object.assign( annotate, annotationsAndDocComment( art, true ) );
435
- if (art.$expand === 'annotate') {
436
- if (art.actions)
437
- attachAnnotations( annotate, 'actions', art.actions, art.$inferred );
438
- else if (art.params)
439
- attachAnnotations( annotate, 'params', art.params, art.$inferred );
440
- const obj = art.returns || art;
441
- const elems = (obj.items || obj).elements; // no targetAspect here
442
- if (elems)
443
- attachAnnotations( annotate, 'elements', elems, art.$inferred, art.returns );
444
- }
445
- if (Object.keys( annotate ).length > 1)
446
- exts.push( annotate );
398
+ const result = { annotate: Object.create(null) };
399
+ attachAnnotations(result, 'annotate', { [name]: art }, art.$inferred );
400
+ if (result.annotate[name])
401
+ exts.push({ annotate: name, ...result.annotate[name] } );
447
402
  }
448
403
  }
449
404
 
@@ -470,7 +425,7 @@ function extensions( node, csn, model ) {
470
425
  const par = art.params[name];
471
426
  if (!inferredParent && !par.$inferred && par.$expand !== 'annotate')
472
427
  continue;
473
- const render = annotationsAndDocComment( par, true );
428
+ const render = annotationsAndDocComment( par );
474
429
  const subElems = par.$expand !== 'origin' && (par.items || par).elements;
475
430
  if (subElems) {
476
431
  const sub = inferred( subElems, par.$inferred );
@@ -487,7 +442,7 @@ function extensions( node, csn, model ) {
487
442
  const par = art.returns;
488
443
  if (!inferredParent && !par.$inferred && par.$expand !== 'annotate')
489
444
  return;
490
- const render = annotationsAndDocComment( par, true );
445
+ const render = annotationsAndDocComment( par );
491
446
  const subElems = par.$expand !== 'origin' && (par.items || par).elements;
492
447
  if (subElems) {
493
448
  const sub = inferred( subElems, par.$inferred );
@@ -508,7 +463,7 @@ function extensions( node, csn, model ) {
508
463
  const name = art.name.absolute;
509
464
  // 'true' because annotations on namespaces and builtins can only
510
465
  // happen through extensions.
511
- const annos = annotationsAndDocComment( art, true );
466
+ const annos = annotationsAndDocComment( art );
512
467
  const annotate = Object.assign( { annotate: name }, annos );
513
468
  if (Object.keys( annotate ).length > 1) {
514
469
  const loc = locationForAnnotationExtension();
@@ -567,16 +522,21 @@ function sources( srcDict, csn ) {
567
522
  function attachAnnotations( annotate, prop, dict, inferred, insideReturns = false ) {
568
523
  const annoDict = Object.create( dictionaryPrototype );
569
524
  for (const name in dict) {
570
- const elem = dict[name];
571
- const inf = inferred || elem.$inferred; // is probably always inferred if parent was
572
- const sub = (inf) ? annotationsAndDocComment( elem, true ) : {};
573
- if (elem.$expand === 'annotate') {
574
- if (elem.params)
575
- attachAnnotations( sub, 'params', elem.params, inf );
576
- const obj = elem.returns || elem;
577
- const elems = (obj.items || obj.targetAspect || obj).elements;
525
+ const entry = dict[name];
526
+ const inf = inferred || entry.$inferred; // is probably always inferred if parent was
527
+ const sub = (inf) ? annotationsAndDocComment( entry ) : {};
528
+ if (entry.$expand === 'annotate') {
529
+ if (entry.actions)
530
+ attachAnnotations( sub, 'actions', entry.actions, inf );
531
+ else if (entry.params)
532
+ attachAnnotations( sub, 'params', entry.params, inf );
533
+ const obj = entry.returns || entry;
534
+ const many = obj.items || obj;
535
+ const elems = (many.targetAspect || many).elements;
578
536
  if (elems)
579
- attachAnnotations( sub, 'elements', elems, inf, elem.returns );
537
+ attachAnnotations( sub, 'elements', elems, inf, entry.returns );
538
+ if (many.enum)
539
+ attachAnnotations( sub, 'enum', many.enum, inf );
580
540
  }
581
541
  if (Object.keys( sub ).length)
582
542
  annoDict[name] = sub;
@@ -723,10 +683,14 @@ function keepElements( node, line ) {
723
683
  return false; // no need to render elements
724
684
  }
725
685
 
726
- // for gensrcFlavor and namespace/builtin annotation extraction:
727
- // return annotations from definition (annotated==false)
728
- // or annotations (annotated==true)
729
- function annotationsAndDocComment( node, annotated ) {
686
+ /**
687
+ * For gensrcFlavor and namespace/builtin annotation extraction:
688
+ * return annotations from definition and annotations.
689
+ * The call side should check that node.$inferred is truthy.
690
+ *
691
+ * @param {object} node
692
+ */
693
+ function annotationsAndDocComment( node ) {
730
694
  const csn = {};
731
695
  const transformer = transformers['@'];
732
696
  const keys = Object.keys( node ).filter( a => a.charAt(0) === '@' ).sort();
@@ -734,18 +698,18 @@ function annotationsAndDocComment( node, annotated ) {
734
698
  const val = node[prop];
735
699
  // val.$priority isn't set for computed annotations like @Core.Computed
736
700
  // and @odata.containment.ignore
737
- // TODO: use $inferred instead special $priority value
738
- if (val.$priority !== undefined && (!!val.$priority) === annotated) {
739
- // transformer (= value) takes care to exclude $inferred annotation assignments
740
- const sub = transformer( val );
741
- // As value() just has one value, so we do not provide ( val, csn, node, prop )
742
- // which would be more robust, but makes some JS checks unhappy
743
- if (sub !== undefined)
744
- csn[prop] = sub;
745
- }
701
+ // transformer (= value) takes care to exclude $inferred annotation assignments
702
+ const sub = transformer( val );
703
+ // As value() just has one value, so we do not provide ( val, csn, node, prop )
704
+ // which would be more robust, but makes some JS checks unhappy
705
+ if (sub !== undefined)
706
+ csn[prop] = sub;
707
+ }
708
+ if (node.doc) {
709
+ const doc = transformers.doc(node.doc);
710
+ if (doc !== undefined)
711
+ csn.doc = doc;
746
712
  }
747
- if (node.doc)
748
- csn.doc = transformers.doc(node.doc);
749
713
  return csn;
750
714
  }
751
715
 
@@ -833,21 +797,18 @@ function dictionary( dict, keys, prop ) {
833
797
  }
834
798
 
835
799
  function foreignKeys( dict, csn, node ) {
836
- if (universalCsn) {
837
- if (!target( node.target, csn, node ) || dict[$inferred] === 'keys')
838
- return;
839
- }
840
- if (gensrcFlavor && node._origin && node._origin.$inferred === 'REDIRECTED')
841
- dict = node._origin.foreignKeys;
842
- const keys = [];
843
- for (const n in dict) {
844
- const d = definition( dict[n] );
845
- if (d !== undefined)
846
- keys.push( d );
847
- else
800
+ if (universalCsn && dict[$inferred] === 'keys' || !target( node.target, csn, node ) )
801
+ return;
802
+
803
+ if (gensrcFlavor) {
804
+ if (node._origin?.$inferred === 'REDIRECTED')
805
+ dict = node._origin.foreignKeys;
806
+ if (dict[$inferred])
848
807
  return;
849
808
  }
850
- csn.keys = keys;
809
+ csn.keys = [];
810
+ for (const n in dict)
811
+ csn.keys.push( definition( dict[n] ) );
851
812
  }
852
813
 
853
814
  function returns( art, csn, _node, prop ) {
@@ -1235,7 +1196,7 @@ function renderArtifactPath( node, path, terse, scope ) {
1235
1196
  const name = item._artifact && item._artifact.name;
1236
1197
  // In localization views, the _artifact link of `item` is important
1237
1198
  const id = name && name.absolute ||
1238
- path.slice( 0, scope ).map( i => i.id ).join('.');
1199
+ pathName( path.slice( 0, scope ) );
1239
1200
  path = [ Object.assign( {}, item, { id } ), ...path.slice( scope ) ];
1240
1201
  }
1241
1202
  const ref = path.map( pathItem );
@@ -1259,7 +1220,7 @@ function args( node ) {
1259
1220
  return node.map( expression );
1260
1221
  const dict = Object.create( dictionaryPrototype );
1261
1222
  for (const param in node)
1262
- dict[param] = expression( node[param], true );
1223
+ dict[param] = expression( node[param] );
1263
1224
  return dict;
1264
1225
  }
1265
1226
 
@@ -1302,7 +1263,7 @@ function enumValue( v, csn, node ) {
1302
1263
  return;
1303
1264
  // (with gensrc, the symbol itself would not make it into the CSN)
1304
1265
  if (node.kind === 'enum' || node._parent && node._parent.kind === 'extend')
1305
- Object.assign( csn, expression( v, true ) );
1266
+ Object.assign( csn, expression( v ) );
1306
1267
  }
1307
1268
 
1308
1269
 
@@ -1317,128 +1278,89 @@ function onCondition( cond, csn, node ) {
1317
1278
  }
1318
1279
 
1319
1280
  function condition( node ) {
1320
- const expr = expression( node );
1321
- // we do not set a hidden $parens on array - we could still do it if requested
1322
- return !expr.cast && !expr.func && expr.xpr || [ expr ];
1281
+ const expr = exprInternal( node, 'no' );
1282
+ return (Array.isArray( expr ))
1283
+ ? flattenenInternalXpr( expr )
1284
+ : !expr.cast && !expr.func && expr.xpr || [ expr ];
1285
+ }
1286
+
1287
+ function expression( node ) {
1288
+ const expr = exprInternal( node, 'no' );
1289
+ return (Array.isArray( expr ))
1290
+ ? { xpr: flattenenInternalXpr( expr ) }
1291
+ : expr;
1323
1292
  }
1324
1293
 
1325
- function expression( node, dollarExtra ) {
1326
- const dollarExtraNode = dollarExtra !== 'ignoreExtra' && node;
1294
+ function exprInternal( node, xprParens ) {
1327
1295
  if (typeof node === 'string')
1328
1296
  return node;
1329
1297
  if (!node) // make to-csn robst
1330
1298
  return {};
1331
1299
  if (node.scope === 'param') {
1332
1300
  if (node.path)
1333
- return extra( { ref: node.path.map( pathItem ), param: true }, dollarExtraNode );
1301
+ return extra( { ref: node.path.map( pathItem ), param: true }, node );
1334
1302
  return { ref: [ node.param.val ], param: true }; // CDL rule for runtimes
1335
1303
  }
1336
1304
  if (node.path) {
1337
1305
  const ref = node.path.map( pathItem );
1338
- if (node.path.$prefix)
1306
+ if (node.path.$prefix) // auto-corrected ORDER BY refs without table alias
1339
1307
  ref.unshift( node.path.$prefix );
1340
1308
  // we would need to consider node.global here if we introduce that
1341
- return extra( { ref }, dollarExtraNode );
1309
+ return extra( { ref }, node );
1342
1310
  }
1343
1311
  if (node.literal) {
1344
1312
  if (typeof node.val === node.literal || node.val === null)
1345
- return extra( { val: node.val }, dollarExtraNode );
1313
+ return extra( { val: node.val }, node );
1346
1314
  else if (node.literal === 'enum')
1347
- return extra( { '#': node.sym.id }, dollarExtraNode );
1315
+ return extra( { '#': node.sym.id }, node );
1348
1316
  else if (node.literal === 'token')
1349
1317
  return node.val; // * in COUNT(*)
1350
- return extra( { val: node.val, literal: node.literal }, dollarExtraNode );
1318
+ return extra( { val: node.val, literal: node.literal }, node );
1351
1319
  }
1352
1320
  if (node.func) { // TODO XSN: remove op: 'call', func is no path
1353
1321
  const call = { func: node.func.path[0].id };
1354
- if (node.args) { // no args from CSN input for CURRENT_DATE etc
1322
+ if (node.args) // no args from CSN input for CURRENT_DATE etc
1355
1323
  call.args = args( node.args );
1356
- const arg0 = call.args[0];
1357
- const { quantifier } = node.func.path[0];
1358
- if (arg0 && quantifier) {
1359
- if (typeof arg0 !== 'object' || !arg0.xpr)
1360
- call.args[0] = { xpr: [ quantifier.val, arg0 ] };
1361
- else
1362
- arg0.xpr.unshift( quantifier.val );
1363
- }
1364
- }
1365
1324
  if (node.suffix)
1366
- call.xpr = [].concat( ...node.suffix.map( xprArg ) );
1367
- return extra( call, dollarExtraNode );
1325
+ call.xpr = condition( { op: { val: 'ixpr' }, args: node.suffix } );
1326
+ // remark: node.suffix.map( expression ) would add $parens: 1 for xpr after "over"
1327
+ return extra( call, node );
1368
1328
  }
1369
1329
  if (node.query)
1370
1330
  return query( node.query, null, null, null, 1 );
1371
1331
  if (!node.op) // parse error
1372
1332
  return { xpr: [] };
1373
- else if (node.op.val === 'xpr')
1374
- // do not use xpr() for xpr, as it would flatten inner xpr's
1375
- return extra({ xpr: node.args.map( expression ) }, dollarExtraNode, 1 );
1376
- else if (node.op.val === 'cast')
1377
- return cast( expression( node.args[0] ), dollarExtraNode );
1378
- // from here on: CDL input (no $extra possible - but $parens)
1379
- else if (node.op.val !== ',')
1380
- return extra( { xpr: xpr( node ) }, dollarExtraNode, (dollarExtra === 'sub-xpr' ? 1 : 0) );
1381
- return (parensAsStrings)
1382
- ? { xpr: [ '(', ...xpr( node ), ')' ] }
1383
- // the inner parens belong to the tuple construct, i.e. won't count as parens
1384
- : extra( { list: node.args.map( expression ) }, dollarExtraNode, 0 );
1385
- }
1386
-
1387
- function xpr( node ) {
1388
- // if (!node.op) console.log(node)
1389
- const op = operators[node.op.val] || node.op.val.split(' ');
1390
- const exprs = node.args.map( xprArg );
1391
- if (op instanceof Function)
1392
- return op( exprs );
1393
- if (node.quantifier)
1394
- op.push( node.quantifier.val );
1395
- if (exprs.length < 2)
1396
- return [ ...op, ...exprs[0] || [] ];
1397
- return exprs[0].concat( ...exprs.slice(1).map( a => [ ...op, ...a ] ) );
1398
- }
1399
-
1400
- function xprArg( sub ) {
1401
- const realXpr = sub.op && sub.op.val === 'xpr';
1402
- const expr = expression( sub, 'sub-xpr' );
1403
- // `sort`/`nulls` will be attached to arguments of orderBy
1404
- // which might be either `path`s or `xpr`s
1405
- const sortAndNulls = [];
1406
- if (sub.sort)
1407
- sortAndNulls.push( sub.sort.val );
1408
- if (sub.nulls)
1409
- sortAndNulls.push( ...[ 'nulls', sub.nulls.val ] );
1410
- // return !sub.$parens && !expr.cast && !expr.func && expr.xpr || [ expr ];
1411
- // if parensAsStrings is gone
1412
- if (realXpr || expr.cast || expr.func || !expr.xpr || sub.$parens && !parensAsStrings)
1413
- return [ expr, ...sortAndNulls ];
1414
- else if (sub.$parens && sub.op.val !== ',')
1415
- return [ '(', ...expr.xpr, ')' ];
1416
-
1417
- expr.xpr.push( ...sortAndNulls );
1418
- return expr.xpr;
1419
- }
1420
-
1421
- function ternary( op1, op2 ) {
1422
- return function ternaryOp( exprs ) {
1423
- return (exprs[2])
1424
- ? [ ...exprs[0], ...op1, ...exprs[1], ...op2, ...exprs[2] ]
1425
- : [ ...exprs[0], ...op1, ...exprs[1] ];
1426
- };
1427
- }
1428
1333
 
1429
- function postfix( op ) {
1430
- return function postfixOp( exprs ) {
1431
- return [ ...exprs[0], ...op ];
1432
- };
1334
+ const { val } = node.op;
1335
+ switch (val) {
1336
+ case 'ixpr':
1337
+ case 'xpr':
1338
+ break;
1339
+ case 'cast':
1340
+ return cast( expression( node.args[0] ), node );
1341
+ case 'list':
1342
+ return extra( { list: node.args.map( expression ) }, node, 0 );
1343
+ default: { // '=', 'and', CSN v0 input: binary (n-ary) and unary prefix
1344
+ if (!node.args.length)
1345
+ return { xpr: [] };
1346
+ const nary = [];
1347
+ for (const item of node.args)
1348
+ nary.push( { val, literal: 'token' }, item );
1349
+ node = {
1350
+ op: { val: 'ixpr' },
1351
+ args: (nary.length > 2 ? nary.slice(1) : nary),
1352
+ $parens: node.$parens,
1353
+ };
1354
+ }
1355
+ }
1356
+ const rargs = node.args.map( exprInternal );
1357
+ if (val === 'xpr' || node.$parens)
1358
+ return extra( { xpr: flattenenInternalXpr( rargs ) }, node, (xprParens === 'no' ? 0 : 1) );
1359
+ return rargs.length === 1 ? rargs[0] : rargs;
1433
1360
  }
1434
1361
 
1435
- function binaryRightParen( op ) {
1436
- return ( exprs ) => {
1437
- const right = exprs[1].length === 1 ? exprs[1][0] : {};
1438
- return (right.xpr || right.list || !right.$parens)
1439
- ? [ ...exprs[0], ...op, ...exprs[1] ]
1440
- : [ ...exprs[0], ...op, { xpr: exprs[1] } ];
1441
- };
1362
+ function flattenenInternalXpr( array ) {
1363
+ return (structXpr) ? array : array.flat( Infinity );
1442
1364
  }
1443
1365
 
1444
1366
  function query( node, csn, xsn, _prop, expectedParens = 0 ) {
@@ -1534,7 +1456,7 @@ function addElementAsColumn( elem, cols ) {
1534
1456
  if (elem.$inferred === '*')
1535
1457
  return;
1536
1458
  // only list annotations here which are provided directly with definition
1537
- const col = (gensrcFlavor) ? annotationsAndDocComment( elem, false ) : {};
1459
+ const col = (gensrcFlavor) ? annotationsAndDocComment( elem ) : {};
1538
1460
  // with `client` flavor, assignments are available at the element
1539
1461
  const gensrcSaved = gensrcFlavor;
1540
1462
 
@@ -1542,7 +1464,7 @@ function addElementAsColumn( elem, cols ) {
1542
1464
  gensrcFlavor = gensrcFlavor || 'column';
1543
1465
  set( 'virtual', col, elem );
1544
1466
  set( 'key', col, elem );
1545
- const expr = expression( elem.value, true );
1467
+ const expr = expression( elem.value );
1546
1468
  Object.assign( col, (expr.cast ? { xpr: [ expr ] } : expr) );
1547
1469
  gensrcFlavor = gensrcSaved; // for not having annotations in inline etc
1548
1470
  if (elem.expand)
@@ -1578,7 +1500,7 @@ function orderBy( node ) {
1578
1500
  expr.sort = node.sort.val;
1579
1501
  if (node.nulls)
1580
1502
  expr.nulls = node.nulls.val;
1581
- return extra( expr, node ); // extra properties after sort/nulls
1503
+ return expr; // extra properties are before sort/nulls - who cares?
1582
1504
  }
1583
1505
 
1584
1506
  function extra( csn, node, expectedParens = 0 ) {
@@ -1643,19 +1565,18 @@ function compactQuery( q ) { // TODO: options
1643
1565
 
1644
1566
  function compactExpr( e ) { // TODO: options
1645
1567
  initModuleVars();
1646
- return e && expression( e, true );
1568
+ return e && expression( e );
1647
1569
  }
1648
1570
 
1649
1571
  /**
1650
1572
  * @param {CSN.Options} options
1651
1573
  */
1652
1574
  function initModuleVars( options = { csnFlavor: 'gensrc' } ) {
1653
- gensrcFlavor = options.parseCdl || options.csnFlavor === 'gensrc' ||
1654
- ( options.toCsn && options.toCsn.flavor === 'gensrc');
1655
- universalCsn = ( options.csnFlavor === 'universal' ||
1656
- ( options.toCsn && options.toCsn.flavor === 'universal') ) &&
1657
- isBetaEnabled( options, 'enableUniversalCsn' ) &&
1658
- !options.parseCdl;
1575
+ const flavor = options.csnFlavor || options.toCsn?.flavor;
1576
+ gensrcFlavor = options.parseCdl || flavor === 'gensrc';
1577
+ universalCsn = flavor === 'universal' &&
1578
+ isBetaEnabled( options, 'enableUniversalCsn' ) &&
1579
+ !options.parseCdl;
1659
1580
  strictMode = options.testMode;
1660
1581
  const proto = options.dictionaryPrototype;
1661
1582
  // eslint-disable-next-line no-nested-ternary
@@ -1663,7 +1584,7 @@ function initModuleVars( options = { csnFlavor: 'gensrc' } ) {
1663
1584
  ? proto
1664
1585
  : (proto) ? Object.prototype : null;
1665
1586
  withLocations = options.withLocations;
1666
- parensAsStrings = isDeprecatedEnabled( options, '_parensAsStrings' );
1587
+ structXpr = options.structXpr;
1667
1588
  projectionAsQuery = isDeprecatedEnabled( options, '_projectionAsQuery' );
1668
1589
  }
1669
1590
 
@@ -164,8 +164,11 @@ function parse( source, filename = '<undefined>.cds',
164
164
  if (options.docComment !== false) {
165
165
  for (const token of tokenStream.tokens) {
166
166
  if (token.type === parser.constructor.DocComment && !token.isUsed) {
167
- messageFunctions.info('syntax-ignoring-doc-comment', parser.tokenLocation(token), {},
168
- 'Ignoring doc comment as it does not belong to any artifact');
167
+ // TODO: think of 'syntax-unexpected-doc-comment'
168
+ messageFunctions.info( 'syntax-ignoring-doc-comment', parser.tokenLocation(token), {},
169
+ 'Ignoring doc comment as it is not written at a defined position' );
170
+ // this is also for position inside some artifact definition, i.e. previous text
171
+ // "does not belong to any artifact" might be confusing
169
172
  }
170
173
  }
171
174
  }
@@ -16,7 +16,7 @@ const {
16
16
  * @returns {string|null} Parsed contents or if the comment has an invalid format or
17
17
  * does not have any content, null is returned.
18
18
  */
19
- function parseDocComment(comment) {
19
+ function parseDocComment( comment ) {
20
20
  // Also return "null" for empty doc comments so that doc comment propagation
21
21
  // can be stopped.
22
22
  if (comment.length <= 5) // at least "/***/"
@@ -83,7 +83,7 @@ function parseDocComment(comment) {
83
83
  * @param {string[]} lines String split into lines.
84
84
  * @param {boolean} ignoreFirstLine Whether to ignore the first line for indentation counting.
85
85
  */
86
- function stripCommentIndentation(lines, ignoreFirstLine) {
86
+ function stripCommentIndentation( lines, ignoreFirstLine ) {
87
87
  const n = lines.length;
88
88
 
89
89
  const minIndent = lines.reduce((min, line, index) => {
@@ -114,7 +114,7 @@ function stripCommentIndentation(lines, ignoreFirstLine) {
114
114
  * @param {string} line
115
115
  * @returns {string} line without fence
116
116
  */
117
- function removeFence(line) {
117
+ function removeFence( line ) {
118
118
  return line.replace(/^\s*[*]\s?/, '');
119
119
  }
120
120
 
@@ -125,7 +125,7 @@ function removeFence(line) {
125
125
  * @param {string} line
126
126
  * @returns {string} Header without fence.
127
127
  */
128
- function removeHeaderFence(line) {
128
+ function removeHeaderFence( line ) {
129
129
  return line.replace(/^\/[*]{2,}\s?/, '');
130
130
  }
131
131
 
@@ -138,7 +138,7 @@ function removeHeaderFence(line) {
138
138
  * @param {string} line
139
139
  * @returns {string} header without fence
140
140
  */
141
- function removeFooterFence(line) {
141
+ function removeFooterFence( line ) {
142
142
  return line.replace(/\s*[*]+\/$/, '');
143
143
  }
144
144
 
@@ -148,7 +148,7 @@ function removeFooterFence(line) {
148
148
  *
149
149
  * @param {string[]} lines
150
150
  */
151
- function isFencedComment(lines) {
151
+ function isFencedComment( lines ) {
152
152
  const index = lines.findIndex((line, i) => {
153
153
  const exclude = (i === 0 || i === lines.length - 1);
154
154
  return !exclude && !(/^\s*[*]/.test(line));