@sap/cds-compiler 2.11.4 → 2.12.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 (80) hide show
  1. package/CHANGELOG.md +58 -1
  2. package/bin/cds_update_identifiers.js +7 -7
  3. package/bin/cdsc.js +9 -10
  4. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  5. package/doc/CHANGELOG_BETA.md +12 -0
  6. package/lib/api/main.js +2 -0
  7. package/lib/api/options.js +2 -2
  8. package/lib/base/message-registry.js +31 -2
  9. package/lib/base/model.js +1 -0
  10. package/lib/base/optionProcessorHelper.js +97 -69
  11. package/lib/checks/.eslintrc.json +2 -0
  12. package/lib/checks/actionsFunctions.js +2 -1
  13. package/lib/checks/foreignKeys.js +4 -4
  14. package/lib/checks/managedInType.js +4 -4
  15. package/lib/checks/queryNoDbArtifacts.js +1 -3
  16. package/lib/checks/sql-snippets.js +93 -0
  17. package/lib/checks/validator.js +8 -0
  18. package/lib/compiler/assert-consistency.js +5 -3
  19. package/lib/compiler/base.js +0 -1
  20. package/lib/compiler/checks.js +32 -9
  21. package/lib/compiler/definer.js +25 -4
  22. package/lib/compiler/index.js +1 -1
  23. package/lib/compiler/propagator.js +3 -2
  24. package/lib/compiler/resolver.js +97 -6
  25. package/lib/compiler/shared.js +12 -1
  26. package/lib/compiler/utils.js +7 -0
  27. package/lib/edm/annotations/genericTranslation.js +34 -17
  28. package/lib/edm/annotations/preprocessAnnotations.js +1 -1
  29. package/lib/edm/csn2edm.js +1 -1
  30. package/lib/edm/edm.js +8 -8
  31. package/lib/edm/edmPreprocessor.js +30 -23
  32. package/lib/edm/edmUtils.js +11 -12
  33. package/lib/gen/Dictionary.json +82 -40
  34. package/lib/gen/language.checksum +1 -1
  35. package/lib/gen/language.interp +3 -1
  36. package/lib/gen/language.tokens +15 -14
  37. package/lib/gen/languageLexer.interp +9 -1
  38. package/lib/gen/languageLexer.js +830 -779
  39. package/lib/gen/languageLexer.tokens +7 -6
  40. package/lib/gen/languageParser.js +2401 -2282
  41. package/lib/json/from-csn.js +47 -16
  42. package/lib/json/to-csn.js +17 -5
  43. package/lib/language/antlrParser.js +3 -3
  44. package/lib/language/docCommentParser.js +1 -1
  45. package/lib/language/genericAntlrParser.js +68 -51
  46. package/lib/language/language.g4 +128 -74
  47. package/lib/language/multiLineStringParser.js +536 -0
  48. package/lib/main.d.ts +5 -3
  49. package/lib/main.js +3 -2
  50. package/lib/model/csnRefs.js +116 -68
  51. package/lib/model/csnUtils.js +40 -48
  52. package/lib/model/enrichCsn.js +30 -14
  53. package/lib/optionProcessor.js +3 -3
  54. package/lib/render/DuplicateChecker.js +1 -1
  55. package/lib/render/manageConstraints.js +1 -1
  56. package/lib/render/toCdl.js +193 -79
  57. package/lib/render/toHdbcds.js +179 -95
  58. package/lib/render/toRename.js +7 -10
  59. package/lib/render/toSql.js +57 -40
  60. package/lib/render/utils/common.js +24 -5
  61. package/lib/render/utils/sql.js +6 -4
  62. package/lib/transform/braceExpression.js +4 -2
  63. package/lib/transform/db/associations.js +389 -0
  64. package/lib/transform/db/cdsPersistence.js +150 -0
  65. package/lib/transform/db/constraints.js +6 -4
  66. package/lib/transform/db/draft.js +3 -2
  67. package/lib/transform/db/expansion.js +4 -5
  68. package/lib/transform/db/flattening.js +5 -6
  69. package/lib/transform/db/temporal.js +236 -0
  70. package/lib/transform/db/transformExists.js +36 -23
  71. package/lib/transform/forHanaNew.js +35 -626
  72. package/lib/transform/forOdataNew.js +5 -4
  73. package/lib/transform/localized.js +3 -14
  74. package/lib/transform/odata/generateForeignKeyElements.js +2 -2
  75. package/lib/transform/transformUtilsNew.js +13 -13
  76. package/lib/transform/translateAssocsToJoins.js +8 -8
  77. package/lib/transform/universalCsnEnricher.js +217 -47
  78. package/lib/utils/file.js +2 -1
  79. package/lib/utils/timetrace.js +8 -2
  80. package/package.json +1 -1
@@ -30,7 +30,7 @@ optionProcessor
30
30
  .option(' --integrity-not-validated')
31
31
  .option(' --integrity-not-enforced')
32
32
  .option(' --assert-integrity <mode>', [ 'true', 'false', 'individual' ])
33
- .option(' --assert-integrity-type <type>', [ 'RT', 'DB' ])
33
+ .option(' --assert-integrity-type <type>', [ 'RT', 'DB' ], { ignoreCase: true })
34
34
  .option(' --constraints-as-alter <boolean>')
35
35
  .option(' --deprecated <list>')
36
36
  .option(' --hana-flavor')
@@ -104,7 +104,7 @@ optionProcessor
104
104
  --integrity-not-validated If this option is supplied, referential constraints are NOT VALIDATED.
105
105
  This option is also applied to result of "cdsc manageConstraints"
106
106
  --assert-integrity <mode> Turn DB constraints on/off:
107
- true : Constraints will be generated for all associations if
107
+ true : (default) Constraints will be generated for all associations if
108
108
  the assert-integrity-type is set to DB
109
109
  false : No constraints will be generated
110
110
  individual : Constraints will be generated for selected associations
@@ -112,7 +112,7 @@ optionProcessor
112
112
  RT : (default) No database constraint for an association
113
113
  if not explicitly demanded via annotation
114
114
  DB : Create database constraints for associations
115
- --constraints-as-alter <boolean> If set to 'true', the foreign key constraints will be rendered as
115
+ --constraints-as-alter <boolean> If set to 'true', the foreign key constraints will be rendered as
116
116
  "ALTER TABLE ADD CONSTRAINT" statement rather than being part of the
117
117
  "CREATE TABLE" statement
118
118
  --deprecated <list> Comma separated list of deprecated options.
@@ -62,7 +62,7 @@ class DuplicateChecker {
62
62
  * Add an element to the "seen"-list
63
63
  *
64
64
  * @param {string} name Rendered element name
65
- * @param {CSN.Location} location
65
+ * @param {CSN.Location|CSN.Path} location
66
66
  * @param {string} modelName CSN element name
67
67
  *
68
68
  */
@@ -65,7 +65,7 @@ function listReferentialIntegrityViolations(csn, options) {
65
65
  const referentialConstraints = getListOfAllConstraints(csn);
66
66
  const resultArtifacts = {};
67
67
  const indent = ' ';
68
- const increaseIndent = indent => `${indent} `;
68
+ const increaseIndent = str => ` ${str}`;
69
69
  // helper function to reduce parent key / foreign key array to a comma separated string which can be used in a select clause
70
70
  const keyStringReducer = prefix => (prev, curr, index) => (index > 0 ? `${prev},\n${curr} AS "${prefix}:${curr}"` : prev);
71
71
  // helper function to reduce the parent key / foreign key arrays of a referential constraint to a join list which can be used in a where clause
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
- getTopLevelArtifactNameOf, getLastPartOf,
4
+ getLastPartOf,
5
5
  getLastPartOfRef,
6
6
  } = require('../model/csnUtils');
7
7
  const {
@@ -40,11 +40,11 @@ function toCdsSourceCsn(csn, options) {
40
40
 
41
41
  checkCSNVersion(csn, options);
42
42
 
43
- const result = Object.create(null);
43
+ const cdlResult = Object.create(null);
44
44
 
45
45
  const main = 'model';
46
46
 
47
- result[main] = `${options.testMode ? '' : `// ${generatedByCompilerVersion()} \n`}`;
47
+ cdlResult[main] = options.testMode ? '' : `// ${generatedByCompilerVersion()} \n`;
48
48
 
49
49
  const subelementAnnotates = [];
50
50
 
@@ -52,7 +52,7 @@ function toCdsSourceCsn(csn, options) {
52
52
  const env = createEnv();
53
53
  const sourceStr = renderArtifact(artifactName, artifact, env); // Must come first because it populates 'env.topLevelAliases'
54
54
  if (sourceStr !== '')
55
- result[main] += `${sourceStr}\n`;
55
+ cdlResult[main] += `${sourceStr}\n`;
56
56
  });
57
57
 
58
58
  // Apply possible subelement/action return annotations with an "annotate X with"
@@ -70,7 +70,7 @@ function toCdsSourceCsn(csn, options) {
70
70
  if (elementName) // action returns do not have element name - we need less {} there
71
71
  sourceStr += ' }\n';
72
72
  sourceStr += '}\n';
73
- result[main] += `${sourceStr}\n`;
73
+ cdlResult[main] += `${sourceStr}\n`;
74
74
  }
75
75
  }
76
76
  }
@@ -117,13 +117,13 @@ function toCdsSourceCsn(csn, options) {
117
117
  sourceStr = renderTypeOrAnnotation(annotationName, anno, env, 'annotation');
118
118
 
119
119
  if (sourceStr !== '')
120
- result[main] += `${sourceStr}\n`;
120
+ cdlResult[main] += `${sourceStr}\n`;
121
121
  }
122
122
  }
123
123
 
124
124
  if (csn.namespace) {
125
- result[csn.namespace] = `namespace ${renderArtifactName(csn.namespace)};\n`;
126
- result[csn.namespace] += `using from './${main}.cds';`;
125
+ cdlResult[csn.namespace] = `namespace ${renderArtifactName(csn.namespace)};\n`;
126
+ cdlResult[csn.namespace] += `using from './${main}.cds';`;
127
127
  }
128
128
 
129
129
 
@@ -132,11 +132,11 @@ function toCdsSourceCsn(csn, options) {
132
132
  if (csn.extensions) {
133
133
  const env = createEnv();
134
134
  const sourceStr = renderUnappliedExtensions(csn.extensions, env);
135
- result.unappliedExtensions = renderUsings('', env) + sourceStr;
135
+ cdlResult.unappliedExtensions = renderUsings('', env) + sourceStr;
136
136
  }
137
137
 
138
138
  timetrace.stop();
139
- return result;
139
+ return cdlResult;
140
140
 
141
141
  /**
142
142
  * Render unapplied 'extend' and 'annotate' statements from the 'extensions array'
@@ -996,35 +996,35 @@ function toCdsSourceCsn(csn, options) {
996
996
  /**
997
997
  * Utility function to make sure that we continue with the same indentation in WHERE, GROUP BY, ... after a closing curly brace and beyond
998
998
  *
999
- * @param {string} result
1000
- * @param {CdlRenderEnvironment} env
999
+ * @param {string} str
1000
+ * @param {CdlRenderEnvironment} indentEnv
1001
1001
  * @return {string}
1002
1002
  */
1003
- function continueIndent(result, env) {
1004
- if (result.endsWith('}') || result.endsWith('})')) {
1003
+ function continueIndent(str, indentEnv) {
1004
+ if (str.endsWith('}') || str.endsWith('})')) {
1005
1005
  // The preceding clause ended with '}', just append after that
1006
1006
  return ' ';
1007
1007
  }
1008
1008
  // Otherwise, start new line and indent normally
1009
- return `\n${increaseIndent(env).indent}`;
1009
+ return `\n${increaseIndent(indentEnv).indent}`;
1010
1010
  }
1011
1011
 
1012
1012
  /**
1013
1013
  * Render a query's LIMIT clause, which may have also have OFFSET.
1014
1014
  *
1015
1015
  * @param {CSN.QueryLimit} limit
1016
- * @param {CdlRenderEnvironment} env
1016
+ * @param {CdlRenderEnvironment} limitEnv
1017
1017
  * @return {string}
1018
1018
  */
1019
- function renderLimit(limit, env) {
1020
- let result = '';
1019
+ function renderLimit(limit, limitEnv) {
1020
+ let limitStr = '';
1021
1021
  if (limit.rows !== undefined)
1022
- result += `limit ${renderExpr(limit.rows, env)}`;
1022
+ limitStr += `limit ${renderExpr(limit.rows, limitEnv)}`;
1023
1023
 
1024
1024
  if (limit.offset !== undefined)
1025
- result += `${result !== '' ? `\n${increaseIndent(env).indent}` : ''}offset ${renderExpr(limit.offset, env)}`;
1025
+ limitStr += `${limitStr !== '' ? `\n${increaseIndent(limitEnv).indent}` : ''}offset ${renderExpr(limit.offset, limitEnv)}`;
1026
1026
 
1027
- return result;
1027
+ return limitStr;
1028
1028
  }
1029
1029
  }
1030
1030
 
@@ -1335,7 +1335,7 @@ function toCdsSourceCsn(csn, options) {
1335
1335
  // Primitive: string, number, boolean
1336
1336
 
1337
1337
  // Quote strings, leave all others as they are
1338
- return (typeof x === 'string') ? `'${x.replace(/'/g, '\'\'')}'` : x;
1338
+ return (typeof x === 'string') ? renderString(x, env) : x;
1339
1339
  }
1340
1340
 
1341
1341
  /**
@@ -1385,7 +1385,7 @@ function toCdsSourceCsn(csn, options) {
1385
1385
  case 'timestamp':
1386
1386
  return `${x.literal}'${x.val}'`;
1387
1387
  case 'string':
1388
- return `'${x.val.replace(/'/g, '\'\'')}'`;
1388
+ return renderString(x.val, env);
1389
1389
  case 'object':
1390
1390
  if (x.val === null)
1391
1391
  return 'null';
@@ -1642,66 +1642,38 @@ function toCdsSourceCsn(csn, options) {
1642
1642
  }
1643
1643
 
1644
1644
  /**
1645
- * Render a single annotation assignment 'ann' with fully qualified name 'name' (no trailing LF).
1646
- * We might see variants like 'A.B.C#foo' or even 'A.B#foo.C'. In both cases, the #foo must be ignored
1647
- * when resolving the name, but must stay in the rendered output (quoted as necessary).
1645
+ * Render a single annotation assignment 'anno' with fully qualified name 'name' (no trailing LF).
1646
+ * We might see variants like 'A.B.C#foo' or even 'A.B#foo.C'. The latter needs to be quoted as
1647
+ * dots in the variant are not recognized by the compiler.
1648
1648
  *
1649
- * @param {any} ann Annotation value
1649
+ * @param {any} anno Annotation value
1650
1650
  * @param {string} name Annotation name, e.g. `@A.B.C#foo.C`
1651
1651
  * @param {CdlRenderEnvironment} env
1652
- * @return {string}
1652
+ * @return {string} Rendered annotation, possibly quoted: `@![A.B.C#foo.C]: value`
1653
1653
  */
1654
- function renderAnnotationAssignment(ann, name, env) {
1655
- // Take the annotation assignment apart into <nameBeforeVariant>[#<variant>[.<nameAfterVariant>]]
1656
- // @1111111 3333333 5555
1657
- const parts = name.match(/@([^#]+)(#([^.]+)(\.(.+))?)?/);
1658
- const nameBeforeVariant = parts[1];
1659
- const variant = parts[3];
1660
- const nameAfterVariant = parts[5];
1661
- const topLevelName = getTopLevelArtifactNameOf(nameBeforeVariant, csn);
1662
- let result = `${env.indent}@`;
1663
- if (topLevelName) {
1664
- // Checked annotation, with declaration - must render top-level absolute name with proper using and alias
1665
- // Annotation names are never flattened
1666
- result += renderAbsoluteNameWithQuotes(topLevelName);
1667
- if (topLevelName !== nameBeforeVariant)
1668
- result += `.${quotePathString(nameBeforeVariant.substring(topLevelName.length + 1))}`;
1669
- }
1670
- else {
1671
- // Unchecked annotation, just render the name as it is
1672
- result += nameBeforeVariant;
1673
- }
1674
- // Append '#'-variant if any
1675
- if (variant) {
1676
- // FIXME: Unfortunately, the compiler does not yet understand an inner variant with proper quoting,
1677
- // i.e. like "__A"."B"#"foo"."C". As a workaround, we present the '#'-variant as a quoted part of the
1678
- // previous path step, i.e as "__A"."B#foo"."C" (which yields the same result). This hack is only necessary
1679
- // for inner '#'-variants, i.e. for those followed by a <nameAfterVariant>.
1680
- // FIXME: Won't work for inner variants on the top-level artifact, because the USING no longer matches
1681
- // for something like "__A#foo"."B"."C"
1682
- if (nameAfterVariant) {
1683
- const resultSteps = result.split('.');
1684
- // Take all paths steps from the result (which is now essentially 'nameBeforeVariant' with USING
1685
- // adaptations) except the last step
1686
- result = resultSteps.slice(0, -1).join('.');
1687
- // Append a combination of last path step and '#'-variant (quoted)
1688
- let lastStep = resultSteps[resultSteps.length - 1];
1689
- if (lastStep.includes('"')) {
1690
- // Last step was already quoted - strip off the existing quotes
1691
- lastStep = lastStep.slice(1, -1);
1692
- }
1693
- result += `.${quoteIdIfRequired(`${lastStep}#${variant}`)}`;
1694
- }
1695
- else {
1696
- // No hack required for trailing '#'-variant
1697
- result += `#${quoteIdIfRequired(variant)}`;
1698
- }
1699
- }
1700
- // Append anything that might have come after the variant
1701
- if (nameAfterVariant)
1702
- result += `.${quotePathString(nameAfterVariant)}`;
1654
+ function renderAnnotationAssignment(anno, name, env) {
1655
+ name = name.substring(1);
1656
+ // Take the annotation assignment apart into <nameBeforeVariant>#<variantAndRest>
1657
+ const parts = name.split('#');
1658
+ const nameBeforeVariant = parts[0];
1659
+ const variant = parts[1];
1660
+
1661
+ // Identifier according to our grammar: /[$_a-zA-Z][$_a-zA-Z0-9]*/
1662
+ // We expand this pattern to also include dots after the first character.
1663
+ // If the annotation does not follow this pattern `ident(.@ident)*`, it must be quoted:
1664
+ // `@identifier@identifier` must be quoted but `@identifier.@identifier` should not.
1665
+ const annoRequiresQuoting = !/^[$_a-zA-Z][$_a-zA-Z0-9.]*(?:\.@[$_a-zA-Z][$_a-zA-Z0-9.]*)*$/.test(nameBeforeVariant);
1666
+ // Unfortunately, the compiler does not allow `.` after the first variant identifier,
1667
+ // even though that is the result after flattening.
1668
+ const variantRequiresQuoting = variant && !/^[$_a-zA-Z][$_a-zA-Z0-9]*$/.test(variant);
1669
+
1670
+ let result;
1671
+ if (annoRequiresQuoting || variantRequiresQuoting)
1672
+ result = `${env.indent}@${quote(name)}`;
1673
+ else
1674
+ result = `${env.indent}@${name}`;
1703
1675
 
1704
- result += ` : ${renderAnnotationValue(ann, env)}`;
1676
+ result += ` : ${renderAnnotationValue(anno, env)}`;
1705
1677
  return `${result}\n`;
1706
1678
  }
1707
1679
 
@@ -1748,6 +1720,8 @@ function toCdsSourceCsn(csn, options) {
1748
1720
  topLevelAliases: Object.create(null),
1749
1721
  // Current name prefix (including trailing dot if not empty)
1750
1722
  namePrefix: '',
1723
+ artifactName: null,
1724
+ elementName: null,
1751
1725
  };
1752
1726
  }
1753
1727
 
@@ -1792,11 +1766,21 @@ function toCdsSourceCsn(csn, options) {
1792
1766
  function quoteIdIfRequired(id) {
1793
1767
  // Quote if required for CDL
1794
1768
  if (requiresQuotingForCdl(id))
1795
- return `![${id.replace(/]/g, ']]')}]`;
1769
+ return quote(id);
1796
1770
 
1797
1771
  return id;
1798
1772
  }
1799
1773
 
1774
+ /**
1775
+ * Quotes the identifier using CDL-style ![]-quotes.
1776
+ *
1777
+ * @param id
1778
+ * @returns {string}
1779
+ */
1780
+ function quote(id) {
1781
+ return `![${id.replace(/]/g, ']]')}]`;
1782
+ }
1783
+
1800
1784
  /**
1801
1785
  * Returns true if 'id' requires quotes for CDL, i.e. if 'id'
1802
1786
  * 1. starts with a digit
@@ -1897,6 +1881,136 @@ function toCdsSourceCsn(csn, options) {
1897
1881
  }
1898
1882
  }
1899
1883
 
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
+ /**
1889
+ * Render the given string. Uses back-tick strings.
1890
+ * env is used for indentation for three-back-tick strings.
1891
+ *
1892
+ * @param str
1893
+ * @param env
1894
+ * @returns {string}
1895
+ */
1896
+ function renderString(str, env) {
1897
+ if (isSimpleString(str))
1898
+ return `'${str.replace(/'/g, '\'\'')}'`;
1899
+
1900
+ const output = [];
1901
+
1902
+ // 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
+ }
1957
+
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
+ // Note: String is normalized, only \n is the line separator.
1985
+ const lines = str.split('\n');
1986
+ // We don't know whether a text block was used or not. But if there
1987
+ // are more than three lines, text blocks with indentation "look nicer".
1988
+ // This value was chosen by personal taste.
1989
+ if (lines.length > 3) {
1990
+ str = lines.join(`\n${env.indent}`);
1991
+ return `\`\`\`\n${env.indent}${str}\n${env.indent}\`\`\``;
1992
+ }
1993
+
1994
+ return `\`${str}\``;
1995
+ }
1996
+
1997
+ /**
1998
+ * Returns true if the given string can be represented by using single quotes.
1999
+ * @param {string} str
2000
+ */
2001
+ function isSimpleString(str) {
2002
+ // A single-line string allows everything except certain line separators/breaks.
2003
+ // See ANTLR grammar for specifics.
2004
+ // Furthermore, if control characters \u{0000}-\u{001F} are used, we escape them,
2005
+ // as these are explicitly mentioned in the JSON spec (§9):
2006
+ // <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.
2008
+ // v3: Not a simple string if ' (\u0027) is in string.
2009
+ return str === '' || (/^[^\u{0000}-\u{001F}\u2028\u2029]+$/u.test(str) &&
2010
+ !highSurrogate.test(str) &&
2011
+ !lowSurrogate.test(str));
2012
+ }
2013
+
1900
2014
  /**
1901
2015
  * @typedef CdlRenderEnvironment Rendering environment used throughout the render process.
1902
2016
  *