@sap/cds-compiler 2.15.8 → 3.1.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 (127) hide show
  1. package/CHANGELOG.md +102 -1590
  2. package/bin/.eslintrc.json +2 -1
  3. package/bin/cdsc.js +61 -46
  4. package/doc/API.md +11 -0
  5. package/doc/CHANGELOG_ARCHIVE.md +1592 -0
  6. package/doc/CHANGELOG_BETA.md +26 -5
  7. package/doc/CHANGELOG_DEPRECATED.md +55 -1
  8. package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
  9. package/doc/Versioning.md +20 -1
  10. package/lib/api/.eslintrc.json +2 -2
  11. package/lib/api/main.js +282 -156
  12. package/lib/api/options.js +17 -88
  13. package/lib/api/validate.js +6 -10
  14. package/lib/base/keywords.js +280 -110
  15. package/lib/base/message-registry.js +85 -25
  16. package/lib/base/messages.js +119 -89
  17. package/lib/base/model.js +46 -2
  18. package/lib/base/optionProcessorHelper.js +53 -21
  19. package/lib/checks/actionsFunctions.js +15 -12
  20. package/lib/checks/annotationsOData.js +1 -1
  21. package/lib/checks/cdsPersistence.js +1 -0
  22. package/lib/checks/elements.js +6 -6
  23. package/lib/checks/invalidTarget.js +1 -1
  24. package/lib/checks/nonexpandableStructured.js +1 -1
  25. package/lib/checks/queryNoDbArtifacts.js +2 -1
  26. package/lib/checks/selectItems.js +101 -15
  27. package/lib/checks/types.js +7 -8
  28. package/lib/checks/utils.js +2 -2
  29. package/lib/checks/validator.js +3 -3
  30. package/lib/compiler/assert-consistency.js +78 -21
  31. package/lib/compiler/base.js +6 -4
  32. package/lib/compiler/builtins.js +177 -10
  33. package/lib/compiler/checks.js +1 -1
  34. package/lib/compiler/define.js +28 -23
  35. package/lib/compiler/extend.js +75 -18
  36. package/lib/compiler/finalize-parse-cdl.js +25 -18
  37. package/lib/compiler/index.js +27 -11
  38. package/lib/compiler/moduleLayers.js +7 -0
  39. package/lib/compiler/populate.js +26 -39
  40. package/lib/compiler/propagator.js +12 -7
  41. package/lib/compiler/resolve.js +207 -236
  42. package/lib/compiler/shared.js +100 -93
  43. package/lib/compiler/tweak-assocs.js +13 -20
  44. package/lib/compiler/utils.js +20 -6
  45. package/lib/edm/annotations/preprocessAnnotations.js +12 -13
  46. package/lib/edm/csn2edm.js +35 -37
  47. package/lib/edm/edm.js +22 -13
  48. package/lib/edm/edmAnnoPreprocessor.js +349 -0
  49. package/lib/edm/edmInboundChecks.js +85 -0
  50. package/lib/edm/edmPreprocessor.js +338 -689
  51. package/lib/edm/edmUtils.js +97 -67
  52. package/lib/gen/Dictionary.json +29 -9
  53. package/lib/gen/language.checksum +1 -1
  54. package/lib/gen/language.interp +8 -31
  55. package/lib/gen/language.tokens +105 -114
  56. package/lib/gen/languageLexer.interp +1 -34
  57. package/lib/gen/languageLexer.js +892 -1007
  58. package/lib/gen/languageLexer.tokens +95 -106
  59. package/lib/gen/languageParser.js +20629 -22474
  60. package/lib/inspect/.eslintrc.json +4 -0
  61. package/lib/inspect/index.js +14 -0
  62. package/lib/inspect/inspectModelStatistics.js +81 -0
  63. package/lib/inspect/inspectPropagation.js +189 -0
  64. package/lib/inspect/inspectUtils.js +44 -0
  65. package/lib/json/from-csn.js +74 -69
  66. package/lib/json/to-csn.js +17 -14
  67. package/lib/language/antlrParser.js +2 -2
  68. package/lib/language/docCommentParser.js +61 -38
  69. package/lib/language/errorStrategy.js +52 -40
  70. package/lib/language/genericAntlrParser.js +424 -292
  71. package/lib/language/language.g4 +604 -687
  72. package/lib/language/multiLineStringParser.js +14 -42
  73. package/lib/language/textUtils.js +44 -0
  74. package/lib/main.d.ts +28 -42
  75. package/lib/main.js +104 -81
  76. package/lib/model/api.js +1 -1
  77. package/lib/model/csnRefs.js +57 -30
  78. package/lib/model/csnUtils.js +189 -287
  79. package/lib/model/revealInternalProperties.js +32 -10
  80. package/lib/model/sortViews.js +32 -31
  81. package/lib/modelCompare/compare.js +3 -0
  82. package/lib/optionProcessor.js +91 -57
  83. package/lib/render/.eslintrc.json +1 -1
  84. package/lib/render/DuplicateChecker.js +4 -7
  85. package/lib/render/manageConstraints.js +70 -2
  86. package/lib/render/toCdl.js +387 -367
  87. package/lib/render/toHdbcds.js +20 -16
  88. package/lib/render/toRename.js +44 -22
  89. package/lib/render/toSql.js +81 -59
  90. package/lib/render/utils/common.js +16 -3
  91. package/lib/render/utils/sql.js +20 -19
  92. package/lib/sql-identifier.js +6 -0
  93. package/lib/transform/db/.eslintrc.json +3 -2
  94. package/lib/transform/db/associations.js +43 -35
  95. package/lib/transform/db/cdsPersistence.js +5 -16
  96. package/lib/transform/db/constraints.js +1 -1
  97. package/lib/transform/db/expansion.js +7 -6
  98. package/lib/transform/db/flattening.js +16 -18
  99. package/lib/transform/db/transformExists.js +7 -5
  100. package/lib/transform/db/views.js +3 -3
  101. package/lib/transform/draft/.eslintrc.json +2 -2
  102. package/lib/transform/draft/db.js +6 -6
  103. package/lib/transform/draft/odata.js +6 -7
  104. package/lib/transform/forHanaNew.js +30 -24
  105. package/lib/transform/forOdataNew.js +14 -16
  106. package/lib/transform/localized.js +35 -25
  107. package/lib/transform/odata/toFinalBaseType.js +10 -10
  108. package/lib/transform/odata/typesExposure.js +17 -8
  109. package/lib/transform/odata/utils.js +1 -38
  110. package/lib/transform/transformUtilsNew.js +63 -77
  111. package/lib/transform/translateAssocsToJoins.js +2 -2
  112. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  113. package/lib/transform/universalCsn/coreComputed.js +11 -6
  114. package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
  115. package/lib/utils/file.js +31 -21
  116. package/lib/utils/moduleResolve.js +0 -1
  117. package/lib/utils/timetrace.js +20 -21
  118. package/package.json +34 -4
  119. package/share/messages/syntax-expected-integer.md +9 -8
  120. package/doc/ApiMigration.md +0 -237
  121. package/doc/CommandLineMigration.md +0 -58
  122. package/doc/ErrorMessages.md +0 -175
  123. package/doc/FioriAnnotations.md +0 -94
  124. package/doc/ODataTransformation.md +0 -273
  125. package/lib/backends.js +0 -529
  126. package/lib/checks/unknownMagic.js +0 -41
  127. package/lib/fix_antlr4-8_warning.js +0 -56
@@ -1,33 +1,32 @@
1
1
  'use strict';
2
2
 
3
3
  const keywords = require('../base/keywords');
4
- const { getLastPartOf } = require('../model/csnUtils');
5
4
  const { isBuiltinType, generatedByCompilerVersion, getNormalizedQuery } = require('../model/csnUtils');
6
- const { renderFunc, findElement, getExpressionRenderer } = require('./utils/common');
5
+ const { findElement, getExpressionRenderer } = require('./utils/common');
7
6
  const { escapeString, hasUnpairedUnicodeSurrogate } = require('./utils/stringEscapes');
8
7
  const { checkCSNVersion } = require('../json/csnVersion');
9
8
  const { timetrace } = require('../utils/timetrace');
10
- const { csnRefs } = require('../model/csnRefs');
11
9
  const { forEachDefinition } = require('../model/csnUtils');
12
10
  const enrichUniversalCsn = require('../transform/universalCsn/universalCsnEnricher');
13
11
  const { isBetaEnabled } = require('../base/model');
14
12
  const { ModelError } = require('../base/error');
15
- const { typeParameters } = require('../compiler/builtins');
13
+ const { typeParameters, specialFunctions } = require('../compiler/builtins');
16
14
  const { forEach } = require('../utils/objectUtils');
17
15
 
16
+ const identifierRegex = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/;
17
+
18
18
  /**
19
19
  * Render the CSN model 'model' to CDS source text.
20
20
  * Returned object has the following properties:
21
21
  * - `model`: CSN model rendered as CDL (string).
22
- * - [csn.namespace]: Namespace statement + `using from './model.cds'.
22
+ * - `namespace`: Namespace statement + `using from './model.cds'.
23
23
  * - `unappliedExtensions`: Annotations / Extensions from the `csn.extensions` array.
24
24
  *
25
25
  * @param {CSN.Model} csn
26
26
  * @param {CSN.Options} [options]
27
27
  */
28
- function toCdsSourceCsn(csn, options) {
28
+ function csnToCdl(csn, options) {
29
29
  timetrace.start('CDL rendering');
30
- const { artifactRef } = csnRefs(csn);
31
30
  let _renderExpr = null;
32
31
 
33
32
  if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn'))
@@ -48,8 +47,8 @@ function toCdsSourceCsn(csn, options) {
48
47
  cdlResult.model += renderVocabularies(csn.vocabularies);
49
48
 
50
49
  if (csn.namespace) {
51
- cdlResult[csn.namespace] = `namespace ${renderArtifactName(csn.namespace)};\n`;
52
- cdlResult[csn.namespace] += 'using from \'./model.cds\';';
50
+ cdlResult.namespace = `namespace ${renderArtifactName(csn.namespace)};\n`;
51
+ cdlResult.namespace += 'using from \'./model.cds\';';
53
52
  }
54
53
 
55
54
  // If there are extensions, such as 'extend' and 'annotate' statements, render them separately.
@@ -93,7 +92,7 @@ function toCdsSourceCsn(csn, options) {
93
92
  if (!anno._ignore) {
94
93
  // This environment is passed down the call hierarchy, for dealing with
95
94
  // indentation and name resolution issues
96
- const env = createEnv();
95
+ const env = envNewPath(createEnv(), [ 'vocabularies', name ]);
97
96
  const sourceStr = renderTypeOrAnnotation(name, anno, env, 'annotation');
98
97
  result += `${sourceStr}\n`;
99
98
  }
@@ -143,11 +142,10 @@ function toCdsSourceCsn(csn, options) {
143
142
  let result = renderAnnotationAssignmentsAndDocComment(ext, env);
144
143
 
145
144
  if (ext.includes && ext.includes.length > 0) {
146
- // Includes can't be combined with anything in braces {}. Multiple includes
147
- // are possible through CSN, but in CDL, only one include at once is possible.
145
+ // Includes can't be combined with anything in braces {}.
148
146
  const affix = isElementExtend ? 'element ' : '';
149
- for (const id of ext.includes)
150
- result += `${env.indent}extend ${affix}${extName} with ${quoteIdIfRequired(id)};\n`;
147
+ const includes = ext.includes.map(inc => quotePathIfRequired(inc)).join(', ');
148
+ result += `${env.indent}extend ${affix}${extName} with ${includes};\n`;
151
149
  return result;
152
150
  }
153
151
 
@@ -293,7 +291,6 @@ function toCdsSourceCsn(csn, options) {
293
291
  result += `${env.indent}}`;
294
292
  }
295
293
 
296
-
297
294
  result += ';\n';
298
295
  return result;
299
296
  }
@@ -348,31 +345,33 @@ function toCdsSourceCsn(csn, options) {
348
345
  * @param {CdlRenderEnvironment} env
349
346
  */
350
347
  function renderArtifact(artifactName, art, env) {
351
- // FIXME: Correctly build the paths during runtime to give better locations
352
- env.path = [ 'definitions', artifactName ];
348
+ env = envNewPath(env, [ 'definitions', artifactName ]);
353
349
  env.artifactName = artifactName;
354
350
 
355
351
  switch (art.kind) {
356
352
  case 'entity':
357
353
  if (art.query || art.projection)
358
354
  return renderView(artifactName, art, env);
359
-
360
355
  return renderEntity(artifactName, art, env);
361
356
 
362
357
  case 'context':
363
358
  case 'service':
364
- return renderContext(artifactName, art, env);
359
+ return renderContextOrService(artifactName, art, env);
360
+
365
361
  case 'type':
366
362
  case 'aspect':
367
- case 'annotation':
368
- return renderTypeOrAnnotation(artifactName, art, env, art.$syntax);
363
+ case 'annotation': // annotation in 'csn.definitions' for compiler v1 compatibility
364
+ return renderTypeOrAnnotation(artifactName, art, env);
365
+
369
366
  case 'action':
370
367
  case 'function':
371
368
  return renderActionOrFunction(artifactName, art, env);
369
+
372
370
  case 'event':
373
371
  return renderEvent(artifactName, art, env);
372
+
374
373
  default:
375
- throw new ModelError(`Unknown artifact kind: ${art.kind}`);
374
+ throw new ModelError(`to.cdl: Unknown artifact kind: ${art.kind}`);
376
375
  }
377
376
  }
378
377
 
@@ -389,16 +388,14 @@ function toCdsSourceCsn(csn, options) {
389
388
  if (art.includes)
390
389
  result += renderIncludes(art.includes);
391
390
  if (art.query || art.projection) {
392
- env._artifact = art;
393
391
  result += ' : ';
394
392
  result += renderQuery(getNormalizedQuery(art).query, true, 'projection', env,
395
393
  [ 'definitions', artifactName, 'query' ]);
396
394
  result += ';\n';
397
- delete env._artifact;
398
395
  }
399
396
  else if (art.type) {
400
397
  // Derived type or annotation with non-anonymous type
401
- result += ` : ${renderTypeReference(art, env)};\n`;
398
+ result += ` : ${renderTypeReferenceAndProps(art, env)};\n`;
402
399
  }
403
400
  else if (art.elements) {
404
401
  result += ' {\n';
@@ -411,18 +408,14 @@ function toCdsSourceCsn(csn, options) {
411
408
  }
412
409
 
413
410
  /**
414
- * Render a context or service. Return the resulting source string.
415
- *
416
411
  * @param {string} artifactName
417
412
  * @param {CSN.Artifact} art
418
413
  * @param {CdlRenderEnvironment} env
414
+ * @returns {string}
419
415
  */
420
- function renderContext(artifactName, art, env) {
416
+ function renderContextOrService(artifactName, art, env) {
421
417
  let result = renderAnnotationAssignmentsAndDocComment(art, env);
422
- result += `${env.indent + (art.abstract ? 'abstract ' : '') + art.kind} ${renderArtifactName(artifactName)}`;
423
- if (art.includes)
424
- result += renderIncludes(art.includes);
425
-
418
+ result += `${env.indent}${art.kind} ${renderArtifactName(artifactName)}`;
426
419
  return `${result} {};\n`;
427
420
  }
428
421
 
@@ -436,28 +429,18 @@ function toCdsSourceCsn(csn, options) {
436
429
  */
437
430
  function renderEntity(artifactName, art, env) {
438
431
  let result = renderAnnotationAssignmentsAndDocComment(art, env);
439
- const childEnv = increaseIndent(env);
440
- const normalizedArtifactName = renderArtifactName(artifactName);
441
- result += `${env.indent + (art.abstract ? 'abstract ' : '')}entity ${normalizedArtifactName}`;
442
- const parameters = Object.keys(art.params || []).map(name => renderParameter(name, art.params[name], childEnv)).join(',\n');
443
- result += (parameters === '') ? '' : ` (\n${parameters}\n${env.indent})`;
432
+ result += env.indent + (art.abstract ? 'abstract ' : '');
433
+ result += `entity ${renderArtifactName(artifactName)}`;
434
+
435
+ if (art.params)
436
+ result += renderParameters(art, env);
437
+
444
438
  if (art.includes)
445
439
  result += renderIncludes(art.includes);
446
440
  result += ' {\n';
441
+ const childEnv = increaseIndent(env);
447
442
  for (const name in art.elements) {
448
443
  const element = art.elements[name];
449
- // For subelement annotations, this seems to be a pattern to recognize them
450
- // plus some other stuff unfortunately...
451
- if (element.type && element.elements) {
452
- subelementAnnotates.push({
453
- annotate: artifactName,
454
- elements: {
455
- [name]: {
456
- elements: element.elements,
457
- },
458
- },
459
- });
460
- }
461
444
  result += renderElement(name, element, childEnv);
462
445
  }
463
446
 
@@ -476,19 +459,15 @@ function toCdsSourceCsn(csn, options) {
476
459
  * @param {Boolean} [isSubElement]
477
460
  */
478
461
  function renderElement(elementName, elm, env, isSubElement) {
479
- env.elementName = elementName;
462
+ env = envAddPath(env, [ 'elements', elementName ]);
480
463
  let result = renderAnnotationAssignmentsAndDocComment(elm, env);
481
- const quotedElementName = quoteIdIfRequired(elementName);
482
- result += `${env.indent + (elm.virtual ? 'virtual ' : '') +
483
- (elm.key && !isSubElement ? 'key ' : '') +
484
- ((elm.masked && !elm._ignoreMasked) ? 'masked ' : '') +
485
- quotedElementName} : ${
486
- renderTypeReference(elm, env, undefined)
487
- }${elm.on ? '' : renderNullability(elm)}`;
488
- if (elm.default)
489
- result += ` default ${renderExpr(elm.default, env)}`;
490
-
491
- delete env.elementName;
464
+ result += env.indent;
465
+ result += elm.virtual ? 'virtual ' : '';
466
+ result += elm.key && !isSubElement ? 'key ' : '';
467
+ // TODO(v4): Remove once deprecated flag for `masked` is removed.
468
+ result += elm.masked ? 'masked ' : '';
469
+ result += `${quoteIdIfRequired(elementName)} : ${renderTypeReferenceAndProps(elm, env)}`;
470
+
492
471
  return `${result};\n`;
493
472
  }
494
473
 
@@ -529,7 +508,7 @@ function toCdsSourceCsn(csn, options) {
529
508
  * @return {string}
530
509
  */
531
510
  function renderQueryElementAnnotations(artifactName, art, env) {
532
- const annotate = collectAnnotationsOfElements(artifactName, art);
511
+ const annotate = collectAnnotationsOfElements(art, { artifactName, path: env.path });
533
512
  if (annotate)
534
513
  return renderExtensions([ annotate ], env);
535
514
  return '';
@@ -539,13 +518,40 @@ function toCdsSourceCsn(csn, options) {
539
518
  * Create an "annotate" statement as a CSN extension for all annotations of (sub-)elements.
540
519
  * If no annotation was found, we return `null`.
541
520
  *
542
- * @param {string} artifactName
543
521
  * @param {CSN.Artifact} artWithElements
522
+ * @param {CdlRenderEnvironment} env
544
523
  * @return {CSN.Extension|null}
545
524
  */
546
- function collectAnnotationsOfElements(artifactName, artWithElements) {
547
- const annotate = { annotate: artifactName };
548
- return collectAnnos(annotate, artWithElements) ? annotate : null;
525
+ function collectAnnotationsOfElements(artWithElements, env) {
526
+ // Array of structures, which may be annotated as well.
527
+ if (!artWithElements.elements && artWithElements.items) {
528
+ env = envAddPath(env, [ 'items' ]);
529
+ artWithElements = artWithElements.items;
530
+ }
531
+
532
+ const annotate = { annotate: env.path[1] };
533
+
534
+ // Based on the current path, create a correctly nested structure
535
+ // of elements for which we collect annotations.
536
+ // TODO: More properties?
537
+ let obj = annotate;
538
+ for (let i = 2; i < env.path.length; ++i) {
539
+ const key = env.path[i];
540
+ if (key === 'elements' || key === 'actions') {
541
+ obj[key] = Object.create(null);
542
+ const elem = env.path[i + 1];
543
+ obj[key][elem] = {};
544
+ obj = obj[key][elem];
545
+ }
546
+ else if (key === 'returns') {
547
+ obj.returns = {};
548
+ obj = obj.returns;
549
+ }
550
+ else {
551
+ // ignore others, e.g. 'items'
552
+ }
553
+ }
554
+ return collectAnnos(obj, artWithElements) ? annotate : null;
549
555
 
550
556
  /**
551
557
  * Recursive function to collect annotations. `annotateObj` will get an `elements`
@@ -663,8 +669,11 @@ function toCdsSourceCsn(csn, options) {
663
669
  if (path.ref[0].args)
664
670
  result += `(${renderArgs(path.ref[0], ':', env)})`;
665
671
 
666
- if (path.ref[0].where)
667
- result += `[${path.ref[0].cardinality ? (`${path.ref[0].cardinality.max}: `) : ''}${renderExpr(path.ref[0].where, env, true, true)}]`;
672
+ if (path.ref[0].where) {
673
+ const cardinality = path.ref[0].cardinality ? (`${path.ref[0].cardinality.max}: `) : '';
674
+ const expr = renderExpr(path.ref[0].where, env, true, true);
675
+ result += `[${cardinality}${expr}]`;
676
+ }
668
677
 
669
678
  // Add any path steps (possibly with parameters and filters) that may follow after that
670
679
  if (path.ref.length > 1)
@@ -749,7 +758,7 @@ function toCdsSourceCsn(csn, options) {
749
758
  if (col.cast.target && !col.cast.type)
750
759
  result += ` : ${renderRedirectedTo(col.cast, env)}`;
751
760
  else
752
- result += ` : ${renderTypeReference(col.cast, env, true)}`;
761
+ result += ` : ${renderTypeReferenceAndProps(col.cast, env, { typeRefOnly: true, noAnnoCollect: true })}`;
753
762
  }
754
763
  return result;
755
764
  }
@@ -758,58 +767,51 @@ function toCdsSourceCsn(csn, options) {
758
767
  * For the current column, render a (nested) inline/expand. If the current column
759
768
  * does not have an .expand/.inline, '' is returned
760
769
  *
761
- * @param {object} col Thing with .expand or .inline
762
- * @param {CdlRenderEnvironment} parentEnv
770
+ * @param {object} obj Thing with .expand or .inline
771
+ * @param {CdlRenderEnvironment} env
763
772
  * @returns {string}
764
773
  */
765
- function renderInlineExpand(col, parentEnv) {
766
- if (!col.inline && !col.expand)
767
- return '';
768
-
769
- return renderIX(col, parentEnv);
770
-
771
- function renderIX(obj, env) {
772
- // No expression to render for { * } as alias
773
- let result = (obj.as && obj.expand && !obj.ref) ? '' : renderExpr(obj, env);
774
-
775
- // s as alias { * }
776
- if (obj.as && (obj.ref || obj.xpr || obj.val !== undefined || obj.func !== undefined))
777
- result += ` as ${obj.as}`;
778
-
779
- // We found a leaf - no further drilling
780
- if (!obj.inline && !obj.expand) {
781
- if (obj.cast && obj.cast.type)
782
- result += ` : ${renderTypeReference(obj.cast, createEnv())}`;
783
- else if (obj.cast && obj.cast.target) // test tbd
784
- result += ` : ${renderRedirectedTo(obj.cast, env)}`;
785
- return result;
786
- }
774
+ function renderInlineExpand(obj, env) {
775
+ // No expression to render for { * } as alias
776
+ let result = (obj.as && obj.expand && !obj.ref) ? '' : renderExpr(obj, env);
777
+
778
+ // s as alias { * }
779
+ if (obj.as && (obj.ref || obj.xpr || obj.val !== undefined || obj.func !== undefined))
780
+ result += ` as ${obj.as}`;
781
+
782
+ // We found a leaf - no further drilling
783
+ if (!obj.inline && !obj.expand) {
784
+ if (obj.cast && obj.cast.type)
785
+ result += ` : ${renderTypeReferenceAndProps(obj.cast, createEnv(), { noAnnoCollect: true })}`;
786
+ else if (obj.cast && obj.cast.target) // test tbd
787
+ result += ` : ${renderRedirectedTo(obj.cast, env)}`;
788
+ return result;
789
+ }
787
790
 
788
- if (obj.inline)
789
- result += '.{\n';
790
- else
791
- result += result !== '' ? ' {\n' : '{\n';
791
+ if (obj.inline)
792
+ result += '.{\n';
793
+ else
794
+ result += result !== '' ? ' {\n' : '{\n';
792
795
 
793
- // Drill down and render children of the expand/inline
794
- const childEnv = increaseIndent(env);
795
- const expandInline = obj.expand || obj.inline;
796
- expandInline.forEach((elm, i) => {
797
- result += `${childEnv.indent}${renderIX(elm, childEnv)}`;
798
- if (i < expandInline.length - 1)
799
- result += ',\n';
800
- });
801
- result += `\n${env.indent}}`;
796
+ // Drill down and render children of the expand/inline
797
+ const childEnv = increaseIndent(env);
798
+ const expandInline = obj.expand || obj.inline;
799
+ expandInline.forEach((elm, i) => {
800
+ result += `${childEnv.indent}${renderInlineExpand(elm, childEnv)}`;
801
+ if (i < expandInline.length - 1)
802
+ result += ',\n';
803
+ });
804
+ result += `\n${env.indent}}`;
802
805
 
803
- // Don't forget about the .excluding
804
- if (obj.excluding)
805
- result += ` excluding { ${obj.excluding.join(',')} }`;
806
+ // Don't forget about the .excluding
807
+ if (obj.excluding)
808
+ result += ` excluding { ${obj.excluding.join(',')} }`;
806
809
 
807
- // { * } as expand
808
- if (!obj.ref && obj.as)
809
- result += ` as ${obj.as}`;
810
+ // { * } as expand
811
+ if (!obj.ref && obj.as)
812
+ result += ` as ${obj.as}`;
810
813
 
811
- return result;
812
- }
814
+ return result;
813
815
  }
814
816
 
815
817
  /**
@@ -847,15 +849,9 @@ function toCdsSourceCsn(csn, options) {
847
849
  const syntax = (art.projection) ? 'projection' : 'entity';
848
850
  let result = renderAnnotationAssignmentsAndDocComment(art, env);
849
851
  result += `${env.indent}${art.abstract ? 'abstract ' : ''}${syntax === 'projection' ? 'entity' : syntax} ${renderArtifactName(artifactName)}`;
850
- if (art.params) {
851
- const childEnv = increaseIndent(env);
852
- const parameters = Object.keys(art.params).map(name => renderParameter(name, art.params[name], childEnv)).join(',\n');
853
- result += `(\n${parameters}\n${env.indent}) as `;
854
- }
855
- else {
856
- result += ' as ';
857
- }
858
- env._artifact = art;
852
+ if (art.params)
853
+ result += renderParameters(art, env);
854
+ result += ' as ';
859
855
  result += renderQuery(getNormalizedQuery(art).query, true, syntax, env, [ 'definitions', artifactName, 'query' ], art.elements);
860
856
  result += ';\n';
861
857
  result += renderQueryElementAnnotations(artifactName, art, env);
@@ -955,7 +951,7 @@ function toCdsSourceCsn(csn, options) {
955
951
  }
956
952
 
957
953
  /**
958
- * Render a query's LIMIT clause, which may have also have OFFSET.
954
+ * Render a query's LIMIT clause, which may also have OFFSET.
959
955
  *
960
956
  * @param {CSN.QueryLimit} limit
961
957
  * @param {CdlRenderEnvironment} limitEnv
@@ -966,9 +962,10 @@ function toCdsSourceCsn(csn, options) {
966
962
  if (limit.rows !== undefined)
967
963
  limitStr += `limit ${renderExpr(limit.rows, limitEnv)}`;
968
964
 
969
- if (limit.offset !== undefined)
970
- limitStr += `${limitStr !== '' ? `\n${increaseIndent(limitEnv).indent}` : ''}offset ${renderExpr(limit.offset, limitEnv)}`;
971
-
965
+ if (limit.offset !== undefined) {
966
+ const offsetIndent = (limitStr === '') ? '' : `\n${increaseIndent(limitEnv).indent}`;
967
+ limitStr += `${offsetIndent}offset ${renderExpr(limit.offset, limitEnv)}`;
968
+ }
972
969
  return limitStr;
973
970
  }
974
971
 
@@ -1029,7 +1026,7 @@ function toCdsSourceCsn(csn, options) {
1029
1026
  let result = '';
1030
1027
  const childEnv = increaseIndent(env);
1031
1028
  for (const name in art.actions)
1032
- result += renderActionOrFunction(name, art.actions[name], childEnv);
1029
+ result += renderActionOrFunction(name, art.actions[name], envAddPath(childEnv, [ 'actions', name ]));
1033
1030
 
1034
1031
  // Even if we have seen actions/functions, they might all have been ignored
1035
1032
  if (result !== '')
@@ -1047,30 +1044,35 @@ function toCdsSourceCsn(csn, options) {
1047
1044
  * @return {string}
1048
1045
  */
1049
1046
  function renderActionOrFunction(actionName, act, env) {
1050
- let result = `${renderAnnotationAssignmentsAndDocComment(act, env) + env.indent + act.kind} ${renderArtifactName(actionName)}`;
1051
- const childEnv = increaseIndent(env);
1052
- const parameters = Object.keys(act.params || []).map(name => renderParameter(name, act.params[name], childEnv)).join(',\n');
1053
- result += (parameters === '') ? '()' : `(\n${parameters}\n${env.indent})`;
1047
+ let result = renderAnnotationAssignmentsAndDocComment(act, env) + env.indent + act.kind;
1048
+ result += ` ${renderArtifactName(actionName)}`;
1049
+ result += renderParameters(act, env);
1054
1050
  if (act.returns) {
1055
- if (act.returns.type && act.returns.elements) {
1056
- // Annotation in action returns' structure. Render it as an annotate statement.
1057
- const isBoundAction = (env.artifactName !== actionName);
1058
- const anno = {
1059
- annotate: env.artifactName || actionName,
1060
- };
1061
- if (isBoundAction)
1062
- anno.actions = { [actionName]: { returns: { elements: act.returns.elements } } };
1063
- else
1064
- anno.returns = { elements: act.returns.elements };
1065
- subelementAnnotates.push(anno);
1066
- }
1067
- result += ` returns ${renderTypeReference(act.returns, env)}${renderNullability(act.returns)}`;
1051
+ const actEnv = envAddPath(env, [ 'returns' ]);
1052
+ result += ` returns ${renderTypeReferenceAndProps(act.returns, actEnv)}`;
1068
1053
  }
1069
1054
 
1070
1055
  result += ';\n';
1071
1056
  return result;
1072
1057
  }
1073
1058
 
1059
+ /**
1060
+ * Render art.params, i.e. list of parameter in parentheses. If there is only one
1061
+ * parameter, a single line is used, otherwise an indented list is used.
1062
+ * If there are no params, an empty list `()` is returned.
1063
+ *
1064
+ * @param {CSN.Artifact} art
1065
+ * @param {CdlRenderEnvironment} env
1066
+ * @returns {string}
1067
+ */
1068
+ function renderParameters(art, env) {
1069
+ const childEnv = increaseIndent(env);
1070
+ const parameters = Object.keys(art.params || {}).map(name => renderParameter(name, art.params[name], childEnv));
1071
+ if (parameters.length === 0)
1072
+ return '()';
1073
+ return `(\n${parameters.join(',\n')}\n${env.indent})`;
1074
+ }
1075
+
1074
1076
  /**
1075
1077
  * Render an action or function parameter 'par' with name 'parName'. Return the resulting source string (no trailing LF).
1076
1078
  *
@@ -1080,11 +1082,9 @@ function toCdsSourceCsn(csn, options) {
1080
1082
  * @return {string}
1081
1083
  */
1082
1084
  function renderParameter(parName, par, env) {
1083
- let result = `${renderAnnotationAssignmentsAndDocComment(par, env) + env.indent + quoteIdIfRequired(parName)} : ${renderTypeReference(par, env)}`;
1084
- if (par.default)
1085
- result += ` default ${renderExpr(par.default, env)}`;
1086
-
1087
- result += renderNullability(par);
1085
+ env = envAddPath(env, [ 'params', parName ]);
1086
+ let result = `${renderAnnotationAssignmentsAndDocComment(par, env)}${env.indent}`;
1087
+ result += `${quoteIdIfRequired(parName)} : ${renderTypeReferenceAndProps(par, env)}`;
1088
1088
  return result;
1089
1089
  }
1090
1090
 
@@ -1095,7 +1095,7 @@ function toCdsSourceCsn(csn, options) {
1095
1095
  * @param {string} artifactName
1096
1096
  * @param {CSN.Artifact} art
1097
1097
  * @param {CdlRenderEnvironment} env
1098
- * @param {String} artType - used for rendering csn.vocabularies, as the annotations there do not have a kind. Only in toCdl mode
1098
+ * @param {String} [artType] - used for rendering csn.vocabularies, as the annotations there do not have a kind.
1099
1099
  * @return {string}
1100
1100
  */
1101
1101
  function renderTypeOrAnnotation(artifactName, art, env, artType) {
@@ -1104,96 +1104,73 @@ function toCdsSourceCsn(csn, options) {
1104
1104
  if (art.includes)
1105
1105
  result += renderIncludes(art.includes);
1106
1106
 
1107
- const childEnv = increaseIndent(env);
1108
- if (art.elements && !art.type) {
1109
- // Structured type or annotation with anonymous struct type
1110
- result += ' {\n';
1111
- for (const name in art.elements)
1112
- result += renderElement(name, art.elements[name], childEnv);
1113
-
1114
- result += `${env.indent}};\n`;
1115
- }
1116
- else {
1117
- // Derived type or annotation with non-anonymous type
1118
- result += ` : ${renderTypeReference(art, env, false)}`;
1119
- if (art.default)
1120
- result += ` default ${renderExpr(art.default, env)}`;
1121
- result += ';\n';
1122
- }
1107
+ if (!art.type && art.elements) // For nicer output, no colon if unnamed structure is used.
1108
+ result += ` ${renderTypeReferenceAndProps(art, env)}`;
1109
+ else
1110
+ result += ` : ${renderTypeReferenceAndProps(art, env)}`;
1111
+ // for aspects, but since types don't have `actions` this does not hurt
1112
+ result += `${renderActionsAndFunctions(art, env)};\n`;
1123
1113
  return result;
1124
1114
  }
1125
1115
 
1126
1116
  /**
1127
- * Render a reference to a type used by 'elm' (named or inline)
1128
- * Allow suppressing enum-rendering - used in columns for example
1117
+ * Render a reference to a type used by 'artifact' (named or inline) and (element) properties
1118
+ * such as `not null` and `default <xpr>`.
1119
+ * Allow suppressing rendering of structs such as enums - used in columns for example.
1129
1120
  *
1130
- * @param {CSN.Element} elm
1121
+ * @param {object} artifact
1131
1122
  * @param {CdlRenderEnvironment} env
1132
- * @param {boolean} [noEnum=false]
1123
+ * @param {object} [config={}] - `typeRefOnly` Whether to only render type defs, no arrayed/structured/enum.
1124
+ * - `noAnnoCollect` Do not collect annotations of sub-elements.
1133
1125
  * @return {string}
1134
1126
  */
1135
- function renderTypeReference(elm, env, noEnum = false) {
1127
+ function renderTypeReferenceAndProps(artifact, env, config = {}) {
1136
1128
  let result = '';
1137
-
1138
- // Array type: Render items instead
1139
- if (elm.items && !elm.type) {
1140
- // HANA CDS does not support keyword many
1141
- let rc = `many ${renderTypeReference(elm.items, env)}`;
1142
- if (elm.items.notNull != null)
1143
- rc += elm.items.notNull ? ' not null' : ' null';
1144
- // many sub element annotates
1145
- // TODO(andre): This does handle deeply nested elements because we take the full `elements`.
1146
- // But when is env.elementName set and when not?
1147
- if (elm.items.type && elm.items.elements && env.artifactName) {
1148
- const annotate = { annotate: env.artifactName };
1149
- if (env.elementName)
1150
- annotate.elements = { [env.elementName]: elm.items };
1151
- else
1152
- annotate.elements = elm.items.elements;
1153
- subelementAnnotates.push(annotate);
1154
- }
1155
-
1156
- return rc;
1129
+ const { typeRefOnly, noAnnoCollect } = config;
1130
+ let isTypeDef = env.path?.length === 2; // e.g [ 'definitions', typeDef ];
1131
+
1132
+ if (typeRefOnly && !artifact.type)
1133
+ throw new ModelError(`Expected artifact to have a type; in: ${env.artifactName}`);
1134
+
1135
+ if (artifact.localized) // works even for type definitions
1136
+ result += 'localized ';
1137
+
1138
+ if (!artifact.type && artifact.items) {
1139
+ result += 'many '; // alternative: 'array of'; but not used
1140
+ artifact = artifact.items;
1141
+ env = envAddPath(env, 'items');
1142
+ // element keywords allowed in MANY case; was an oversight when arrays were introduced.
1143
+ isTypeDef = false;
1144
+ // "many many" does not work in CDL, so we don't check for it.
1157
1145
  }
1158
1146
 
1159
- // FIXME: Is this a type attribute?
1160
- result += (elm.localized ? 'localized ' : '');
1161
-
1162
- // Anonymous structured type
1163
- if (!elm.type) {
1164
- if (!elm.elements)
1165
- throw new ModelError(`Missing type of: ${JSON.stringify(elm)}`);
1166
-
1147
+ if (!artifact.type && artifact.elements) {
1167
1148
  result += '{\n';
1168
- const childEnv = increaseIndent(env);
1169
- for (const name in elm.elements)
1170
- result += renderElement(name, elm.elements[name], childEnv, null);
1149
+ const childEnv = envAddPath(increaseIndent(env), 'items');
1150
+ for (const name in artifact.elements)
1151
+ result += renderElement(name, artifact.elements[name], childEnv, null);
1171
1152
 
1172
1153
  result += `${env.indent}}`;
1154
+ if (!isTypeDef)
1155
+ result += renderNullability(artifact);
1156
+ // structured default not possible at the moment
1173
1157
  return result;
1174
1158
  }
1175
1159
 
1176
- const comp = 'cds.Composition';
1177
1160
  // Association type
1178
- if ([ 'cds.Association', comp ].includes(elm.type)) {
1161
+ if (artifact.type === 'cds.Association' || artifact.type === 'cds.Composition') {
1162
+ const isComp = artifact.type === 'cds.Composition';
1179
1163
  // Type, cardinality and target; CAPire uses CamelCase
1180
- result += (elm.type === comp) ? 'Composition' : 'Association';
1181
-
1182
- if (isSimpleCardinality(elm.cardinality)) {
1183
- result += renderSimpleCardinality(elm);
1184
- }
1185
- else {
1186
- result += renderCardinality(elm.cardinality) +
1187
- ((elm.type === comp) ? ' of ' : ' to ');
1188
- }
1164
+ result += isComp ? 'Composition' : 'Association';
1165
+ result += renderCardinality(artifact);
1189
1166
 
1190
1167
  // `targetAspect` may be set by the core compiler and refers to the original named or unnamed aspect.
1191
1168
  // In parseCdl, `target` may still be an object containing elements. This would be replaced
1192
1169
  // by targetAspect in client CSN, but we can't rely on that.
1193
1170
  // If a name exists (either in target or targetAspect), prefer it over rendering elements.
1194
- const elements = elm.target && elm.target.elements || elm.targetAspect && elm.targetAspect.elements;
1195
- if (typeof elm.target === 'string' || typeof elm.targetAspect === 'string') {
1196
- result += renderAbsolutePath({ ref: [ elm.target || elm.targetAspect ] }, env);
1171
+ const elements = artifact.target && artifact.target.elements || artifact.targetAspect && artifact.targetAspect.elements;
1172
+ if (typeof artifact.target === 'string' || typeof artifact.targetAspect === 'string') {
1173
+ result += renderAbsolutePath({ ref: [ artifact.target || artifact.targetAspect ] }, env);
1197
1174
  }
1198
1175
  else if (elements) {
1199
1176
  // anonymous aspect, either parseCdl or client CSN.
@@ -1209,40 +1186,48 @@ function toCdsSourceCsn(csn, options) {
1209
1186
  }
1210
1187
 
1211
1188
  // ON-condition (if any)
1212
- if (elm.on)
1213
- result += ` on ${renderExpr(elm.on, env, true, true)}`;
1189
+ if (artifact.on)
1190
+ result += ` on ${renderExpr(artifact.on, env, true, true)}`;
1214
1191
 
1215
1192
 
1216
1193
  // Foreign keys (if any, unless we also have an ON_condition (which means we have been transformed from managed to unmanaged)
1217
- if (elm.keys && !elm.on)
1218
- result += ` { ${Object.keys(elm.keys).map(name => renderForeignKey(elm.keys[name], env)).join(', ')} }`;
1194
+ if (artifact.keys && !artifact.on)
1195
+ result += ` { ${Object.keys(artifact.keys).map(name => renderForeignKey(artifact.keys[name], env)).join(', ')} }`;
1196
+
1197
+ if (!isTypeDef && !artifact.on) // unmanaged associations can't be followed by "not null"
1198
+ result += renderNullability(artifact);
1199
+ // DEFAULT not possible here.
1219
1200
 
1220
1201
  return result;
1221
1202
  }
1222
1203
 
1223
- // Reference to another element
1224
- if (elm.type.ref) {
1225
- if (elm.enum) {
1226
- const source = artifactRef(elm.type);
1227
- if (!source.enum) {
1228
- // enum was defined at this element and not at the referenced one
1229
- result += renderAbsolutePath(elm.type, env) + renderEnum(elm.enum, env);
1230
- }
1231
- else {
1232
- result += renderAbsolutePath(elm.type, env);
1233
- }
1234
- }
1235
- else {
1236
- result += renderAbsolutePath(elm.type, env);
1237
- }
1238
- return result;
1204
+ // At this point, we will render a named type.
1205
+
1206
+ // If we have a type and elements, we may have sub-structure annotates that would
1207
+ // get lost if we only render the type name.
1208
+ // TODO: Can we annotate elements of targetAspect?
1209
+ // If so, move this block before the composition rendering.
1210
+ if (!noAnnoCollect && (artifact.elements || artifact.items?.elements)) {
1211
+ const annotate = collectAnnotationsOfElements(artifact, env);
1212
+ if (annotate)
1213
+ subelementAnnotates.push(annotate);
1239
1214
  }
1240
1215
 
1241
- // If we get here, it must be a named type
1242
- result += renderNamedTypeWithParameters(elm);
1216
+ // Reference to another artifact
1217
+ if (typeof artifact.type === 'string') {
1218
+ // If we get here, it must be a named type
1219
+ result += renderNamedTypeWithParameters(artifact);
1220
+ }
1221
+ else if (artifact.type?.ref) {
1222
+ result += renderAbsolutePath(artifact.type, env);
1223
+ }
1243
1224
 
1244
- if (elm.enum && !noEnum)
1245
- result += renderEnum(elm.enum, env);
1225
+ if (artifact.enum && !typeRefOnly)
1226
+ result += renderEnum(artifact.enum, env);
1227
+ if (!isTypeDef) // NOT NULL not possible for not-arrayed type definitions
1228
+ result += renderNullability(artifact);
1229
+ if (artifact.default)
1230
+ result += ` default ${renderExpr(artifact.default, env)}`;
1246
1231
 
1247
1232
  return result;
1248
1233
  }
@@ -1255,7 +1240,7 @@ function toCdsSourceCsn(csn, options) {
1255
1240
  * @return {string}
1256
1241
  */
1257
1242
  function renderRedirectedTo(art, env) {
1258
- let result = `redirected to ${quoteIdIfRequired(art.target)}`;
1243
+ let result = `redirected to ${quotePathIfRequired(art.target)}`;
1259
1244
  if (art.on)
1260
1245
  result += ` on ${renderExpr(art.on, env, true, true)}`;
1261
1246
  else if (art.keys)
@@ -1280,8 +1265,6 @@ function toCdsSourceCsn(csn, options) {
1280
1265
  result += artWithType.type.slice(4);
1281
1266
  }
1282
1267
  else {
1283
- // Simple absolute name
1284
- // Type names are never flattened (derived types are unraveled in HANA)
1285
1268
  result += renderArtifactName(artWithType.type);
1286
1269
  }
1287
1270
 
@@ -1346,7 +1329,7 @@ function toCdsSourceCsn(csn, options) {
1346
1329
  }
1347
1330
  // Shorthand for absolute path (as string)
1348
1331
  else if (x['=']) {
1349
- return quotePathString(x['=']);
1332
+ return quotePathIfRequired(x['=']);
1350
1333
  }
1351
1334
  // Shorthand for ellipsis: `... up to <val>`
1352
1335
  else if (x['...']) {
@@ -1355,11 +1338,15 @@ function toCdsSourceCsn(csn, options) {
1355
1338
  return `... up to ${renderAnnotationValue(x['...'], env)}`;
1356
1339
  }
1357
1340
 
1358
- // Struct value (can actually only occur within an array)
1359
-
1360
- // Note that we have to quote the struct keys here manually and not use quoteIdIfRequired, because they may even contain dots (yuc!)
1361
- // FIXME: Should that really be allowed?
1362
- return `{${Object.keys(x).map(key => `![${key}]: ${renderAnnotationValue(x[key], env)}`).join(', ')}}`;
1341
+ // Struct value (can currently only occur within an array)
1342
+ // Render as one-liner if there is at most one key. Render as multi-line
1343
+ // struct if there are more and use nicer indentation.
1344
+ const keys = Object.keys(x);
1345
+ const childEnv = keys.length <= 1 ? env : increaseIndent(env);
1346
+ const values = keys.map(key => `${quoteAnnotationPathIfRequired(key)}: ${renderAnnotationValue(x[key], childEnv)}`);
1347
+ if (values.length <= 1)
1348
+ return `{ ${values.join(', ')} }`;
1349
+ return `{\n${childEnv.indent}${values.join(`,\n${childEnv.indent}`)}\n${env.indent}}`;
1363
1350
  }
1364
1351
  // Null
1365
1352
  else if (x === null) {
@@ -1408,7 +1395,9 @@ function toCdsSourceCsn(csn, options) {
1408
1395
  }
1409
1396
  if (s.where) {
1410
1397
  // Filter, possibly with cardinality
1411
- result += `[${s.cardinality ? (`${s.cardinality.max}: `) : ''}${renderExpr(s.where, env, inline, true)}]`;
1398
+ const cardinality = s.cardinality ? (`${s.cardinality.max}: `) : '';
1399
+ const expr = renderExpr(s.where, env, inline, true);
1400
+ result += `[${cardinality}${expr}]`;
1412
1401
  }
1413
1402
 
1414
1403
  return result;
@@ -1430,12 +1419,18 @@ function toCdsSourceCsn(csn, options) {
1430
1419
  const args = node.args || [];
1431
1420
 
1432
1421
  // Positional arguments
1433
- if (Array.isArray(args))
1422
+ if (Array.isArray(args)) {
1423
+ const func = node.func?.toUpperCase();
1424
+ if (func)
1425
+ return args.map((arg, i) => renderArgument(arg, env, getKeywordsForSpecialFunctionArgument(func, i))).join(', ');
1426
+
1434
1427
  return args.map(arg => renderArgument(arg, env)).join(', ');
1428
+ }
1435
1429
 
1436
1430
  // Named arguments (object/dict)
1437
- else if (typeof args === 'object')
1431
+ else if (typeof args === 'object') {
1438
1432
  return Object.keys(args).map(key => `${quoteIdIfRequired(key)} ${sep} ${renderArgument(args[key], env)}`).join(', ');
1433
+ }
1439
1434
 
1440
1435
  throw new ModelError(`Unknown args: ${JSON.stringify(args)}`);
1441
1436
  }
@@ -1446,23 +1441,40 @@ function toCdsSourceCsn(csn, options) {
1446
1441
  *
1447
1442
  * @param {any} arg
1448
1443
  * @param {CdlRenderEnvironment} env
1444
+ * @param {string[]} additionalAllowedKeywords
1449
1445
  * @return {string}
1450
1446
  */
1451
- function renderArgument(arg, env) {
1447
+ function renderArgument(arg, env, additionalAllowedKeywords = []) {
1452
1448
  // If the argument is a xpr with e.g. `=`, it may require parentheses.
1453
1449
  // For nested xpr, `renderExpr()` will already add parentheses.
1454
- return renderExpr(arg, env, true, !isSimpleFunctionExpression(arg && arg.xpr), true);
1450
+ return renderExpr(arg, env, true, !isSimpleFunctionExpression(arg && arg.xpr, additionalAllowedKeywords), true);
1451
+ }
1452
+
1453
+ /**
1454
+ * Render an artifact's cardinality.
1455
+ *
1456
+ * @param artifact
1457
+ * @returns {string}
1458
+ */
1459
+ function renderCardinality(artifact) {
1460
+ if (isSimpleCardinality(artifact.cardinality))
1461
+ return renderSimpleCardinality(artifact);
1462
+ return renderBracketCardinality(artifact);
1455
1463
  }
1456
1464
 
1457
1465
  /**
1458
1466
  * Render a cardinality (only those parts that were actually provided)
1459
1467
  *
1460
- * @param {CSN.Cardinality} card
1468
+ * @param {CSN.Artifact} art
1461
1469
  * @return {string}
1462
1470
  */
1463
- function renderCardinality(card) {
1471
+ function renderBracketCardinality(art) {
1472
+ const isComp = art.type === 'cds.Composition';
1473
+ const suffix = (isComp ? ' of ' : ' to ');
1474
+ const card = art.cardinality;
1475
+
1464
1476
  if (!card)
1465
- return '';
1477
+ return suffix;
1466
1478
 
1467
1479
  let result = '[';
1468
1480
  if (card.src !== undefined)
@@ -1474,7 +1486,7 @@ function toCdsSourceCsn(csn, options) {
1474
1486
  if (card.max !== undefined)
1475
1487
  result += card.max;
1476
1488
 
1477
- return `${result}]`;
1489
+ return `${result}]${suffix}`;
1478
1490
  }
1479
1491
 
1480
1492
  /**
@@ -1528,7 +1540,8 @@ function toCdsSourceCsn(csn, options) {
1528
1540
  * @return {string}
1529
1541
  */
1530
1542
  function renderForeignKey(fKey, env) {
1531
- return `${renderExpr(fKey, env)}${fKey.as ? (` as ${fKey.as}`) : ''}`;
1543
+ const alias = fKey.as ? (` as ${fKey.as}`) : '';
1544
+ return renderExpr(fKey, env) + alias;
1532
1545
  }
1533
1546
 
1534
1547
  /**
@@ -1543,12 +1556,6 @@ function toCdsSourceCsn(csn, options) {
1543
1556
  if (params.length === 0)
1544
1557
  return '';
1545
1558
 
1546
- // TODO(v3): Remove special handling of srid
1547
- // For backwards compatibility, do not render srid as a named argument for POINT/GEOMETRY builtins.
1548
- if (artWithType.srid !== undefined &&
1549
- (artWithType.type === 'cds.hana.ST_POINT' || artWithType.type === 'cds.hana.ST_GEOMETRY'))
1550
- return `(${artWithType.srid})`;
1551
-
1552
1559
  // Special cases for 1 or 2 arguments.
1553
1560
  if (params.length === 1 && artWithType.length !== undefined)
1554
1561
  return `(${artWithType.length})`;
@@ -1603,6 +1610,7 @@ function toCdsSourceCsn(csn, options) {
1603
1610
  // We expand this pattern to also include dots after the first character.
1604
1611
  // If the annotation does not follow this pattern `ident(.@ident)*`, it must be quoted:
1605
1612
  // `@identifier@identifier` must be quoted but `@identifier.@identifier` should not.
1613
+ // TODO: Use quoteAnnotationPathIfRequired()
1606
1614
  const annoRequiresQuoting = !/^[$_a-zA-Z][$_a-zA-Z0-9.]*(?:\.@[$_a-zA-Z][$_a-zA-Z0-9.]*)*$/.test(nameBeforeVariant);
1607
1615
  // Unfortunately, the compiler does not allow `.` after the first variant identifier,
1608
1616
  // even though that is the result after flattening.
@@ -1624,77 +1632,47 @@ function toCdsSourceCsn(csn, options) {
1624
1632
  }
1625
1633
 
1626
1634
  /**
1627
- * Render the name of an artifact, using the current name prefix from 'env'
1628
- * and the real name of the artifact. In case of plain names, this
1629
- * is equivalent to simply flattening and uppercasing the whole name.
1630
- *
1631
- * In cdlMode, the prefix is extended to handle cases like an entity shadowing the prefix
1632
- * of another entity -> Service.E and Service.E.Sub
1633
- *
1634
- * To handle such cases for hdbcds in quoted/hdbcds, we:
1635
- * - Find the part of the name that is no longer prefix (context/service)
1636
- * - For Service.E -> E, for Service.E.Sub -> E.Sub
1637
- * - Replace all dots in this "real name" with underscores
1638
- * - Join with the env prefix
1635
+ * Render the name of an artifact, quote path steps if necessary.
1639
1636
  *
1640
1637
  * @param {string} artifactName Artifact name to render
1641
1638
  * @return {string} Artifact name ready for rendering
1642
1639
  */
1643
1640
  function renderArtifactName(artifactName) {
1644
- const realname = getRealName(artifactName);
1645
- const prefix = (realname !== artifactName) ? artifactName.slice(0, artifactName.length - realname.length - 1) : '';
1646
- return prefix ? `${quoteIdIfRequired(prefix)}.${realname.split('.').map(quoteIdIfRequired).join('.')}` : realname.split('.').map(quoteIdIfRequired).join('.');
1641
+ return quotePathIfRequired(artifactName);
1647
1642
  }
1648
1643
 
1649
1644
  /**
1650
- * Get the part that is really the name of this artifact and not just prefix caused by a context/service
1645
+ * Render a function expression.
1651
1646
  *
1652
- * @param {String} artifactName Artifact name to use
1653
- * @returns {String} non-prefix part of the artifact name
1647
+ * @param {object} obj Object with .func and optionally .args
1648
+ * @param {CdlRenderEnvironment} env
1649
+ * @returns {string}
1654
1650
  */
1655
- function getRealName(artifactName) {
1656
- const parts = artifactName.split('.');
1657
- // Length of 1 -> There can be no prefix
1658
- if (parts.length === 1)
1659
- return artifactName;
1660
-
1661
-
1662
- let seen = '';
1663
- for (let i = 0; i < parts.length; i++) {
1664
- if (seen !== '')
1665
- seen = `${seen}.${parts[i]}`;
1666
- else
1667
- seen = parts[i];
1668
-
1669
-
1670
- const art = csn.definitions[seen];
1671
- if (!art || (art.kind !== 'service' && art.kind !== 'context')) {
1672
- // We found a case where the prefix ended
1673
- // Return everything following
1674
- return parts.slice(i).join('.');
1675
- }
1676
- }
1677
-
1678
- // we seem to have a normal case - just return the last part
1679
- return getLastPartOf(artifactName);
1651
+ function renderFuncExpr( obj, env ) {
1652
+ if (keywords.cdl_functions.includes(obj.func.toUpperCase()))
1653
+ return obj.func;
1654
+ const name = identifierRegex.test(obj.func) ? obj.func : quote(obj.func);
1655
+ return `${name}(${renderArgs( obj, '=>', env )})`;
1680
1656
  }
1681
1657
 
1682
1658
  /**
1683
1659
  * Render an expression.
1684
1660
  *
1685
1661
  * @param {any} expr
1686
- * @param {CdlRenderEnvironment} env
1687
- * @param {boolean} [inline]
1688
- * @param {boolean} [nestedExpr]
1662
+ * @param {CdlRenderEnvironment} exprEnv
1663
+ * @param {boolean} [isInline]
1664
+ * @param {boolean} [isNestedExpr]
1689
1665
  * @param {boolean} [alwaysRenderCast]
1690
1666
  * @returns {string}
1691
1667
  */
1692
- function renderExpr(expr, env, inline, nestedExpr, alwaysRenderCast) {
1668
+ function renderExpr(expr, exprEnv, isInline, isNestedExpr, alwaysRenderCast) {
1693
1669
  if (!_renderExpr) {
1694
1670
  _renderExpr = getExpressionRenderer({
1695
- finalize: x => x,
1696
- explicitTypeCast: (x, env) => {
1697
- const typeRef = renderTypeReference(x.cast, env, true);
1671
+ finalize(x) {
1672
+ return x;
1673
+ },
1674
+ explicitTypeCast(x, env) {
1675
+ const typeRef = renderTypeReferenceAndProps(x.cast, env, { typeRefOnly: true, noAnnoCollect: true });
1698
1676
  const arg = { ...x, cast: null }; // "arg" without cast to avoid recursion.
1699
1677
  return `cast(${renderArgument(arg, env)} as ${typeRef})`;
1700
1678
  },
@@ -1723,42 +1701,40 @@ function toCdsSourceCsn(csn, options) {
1723
1701
  aliasOnly(x, _env) {
1724
1702
  return x.as;
1725
1703
  },
1726
- enum: x => `#${x['#']}`,
1704
+ enum(x) {
1705
+ return `#${x['#']}`;
1706
+ },
1727
1707
  ref(x, env) {
1728
1708
  const { inline } = this;
1729
1709
  return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, inline, env)).join('.')}`;
1730
1710
  },
1731
- windowFunction: (x, env) => {
1732
- const funcDef = renderFunc(x.func, x, null, a => renderArgs(a, '=>', env));
1711
+ windowFunction(x, env) {
1712
+ const funcDef = renderFuncExpr(x, env);
1733
1713
  const windowFunctionOperator = x.xpr.shift(); // OVER ...
1734
1714
  return `${funcDef} ${windowFunctionOperator} ( ${renderExpr(x.xpr, env, true)} )`;
1735
1715
  },
1736
- func: (x, env) => {
1737
- // FIXME: Why care about HANA identifier?
1738
- // test for non-regular HANA identifier that needs to be quoted
1739
- // identifier {letter}({letter_or_digit}|[#$])*
1740
- // letter [A-Za-z_]
1741
- // letter_or_digit [A-Za-z_0-9]
1742
-
1743
- const regex = /^[a-zA-Z][\w#$]*$/g;
1744
- const funcName = regex.test(x.func) ? x.func : quoteIdIfRequired(x.func);
1745
- return renderFunc(funcName, x, 'cap', a => renderArgs(a, '=>', env));
1716
+ func(x, env) {
1717
+ return renderFuncExpr(x, env);
1746
1718
  },
1747
1719
  xpr(x, env) {
1748
1720
  if (this.nestedExpr && !x.cast || x.xpr.some(s => s === 'exists'))
1749
1721
  return `(${renderExpr(x.xpr, env, this.inline, true)})`;
1750
-
1751
1722
  return renderExpr(x.xpr, env, this.inline, true);
1752
1723
  },
1753
1724
  // Sub-queries in expressions need to be in parentheses, otherwise
1754
1725
  // left-associativity of UNIONS may result in different results.
1755
1726
  // For example: `select from E where id in (select from E union select from E);`:
1756
1727
  // Without parentheses, it would be different query.
1757
- SET: (x, env) => `(${renderQuery(x, false, 'view', increaseIndent(env))})`,
1758
- SELECT: (x, env) => `(${renderQuery(x, false, 'view', increaseIndent(env))})`,
1728
+ SET(x, env) {
1729
+ return `(${renderQuery(x, false, 'view', increaseIndent(env))})`;
1730
+ },
1731
+ SELECT(x, env) {
1732
+ return `(${renderQuery(x, false, 'view', increaseIndent(env))})`;
1733
+ },
1759
1734
  });
1760
1735
  }
1761
- return _renderExpr(expr, env, inline, nestedExpr, alwaysRenderCast);
1736
+
1737
+ return _renderExpr(expr, exprEnv, isInline, isNestedExpr, alwaysRenderCast);
1762
1738
  }
1763
1739
  }
1764
1740
 
@@ -1772,15 +1748,19 @@ function createEnv() {
1772
1748
  return {
1773
1749
  // Current indentation string
1774
1750
  indent: '',
1775
- // Dictionary of aliases for used artifact names, each entry like 'name' : { quotedName, quotedAlias }
1776
- topLevelAliases: Object.create(null),
1777
- // Current name prefix (including trailing dot if not empty)
1778
- namePrefix: '',
1751
+ path: null,
1779
1752
  artifactName: '',
1780
1753
  elementName: '',
1781
1754
  };
1782
1755
  }
1783
1756
 
1757
+ function envAddPath(env, path) {
1758
+ return Object.assign({}, env, { path: [ ...env.path, ...path ] } );
1759
+ }
1760
+ function envNewPath(env, path) {
1761
+ return Object.assign({}, env, { path: [ ...path ] } );
1762
+ }
1763
+
1784
1764
  /**
1785
1765
  * Returns a copy of 'env' with increased indentation (and reset name prefix)
1786
1766
  *
@@ -1788,22 +1768,25 @@ function createEnv() {
1788
1768
  * @returns {CdlRenderEnvironment}
1789
1769
  */
1790
1770
  function increaseIndent(env) {
1791
- return Object.assign({}, env, { namePrefix: '', indent: `${env.indent} ` });
1771
+ return Object.assign({}, env, { indent: `${env.indent} ` });
1792
1772
  }
1793
1773
 
1794
1774
  /**
1795
- * Return a path string 'path' with appropriate "-quotes.
1775
+ * Quote the path steps with `![]` if necessary. For simple ids such as
1776
+ * `elem` use `quoteIdIfRequired` instead.
1796
1777
  *
1797
1778
  * @param {string} path
1798
1779
  * @returns {string}
1780
+ *
1781
+ * @todo For paths such as `E.key`, `key` does not have to be in quotes.
1799
1782
  */
1800
- function quotePathString(path) {
1801
- // "foo"."bar"."wiz"."blub"
1783
+ function quotePathIfRequired(path) {
1802
1784
  return path.split('.').map(quoteIdIfRequired).join('.');
1803
1785
  }
1804
1786
 
1805
1787
  /**
1806
- * Return an id 'id' with appropriate "-quotes
1788
+ * Quote the id with `![]` if necessary. For paths such as `E.key` use
1789
+ * `quotePathIfRequired` instead.
1807
1790
  *
1808
1791
  * @param {string} id
1809
1792
  * @return {string}
@@ -1816,6 +1799,23 @@ function quoteIdIfRequired(id) {
1816
1799
  return id;
1817
1800
  }
1818
1801
 
1802
+ /**
1803
+ * Quote an annotation path, e.g. `@My.@Anno.Description` if necessary.
1804
+ * `anno` can start with `@` but is not required to be.
1805
+ * Example of an annotation path that needs to be quoted:
1806
+ * `@![ spaces in path ].@!["double quotes"]`.
1807
+ *
1808
+ * @param {string} anno
1809
+ * @returns {string}
1810
+ */
1811
+ function quoteAnnotationPathIfRequired(anno) {
1812
+ return anno.split('.').map((segment) => {
1813
+ if (segment.startsWith('@'))
1814
+ return `@${quoteIdIfRequired(segment.slice(1))}`;
1815
+ return quoteIdIfRequired(segment);
1816
+ }).join('.');
1817
+ }
1818
+
1819
1819
  /**
1820
1820
  * Quotes the identifier using CDL-style ![]-quotes.
1821
1821
  *
@@ -1828,20 +1828,14 @@ function quote(id) {
1828
1828
 
1829
1829
  /**
1830
1830
  * Returns true if 'id' requires quotes for CDL, i.e. if 'id'
1831
- * 1. starts with a digit
1832
- * 2. contains chars different than:
1833
- * - uppercase letters
1834
- * - lowercase letters
1835
- * - digits
1836
- * - underscore
1837
- * 3. is a CDL keyword or a CDL function without parentheses (CURRENT_*, SYSUUID, ...)
1831
+ * does not match the first part of the `Identifier` rule of `language.g4`
1832
+ * or if 'id' is a reserved keyword.
1838
1833
  *
1839
1834
  * @param {string} id
1840
1835
  * @return {boolean}
1841
1836
  */
1842
1837
  function requiresQuotingForCdl(id) {
1843
- return /^\d/.test(id) ||
1844
- /\W/g.test(id.replace(/\./g, '')) ||
1838
+ return !identifierRegex.test(id) ||
1845
1839
  keywords.cdl.includes(id.toUpperCase()) ||
1846
1840
  keywords.cdl_functions.includes(id.toUpperCase());
1847
1841
  }
@@ -1865,18 +1859,52 @@ const functionExpressionOperatorsRequireParentheses = [
1865
1859
  * in a `fct(<xpr>)` expression such as `cast(<xpr> as Type)`. We only need to
1866
1860
  * look at the first nesting level. Otherwise, `renderExpr()` will already add parentheses.
1867
1861
  *
1868
- * The list was created by looking at the `expression` Antlr rule.
1862
+ * The list of `functionExpressionOperatorsRequireParentheses` was created by looking at
1863
+ * the `expression` Antlr rule.
1864
+ * Because of token-rewrites, there are functions that allow operators/tokens that would
1865
+ * require parentheses in other functions. For example *regex functions allow `IN` but
1866
+ * if `IN` is used in other functions, it requires parentheses. To allow for that case,
1867
+ * you can set `additionalAllowedKeywords` to list of tokens that are allowed.
1869
1868
  *
1870
- * This is more of a heuristic for "nicer" CDL output. For example the
1869
+ * Note that this is more of a heuristic for "nicer" CDL output. For example the
1871
1870
  * following snippet is parsable without parentheses:
1872
1871
  * `cast( case when int > 1 then int else 0 end as Integer ),`
1873
- * However, because it is a flat xpr-array, we see `>` and assume that it is not a simple expression.
1872
+ * However, because it is a flat xpr-array, we see `>` and assume that it is not
1873
+ * a simple expression.
1874
1874
  *
1875
1875
  * @param {any[]} xpr
1876
+ * @param {string[]} additionalAllowedKeywords
1876
1877
  * @return {boolean}
1877
1878
  */
1878
- function isSimpleFunctionExpression(xpr) {
1879
- return !xpr || xpr.every(val => typeof val !== 'string' || !functionExpressionOperatorsRequireParentheses.includes(val.toLowerCase()));
1879
+ function isSimpleFunctionExpression(xpr, additionalAllowedKeywords = []) {
1880
+ return !xpr || xpr.every(val => typeof val !== 'string' ||
1881
+ (additionalAllowedKeywords.includes(val) ||
1882
+ !functionExpressionOperatorsRequireParentheses.includes(val.toLowerCase())));
1883
+ }
1884
+
1885
+ /**
1886
+ * Special functions may have special parser rules, such as SAP HANA RegEx functions.
1887
+ * They allow certain keywords in their arguments.
1888
+ *
1889
+ * This function is used to determine if arguments need to be put in parentheses or not.
1890
+ * See {@link isSimpleFunctionExpression}.
1891
+ *
1892
+ * @param {string} funcName
1893
+ * @param {number} argumentIndex
1894
+ * @returns {string[]}
1895
+ */
1896
+ function getKeywordsForSpecialFunctionArgument(funcName, argumentIndex) {
1897
+ const f = specialFunctions[funcName] && specialFunctions[funcName][argumentIndex];
1898
+ if (!f)
1899
+ return [];
1900
+ const additionalKeywords = [];
1901
+ if (f.intro)
1902
+ additionalKeywords.push(...f.intro);
1903
+ if (f.expr)
1904
+ additionalKeywords.push(...f.expr);
1905
+ if (f.separator)
1906
+ additionalKeywords.push(...f.separator);
1907
+ return additionalKeywords;
1880
1908
  }
1881
1909
 
1882
1910
  /**
@@ -1884,7 +1912,7 @@ function isSimpleFunctionExpression(xpr) {
1884
1912
  * @return {string}
1885
1913
  */
1886
1914
  function renderIncludes(includes) {
1887
- return ` : ${includes.map(name => quoteIdIfRequired(name)).join(', ')}`;
1915
+ return ` : ${includes.map(name => quotePathIfRequired(name)).join(', ')}`;
1888
1916
  }
1889
1917
 
1890
1918
  /**
@@ -1965,17 +1993,9 @@ function isSimpleString(str) {
1965
1993
  * @typedef CdlRenderEnvironment Rendering environment used throughout the render process.
1966
1994
  *
1967
1995
  * @property {string} indent Current indentation as a string, e.g. ' ' for two spaces.
1968
- * @property {CSN.Path} [path] CSN path to the current artifact
1996
+ * @property {string[]} [path] CSN path to the current artifact
1969
1997
  * @property {string} [artifactName] Name of the artifact - set in renderArtifact
1970
1998
  * @property {string} [elementName] Name of the element being rendered - set in renderElement
1971
- * @property {{[name: string]: {
1972
- quotedName: string,
1973
- quotedAlias: string
1974
- }}} topLevelAliases Dictionary of aliases for used artifact names
1975
- *
1976
- * @property {string} [namePrefix] Current name prefix (including trailing dot if not empty)
1977
- * @property {boolean} [skipKeys]
1978
- * @property {CSN.Artifact} [_artifact]
1979
1999
  */
1980
2000
 
1981
- module.exports = { toCdsSourceCsn };
2001
+ module.exports = { csnToCdl };