@sap/cds-compiler 3.6.2 → 3.8.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 +109 -1
- package/README.md +3 -0
- package/bin/cdsc.js +12 -5
- package/doc/CHANGELOG_ARCHIVE.md +6 -6
- package/doc/CHANGELOG_BETA.md +35 -2
- package/doc/CHANGELOG_DEPRECATED.md +2 -2
- package/doc/DeprecatedOptions_v2.md +1 -1
- package/doc/NameResolution.md +1 -1
- package/lib/api/main.js +63 -23
- package/lib/api/options.js +1 -0
- package/lib/api/validate.js +5 -0
- package/lib/base/dictionaries.js +15 -3
- package/lib/base/keywords.js +2 -0
- package/lib/base/message-registry.js +120 -34
- package/lib/base/messages.js +51 -27
- package/lib/base/model.js +4 -2
- package/lib/base/shuffle.js +2 -1
- package/lib/checks/arrayOfs.js +1 -1
- package/lib/checks/defaultValues.js +1 -1
- package/lib/checks/elements.js +29 -1
- package/lib/checks/{emptyOrOnlyVirtual.js → hasPersistedElements.js} +10 -6
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/nonexpandableStructured.js +1 -1
- package/lib/checks/onConditions.js +15 -9
- package/lib/checks/sql-snippets.js +2 -2
- package/lib/checks/types.js +5 -1
- package/lib/checks/validator.js +7 -3
- package/lib/compiler/assert-consistency.js +42 -26
- package/lib/compiler/base.js +50 -4
- package/lib/compiler/builtins.js +17 -8
- package/lib/compiler/checks.js +241 -246
- package/lib/compiler/define.js +113 -146
- package/lib/compiler/extend.js +889 -383
- package/lib/compiler/finalize-parse-cdl.js +5 -58
- package/lib/compiler/index.js +1 -1
- package/lib/compiler/kick-start.js +7 -8
- package/lib/compiler/populate.js +297 -293
- package/lib/compiler/propagator.js +27 -18
- package/lib/compiler/resolve.js +146 -463
- package/lib/compiler/shared.js +36 -79
- package/lib/compiler/tweak-assocs.js +30 -28
- package/lib/compiler/utils.js +31 -5
- package/lib/edm/annotations/genericTranslation.js +131 -59
- package/lib/edm/annotations/preprocessAnnotations.js +3 -0
- package/lib/edm/csn2edm.js +22 -5
- package/lib/edm/edm.js +6 -4
- package/lib/edm/edmAnnoPreprocessor.js +1 -0
- package/lib/edm/edmPreprocessor.js +42 -26
- package/lib/gen/Dictionary.json +38 -2
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/languageLexer.js +1 -1
- package/lib/gen/languageParser.js +4828 -4472
- package/lib/inspect/inspectPropagation.js +20 -34
- package/lib/json/from-csn.js +140 -44
- package/lib/json/to-csn.js +114 -122
- package/lib/language/errorStrategy.js +2 -0
- package/lib/language/genericAntlrParser.js +156 -36
- package/lib/language/language.g4 +100 -58
- package/lib/language/textUtils.js +13 -0
- package/lib/main.d.ts +43 -3
- package/lib/main.js +4 -2
- package/lib/model/csnRefs.js +15 -3
- package/lib/model/csnUtils.js +12 -74
- package/lib/model/revealInternalProperties.js +4 -2
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +3 -0
- package/lib/render/manageConstraints.js +5 -2
- package/lib/render/toCdl.js +216 -104
- package/lib/render/toHdbcds.js +2 -9
- package/lib/render/toRename.js +14 -51
- package/lib/render/toSql.js +4 -3
- package/lib/render/utils/common.js +9 -5
- package/lib/transform/braceExpression.js +6 -0
- package/lib/transform/db/assertUnique.js +2 -1
- package/lib/transform/db/expansion.js +2 -0
- package/lib/transform/db/flattening.js +37 -36
- package/lib/transform/db/rewriteCalculatedElements.js +600 -0
- package/lib/transform/db/transformExists.js +4 -0
- package/lib/transform/db/views.js +40 -37
- package/lib/transform/forOdataNew.js +20 -15
- package/lib/transform/forRelationalDB.js +58 -41
- package/lib/transform/odata/typesExposure.js +50 -15
- package/lib/transform/parseExpr.js +16 -8
- package/lib/transform/transformUtilsNew.js +42 -14
- package/lib/transform/translateAssocsToJoins.js +60 -37
- package/lib/transform/universalCsn/coreComputed.js +15 -7
- package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
- package/package.json +2 -1
package/lib/render/toCdl.js
CHANGED
|
@@ -9,6 +9,7 @@ const { forEachDefinition, normalizeTypeRef } = require('../model/csnUtils');
|
|
|
9
9
|
const enrichUniversalCsn = require('../transform/universalCsn/universalCsnEnricher');
|
|
10
10
|
const { isBetaEnabled } = require('../base/model');
|
|
11
11
|
const { ModelError } = require('../base/error');
|
|
12
|
+
const { makeMessageFunction } = require('../base/messages.js');
|
|
12
13
|
const { typeParameters, specialFunctions } = require('../compiler/builtins');
|
|
13
14
|
const { forEach } = require('../utils/objectUtils');
|
|
14
15
|
const {
|
|
@@ -34,6 +35,8 @@ function csnToCdl( csn, options ) {
|
|
|
34
35
|
const special$self = !csn?.definitions?.$self && '$self';
|
|
35
36
|
timetrace.start('CDL rendering');
|
|
36
37
|
|
|
38
|
+
const msg = makeMessageFunction(csn, options, 'to.cdl');
|
|
39
|
+
|
|
37
40
|
if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn')) {
|
|
38
41
|
// Since the expander modifies the CSN, we need to clone it first or
|
|
39
42
|
// toCdl can't guarantee that the input CSN is not modified.
|
|
@@ -214,7 +217,6 @@ function csnToCdl( csn, options ) {
|
|
|
214
217
|
|
|
215
218
|
// Not part of if/else cascade, because it may be in postfix notation.
|
|
216
219
|
if (ext.actions) {
|
|
217
|
-
// TODO: Merge with renderActionsAndFunctions() -> requires removal of static `with actions`
|
|
218
220
|
const childEnv = increaseIndent(env);
|
|
219
221
|
let actions = '';
|
|
220
222
|
forEach(ext.actions, (actionName, action) => {
|
|
@@ -260,8 +262,6 @@ function csnToCdl( csn, options ) {
|
|
|
260
262
|
if (ext.enum)
|
|
261
263
|
return 'enum ';
|
|
262
264
|
if (ext.elements) { // enum/elements ambiguity -> look into elements
|
|
263
|
-
// TODO: Check for `type` as well once this is supported by the parser (and identified as elements):
|
|
264
|
-
// `extend E with { a = 'string'; b: String }`
|
|
265
265
|
const isLikelyElement = Object.keys(ext.elements)
|
|
266
266
|
.find(name => ext.elements[name].value !== undefined);
|
|
267
267
|
if (isLikelyElement)
|
|
@@ -326,7 +326,7 @@ function csnToCdl( csn, options ) {
|
|
|
326
326
|
const childEnv = increaseIndent(env);
|
|
327
327
|
for (const name in ext.actions) {
|
|
328
328
|
const action = ext.actions[name];
|
|
329
|
-
result += renderAnnotationAssignmentsAndDocComment(action, childEnv) + childEnv.indent +
|
|
329
|
+
result += renderAnnotationAssignmentsAndDocComment(action, childEnv) + childEnv.indent + quoteNonIdentifierOrKeyword(name);
|
|
330
330
|
// Action parameter annotations
|
|
331
331
|
if (action.params)
|
|
332
332
|
result += renderAnnotateParamsInParentheses(action.params, childEnv);
|
|
@@ -361,7 +361,7 @@ function csnToCdl( csn, options ) {
|
|
|
361
361
|
for (const name in elements) {
|
|
362
362
|
const elem = elements[name];
|
|
363
363
|
result += renderAnnotationAssignmentsAndDocComment(elem, childEnv);
|
|
364
|
-
result += childEnv.indent +
|
|
364
|
+
result += childEnv.indent + quoteNonIdentifierOrKeyword(name);
|
|
365
365
|
if (elem.elements)
|
|
366
366
|
result += renderAnnotateStatementElements(elem.elements, childEnv);
|
|
367
367
|
if (elem.enum)
|
|
@@ -385,7 +385,7 @@ function csnToCdl( csn, options ) {
|
|
|
385
385
|
let result = '(\n';
|
|
386
386
|
const paramAnnotations = [];
|
|
387
387
|
forEach(params, (paramName, param) => {
|
|
388
|
-
paramAnnotations.push( renderAnnotationAssignmentsAndDocComment(param, childEnv) + childEnv.indent +
|
|
388
|
+
paramAnnotations.push( renderAnnotationAssignmentsAndDocComment(param, childEnv) + childEnv.indent + quoteNonIdentifierOrKeyword(paramName) );
|
|
389
389
|
});
|
|
390
390
|
result += `${paramAnnotations.join(',\n')}\n${env.indent})`;
|
|
391
391
|
return result;
|
|
@@ -541,37 +541,32 @@ function csnToCdl( csn, options ) {
|
|
|
541
541
|
* Returns the resulting source string.
|
|
542
542
|
*
|
|
543
543
|
* @param {string} elementName
|
|
544
|
-
* @param {CSN.Element|CSN.Enum}
|
|
544
|
+
* @param {CSN.Element|CSN.Enum} element
|
|
545
545
|
* @param {CdlRenderEnvironment} env
|
|
546
546
|
*/
|
|
547
|
-
function renderElement( elementName,
|
|
547
|
+
function renderElement( elementName, element, env ) {
|
|
548
548
|
env = envAddPath(env, [ 'elements', elementName ]);
|
|
549
|
-
let result = renderAnnotationAssignmentsAndDocComment(
|
|
549
|
+
let result = renderAnnotationAssignmentsAndDocComment(element, env);
|
|
550
550
|
result += env.indent;
|
|
551
|
-
result +=
|
|
552
|
-
result +=
|
|
551
|
+
result += element.virtual ? 'virtual ' : '';
|
|
552
|
+
result += element.key ? 'key ' : '';
|
|
553
553
|
// TODO(v4): Remove once deprecated flag for `masked` is removed.
|
|
554
|
-
result +=
|
|
555
|
-
result +=
|
|
556
|
-
if (
|
|
557
|
-
result += ` = ${exprRenderer.renderExpr(
|
|
554
|
+
result += element.masked ? 'masked ' : '';
|
|
555
|
+
result += quoteNonIdentifierOrKeyword(elementName);
|
|
556
|
+
if (element.val !== undefined) { // enum value
|
|
557
|
+
result += ` = ${exprRenderer.renderExpr(element, env)}`;
|
|
558
558
|
}
|
|
559
|
-
else if (
|
|
560
|
-
result += ` = #${
|
|
559
|
+
else if (element['#'] !== undefined) { // enum symbol reference
|
|
560
|
+
result += ` = #${element['#']}`;
|
|
561
561
|
}
|
|
562
562
|
else {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
if (!isAssoc || elm.value === undefined) {
|
|
567
|
-
const props = renderTypeReferenceAndProps(elm, env);
|
|
568
|
-
if (props !== '')
|
|
569
|
-
result += ` : ${props}`;
|
|
570
|
-
}
|
|
563
|
+
const props = renderTypeReferenceAndProps(element, env);
|
|
564
|
+
if (props !== '')
|
|
565
|
+
result += ` : ${props}`;
|
|
571
566
|
}
|
|
572
567
|
|
|
573
|
-
if (
|
|
574
|
-
result += ` = ${exprRenderer.renderExpr(
|
|
568
|
+
if (element.value !== undefined) { // calculated element // @ts-ignore
|
|
569
|
+
result += ` = ${exprRenderer.renderExpr(element.value, env)}`;
|
|
575
570
|
}
|
|
576
571
|
|
|
577
572
|
return `${result};\n`;
|
|
@@ -697,7 +692,8 @@ function csnToCdl( csn, options ) {
|
|
|
697
692
|
function renderViewSource( source, env ) {
|
|
698
693
|
// Sub-SELECT
|
|
699
694
|
if (source.SELECT || source.SET) {
|
|
700
|
-
|
|
695
|
+
const subEnv = increaseIndent(env);
|
|
696
|
+
let result = `(\n${subEnv.indent}${renderQuery(source, false, 'view', subEnv)}\n${env.indent})`;
|
|
701
697
|
if (source.as)
|
|
702
698
|
result += renderAlias(source.as);
|
|
703
699
|
|
|
@@ -762,9 +758,13 @@ function csnToCdl( csn, options ) {
|
|
|
762
758
|
result += `(${renderArguments(path.ref[0], ':', env)})`;
|
|
763
759
|
|
|
764
760
|
if (path.ref[0].where) {
|
|
761
|
+
// TODO: Unify with other filter rendering
|
|
765
762
|
const cardinality = path.ref[0].cardinality ? (`${path.ref[0].cardinality.max}: `) : '';
|
|
766
763
|
const expr = exprRenderer.renderExpr(path.ref[0].where, env);
|
|
767
|
-
|
|
764
|
+
if (expr.endsWith(']')) // for cases such as [… ![id] ]
|
|
765
|
+
result += `[ ${cardinality}${expr} ]`;
|
|
766
|
+
else
|
|
767
|
+
result += `[${cardinality}${expr}]`;
|
|
768
768
|
}
|
|
769
769
|
|
|
770
770
|
// Add any path steps (possibly with parameters and filters) that may follow after that
|
|
@@ -827,7 +827,7 @@ function csnToCdl( csn, options ) {
|
|
|
827
827
|
result += renderDocComment(element, env);
|
|
828
828
|
}
|
|
829
829
|
// Note: parentheses are a workaround for #9015
|
|
830
|
-
result += renderAnnotationAssignmentsAndDocComment(col, env, {
|
|
830
|
+
result += renderAnnotationAssignmentsAndDocComment(col, env, { parentheses: true });
|
|
831
831
|
result += env.indent;
|
|
832
832
|
|
|
833
833
|
// only if column is virtual, keyword virtual was present in the source text
|
|
@@ -1004,7 +1004,8 @@ function csnToCdl( csn, options ) {
|
|
|
1004
1004
|
}
|
|
1005
1005
|
|
|
1006
1006
|
if (select.excluding) {
|
|
1007
|
-
|
|
1007
|
+
const excludes = select.excluding.map(id => `${childEnv.indent}${quoteNonIdentifierOrKeyword(id)}`).join(',\n');
|
|
1008
|
+
result += ` excluding {\n${excludes}\n`;
|
|
1008
1009
|
result += `${env.indent}}`;
|
|
1009
1010
|
}
|
|
1010
1011
|
|
|
@@ -1178,7 +1179,7 @@ function csnToCdl( csn, options ) {
|
|
|
1178
1179
|
function renderParameter( parName, par, env ) {
|
|
1179
1180
|
env = envAddPath(env, [ 'params', parName ]);
|
|
1180
1181
|
let result = `${renderAnnotationAssignmentsAndDocComment(par, env)}${env.indent}`;
|
|
1181
|
-
result += `${
|
|
1182
|
+
result += `${quoteNonIdentifierOrKeyword(parName)} : ${renderTypeReferenceAndProps(par, env)}`;
|
|
1182
1183
|
return result;
|
|
1183
1184
|
}
|
|
1184
1185
|
|
|
@@ -1211,7 +1212,7 @@ function csnToCdl( csn, options ) {
|
|
|
1211
1212
|
* such as `not null` and `default <xpr>`.
|
|
1212
1213
|
* Allow suppressing rendering of structs such as enums - used in columns for example.
|
|
1213
1214
|
*
|
|
1214
|
-
* @param {
|
|
1215
|
+
* @param {CSN.Artifact} artifact
|
|
1215
1216
|
* @param {CdlRenderEnvironment} env
|
|
1216
1217
|
* @param {object} [config={}] - `typeRefOnly` Whether to only render type defs, no arrayed/structured/enum.
|
|
1217
1218
|
* - `noAnnoCollect` Do not collect annotations of sub-elements.
|
|
@@ -1220,7 +1221,6 @@ function csnToCdl( csn, options ) {
|
|
|
1220
1221
|
function renderTypeReferenceAndProps( artifact, env, config = {} ) {
|
|
1221
1222
|
let result = '';
|
|
1222
1223
|
const { typeRefOnly, noAnnoCollect } = config;
|
|
1223
|
-
let isTypeDef = env.path?.length === 2; // e.g [ 'definitions', typeDef ];
|
|
1224
1224
|
|
|
1225
1225
|
if (typeRefOnly && !artifact.type)
|
|
1226
1226
|
throw new ModelError(`Expected artifact to have a type; in: ${env.artifactName}`);
|
|
@@ -1229,20 +1229,16 @@ function csnToCdl( csn, options ) {
|
|
|
1229
1229
|
result += 'localized ';
|
|
1230
1230
|
|
|
1231
1231
|
if (!artifact.type && artifact.items) {
|
|
1232
|
+
checkArrayedArtifact(artifact, env);
|
|
1232
1233
|
result += 'many '; // alternative: 'array of'; but not used
|
|
1233
|
-
artifact = artifact
|
|
1234
|
-
env = envAddPath(env, 'items');
|
|
1235
|
-
// element keywords allowed in MANY case; was an oversight when arrays were introduced.
|
|
1236
|
-
isTypeDef = false;
|
|
1237
|
-
// "many many" does not work in CDL, so we don't check for it.
|
|
1234
|
+
({ art: artifact, env } = checkInnerMostArray(artifact, env));
|
|
1238
1235
|
}
|
|
1239
1236
|
|
|
1240
1237
|
const type = normalizeTypeRef(artifact.type);
|
|
1241
1238
|
|
|
1242
1239
|
if (!type && artifact.elements) {
|
|
1243
1240
|
result += renderElements(artifact, env);
|
|
1244
|
-
|
|
1245
|
-
result += renderNullability(artifact);
|
|
1241
|
+
result += renderNullability(artifact);
|
|
1246
1242
|
// structured default not possible at the moment
|
|
1247
1243
|
return result;
|
|
1248
1244
|
}
|
|
@@ -1278,7 +1274,7 @@ function csnToCdl( csn, options ) {
|
|
|
1278
1274
|
if (artifact.keys && !artifact.on)
|
|
1279
1275
|
result += ` { ${Object.keys(artifact.keys).map(name => renderForeignKey(artifact.keys[name], env)).join(', ')} }`;
|
|
1280
1276
|
|
|
1281
|
-
if (
|
|
1277
|
+
if (artifact.notNull !== undefined && !artifact.on) // unmanaged associations can't be followed by "not null"
|
|
1282
1278
|
result += renderNullability(artifact);
|
|
1283
1279
|
// DEFAULT not possible here.
|
|
1284
1280
|
|
|
@@ -1291,8 +1287,6 @@ function csnToCdl( csn, options ) {
|
|
|
1291
1287
|
// get lost if we only render the type name.
|
|
1292
1288
|
// We only extract annotations of enums, if "typeRefOnly" is true. Otherwise, since
|
|
1293
1289
|
// the full enum is rendered below, we would have unnecessary annotations.
|
|
1294
|
-
// TODO: Can we annotate elements of targetAspect?
|
|
1295
|
-
// If so, move this block before the composition rendering.
|
|
1296
1290
|
if (!noAnnoCollect && (!artifact.enum || typeRefOnly)) {
|
|
1297
1291
|
const annotate = collectAnnotationsOfElementsAndEnum(artifact, env);
|
|
1298
1292
|
if (annotate)
|
|
@@ -1310,9 +1304,9 @@ function csnToCdl( csn, options ) {
|
|
|
1310
1304
|
|
|
1311
1305
|
if (artifact.enum && !typeRefOnly)
|
|
1312
1306
|
result += renderEnum(artifact.enum, env);
|
|
1313
|
-
if (
|
|
1307
|
+
if (artifact.notNull !== undefined)
|
|
1314
1308
|
result += renderNullability(artifact);
|
|
1315
|
-
if (artifact.default)
|
|
1309
|
+
if (artifact.default !== undefined)
|
|
1316
1310
|
result += ` default ${exprRenderer.renderExpr(artifact.default, env)}`;
|
|
1317
1311
|
|
|
1318
1312
|
return result;
|
|
@@ -1380,53 +1374,94 @@ function csnToCdl( csn, options ) {
|
|
|
1380
1374
|
}
|
|
1381
1375
|
|
|
1382
1376
|
/**
|
|
1383
|
-
* Render an annotation value
|
|
1384
|
-
*
|
|
1377
|
+
* Render an annotation value, which is either
|
|
1378
|
+
* - a normal expressions
|
|
1379
|
+
* - a somewhat simplified expression, with slightly different representation
|
|
1385
1380
|
*
|
|
1386
|
-
* @param {any}
|
|
1381
|
+
* @param {any} annoValue
|
|
1387
1382
|
* @param {CdlRenderEnvironment} env
|
|
1388
1383
|
*/
|
|
1389
|
-
function renderAnnotationValue(
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1384
|
+
function renderAnnotationValue( annoValue, env ) {
|
|
1385
|
+
// TODO: There must be at least one known expression property, otherwise
|
|
1386
|
+
// it could be `type: 'unchecked'`.
|
|
1387
|
+
const isXpr = annoValue?.['='] !== undefined && (Object.keys(annoValue).length > 1) &&
|
|
1388
|
+
isBetaEnabled(options, 'annotationExpressions');
|
|
1389
|
+
if (isXpr) {
|
|
1390
|
+
// Once inside an expression, we stay there.
|
|
1391
|
+
const xpr = exprRenderer.renderExpr(annoValue, env);
|
|
1392
|
+
return `( ${xpr} )`;
|
|
1394
1393
|
}
|
|
1395
|
-
else if (
|
|
1394
|
+
else if (Array.isArray(annoValue)) {
|
|
1395
|
+
return renderAnnotationArrayValue( annoValue, env );
|
|
1396
|
+
}
|
|
1397
|
+
else if (typeof annoValue === 'object' && annoValue !== null) {
|
|
1396
1398
|
// Enum symbol
|
|
1397
|
-
if (
|
|
1398
|
-
return `#${
|
|
1399
|
+
if (annoValue['#']) {
|
|
1400
|
+
return `#${annoValue['#']}`;
|
|
1399
1401
|
}
|
|
1400
1402
|
// Shorthand for absolute path (as string)
|
|
1401
|
-
else if (
|
|
1402
|
-
|
|
1403
|
+
else if (annoValue['=']) {
|
|
1404
|
+
if (annoValue['='].startsWith('@'))
|
|
1405
|
+
return quoteAnnotationPathIfRequired(annoValue['=']);
|
|
1406
|
+
return quotePathIfRequired(annoValue['=']);
|
|
1403
1407
|
}
|
|
1404
1408
|
// Shorthand for ellipsis: `... up to <val>`
|
|
1405
|
-
else if (
|
|
1406
|
-
if (
|
|
1409
|
+
else if (annoValue['...']) {
|
|
1410
|
+
if (annoValue['...'] === true)
|
|
1407
1411
|
return '...';
|
|
1408
|
-
return `... up to ${renderAnnotationValue(
|
|
1412
|
+
return `... up to ${renderAnnotationValue(annoValue['...'], env)}`;
|
|
1409
1413
|
}
|
|
1410
1414
|
|
|
1411
1415
|
// Struct value (can currently only occur within an array)
|
|
1412
1416
|
// Render as one-liner if there is at most one key. Render as multi-line
|
|
1413
1417
|
// struct if there are more and use nicer indentation.
|
|
1414
|
-
const keys = Object.keys(
|
|
1418
|
+
const keys = Object.keys(annoValue);
|
|
1415
1419
|
const childEnv = keys.length <= 1 ? env : increaseIndent(env);
|
|
1416
|
-
const values = keys.map(key => `${quoteAnnotationPathIfRequired(key)}: ${renderAnnotationValue(
|
|
1420
|
+
const values = keys.map(key => `${quoteAnnotationPathIfRequired(key)}: ${renderAnnotationValue(annoValue[key], childEnv)}`);
|
|
1417
1421
|
if (values.length <= 1)
|
|
1418
1422
|
return `{ ${values.join(', ')} }`;
|
|
1419
1423
|
const valueList = values.join(`,\n${childEnv.indent}`);
|
|
1420
1424
|
return `{\n${childEnv.indent}${valueList}\n${env.indent}}`;
|
|
1421
1425
|
}
|
|
1422
1426
|
// Null
|
|
1423
|
-
else if (
|
|
1427
|
+
else if (annoValue === null) {
|
|
1424
1428
|
return 'null';
|
|
1425
1429
|
}
|
|
1426
1430
|
// Primitive: string, number, boolean
|
|
1427
1431
|
|
|
1428
1432
|
// Quote strings, leave all others as they are
|
|
1429
|
-
return (typeof
|
|
1433
|
+
return (typeof annoValue === 'string') ? renderString(annoValue, env) : String(annoValue);
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
/**
|
|
1437
|
+
* Renders an array annotation value. Uses a heuristic to put each element on its own line
|
|
1438
|
+
* if a single-line becomes longer than 100 characters or if any sub-expression already
|
|
1439
|
+
* contains a line break. The latter checks makes nested arrays with structures more
|
|
1440
|
+
* readable.
|
|
1441
|
+
*
|
|
1442
|
+
* @param {any[]} annoValue
|
|
1443
|
+
* @param {CdlRenderEnvironment} env
|
|
1444
|
+
* @return {string}
|
|
1445
|
+
*/
|
|
1446
|
+
function renderAnnotationArrayValue( annoValue, env ) {
|
|
1447
|
+
const childEnv = increaseIndent(env);
|
|
1448
|
+
// Render array parts as values.
|
|
1449
|
+
let length = 0;
|
|
1450
|
+
let hasLineBreak = false;
|
|
1451
|
+
const items = annoValue.map((item) => {
|
|
1452
|
+
const result = renderAnnotationValue(item, childEnv);
|
|
1453
|
+
length += result.length + 2; // just a heuristic; add 2 for `, `.
|
|
1454
|
+
if (!hasLineBreak && result.includes('\n'))
|
|
1455
|
+
hasLineBreak = true;
|
|
1456
|
+
return result;
|
|
1457
|
+
});
|
|
1458
|
+
|
|
1459
|
+
if (!hasLineBreak && (length + env.indent.length) < 100) {
|
|
1460
|
+
// Spaces required if last array value is a delimited identifier.
|
|
1461
|
+
return `[ ${items.join(', ')} ]`;
|
|
1462
|
+
}
|
|
1463
|
+
const renderedItems = items.join(`,\n${childEnv.indent}`);
|
|
1464
|
+
return `[\n${childEnv.indent}${renderedItems}\n${env.indent}]`;
|
|
1430
1465
|
}
|
|
1431
1466
|
|
|
1432
1467
|
/**
|
|
@@ -1444,7 +1479,7 @@ function csnToCdl( csn, options ) {
|
|
|
1444
1479
|
// FIXME: We should rather explicitly recognize quoting somehow
|
|
1445
1480
|
if (idx === 0 && s.startsWith('$'))
|
|
1446
1481
|
return s;
|
|
1447
|
-
return
|
|
1482
|
+
return quoteNonIdentifierOrKeyword(s, env.additionalKeywords);
|
|
1448
1483
|
}
|
|
1449
1484
|
// ID with filters or parameters
|
|
1450
1485
|
else if (typeof s === 'object') {
|
|
@@ -1457,7 +1492,7 @@ function csnToCdl( csn, options ) {
|
|
|
1457
1492
|
return `${s.func}(${renderArguments(s, '=>', env)})`;
|
|
1458
1493
|
|
|
1459
1494
|
// Path step, possibly with view parameters and/or filters
|
|
1460
|
-
let result = `${
|
|
1495
|
+
let result = `${quoteNonIdentifierOrKeyword(s.id, env.additionalKeywords)}`;
|
|
1461
1496
|
if (s.args) {
|
|
1462
1497
|
// View parameters
|
|
1463
1498
|
result += `(${renderArguments(s, ':', env)})`;
|
|
@@ -1466,7 +1501,10 @@ function csnToCdl( csn, options ) {
|
|
|
1466
1501
|
// Filter, possibly with cardinality
|
|
1467
1502
|
const cardinality = s.cardinality ? (`${s.cardinality.max}: `) : '';
|
|
1468
1503
|
const expr = exprRenderer.renderExpr(s.where, env);
|
|
1469
|
-
|
|
1504
|
+
if (expr.endsWith(']')) // for cases such as [… ![id] ]
|
|
1505
|
+
result += `[ ${cardinality}${expr} ]`;
|
|
1506
|
+
else
|
|
1507
|
+
result += `[${cardinality}${expr}]`;
|
|
1470
1508
|
}
|
|
1471
1509
|
|
|
1472
1510
|
return result;
|
|
@@ -1505,7 +1543,7 @@ function csnToCdl( csn, options ) {
|
|
|
1505
1543
|
*/
|
|
1506
1544
|
function renderNamedArguments( node, separator, env ) {
|
|
1507
1545
|
return Object.keys(node.args).map(function renderNamedArgument(key) {
|
|
1508
|
-
return `${
|
|
1546
|
+
return `${quoteNonIdentifierOrKeyword(key, env.additionalKeywords)} ${separator} ${renderArgument(node.args[key], env)}`;
|
|
1509
1547
|
}).join(', ');
|
|
1510
1548
|
}
|
|
1511
1549
|
|
|
@@ -1647,7 +1685,7 @@ function csnToCdl( csn, options ) {
|
|
|
1647
1685
|
* @return {string}
|
|
1648
1686
|
*/
|
|
1649
1687
|
function renderAlias( alias ) {
|
|
1650
|
-
return ` as ${
|
|
1688
|
+
return ` as ${quoteNonIdentifierOrKeyword(alias)}`;
|
|
1651
1689
|
}
|
|
1652
1690
|
|
|
1653
1691
|
/**
|
|
@@ -1685,7 +1723,7 @@ function csnToCdl( csn, options ) {
|
|
|
1685
1723
|
*
|
|
1686
1724
|
* @param {object} obj Object that has annotations
|
|
1687
1725
|
* @param {CdlRenderEnvironment} env
|
|
1688
|
-
* @param {{
|
|
1726
|
+
* @param {{parentheses: boolean}} [config] Config for renderAnnotationAssignment()
|
|
1689
1727
|
* @return {string}
|
|
1690
1728
|
*/
|
|
1691
1729
|
function renderAnnotationAssignmentsAndDocComment( obj, env, config ) {
|
|
@@ -1705,38 +1743,29 @@ function csnToCdl( csn, options ) {
|
|
|
1705
1743
|
* @param {any} anno Annotation value
|
|
1706
1744
|
* @param {string} name Annotation name, e.g. `@A.B.C#foo.C`
|
|
1707
1745
|
* @param {CdlRenderEnvironment} env
|
|
1708
|
-
* @param {object} [config]
|
|
1746
|
+
* @param {object} [config] parentheses: Whether the annotation assignment must be surrounded by parentheses.
|
|
1709
1747
|
* @return {string} Rendered annotation, possibly quoted: `@![A.B.C#foo.C]: value`
|
|
1710
1748
|
*/
|
|
1711
|
-
function renderAnnotationAssignment( anno, name, env, config = {
|
|
1749
|
+
function renderAnnotationAssignment( anno, name, env, config = { parentheses: false } ) {
|
|
1712
1750
|
name = name.substring(1);
|
|
1713
1751
|
// Take the annotation assignment apart into <nameBeforeVariant>#<variantAndRest>
|
|
1714
1752
|
const parts = name.split('#');
|
|
1715
1753
|
const nameBeforeVariant = parts[0];
|
|
1716
1754
|
const variant = parts[1];
|
|
1717
|
-
const {
|
|
1718
|
-
|
|
1719
|
-
// Identifier according to our grammar: /[$_a-zA-Z][$_a-zA-Z0-9]*/
|
|
1720
|
-
// We expand this pattern to also include dots after the first character.
|
|
1721
|
-
// If the annotation does not follow this pattern `ident(.@ident)*`, it must be quoted:
|
|
1722
|
-
// `@identifier@identifier` must be quoted but `@identifier.@identifier` should not.
|
|
1723
|
-
// TODO: Use quoteAnnotationPathIfRequired()
|
|
1724
|
-
const annoRequiresQuoting = !/^[$_a-zA-Z][$_a-zA-Z0-9.]*(?:\.@[$_a-zA-Z][$_a-zA-Z0-9.]*)*$/.test(nameBeforeVariant);
|
|
1725
|
-
// Unfortunately, the compiler does not allow `.` after the first variant identifier,
|
|
1726
|
-
// even though that is the result after flattening.
|
|
1727
|
-
const variantRequiresQuoting = variant && !/^[$_a-zA-Z][$_a-zA-Z0-9]*$/.test(variant);
|
|
1755
|
+
const { parentheses } = config;
|
|
1728
1756
|
|
|
1729
1757
|
let result = `${env.indent}@`;
|
|
1730
|
-
if (
|
|
1758
|
+
if (parentheses)
|
|
1731
1759
|
result += '(';
|
|
1732
1760
|
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1761
|
+
result += quoteAnnotationPathIfRequired(nameBeforeVariant);
|
|
1762
|
+
if (variant !== undefined)
|
|
1763
|
+
// Unfortunately, the compiler does not allow `.@` after the first variant identifier,
|
|
1764
|
+
// so we're back at simple paths.
|
|
1765
|
+
result += `#${quotePathIfRequired(variant)}`;
|
|
1738
1766
|
result += ` : ${renderAnnotationValue(anno, env)}`;
|
|
1739
|
-
|
|
1767
|
+
|
|
1768
|
+
if (parentheses)
|
|
1740
1769
|
result += ')';
|
|
1741
1770
|
return `${result}\n`;
|
|
1742
1771
|
}
|
|
@@ -1753,7 +1782,7 @@ function csnToCdl( csn, options ) {
|
|
|
1753
1782
|
|
|
1754
1783
|
/**
|
|
1755
1784
|
* Render the name of a definition. Ensures the first segment of the name
|
|
1756
|
-
* is available in the rendered CDL. Otherwise a USING is added.
|
|
1785
|
+
* is available in the rendered CDL. Otherwise, a USING is added.
|
|
1757
1786
|
*
|
|
1758
1787
|
* @param {string} name
|
|
1759
1788
|
* @return {string}
|
|
@@ -1814,6 +1843,8 @@ function csnToCdl( csn, options ) {
|
|
|
1814
1843
|
if (keywords.cdl_functions.includes(x.func.toUpperCase()) && !x.args)
|
|
1815
1844
|
return x.func;
|
|
1816
1845
|
const name = smartFunctionId(x.func);
|
|
1846
|
+
if (!x.args) // e.g. for methods without arguments, `args` is not set at all.
|
|
1847
|
+
return `${name}`;
|
|
1817
1848
|
return `${name}(${renderArguments( x, '=>', this.env )})`;
|
|
1818
1849
|
},
|
|
1819
1850
|
xpr(x) {
|
|
@@ -1833,6 +1864,62 @@ function csnToCdl( csn, options ) {
|
|
|
1833
1864
|
},
|
|
1834
1865
|
});
|
|
1835
1866
|
}
|
|
1867
|
+
|
|
1868
|
+
// checks -------------------------------------------------------------------
|
|
1869
|
+
// The CDL backend has very few checks, but we need to tell the user if
|
|
1870
|
+
// something can't be rendered.
|
|
1871
|
+
|
|
1872
|
+
/**
|
|
1873
|
+
* to.cdl() can only render one nesting level of `items`. `items` inside `items, etc.
|
|
1874
|
+
* can't be represented in CDL, hence can't be rendered.
|
|
1875
|
+
* However, it's possible that due to CSN expansion because of type-ofs, we have
|
|
1876
|
+
* nested `.items` with a `.type` next to it. In that case, return the node with `.type`.
|
|
1877
|
+
*
|
|
1878
|
+
* Returns the most deeply nested `.items`. Upper bound are 100 nesting levels.
|
|
1879
|
+
*
|
|
1880
|
+
* @param {CSN.Artifact} art
|
|
1881
|
+
* @param {CdlRenderEnvironment} env
|
|
1882
|
+
* @return {{art: CSN.Artifact, env: CdlRenderEnvironment}} `art` and new `env` with adapted `env.path`.
|
|
1883
|
+
*/
|
|
1884
|
+
function checkInnerMostArray( art, env ) {
|
|
1885
|
+
env = envNewPath(env, env.path); // copy path, so we can modify it directly
|
|
1886
|
+
|
|
1887
|
+
let nesting = 0;
|
|
1888
|
+
while (art.items && nesting < 100) {
|
|
1889
|
+
art = art.items;
|
|
1890
|
+
env.path.push( 'items');
|
|
1891
|
+
++nesting;
|
|
1892
|
+
if (art.type)
|
|
1893
|
+
break; // after first `.items`, break at nesting level that has a type.
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
if (nesting >= 100) {
|
|
1897
|
+
msg.error('def-invalid-nesting', env.path, { count: nesting, prop: 'items' },
|
|
1898
|
+
'Property $(PROP) is nested more than $(COUNT) levels and can\'t be rendered');
|
|
1899
|
+
}
|
|
1900
|
+
else if (nesting > 1) {
|
|
1901
|
+
msg.warning('def-unexpected-nesting', env.path, { prop: 'items' },
|
|
1902
|
+
'Property $(PROP) is nested more than one level; only rendering deepest level');
|
|
1903
|
+
}
|
|
1904
|
+
return { art, env };
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
/**
|
|
1908
|
+
* If an artifact is an array via `.items`, some properties on `art` can't be rendered,
|
|
1909
|
+
* for example "not null", because there is no CDL representation for it. Only "not null"
|
|
1910
|
+
* on `.items` can be rendered.
|
|
1911
|
+
*
|
|
1912
|
+
* @param {CSN.Artifact} art
|
|
1913
|
+
* @param {CdlRenderEnvironment} env
|
|
1914
|
+
*/
|
|
1915
|
+
function checkArrayedArtifact( art, env ) {
|
|
1916
|
+
if (!art.items)
|
|
1917
|
+
return;
|
|
1918
|
+
if (art.notNull !== undefined) {
|
|
1919
|
+
msg.warning('def-unexpected-nullability', env.path, { prop: 'not null', otherprop: 'items' },
|
|
1920
|
+
'Property $(PROP) not rendered, because it can only be rendered inside $(OTHERPROP) for arrayed artifacts');
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1836
1923
|
}
|
|
1837
1924
|
|
|
1838
1925
|
/**
|
|
@@ -1870,24 +1957,32 @@ function increaseIndent( env ) {
|
|
|
1870
1957
|
}
|
|
1871
1958
|
|
|
1872
1959
|
/**
|
|
1873
|
-
* Quote
|
|
1874
|
-
* `elem` use `
|
|
1960
|
+
* Quote simple path steps with `![]` if necessary. For simple ids such as
|
|
1961
|
+
* `elem` use `quoteNonIdentifierOrKeyword` instead.
|
|
1875
1962
|
*
|
|
1876
|
-
* In contrast to
|
|
1963
|
+
* In contrast to quoteNonIdentifierOrKeyword, does not handle additional keywords,
|
|
1877
1964
|
* because it was not required, yet.
|
|
1878
1965
|
*
|
|
1966
|
+
* Due to token rewrite, all keywords after a dot (`.`) are rewritten to
|
|
1967
|
+
* identifiers, i.e. we only need to check for the identifier RegEx.
|
|
1968
|
+
*
|
|
1879
1969
|
* @param {string} path
|
|
1880
1970
|
* @returns {string}
|
|
1881
|
-
*
|
|
1882
|
-
* @todo For paths such as `E.key`, `key` does not have to be in quotes.
|
|
1883
1971
|
*/
|
|
1884
1972
|
function quotePathIfRequired( path ) {
|
|
1885
|
-
return path.split('.').map(step =>
|
|
1973
|
+
return path.split('.').map((step, index) => {
|
|
1974
|
+
if (index === 0)
|
|
1975
|
+
return quoteNonIdentifierOrKeyword(step);
|
|
1976
|
+
else if (!identifierRegex.test(step))
|
|
1977
|
+
return delimitedId(step);
|
|
1978
|
+
return step;
|
|
1979
|
+
}).join('.');
|
|
1886
1980
|
}
|
|
1887
1981
|
|
|
1888
1982
|
/**
|
|
1889
1983
|
* Quote the id with `![]` if necessary. For paths such as `E.key` use
|
|
1890
1984
|
* `quotePathIfRequired` instead.
|
|
1985
|
+
* See quoteNonIdentifier() if you want to ignore keywords.
|
|
1891
1986
|
*
|
|
1892
1987
|
* Set additionalKeywords to an array of UPPERCASE keywords
|
|
1893
1988
|
* that also need quoting, e.g. in special functions.
|
|
@@ -1896,13 +1991,30 @@ function quotePathIfRequired( path ) {
|
|
|
1896
1991
|
* @param {string[]} [additionalKeywords]
|
|
1897
1992
|
* @return {string}
|
|
1898
1993
|
*/
|
|
1899
|
-
function
|
|
1994
|
+
function quoteNonIdentifierOrKeyword( id, additionalKeywords ) {
|
|
1900
1995
|
// Quote if required for CDL
|
|
1901
1996
|
if (requiresQuotingForCdl(id, additionalKeywords || []))
|
|
1902
1997
|
return delimitedId(id);
|
|
1903
1998
|
return id;
|
|
1904
1999
|
}
|
|
1905
2000
|
|
|
2001
|
+
/**
|
|
2002
|
+
* Quote the id with `![]` if necessary. For paths such as `E.key` use
|
|
2003
|
+
* `quotePathIfRequired` instead.
|
|
2004
|
+
* See quoteNonIdentifierOrKeyword() if you want to quote identifiers
|
|
2005
|
+
* that are keywords as well.
|
|
2006
|
+
*
|
|
2007
|
+
* Does not quote the given id if it is a keyword.
|
|
2008
|
+
*
|
|
2009
|
+
* @param {string} id
|
|
2010
|
+
* @return {string}
|
|
2011
|
+
*/
|
|
2012
|
+
function quoteNonIdentifier( id ) {
|
|
2013
|
+
if (!identifierRegex.test(id))
|
|
2014
|
+
return delimitedId(id);
|
|
2015
|
+
return id;
|
|
2016
|
+
}
|
|
2017
|
+
|
|
1906
2018
|
/**
|
|
1907
2019
|
* Quote an annotation path, e.g. `@My.@Anno.Description` if necessary.
|
|
1908
2020
|
* `anno` can start with `@` but is not required to be.
|
|
@@ -1915,8 +2027,8 @@ function quoteIdIfRequired( id, additionalKeywords ) {
|
|
|
1915
2027
|
function quoteAnnotationPathIfRequired( anno ) {
|
|
1916
2028
|
return anno.split('.').map((segment) => {
|
|
1917
2029
|
if (segment.startsWith('@'))
|
|
1918
|
-
return `@${
|
|
1919
|
-
return
|
|
2030
|
+
return `@${quoteNonIdentifier(segment.slice(1))}`;
|
|
2031
|
+
return quoteNonIdentifier(segment);
|
|
1920
2032
|
}).join('.');
|
|
1921
2033
|
}
|
|
1922
2034
|
|
|
@@ -2141,8 +2253,8 @@ function availableFirstPathSteps( csn ) {
|
|
|
2141
2253
|
function smartId( id, insideFunction = null ) {
|
|
2142
2254
|
insideFunction = insideFunction?.toUpperCase();
|
|
2143
2255
|
if (!insideFunction || !specialFunctions[insideFunction])
|
|
2144
|
-
return
|
|
2145
|
-
return
|
|
2256
|
+
return quoteNonIdentifierOrKeyword(id);
|
|
2257
|
+
return quoteNonIdentifierOrKeyword(id, getAllKeywordsForSpecialFunction(insideFunction));
|
|
2146
2258
|
}
|
|
2147
2259
|
|
|
2148
2260
|
/**
|
package/lib/render/toHdbcds.js
CHANGED
|
@@ -632,7 +632,6 @@ function toHdbcdsSource( csn, options ) {
|
|
|
632
632
|
let result = `(${renderQuery(source, false, increaseIndent(env))})`;
|
|
633
633
|
if (source.as)
|
|
634
634
|
result += ` as ${formatIdentifier(source.as)}`;
|
|
635
|
-
|
|
636
635
|
return result;
|
|
637
636
|
}
|
|
638
637
|
// JOIN
|
|
@@ -1684,25 +1683,19 @@ function toHdbcdsSource( csn, options ) {
|
|
|
1684
1683
|
}
|
|
1685
1684
|
|
|
1686
1685
|
/**
|
|
1687
|
-
* Return an id 'id' with appropriate
|
|
1686
|
+
* Return an id 'id' with appropriate double-quotes
|
|
1688
1687
|
*
|
|
1689
1688
|
* @param {string} id Identifier to quote
|
|
1690
1689
|
* @returns {string} Properly quoted identifier
|
|
1691
1690
|
*/
|
|
1692
1691
|
function quoteId( id ) {
|
|
1693
|
-
// Should only ever be called for real IDs (i.e. no dots inside)
|
|
1694
|
-
if (id.indexOf('.') !== -1)
|
|
1695
|
-
throw new ModelError(`HDBCDS: Tried to quote id with dot: ${id}`);
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
1692
|
switch (options.sqlMapping) {
|
|
1699
1693
|
case 'plain':
|
|
1700
1694
|
return smartId(id, 'hdbcds');
|
|
1701
1695
|
case 'quoted':
|
|
1702
1696
|
case 'hdbcds':
|
|
1703
|
-
return delimitedId(id, 'hdbcds');
|
|
1704
1697
|
default:
|
|
1705
|
-
return
|
|
1698
|
+
return delimitedId(id, 'hdbcds');
|
|
1706
1699
|
}
|
|
1707
1700
|
}
|
|
1708
1701
|
|