@sap/cds-compiler 2.10.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 +136 -0
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +10 -8
- package/bin/cdsc.js +58 -35
- package/bin/cdsse.js +1 -0
- package/bin/cdsv2m.js +3 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +16 -0
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +10 -36
- package/lib/api/options.js +17 -8
- package/lib/api/validate.js +30 -3
- package/lib/backends.js +12 -13
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/keywords.js +3 -2
- package/lib/base/message-registry.js +64 -11
- package/lib/base/messages.js +38 -18
- package/lib/base/model.js +6 -4
- package/lib/base/optionProcessorHelper.js +148 -86
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -0
- package/lib/checks/sql-snippets.js +93 -0
- package/lib/checks/unknownMagic.js +6 -3
- package/lib/checks/validator.js +8 -0
- package/lib/compiler/assert-consistency.js +14 -5
- package/lib/compiler/base.js +64 -0
- package/lib/compiler/builtins.js +62 -16
- package/lib/compiler/checks.js +34 -10
- package/lib/compiler/definer.js +91 -112
- package/lib/compiler/index.js +30 -30
- package/lib/compiler/propagator.js +8 -4
- package/lib/compiler/resolver.js +279 -63
- package/lib/compiler/shared.js +65 -230
- package/lib/compiler/utils.js +191 -0
- package/lib/edm/annotations/genericTranslation.js +35 -18
- package/lib/edm/annotations/preprocessAnnotations.js +1 -1
- package/lib/edm/csn2edm.js +4 -3
- package/lib/edm/edm.js +8 -8
- package/lib/edm/edmPreprocessor.js +61 -59
- package/lib/edm/edmUtils.js +14 -15
- package/lib/gen/Dictionary.json +82 -40
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +19 -1
- package/lib/gen/language.tokens +80 -73
- package/lib/gen/languageLexer.interp +27 -1
- package/lib/gen/languageLexer.js +925 -826
- package/lib/gen/languageLexer.tokens +72 -65
- package/lib/gen/languageParser.js +4817 -4102
- package/lib/json/from-csn.js +57 -26
- package/lib/json/to-csn.js +244 -51
- package/lib/language/antlrParser.js +12 -1
- package/lib/language/docCommentParser.js +1 -1
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +106 -30
- package/lib/language/language.g4 +200 -70
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +220 -21
- package/lib/main.js +6 -3
- package/lib/model/api.js +2 -2
- package/lib/model/csnRefs.js +218 -86
- package/lib/model/csnUtils.js +99 -178
- package/lib/model/enrichCsn.js +84 -43
- package/lib/model/revealInternalProperties.js +25 -8
- package/lib/model/sortViews.js +8 -1
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +33 -18
- package/lib/render/.eslintrc.json +1 -2
- package/lib/render/DuplicateChecker.js +2 -2
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +202 -82
- package/lib/render/toHdbcds.js +194 -135
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +91 -51
- 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/applyTransformations.js +189 -0
- package/lib/transform/db/associations.js +389 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +275 -119
- package/lib/transform/db/draft.js +6 -4
- package/lib/transform/db/expansion.js +10 -9
- package/lib/transform/db/flattening.js +23 -8
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +106 -25
- package/lib/transform/db/views.js +485 -0
- package/lib/transform/forHanaNew.js +90 -1036
- package/lib/transform/forOdataNew.js +11 -3
- package/lib/transform/localized.js +5 -14
- package/lib/transform/odata/generateForeignKeyElements.js +2 -2
- package/lib/transform/transformUtilsNew.js +34 -20
- package/lib/transform/translateAssocsToJoins.js +15 -23
- package/lib/transform/universalCsnEnricher.js +217 -47
- package/lib/utils/file.js +13 -6
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +55 -27
- package/package.json +1 -1
- package/lib/transform/db/helpers.js +0 -58
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 {
|
|
@@ -10,7 +10,7 @@ const {
|
|
|
10
10
|
const keywords = require('../base/keywords');
|
|
11
11
|
const { renderFunc, beautifyExprArray, findElement } = require('./utils/common');
|
|
12
12
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
13
|
-
const timetrace = require('../utils/timetrace');
|
|
13
|
+
const { timetrace } = require('../utils/timetrace');
|
|
14
14
|
const { csnRefs } = require('../model/csnRefs');
|
|
15
15
|
const { forEachDefinition } = require('../model/csnUtils');
|
|
16
16
|
const enrichUniversalCsn = require('../transform/universalCsnEnricher');
|
|
@@ -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'
|
|
@@ -579,7 +579,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
579
579
|
* @return {string}
|
|
580
580
|
*/
|
|
581
581
|
function renderQueryActionsAndFunctions(artifactName, art, env) {
|
|
582
|
-
let result =
|
|
582
|
+
let result = renderActionsAndFunctions(art, env);
|
|
583
583
|
// Even if we have seen actions/functions, they might all have been ignored
|
|
584
584
|
if (result !== '')
|
|
585
585
|
result = `${env.indent}extend entity ${artifactName} with${result};`;
|
|
@@ -612,7 +612,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
612
612
|
}
|
|
613
613
|
// Now iterate elements - render an annotation if it is different from the column's
|
|
614
614
|
const childEnv = increaseIndent(env);
|
|
615
|
-
let result =
|
|
615
|
+
let result = '';
|
|
616
616
|
for (const elemName in art.elements) {
|
|
617
617
|
let elemAnnotations = '';
|
|
618
618
|
const elem = art.elements[elemName];
|
|
@@ -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';
|
|
@@ -1404,6 +1404,12 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1404
1404
|
// FIXME: no extra magic with x.param or x.global
|
|
1405
1405
|
return `${(x.param || x.global) ? ':' : ''}${x.ref.map(renderPathStep).join('.')}`;
|
|
1406
1406
|
}
|
|
1407
|
+
else if (x.xpr && x.func) {
|
|
1408
|
+
// window function
|
|
1409
|
+
const funcDef = renderFunc( x.func, x, null, a => renderArgs(a, '=>', env) );
|
|
1410
|
+
const windowFunctionOperator = x.xpr.shift(); // OVER ...
|
|
1411
|
+
return `${funcDef} ${windowFunctionOperator} ( ${renderExpr(x.xpr, env, true)} )`;
|
|
1412
|
+
}
|
|
1407
1413
|
// Function call, possibly with args (use '=>' for named args)
|
|
1408
1414
|
else if (x.func) {
|
|
1409
1415
|
// test for non-regular HANA identifier that needs to be quoted
|
|
@@ -1636,66 +1642,38 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1636
1642
|
}
|
|
1637
1643
|
|
|
1638
1644
|
/**
|
|
1639
|
-
* Render a single annotation assignment '
|
|
1640
|
-
* We might see variants like 'A.B.C#foo' or even 'A.B#foo.C'.
|
|
1641
|
-
*
|
|
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.
|
|
1642
1648
|
*
|
|
1643
|
-
* @param {any}
|
|
1649
|
+
* @param {any} anno Annotation value
|
|
1644
1650
|
* @param {string} name Annotation name, e.g. `@A.B.C#foo.C`
|
|
1645
1651
|
* @param {CdlRenderEnvironment} env
|
|
1646
|
-
* @return {string}
|
|
1652
|
+
* @return {string} Rendered annotation, possibly quoted: `@![A.B.C#foo.C]: value`
|
|
1647
1653
|
*/
|
|
1648
|
-
function renderAnnotationAssignment(
|
|
1649
|
-
|
|
1650
|
-
//
|
|
1651
|
-
const parts = name.
|
|
1652
|
-
const nameBeforeVariant = parts[
|
|
1653
|
-
const variant = parts[
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
result
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
if (variant) {
|
|
1670
|
-
// FIXME: Unfortunately, the compiler does not yet understand an inner variant with proper quoting,
|
|
1671
|
-
// i.e. like "__A"."B"#"foo"."C". As a workaround, we present the '#'-variant as a quoted part of the
|
|
1672
|
-
// previous path step, i.e as "__A"."B#foo"."C" (which yields the same result). This hack is only necessary
|
|
1673
|
-
// for inner '#'-variants, i.e. for those followed by a <nameAfterVariant>.
|
|
1674
|
-
// FIXME: Won't work for inner variants on the top-level artifact, because the USING no longer matches
|
|
1675
|
-
// for something like "__A#foo"."B"."C"
|
|
1676
|
-
if (nameAfterVariant) {
|
|
1677
|
-
const resultSteps = result.split('.');
|
|
1678
|
-
// Take all paths steps from the result (which is now essentially 'nameBeforeVariant' with USING
|
|
1679
|
-
// adaptations) except the last step
|
|
1680
|
-
result = resultSteps.slice(0, -1).join('.');
|
|
1681
|
-
// Append a combination of last path step and '#'-variant (quoted)
|
|
1682
|
-
let lastStep = resultSteps[resultSteps.length - 1];
|
|
1683
|
-
if (lastStep.includes('"')) {
|
|
1684
|
-
// Last step was already quoted - strip off the existing quotes
|
|
1685
|
-
lastStep = lastStep.slice(1, -1);
|
|
1686
|
-
}
|
|
1687
|
-
result += `.${quoteIdIfRequired(`${lastStep}#${variant}`)}`;
|
|
1688
|
-
}
|
|
1689
|
-
else {
|
|
1690
|
-
// No hack required for trailing '#'-variant
|
|
1691
|
-
result += `#${quoteIdIfRequired(variant)}`;
|
|
1692
|
-
}
|
|
1693
|
-
}
|
|
1694
|
-
// Append anything that might have come after the variant
|
|
1695
|
-
if (nameAfterVariant)
|
|
1696
|
-
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}`;
|
|
1697
1675
|
|
|
1698
|
-
result += ` : ${renderAnnotationValue(
|
|
1676
|
+
result += ` : ${renderAnnotationValue(anno, env)}`;
|
|
1699
1677
|
return `${result}\n`;
|
|
1700
1678
|
}
|
|
1701
1679
|
|
|
@@ -1742,6 +1720,8 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1742
1720
|
topLevelAliases: Object.create(null),
|
|
1743
1721
|
// Current name prefix (including trailing dot if not empty)
|
|
1744
1722
|
namePrefix: '',
|
|
1723
|
+
artifactName: null,
|
|
1724
|
+
elementName: null,
|
|
1745
1725
|
};
|
|
1746
1726
|
}
|
|
1747
1727
|
|
|
@@ -1786,11 +1766,21 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1786
1766
|
function quoteIdIfRequired(id) {
|
|
1787
1767
|
// Quote if required for CDL
|
|
1788
1768
|
if (requiresQuotingForCdl(id))
|
|
1789
|
-
return
|
|
1769
|
+
return quote(id);
|
|
1790
1770
|
|
|
1791
1771
|
return id;
|
|
1792
1772
|
}
|
|
1793
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
|
+
|
|
1794
1784
|
/**
|
|
1795
1785
|
* Returns true if 'id' requires quotes for CDL, i.e. if 'id'
|
|
1796
1786
|
* 1. starts with a digit
|
|
@@ -1891,6 +1881,136 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1891
1881
|
}
|
|
1892
1882
|
}
|
|
1893
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
|
+
|
|
1894
2014
|
/**
|
|
1895
2015
|
* @typedef CdlRenderEnvironment Rendering environment used throughout the render process.
|
|
1896
2016
|
*
|