@sap/cds-compiler 2.12.0 → 2.13.6

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 (118) hide show
  1. package/CHANGELOG.md +110 -15
  2. package/bin/cdsc.js +13 -13
  3. package/bin/cdsse.js +2 -2
  4. package/doc/CHANGELOG_BETA.md +13 -6
  5. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  6. package/doc/NameResolution.md +21 -16
  7. package/lib/api/main.js +28 -63
  8. package/lib/api/options.js +3 -3
  9. package/lib/api/validate.js +0 -5
  10. package/lib/backends.js +15 -23
  11. package/lib/base/dictionaries.js +0 -8
  12. package/lib/base/error.js +26 -0
  13. package/lib/base/keywords.js +7 -17
  14. package/lib/base/location.js +9 -4
  15. package/lib/base/message-registry.js +25 -4
  16. package/lib/base/messages.js +16 -26
  17. package/lib/base/model.js +2 -63
  18. package/lib/base/optionProcessorHelper.js +158 -123
  19. package/lib/checks/annotationsOData.js +1 -1
  20. package/lib/checks/cdsPersistence.js +2 -1
  21. package/lib/checks/enricher.js +17 -1
  22. package/lib/checks/invalidTarget.js +3 -1
  23. package/lib/checks/managedWithoutKeys.js +3 -1
  24. package/lib/checks/selectItems.js +4 -4
  25. package/lib/checks/sql-snippets.js +27 -26
  26. package/lib/checks/types.js +1 -1
  27. package/lib/checks/validator.js +4 -7
  28. package/lib/compiler/assert-consistency.js +5 -3
  29. package/lib/compiler/builtins.js +8 -6
  30. package/lib/compiler/checks.js +14 -3
  31. package/lib/compiler/cycle-detector.js +1 -1
  32. package/lib/compiler/define.js +1103 -0
  33. package/lib/compiler/extend.js +983 -0
  34. package/lib/compiler/finalize-parse-cdl.js +231 -0
  35. package/lib/compiler/index.js +32 -13
  36. package/lib/compiler/kick-start.js +190 -0
  37. package/lib/compiler/moduleLayers.js +4 -4
  38. package/lib/compiler/populate.js +1226 -0
  39. package/lib/compiler/propagator.js +111 -46
  40. package/lib/compiler/resolve.js +1433 -0
  41. package/lib/compiler/shared.js +64 -37
  42. package/lib/compiler/tweak-assocs.js +529 -0
  43. package/lib/compiler/utils.js +197 -33
  44. package/lib/edm/.eslintrc.json +5 -0
  45. package/lib/edm/annotations/genericTranslation.js +5 -9
  46. package/lib/edm/annotations/preprocessAnnotations.js +2 -2
  47. package/lib/edm/csn2edm.js +9 -8
  48. package/lib/edm/edm.js +11 -12
  49. package/lib/edm/edmPreprocessor.js +137 -73
  50. package/lib/edm/edmUtils.js +116 -22
  51. package/lib/gen/Dictionary.json +10 -3
  52. package/lib/gen/language.checksum +1 -1
  53. package/lib/gen/language.interp +9 -1
  54. package/lib/gen/language.tokens +86 -83
  55. package/lib/gen/languageLexer.interp +10 -1
  56. package/lib/gen/languageLexer.js +860 -833
  57. package/lib/gen/languageLexer.tokens +78 -75
  58. package/lib/gen/languageParser.js +5282 -4265
  59. package/lib/json/from-csn.js +12 -1
  60. package/lib/json/to-csn.js +126 -66
  61. package/lib/language/docCommentParser.js +2 -2
  62. package/lib/language/genericAntlrParser.js +76 -3
  63. package/lib/language/language.g4 +297 -130
  64. package/lib/language/multiLineStringParser.js +5 -5
  65. package/lib/main.d.ts +468 -59
  66. package/lib/main.js +35 -9
  67. package/lib/model/api.js +3 -1
  68. package/lib/model/csnRefs.js +225 -156
  69. package/lib/model/csnUtils.js +192 -223
  70. package/lib/model/enrichCsn.js +70 -29
  71. package/lib/model/revealInternalProperties.js +27 -6
  72. package/lib/model/sortViews.js +2 -1
  73. package/lib/modelCompare/compare.js +17 -12
  74. package/lib/optionProcessor.js +5 -4
  75. package/lib/render/manageConstraints.js +35 -32
  76. package/lib/render/toCdl.js +73 -288
  77. package/lib/render/toHdbcds.js +25 -23
  78. package/lib/render/toSql.js +98 -41
  79. package/lib/render/utils/common.js +5 -10
  80. package/lib/render/utils/sql.js +4 -3
  81. package/lib/render/utils/stringEscapes.js +111 -0
  82. package/lib/sql-identifier.js +1 -1
  83. package/lib/transform/.eslintrc.json +5 -0
  84. package/lib/transform/db/.eslintrc.json +2 -0
  85. package/lib/transform/db/applyTransformations.js +35 -12
  86. package/lib/transform/db/assertUnique.js +1 -1
  87. package/lib/transform/db/associations.js +103 -305
  88. package/lib/transform/db/cdsPersistence.js +2 -2
  89. package/lib/transform/db/constraints.js +55 -52
  90. package/lib/transform/db/expansion.js +46 -24
  91. package/lib/transform/db/flattening.js +553 -102
  92. package/lib/transform/db/groupByOrderBy.js +3 -1
  93. package/lib/transform/db/transformExists.js +59 -6
  94. package/lib/transform/db/views.js +5 -4
  95. package/lib/transform/draft/.eslintrc.json +38 -0
  96. package/lib/transform/{db/draft.js → draft/db.js} +6 -5
  97. package/lib/transform/draft/odata.js +227 -0
  98. package/lib/transform/forHanaNew.js +67 -183
  99. package/lib/transform/forOdataNew.js +17 -171
  100. package/lib/transform/localized.js +34 -19
  101. package/lib/transform/odata/generateForeignKeyElements.js +1 -1
  102. package/lib/transform/odata/referenceFlattener.js +95 -89
  103. package/lib/transform/odata/structureFlattener.js +1 -1
  104. package/lib/transform/odata/toFinalBaseType.js +86 -12
  105. package/lib/transform/odata/typesExposure.js +5 -5
  106. package/lib/transform/odata/utils.js +2 -2
  107. package/lib/transform/transformUtilsNew.js +36 -22
  108. package/lib/transform/translateAssocsToJoins.js +2 -19
  109. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  110. package/lib/transform/universalCsn/coreComputed.js +170 -0
  111. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  112. package/lib/transform/universalCsn/utils.js +63 -0
  113. package/lib/utils/objectUtils.js +30 -0
  114. package/package.json +1 -1
  115. package/share/messages/README.md +26 -0
  116. package/lib/compiler/definer.js +0 -2361
  117. package/lib/compiler/resolver.js +0 -3079
  118. package/lib/transform/universalCsnEnricher.js +0 -237
@@ -9,12 +9,14 @@ const {
9
9
  } = require('../model/csnUtils');
10
10
  const keywords = require('../base/keywords');
11
11
  const { renderFunc, beautifyExprArray, findElement } = require('./utils/common');
12
+ const { escapeString, hasUnpairedUnicodeSurrogate } = require('./utils/stringEscapes');
12
13
  const { checkCSNVersion } = require('../json/csnVersion');
13
14
  const { timetrace } = require('../utils/timetrace');
14
15
  const { csnRefs } = require('../model/csnRefs');
15
16
  const { forEachDefinition } = require('../model/csnUtils');
16
- const enrichUniversalCsn = require('../transform/universalCsnEnricher');
17
+ const enrichUniversalCsn = require('../transform/universalCsn/universalCsnEnricher');
17
18
  const { isBetaEnabled } = require('../base/model');
19
+ const { ModelError } = require('../base/error');
18
20
 
19
21
  /**
20
22
  * Render the CSN model 'model' to CDS source text. One source is created per
@@ -50,7 +52,7 @@ function toCdsSourceCsn(csn, options) {
50
52
 
51
53
  forEachDefinition(csn, (artifact, artifactName) => {
52
54
  const env = createEnv();
53
- const sourceStr = renderArtifact(artifactName, artifact, env); // Must come first because it populates 'env.topLevelAliases'
55
+ const sourceStr = renderArtifact(artifactName, artifact, env);
54
56
  if (sourceStr !== '')
55
57
  cdlResult[main] += `${sourceStr}\n`;
56
58
  });
@@ -132,7 +134,7 @@ function toCdsSourceCsn(csn, options) {
132
134
  if (csn.extensions) {
133
135
  const env = createEnv();
134
136
  const sourceStr = renderUnappliedExtensions(csn.extensions, env);
135
- cdlResult.unappliedExtensions = renderUsings('', env) + sourceStr;
137
+ cdlResult.unappliedExtensions = sourceStr;
136
138
  }
137
139
 
138
140
  timetrace.stop();
@@ -233,7 +235,6 @@ function toCdsSourceCsn(csn, options) {
233
235
 
234
236
  switch (art.kind) {
235
237
  case 'entity':
236
- case 'view':
237
238
  if (art.query || art.projection)
238
239
  return renderView(artifactName, art, env);
239
240
 
@@ -242,8 +243,6 @@ function toCdsSourceCsn(csn, options) {
242
243
  case 'context':
243
244
  case 'service':
244
245
  return renderContext(artifactName, art, env);
245
- case 'namespace':
246
- return renderNamespace(artifactName, art, env);
247
246
  case 'type':
248
247
  case 'aspect':
249
248
  case 'annotation':
@@ -254,7 +253,7 @@ function toCdsSourceCsn(csn, options) {
254
253
  case 'event':
255
254
  return renderEventIfCDLMode(artifactName, art, env);
256
255
  default:
257
- throw new Error(`Unknown artifact kind: ${art.kind}`);
256
+ throw new ModelError(`Unknown artifact kind: ${art.kind}`);
258
257
  }
259
258
  }
260
259
 
@@ -294,37 +293,6 @@ function toCdsSourceCsn(csn, options) {
294
293
  return result;
295
294
  }
296
295
 
297
- /**
298
- * Return a dictionary with the direct sub-artifacts of the artifact with name 'artifactName' in the csn
299
- *
300
- * @param {string} artifactName
301
- * @return {object}
302
- */
303
- function getSubArtifacts(artifactName) {
304
- const prefix = `${artifactName}.`;
305
- const result = Object.create(null);
306
- for (const name in csn.definitions) {
307
- // We have a direct child if its name starts with prefix and contains no more dots
308
- if (name.startsWith(prefix) && !name.substring(prefix.length).includes('.')) {
309
- result[getLastPartOf(name)] = csn.definitions[name];
310
- }
311
- else if (name.startsWith(prefix)) {
312
- const prefixPlusNextPart = name.substring(0, name.substring(prefix.length).indexOf('.') + prefix.length);
313
- if (csn.definitions[prefixPlusNextPart]) {
314
- const art = csn.definitions[prefixPlusNextPart];
315
- if (![ 'service', 'context', 'namespace' ].includes(art.kind)) {
316
- const nameWithoutPrefix = name.substring(prefix.length);
317
- result[nameWithoutPrefix] = csn.definitions[name];
318
- }
319
- }
320
- else {
321
- result[name.substring(prefix.length)] = csn.definitions[name];
322
- }
323
- }
324
- }
325
- return result;
326
- }
327
-
328
296
  /* FIXME: Not yet required
329
297
  // Returns the artifact or element that constitutes the final type of
330
298
  // construct 'node', i.e. the object in which we would find type properties for
@@ -385,37 +353,6 @@ function toCdsSourceCsn(csn, options) {
385
353
  return `${result} {};\n`;
386
354
  }
387
355
 
388
- function updatePrefixForDottedName(env, name) {
389
- let innerEnv = env;
390
- if (name.indexOf('.') !== -1) {
391
- const parts = name.split('.');
392
- for (let i = 0; i < parts.length - 1; i++)
393
- innerEnv = addNamePrefix(innerEnv, parts[i]);
394
- }
395
-
396
- return innerEnv;
397
- }
398
-
399
- /**
400
- * Render a namespace. Return the resulting source string.
401
- *
402
- * @param {string} artifactName
403
- * @param {CSN.Artifact} art
404
- * @param {CdlRenderEnvironment} env
405
- * @return {string}
406
- */
407
- function renderNamespace(artifactName, art, env) {
408
- // We currently do not render anything for a namespace, we just append its id to
409
- // the environment's current name prefix and descend into its children
410
- let result = renderDocComment(art, env);
411
- const childEnv = addNamePrefix(env, getLastPartOf(artifactName));
412
- const subArtifacts = getSubArtifacts(artifactName);
413
- for (const name in subArtifacts)
414
- result += renderArtifact(`${artifactName}.${name}`, subArtifacts[name], updatePrefixForDottedName(childEnv, name));
415
-
416
- return result;
417
- }
418
-
419
356
  /**
420
357
  * Render a (non-projection, non-view) entity. Return the resulting source string.
421
358
  *
@@ -446,82 +383,8 @@ function toCdsSourceCsn(csn, options) {
446
383
  }
447
384
 
448
385
  result += `${env.indent}}`;
449
- result += `${renderActionsAndFunctions(art, env) + renderTechnicalConfiguration(art.technicalConfig, env)};\n`;
450
- return result;
451
- }
452
-
453
- // Render the 'technical configuration { ... }' section 'tc' of an entity.
454
- // Return the resulting source string.
455
- function renderTechnicalConfiguration(tc, env) {
456
- let result = renderDocComment(tc, env);
457
- const childEnv = increaseIndent(env);
458
-
459
- if (!tc)
460
- return result;
461
-
462
-
463
- // FIXME: How to deal with non-HANA technical configurations? We should probably just iterate all entries
464
- // in 'tc' that we find and render them all (is it syntactically allowed yet to have more than one?)
465
- tc = tc.hana;
466
- if (!tc)
467
- throw new Error('Expecting a HANA technical configuration');
468
-
469
- result += `\n${env.indent}technical ${tc.calculated ? '' : 'hana '}configuration {\n`;
470
-
471
- // Store type (must be separate because SQL wants it between 'CREATE' and 'TABLE')
472
- if (tc.storeType)
473
- result += `${tc.storeType} store;\n`;
474
-
475
- // Fixed parts belonging to the table (includes migration, unload prio, extended storage,
476
- // auto merge, partitioning, ...)
477
- if (tc.tableSuffix) {
478
- // Unlike SQL, CDL and HANA CDS require a semicolon after each table-suffix part
479
- // (e.g. `migration enabled; row store; ...`). In order to keep both
480
- // the simplicity of "the whole bandwurm is just one expression that can be
481
- // rendered to SQL without further knowledge" and at the same time telling
482
- // CDS about the boundaries, the compactor has put each part into its own `xpr`
483
- // object. Semantically equivalent because a "trivial" SQL renderer would just
484
- // concatenate them.
485
- for (const xpr of tc.tableSuffix)
486
- result += `${childEnv.indent + renderExpr(xpr, childEnv)};\n`;
487
- }
488
-
489
- // Indices and full-text indices
490
- for (const idxName in tc.indexes || {}) {
491
- if (Array.isArray(tc.indexes[idxName][0])) {
492
- // FIXME: Should we allow multiple indices with the same name at all?
493
- for (const index of tc.indexes[idxName])
494
- result += `${childEnv.indent + renderExpr(index, childEnv)};\n`;
495
- }
496
- else {
497
- result += `${childEnv.indent + renderExpr(tc.indexes[idxName], childEnv)};\n`;
498
- }
499
- }
500
- // Fuzzy search indices
501
- for (const columnName in tc.fzindexes || {}) {
502
- if (Array.isArray(tc.fzindexes[columnName][0])) {
503
- // FIXME: Should we allow multiple fuzzy search indices on the same column at all?
504
- // And if not, why do we wrap this into an array?
505
- for (const index of tc.fzindexes[columnName])
506
- result += `${childEnv.indent + renderExpr(fixFuzzyIndex(index, columnName), childEnv)};\n`;
507
- }
508
- else {
509
- result += `${childEnv.indent + renderExpr(fixFuzzyIndex(tc.fzindexes[columnName], columnName), childEnv)};\n`;
510
- }
511
- }
512
- result += `${env.indent}}`;
386
+ result += `${renderActionsAndFunctions(art, env)};\n`;
513
387
  return result;
514
-
515
- // Fuzzy indices are stored in compact CSN as they would appear in SQL after the column name,
516
- // i.e. the whole line in SQL looks somewhat like this:
517
- // s nvarchar(10) FUZZY SEARCH INDEX ON FUZZY SEARCH MODE 'ALPHANUM'
518
- // But in CDL, we don't write fuzzy search indices together with the table column, so we need
519
- // to insert the name of the column after 'ON' in CDS syntax, making it look like this:
520
- // fuzzy search mode on (s) search mode 'ALPHANUM'
521
- // This function expects an array with the original expression and returns an array with the modified expression
522
- function fixFuzzyIndex(fuzzyIndex, columnName) {
523
- return fuzzyIndex.map(token => (token === 'on' ? { xpr: [ 'on', '(', { ref: columnName.split('.') }, ')' ] } : token));
524
- }
525
388
  }
526
389
 
527
390
  /**
@@ -561,7 +424,7 @@ function toCdsSourceCsn(csn, options) {
561
424
 
562
425
  // Sanity checks
563
426
  if (!query.SET || !query.SET.args || !query.SET.args[0])
564
- throw new Error(`Expecting set with args in query: ${JSON.stringify(query)}`);
427
+ throw new ModelError(`Expecting set with args in query: ${JSON.stringify(query)}`);
565
428
 
566
429
  return leadingQuerySelect(query.SET.args[0]);
567
430
  }
@@ -620,14 +483,14 @@ function toCdsSourceCsn(csn, options) {
620
483
  if (!name.startsWith('@'))
621
484
  continue;
622
485
 
623
- const annotationValue = renderAnnotationValue(elem[name], childEnv);
486
+ const annotationValue = renderAnnotationAssignment(elem[name], name, childEnv);
624
487
  // Skip annotation if column has the same
625
488
  if (columnMap[elemName] && columnMap[elemName][name] &&
626
- renderAnnotationValue(columnMap[elemName][name], childEnv) === annotationValue)
489
+ renderAnnotationAssignment(columnMap[elemName][name], name, childEnv) === annotationValue)
627
490
  continue;
628
491
 
629
492
  // Annotation names are never flattened
630
- elemAnnotations += `${childEnv.indent}${`@${renderAbsoluteNameWithQuotes(name.substring(1))}`} : ${annotationValue}\n`;
493
+ elemAnnotations += annotationValue;
631
494
  }
632
495
  if (elemAnnotations !== '')
633
496
  result += `${elemAnnotations}${childEnv.indent}${quoteOrUppercaseId(elemName)};\n`;
@@ -703,7 +566,7 @@ function toCdsSourceCsn(csn, options) {
703
566
  function renderAbsolutePath(path, env) {
704
567
  // Sanity checks
705
568
  if (!path.ref)
706
- throw new Error(`Expecting ref in path: ${JSON.stringify(path)}`);
569
+ throw new ModelError(`Expecting ref in path: ${JSON.stringify(path)}`);
707
570
 
708
571
 
709
572
  // Determine the absolute name of the first artifact on the path (before any associations or element traversals)
@@ -909,6 +772,7 @@ function toCdsSourceCsn(csn, options) {
909
772
  * @param {string} syntax The query syntax, either "projection", "entity" or "view"
910
773
  * @param {CdlRenderEnvironment} env
911
774
  * @param {CSN.Path} [path=[]]
775
+ * @param {object} [elements]
912
776
  */
913
777
  function renderQuery(query, isLeadingQuery, syntax, env, path = [], elements = query.elements || Object.create(null)) {
914
778
  let result = renderDocComment(query, env);
@@ -935,7 +799,7 @@ function toCdsSourceCsn(csn, options) {
935
799
  }
936
800
  // Otherwise must have a SELECT
937
801
  else if (!query.SELECT) {
938
- throw new Error(`Unexpected query operation ${JSON.stringify(query)}`);
802
+ throw new ModelError(`Unexpected query operation ${JSON.stringify(query)}`);
939
803
  }
940
804
  const select = query.SELECT;
941
805
  const childEnv = increaseIndent(env);
@@ -945,7 +809,7 @@ function toCdsSourceCsn(csn, options) {
945
809
  else if (syntax === 'view' || syntax === 'entity')
946
810
  result += `select from ${renderViewSource(select.from, env)}`;
947
811
  else
948
- throw new Error(`Unknown query syntax: ${syntax}`);
812
+ throw new ModelError(`Unknown query syntax: ${syntax}`);
949
813
 
950
814
  if (select.mixin) {
951
815
  let elems = '';
@@ -980,7 +844,7 @@ function toCdsSourceCsn(csn, options) {
980
844
  result += `${continueIndent(result, env)}where ${renderExpr(select.where, env, true, true)}`;
981
845
 
982
846
  if (select.groupBy)
983
- result += `${continueIndent(result, env)}group by ${select.groupBy.map(expr => renderExpr(expr, env)).join(', ')}`;
847
+ result += `${continueIndent(result, env)}group by ${select.groupBy.map(expr => renderExpr(expr, env, true, false, true)).join(', ')}`;
984
848
 
985
849
  if (select.having)
986
850
  result += `${continueIndent(result, env)}having ${renderExpr(select.having, env, true, true)}`;
@@ -1037,7 +901,7 @@ function toCdsSourceCsn(csn, options) {
1037
901
  * @return {string}
1038
902
  */
1039
903
  function renderOrderByEntry(entry, env) {
1040
- let result = renderDocComment(entry, env) + renderExpr(entry, env);
904
+ let result = renderDocComment(entry, env) + renderExpr(entry, env, true, false, true);
1041
905
  if (entry.sort)
1042
906
  result += ` ${entry.sort}`;
1043
907
 
@@ -1140,7 +1004,10 @@ function toCdsSourceCsn(csn, options) {
1140
1004
  }
1141
1005
  else {
1142
1006
  // Derived type or annotation with non-anonymous type
1143
- result += ` : ${renderTypeReference(art, env, false)};\n`;
1007
+ result += ` : ${renderTypeReference(art, env, false)}`;
1008
+ if (art.default)
1009
+ result += ` default ${renderExpr(art.default, env)}`;
1010
+ result += ';\n';
1144
1011
  }
1145
1012
  return result;
1146
1013
  }
@@ -1176,7 +1043,7 @@ function toCdsSourceCsn(csn, options) {
1176
1043
  // Anonymous structured type
1177
1044
  if (!elm.type) {
1178
1045
  if (!elm.elements)
1179
- throw new Error(`Missing type of: ${JSON.stringify(elm)}`);
1046
+ throw new ModelError(`Missing type of: ${JSON.stringify(elm)}`);
1180
1047
 
1181
1048
  result += '{\n';
1182
1049
  const childEnv = increaseIndent(env);
@@ -1214,7 +1081,7 @@ function toCdsSourceCsn(csn, options) {
1214
1081
  result += `${env.indent}}`;
1215
1082
  }
1216
1083
  else {
1217
- throw new Error('Association/Composition is missing its target! Throwing exception to trigger recompilation.');
1084
+ throw new ModelError('Association/Composition is missing its target! Throwing exception to trigger recompilation.');
1218
1085
  }
1219
1086
 
1220
1087
 
@@ -1346,14 +1213,16 @@ function toCdsSourceCsn(csn, options) {
1346
1213
  * @param {CdlRenderEnvironment} env
1347
1214
  * @param {boolean} [inline=true]
1348
1215
  * @param {boolean} [inExpr=false] Whether the expression is already inside another expression
1216
+ * @param {boolean} [alwaysRenderCast=false] Whether to _always_ render SQL-style casts, even if `inExpr === false`
1217
+ * Note: This is a hack for casts() inside groupBy.
1349
1218
  */
1350
- function renderExpr(x, env, inline = true, inExpr = false) {
1219
+ function renderExpr(x, env, inline = true, inExpr = false, alwaysRenderCast = false) {
1351
1220
  // Compound expression
1352
1221
  if (Array.isArray(x))
1353
1222
  return beautifyExprArray(x.map(item => renderExpr(item, env, inline, inExpr)));
1354
1223
 
1355
1224
  if (typeof x === 'object' && x !== null) {
1356
- if (inExpr && x.cast && x.cast.type)
1225
+ if ((inExpr || alwaysRenderCast) && x.cast && x.cast.type)
1357
1226
  return renderExplicitTypeCast(renderExprObject());
1358
1227
  return renderExprObject();
1359
1228
  }
@@ -1392,7 +1261,7 @@ function toCdsSourceCsn(csn, options) {
1392
1261
 
1393
1262
  // otherwise fall through to
1394
1263
  default:
1395
- throw new Error(`Unknown literal or type: ${JSON.stringify(x)}`);
1264
+ throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
1396
1265
  }
1397
1266
  }
1398
1267
  // Enum symbol
@@ -1439,7 +1308,7 @@ function toCdsSourceCsn(csn, options) {
1439
1308
  return `${renderQuery(x, false, 'view', increaseIndent(env))}`;
1440
1309
  }
1441
1310
  else {
1442
- throw new Error(`Unknown expression: ${JSON.stringify(x)}`);
1311
+ throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
1443
1312
  }
1444
1313
  }
1445
1314
 
@@ -1476,7 +1345,7 @@ function toCdsSourceCsn(csn, options) {
1476
1345
  else if (typeof s === 'object') {
1477
1346
  // Sanity check
1478
1347
  if (!s.func && !s.id)
1479
- throw new Error(`Unknown path step object: ${JSON.stringify(s)}`);
1348
+ throw new ModelError(`Unknown path step object: ${JSON.stringify(s)}`);
1480
1349
 
1481
1350
  // Not really a path step but an object-like function call
1482
1351
  if (s.func)
@@ -1496,7 +1365,7 @@ function toCdsSourceCsn(csn, options) {
1496
1365
  return result;
1497
1366
  }
1498
1367
 
1499
- throw new Error(`Unknown path step: ${JSON.stringify(s)}`);
1368
+ throw new ModelError(`Unknown path step: ${JSON.stringify(s)}`);
1500
1369
  }
1501
1370
  }
1502
1371
 
@@ -1513,14 +1382,14 @@ function toCdsSourceCsn(csn, options) {
1513
1382
  const args = node.args ? node.args : {};
1514
1383
  // Positional arguments
1515
1384
  if (Array.isArray(args))
1516
- return args.map(arg => renderExpr(arg, env)).join(', ');
1385
+ return args.map(arg => renderExpr(arg, env, true, false, true)).join(', ');
1517
1386
 
1518
1387
  // Named arguments (object/dict)
1519
1388
  else if (typeof args === 'object')
1520
- return Object.keys(args).map(key => `${quoteOrUppercaseId(key)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
1389
+ return Object.keys(args).map(key => `${quoteOrUppercaseId(key)} ${sep} ${renderExpr(args[key], env, true, false, true)}`).join(', ');
1521
1390
 
1522
1391
 
1523
- throw new Error(`Unknown args: ${JSON.stringify(args)}`);
1392
+ throw new ModelError(`Unknown args: ${JSON.stringify(args)}`);
1524
1393
  }
1525
1394
 
1526
1395
  /**
@@ -1689,23 +1558,6 @@ function toCdsSourceCsn(csn, options) {
1689
1558
  return absName;
1690
1559
  }
1691
1560
 
1692
- /**
1693
- * Render appropriate USING directives for all artifacts used by artifact 'artifactName' in 'env'.
1694
- *
1695
- * @param {string} artifactName
1696
- * @param {CdlRenderEnvironment} env
1697
- * @return {string}
1698
- */
1699
- function renderUsings(artifactName, env) {
1700
- const distinct = {};
1701
- Object.keys(env.topLevelAliases)
1702
- .forEach((name) => {
1703
- distinct[`using ${env.topLevelAliases[name].quotedName} as ${env.topLevelAliases[name].quotedAlias};\n`] = '';
1704
- });
1705
-
1706
- return Object.keys(distinct).join('');
1707
- }
1708
-
1709
1561
  /**
1710
1562
  * Returns a newly created default environment (which keeps track of indentation, required USING
1711
1563
  * declarations and name prefixes.
@@ -1725,17 +1577,6 @@ function toCdsSourceCsn(csn, options) {
1725
1577
  };
1726
1578
  }
1727
1579
 
1728
- /**
1729
- * Returns a copy of 'env' with (quoted) name prefix 'id' and a dot appended to the current name prefix
1730
- *
1731
- * @param {CdlRenderEnvironment} env
1732
- * @param {string} id
1733
- * @returns {CdlRenderEnvironment}
1734
- */
1735
- function addNamePrefix(env, id) {
1736
- return Object.assign({}, env, { namePrefix: `${env.namePrefix + quoteIdIfRequired(id)}.` });
1737
- }
1738
-
1739
1580
  /**
1740
1581
  * Returns a copy of 'env' with increased indentation (and resetted name prefix)
1741
1582
  *
@@ -1821,7 +1662,7 @@ function toCdsSourceCsn(csn, options) {
1821
1662
  * of another entity -> Service.E and Service.E.Sub
1822
1663
  *
1823
1664
  * To handle such cases for hdbcds in quoted/hdbcds, we:
1824
- * - Find the part of the name that is no longer prefix (context/service/namespace)
1665
+ * - Find the part of the name that is no longer prefix (context/service)
1825
1666
  * - For Service.E -> E, for Service.E.Sub -> E.Sub
1826
1667
  * - Replace all dots in this "real name" with underscores
1827
1668
  * - Join with the env prefix
@@ -1848,14 +1689,14 @@ function toCdsSourceCsn(csn, options) {
1848
1689
  }
1849
1690
 
1850
1691
  /**
1851
- * Get the part that is really the name of this artifact and not just prefix caused by a context/service/namespace
1692
+ * Get the part that is really the name of this artifact and not just prefix caused by a context/service
1852
1693
  *
1853
1694
  * @param {String} artifactName Artifact name to use
1854
1695
  * @returns {String} non-prefix part of the artifact name
1855
1696
  */
1856
1697
  function getRealName(artifactName) {
1857
1698
  const parts = artifactName.split('.');
1858
- // Lenght of 1 -> There can be no prefix
1699
+ // Length of 1 -> There can be no prefix
1859
1700
  if (parts.length === 1)
1860
1701
  return artifactName;
1861
1702
 
@@ -1869,7 +1710,7 @@ function toCdsSourceCsn(csn, options) {
1869
1710
 
1870
1711
 
1871
1712
  const art = csn.definitions[seen];
1872
- if (!art || ![ 'service', 'context', 'namespace' ].includes(art.kind)) {
1713
+ if (!art || (art.kind !== 'service' && art.kind !== 'context')) {
1873
1714
  // We found a case where the prefix ended
1874
1715
  // Return everything following
1875
1716
  return parts.slice(i).join('.');
@@ -1881,13 +1722,9 @@ function toCdsSourceCsn(csn, options) {
1881
1722
  }
1882
1723
  }
1883
1724
 
1884
- const controlCharacters = /[\u{0000}-\u{0009}\u{000B}\u{001F}]/u;
1885
- const highSurrogate = /[\u{D800}-\u{DBFF}]/u;
1886
- const lowSurrogate = /[\u{DC00}-\u{DFFF}]/u;
1887
-
1888
1725
  /**
1889
1726
  * Render the given string. Uses back-tick strings.
1890
- * env is used for indentation for three-back-tick strings.
1727
+ * env is used for indentation of three-back-tick strings.
1891
1728
  *
1892
1729
  * @param str
1893
1730
  * @param env
@@ -1897,90 +1734,33 @@ function renderString(str, env) {
1897
1734
  if (isSimpleString(str))
1898
1735
  return `'${str.replace(/'/g, '\'\'')}'`;
1899
1736
 
1900
- const output = [];
1901
-
1902
1737
  // We try to work similar to how JavaScript implements JSON.stringify.
1903
- // JSON.stringify, however, checks for unpaired unicode surrogates (see §25.5.2.2,
1904
- // <https://tc39.es/ecma262/#sec-quotejsonstring>), which we do not do here.
1905
-
1906
- for (let i = 0; i < str.length; ++i) {
1907
- const char = str[i];
1908
-
1909
- switch (char) {
1910
- case '$':
1911
- output.push('\\$');
1912
- break;
1913
- case '`':
1914
- output.push('\\`');
1915
- break;
1916
- case '\\':
1917
- output.push('\\\\');
1918
- break;
1919
- // Replace commonly known escape sequences for control characters
1920
- // See lib/language/multiLineStringParser.js
1921
- case '\f':
1922
- output.push('\\f');
1923
- break;
1924
- case '\v':
1925
- output.push('\\v');
1926
- break;
1927
- case '\t':
1928
- output.push('\\t');
1929
- break;
1930
- case '\b':
1931
- output.push('\\b');
1932
- break;
1933
- // If CR, LS, or PS appear, they have been encoded explicitly. If we don't escape
1934
- // them, a recompilation may yield different results because of newline normalization.
1935
- case '\r':
1936
- output.push('\\r');
1937
- break;
1938
- case '\u{2028}':
1939
- output.push('\\u{2028}');
1940
- break;
1941
- case '\u{2029}':
1942
- output.push('\\u{2029}');
1943
- break;
1944
- default: {
1945
- // Control Characters: C0
1946
- // See <https://en.wikipedia.org/wiki/C0_and_C1_control_codes#Basic_ASCII_control_codes>
1947
- //
1948
- // JSON.stringify is not required to escape all control characters, but if used, files may
1949
- // be interpreted as binary. Therefore, we replace them.
1950
- //
1951
- // We exclude LF from this list (\u000A). Characters with "nice" escapes have been replaced above.
1952
- if (controlCharacters.test(char)) {
1953
- const hex = char.codePointAt(0).toString(16);
1954
- output.push(`\\u{${hex}}`);
1955
- break;
1956
- }
1738
+ // JSON.stringify() also checks for unpaired unicode surrogates (see §25.5.2.2,
1739
+ // <https://tc39.es/ecma262/#sec-quotejsonstring>).
1740
+ str = escapeString(str, {
1741
+ $: '\\$',
1742
+ '`': '\\`',
1743
+ '\\': '\\\\',
1744
+ // Replace commonly known escape sequences for control characters
1745
+ // See lib/language/multiLineStringParser.js
1746
+ '\f': '\\f',
1747
+ '\v': '\\v',
1748
+ '\t': '\\t',
1749
+ '\b': '\\b',
1750
+ // If CR, LS, or PS appear, they have been encoded explicitly. If we don't escape
1751
+ // them, a recompilation may yield different results because of newline normalization.
1752
+ '\r': '\\r',
1753
+ '\u{2028}': '\\u{2028}',
1754
+ '\u{2029}': '\\u{2029}',
1755
+ // Don't encode LF
1756
+ '\n': '\n',
1757
+ // JSON.stringify() is not required to escape all control characters, but if used, files may
1758
+ // be interpreted as binary. Therefore, we replace them.
1759
+ // We exclude LF from this list (\n). Characters with "nice" escapes have been replaced above.
1760
+ control: hexEscape,
1761
+ unpairedSurrogate: hexEscape,
1762
+ });
1957
1763
 
1958
- // Unicode Surrogates
1959
- // These characters appear in _pairs_. A high surrogate must be followed by a low surrogate.
1960
- // If this is not the case, either needs to be encoded. This is also done by JSON.
1961
- // See also <https://docs.microsoft.com/en-us/globalization/encoding/surrogate-pairs>
1962
- if (highSurrogate.test(char)) {
1963
- if (i + 1 >= str.length || !lowSurrogate.test(str[i + 1])) {
1964
- const hex = char.codePointAt(0).toString(16);
1965
- output.push(`\\u{${hex}}`);
1966
- }
1967
- else {
1968
- output.push(char);
1969
- ++i;
1970
- output.push(str[i]);
1971
- }
1972
- }
1973
- else if (lowSurrogate.test(char)) {
1974
- const hex = char.codePointAt(0).toString(16);
1975
- output.push(`\\u{${hex}}`);
1976
- }
1977
- else {
1978
- output.push(char);
1979
- }
1980
- }
1981
- }
1982
- }
1983
- str = output.join('');
1984
1764
  // Note: String is normalized, only \n is the line separator.
1985
1765
  const lines = str.split('\n');
1986
1766
  // We don't know whether a text block was used or not. But if there
@@ -1994,6 +1774,12 @@ function renderString(str, env) {
1994
1774
  return `\`${str}\``;
1995
1775
  }
1996
1776
 
1777
+ /** @param {number} codePoint */
1778
+ function hexEscape(codePoint) {
1779
+ const hex = codePoint.toString(16);
1780
+ return `\\u{${hex}}`;
1781
+ }
1782
+
1997
1783
  /**
1998
1784
  * Returns true if the given string can be represented by using single quotes.
1999
1785
  * @param {string} str
@@ -2001,14 +1787,13 @@ function renderString(str, env) {
2001
1787
  function isSimpleString(str) {
2002
1788
  // A single-line string allows everything except certain line separators/breaks.
2003
1789
  // See ANTLR grammar for specifics.
2004
- // Furthermore, if control characters \u{0000}-\u{001F} are used, we escape them,
1790
+ // Furthermore, if control characters are used, we escape them,
2005
1791
  // as these are explicitly mentioned in the JSON spec (§9):
2006
1792
  // <https://www.ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf>
2007
- // On top, as (invalid) surrogate pairs need to be handled, we check for them as well.
1793
+ // On top, because (invalid) surrogate pairs need to be handled, we check for them as well.
2008
1794
  // v3: Not a simple string if ' (\u0027) is in string.
2009
1795
  return str === '' || (/^[^\u{0000}-\u{001F}\u2028\u2029]+$/u.test(str) &&
2010
- !highSurrogate.test(str) &&
2011
- !lowSurrogate.test(str));
1796
+ !hasUnpairedUnicodeSurrogate(str));
2012
1797
  }
2013
1798
 
2014
1799
  /**