@sap/cds-compiler 3.8.0 → 3.9.2

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 (79) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/bin/cdsc.js +2 -2
  3. package/doc/CHANGELOG_BETA.md +26 -5
  4. package/lib/api/.eslintrc.json +3 -2
  5. package/lib/api/options.js +3 -1
  6. package/lib/api/validate.js +1 -1
  7. package/lib/base/message-registry.js +27 -18
  8. package/lib/base/messages.js +6 -1
  9. package/lib/base/model.js +2 -2
  10. package/lib/checks/.eslintrc.json +1 -0
  11. package/lib/checks/actionsFunctions.js +6 -6
  12. package/lib/checks/annotationsOData.js +1 -1
  13. package/lib/checks/elements.js +28 -17
  14. package/lib/checks/foreignKeys.js +1 -1
  15. package/lib/checks/invalidTarget.js +1 -1
  16. package/lib/checks/onConditions.js +11 -6
  17. package/lib/checks/queryNoDbArtifacts.js +1 -1
  18. package/lib/checks/types.js +1 -1
  19. package/lib/checks/utils.js +1 -1
  20. package/lib/checks/validator.js +3 -2
  21. package/lib/compiler/assert-consistency.js +8 -3
  22. package/lib/compiler/base.js +19 -13
  23. package/lib/compiler/builtins.js +7 -0
  24. package/lib/compiler/checks.js +73 -6
  25. package/lib/compiler/define.js +10 -5
  26. package/lib/compiler/extend.js +924 -1709
  27. package/lib/compiler/finalize-parse-cdl.js +1 -1
  28. package/lib/compiler/generate.js +838 -0
  29. package/lib/compiler/index.js +2 -0
  30. package/lib/compiler/populate.js +2 -2
  31. package/lib/compiler/propagator.js +20 -8
  32. package/lib/compiler/resolve.js +3 -3
  33. package/lib/compiler/shared.js +11 -6
  34. package/lib/edm/annotations/genericTranslation.js +6 -6
  35. package/lib/edm/csn2edm.js +1 -1
  36. package/lib/edm/edm.js +25 -11
  37. package/lib/edm/edmPreprocessor.js +47 -23
  38. package/lib/edm/edmUtils.js +37 -9
  39. package/lib/gen/Dictionary.json +5 -7
  40. package/lib/gen/language.checksum +1 -1
  41. package/lib/gen/language.interp +3 -1
  42. package/lib/gen/language.tokens +24 -23
  43. package/lib/gen/languageLexer.interp +4 -1
  44. package/lib/gen/languageLexer.js +792 -784
  45. package/lib/gen/languageLexer.tokens +12 -11
  46. package/lib/gen/languageParser.js +3944 -3865
  47. package/lib/json/from-csn.js +27 -6
  48. package/lib/json/to-csn.js +10 -6
  49. package/lib/language/antlrParser.js +11 -3
  50. package/lib/language/genericAntlrParser.js +4 -2
  51. package/lib/language/language.g4 +32 -24
  52. package/lib/model/csnRefs.js +15 -7
  53. package/lib/model/csnUtils.js +41 -76
  54. package/lib/modelCompare/utils/.eslintrc.json +1 -1
  55. package/lib/optionProcessor.js +7 -4
  56. package/lib/render/.eslintrc.json +1 -1
  57. package/lib/render/toCdl.js +244 -168
  58. package/lib/render/toHdbcds.js +18 -10
  59. package/lib/render/toSql.js +24 -2
  60. package/lib/transform/db/.eslintrc.json +4 -3
  61. package/lib/transform/db/cdsPersistence.js +1 -1
  62. package/lib/transform/db/expansion.js +11 -6
  63. package/lib/transform/db/flattening.js +22 -15
  64. package/lib/transform/db/rewriteCalculatedElements.js +50 -29
  65. package/lib/transform/db/temporal.js +1 -1
  66. package/lib/transform/db/views.js +1 -1
  67. package/lib/transform/draft/db.js +1 -1
  68. package/lib/transform/draft/odata.js +3 -4
  69. package/lib/transform/forOdataNew.js +5 -6
  70. package/lib/transform/forRelationalDB.js +7 -7
  71. package/lib/transform/odata/toFinalBaseType.js +6 -6
  72. package/lib/transform/odata/typesExposure.js +12 -3
  73. package/lib/transform/odata/utils.js +3 -0
  74. package/lib/transform/transformUtilsNew.js +11 -26
  75. package/lib/transform/translateAssocsToJoins.js +9 -9
  76. package/lib/transform/universalCsn/.eslintrc.json +3 -2
  77. package/lib/transform/universalCsn/coreComputed.js +1 -1
  78. package/lib/transform/universalCsn/universalCsnEnricher.js +6 -4
  79. package/package.json +1 -1
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const keywords = require('../base/keywords');
4
+ const { cdlNewLineRegEx } = require('../language/textUtils');
4
5
  const { findElement, createExpressionRenderer, withoutCast } = require('./utils/common');
5
6
  const { escapeString, hasUnpairedUnicodeSurrogate } = require('./utils/stringEscapes');
6
7
  const { checkCSNVersion } = require('../json/csnVersion');
@@ -79,7 +80,7 @@ function csnToCdl( csn, options ) {
79
80
  cdlResult.model += renderExtensions(csn.extensions, createEnv());
80
81
 
81
82
  if (csn.namespace) {
82
- cdlResult.namespace = `namespace ${renderArtifactName(csn.namespace)};\n`;
83
+ cdlResult.namespace = `namespace ${renderArtifactName(csn.namespace, createEnv())};\n`;
83
84
  cdlResult.namespace += 'using from \'./model.cds\';';
84
85
  }
85
86
 
@@ -175,12 +176,12 @@ function csnToCdl( csn, options ) {
175
176
  // Element extensions have `kind` set. Don't use for enum extension.
176
177
  const isElementExtend = (ext.kind === 'extend' && !ext.enum);
177
178
  let result = renderAnnotationAssignmentsAndDocComment(ext, env);
178
- extName = isElementExtend ? renderArtifactName(extName) : renderDefinitionReference(extName);
179
+ extName = isElementExtend ? renderArtifactName(extName, env) : renderDefinitionReference(extName, env);
179
180
 
180
181
  if (ext.includes && ext.includes.length > 0) {
181
182
  // Includes can't be combined with anything in braces {}.
182
183
  const affix = isElementExtend ? 'element ' : '';
183
- const includes = ext.includes.map(inc => renderDefinitionReference(inc)).join(', ');
184
+ const includes = ext.includes.map(inc => renderDefinitionReference(inc, env)).join(', ');
184
185
  result += `${env.indent}extend ${affix}${extName} with ${includes};\n`;
185
186
  return result;
186
187
  }
@@ -303,7 +304,7 @@ function csnToCdl( csn, options ) {
303
304
  let result = renderAnnotationAssignmentsAndDocComment(ext, env);
304
305
  // Note: Not renderDefinitionReference, because we don't care if there
305
306
  // are annotations to unknown things. That's allowed!
306
- result += `${env.indent}annotate ${renderArtifactName(ext.annotate)}`;
307
+ result += `${env.indent}annotate ${renderArtifactName(ext.annotate, env)}`;
307
308
 
308
309
  if (ext.params)
309
310
  result += renderAnnotateParamsInParentheses(ext.params, env);
@@ -326,7 +327,7 @@ function csnToCdl( csn, options ) {
326
327
  const childEnv = increaseIndent(env);
327
328
  for (const name in ext.actions) {
328
329
  const action = ext.actions[name];
329
- result += renderAnnotationAssignmentsAndDocComment(action, childEnv) + childEnv.indent + quoteNonIdentifierOrKeyword(name);
330
+ result += renderAnnotationAssignmentsAndDocComment(action, childEnv) + childEnv.indent + quoteNonIdentifierOrKeyword(name, env);
330
331
  // Action parameter annotations
331
332
  if (action.params)
332
333
  result += renderAnnotateParamsInParentheses(action.params, childEnv);
@@ -361,7 +362,7 @@ function csnToCdl( csn, options ) {
361
362
  for (const name in elements) {
362
363
  const elem = elements[name];
363
364
  result += renderAnnotationAssignmentsAndDocComment(elem, childEnv);
364
- result += childEnv.indent + quoteNonIdentifierOrKeyword(name);
365
+ result += childEnv.indent + quoteNonIdentifierOrKeyword(name, env);
365
366
  if (elem.elements)
366
367
  result += renderAnnotateStatementElements(elem.elements, childEnv);
367
368
  if (elem.enum)
@@ -385,7 +386,7 @@ function csnToCdl( csn, options ) {
385
386
  let result = '(\n';
386
387
  const paramAnnotations = [];
387
388
  forEach(params, (paramName, param) => {
388
- paramAnnotations.push( renderAnnotationAssignmentsAndDocComment(param, childEnv) + childEnv.indent + quoteNonIdentifierOrKeyword(paramName) );
389
+ paramAnnotations.push( renderAnnotationAssignmentsAndDocComment(param, childEnv) + childEnv.indent + quoteNonIdentifierOrKeyword(paramName, env) );
389
390
  });
390
391
  result += `${paramAnnotations.join(',\n')}\n${env.indent})`;
391
392
  return result;
@@ -437,10 +438,10 @@ function csnToCdl( csn, options ) {
437
438
  */
438
439
  function renderEvent( artifactName, art, env ) {
439
440
  let result = renderAnnotationAssignmentsAndDocComment(art, env);
440
- const normalizedArtifactName = renderArtifactName(artifactName);
441
+ const normalizedArtifactName = renderArtifactName(artifactName, env);
441
442
  result += `${env.indent}event ${normalizedArtifactName}`;
442
443
  if (art.includes)
443
- result += renderIncludes(art.includes);
444
+ result += renderIncludes(art.includes, env);
444
445
  if (art.query || art.projection) {
445
446
  result += ' : ';
446
447
  result += renderQuery(getNormalizedQuery(art).query, true, 'projection', env,
@@ -465,7 +466,7 @@ function csnToCdl( csn, options ) {
465
466
  */
466
467
  function renderContextOrService( artifactName, art, env ) {
467
468
  let result = renderAnnotationAssignmentsAndDocComment(art, env);
468
- result += `${env.indent}${art.kind} ${renderArtifactName(artifactName)}`;
469
+ result += `${env.indent}${art.kind} ${renderArtifactName(artifactName, env)}`;
469
470
  return `${result} {};\n`;
470
471
  }
471
472
 
@@ -480,12 +481,12 @@ function csnToCdl( csn, options ) {
480
481
  function renderEntity( artifactName, art, env ) {
481
482
  let result = renderAnnotationAssignmentsAndDocComment(art, env);
482
483
  result += env.indent + (art.abstract ? 'abstract ' : '');
483
- result += `entity ${renderArtifactName(artifactName)}`;
484
+ result += `entity ${renderArtifactName(artifactName, env)}`;
484
485
 
485
486
  if (art.params)
486
487
  result += renderParameters(art, env);
487
488
  if (art.includes)
488
- result += renderIncludes(art.includes);
489
+ result += renderIncludes(art.includes, env);
489
490
  result += ` ${renderElements(art, env)}`;
490
491
  result += `${renderActionsAndFunctions(art, env)};\n`;
491
492
  return result;
@@ -503,9 +504,9 @@ function csnToCdl( csn, options ) {
503
504
  */
504
505
  function renderAspect( artifactName, art, env ) {
505
506
  let result = renderAnnotationAssignmentsAndDocComment(art, env);
506
- result += `${env.indent}aspect ${renderArtifactName(artifactName)}`;
507
+ result += `${env.indent}aspect ${renderArtifactName(artifactName, env)}`;
507
508
  if (art.includes)
508
- result += renderIncludes(art.includes);
509
+ result += renderIncludes(art.includes, env);
509
510
 
510
511
  if (art.elements)
511
512
  result += ` ${renderElements(art, env)}`;
@@ -552,7 +553,7 @@ function csnToCdl( csn, options ) {
552
553
  result += element.key ? 'key ' : '';
553
554
  // TODO(v4): Remove once deprecated flag for `masked` is removed.
554
555
  result += element.masked ? 'masked ' : '';
555
- result += quoteNonIdentifierOrKeyword(elementName);
556
+ result += quoteNonIdentifierOrKeyword(elementName, env);
556
557
  if (element.val !== undefined) { // enum value
557
558
  result += ` = ${exprRenderer.renderExpr(element, env)}`;
558
559
  }
@@ -566,7 +567,13 @@ function csnToCdl( csn, options ) {
566
567
  }
567
568
 
568
569
  if (element.value !== undefined) { // calculated element // @ts-ignore
569
- result += ` = ${exprRenderer.renderExpr(element.value, env)}`;
570
+ result += ' = ';
571
+ if (element.value.xpr && xprContainsCondition(element.value.xpr))
572
+ result += exprRenderer.renderSubExpr(element.value, env);
573
+ else
574
+ result += exprRenderer.renderExpr(element.value, env);
575
+ if (element.value.stored === true)
576
+ result += ' stored';
570
577
  }
571
578
 
572
579
  return `${result};\n`;
@@ -695,7 +702,7 @@ function csnToCdl( csn, options ) {
695
702
  const subEnv = increaseIndent(env);
696
703
  let result = `(\n${subEnv.indent}${renderQuery(source, false, 'view', subEnv)}\n${env.indent})`;
697
704
  if (source.as)
698
- result += renderAlias(source.as);
705
+ result += renderAlias(source.as, env);
699
706
 
700
707
  return result;
701
708
  }
@@ -751,7 +758,7 @@ function csnToCdl( csn, options ) {
751
758
  const firstArtifactName = path.ref[0].id || path.ref[0];
752
759
 
753
760
  // Render the first path step (absolute name, with different quoting/naming ..)
754
- let result = renderDefinitionReference(firstArtifactName);
761
+ let result = renderDefinitionReference(firstArtifactName, env);
755
762
 
756
763
  // Even the first step might have parameters and/or a filter
757
764
  if (path.ref[0].args)
@@ -789,7 +796,7 @@ function csnToCdl( csn, options ) {
789
796
  let result = renderAbsolutePath(path, env);
790
797
  if (path.as) {
791
798
  // Source had an alias - render it
792
- result += renderAlias(path.as);
799
+ result += renderAlias(path.as, env);
793
800
  }
794
801
  return result;
795
802
  }
@@ -837,6 +844,8 @@ function csnToCdl( csn, options ) {
837
844
  // Use special rendering for .expand/.inline - renderExpr cannot easily handle some cases
838
845
  if (col.expand || col.inline)
839
846
  result += renderInlineExpand(col, env);
847
+ else if (col.xpr && xprContainsCondition(col.xpr))
848
+ result += exprRenderer.renderSubExpr(withoutCast(col), env);
840
849
  else
841
850
  result += exprRenderer.renderExpr(withoutCast(col), env);
842
851
 
@@ -844,7 +853,7 @@ function csnToCdl( csn, options ) {
844
853
  // A new association (cast with `type` and `target`) uses `as` as its primary name, not alias.
845
854
  const isNewAssociation = col.cast?.type && col.cast.target;
846
855
  if (!isNewAssociation && col.as && !col.inline && !col.expand)
847
- result += renderAlias(col.as);
856
+ result += renderAlias(col.as, env);
848
857
 
849
858
  // Explicit type provided for the view element?
850
859
  if (col.cast) {
@@ -871,7 +880,7 @@ function csnToCdl( csn, options ) {
871
880
 
872
881
  // s as alias { * }
873
882
  if (obj.as && (obj.ref || obj.xpr || obj.val !== undefined || obj.func !== undefined))
874
- result += renderAlias(obj.as);
883
+ result += renderAlias(obj.as, env);
875
884
 
876
885
  // We found a leaf - no further drilling
877
886
  if (!obj.inline && !obj.expand) {
@@ -901,7 +910,7 @@ function csnToCdl( csn, options ) {
901
910
 
902
911
  // { * } as expand
903
912
  if (!obj.ref && obj.as)
904
- result += renderAlias(obj.as);
913
+ result += renderAlias(obj.as, env);
905
914
 
906
915
  return result;
907
916
  }
@@ -940,7 +949,7 @@ function csnToCdl( csn, options ) {
940
949
  function renderView( artifactName, art, env ) {
941
950
  const syntax = (art.projection) ? 'projection' : 'entity';
942
951
  let result = renderAnnotationAssignmentsAndDocComment(art, env);
943
- result += `${env.indent}entity ${renderArtifactName(artifactName)}`;
952
+ result += `${env.indent}entity ${renderArtifactName(artifactName, env)}`;
944
953
  if (art.params)
945
954
  result += renderParameters(art, env);
946
955
  result += ' as ';
@@ -1004,7 +1013,7 @@ function csnToCdl( csn, options ) {
1004
1013
  }
1005
1014
 
1006
1015
  if (select.excluding) {
1007
- const excludes = select.excluding.map(id => `${childEnv.indent}${quoteNonIdentifierOrKeyword(id)}`).join(',\n');
1016
+ const excludes = select.excluding.map(id => `${childEnv.indent}${quoteNonIdentifierOrKeyword(id, env)}`).join(',\n');
1008
1017
  result += ` excluding {\n${excludes}\n`;
1009
1018
  result += `${env.indent}}`;
1010
1019
  }
@@ -1140,7 +1149,7 @@ function csnToCdl( csn, options ) {
1140
1149
  */
1141
1150
  function renderActionOrFunction( actionName, act, env ) {
1142
1151
  let result = renderAnnotationAssignmentsAndDocComment(act, env) + env.indent + act.kind;
1143
- result += ` ${renderArtifactName(actionName)}`;
1152
+ result += ` ${renderArtifactName(actionName, env)}`;
1144
1153
  result += renderParameters(act, env);
1145
1154
  if (act.returns) {
1146
1155
  const actEnv = envAddPath(env, [ 'returns' ]);
@@ -1179,7 +1188,7 @@ function csnToCdl( csn, options ) {
1179
1188
  function renderParameter( parName, par, env ) {
1180
1189
  env = envAddPath(env, [ 'params', parName ]);
1181
1190
  let result = `${renderAnnotationAssignmentsAndDocComment(par, env)}${env.indent}`;
1182
- result += `${quoteNonIdentifierOrKeyword(parName)} : ${renderTypeReferenceAndProps(par, env)}`;
1191
+ result += `${quoteNonIdentifierOrKeyword(parName, env)} : ${renderTypeReferenceAndProps(par, env)}`;
1183
1192
  return result;
1184
1193
  }
1185
1194
 
@@ -1195,9 +1204,9 @@ function csnToCdl( csn, options ) {
1195
1204
  */
1196
1205
  function renderTypeOrAnnotation( artifactName, art, env, artType ) {
1197
1206
  let result = renderAnnotationAssignmentsAndDocComment(art, env);
1198
- result += `${env.indent + (artType || art.$syntax || art.kind )} ${renderArtifactName(artifactName)}`;
1207
+ result += `${env.indent + (artType || art.$syntax || art.kind )} ${renderArtifactName(artifactName, env)}`;
1199
1208
  if (art.includes)
1200
- result += renderIncludes(art.includes);
1209
+ result += renderIncludes(art.includes, env);
1201
1210
 
1202
1211
  if (!art.type && art.elements) // For nicer output, no colon if unnamed structure is used.
1203
1212
  result += ` ${renderTypeReferenceAndProps(art, env)}`;
@@ -1296,7 +1305,7 @@ function csnToCdl( csn, options ) {
1296
1305
  // Reference to another artifact
1297
1306
  if (typeof type === 'string') {
1298
1307
  // If we get here, it must be a named type
1299
- result += renderNamedTypeWithParameters(artifact);
1308
+ result += renderNamedTypeWithParameters(artifact, env);
1300
1309
  }
1301
1310
  else if (type?.ref) {
1302
1311
  result += renderAbsolutePath(artifact.type, env);
@@ -1320,7 +1329,7 @@ function csnToCdl( csn, options ) {
1320
1329
  * @return {string}
1321
1330
  */
1322
1331
  function renderRedirectedTo( art, env ) {
1323
- let result = `redirected to ${renderDefinitionReference(art.target)}`;
1332
+ let result = `redirected to ${renderDefinitionReference(art.target, env)}`;
1324
1333
  if (art.on)
1325
1334
  result += ` on ${exprRenderer.renderExpr(art.on, env)}`;
1326
1335
  else if (art.keys)
@@ -1330,10 +1339,12 @@ function csnToCdl( csn, options ) {
1330
1339
 
1331
1340
  /**
1332
1341
  * Render the named type with optional parameters, e.g. `MyString(length: 10)`.
1342
+ *
1333
1343
  * @param {CSN.Artifact} artWithType
1344
+ * @param {CdlRenderEnvironment} env
1334
1345
  * @return {string}
1335
1346
  */
1336
- function renderNamedTypeWithParameters( artWithType ) {
1347
+ function renderNamedTypeWithParameters( artWithType, env ) {
1337
1348
  const type = normalizeTypeRef(artWithType.type);
1338
1349
  let result = '';
1339
1350
 
@@ -1349,7 +1360,7 @@ function csnToCdl( csn, options ) {
1349
1360
  result += shortHand;
1350
1361
  }
1351
1362
  else {
1352
- result += renderDefinitionReference(type);
1363
+ result += renderDefinitionReference(type, env);
1353
1364
  }
1354
1365
 
1355
1366
  result += renderTypeParameters(artWithType);
@@ -1402,8 +1413,8 @@ function csnToCdl( csn, options ) {
1402
1413
  // Shorthand for absolute path (as string)
1403
1414
  else if (annoValue['=']) {
1404
1415
  if (annoValue['='].startsWith('@'))
1405
- return quoteAnnotationPathIfRequired(annoValue['=']);
1406
- return quotePathIfRequired(annoValue['=']);
1416
+ return quoteAnnotationPathIfRequired(annoValue['='], env);
1417
+ return quotePathIfRequired(annoValue['='], env);
1407
1418
  }
1408
1419
  // Shorthand for ellipsis: `... up to <val>`
1409
1420
  else if (annoValue['...']) {
@@ -1417,7 +1428,7 @@ function csnToCdl( csn, options ) {
1417
1428
  // struct if there are more and use nicer indentation.
1418
1429
  const keys = Object.keys(annoValue);
1419
1430
  const childEnv = keys.length <= 1 ? env : increaseIndent(env);
1420
- const values = keys.map(key => `${quoteAnnotationPathIfRequired(key)}: ${renderAnnotationValue(annoValue[key], childEnv)}`);
1431
+ const values = keys.map(key => `${quoteAnnotationPathIfRequired(key, env)}: ${renderAnnotationValue(annoValue[key], childEnv)}`);
1421
1432
  if (values.length <= 1)
1422
1433
  return `{ ${values.join(', ')} }`;
1423
1434
  const valueList = values.join(`,\n${childEnv.indent}`);
@@ -1479,7 +1490,7 @@ function csnToCdl( csn, options ) {
1479
1490
  // FIXME: We should rather explicitly recognize quoting somehow
1480
1491
  if (idx === 0 && s.startsWith('$'))
1481
1492
  return s;
1482
- return quoteNonIdentifierOrKeyword(s, env.additionalKeywords);
1493
+ return quoteNonIdentifierOrKeyword(s, env);
1483
1494
  }
1484
1495
  // ID with filters or parameters
1485
1496
  else if (typeof s === 'object') {
@@ -1492,7 +1503,7 @@ function csnToCdl( csn, options ) {
1492
1503
  return `${s.func}(${renderArguments(s, '=>', env)})`;
1493
1504
 
1494
1505
  // Path step, possibly with view parameters and/or filters
1495
- let result = `${quoteNonIdentifierOrKeyword(s.id, env.additionalKeywords)}`;
1506
+ let result = `${quoteNonIdentifierOrKeyword(s.id, env)}`;
1496
1507
  if (s.args) {
1497
1508
  // View parameters
1498
1509
  result += `(${renderArguments(s, ':', env)})`;
@@ -1543,7 +1554,7 @@ function csnToCdl( csn, options ) {
1543
1554
  */
1544
1555
  function renderNamedArguments( node, separator, env ) {
1545
1556
  return Object.keys(node.args).map(function renderNamedArgument(key) {
1546
- return `${quoteNonIdentifierOrKeyword(key, env.additionalKeywords)} ${separator} ${renderArgument(node.args[key], env)}`;
1557
+ return `${quoteNonIdentifierOrKeyword(key, env)} ${separator} ${renderArgument(node.args[key], env)}`;
1547
1558
  }).join(', ');
1548
1559
  }
1549
1560
 
@@ -1599,6 +1610,8 @@ function csnToCdl( csn, options ) {
1599
1610
  /**
1600
1611
  * Render a cardinality (only those parts that were actually provided)
1601
1612
  *
1613
+ * TODO: Check srcmin as well?
1614
+ *
1602
1615
  * @param {CSN.Artifact} art
1603
1616
  * @return {string}
1604
1617
  */
@@ -1674,7 +1687,7 @@ function csnToCdl( csn, options ) {
1674
1687
  * @return {string}
1675
1688
  */
1676
1689
  function renderForeignKey( fKey, env ) {
1677
- const alias = fKey.as ? renderAlias(fKey.as) : '';
1690
+ const alias = fKey.as ? renderAlias(fKey.as, env) : '';
1678
1691
  return exprRenderer.renderExpr(fKey, env) + alias;
1679
1692
  }
1680
1693
 
@@ -1682,10 +1695,11 @@ function csnToCdl( csn, options ) {
1682
1695
  * Render an explicit alias, e.g. for columns.
1683
1696
  *
1684
1697
  * @param {string} alias
1698
+ * @param {CdlRenderEnvironment} env
1685
1699
  * @return {string}
1686
1700
  */
1687
- function renderAlias( alias ) {
1688
- return ` as ${quoteNonIdentifierOrKeyword(alias)}`;
1701
+ function renderAlias( alias, env ) {
1702
+ return ` as ${quoteNonIdentifierOrKeyword(alias, env)}`;
1689
1703
  }
1690
1704
 
1691
1705
  /**
@@ -1758,11 +1772,11 @@ function csnToCdl( csn, options ) {
1758
1772
  if (parentheses)
1759
1773
  result += '(';
1760
1774
 
1761
- result += quoteAnnotationPathIfRequired(nameBeforeVariant);
1775
+ result += quoteAnnotationPathIfRequired(nameBeforeVariant, env);
1762
1776
  if (variant !== undefined)
1763
1777
  // Unfortunately, the compiler does not allow `.@` after the first variant identifier,
1764
1778
  // so we're back at simple paths.
1765
- result += `#${quotePathIfRequired(variant)}`;
1779
+ result += `#${quotePathIfRequired(variant, env)}`;
1766
1780
  result += ` : ${renderAnnotationValue(anno, env)}`;
1767
1781
 
1768
1782
  if (parentheses)
@@ -1774,10 +1788,11 @@ function csnToCdl( csn, options ) {
1774
1788
  * Render the name of an artifact, quote path steps if necessary.
1775
1789
  *
1776
1790
  * @param {string} artifactName Artifact name to render
1791
+ * @param {CdlRenderEnvironment} env
1777
1792
  * @return {string} Artifact name ready for rendering
1778
1793
  */
1779
- function renderArtifactName( artifactName ) {
1780
- return quotePathIfRequired(artifactName);
1794
+ function renderArtifactName( artifactName, env ) {
1795
+ return quotePathIfRequired(artifactName, env);
1781
1796
  }
1782
1797
 
1783
1798
  /**
@@ -1785,19 +1800,21 @@ function csnToCdl( csn, options ) {
1785
1800
  * is available in the rendered CDL. Otherwise, a USING is added.
1786
1801
  *
1787
1802
  * @param {string} name
1803
+ * @param {CdlRenderEnvironment} env
1788
1804
  * @return {string}
1789
1805
  */
1790
- function renderDefinitionReference( name ) {
1806
+ function renderDefinitionReference( name, env ) {
1791
1807
  usings.addIfRequired(name);
1792
- return quotePathIfRequired(name);
1808
+ return quotePathIfRequired(name, env);
1793
1809
  }
1794
1810
 
1795
1811
  /**
1796
1812
  * @param {string[]} includes
1813
+ * @param {CdlRenderEnvironment} env
1797
1814
  * @return {string}
1798
1815
  */
1799
- function renderIncludes( includes ) {
1800
- return ` : ${includes.map(name => renderDefinitionReference(name)).join(', ')}`;
1816
+ function renderIncludes( includes, env ) {
1817
+ return ` : ${includes.map(name => renderDefinitionReference(name, env)).join(', ')}`;
1801
1818
  }
1802
1819
 
1803
1820
  function createCdlExpressionRenderer() {
@@ -1842,13 +1859,13 @@ function csnToCdl( csn, options ) {
1842
1859
  func(x) {
1843
1860
  if (keywords.cdl_functions.includes(x.func.toUpperCase()) && !x.args)
1844
1861
  return x.func;
1845
- const name = smartFunctionId(x.func);
1862
+ const name = quoteFunctionIfRequired(x.func, this.env);
1846
1863
  if (!x.args) // e.g. for methods without arguments, `args` is not set at all.
1847
1864
  return `${name}`;
1848
1865
  return `${name}(${renderArguments( x, '=>', this.env )})`;
1849
1866
  },
1850
1867
  xpr(x) {
1851
- if (this.isNestedXpr && !x.cast || x.xpr.some(s => s === 'exists'))
1868
+ if (this.isNestedXpr && !x.cast)
1852
1869
  return `(${this.renderExpr(x.xpr)})`;
1853
1870
  return this.renderExpr(x.xpr);
1854
1871
  },
@@ -1920,6 +1937,129 @@ function csnToCdl( csn, options ) {
1920
1937
  'Property $(PROP) not rendered, because it can only be rendered inside $(OTHERPROP) for arrayed artifacts');
1921
1938
  }
1922
1939
  }
1940
+
1941
+ /**
1942
+ * Quote simple path steps with `![]` if necessary. For simple ids such as
1943
+ * `elem` use `quoteNonIdentifierOrKeyword` instead.
1944
+ *
1945
+ * In contrast to quoteNonIdentifierOrKeyword, does not handle additional keywords,
1946
+ * because it was not required, yet.
1947
+ *
1948
+ * Due to token rewrite, all keywords after a dot (`.`) are rewritten to
1949
+ * identifiers, i.e. we only need to check for the identifier RegEx.
1950
+ *
1951
+ * @param {string} path
1952
+ * @param {CdlRenderEnvironment} env
1953
+ * @returns {string}
1954
+ */
1955
+ function quotePathIfRequired( path, env ) {
1956
+ return path.split('.').map((step, index) => {
1957
+ if (index === 0)
1958
+ return quoteNonIdentifierOrKeyword(step, env);
1959
+ else if (!identifierRegex.test(step))
1960
+ return delimitedId(step, env);
1961
+ return step;
1962
+ }).join('.');
1963
+ }
1964
+
1965
+ /**
1966
+ * Quote the id with `![]` if necessary. For paths such as `E.key` use
1967
+ * `quotePathIfRequired` instead.
1968
+ * See quoteNonIdentifier() if you want to ignore keywords.
1969
+ *
1970
+ * Set env.additionalKeywords to an array of UPPERCASE keywords
1971
+ * that also need quoting, e.g. in special functions.
1972
+ *
1973
+ * @param {string} id
1974
+ * @param {CdlRenderEnvironment} env
1975
+ * @return {string}
1976
+ */
1977
+ function quoteNonIdentifierOrKeyword( id, env ) {
1978
+ // Quote if required for CDL
1979
+ if (requiresQuotingForCdl(id, env?.additionalKeywords || []))
1980
+ return delimitedId(id, env);
1981
+ return id;
1982
+ }
1983
+
1984
+ /**
1985
+ * Quote the id with `![]` if necessary. For paths such as `E.key` use
1986
+ * `quotePathIfRequired` instead.
1987
+ * See quoteNonIdentifierOrKeyword() if you want to quote identifiers
1988
+ * that are keywords as well.
1989
+ *
1990
+ * Does not quote the given id if it is a keyword.
1991
+ *
1992
+ * @param {string} id
1993
+ * @param {CdlRenderEnvironment} env
1994
+ * @return {string}
1995
+ */
1996
+ function quoteNonIdentifier( id, env ) {
1997
+ if (!identifierRegex.test(id))
1998
+ return delimitedId(id, env);
1999
+ return id;
2000
+ }
2001
+
2002
+ /**
2003
+ * Quote the given function name if required.
2004
+ *
2005
+ * @param {string} funcName
2006
+ * @param {CdlRenderEnvironment} env
2007
+ * @return {string}
2008
+ */
2009
+ function quoteFunctionIfRequired( funcName, env ) {
2010
+ if (cdlNewLineRegEx.test(funcName)) {
2011
+ msg.error('name-invalid-identifier', env.path, {},
2012
+ 'An identifier can\'t contain newline characters in CDL');
2013
+ }
2014
+ return apiSmartFunctionId(funcName);
2015
+ }
2016
+
2017
+ /**
2018
+ * Quote an annotation path, e.g. `@My.@Anno.Description` if necessary.
2019
+ * `anno` can start with `@` but is not required to be.
2020
+ * Example of an annotation path that needs to be quoted:
2021
+ * `@![ spaces in path ].@!["double quotes"]`.
2022
+ *
2023
+ * @param {string} anno
2024
+ * @param {CdlRenderEnvironment} env
2025
+ * @returns {string}
2026
+ */
2027
+ function quoteAnnotationPathIfRequired( anno, env ) {
2028
+ return anno.split('.').map((segment) => {
2029
+ if (segment.startsWith('@'))
2030
+ return `@${quoteNonIdentifier(segment.slice(1), env)}`;
2031
+ return quoteNonIdentifier(segment, env);
2032
+ }).join('.');
2033
+ }
2034
+
2035
+ /**
2036
+ * The same as the exported function apiDelimitedId, but checks that we can actually represent the
2037
+ * string: newline characters are not allowed.
2038
+ *
2039
+ * @param {string} id
2040
+ * @param {CdlRenderEnvironment} env
2041
+ * @return {string}
2042
+ */
2043
+ function delimitedId( id, env ) {
2044
+ if (cdlNewLineRegEx.test(id)) {
2045
+ msg.error('name-invalid-identifier', env.path, {},
2046
+ 'An identifier can\'t contain newline characters in CDL');
2047
+ }
2048
+ return apiDelimitedId(id);
2049
+ }
2050
+ }
2051
+
2052
+
2053
+ class CdlRenderEnvironment {
2054
+ indent = '';
2055
+ path = null;
2056
+ artifactName = null;
2057
+ elementName = null;
2058
+ additionalKeywords = [];
2059
+
2060
+ constructor(values) {
2061
+ Object.assign(this, values);
2062
+ }
1923
2063
  }
1924
2064
 
1925
2065
  /**
@@ -1930,20 +2070,14 @@ function csnToCdl( csn, options ) {
1930
2070
  * @return {CdlRenderEnvironment}
1931
2071
  */
1932
2072
  function createEnv( values = {} ) {
1933
- return Object.assign({
1934
- // Current indentation string
1935
- indent: '',
1936
- path: null,
1937
- artifactName: '',
1938
- elementName: '',
1939
- }, values);
2073
+ return new CdlRenderEnvironment( values );
1940
2074
  }
1941
2075
 
1942
2076
  function envAddPath( env, path ) {
1943
- return Object.assign({}, env, { path: [ ...env.path, ...path ] } );
2077
+ return Object.assign(new CdlRenderEnvironment(env), { path: [ ...env.path, ...path ] } );
1944
2078
  }
1945
2079
  function envNewPath( env, path ) {
1946
- return Object.assign({}, env, { path: [ ...path ] } );
2080
+ return Object.assign(new CdlRenderEnvironment(env), { path: [ ...path ] } );
1947
2081
  }
1948
2082
 
1949
2083
  /**
@@ -1956,92 +2090,6 @@ function increaseIndent( env ) {
1956
2090
  return Object.assign({}, env, { indent: `${env.indent} ` });
1957
2091
  }
1958
2092
 
1959
- /**
1960
- * Quote simple path steps with `![]` if necessary. For simple ids such as
1961
- * `elem` use `quoteNonIdentifierOrKeyword` instead.
1962
- *
1963
- * In contrast to quoteNonIdentifierOrKeyword, does not handle additional keywords,
1964
- * because it was not required, yet.
1965
- *
1966
- * Due to token rewrite, all keywords after a dot (`.`) are rewritten to
1967
- * identifiers, i.e. we only need to check for the identifier RegEx.
1968
- *
1969
- * @param {string} path
1970
- * @returns {string}
1971
- */
1972
- function quotePathIfRequired( path ) {
1973
- return path.split('.').map((step, index) => {
1974
- if (index === 0)
1975
- return quoteNonIdentifierOrKeyword(step);
1976
- else if (!identifierRegex.test(step))
1977
- return delimitedId(step);
1978
- return step;
1979
- }).join('.');
1980
- }
1981
-
1982
- /**
1983
- * Quote the id with `![]` if necessary. For paths such as `E.key` use
1984
- * `quotePathIfRequired` instead.
1985
- * See quoteNonIdentifier() if you want to ignore keywords.
1986
- *
1987
- * Set additionalKeywords to an array of UPPERCASE keywords
1988
- * that also need quoting, e.g. in special functions.
1989
- *
1990
- * @param {string} id
1991
- * @param {string[]} [additionalKeywords]
1992
- * @return {string}
1993
- */
1994
- function quoteNonIdentifierOrKeyword( id, additionalKeywords ) {
1995
- // Quote if required for CDL
1996
- if (requiresQuotingForCdl(id, additionalKeywords || []))
1997
- return delimitedId(id);
1998
- return id;
1999
- }
2000
-
2001
- /**
2002
- * Quote the id with `![]` if necessary. For paths such as `E.key` use
2003
- * `quotePathIfRequired` instead.
2004
- * See quoteNonIdentifierOrKeyword() if you want to quote identifiers
2005
- * that are keywords as well.
2006
- *
2007
- * Does not quote the given id if it is a keyword.
2008
- *
2009
- * @param {string} id
2010
- * @return {string}
2011
- */
2012
- function quoteNonIdentifier( id ) {
2013
- if (!identifierRegex.test(id))
2014
- return delimitedId(id);
2015
- return id;
2016
- }
2017
-
2018
- /**
2019
- * Quote an annotation path, e.g. `@My.@Anno.Description` if necessary.
2020
- * `anno` can start with `@` but is not required to be.
2021
- * Example of an annotation path that needs to be quoted:
2022
- * `@![ spaces in path ].@!["double quotes"]`.
2023
- *
2024
- * @param {string} anno
2025
- * @returns {string}
2026
- */
2027
- function quoteAnnotationPathIfRequired( anno ) {
2028
- return anno.split('.').map((segment) => {
2029
- if (segment.startsWith('@'))
2030
- return `@${quoteNonIdentifier(segment.slice(1))}`;
2031
- return quoteNonIdentifier(segment);
2032
- }).join('.');
2033
- }
2034
-
2035
- /**
2036
- * Quotes the identifier using CDL-style ![]-quotes.
2037
- *
2038
- * @param id
2039
- * @returns {string}
2040
- */
2041
- function delimitedId( id ) {
2042
- return `![${id.replace(/]/g, ']]')}]`;
2043
- }
2044
-
2045
2093
  /**
2046
2094
  * Returns true if 'id' requires quotes for CDL, i.e. if 'id'
2047
2095
  * does not match the first part of the `Identifier` rule of `language.g4`
@@ -2061,7 +2109,7 @@ function requiresQuotingForCdl( id, additionalKeywords ) {
2061
2109
  additionalKeywords.includes(id.toUpperCase());
2062
2110
  }
2063
2111
 
2064
- const functionExpressionOperatorsRequireParentheses = [
2112
+ const conditionOperators = [
2065
2113
  // Antlr rule 'condition', 'conditionAnd'
2066
2114
  'AND', 'OR',
2067
2115
 
@@ -2080,8 +2128,7 @@ const functionExpressionOperatorsRequireParentheses = [
2080
2128
  * in a `fct(<xpr>)` expression such as `cast(<xpr> as Type)`. We only need to
2081
2129
  * look at the first nesting level. Otherwise, `renderExpr()` will already add parentheses.
2082
2130
  *
2083
- * The list of `functionExpressionOperatorsRequireParentheses` was created by looking at
2084
- * the `expression` Antlr rule.
2131
+ * The list of `conditionOperators` was created by looking at the `expression` Antlr rule.
2085
2132
  * Because of token-rewrites, there are functions that allow operators/tokens that would
2086
2133
  * require parentheses in other functions. For example *regex functions allow `IN` but
2087
2134
  * if `IN` is used in other functions, it requires parentheses. To allow for that case,
@@ -2100,7 +2147,22 @@ const functionExpressionOperatorsRequireParentheses = [
2100
2147
  function isSimpleFunctionExpression( xpr, additionalAllowedKeywords = [] ) {
2101
2148
  return !xpr || xpr.every(val => typeof val !== 'string' ||
2102
2149
  (additionalAllowedKeywords.includes(val.toUpperCase()) ||
2103
- !functionExpressionOperatorsRequireParentheses.includes(val.toUpperCase())));
2150
+ !conditionOperators.includes(val.toUpperCase())));
2151
+ }
2152
+
2153
+ /**
2154
+ * If `xpr` contains tokens that are used in conditions, it may be required to put the
2155
+ * rendered expression in parentheses. This function checks if any direkt entry in
2156
+ * `xpr` is a condition token such as `AND`.
2157
+ *
2158
+ * May report false positives for e.g. `CASE WHEN 1>1 THEN …`.
2159
+ *
2160
+ * @param {any[]} xpr
2161
+ * @return {boolean}
2162
+ */
2163
+ function xprContainsCondition( xpr ) {
2164
+ return xpr && xpr.some(val => typeof val === 'string' &&
2165
+ conditionOperators.includes(val.toUpperCase()));
2104
2166
  }
2105
2167
 
2106
2168
  /**
@@ -2241,50 +2303,64 @@ function availableFirstPathSteps( csn ) {
2241
2303
  return Array.from(unique);
2242
2304
  }
2243
2305
 
2306
+ /**
2307
+ * Quotes the identifier using CDL-style ![]-quotes.
2308
+ *
2309
+ * NOTE: It is not guaranteed that the resulting string can _always_ be parsed!
2310
+ * If `id` contains newline characters, the resulting delimited identifier
2311
+ * will not be parsable by the compiler!
2312
+ *
2313
+ * @param id
2314
+ * @returns {string}
2315
+ */
2316
+ function apiDelimitedId( id ) {
2317
+ return `![${id.replace(/]/g, ']]')}]`;
2318
+ }
2319
+
2244
2320
  /**
2245
2321
  * Returns a delimited identifier if the given identifier needs quoting.
2246
2322
  * Because "special functions" such as SAP HANA RegEx functions have local keywords that
2247
2323
  * are not default CDL keywords, specify a function name to take care of that.
2248
2324
  *
2325
+ * NOTE: It is not guaranteed that the resulting string can _always_ be parsed!
2326
+ * If `id` contains newline characters, the resulting delimited identifier
2327
+ * will not be parsable by the compiler!
2328
+ *
2249
2329
  * @param {string} id
2250
2330
  * @param {null|string} insideFunction
2251
2331
  * @return {string}
2252
2332
  */
2253
- function smartId( id, insideFunction = null ) {
2333
+ function apiSmartId( id, insideFunction = null ) {
2254
2334
  insideFunction = insideFunction?.toUpperCase();
2255
- if (!insideFunction || !specialFunctions[insideFunction])
2256
- return quoteNonIdentifierOrKeyword(id);
2257
- return quoteNonIdentifierOrKeyword(id, getAllKeywordsForSpecialFunction(insideFunction));
2335
+ const extra = insideFunction && specialFunctions[insideFunction] ? getAllKeywordsForSpecialFunction(insideFunction) : [];
2336
+ if (requiresQuotingForCdl(id, extra))
2337
+ return apiDelimitedId(id);
2338
+ return id;
2258
2339
  }
2259
2340
 
2260
2341
  /**
2261
2342
  * Quote the given function name if required.
2262
2343
  *
2344
+ * NOTE: It is not guaranteed that the resulting string can _always_ be parsed!
2345
+ * If `funcName` contains newline characters, the resulting delimited identifier
2346
+ * will not be parsable by the compiler!
2347
+ *
2263
2348
  * @param {string} funcName
2264
2349
  * @return {string}
2265
2350
  */
2266
- function smartFunctionId( funcName ) {
2351
+ function apiSmartFunctionId( funcName ) {
2267
2352
  const funcId = funcName.toUpperCase();
2268
2353
  const requiresQuoting = !identifierRegex.test(funcName) ||
2269
2354
  (keywords.cdl.includes(funcId) && !specialFunctions[funcId]);
2270
2355
  if (requiresQuoting)
2271
- return delimitedId(funcName);
2356
+ return apiDelimitedId(funcName);
2272
2357
  return funcName;
2273
2358
  }
2274
2359
 
2275
- /**
2276
- * @typedef CdlRenderEnvironment Rendering environment used throughout the render process.
2277
- *
2278
- * @property {string} indent Current indentation as a string, e.g. ' ' for two spaces.
2279
- * @property {CSN.Path} [path] CSN path to the current artifact
2280
- * @property {string} [artifactName] Name of the artifact - set in renderArtifact
2281
- * @property {string} [elementName] Name of the element being rendered - set in renderElement
2282
- * @property {string[]} [additionalKeywords] For function rendering: Words that are also keywords.
2283
- */
2284
2360
 
2285
2361
  module.exports = {
2286
2362
  csnToCdl,
2287
- smartId,
2288
- smartFunctionId,
2289
- delimitedId,
2363
+ smartId: apiSmartId,
2364
+ smartFunctionId: apiSmartFunctionId,
2365
+ delimitedId: apiDelimitedId,
2290
2366
  };