@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.
- package/CHANGELOG.md +58 -1
- package/bin/cds_update_identifiers.js +7 -7
- package/bin/cdsc.js +9 -10
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +12 -0
- package/lib/api/main.js +2 -0
- package/lib/api/options.js +2 -2
- package/lib/base/message-registry.js +31 -2
- package/lib/base/model.js +1 -0
- package/lib/base/optionProcessorHelper.js +97 -69
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/sql-snippets.js +93 -0
- package/lib/checks/validator.js +8 -0
- package/lib/compiler/assert-consistency.js +5 -3
- package/lib/compiler/base.js +0 -1
- package/lib/compiler/checks.js +32 -9
- package/lib/compiler/definer.js +25 -4
- package/lib/compiler/index.js +1 -1
- package/lib/compiler/propagator.js +3 -2
- package/lib/compiler/resolver.js +97 -6
- package/lib/compiler/shared.js +12 -1
- package/lib/compiler/utils.js +7 -0
- package/lib/edm/annotations/genericTranslation.js +34 -17
- package/lib/edm/annotations/preprocessAnnotations.js +1 -1
- package/lib/edm/csn2edm.js +1 -1
- package/lib/edm/edm.js +8 -8
- package/lib/edm/edmPreprocessor.js +30 -23
- package/lib/edm/edmUtils.js +11 -12
- package/lib/gen/Dictionary.json +82 -40
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/language.tokens +15 -14
- package/lib/gen/languageLexer.interp +9 -1
- package/lib/gen/languageLexer.js +830 -779
- package/lib/gen/languageLexer.tokens +7 -6
- package/lib/gen/languageParser.js +2401 -2282
- package/lib/json/from-csn.js +47 -16
- package/lib/json/to-csn.js +17 -5
- package/lib/language/antlrParser.js +3 -3
- package/lib/language/docCommentParser.js +1 -1
- package/lib/language/genericAntlrParser.js +68 -51
- package/lib/language/language.g4 +128 -74
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +5 -3
- package/lib/main.js +3 -2
- package/lib/model/csnRefs.js +116 -68
- package/lib/model/csnUtils.js +40 -48
- package/lib/model/enrichCsn.js +30 -14
- package/lib/optionProcessor.js +3 -3
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +193 -79
- package/lib/render/toHdbcds.js +179 -95
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +57 -40
- package/lib/render/utils/common.js +24 -5
- package/lib/render/utils/sql.js +6 -4
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/associations.js +389 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +6 -4
- package/lib/transform/db/draft.js +3 -2
- package/lib/transform/db/expansion.js +4 -5
- package/lib/transform/db/flattening.js +5 -6
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +36 -23
- package/lib/transform/forHanaNew.js +35 -626
- package/lib/transform/forOdataNew.js +5 -4
- package/lib/transform/localized.js +3 -14
- package/lib/transform/odata/generateForeignKeyElements.js +2 -2
- package/lib/transform/transformUtilsNew.js +13 -13
- package/lib/transform/translateAssocsToJoins.js +8 -8
- package/lib/transform/universalCsnEnricher.js +217 -47
- package/lib/utils/file.js +2 -1
- package/lib/utils/timetrace.js +8 -2
- package/package.json +1 -1
package/lib/optionProcessor.js
CHANGED
|
@@ -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.
|
|
@@ -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 =
|
|
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
|
package/lib/render/toCdl.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
|
|
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
|
|
43
|
+
const cdlResult = Object.create(null);
|
|
44
44
|
|
|
45
45
|
const main = 'model';
|
|
46
46
|
|
|
47
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
120
|
+
cdlResult[main] += `${sourceStr}\n`;
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
if (csn.namespace) {
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
135
|
+
cdlResult.unappliedExtensions = renderUsings('', env) + sourceStr;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
timetrace.stop();
|
|
139
|
-
return
|
|
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}
|
|
1000
|
-
* @param {CdlRenderEnvironment}
|
|
999
|
+
* @param {string} str
|
|
1000
|
+
* @param {CdlRenderEnvironment} indentEnv
|
|
1001
1001
|
* @return {string}
|
|
1002
1002
|
*/
|
|
1003
|
-
function continueIndent(
|
|
1004
|
-
if (
|
|
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(
|
|
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}
|
|
1016
|
+
* @param {CdlRenderEnvironment} limitEnv
|
|
1017
1017
|
* @return {string}
|
|
1018
1018
|
*/
|
|
1019
|
-
function renderLimit(limit,
|
|
1020
|
-
let
|
|
1019
|
+
function renderLimit(limit, limitEnv) {
|
|
1020
|
+
let limitStr = '';
|
|
1021
1021
|
if (limit.rows !== undefined)
|
|
1022
|
-
|
|
1022
|
+
limitStr += `limit ${renderExpr(limit.rows, limitEnv)}`;
|
|
1023
1023
|
|
|
1024
1024
|
if (limit.offset !== undefined)
|
|
1025
|
-
|
|
1025
|
+
limitStr += `${limitStr !== '' ? `\n${increaseIndent(limitEnv).indent}` : ''}offset ${renderExpr(limit.offset, limitEnv)}`;
|
|
1026
1026
|
|
|
1027
|
-
return
|
|
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') ?
|
|
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
|
|
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 '
|
|
1646
|
-
* We might see variants like 'A.B.C#foo' or even 'A.B#foo.C'.
|
|
1647
|
-
*
|
|
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}
|
|
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(
|
|
1655
|
-
|
|
1656
|
-
//
|
|
1657
|
-
const parts = name.
|
|
1658
|
-
const nameBeforeVariant = parts[
|
|
1659
|
-
const variant = parts[
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
result
|
|
1673
|
-
|
|
1674
|
-
|
|
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(
|
|
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
|
|
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
|
*
|