@sap/cds-compiler 2.15.4 → 3.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 (105) hide show
  1. package/CHANGELOG.md +33 -1590
  2. package/bin/cdsc.js +36 -33
  3. package/doc/CHANGELOG_ARCHIVE.md +1592 -0
  4. package/doc/CHANGELOG_BETA.md +3 -4
  5. package/doc/CHANGELOG_DEPRECATED.md +35 -1
  6. package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
  7. package/doc/Versioning.md +20 -1
  8. package/lib/api/.eslintrc.json +2 -2
  9. package/lib/api/main.js +220 -103
  10. package/lib/api/options.js +15 -85
  11. package/lib/api/validate.js +6 -10
  12. package/lib/base/keywords.js +216 -109
  13. package/lib/base/message-registry.js +60 -20
  14. package/lib/base/messages.js +65 -24
  15. package/lib/base/model.js +44 -2
  16. package/lib/checks/actionsFunctions.js +7 -5
  17. package/lib/checks/annotationsOData.js +1 -1
  18. package/lib/checks/cdsPersistence.js +1 -0
  19. package/lib/checks/elements.js +6 -6
  20. package/lib/checks/invalidTarget.js +1 -1
  21. package/lib/checks/nonexpandableStructured.js +1 -1
  22. package/lib/checks/queryNoDbArtifacts.js +2 -1
  23. package/lib/checks/selectItems.js +5 -1
  24. package/lib/checks/types.js +4 -2
  25. package/lib/checks/utils.js +2 -2
  26. package/lib/checks/validator.js +2 -1
  27. package/lib/compiler/assert-consistency.js +15 -10
  28. package/lib/compiler/builtins.js +87 -9
  29. package/lib/compiler/define.js +2 -2
  30. package/lib/compiler/extend.js +59 -11
  31. package/lib/compiler/finalize-parse-cdl.js +20 -9
  32. package/lib/compiler/index.js +25 -11
  33. package/lib/compiler/moduleLayers.js +7 -0
  34. package/lib/compiler/populate.js +13 -13
  35. package/lib/compiler/propagator.js +3 -3
  36. package/lib/compiler/resolve.js +193 -218
  37. package/lib/compiler/shared.js +47 -76
  38. package/lib/compiler/tweak-assocs.js +9 -10
  39. package/lib/compiler/utils.js +5 -0
  40. package/lib/edm/csn2edm.js +18 -21
  41. package/lib/edm/edmPreprocessor.js +25 -30
  42. package/lib/edm/edmUtils.js +10 -24
  43. package/lib/gen/language.checksum +1 -1
  44. package/lib/gen/language.interp +8 -30
  45. package/lib/gen/language.tokens +105 -114
  46. package/lib/gen/languageLexer.interp +1 -34
  47. package/lib/gen/languageLexer.js +889 -1007
  48. package/lib/gen/languageLexer.tokens +95 -106
  49. package/lib/gen/languageParser.js +20632 -22313
  50. package/lib/json/from-csn.js +56 -49
  51. package/lib/json/to-csn.js +10 -8
  52. package/lib/language/antlrParser.js +2 -2
  53. package/lib/language/docCommentParser.js +61 -38
  54. package/lib/language/errorStrategy.js +52 -40
  55. package/lib/language/genericAntlrParser.js +303 -229
  56. package/lib/language/language.g4 +573 -629
  57. package/lib/language/multiLineStringParser.js +14 -42
  58. package/lib/language/textUtils.js +44 -0
  59. package/lib/main.d.ts +27 -42
  60. package/lib/main.js +104 -81
  61. package/lib/model/csnRefs.js +1 -1
  62. package/lib/model/csnUtils.js +170 -283
  63. package/lib/model/revealInternalProperties.js +28 -8
  64. package/lib/model/sortViews.js +32 -31
  65. package/lib/optionProcessor.js +12 -21
  66. package/lib/render/.eslintrc.json +1 -1
  67. package/lib/render/DuplicateChecker.js +4 -7
  68. package/lib/render/manageConstraints.js +70 -2
  69. package/lib/render/toCdl.js +334 -339
  70. package/lib/render/toHdbcds.js +19 -15
  71. package/lib/render/toRename.js +44 -22
  72. package/lib/render/toSql.js +53 -51
  73. package/lib/render/utils/common.js +15 -1
  74. package/lib/render/utils/sql.js +20 -19
  75. package/lib/sql-identifier.js +6 -0
  76. package/lib/transform/db/.eslintrc.json +3 -2
  77. package/lib/transform/db/cdsPersistence.js +5 -15
  78. package/lib/transform/db/constraints.js +1 -1
  79. package/lib/transform/db/expansion.js +7 -6
  80. package/lib/transform/db/flattening.js +18 -19
  81. package/lib/transform/db/views.js +3 -3
  82. package/lib/transform/draft/.eslintrc.json +2 -2
  83. package/lib/transform/draft/db.js +6 -6
  84. package/lib/transform/draft/odata.js +6 -7
  85. package/lib/transform/forHanaNew.js +19 -22
  86. package/lib/transform/forOdataNew.js +10 -12
  87. package/lib/transform/localized.js +22 -16
  88. package/lib/transform/odata/toFinalBaseType.js +10 -10
  89. package/lib/transform/odata/typesExposure.js +3 -3
  90. package/lib/transform/odata/utils.js +1 -38
  91. package/lib/transform/transformUtilsNew.js +63 -77
  92. package/lib/transform/translateAssocsToJoins.js +2 -2
  93. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  94. package/lib/transform/universalCsn/coreComputed.js +11 -6
  95. package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
  96. package/lib/utils/file.js +3 -3
  97. package/lib/utils/timetrace.js +20 -21
  98. package/package.json +35 -4
  99. package/doc/ApiMigration.md +0 -237
  100. package/doc/CommandLineMigration.md +0 -58
  101. package/doc/ErrorMessages.md +0 -175
  102. package/doc/FioriAnnotations.md +0 -94
  103. package/doc/ODataTransformation.md +0 -273
  104. package/lib/backends.js +0 -529
  105. 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
  }
@@ -147,7 +146,7 @@ function toCdsSourceCsn(csn, options) {
147
146
  // are possible through CSN, but in CDL, only one include at once is possible.
148
147
  const affix = isElementExtend ? 'element ' : '';
149
148
  for (const id of ext.includes)
150
- result += `${env.indent}extend ${affix}${extName} with ${quoteIdIfRequired(id)};\n`;
149
+ result += `${env.indent}extend ${affix}${extName} with ${quotePathIfRequired(id)};\n`;
151
150
  return result;
152
151
  }
153
152
 
@@ -293,7 +292,6 @@ function toCdsSourceCsn(csn, options) {
293
292
  result += `${env.indent}}`;
294
293
  }
295
294
 
296
-
297
295
  result += ';\n';
298
296
  return result;
299
297
  }
@@ -348,24 +346,22 @@ function toCdsSourceCsn(csn, options) {
348
346
  * @param {CdlRenderEnvironment} env
349
347
  */
350
348
  function renderArtifact(artifactName, art, env) {
351
- // FIXME: Correctly build the paths during runtime to give better locations
352
- env.path = [ 'definitions', artifactName ];
349
+ env = envNewPath(env, [ 'definitions', artifactName ]);
353
350
  env.artifactName = artifactName;
354
351
 
355
352
  switch (art.kind) {
356
353
  case 'entity':
357
354
  if (art.query || art.projection)
358
355
  return renderView(artifactName, art, env);
359
-
360
356
  return renderEntity(artifactName, art, env);
361
357
 
362
358
  case 'context':
363
359
  case 'service':
364
- return renderContext(artifactName, art, env);
360
+ return renderContextOrService(artifactName, art, env);
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);
369
365
  case 'action':
370
366
  case 'function':
371
367
  return renderActionOrFunction(artifactName, art, env);
@@ -389,16 +385,14 @@ function toCdsSourceCsn(csn, options) {
389
385
  if (art.includes)
390
386
  result += renderIncludes(art.includes);
391
387
  if (art.query || art.projection) {
392
- env._artifact = art;
393
388
  result += ' : ';
394
389
  result += renderQuery(getNormalizedQuery(art).query, true, 'projection', env,
395
390
  [ 'definitions', artifactName, 'query' ]);
396
391
  result += ';\n';
397
- delete env._artifact;
398
392
  }
399
393
  else if (art.type) {
400
394
  // Derived type or annotation with non-anonymous type
401
- result += ` : ${renderTypeReference(art, env)};\n`;
395
+ result += ` : ${renderTypeReferenceAndProps(art, env)};\n`;
402
396
  }
403
397
  else if (art.elements) {
404
398
  result += ' {\n';
@@ -411,18 +405,14 @@ function toCdsSourceCsn(csn, options) {
411
405
  }
412
406
 
413
407
  /**
414
- * Render a context or service. Return the resulting source string.
415
- *
416
408
  * @param {string} artifactName
417
409
  * @param {CSN.Artifact} art
418
410
  * @param {CdlRenderEnvironment} env
411
+ * @returns {string}
419
412
  */
420
- function renderContext(artifactName, art, env) {
413
+ function renderContextOrService(artifactName, art, env) {
421
414
  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
-
415
+ result += `${env.indent}${art.kind} ${renderArtifactName(artifactName)}`;
426
416
  return `${result} {};\n`;
427
417
  }
428
418
 
@@ -446,18 +436,6 @@ function toCdsSourceCsn(csn, options) {
446
436
  result += ' {\n';
447
437
  for (const name in art.elements) {
448
438
  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
439
  result += renderElement(name, element, childEnv);
462
440
  }
463
441
 
@@ -476,19 +454,15 @@ function toCdsSourceCsn(csn, options) {
476
454
  * @param {Boolean} [isSubElement]
477
455
  */
478
456
  function renderElement(elementName, elm, env, isSubElement) {
479
- env.elementName = elementName;
457
+ env = envAddPath(env, [ 'elements', elementName ]);
480
458
  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;
459
+ result += env.indent;
460
+ result += elm.virtual ? 'virtual ' : '';
461
+ result += elm.key && !isSubElement ? 'key ' : '';
462
+ // TODO(v4): Remove once deprecated flag for `masked` is removed.
463
+ result += elm.masked ? 'masked ' : '';
464
+ result += `${quoteIdIfRequired(elementName)} : ${renderTypeReferenceAndProps(elm, env)}`;
465
+
492
466
  return `${result};\n`;
493
467
  }
494
468
 
@@ -529,7 +503,7 @@ function toCdsSourceCsn(csn, options) {
529
503
  * @return {string}
530
504
  */
531
505
  function renderQueryElementAnnotations(artifactName, art, env) {
532
- const annotate = collectAnnotationsOfElements(artifactName, art);
506
+ const annotate = collectAnnotationsOfElements(art, { artifactName, path: env.path });
533
507
  if (annotate)
534
508
  return renderExtensions([ annotate ], env);
535
509
  return '';
@@ -539,13 +513,40 @@ function toCdsSourceCsn(csn, options) {
539
513
  * Create an "annotate" statement as a CSN extension for all annotations of (sub-)elements.
540
514
  * If no annotation was found, we return `null`.
541
515
  *
542
- * @param {string} artifactName
543
516
  * @param {CSN.Artifact} artWithElements
517
+ * @param {CdlRenderEnvironment} env
544
518
  * @return {CSN.Extension|null}
545
519
  */
546
- function collectAnnotationsOfElements(artifactName, artWithElements) {
547
- const annotate = { annotate: artifactName };
548
- return collectAnnos(annotate, artWithElements) ? annotate : null;
520
+ function collectAnnotationsOfElements(artWithElements, env) {
521
+ // Array of structures, which may be annotated as well.
522
+ if (!artWithElements.elements && artWithElements.items) {
523
+ env = envAddPath(env, [ 'items' ]);
524
+ artWithElements = artWithElements.items;
525
+ }
526
+
527
+ const annotate = { annotate: env.path[1] };
528
+
529
+ // Based on the current path, create a correctly nested structure
530
+ // of elements for which we collect annotations.
531
+ // TODO: More properties?
532
+ let obj = annotate;
533
+ for (let i = 2; i < env.path.length; ++i) {
534
+ const key = env.path[i];
535
+ if (key === 'elements' || key === 'actions') {
536
+ obj[key] = Object.create(null);
537
+ const elem = env.path[i + 1];
538
+ obj[key][elem] = {};
539
+ obj = obj[key][elem];
540
+ }
541
+ else if (key === 'returns') {
542
+ obj.returns = {};
543
+ obj = obj.returns;
544
+ }
545
+ else {
546
+ // ignore others, e.g. 'items'
547
+ }
548
+ }
549
+ return collectAnnos(obj, artWithElements) ? annotate : null;
549
550
 
550
551
  /**
551
552
  * Recursive function to collect annotations. `annotateObj` will get an `elements`
@@ -749,7 +750,7 @@ function toCdsSourceCsn(csn, options) {
749
750
  if (col.cast.target && !col.cast.type)
750
751
  result += ` : ${renderRedirectedTo(col.cast, env)}`;
751
752
  else
752
- result += ` : ${renderTypeReference(col.cast, env, true)}`;
753
+ result += ` : ${renderTypeReferenceAndProps(col.cast, env, { typeRefOnly: true, noAnnoCollect: true })}`;
753
754
  }
754
755
  return result;
755
756
  }
@@ -758,58 +759,51 @@ function toCdsSourceCsn(csn, options) {
758
759
  * For the current column, render a (nested) inline/expand. If the current column
759
760
  * does not have an .expand/.inline, '' is returned
760
761
  *
761
- * @param {object} col Thing with .expand or .inline
762
- * @param {CdlRenderEnvironment} parentEnv
762
+ * @param {object} obj Thing with .expand or .inline
763
+ * @param {CdlRenderEnvironment} env
763
764
  * @returns {string}
764
765
  */
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
- }
766
+ function renderInlineExpand(obj, env) {
767
+ // No expression to render for { * } as alias
768
+ let result = (obj.as && obj.expand && !obj.ref) ? '' : renderExpr(obj, env);
769
+
770
+ // s as alias { * }
771
+ if (obj.as && (obj.ref || obj.xpr || obj.val !== undefined || obj.func !== undefined))
772
+ result += ` as ${obj.as}`;
773
+
774
+ // We found a leaf - no further drilling
775
+ if (!obj.inline && !obj.expand) {
776
+ if (obj.cast && obj.cast.type)
777
+ result += ` : ${renderTypeReferenceAndProps(obj.cast, createEnv(), { noAnnoCollect: true })}`;
778
+ else if (obj.cast && obj.cast.target) // test tbd
779
+ result += ` : ${renderRedirectedTo(obj.cast, env)}`;
780
+ return result;
781
+ }
787
782
 
788
- if (obj.inline)
789
- result += '.{\n';
790
- else
791
- result += result !== '' ? ' {\n' : '{\n';
783
+ if (obj.inline)
784
+ result += '.{\n';
785
+ else
786
+ result += result !== '' ? ' {\n' : '{\n';
792
787
 
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}}`;
788
+ // Drill down and render children of the expand/inline
789
+ const childEnv = increaseIndent(env);
790
+ const expandInline = obj.expand || obj.inline;
791
+ expandInline.forEach((elm, i) => {
792
+ result += `${childEnv.indent}${renderInlineExpand(elm, childEnv)}`;
793
+ if (i < expandInline.length - 1)
794
+ result += ',\n';
795
+ });
796
+ result += `\n${env.indent}}`;
802
797
 
803
- // Don't forget about the .excluding
804
- if (obj.excluding)
805
- result += ` excluding { ${obj.excluding.join(',')} }`;
798
+ // Don't forget about the .excluding
799
+ if (obj.excluding)
800
+ result += ` excluding { ${obj.excluding.join(',')} }`;
806
801
 
807
- // { * } as expand
808
- if (!obj.ref && obj.as)
809
- result += ` as ${obj.as}`;
802
+ // { * } as expand
803
+ if (!obj.ref && obj.as)
804
+ result += ` as ${obj.as}`;
810
805
 
811
- return result;
812
- }
806
+ return result;
813
807
  }
814
808
 
815
809
  /**
@@ -855,7 +849,6 @@ function toCdsSourceCsn(csn, options) {
855
849
  else {
856
850
  result += ' as ';
857
851
  }
858
- env._artifact = art;
859
852
  result += renderQuery(getNormalizedQuery(art).query, true, syntax, env, [ 'definitions', artifactName, 'query' ], art.elements);
860
853
  result += ';\n';
861
854
  result += renderQueryElementAnnotations(artifactName, art, env);
@@ -955,7 +948,7 @@ function toCdsSourceCsn(csn, options) {
955
948
  }
956
949
 
957
950
  /**
958
- * Render a query's LIMIT clause, which may have also have OFFSET.
951
+ * Render a query's LIMIT clause, which may also have OFFSET.
959
952
  *
960
953
  * @param {CSN.QueryLimit} limit
961
954
  * @param {CdlRenderEnvironment} limitEnv
@@ -1029,7 +1022,7 @@ function toCdsSourceCsn(csn, options) {
1029
1022
  let result = '';
1030
1023
  const childEnv = increaseIndent(env);
1031
1024
  for (const name in art.actions)
1032
- result += renderActionOrFunction(name, art.actions[name], childEnv);
1025
+ result += renderActionOrFunction(name, art.actions[name], envAddPath(childEnv, [ 'actions', name ]));
1033
1026
 
1034
1027
  // Even if we have seen actions/functions, they might all have been ignored
1035
1028
  if (result !== '')
@@ -1052,19 +1045,8 @@ function toCdsSourceCsn(csn, options) {
1052
1045
  const parameters = Object.keys(act.params || []).map(name => renderParameter(name, act.params[name], childEnv)).join(',\n');
1053
1046
  result += (parameters === '') ? '()' : `(\n${parameters}\n${env.indent})`;
1054
1047
  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)}`;
1048
+ const actEnv = envAddPath(env, [ 'returns' ]);
1049
+ result += ` returns ${renderTypeReferenceAndProps(act.returns, actEnv)}`;
1068
1050
  }
1069
1051
 
1070
1052
  result += ';\n';
@@ -1080,11 +1062,9 @@ function toCdsSourceCsn(csn, options) {
1080
1062
  * @return {string}
1081
1063
  */
1082
1064
  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);
1065
+ env = envAddPath(env, [ 'params', parName ]);
1066
+ let result = `${renderAnnotationAssignmentsAndDocComment(par, env)}${env.indent}`;
1067
+ result += `${quoteIdIfRequired(parName)} : ${renderTypeReferenceAndProps(par, env)}`;
1088
1068
  return result;
1089
1069
  }
1090
1070
 
@@ -1095,7 +1075,7 @@ function toCdsSourceCsn(csn, options) {
1095
1075
  * @param {string} artifactName
1096
1076
  * @param {CSN.Artifact} art
1097
1077
  * @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
1078
+ * @param {String} [artType] - used for rendering csn.vocabularies, as the annotations there do not have a kind. Only in toCdl mode
1099
1079
  * @return {string}
1100
1080
  */
1101
1081
  function renderTypeOrAnnotation(artifactName, art, env, artType) {
@@ -1104,96 +1084,71 @@ function toCdsSourceCsn(csn, options) {
1104
1084
  if (art.includes)
1105
1085
  result += renderIncludes(art.includes);
1106
1086
 
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
- }
1087
+ if (!art.type && art.elements) // For nicer output, no colon if unnamed structure is used.
1088
+ result += ` ${renderTypeReferenceAndProps(art, env)};\n`;
1089
+ else
1090
+ result += ` : ${renderTypeReferenceAndProps(art, env)};\n`;
1123
1091
  return result;
1124
1092
  }
1125
1093
 
1126
1094
  /**
1127
- * Render a reference to a type used by 'elm' (named or inline)
1128
- * Allow suppressing enum-rendering - used in columns for example
1095
+ * Render a reference to a type used by 'artifact' (named or inline) and (element) properties
1096
+ * such as `not null` and `default <xpr>`.
1097
+ * Allow suppressing rendering of structs such as enums - used in columns for example.
1129
1098
  *
1130
- * @param {CSN.Element} elm
1099
+ * @param {object} artifact
1131
1100
  * @param {CdlRenderEnvironment} env
1132
- * @param {boolean} [noEnum=false]
1101
+ * @param {object} [config={}] - `typeRefOnly` Whether to only render type defs, no arrayed/structured/enum.
1102
+ * - `noAnnoCollect` Do not collect annotations of sub-elements.
1133
1103
  * @return {string}
1134
1104
  */
1135
- function renderTypeReference(elm, env, noEnum = false) {
1105
+ function renderTypeReferenceAndProps(artifact, env, config = {}) {
1136
1106
  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;
1107
+ const { typeRefOnly, noAnnoCollect } = config;
1108
+ let isTypeDef = env.path?.length === 2; // e.g [ 'definitions', typeDef ];
1109
+
1110
+ if (typeRefOnly && !artifact.type)
1111
+ throw new ModelError(`Expected artifact to have a type; in: ${env.artifactName}`);
1112
+
1113
+ if (artifact.localized) // works even for type definitions
1114
+ result += 'localized ';
1115
+
1116
+ if (!artifact.type && artifact.items) {
1117
+ result += 'many '; // alternative: 'array of'; but not used
1118
+ artifact = artifact.items;
1119
+ env = envAddPath(env, 'items');
1120
+ // element keywords allowed in MANY case; was an oversight when arrays were introduced.
1121
+ isTypeDef = false;
1122
+ // "many many" does not work in CDL, so we don't check for it.
1157
1123
  }
1158
1124
 
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
-
1125
+ if (!artifact.type && artifact.elements) {
1167
1126
  result += '{\n';
1168
- const childEnv = increaseIndent(env);
1169
- for (const name in elm.elements)
1170
- result += renderElement(name, elm.elements[name], childEnv, null);
1127
+ const childEnv = envAddPath(increaseIndent(env), 'items');
1128
+ for (const name in artifact.elements)
1129
+ result += renderElement(name, artifact.elements[name], childEnv, null);
1171
1130
 
1172
1131
  result += `${env.indent}}`;
1132
+ if (!isTypeDef)
1133
+ result += renderNullability(artifact);
1134
+ // structured default not possible at the moment
1173
1135
  return result;
1174
1136
  }
1175
1137
 
1176
- const comp = 'cds.Composition';
1177
1138
  // Association type
1178
- if ([ 'cds.Association', comp ].includes(elm.type)) {
1139
+ if (artifact.type === 'cds.Association' || artifact.type === 'cds.Composition') {
1140
+ const isComp = artifact.type === 'cds.Composition';
1179
1141
  // 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
- }
1142
+ result += isComp ? 'Composition' : 'Association';
1143
+ result += renderCardinality(artifact);
1189
1144
 
1190
1145
  // `targetAspect` may be set by the core compiler and refers to the original named or unnamed aspect.
1191
1146
  // In parseCdl, `target` may still be an object containing elements. This would be replaced
1192
1147
  // by targetAspect in client CSN, but we can't rely on that.
1193
1148
  // 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);
1149
+ const elements = artifact.target && artifact.target.elements || artifact.targetAspect && artifact.targetAspect.elements;
1150
+ if (typeof artifact.target === 'string' || typeof artifact.targetAspect === 'string') {
1151
+ result += renderAbsolutePath({ ref: [ artifact.target || artifact.targetAspect ] }, env);
1197
1152
  }
1198
1153
  else if (elements) {
1199
1154
  // anonymous aspect, either parseCdl or client CSN.
@@ -1209,40 +1164,48 @@ function toCdsSourceCsn(csn, options) {
1209
1164
  }
1210
1165
 
1211
1166
  // ON-condition (if any)
1212
- if (elm.on)
1213
- result += ` on ${renderExpr(elm.on, env, true, true)}`;
1167
+ if (artifact.on)
1168
+ result += ` on ${renderExpr(artifact.on, env, true, true)}`;
1214
1169
 
1215
1170
 
1216
1171
  // 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(', ')} }`;
1172
+ if (artifact.keys && !artifact.on)
1173
+ result += ` { ${Object.keys(artifact.keys).map(name => renderForeignKey(artifact.keys[name], env)).join(', ')} }`;
1174
+
1175
+ if (!isTypeDef && !artifact.on) // unmanaged associations can't be followed by "not null"
1176
+ result += renderNullability(artifact);
1177
+ // DEFAULT not possible here.
1219
1178
 
1220
1179
  return result;
1221
1180
  }
1222
1181
 
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;
1182
+ // At this point, we will render a named type.
1183
+
1184
+ // If we have a type and elements, we may have sub-structure annotates that would
1185
+ // get lost if we only render the type name.
1186
+ // TODO: Can we annotate elements of targetAspect?
1187
+ // If so, move this block before the composition rendering.
1188
+ if (!noAnnoCollect && (artifact.elements || artifact.items?.elements)) {
1189
+ const annotate = collectAnnotationsOfElements(artifact, env);
1190
+ if (annotate)
1191
+ subelementAnnotates.push(annotate);
1239
1192
  }
1240
1193
 
1241
- // If we get here, it must be a named type
1242
- result += renderNamedTypeWithParameters(elm);
1194
+ // Reference to another artifact
1195
+ if (typeof artifact.type === 'string') {
1196
+ // If we get here, it must be a named type
1197
+ result += renderNamedTypeWithParameters(artifact);
1198
+ }
1199
+ else if (artifact.type?.ref) {
1200
+ result += renderAbsolutePath(artifact.type, env);
1201
+ }
1243
1202
 
1244
- if (elm.enum && !noEnum)
1245
- result += renderEnum(elm.enum, env);
1203
+ if (artifact.enum && !typeRefOnly)
1204
+ result += renderEnum(artifact.enum, env);
1205
+ if (!isTypeDef) // NOT NULL not possible for not-arrayed type definitions
1206
+ result += renderNullability(artifact);
1207
+ if (artifact.default)
1208
+ result += ` default ${renderExpr(artifact.default, env)}`;
1246
1209
 
1247
1210
  return result;
1248
1211
  }
@@ -1255,7 +1218,7 @@ function toCdsSourceCsn(csn, options) {
1255
1218
  * @return {string}
1256
1219
  */
1257
1220
  function renderRedirectedTo(art, env) {
1258
- let result = `redirected to ${quoteIdIfRequired(art.target)}`;
1221
+ let result = `redirected to ${quotePathIfRequired(art.target)}`;
1259
1222
  if (art.on)
1260
1223
  result += ` on ${renderExpr(art.on, env, true, true)}`;
1261
1224
  else if (art.keys)
@@ -1280,8 +1243,6 @@ function toCdsSourceCsn(csn, options) {
1280
1243
  result += artWithType.type.slice(4);
1281
1244
  }
1282
1245
  else {
1283
- // Simple absolute name
1284
- // Type names are never flattened (derived types are unraveled in HANA)
1285
1246
  result += renderArtifactName(artWithType.type);
1286
1247
  }
1287
1248
 
@@ -1346,7 +1307,7 @@ function toCdsSourceCsn(csn, options) {
1346
1307
  }
1347
1308
  // Shorthand for absolute path (as string)
1348
1309
  else if (x['=']) {
1349
- return quotePathString(x['=']);
1310
+ return quotePathIfRequired(x['=']);
1350
1311
  }
1351
1312
  // Shorthand for ellipsis: `... up to <val>`
1352
1313
  else if (x['...']) {
@@ -1355,11 +1316,15 @@ function toCdsSourceCsn(csn, options) {
1355
1316
  return `... up to ${renderAnnotationValue(x['...'], env)}`;
1356
1317
  }
1357
1318
 
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(', ')}}`;
1319
+ // Struct value (can currently only occur within an array)
1320
+ // Render as one-liner if there is at most one key. Render as multi-line
1321
+ // struct if there are more and use nicer indentation.
1322
+ const keys = Object.keys(x);
1323
+ const childEnv = keys.length <= 1 ? env : increaseIndent(env);
1324
+ const values = keys.map(key => `${quoteAnnotationPathIfRequired(key)}: ${renderAnnotationValue(x[key], childEnv)}`);
1325
+ if (values.length <= 1)
1326
+ return `{ ${values.join(', ')} }`;
1327
+ return `{\n${childEnv.indent}${values.join(`,\n${childEnv.indent}`)}\n${env.indent}}`;
1363
1328
  }
1364
1329
  // Null
1365
1330
  else if (x === null) {
@@ -1430,12 +1395,18 @@ function toCdsSourceCsn(csn, options) {
1430
1395
  const args = node.args || [];
1431
1396
 
1432
1397
  // Positional arguments
1433
- if (Array.isArray(args))
1398
+ if (Array.isArray(args)) {
1399
+ const func = node.func?.toUpperCase();
1400
+ if (func)
1401
+ return args.map((arg, i) => renderArgument(arg, env, getKeywordsForSpecialFunctionArgument(func, i))).join(', ');
1402
+
1434
1403
  return args.map(arg => renderArgument(arg, env)).join(', ');
1404
+ }
1435
1405
 
1436
1406
  // Named arguments (object/dict)
1437
- else if (typeof args === 'object')
1407
+ else if (typeof args === 'object') {
1438
1408
  return Object.keys(args).map(key => `${quoteIdIfRequired(key)} ${sep} ${renderArgument(args[key], env)}`).join(', ');
1409
+ }
1439
1410
 
1440
1411
  throw new ModelError(`Unknown args: ${JSON.stringify(args)}`);
1441
1412
  }
@@ -1446,23 +1417,40 @@ function toCdsSourceCsn(csn, options) {
1446
1417
  *
1447
1418
  * @param {any} arg
1448
1419
  * @param {CdlRenderEnvironment} env
1420
+ * @param {string[]} additionalAllowedKeywords
1449
1421
  * @return {string}
1450
1422
  */
1451
- function renderArgument(arg, env) {
1423
+ function renderArgument(arg, env, additionalAllowedKeywords = []) {
1452
1424
  // If the argument is a xpr with e.g. `=`, it may require parentheses.
1453
1425
  // For nested xpr, `renderExpr()` will already add parentheses.
1454
- return renderExpr(arg, env, true, !isSimpleFunctionExpression(arg && arg.xpr), true);
1426
+ return renderExpr(arg, env, true, !isSimpleFunctionExpression(arg && arg.xpr, additionalAllowedKeywords), true);
1427
+ }
1428
+
1429
+ /**
1430
+ * Render an artifact's cardinality.
1431
+ *
1432
+ * @param artifact
1433
+ * @returns {string}
1434
+ */
1435
+ function renderCardinality(artifact) {
1436
+ if (isSimpleCardinality(artifact.cardinality))
1437
+ return renderSimpleCardinality(artifact);
1438
+ return renderBracketCardinality(artifact);
1455
1439
  }
1456
1440
 
1457
1441
  /**
1458
1442
  * Render a cardinality (only those parts that were actually provided)
1459
1443
  *
1460
- * @param {CSN.Cardinality} card
1444
+ * @param {CSN.Artifact} art
1461
1445
  * @return {string}
1462
1446
  */
1463
- function renderCardinality(card) {
1447
+ function renderBracketCardinality(art) {
1448
+ const isComp = art.type === 'cds.Composition';
1449
+ const suffix = (isComp ? ' of ' : ' to ');
1450
+ const card = art.cardinality;
1451
+
1464
1452
  if (!card)
1465
- return '';
1453
+ return suffix;
1466
1454
 
1467
1455
  let result = '[';
1468
1456
  if (card.src !== undefined)
@@ -1474,7 +1462,7 @@ function toCdsSourceCsn(csn, options) {
1474
1462
  if (card.max !== undefined)
1475
1463
  result += card.max;
1476
1464
 
1477
- return `${result}]`;
1465
+ return `${result}]${suffix}`;
1478
1466
  }
1479
1467
 
1480
1468
  /**
@@ -1543,12 +1531,6 @@ function toCdsSourceCsn(csn, options) {
1543
1531
  if (params.length === 0)
1544
1532
  return '';
1545
1533
 
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
1534
  // Special cases for 1 or 2 arguments.
1553
1535
  if (params.length === 1 && artWithType.length !== undefined)
1554
1536
  return `(${artWithType.length})`;
@@ -1603,6 +1585,7 @@ function toCdsSourceCsn(csn, options) {
1603
1585
  // We expand this pattern to also include dots after the first character.
1604
1586
  // If the annotation does not follow this pattern `ident(.@ident)*`, it must be quoted:
1605
1587
  // `@identifier@identifier` must be quoted but `@identifier.@identifier` should not.
1588
+ // TODO: Use quoteAnnotationPathIfRequired()
1606
1589
  const annoRequiresQuoting = !/^[$_a-zA-Z][$_a-zA-Z0-9.]*(?:\.@[$_a-zA-Z][$_a-zA-Z0-9.]*)*$/.test(nameBeforeVariant);
1607
1590
  // Unfortunately, the compiler does not allow `.` after the first variant identifier,
1608
1591
  // even though that is the result after flattening.
@@ -1624,77 +1607,47 @@ function toCdsSourceCsn(csn, options) {
1624
1607
  }
1625
1608
 
1626
1609
  /**
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
1610
+ * Render the name of an artifact, quote path steps if necessary.
1639
1611
  *
1640
1612
  * @param {string} artifactName Artifact name to render
1641
1613
  * @return {string} Artifact name ready for rendering
1642
1614
  */
1643
1615
  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('.');
1616
+ return quotePathIfRequired(artifactName);
1647
1617
  }
1648
1618
 
1649
1619
  /**
1650
- * Get the part that is really the name of this artifact and not just prefix caused by a context/service
1620
+ * Render a function expression.
1651
1621
  *
1652
- * @param {String} artifactName Artifact name to use
1653
- * @returns {String} non-prefix part of the artifact name
1622
+ * @param {object} obj Object with .func and optionally .args
1623
+ * @param {CdlRenderEnvironment} env
1624
+ * @returns {string}
1654
1625
  */
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);
1626
+ function renderFuncExpr( obj, env ) {
1627
+ if (keywords.cdl_functions.includes(obj.func.toUpperCase()))
1628
+ return obj.func;
1629
+ const name = identifierRegex.test(obj.func) ? obj.func : quote(obj.func);
1630
+ return `${name}(${renderArgs( obj, '=>', env )})`;
1680
1631
  }
1681
1632
 
1682
1633
  /**
1683
1634
  * Render an expression.
1684
1635
  *
1685
1636
  * @param {any} expr
1686
- * @param {CdlRenderEnvironment} env
1687
- * @param {boolean} [inline]
1688
- * @param {boolean} [nestedExpr]
1637
+ * @param {CdlRenderEnvironment} exprEnv
1638
+ * @param {boolean} [isInline]
1639
+ * @param {boolean} [isNestedExpr]
1689
1640
  * @param {boolean} [alwaysRenderCast]
1690
1641
  * @returns {string}
1691
1642
  */
1692
- function renderExpr(expr, env, inline, nestedExpr, alwaysRenderCast) {
1643
+ function renderExpr(expr, exprEnv, isInline, isNestedExpr, alwaysRenderCast) {
1693
1644
  if (!_renderExpr) {
1694
1645
  _renderExpr = getExpressionRenderer({
1695
- finalize: x => x,
1696
- explicitTypeCast: (x, env) => {
1697
- const typeRef = renderTypeReference(x.cast, env, true);
1646
+ finalize(x) {
1647
+ return x;
1648
+ },
1649
+ explicitTypeCast(x, env) {
1650
+ const typeRef = renderTypeReferenceAndProps(x.cast, env, { typeRefOnly: true, noAnnoCollect: true });
1698
1651
  const arg = { ...x, cast: null }; // "arg" without cast to avoid recursion.
1699
1652
  return `cast(${renderArgument(arg, env)} as ${typeRef})`;
1700
1653
  },
@@ -1723,42 +1676,40 @@ function toCdsSourceCsn(csn, options) {
1723
1676
  aliasOnly(x, _env) {
1724
1677
  return x.as;
1725
1678
  },
1726
- enum: x => `#${x['#']}`,
1679
+ enum(x) {
1680
+ return `#${x['#']}`;
1681
+ },
1727
1682
  ref(x, env) {
1728
1683
  const { inline } = this;
1729
1684
  return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, inline, env)).join('.')}`;
1730
1685
  },
1731
- windowFunction: (x, env) => {
1732
- const funcDef = renderFunc(x.func, x, null, a => renderArgs(a, '=>', env));
1686
+ windowFunction(x, env) {
1687
+ const funcDef = renderFuncExpr(x, env);
1733
1688
  const windowFunctionOperator = x.xpr.shift(); // OVER ...
1734
1689
  return `${funcDef} ${windowFunctionOperator} ( ${renderExpr(x.xpr, env, true)} )`;
1735
1690
  },
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));
1691
+ func(x, env) {
1692
+ return renderFuncExpr(x, env);
1746
1693
  },
1747
1694
  xpr(x, env) {
1748
1695
  if (this.nestedExpr && !x.cast || x.xpr.some(s => s === 'exists'))
1749
1696
  return `(${renderExpr(x.xpr, env, this.inline, true)})`;
1750
-
1751
1697
  return renderExpr(x.xpr, env, this.inline, true);
1752
1698
  },
1753
1699
  // Sub-queries in expressions need to be in parentheses, otherwise
1754
1700
  // left-associativity of UNIONS may result in different results.
1755
1701
  // For example: `select from E where id in (select from E union select from E);`:
1756
1702
  // 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))})`,
1703
+ SET(x, env) {
1704
+ return `(${renderQuery(x, false, 'view', increaseIndent(env))})`;
1705
+ },
1706
+ SELECT(x, env) {
1707
+ return `(${renderQuery(x, false, 'view', increaseIndent(env))})`;
1708
+ },
1759
1709
  });
1760
1710
  }
1761
- return _renderExpr(expr, env, inline, nestedExpr, alwaysRenderCast);
1711
+
1712
+ return _renderExpr(expr, exprEnv, isInline, isNestedExpr, alwaysRenderCast);
1762
1713
  }
1763
1714
  }
1764
1715
 
@@ -1772,15 +1723,19 @@ function createEnv() {
1772
1723
  return {
1773
1724
  // Current indentation string
1774
1725
  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: '',
1726
+ path: null,
1779
1727
  artifactName: '',
1780
1728
  elementName: '',
1781
1729
  };
1782
1730
  }
1783
1731
 
1732
+ function envAddPath(env, path) {
1733
+ return Object.assign({}, env, { path: [ ...env.path, ...path ] } );
1734
+ }
1735
+ function envNewPath(env, path) {
1736
+ return Object.assign({}, env, { path: [ ...path ] } );
1737
+ }
1738
+
1784
1739
  /**
1785
1740
  * Returns a copy of 'env' with increased indentation (and reset name prefix)
1786
1741
  *
@@ -1788,22 +1743,25 @@ function createEnv() {
1788
1743
  * @returns {CdlRenderEnvironment}
1789
1744
  */
1790
1745
  function increaseIndent(env) {
1791
- return Object.assign({}, env, { namePrefix: '', indent: `${env.indent} ` });
1746
+ return Object.assign({}, env, { indent: `${env.indent} ` });
1792
1747
  }
1793
1748
 
1794
1749
  /**
1795
- * Return a path string 'path' with appropriate "-quotes.
1750
+ * Quote the path steps with `![]` if necessary. For simple ids such as
1751
+ * `elem` use `quoteIdIfRequired` instead.
1796
1752
  *
1797
1753
  * @param {string} path
1798
1754
  * @returns {string}
1755
+ *
1756
+ * @todo For paths such as `E.key`, `key` does not have to be in quotes.
1799
1757
  */
1800
- function quotePathString(path) {
1801
- // "foo"."bar"."wiz"."blub"
1758
+ function quotePathIfRequired(path) {
1802
1759
  return path.split('.').map(quoteIdIfRequired).join('.');
1803
1760
  }
1804
1761
 
1805
1762
  /**
1806
- * Return an id 'id' with appropriate "-quotes
1763
+ * Quote the id with `![]` if necessary. For paths such as `E.key` use
1764
+ * `quotePathIfRequired` instead.
1807
1765
  *
1808
1766
  * @param {string} id
1809
1767
  * @return {string}
@@ -1816,6 +1774,23 @@ function quoteIdIfRequired(id) {
1816
1774
  return id;
1817
1775
  }
1818
1776
 
1777
+ /**
1778
+ * Quote an annotation path, e.g. `@My.@Anno.Description` if necessary.
1779
+ * `anno` can start with `@` but is not required to be.
1780
+ * Example of an annotation path that needs to be quoted:
1781
+ * `@![ spaces in path ].@!["double quotes"]`.
1782
+ *
1783
+ * @param {string} anno
1784
+ * @returns {string}
1785
+ */
1786
+ function quoteAnnotationPathIfRequired(anno) {
1787
+ return anno.split('.').map((segment) => {
1788
+ if (segment.startsWith('@'))
1789
+ return `@${quoteIdIfRequired(segment.slice(1))}`;
1790
+ return quoteIdIfRequired(segment);
1791
+ }).join('.');
1792
+ }
1793
+
1819
1794
  /**
1820
1795
  * Quotes the identifier using CDL-style ![]-quotes.
1821
1796
  *
@@ -1828,20 +1803,14 @@ function quote(id) {
1828
1803
 
1829
1804
  /**
1830
1805
  * 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, ...)
1806
+ * does not match the first part of the `Identifier` rule of `language.g4`
1807
+ * or if 'id' is a reserved keyword.
1838
1808
  *
1839
1809
  * @param {string} id
1840
1810
  * @return {boolean}
1841
1811
  */
1842
1812
  function requiresQuotingForCdl(id) {
1843
- return /^\d/.test(id) ||
1844
- /\W/g.test(id.replace(/\./g, '')) ||
1813
+ return !identifierRegex.test(id) ||
1845
1814
  keywords.cdl.includes(id.toUpperCase()) ||
1846
1815
  keywords.cdl_functions.includes(id.toUpperCase());
1847
1816
  }
@@ -1865,18 +1834,52 @@ const functionExpressionOperatorsRequireParentheses = [
1865
1834
  * in a `fct(<xpr>)` expression such as `cast(<xpr> as Type)`. We only need to
1866
1835
  * look at the first nesting level. Otherwise, `renderExpr()` will already add parentheses.
1867
1836
  *
1868
- * The list was created by looking at the `expression` Antlr rule.
1837
+ * The list of `functionExpressionOperatorsRequireParentheses` was created by looking at
1838
+ * the `expression` Antlr rule.
1839
+ * Because of token-rewrites, there are functions that allow operators/tokens that would
1840
+ * require parentheses in other functions. For example *regex functions allow `IN` but
1841
+ * if `IN` is used in other functions, it requires parentheses. To allow for that case,
1842
+ * you can set `additionalAllowedKeywords` to list of tokens that are allowed.
1869
1843
  *
1870
- * This is more of a heuristic for "nicer" CDL output. For example the
1844
+ * Note that this is more of a heuristic for "nicer" CDL output. For example the
1871
1845
  * following snippet is parsable without parentheses:
1872
1846
  * `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.
1847
+ * However, because it is a flat xpr-array, we see `>` and assume that it is not
1848
+ * a simple expression.
1874
1849
  *
1875
1850
  * @param {any[]} xpr
1851
+ * @param {string[]} additionalAllowedKeywords
1876
1852
  * @return {boolean}
1877
1853
  */
1878
- function isSimpleFunctionExpression(xpr) {
1879
- return !xpr || xpr.every(val => typeof val !== 'string' || !functionExpressionOperatorsRequireParentheses.includes(val.toLowerCase()));
1854
+ function isSimpleFunctionExpression(xpr, additionalAllowedKeywords = []) {
1855
+ return !xpr || xpr.every(val => typeof val !== 'string' ||
1856
+ (additionalAllowedKeywords.includes(val) ||
1857
+ !functionExpressionOperatorsRequireParentheses.includes(val.toLowerCase())));
1858
+ }
1859
+
1860
+ /**
1861
+ * Special functions may have special parser rules, such as SAP HANA RegEx functions.
1862
+ * They allow certain keywords in their arguments.
1863
+ *
1864
+ * This function is used to determine if arguments need to be put in parentheses or not.
1865
+ * See {@link isSimpleFunctionExpression}.
1866
+ *
1867
+ * @param {string} funcName
1868
+ * @param {number} argumentIndex
1869
+ * @returns {string[]}
1870
+ */
1871
+ function getKeywordsForSpecialFunctionArgument(funcName, argumentIndex) {
1872
+ const f = specialFunctions[funcName] && specialFunctions[funcName][argumentIndex];
1873
+ if (!f)
1874
+ return [];
1875
+ const additionalKeywords = [];
1876
+ if (f.intro)
1877
+ additionalKeywords.push(...f.intro);
1878
+ if (f.expr)
1879
+ additionalKeywords.push(...f.expr);
1880
+ if (f.separator)
1881
+ additionalKeywords.push(...f.separator);
1882
+ return additionalKeywords;
1880
1883
  }
1881
1884
 
1882
1885
  /**
@@ -1884,7 +1887,7 @@ function isSimpleFunctionExpression(xpr) {
1884
1887
  * @return {string}
1885
1888
  */
1886
1889
  function renderIncludes(includes) {
1887
- return ` : ${includes.map(name => quoteIdIfRequired(name)).join(', ')}`;
1890
+ return ` : ${includes.map(name => quotePathIfRequired(name)).join(', ')}`;
1888
1891
  }
1889
1892
 
1890
1893
  /**
@@ -1965,17 +1968,9 @@ function isSimpleString(str) {
1965
1968
  * @typedef CdlRenderEnvironment Rendering environment used throughout the render process.
1966
1969
  *
1967
1970
  * @property {string} indent Current indentation as a string, e.g. ' ' for two spaces.
1968
- * @property {CSN.Path} [path] CSN path to the current artifact
1971
+ * @property {string[]} [path] CSN path to the current artifact
1969
1972
  * @property {string} [artifactName] Name of the artifact - set in renderArtifact
1970
1973
  * @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
1974
  */
1980
1975
 
1981
- module.exports = { toCdsSourceCsn };
1976
+ module.exports = { csnToCdl };