@sap/cds-compiler 3.9.2 → 4.0.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 (96) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/README.md +0 -1
  3. package/bin/cdsc.js +11 -23
  4. package/bin/cdsse.js +3 -3
  5. package/doc/API.md +5 -0
  6. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  7. package/doc/CHANGELOG_BETA.md +17 -1
  8. package/doc/CHANGELOG_DEPRECATED.md +28 -0
  9. package/lib/api/.eslintrc.json +1 -1
  10. package/lib/api/main.js +26 -8
  11. package/lib/api/options.js +2 -0
  12. package/lib/base/error.js +2 -0
  13. package/lib/base/message-registry.js +144 -65
  14. package/lib/base/messages.js +213 -107
  15. package/lib/base/model.js +11 -11
  16. package/lib/checks/.eslintrc.json +1 -1
  17. package/lib/checks/annotationsOData.js +2 -2
  18. package/lib/checks/elements.js +1 -1
  19. package/lib/checks/enricher.js +26 -3
  20. package/lib/checks/onConditions.js +67 -12
  21. package/lib/checks/queryNoDbArtifacts.js +106 -105
  22. package/lib/checks/sql-snippets.js +2 -0
  23. package/lib/checks/types.js +12 -6
  24. package/lib/checks/validator.js +2 -2
  25. package/lib/compiler/assert-consistency.js +10 -8
  26. package/lib/compiler/builtins.js +8 -2
  27. package/lib/compiler/checks.js +52 -35
  28. package/lib/compiler/define.js +31 -26
  29. package/lib/compiler/extend.js +120 -65
  30. package/lib/compiler/finalize-parse-cdl.js +12 -43
  31. package/lib/compiler/generate.js +16 -5
  32. package/lib/compiler/index.js +8 -5
  33. package/lib/compiler/kick-start.js +4 -3
  34. package/lib/compiler/populate.js +96 -95
  35. package/lib/compiler/propagator.js +7 -8
  36. package/lib/compiler/resolve.js +377 -103
  37. package/lib/compiler/shared.js +794 -517
  38. package/lib/compiler/tweak-assocs.js +8 -6
  39. package/lib/compiler/utils.js +44 -0
  40. package/lib/edm/annotations/genericTranslation.js +24 -6
  41. package/lib/edm/csn2edm.js +47 -45
  42. package/lib/edm/edm.js +34 -31
  43. package/lib/edm/edmAnnoPreprocessor.js +0 -23
  44. package/lib/edm/edmInboundChecks.js +7 -2
  45. package/lib/edm/edmPreprocessor.js +18 -17
  46. package/lib/edm/edmUtils.js +8 -4
  47. package/lib/gen/Dictionary.json +18 -0
  48. package/lib/gen/language.checksum +1 -1
  49. package/lib/gen/language.interp +4 -2
  50. package/lib/gen/languageParser.js +5006 -4582
  51. package/lib/json/from-csn.js +159 -114
  52. package/lib/json/to-csn.js +60 -89
  53. package/lib/language/antlrParser.js +17 -13
  54. package/lib/language/docCommentParser.js +11 -1
  55. package/lib/language/genericAntlrParser.js +13 -10
  56. package/lib/language/language.g4 +168 -97
  57. package/lib/main.d.ts +128 -36
  58. package/lib/main.js +1 -1
  59. package/lib/model/csnRefs.js +24 -5
  60. package/lib/model/csnUtils.js +9 -8
  61. package/lib/model/revealInternalProperties.js +7 -12
  62. package/lib/modelCompare/compare.js +1 -1
  63. package/lib/modelCompare/utils/filter.js +40 -2
  64. package/lib/optionProcessor.js +0 -3
  65. package/lib/render/toCdl.js +247 -214
  66. package/lib/render/toHdbcds.js +197 -181
  67. package/lib/render/toSql.js +325 -289
  68. package/lib/render/utils/common.js +42 -4
  69. package/lib/render/utils/delta.js +1 -1
  70. package/lib/render/utils/sql.js +3 -3
  71. package/lib/transform/braceExpression.js +2 -2
  72. package/lib/transform/db/.eslintrc.json +1 -1
  73. package/lib/transform/db/applyTransformations.js +3 -3
  74. package/lib/transform/db/associations.js +24 -12
  75. package/lib/transform/db/expansion.js +17 -18
  76. package/lib/transform/db/flattening.js +17 -21
  77. package/lib/transform/db/rewriteCalculatedElements.js +171 -64
  78. package/lib/transform/db/views.js +3 -4
  79. package/lib/transform/draft/db.js +21 -12
  80. package/lib/transform/draft/odata.js +4 -0
  81. package/lib/transform/forOdataNew.js +11 -10
  82. package/lib/transform/forRelationalDB.js +12 -7
  83. package/lib/transform/localized.js +5 -3
  84. package/lib/transform/odata/toFinalBaseType.js +5 -5
  85. package/lib/transform/odata/typesExposure.js +3 -3
  86. package/lib/transform/parseExpr.js +3 -0
  87. package/lib/transform/transformUtilsNew.js +43 -23
  88. package/lib/transform/translateAssocsToJoins.js +7 -6
  89. package/lib/transform/universalCsn/.eslintrc.json +1 -1
  90. package/lib/transform/universalCsn/coreComputed.js +7 -5
  91. package/lib/transform/universalCsn/universalCsnEnricher.js +12 -12
  92. package/lib/utils/file.js +3 -3
  93. package/lib/utils/moduleResolve.js +1 -1
  94. package/package.json +2 -2
  95. package/share/messages/{duplicate-autoexposed.md → def-duplicate-autoexposed.md} +5 -1
  96. package/share/messages/message-explanations.json +1 -1
@@ -92,6 +92,8 @@ function csnToCdl( csn, options ) {
92
92
  }
93
93
 
94
94
  timetrace.stop('CDL rendering');
95
+
96
+ msg.throwWithError();
95
97
  return cdlResult;
96
98
 
97
99
  /**
@@ -104,7 +106,7 @@ function csnToCdl( csn, options ) {
104
106
  let result = '';
105
107
  const env = createEnv();
106
108
  forEachDefinition(csn, (artifact, artifactName) => {
107
- const sourceStr = renderArtifact(artifactName, artifact, env);
109
+ const sourceStr = renderDefinition(artifactName, artifact, env);
108
110
  if (sourceStr !== '')
109
111
  result += `${sourceStr}\n`;
110
112
  });
@@ -127,7 +129,7 @@ function csnToCdl( csn, options ) {
127
129
  if (!anno._ignore) {
128
130
  // This environment is passed down the call hierarchy, for dealing with
129
131
  // indentation and name resolution issues
130
- const env = envNewPath(createEnv(), [ 'vocabularies', name ]);
132
+ const env = createEnv({ path: [ 'vocabularies', name ] });
131
133
  const sourceStr = renderTypeOrAnnotation(name, anno, env, 'annotation');
132
134
  result += `${sourceStr}\n`;
133
135
  }
@@ -145,8 +147,8 @@ function csnToCdl( csn, options ) {
145
147
  */
146
148
  function renderExtensions( extensions, env ) {
147
149
  if (!env.path)
148
- env = envNewPath(env, [ 'extensions' ]);
149
- return extensions.map((ext, index) => renderExtension(ext, envAddPath(env, [ index ]))).join('\n');
150
+ env = env.cloneWith({ path: [ 'extensions' ] });
151
+ return extensions.map((ext, index) => renderExtension(ext, env.withSubPath([ index ]))).join('\n');
150
152
  }
151
153
 
152
154
  /**
@@ -181,7 +183,7 @@ function csnToCdl( csn, options ) {
181
183
  if (ext.includes && ext.includes.length > 0) {
182
184
  // Includes can't be combined with anything in braces {}.
183
185
  const affix = isElementExtend ? 'element ' : '';
184
- const includes = ext.includes.map(inc => renderDefinitionReference(inc, env)).join(', ');
186
+ const includes = ext.includes.map((inc, i) => renderDefinitionReference(inc, env.withSubPath([ 'includes', i ]))).join(', ');
185
187
  result += `${env.indent}extend ${affix}${extName} with ${includes};\n`;
186
188
  return result;
187
189
  }
@@ -210,18 +212,17 @@ function csnToCdl( csn, options ) {
210
212
  result += `${env.indent}extend ${extName} with ${getExtendPostfixVariant(ext)}{\n`;
211
213
 
212
214
  if (ext.columns)
213
- result += renderViewColumns(ext.columns, increaseIndent(env));
215
+ result += renderViewColumns(ext, env.withIncreasedIndent());
214
216
 
215
217
  else if (ext.elements || ext.enum)
216
218
  result += renderExtendStatementElements(ext, env);
217
219
 
218
-
219
220
  // Not part of if/else cascade, because it may be in postfix notation.
220
221
  if (ext.actions) {
221
- const childEnv = increaseIndent(env);
222
+ const childEnv = env.withIncreasedIndent();
222
223
  let actions = '';
223
224
  forEach(ext.actions, (actionName, action) => {
224
- actions += renderActionOrFunction(actionName, action, childEnv);
225
+ actions += renderActionOrFunction(actionName, action, childEnv.withSubPath([ 'actions', actionName ]));
225
226
  });
226
227
  if (!usePrefixNotation)
227
228
  result += actions;
@@ -281,13 +282,15 @@ function csnToCdl( csn, options ) {
281
282
  */
282
283
  function renderExtendStatementElements( ext, env ) {
283
284
  let result = '';
284
- forEach(ext.elements || ext.enum || {}, (elemName, element) => {
285
+ const prop = ext.elements ? 'elements' : 'enum';
286
+ forEach(ext[prop] || {}, (elemName, element) => {
287
+ const childEnv = env.withIncreasedIndent().withSubPath([ 'elements', elemName ]);
285
288
  if (element.kind === 'extend')
286
- result += renderExtendStatement(elemName, element, increaseIndent(env));
289
+ result += renderExtendStatement(elemName, element, childEnv);
287
290
  else
288
291
  // As soon as we are inside an element, nested `extend` are not possible,
289
292
  // since we can't extend an existing element of a new one.
290
- result += renderElement(elemName, element, increaseIndent(env));
293
+ result += renderElement(elemName, element, childEnv.withSubPath([ prop, elemName ]));
291
294
  });
292
295
  return result;
293
296
  }
@@ -307,42 +310,36 @@ function csnToCdl( csn, options ) {
307
310
  result += `${env.indent}annotate ${renderArtifactName(ext.annotate, env)}`;
308
311
 
309
312
  if (ext.params)
310
- result += renderAnnotateParamsInParentheses(ext.params, env);
313
+ result += renderAnnotateParamsInParentheses(ext, env);
311
314
 
312
315
  // Element extensions and annotations (possibly nested)
316
+ // TODO: Deduplicate coding, see renderAnnotateStatementElements()
313
317
  if (ext.elements)
314
- result += renderAnnotateStatementElements(ext.elements, env);
315
- if (ext.enum)
316
- result += renderAnnotateStatementElements(ext.enum, env);
317
-
318
- // Returns annotations
319
- if (ext.returns) {
320
- const childEnv = increaseIndent(env);
321
- result += ` returns${renderAnnotateStatementElements(ext.returns.elements, childEnv)}`;
322
- }
318
+ result += ` ${renderAnnotateStatementElements(ext.elements, env.withSubPath([ 'elements' ]))}`;
319
+ else if (ext.enum)
320
+ result += ` ${renderAnnotateStatementElements(ext.enum, env.withSubPath([ 'enum' ]))}`;
321
+ else if (ext.returns)
322
+ result += renderAnnotateReturns(ext, env);
323
323
 
324
- // Action annotations
325
- if (ext.actions) {
324
+ if (ext.actions) { // Bound action annotations
326
325
  result += ' actions {\n';
327
- const childEnv = increaseIndent(env);
328
326
  for (const name in ext.actions) {
327
+ const childEnv = env.withIncreasedIndent().withSubPath([ 'actions', name ]);
329
328
  const action = ext.actions[name];
330
- result += renderAnnotationAssignmentsAndDocComment(action, childEnv) + childEnv.indent + quoteNonIdentifierOrKeyword(name, env);
329
+ result += renderAnnotationAssignmentsAndDocComment(action, childEnv) + childEnv.indent + quoteNonIdentifierOrKeyword(name, childEnv);
331
330
  // Action parameter annotations
332
331
  if (action.params)
333
- result += renderAnnotateParamsInParentheses(action.params, childEnv);
334
-
335
- // Annotations on action returns
336
- if (action.returns && action.returns.elements) {
337
- const grandChildEnv = increaseIndent(childEnv);
338
- result += ` returns${renderAnnotateStatementElements(action.returns.elements, grandChildEnv)}`;
339
- }
332
+ result += renderAnnotateParamsInParentheses(action, childEnv);
333
+ if (action.returns)
334
+ result += renderAnnotateReturns(action, childEnv);
340
335
 
336
+ result = removeTrailingNewline(result);
341
337
  result += ';\n';
342
338
  }
343
339
  result += `${env.indent}}`;
344
340
  }
345
341
 
342
+ result = removeTrailingNewline(result);
346
343
  result += ';\n';
347
344
  return result;
348
345
  }
@@ -357,16 +354,16 @@ function csnToCdl( csn, options ) {
357
354
  * @return {string}
358
355
  */
359
356
  function renderAnnotateStatementElements( elements, env ) {
360
- let result = ' {\n';
361
- const childEnv = increaseIndent(env);
357
+ let result = '{\n';
362
358
  for (const name in elements) {
359
+ const childEnv = env.withIncreasedIndent().withSubPath([ name ]);
363
360
  const elem = elements[name];
364
361
  result += renderAnnotationAssignmentsAndDocComment(elem, childEnv);
365
362
  result += childEnv.indent + quoteNonIdentifierOrKeyword(name, env);
366
363
  if (elem.elements)
367
- result += renderAnnotateStatementElements(elem.elements, childEnv);
368
- if (elem.enum)
369
- result += renderAnnotateStatementElements(elem.enum, childEnv);
364
+ result += ` ${renderAnnotateStatementElements(elem.elements, childEnv.withSubPath([ 'elements' ]))}`;
365
+ else if (elem.enum)
366
+ result += ` ${renderAnnotateStatementElements(elem.enum, childEnv.withSubPath([ 'enum' ]))}`;
370
367
 
371
368
  result += ';\n';
372
369
  }
@@ -374,19 +371,45 @@ function csnToCdl( csn, options ) {
374
371
  return result;
375
372
  }
376
373
 
374
+ /**
375
+ * Renders the `returns` part of an `annotate` statement for (bound) actions.
376
+ * `ext` must be an object with a `returns` property.
377
+ *
378
+ * @param {CSN.Extension} ext
379
+ * @param {CdlRenderEnvironment} env
380
+ * @return {string}
381
+ */
382
+ function renderAnnotateReturns( ext, env ) {
383
+ env = env.withSubPath([ 'returns', 'elements' ]);
384
+ let result = ' returns';
385
+
386
+ const returnAnnos = renderAnnotationAssignmentsAndDocComment(ext.returns, env.withIncreasedIndent());
387
+ if (returnAnnos)
388
+ result += `\n${returnAnnos}`;
389
+
390
+ if (ext.returns.elements) {
391
+ // Annotations are on separate lines: Have it aligned nicely
392
+ result += returnAnnos ? `${env.indent}` : ' ';
393
+ result += renderAnnotateStatementElements(ext.returns.elements, env);
394
+ }
395
+ return result;
396
+ }
397
+
377
398
  /**
378
399
  * Render a parameter list for `annotate` statements, in parentheses `()`.
379
400
  *
380
- * @param {object} params
401
+ * @param {CSN.Artifact} art
381
402
  * @param {CdlRenderEnvironment} env
382
403
  * @return {string}
383
404
  */
384
- function renderAnnotateParamsInParentheses( params, env ) {
385
- const childEnv = increaseIndent(env);
405
+ function renderAnnotateParamsInParentheses( art, env ) {
406
+ const childEnv = env.withIncreasedIndent();
386
407
  let result = '(\n';
387
408
  const paramAnnotations = [];
388
- forEach(params, (paramName, param) => {
389
- paramAnnotations.push( renderAnnotationAssignmentsAndDocComment(param, childEnv) + childEnv.indent + quoteNonIdentifierOrKeyword(paramName, env) );
409
+ forEach(art.params, (paramName, param) => {
410
+ const annos = renderAnnotationAssignmentsAndDocComment(param, childEnv);
411
+ const name = quoteNonIdentifierOrKeyword(paramName, env.withSubPath([ 'params', paramName ]));
412
+ paramAnnotations.push( annos + childEnv.indent + name );
390
413
  });
391
414
  result += `${paramAnnotations.join(',\n')}\n${env.indent})`;
392
415
  return result;
@@ -399,11 +422,11 @@ function csnToCdl( csn, options ) {
399
422
  * @param {CSN.Artifact} art
400
423
  * @param {CdlRenderEnvironment} env
401
424
  */
402
- function renderArtifact( artifactName, art, env ) {
403
- env = envNewPath(env, [ 'definitions', artifactName ]);
404
- env.artifactName = artifactName;
425
+ function renderDefinition( artifactName, art, env ) {
426
+ env = env.cloneWith({ path: [ 'definitions', artifactName ] });
405
427
 
406
- switch (art.kind) {
428
+ const kind = art.kind || 'type'; // the default kind is "type".
429
+ switch (kind) {
407
430
  case 'entity':
408
431
  if (art.query || art.projection)
409
432
  return renderView(artifactName, art, env);
@@ -427,6 +450,7 @@ function csnToCdl( csn, options ) {
427
450
  return renderEvent(artifactName, art, env);
428
451
 
429
452
  default:
453
+ // TODO: Make it a error message.
430
454
  throw new ModelError(`to.cdl: Unknown artifact kind: ${art.kind}`);
431
455
  }
432
456
  }
@@ -444,8 +468,9 @@ function csnToCdl( csn, options ) {
444
468
  result += renderIncludes(art.includes, env);
445
469
  if (art.query || art.projection) {
446
470
  result += ' : ';
447
- result += renderQuery(getNormalizedQuery(art).query, true, 'projection', env,
448
- [ 'definitions', artifactName, 'query' ]);
471
+ // events (should) only support "projections"
472
+ result += renderQuery(getNormalizedQuery(art).query, true, 'projection',
473
+ env.withSubPath([ art.projection ? 'projection' : 'query' ]));
449
474
  result += ';\n';
450
475
  }
451
476
  else if (art.type) {
@@ -527,13 +552,11 @@ function csnToCdl( csn, options ) {
527
552
  */
528
553
  function renderElements( artifact, env ) {
529
554
  let elements = '';
530
- const childEnv = increaseIndent(env);
555
+ const childEnv = env.withIncreasedIndent();
531
556
  for (const name in artifact.elements)
532
- elements += renderElement(name, artifact.elements[name], childEnv);
557
+ elements += renderElement(name, artifact.elements[name], childEnv.withSubPath([ 'elements', name ]));
533
558
 
534
- if (elements === '')
535
- return '{ }';
536
- return `{\n${elements}${env.indent}}`;
559
+ return (elements === '') ? '{ }' : `{\n${elements}${env.indent}}`;
537
560
  }
538
561
 
539
562
  /**
@@ -546,7 +569,6 @@ function csnToCdl( csn, options ) {
546
569
  * @param {CdlRenderEnvironment} env
547
570
  */
548
571
  function renderElement( elementName, element, env ) {
549
- env = envAddPath(env, [ 'elements', elementName ]);
550
572
  let result = renderAnnotationAssignmentsAndDocComment(element, env);
551
573
  result += env.indent;
552
574
  result += element.virtual ? 'virtual ' : '';
@@ -569,9 +591,9 @@ function csnToCdl( csn, options ) {
569
591
  if (element.value !== undefined) { // calculated element // @ts-ignore
570
592
  result += ' = ';
571
593
  if (element.value.xpr && xprContainsCondition(element.value.xpr))
572
- result += exprRenderer.renderSubExpr(element.value, env);
594
+ result += exprRenderer.renderSubExpr(element.value, env.withSubPath([ 'value' ]));
573
595
  else
574
- result += exprRenderer.renderExpr(element.value, env);
596
+ result += exprRenderer.renderExpr(element.value, env.withSubPath([ 'value' ]));
575
597
  if (element.value.stored === true)
576
598
  result += ' stored';
577
599
  }
@@ -589,13 +611,12 @@ function csnToCdl( csn, options ) {
589
611
  * however, in client CSN, annotations are not part of the column and in parseCdl CSN,
590
612
  * no `elements` exist.
591
613
  *
592
- * @param {string} artifactName
593
614
  * @param {CSN.Artifact} art
594
615
  * @param {CdlRenderEnvironment} env
595
616
  * @return {string}
596
617
  */
597
- function renderQueryElementAndEnumAnnotations( artifactName, art, env ) {
598
- const annotate = collectAnnotationsOfElementsAndEnum(art, createEnv({ artifactName, path: env.path }));
618
+ function renderQueryElementAndEnumAnnotations( art, env ) {
619
+ const annotate = collectAnnotationsOfElementsAndEnum(art, env);
599
620
  if (annotate)
600
621
  return renderExtensions([ annotate ], env);
601
622
  return '';
@@ -612,7 +633,7 @@ function csnToCdl( csn, options ) {
612
633
  function collectAnnotationsOfElementsAndEnum( artifact, env ) {
613
634
  // Array, which may be annotated as well.
614
635
  if (artifact.items) {
615
- env = envAddPath(env, [ 'items' ]);
636
+ env = env.withSubPath([ 'items' ]);
616
637
  artifact = artifact.items;
617
638
  }
618
639
 
@@ -699,7 +720,7 @@ function csnToCdl( csn, options ) {
699
720
  function renderViewSource( source, env ) {
700
721
  // Sub-SELECT
701
722
  if (source.SELECT || source.SET) {
702
- const subEnv = increaseIndent(env);
723
+ const subEnv = env.withIncreasedIndent();
703
724
  let result = `(\n${subEnv.indent}${renderQuery(source, false, 'view', subEnv)}\n${env.indent})`;
704
725
  if (source.as)
705
726
  result += renderAlias(source.as, env);
@@ -709,13 +730,13 @@ function csnToCdl( csn, options ) {
709
730
  // JOIN
710
731
  else if (source.join) {
711
732
  // One join operation, possibly with ON-condition
712
- let result = `(${renderViewSource(source.args[0], env)}`;
733
+ let result = `(${renderViewSource(source.args[0], env.withSubPath([ 'args', 0 ]))}`;
713
734
  for (let i = 1; i < source.args.length; i++) {
714
735
  result += ` ${source.join} `;
715
736
  result += renderJoinCardinality(source.cardinality);
716
- result += `join ${renderViewSource(source.args[i], env)}`;
737
+ result += `join ${renderViewSource(source.args[i], env.withSubPath([ 'args', i ]))}`;
717
738
  if (source.on)
718
- result += ` on ${exprRenderer.renderExpr(source.on, env)}`;
739
+ result += ` on ${exprRenderer.renderExpr(source.on, env.withSubPath([ 'on' ]))}`;
719
740
  }
720
741
  result += ')';
721
742
  return result;
@@ -762,12 +783,12 @@ function csnToCdl( csn, options ) {
762
783
 
763
784
  // Even the first step might have parameters and/or a filter
764
785
  if (path.ref[0].args)
765
- result += `(${renderArguments(path.ref[0], ':', env)})`;
786
+ result += `(${renderArguments(path.ref[0], ':', env.withSubPath([ 'ref', 0 ]))})`;
766
787
 
767
788
  if (path.ref[0].where) {
768
789
  // TODO: Unify with other filter rendering
769
790
  const cardinality = path.ref[0].cardinality ? (`${path.ref[0].cardinality.max}: `) : '';
770
- const expr = exprRenderer.renderExpr(path.ref[0].where, env);
791
+ const expr = exprRenderer.renderExpr(path.ref[0].where, env.withSubPath([ 'ref', 0, 'where' ]));
771
792
  if (expr.endsWith(']')) // for cases such as [… ![id] ]
772
793
  result += `[ ${cardinality}${expr} ]`;
773
794
  else
@@ -804,14 +825,13 @@ function csnToCdl( csn, options ) {
804
825
  /**
805
826
  * Render the given columns.
806
827
  *
807
- * @param {any[]} columns
828
+ * @param {CSN.Artifact} art
808
829
  * @param {object} elements
809
830
  * @param {CdlRenderEnvironment} env
810
831
  * @return {string}
811
832
  */
812
- function renderViewColumns( columns, env, elements = Object.create(null) ) {
813
- const result = columns.map(col => renderViewColumn(col, env, findElement(elements, col)))
814
- .filter(s => s !== '')
833
+ function renderViewColumns( art, env, elements = Object.create(null) ) {
834
+ const result = art.columns.map((col, i) => renderViewColumn(col, env.withSubPath([ 'columns', i ]), findElement(elements, col)))
815
835
  .join(',\n');
816
836
  return `${result}\n`;
817
837
  }
@@ -859,9 +879,9 @@ function csnToCdl( csn, options ) {
859
879
  if (col.cast) {
860
880
  // Special case: Explicit association type is actually a redirect
861
881
  if (col.cast.target && !col.cast.type)
862
- result += ` : ${renderRedirectedTo(col.cast, env)}`;
882
+ result += ` : ${renderRedirectedTo(col.cast, env.withSubPath([ 'cast' ]))}`;
863
883
  else
864
- result += ` : ${renderTypeReferenceAndProps(col.cast, env, { typeRefOnly: true, noAnnoCollect: true })}`;
884
+ result += ` : ${renderTypeReferenceAndProps(col.cast, env.withSubPath([ 'cast' ]), { typeRefOnly: true, noAnnoCollect: true })}`;
865
885
  }
866
886
  return result;
867
887
  }
@@ -885,9 +905,9 @@ function csnToCdl( csn, options ) {
885
905
  // We found a leaf - no further drilling
886
906
  if (!obj.inline && !obj.expand) {
887
907
  if (obj.cast && obj.cast.type)
888
- result += ` : ${renderTypeReferenceAndProps(obj.cast, createEnv(), { noAnnoCollect: true })}`;
908
+ result += ` : ${renderTypeReferenceAndProps(obj.cast, env.withSubPath([ 'cast' ]), { noAnnoCollect: true })}`;
889
909
  else if (obj.cast && obj.cast.target) // test tbd
890
- result += ` : ${renderRedirectedTo(obj.cast, env)}`;
910
+ result += ` : ${renderRedirectedTo(obj.cast, env.withSubPath([ 'cast' ]))}`;
891
911
  return result;
892
912
  }
893
913
 
@@ -897,7 +917,7 @@ function csnToCdl( csn, options ) {
897
917
  result += result !== '' ? ' {\n' : '{\n';
898
918
 
899
919
  // Drill down and render children of the expand/inline
900
- const childEnv = increaseIndent(env);
920
+ const childEnv = env.withIncreasedIndent();
901
921
  const expandInline = obj.expand || obj.inline;
902
922
  result += expandInline //
903
923
  .map(elm => renderAnnotationAssignmentsAndDocComment(elm, childEnv) + childEnv.indent + renderInlineExpand(elm, childEnv))
@@ -929,8 +949,8 @@ function csnToCdl( csn, options ) {
929
949
  return `\n${env.indent}/** */\n`;
930
950
 
931
951
  let { doc } = obj;
932
- if (/[^\\][*]\//.test(doc))
933
- doc = doc.replace(/([^\\])[*]\//g, '$1*\\/');
952
+ if (/[*]\//.test(doc)) // only escape sequence allowed in CDL for doc comments
953
+ doc = doc.replace(/[*]\//g, '*\\/');
934
954
 
935
955
  // Smaller comment for single-line comments. If the comment starts or ends with whitespace
936
956
  // we must use a block comment, or it will be lost when compiling the source again.
@@ -953,11 +973,11 @@ function csnToCdl( csn, options ) {
953
973
  if (art.params)
954
974
  result += renderParameters(art, env);
955
975
  result += ' as ';
956
- result += renderQuery(getNormalizedQuery(art).query, true, syntax, env, [ 'definitions', artifactName, 'query' ], art.elements);
976
+ result += renderQuery(getNormalizedQuery(art).query, true, syntax, env.withSubPath([ art.projection ? 'projection' : 'query' ]), art.elements);
957
977
  if (art.actions) // Views/Projections also allow actions. Just the VIEW keyword variant did not.
958
978
  result += renderActionsAndFunctions(art, env);
959
979
  result += ';\n';
960
- result += renderQueryElementAndEnumAnnotations(artifactName, art, env);
980
+ result += renderQueryElementAndEnumAnnotations(art, env);
961
981
  if (art.includes)
962
982
  result += renderExtension({ extend: artifactName, includes: art.includes }, env);
963
983
  return result;
@@ -973,10 +993,9 @@ function csnToCdl( csn, options ) {
973
993
  * @param {boolean} isLeadingQuery
974
994
  * @param {string} syntax The query syntax, either "projection", "entity" or "view"
975
995
  * @param {CdlRenderEnvironment} env
976
- * @param {CSN.Path} [path=[]]
977
996
  * @param {object} [elements]
978
997
  */
979
- function renderQuery( query, isLeadingQuery, syntax, env, path = [], elements = query.elements || Object.create(null) ) {
998
+ function renderQuery( query, isLeadingQuery, syntax, env, elements = query.elements || Object.create(null) ) {
980
999
  if (query.SET) {
981
1000
  // Set operator, such as UNION, INTERSECT, or EXCEPT...
982
1001
  return renderQuerySet();
@@ -988,16 +1007,16 @@ function csnToCdl( csn, options ) {
988
1007
 
989
1008
  let result = '';
990
1009
  const select = query.SELECT;
991
- const childEnv = increaseIndent(env);
1010
+ const childEnv = env.withIncreasedIndent();
992
1011
 
993
1012
  // If not a projection, must be view/entity.
994
1013
  result += (syntax === 'projection') ? 'projection on ' : 'select from ';
995
- result += renderViewSource(select.from, env);
1014
+ result += renderViewSource(select.from, env.withSubPath([ 'from' ]));
996
1015
 
997
1016
  if (select.mixin) {
998
1017
  let elems = '';
999
1018
  forEach(select.mixin, (name, mixin) => {
1000
- elems += renderElement(name, mixin, childEnv);
1019
+ elems += renderElement(name, mixin, childEnv.withSubPath([ 'mixin', name ]));
1001
1020
  });
1002
1021
  if (elems) {
1003
1022
  result += ' mixin {\n';
@@ -1008,7 +1027,7 @@ function csnToCdl( csn, options ) {
1008
1027
  result += select.distinct ? ' distinct' : '';
1009
1028
  if (select.columns) {
1010
1029
  result += ' {\n';
1011
- result += renderViewColumns(select.columns, increaseIndent(env), elements);
1030
+ result += renderViewColumns(select, env.withIncreasedIndent(), elements);
1012
1031
  result += `${env.indent}}`;
1013
1032
  }
1014
1033
 
@@ -1022,19 +1041,19 @@ function csnToCdl( csn, options ) {
1022
1041
  result += renderActionsAndFunctions(query, env);
1023
1042
 
1024
1043
  if (select.where)
1025
- result += `${continueIndent(result, env)}where ${exprRenderer.renderExpr(select.where, env)}`;
1044
+ result += `${continueIndent(result, env)}where ${exprRenderer.renderExpr(select.where, env.withSubPath([ 'where' ]))}`;
1026
1045
 
1027
1046
  if (select.groupBy)
1028
- result += `${continueIndent(result, env)}group by ${select.groupBy.map(expr => exprRenderer.renderExpr(expr, env)).join(', ')}`;
1047
+ result += `${continueIndent(result, env)}group by ${select.groupBy.map((expr, i) => exprRenderer.renderExpr(expr, env.withSubPath([ 'groupBy', i ]))).join(', ')}`;
1029
1048
 
1030
1049
  if (select.having)
1031
- result += `${continueIndent(result, env)}having ${exprRenderer.renderExpr(select.having, env)}`;
1050
+ result += `${continueIndent(result, env)}having ${exprRenderer.renderExpr(select.having, env.withSubPath([ 'having' ]))}`;
1032
1051
 
1033
1052
  if (select.orderBy)
1034
- result += `${continueIndent(result, env)}order by ${select.orderBy.map(entry => renderOrderByEntry(entry, env)).join(', ')}`;
1053
+ result += `${continueIndent(result, env)}order by ${select.orderBy.map((entry, i) => renderOrderByEntry(entry, env.withSubPath([ 'orderBy', i ]))).join(', ')}`;
1035
1054
 
1036
1055
  if (select.limit)
1037
- result += `${continueIndent(result, env)}${renderLimit(select.limit, env)}`;
1056
+ result += `${continueIndent(result, env)}${renderLimit(select.limit, env.withSubPath([ 'limit' ]))}`;
1038
1057
 
1039
1058
  return result;
1040
1059
 
@@ -1051,26 +1070,7 @@ function csnToCdl( csn, options ) {
1051
1070
  return ' ';
1052
1071
  }
1053
1072
  // Otherwise, start new line and indent normally
1054
- return `\n${increaseIndent(indentEnv).indent}`;
1055
- }
1056
-
1057
- /**
1058
- * Render a query's LIMIT clause, which may also have OFFSET.
1059
- *
1060
- * @param {CSN.QueryLimit} limit
1061
- * @param {CdlRenderEnvironment} limitEnv
1062
- * @return {string}
1063
- */
1064
- function renderLimit( limit, limitEnv ) {
1065
- let limitStr = '';
1066
- if (limit.rows !== undefined)
1067
- limitStr += `limit ${exprRenderer.renderExpr(limit.rows, limitEnv)}`;
1068
-
1069
- if (limit.offset !== undefined) {
1070
- const offsetIndent = (limitStr === '') ? '' : `\n${increaseIndent(limitEnv).indent}`;
1071
- limitStr += `${offsetIndent}offset ${exprRenderer.renderExpr(limit.offset, limitEnv)}`;
1072
- }
1073
- return limitStr;
1073
+ return `\n${indentEnv.withIncreasedIndent().indent}`;
1074
1074
  }
1075
1075
 
1076
1076
  /**
@@ -1081,7 +1081,8 @@ function csnToCdl( csn, options ) {
1081
1081
  function renderQuerySet() {
1082
1082
  const subQueries = query.SET.args.map((arg, i) => {
1083
1083
  // First arg may be leading query
1084
- const subQuery = renderQuery(arg, isLeadingQuery && (i === 0), 'view', env, path.concat([ 'SET', 'args', i ]), elements);
1084
+ const subEnv = env.withSubPath([ 'SET', 'args', i ]);
1085
+ const subQuery = renderQuery(arg, isLeadingQuery && (i === 0), 'view', subEnv, elements);
1085
1086
  return `(${subQuery})`;
1086
1087
  });
1087
1088
 
@@ -1089,10 +1090,10 @@ function csnToCdl( csn, options ) {
1089
1090
  // Set operation may also have an ORDER BY and LIMIT/OFFSET (in contrast to the ones belonging to
1090
1091
  // each SELECT)
1091
1092
  if (query.SET.orderBy)
1092
- setResult += `${continueIndent(setResult, env)}order by ${query.SET.orderBy.map(entry => renderOrderByEntry(entry, env)).join(', ')}`;
1093
+ setResult += `${continueIndent(setResult, env)}order by ${query.SET.orderBy.map((entry, i) => renderOrderByEntry(entry, env.withSubPath([ 'SET', 'orderBy', i ]))).join(', ')}`;
1093
1094
 
1094
1095
  if (query.SET.limit)
1095
- setResult += `${continueIndent(setResult, env)}${renderLimit(query.SET.limit, env)}`;
1096
+ setResult += `${continueIndent(setResult, env)}${renderLimit(query.SET.limit, env.withSubPath([ 'SET', 'limit' ]))}`;
1096
1097
  return setResult;
1097
1098
  }
1098
1099
  }
@@ -1116,6 +1117,25 @@ function csnToCdl( csn, options ) {
1116
1117
  return result;
1117
1118
  }
1118
1119
 
1120
+ /**
1121
+ * Render a query's LIMIT clause, which may also have OFFSET.
1122
+ *
1123
+ * @param {CSN.QueryLimit} limit
1124
+ * @param {CdlRenderEnvironment} limitEnv
1125
+ * @return {string}
1126
+ */
1127
+ function renderLimit( limit, limitEnv ) {
1128
+ let limitStr = '';
1129
+ if (limit.rows !== undefined)
1130
+ limitStr += `limit ${exprRenderer.renderExpr(limit.rows, limitEnv.withSubPath([ 'rows' ]))}`;
1131
+
1132
+ if (limit.offset !== undefined) {
1133
+ const offsetIndent = (limitStr === '') ? '' : `\n${limitEnv.withIncreasedIndent().indent}`;
1134
+ limitStr += `${offsetIndent}offset ${exprRenderer.renderExpr(limit.offset, limitEnv.withSubPath([ 'offset' ]))}`;
1135
+ }
1136
+ return limitStr;
1137
+ }
1138
+
1119
1139
  /**
1120
1140
  * Render an entity's actions and functions (if any)
1121
1141
  * (expect an entity with trailing '}' or an 'extend' statement ending with 'with'
@@ -1128,9 +1148,9 @@ function csnToCdl( csn, options ) {
1128
1148
  */
1129
1149
  function renderActionsAndFunctions( art, env ) {
1130
1150
  let result = '';
1131
- const childEnv = increaseIndent(env);
1151
+ const childEnv = env.withIncreasedIndent();
1132
1152
  for (const name in art.actions)
1133
- result += renderActionOrFunction(name, art.actions[name], envAddPath(childEnv, [ 'actions', name ]));
1153
+ result += renderActionOrFunction(name, art.actions[name], childEnv.withSubPath([ 'actions', name ]));
1134
1154
 
1135
1155
  // Even if we have seen actions/functions, they might all have been ignored
1136
1156
  if (result !== '')
@@ -1152,8 +1172,12 @@ function csnToCdl( csn, options ) {
1152
1172
  result += ` ${renderArtifactName(actionName, env)}`;
1153
1173
  result += renderParameters(act, env);
1154
1174
  if (act.returns) {
1155
- const actEnv = envAddPath(env, [ 'returns' ]);
1156
- result += ` returns ${renderTypeReferenceAndProps(act.returns, actEnv)}`;
1175
+ let actEnv = env.withSubPath([ 'returns' ]);
1176
+ const annos = renderAnnotationAssignmentsAndDocComment(act.returns, actEnv.withIncreasedIndent());
1177
+ if (annos) // if `returns` has annotations, increase indent for nicer aligned output
1178
+ actEnv = actEnv.withIncreasedIndent();
1179
+ const type = renderTypeReferenceAndProps(act.returns, actEnv);
1180
+ result += ` returns${annos ? '\n' : ' '}${annos}${annos ? actEnv.indent : ''}${type}`;
1157
1181
  }
1158
1182
 
1159
1183
  result += ';\n';
@@ -1170,8 +1194,8 @@ function csnToCdl( csn, options ) {
1170
1194
  * @returns {string}
1171
1195
  */
1172
1196
  function renderParameters( art, env ) {
1173
- const childEnv = increaseIndent(env);
1174
- const parameters = Object.keys(art.params || {}).map(name => renderParameter(name, art.params[name], childEnv));
1197
+ const childEnv = env.withIncreasedIndent();
1198
+ const parameters = Object.keys(art.params || {}).map(name => renderParameter(name, art.params[name], childEnv.withSubPath([ 'params', name ])));
1175
1199
  if (parameters.length === 0)
1176
1200
  return '()';
1177
1201
  return `(\n${parameters.join(',\n')}\n${env.indent})`;
@@ -1186,7 +1210,7 @@ function csnToCdl( csn, options ) {
1186
1210
  * @return {string}
1187
1211
  */
1188
1212
  function renderParameter( parName, par, env ) {
1189
- env = envAddPath(env, [ 'params', parName ]);
1213
+ env = env.withSubPath( [ 'params', parName ]);
1190
1214
  let result = `${renderAnnotationAssignmentsAndDocComment(par, env)}${env.indent}`;
1191
1215
  result += `${quoteNonIdentifierOrKeyword(parName, env)} : ${renderTypeReferenceAndProps(par, env)}`;
1192
1216
  return result;
@@ -1199,7 +1223,7 @@ function csnToCdl( csn, options ) {
1199
1223
  * @param {string} artifactName
1200
1224
  * @param {CSN.Artifact} art
1201
1225
  * @param {CdlRenderEnvironment} env
1202
- * @param {String} [artType] - used for rendering csn.vocabularies, as the annotations there do not have a kind.
1226
+ * @param {String} [artType] Used for rendering `csn.vocabularies`, as the annotations there do not have a kind.
1203
1227
  * @return {string}
1204
1228
  */
1205
1229
  function renderTypeOrAnnotation( artifactName, art, env, artType ) {
@@ -1232,7 +1256,7 @@ function csnToCdl( csn, options ) {
1232
1256
  const { typeRefOnly, noAnnoCollect } = config;
1233
1257
 
1234
1258
  if (typeRefOnly && !artifact.type)
1235
- throw new ModelError(`Expected artifact to have a type; in: ${env.artifactName}`);
1259
+ throw new ModelError(`Expected artifact to have a type; in: ${JSON.stringify(env.path)}`);
1236
1260
 
1237
1261
  if (artifact.localized) // works even for type definitions
1238
1262
  result += 'localized ';
@@ -1240,7 +1264,8 @@ function csnToCdl( csn, options ) {
1240
1264
  if (!artifact.type && artifact.items) {
1241
1265
  checkArrayedArtifact(artifact, env);
1242
1266
  result += 'many '; // alternative: 'array of'; but not used
1243
- ({ art: artifact, env } = checkInnerMostArray(artifact, env));
1267
+ artifact = artifact.items;
1268
+ env = env.withSubPath([ 'items' ]);
1244
1269
  }
1245
1270
 
1246
1271
  const type = normalizeTypeRef(artifact.type);
@@ -1263,13 +1288,13 @@ function csnToCdl( csn, options ) {
1263
1288
  // In parseCdl, `target` may still be an object containing elements. This would be replaced
1264
1289
  // by targetAspect in client CSN, but we can't rely on that.
1265
1290
  // If a name exists (either in target or targetAspect), prefer it over rendering elements.
1266
- const elements = artifact.target && artifact.target.elements || artifact.targetAspect && artifact.targetAspect.elements;
1291
+ const elements = artifact.target?.elements || artifact.targetAspect?.elements;
1267
1292
  if (typeof artifact.target === 'string' || typeof artifact.targetAspect === 'string') {
1268
1293
  result += renderAbsolutePath({ ref: [ artifact.target || artifact.targetAspect ] }, env);
1269
1294
  }
1270
1295
  else if (elements) {
1271
1296
  // anonymous aspect, either parseCdl or client CSN.
1272
- result += renderElements({ elements }, env);
1297
+ result += renderElements({ elements }, env.withSubPath([ artifact.target?.elements ? 'target' : 'targetAspect' ]));
1273
1298
  }
1274
1299
  else {
1275
1300
  throw new ModelError('Association/Composition is missing its target! Throwing exception to trigger recompilation.');
@@ -1277,11 +1302,11 @@ function csnToCdl( csn, options ) {
1277
1302
 
1278
1303
  // ON-condition (if any)
1279
1304
  if (artifact.on)
1280
- result += ` on ${exprRenderer.renderExpr(artifact.on, env)}`;
1305
+ result += ` on ${exprRenderer.renderExpr(artifact.on, env.withSubPath([ 'on' ]))}`;
1281
1306
 
1282
1307
  // Foreign keys (if any, unless we also have an ON_condition (which means we have been transformed from managed to unmanaged)
1283
1308
  if (artifact.keys && !artifact.on)
1284
- result += ` { ${Object.keys(artifact.keys).map(name => renderForeignKey(artifact.keys[name], env)).join(', ')} }`;
1309
+ result += ` { ${Object.keys(artifact.keys).map(name => renderForeignKey(artifact.keys[name], env.withSubPath([ 'keys', name ]))).join(', ')} }`;
1285
1310
 
1286
1311
  if (artifact.notNull !== undefined && !artifact.on) // unmanaged associations can't be followed by "not null"
1287
1312
  result += renderNullability(artifact);
@@ -1315,8 +1340,11 @@ function csnToCdl( csn, options ) {
1315
1340
  result += renderEnum(artifact.enum, env);
1316
1341
  if (artifact.notNull !== undefined)
1317
1342
  result += renderNullability(artifact);
1318
- if (artifact.default !== undefined)
1319
- result += ` default ${exprRenderer.renderExpr(artifact.default, env)}`;
1343
+
1344
+ // If there is a default value, and it's a calculated element, do not
1345
+ // render the default (because it's not supported for calc elements).
1346
+ if (artifact.default !== undefined && !artifact.value)
1347
+ result += ` default ${exprRenderer.renderExpr(artifact.default, env.withSubPath([ 'default' ]))}`;
1320
1348
 
1321
1349
  return result;
1322
1350
  }
@@ -1331,9 +1359,9 @@ function csnToCdl( csn, options ) {
1331
1359
  function renderRedirectedTo( art, env ) {
1332
1360
  let result = `redirected to ${renderDefinitionReference(art.target, env)}`;
1333
1361
  if (art.on)
1334
- result += ` on ${exprRenderer.renderExpr(art.on, env)}`;
1362
+ result += ` on ${exprRenderer.renderExpr(art.on, env.withSubPath([ 'on' ]))}`;
1335
1363
  else if (art.keys)
1336
- result += ` { ${Object.keys(art.keys).map(name => renderForeignKey(art.keys[name], env)).join(', ')} }`;
1364
+ result += ` { ${Object.keys(art.keys).map(name => renderForeignKey(art.keys[name], env.withSubPath([ 'keys', name ]))).join(', ')} }`;
1337
1365
  return result;
1338
1366
  }
1339
1367
 
@@ -1377,9 +1405,9 @@ function csnToCdl( csn, options ) {
1377
1405
  */
1378
1406
  function renderEnum( enumPart, env ) {
1379
1407
  let result = ' enum {\n';
1380
- const childEnv = increaseIndent(env);
1408
+ const childEnv = env.withIncreasedIndent();
1381
1409
  for (const name in enumPart)
1382
- result += renderElement(name, enumPart[name], childEnv);
1410
+ result += renderElement(name, enumPart[name], childEnv.withSubPath([ 'enum', name ]));
1383
1411
  result += `${env.indent}}`;
1384
1412
  return result;
1385
1413
  }
@@ -1407,17 +1435,17 @@ function csnToCdl( csn, options ) {
1407
1435
  }
1408
1436
  else if (typeof annoValue === 'object' && annoValue !== null) {
1409
1437
  // Enum symbol
1410
- if (annoValue['#']) {
1438
+ if (annoValue['#'] !== undefined) {
1411
1439
  return `#${annoValue['#']}`;
1412
1440
  }
1413
1441
  // Shorthand for absolute path (as string)
1414
- else if (annoValue['=']) {
1442
+ else if (annoValue['='] !== undefined) {
1415
1443
  if (annoValue['='].startsWith('@'))
1416
1444
  return quoteAnnotationPathIfRequired(annoValue['='], env);
1417
1445
  return quotePathIfRequired(annoValue['='], env);
1418
1446
  }
1419
1447
  // Shorthand for ellipsis: `... up to <val>`
1420
- else if (annoValue['...']) {
1448
+ else if (annoValue['...'] !== undefined) {
1421
1449
  if (annoValue['...'] === true)
1422
1450
  return '...';
1423
1451
  return `... up to ${renderAnnotationValue(annoValue['...'], env)}`;
@@ -1427,8 +1455,8 @@ function csnToCdl( csn, options ) {
1427
1455
  // Render as one-liner if there is at most one key. Render as multi-line
1428
1456
  // struct if there are more and use nicer indentation.
1429
1457
  const keys = Object.keys(annoValue);
1430
- const childEnv = keys.length <= 1 ? env : increaseIndent(env);
1431
- const values = keys.map(key => `${quoteAnnotationPathIfRequired(key, env)}: ${renderAnnotationValue(annoValue[key], childEnv)}`);
1458
+ const childEnv = keys.length <= 1 ? env : env.withIncreasedIndent();
1459
+ const values = keys.map(key => `${quoteAnnotationPathIfRequired(key, env)}: ${renderAnnotationValue(annoValue[key], childEnv.withSubPath([ key ]))}`);
1432
1460
  if (values.length <= 1)
1433
1461
  return `{ ${values.join(', ')} }`;
1434
1462
  const valueList = values.join(`,\n${childEnv.indent}`);
@@ -1455,12 +1483,12 @@ function csnToCdl( csn, options ) {
1455
1483
  * @return {string}
1456
1484
  */
1457
1485
  function renderAnnotationArrayValue( annoValue, env ) {
1458
- const childEnv = increaseIndent(env);
1486
+ const childEnv = env.withIncreasedIndent();
1459
1487
  // Render array parts as values.
1460
1488
  let length = 0;
1461
1489
  let hasLineBreak = false;
1462
- const items = annoValue.map((item) => {
1463
- const result = renderAnnotationValue(item, childEnv);
1490
+ const items = annoValue.map((item, i) => {
1491
+ const result = renderAnnotationValue(item, childEnv.withSubPath([ i ]));
1464
1492
  length += result.length + 2; // just a heuristic; add 2 for `, `.
1465
1493
  if (!hasLineBreak && result.includes('\n'))
1466
1494
  hasLineBreak = true;
@@ -1508,14 +1536,35 @@ function csnToCdl( csn, options ) {
1508
1536
  // View parameters
1509
1537
  result += `(${renderArguments(s, ':', env)})`;
1510
1538
  }
1539
+
1540
+ const cardinality = s.cardinality ? (`${s.cardinality.max}: `) : '';
1541
+ let filter = '';
1542
+
1543
+ // TODO: Unify with other filter rendering for SELECT
1544
+ if (s.groupBy)
1545
+ filter += ` group by ${s.groupBy.map((expr, i) => exprRenderer.renderExpr(expr, env.withSubPath([ 'groupBy', i ]))).join(', ')}`;
1546
+ if (s.having)
1547
+ filter += ` having ${exprRenderer.renderExpr(s.having, env.withSubPath([ 'having' ]))}`;
1548
+ if (s.orderBy)
1549
+ filter += ` order by ${s.orderBy.map((entry, i) => renderOrderByEntry(entry, env.withSubPath([ 'orderBy', i ]))).join(', ')}`;
1550
+ if (s.limit)
1551
+ filter += ` ${renderLimit(s.limit, env.withSubPath([ 'limit' ]))}`;
1552
+
1511
1553
  if (s.where) {
1512
- // Filter, possibly with cardinality
1513
- const cardinality = s.cardinality ? (`${s.cardinality.max}: `) : '';
1514
- const expr = exprRenderer.renderExpr(s.where, env);
1515
- if (expr.endsWith(']')) // for cases such as [… ![id] ]
1516
- result += `[ ${cardinality}${expr} ]`;
1554
+ let where = exprRenderer.renderExpr(s.where, env.withSubPath([ 'where' ]));
1555
+ // Special rules in CDS parser: If filter starts with one of these SQL keywords, WHERE is mandatory.
1556
+ if (filter || /^(?:group|having|order|limit)\s/i.test(where))
1557
+ where = ` where ${where}`;
1558
+ filter = `${where} ${filter}`;
1559
+ }
1560
+
1561
+ filter = filter.trim();
1562
+
1563
+ if (cardinality || filter) {
1564
+ if (filter.endsWith(']')) // for cases such as [… ![id] ]
1565
+ result += `[ ${cardinality}${filter} ]`;
1517
1566
  else
1518
- result += `[${cardinality}${expr}]`;
1567
+ result += `[${cardinality}${filter}]`;
1519
1568
  }
1520
1569
 
1521
1570
  return result;
@@ -1554,7 +1603,7 @@ function csnToCdl( csn, options ) {
1554
1603
  */
1555
1604
  function renderNamedArguments( node, separator, env ) {
1556
1605
  return Object.keys(node.args).map(function renderNamedArgument(key) {
1557
- return `${quoteNonIdentifierOrKeyword(key, env)} ${separator} ${renderArgument(node.args[key], env)}`;
1606
+ return `${quoteNonIdentifierOrKeyword(key, env)} ${separator} ${renderArgument(node.args[key], env.withSubPath([ 'args', key ]))}`;
1558
1607
  }).join(', ');
1559
1608
  }
1560
1609
 
@@ -1571,10 +1620,10 @@ function csnToCdl( csn, options ) {
1571
1620
  const func = node.func?.toUpperCase();
1572
1621
  if (func) {
1573
1622
  return node.args.map(function renderFunctionArg(arg, i) {
1574
- return renderArgument(arg, env, getKeywordsForSpecialFunctionArgument(func, i));
1623
+ return renderArgument(arg, env.withSubPath([ 'args', i ]), getKeywordsForSpecialFunctionArgument(func, i));
1575
1624
  }).join(', ');
1576
1625
  }
1577
- return node.args.map(arg => renderArgument(arg, env)).join(', ');
1626
+ return node.args.map((arg, i) => renderArgument(arg, env.withSubPath([ 'args', i ]))).join(', ');
1578
1627
  }
1579
1628
 
1580
1629
  /**
@@ -1589,7 +1638,7 @@ function csnToCdl( csn, options ) {
1589
1638
  function renderArgument( arg, env, additionalKeywords = [] ) {
1590
1639
  // If the argument is a xpr with e.g. `=`, it may require parentheses.
1591
1640
  // For nested xpr, `exprRenderer.renderExpr()` will already add parentheses.
1592
- env = { ...env, additionalKeywords };
1641
+ env = env.cloneWith({ additionalKeywords });
1593
1642
  if (isSimpleFunctionExpression(arg && arg.xpr, additionalKeywords))
1594
1643
  return exprRenderer.renderExpr(arg, env);
1595
1644
  return exprRenderer.renderSubExpr(arg, env);
@@ -1744,7 +1793,7 @@ function csnToCdl( csn, options ) {
1744
1793
  let result = renderDocComment(obj, env);
1745
1794
  for (const name in obj) {
1746
1795
  if (name.startsWith('@'))
1747
- result += renderAnnotationAssignment(obj[name], name, env, config);
1796
+ result += renderAnnotationAssignment(obj[name], name, env.withSubPath([ name ]), config);
1748
1797
  }
1749
1798
  return result;
1750
1799
  }
@@ -1814,14 +1863,14 @@ function csnToCdl( csn, options ) {
1814
1863
  * @return {string}
1815
1864
  */
1816
1865
  function renderIncludes( includes, env ) {
1817
- return ` : ${includes.map(name => renderDefinitionReference(name, env)).join(', ')}`;
1866
+ return ` : ${includes.map((name, i) => renderDefinitionReference(name, env.withSubPath([ 'includes', i ]))).join(', ')}`;
1818
1867
  }
1819
1868
 
1820
1869
  function createCdlExpressionRenderer() {
1821
1870
  return createExpressionRenderer({
1822
1871
  finalize: x => x,
1823
1872
  typeCast(x) {
1824
- const typeRef = renderTypeReferenceAndProps(x.cast, this.env, { typeRefOnly: true, noAnnoCollect: true });
1873
+ const typeRef = renderTypeReferenceAndProps(x.cast, this.env.withSubPath([ 'cast' ]), { typeRefOnly: true, noAnnoCollect: true });
1825
1874
  const arg = { ...x, cast: null }; // "arg" without cast to avoid recursion.
1826
1875
  return `cast(${renderArgument(arg, this.env)} as ${typeRef})`;
1827
1876
  },
@@ -1850,11 +1899,11 @@ function csnToCdl( csn, options ) {
1850
1899
  aliasOnly: x => x.as,
1851
1900
  enum: x => `#${x['#']}`,
1852
1901
  ref(x) {
1853
- return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, this.env)).join('.')}`;
1902
+ return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, this.env.withSubPath([ 'ref', index ]))).join('.')}`;
1854
1903
  },
1855
1904
  windowFunction(x) {
1856
1905
  const funcDef = this.func(x);
1857
- return `${funcDef} ${this.renderExpr(x.xpr)}`; // xpr[0] is 'over'
1906
+ return `${funcDef} ${this.renderExpr(x.xpr, this.env.withSubPath([ 'xpr' ]))}`; // xpr[0] is 'over'
1858
1907
  },
1859
1908
  func(x) {
1860
1909
  if (keywords.cdl_functions.includes(x.func.toUpperCase()) && !x.args)
@@ -1865,67 +1914,36 @@ function csnToCdl( csn, options ) {
1865
1914
  return `${name}(${renderArguments( x, '=>', this.env )})`;
1866
1915
  },
1867
1916
  xpr(x) {
1917
+ const xprEnv = this.env.withSubPath([ 'xpr' ]);
1868
1918
  if (this.isNestedXpr && !x.cast)
1869
- return `(${this.renderExpr(x.xpr)})`;
1870
- return this.renderExpr(x.xpr);
1919
+ return `(${this.renderExpr(x.xpr, xprEnv)})`;
1920
+ return this.renderExpr(x.xpr, xprEnv);
1871
1921
  },
1872
1922
  // Sub-queries in expressions need to be in parentheses, otherwise
1873
1923
  // left-associativity of UNIONS may result in different results.
1874
1924
  // For example: `select from E where id in (select from E union select from E);`:
1875
1925
  // Without parentheses, it would be different query.
1876
1926
  SET(x) {
1877
- return `(${renderQuery(x, false, 'view', increaseIndent(this.env))})`;
1927
+ return `(${renderQuery(x, false, 'view', this.env.withIncreasedIndent())})`;
1878
1928
  },
1879
1929
  SELECT(x) {
1880
- return `(${renderQuery(x, false, 'view', increaseIndent(this.env))})`;
1930
+ return `(${renderQuery(x, false, 'view', this.env.withIncreasedIndent())})`;
1881
1931
  },
1882
- });
1932
+ }, true);
1883
1933
  }
1884
1934
 
1885
1935
  // checks -------------------------------------------------------------------
1886
1936
  // The CDL backend has very few checks, but we need to tell the user if
1887
1937
  // something can't be rendered.
1888
1938
 
1889
- /**
1890
- * to.cdl() can only render one nesting level of `items`. `items` inside `items, etc.
1891
- * can't be represented in CDL, hence can't be rendered.
1892
- * However, it's possible that due to CSN expansion because of type-ofs, we have
1893
- * nested `.items` with a `.type` next to it. In that case, return the node with `.type`.
1894
- *
1895
- * Returns the most deeply nested `.items`. Upper bound are 100 nesting levels.
1896
- *
1897
- * @param {CSN.Artifact} art
1898
- * @param {CdlRenderEnvironment} env
1899
- * @return {{art: CSN.Artifact, env: CdlRenderEnvironment}} `art` and new `env` with adapted `env.path`.
1900
- */
1901
- function checkInnerMostArray( art, env ) {
1902
- env = envNewPath(env, env.path); // copy path, so we can modify it directly
1903
-
1904
- let nesting = 0;
1905
- while (art.items && nesting < 100) {
1906
- art = art.items;
1907
- env.path.push( 'items');
1908
- ++nesting;
1909
- if (art.type)
1910
- break; // after first `.items`, break at nesting level that has a type.
1911
- }
1912
-
1913
- if (nesting >= 100) {
1914
- msg.error('def-invalid-nesting', env.path, { count: nesting, prop: 'items' },
1915
- 'Property $(PROP) is nested more than $(COUNT) levels and can\'t be rendered');
1916
- }
1917
- else if (nesting > 1) {
1918
- msg.warning('def-unexpected-nesting', env.path, { prop: 'items' },
1919
- 'Property $(PROP) is nested more than one level; only rendering deepest level');
1920
- }
1921
- return { art, env };
1922
- }
1923
-
1924
1939
  /**
1925
1940
  * If an artifact is an array via `.items`, some properties on `art` can't be rendered,
1926
1941
  * for example "not null", because there is no CDL representation for it. Only "not null"
1927
1942
  * on `.items` can be rendered.
1928
1943
  *
1944
+ * Furthermore, to.cdl() can only render one nesting level of `items`. `items` inside
1945
+ * `items, etc. can't be represented in CDL, hence can't be rendered.
1946
+ *
1929
1947
  * @param {CSN.Artifact} art
1930
1948
  * @param {CdlRenderEnvironment} env
1931
1949
  */
@@ -1936,6 +1954,16 @@ function csnToCdl( csn, options ) {
1936
1954
  msg.warning('def-unexpected-nullability', env.path, { prop: 'not null', otherprop: 'items' },
1937
1955
  'Property $(PROP) not rendered, because it can only be rendered inside $(OTHERPROP) for arrayed artifacts');
1938
1956
  }
1957
+
1958
+ if (art.items.items && !art.items.type)
1959
+ msg.message('type-invalid-items', [ ...env.path, 'items' ], { '#': 'nested', prop: 'items' } );
1960
+
1961
+ const type = art.items.type && normalizeTypeRef(art.items.type);
1962
+ if (type === 'cds.Association' || type === 'cds.Composition') {
1963
+ // check for `art.items.target` not sufficient; could be indirect type reference
1964
+ const isComp = type === 'cds.Composition';
1965
+ msg.message('type-invalid-items', [ ...env.path, 'items' ], { '#': isComp ? 'comp' : 'assoc', prop: 'items' });
1966
+ }
1939
1967
  }
1940
1968
 
1941
1969
  /**
@@ -2049,19 +2077,28 @@ function csnToCdl( csn, options ) {
2049
2077
  }
2050
2078
  }
2051
2079
 
2052
-
2053
2080
  class CdlRenderEnvironment {
2054
2081
  indent = '';
2055
2082
  path = null;
2056
- artifactName = null;
2057
2083
  elementName = null;
2058
2084
  additionalKeywords = [];
2059
2085
 
2060
2086
  constructor(values) {
2061
2087
  Object.assign(this, values);
2062
2088
  }
2089
+
2090
+ withIncreasedIndent() {
2091
+ return new CdlRenderEnvironment({ ...this, indent: ` ${this.indent}` });
2092
+ }
2093
+ withSubPath(path) {
2094
+ return new CdlRenderEnvironment({ ...this, path: [ ...this.path, ...path ] });
2095
+ }
2096
+ cloneWith(values) {
2097
+ return Object.assign(new CdlRenderEnvironment(this), values);
2098
+ }
2063
2099
  }
2064
2100
 
2101
+
2065
2102
  /**
2066
2103
  * Returns a newly created default environment (which keeps track of indentation, required USING
2067
2104
  * declarations and name prefixes.
@@ -2073,21 +2110,17 @@ function createEnv( values = {} ) {
2073
2110
  return new CdlRenderEnvironment( values );
2074
2111
  }
2075
2112
 
2076
- function envAddPath( env, path ) {
2077
- return Object.assign(new CdlRenderEnvironment(env), { path: [ ...env.path, ...path ] } );
2078
- }
2079
- function envNewPath( env, path ) {
2080
- return Object.assign(new CdlRenderEnvironment(env), { path: [ ...path ] } );
2081
- }
2082
-
2083
2113
  /**
2084
- * Returns a copy of 'env' with increased indentation (and reset name prefix)
2114
+ * Remove a trailing `\n`/newline/LF from `str` and return the modified string.
2115
+ * Useful if you want to append a `;` to the string, but not on a separate line.
2085
2116
  *
2086
- * @param {object} env
2087
- * @returns {object}
2117
+ * @param {string} str
2118
+ * @return {string}
2088
2119
  */
2089
- function increaseIndent( env ) {
2090
- return Object.assign({}, env, { indent: `${env.indent} ` });
2120
+ function removeTrailingNewline( str ) {
2121
+ if (str[str.length - 1] === '\n')
2122
+ str = str.substring(0, str.length - 1);
2123
+ return str;
2091
2124
  }
2092
2125
 
2093
2126
  /**
@@ -2152,7 +2185,7 @@ function isSimpleFunctionExpression( xpr, additionalAllowedKeywords = [] ) {
2152
2185
 
2153
2186
  /**
2154
2187
  * If `xpr` contains tokens that are used in conditions, it may be required to put the
2155
- * rendered expression in parentheses. This function checks if any direkt entry in
2188
+ * rendered expression in parentheses. This function checks if any direct entry in
2156
2189
  * `xpr` is a condition token such as `AND`.
2157
2190
  *
2158
2191
  * May report false positives for e.g. `CASE WHEN 1>1 THEN …`.
@@ -2219,8 +2252,8 @@ function getAllKeywordsForSpecialFunction( funcName ) {
2219
2252
  * Render the given string. Uses back-tick strings.
2220
2253
  * env is used for indentation of three-back-tick strings.
2221
2254
  *
2222
- * @param str
2223
- * @param env
2255
+ * @param {string} str
2256
+ * @param {CdlRenderEnvironment} env
2224
2257
  * @returns {string}
2225
2258
  */
2226
2259
  function renderString( str, env ) {