@sap/cds-compiler 3.7.2 → 3.8.2
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 +71 -4
- package/bin/cdsc.js +3 -0
- package/doc/CHANGELOG_ARCHIVE.md +6 -6
- package/doc/CHANGELOG_BETA.md +15 -0
- package/doc/DeprecatedOptions_v2.md +1 -1
- package/doc/NameResolution.md +1 -1
- package/lib/api/main.js +61 -22
- package/lib/api/options.js +1 -0
- package/lib/api/validate.js +5 -0
- package/lib/base/dictionaries.js +5 -3
- package/lib/base/keywords.js +2 -0
- package/lib/base/message-registry.js +64 -22
- package/lib/base/messages.js +12 -7
- package/lib/base/model.js +3 -2
- package/lib/checks/arrayOfs.js +1 -1
- package/lib/checks/defaultValues.js +1 -1
- package/lib/checks/hasPersistedElements.js +1 -1
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/onConditions.js +9 -6
- package/lib/checks/sql-snippets.js +2 -2
- package/lib/checks/types.js +1 -2
- package/lib/compiler/assert-consistency.js +25 -6
- package/lib/compiler/base.js +51 -2
- package/lib/compiler/builtins.js +15 -6
- package/lib/compiler/checks.js +4 -4
- package/lib/compiler/define.js +59 -80
- package/lib/compiler/extend.js +717 -498
- package/lib/compiler/finalize-parse-cdl.js +4 -3
- package/lib/compiler/index.js +1 -1
- package/lib/compiler/kick-start.js +2 -2
- package/lib/compiler/populate.js +17 -9
- package/lib/compiler/propagator.js +12 -5
- package/lib/compiler/resolve.js +26 -173
- package/lib/compiler/shared.js +20 -58
- package/lib/compiler/tweak-assocs.js +1 -1
- package/lib/compiler/utils.js +2 -2
- package/lib/edm/annotations/genericTranslation.js +124 -46
- package/lib/edm/csn2edm.js +22 -1
- package/lib/edm/edmPreprocessor.js +41 -21
- package/lib/gen/Dictionary.json +4 -0
- 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 +4844 -4508
- package/lib/inspect/inspectPropagation.js +20 -36
- package/lib/json/from-csn.js +56 -7
- package/lib/json/to-csn.js +71 -110
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +49 -9
- package/lib/language/language.g4 +106 -83
- 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 +19 -4
- package/lib/model/csnUtils.js +11 -74
- package/lib/model/revealInternalProperties.js +3 -0
- package/lib/optionProcessor.js +3 -0
- package/lib/render/toCdl.js +203 -104
- package/lib/render/toHdbcds.js +0 -1
- package/lib/render/toRename.js +14 -51
- package/lib/transform/braceExpression.js +6 -0
- package/lib/transform/db/rewriteCalculatedElements.js +55 -14
- package/lib/transform/forOdataNew.js +20 -15
- package/lib/transform/forRelationalDB.js +21 -14
- package/lib/transform/parseExpr.js +2 -0
- package/lib/transform/transformUtilsNew.js +36 -9
- package/lib/transform/translateAssocsToJoins.js +11 -4
- 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;
|
|
@@ -1388,56 +1382,86 @@ function csnToCdl( csn, options ) {
|
|
|
1388
1382
|
* @param {CdlRenderEnvironment} env
|
|
1389
1383
|
*/
|
|
1390
1384
|
function renderAnnotationValue( annoValue, env ) {
|
|
1385
|
+
// TODO: There must be at least one known expression property, otherwise
|
|
1386
|
+
// it could be `type: 'unchecked'`.
|
|
1391
1387
|
const isXpr = annoValue?.['='] !== undefined && (Object.keys(annoValue).length > 1) &&
|
|
1392
1388
|
isBetaEnabled(options, 'annotationExpressions');
|
|
1393
1389
|
if (isXpr) {
|
|
1390
|
+
// Once inside an expression, we stay there.
|
|
1394
1391
|
const xpr = exprRenderer.renderExpr(annoValue, env);
|
|
1395
1392
|
return `( ${xpr} )`;
|
|
1396
1393
|
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
function renderSimpleAnnotationValue( x, env ) {
|
|
1401
|
-
if (Array.isArray(x)) {
|
|
1402
|
-
// Render array parts as values. Spaces required if last array value is
|
|
1403
|
-
// a delimited identifier.
|
|
1404
|
-
return `[ ${x.map(item => renderSimpleAnnotationValue(item, env)).join(', ')} ]`;
|
|
1394
|
+
else if (Array.isArray(annoValue)) {
|
|
1395
|
+
return renderAnnotationArrayValue( annoValue, env );
|
|
1405
1396
|
}
|
|
1406
|
-
else if (typeof
|
|
1397
|
+
else if (typeof annoValue === 'object' && annoValue !== null) {
|
|
1407
1398
|
// Enum symbol
|
|
1408
|
-
if (
|
|
1409
|
-
return `#${
|
|
1399
|
+
if (annoValue['#']) {
|
|
1400
|
+
return `#${annoValue['#']}`;
|
|
1410
1401
|
}
|
|
1411
1402
|
// Shorthand for absolute path (as string)
|
|
1412
|
-
else if (
|
|
1413
|
-
|
|
1403
|
+
else if (annoValue['=']) {
|
|
1404
|
+
if (annoValue['='].startsWith('@'))
|
|
1405
|
+
return quoteAnnotationPathIfRequired(annoValue['=']);
|
|
1406
|
+
return quotePathIfRequired(annoValue['=']);
|
|
1414
1407
|
}
|
|
1415
1408
|
// Shorthand for ellipsis: `... up to <val>`
|
|
1416
|
-
else if (
|
|
1417
|
-
if (
|
|
1409
|
+
else if (annoValue['...']) {
|
|
1410
|
+
if (annoValue['...'] === true)
|
|
1418
1411
|
return '...';
|
|
1419
|
-
return `... up to ${
|
|
1412
|
+
return `... up to ${renderAnnotationValue(annoValue['...'], env)}`;
|
|
1420
1413
|
}
|
|
1421
1414
|
|
|
1422
1415
|
// Struct value (can currently only occur within an array)
|
|
1423
1416
|
// Render as one-liner if there is at most one key. Render as multi-line
|
|
1424
1417
|
// struct if there are more and use nicer indentation.
|
|
1425
|
-
const keys = Object.keys(
|
|
1418
|
+
const keys = Object.keys(annoValue);
|
|
1426
1419
|
const childEnv = keys.length <= 1 ? env : increaseIndent(env);
|
|
1427
|
-
const values = keys.map(key => `${quoteAnnotationPathIfRequired(key)}: ${
|
|
1420
|
+
const values = keys.map(key => `${quoteAnnotationPathIfRequired(key)}: ${renderAnnotationValue(annoValue[key], childEnv)}`);
|
|
1428
1421
|
if (values.length <= 1)
|
|
1429
1422
|
return `{ ${values.join(', ')} }`;
|
|
1430
1423
|
const valueList = values.join(`,\n${childEnv.indent}`);
|
|
1431
1424
|
return `{\n${childEnv.indent}${valueList}\n${env.indent}}`;
|
|
1432
1425
|
}
|
|
1433
1426
|
// Null
|
|
1434
|
-
else if (
|
|
1427
|
+
else if (annoValue === null) {
|
|
1435
1428
|
return 'null';
|
|
1436
1429
|
}
|
|
1437
1430
|
// Primitive: string, number, boolean
|
|
1438
1431
|
|
|
1439
1432
|
// Quote strings, leave all others as they are
|
|
1440
|
-
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}]`;
|
|
1441
1465
|
}
|
|
1442
1466
|
|
|
1443
1467
|
/**
|
|
@@ -1455,7 +1479,7 @@ function csnToCdl( csn, options ) {
|
|
|
1455
1479
|
// FIXME: We should rather explicitly recognize quoting somehow
|
|
1456
1480
|
if (idx === 0 && s.startsWith('$'))
|
|
1457
1481
|
return s;
|
|
1458
|
-
return
|
|
1482
|
+
return quoteNonIdentifierOrKeyword(s, env.additionalKeywords);
|
|
1459
1483
|
}
|
|
1460
1484
|
// ID with filters or parameters
|
|
1461
1485
|
else if (typeof s === 'object') {
|
|
@@ -1468,7 +1492,7 @@ function csnToCdl( csn, options ) {
|
|
|
1468
1492
|
return `${s.func}(${renderArguments(s, '=>', env)})`;
|
|
1469
1493
|
|
|
1470
1494
|
// Path step, possibly with view parameters and/or filters
|
|
1471
|
-
let result = `${
|
|
1495
|
+
let result = `${quoteNonIdentifierOrKeyword(s.id, env.additionalKeywords)}`;
|
|
1472
1496
|
if (s.args) {
|
|
1473
1497
|
// View parameters
|
|
1474
1498
|
result += `(${renderArguments(s, ':', env)})`;
|
|
@@ -1477,7 +1501,10 @@ function csnToCdl( csn, options ) {
|
|
|
1477
1501
|
// Filter, possibly with cardinality
|
|
1478
1502
|
const cardinality = s.cardinality ? (`${s.cardinality.max}: `) : '';
|
|
1479
1503
|
const expr = exprRenderer.renderExpr(s.where, env);
|
|
1480
|
-
|
|
1504
|
+
if (expr.endsWith(']')) // for cases such as [… ![id] ]
|
|
1505
|
+
result += `[ ${cardinality}${expr} ]`;
|
|
1506
|
+
else
|
|
1507
|
+
result += `[${cardinality}${expr}]`;
|
|
1481
1508
|
}
|
|
1482
1509
|
|
|
1483
1510
|
return result;
|
|
@@ -1516,7 +1543,7 @@ function csnToCdl( csn, options ) {
|
|
|
1516
1543
|
*/
|
|
1517
1544
|
function renderNamedArguments( node, separator, env ) {
|
|
1518
1545
|
return Object.keys(node.args).map(function renderNamedArgument(key) {
|
|
1519
|
-
return `${
|
|
1546
|
+
return `${quoteNonIdentifierOrKeyword(key, env.additionalKeywords)} ${separator} ${renderArgument(node.args[key], env)}`;
|
|
1520
1547
|
}).join(', ');
|
|
1521
1548
|
}
|
|
1522
1549
|
|
|
@@ -1658,7 +1685,7 @@ function csnToCdl( csn, options ) {
|
|
|
1658
1685
|
* @return {string}
|
|
1659
1686
|
*/
|
|
1660
1687
|
function renderAlias( alias ) {
|
|
1661
|
-
return ` as ${
|
|
1688
|
+
return ` as ${quoteNonIdentifierOrKeyword(alias)}`;
|
|
1662
1689
|
}
|
|
1663
1690
|
|
|
1664
1691
|
/**
|
|
@@ -1696,7 +1723,7 @@ function csnToCdl( csn, options ) {
|
|
|
1696
1723
|
*
|
|
1697
1724
|
* @param {object} obj Object that has annotations
|
|
1698
1725
|
* @param {CdlRenderEnvironment} env
|
|
1699
|
-
* @param {{
|
|
1726
|
+
* @param {{parentheses: boolean}} [config] Config for renderAnnotationAssignment()
|
|
1700
1727
|
* @return {string}
|
|
1701
1728
|
*/
|
|
1702
1729
|
function renderAnnotationAssignmentsAndDocComment( obj, env, config ) {
|
|
@@ -1716,38 +1743,29 @@ function csnToCdl( csn, options ) {
|
|
|
1716
1743
|
* @param {any} anno Annotation value
|
|
1717
1744
|
* @param {string} name Annotation name, e.g. `@A.B.C#foo.C`
|
|
1718
1745
|
* @param {CdlRenderEnvironment} env
|
|
1719
|
-
* @param {object} [config]
|
|
1746
|
+
* @param {object} [config] parentheses: Whether the annotation assignment must be surrounded by parentheses.
|
|
1720
1747
|
* @return {string} Rendered annotation, possibly quoted: `@![A.B.C#foo.C]: value`
|
|
1721
1748
|
*/
|
|
1722
|
-
function renderAnnotationAssignment( anno, name, env, config = {
|
|
1749
|
+
function renderAnnotationAssignment( anno, name, env, config = { parentheses: false } ) {
|
|
1723
1750
|
name = name.substring(1);
|
|
1724
1751
|
// Take the annotation assignment apart into <nameBeforeVariant>#<variantAndRest>
|
|
1725
1752
|
const parts = name.split('#');
|
|
1726
1753
|
const nameBeforeVariant = parts[0];
|
|
1727
1754
|
const variant = parts[1];
|
|
1728
|
-
const {
|
|
1729
|
-
|
|
1730
|
-
// Identifier according to our grammar: /[$_a-zA-Z][$_a-zA-Z0-9]*/
|
|
1731
|
-
// We expand this pattern to also include dots after the first character.
|
|
1732
|
-
// If the annotation does not follow this pattern `ident(.@ident)*`, it must be quoted:
|
|
1733
|
-
// `@identifier@identifier` must be quoted but `@identifier.@identifier` should not.
|
|
1734
|
-
// TODO: Use quoteAnnotationPathIfRequired()
|
|
1735
|
-
const annoRequiresQuoting = !/^[$_a-zA-Z][$_a-zA-Z0-9.]*(?:\.@[$_a-zA-Z][$_a-zA-Z0-9.]*)*$/.test(nameBeforeVariant);
|
|
1736
|
-
// Unfortunately, the compiler does not allow `.` after the first variant identifier,
|
|
1737
|
-
// even though that is the result after flattening.
|
|
1738
|
-
const variantRequiresQuoting = variant && !/^[$_a-zA-Z][$_a-zA-Z0-9]*$/.test(variant);
|
|
1755
|
+
const { parentheses } = config;
|
|
1739
1756
|
|
|
1740
1757
|
let result = `${env.indent}@`;
|
|
1741
|
-
if (
|
|
1758
|
+
if (parentheses)
|
|
1742
1759
|
result += '(';
|
|
1743
1760
|
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
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)}`;
|
|
1749
1766
|
result += ` : ${renderAnnotationValue(anno, env)}`;
|
|
1750
|
-
|
|
1767
|
+
|
|
1768
|
+
if (parentheses)
|
|
1751
1769
|
result += ')';
|
|
1752
1770
|
return `${result}\n`;
|
|
1753
1771
|
}
|
|
@@ -1764,7 +1782,7 @@ function csnToCdl( csn, options ) {
|
|
|
1764
1782
|
|
|
1765
1783
|
/**
|
|
1766
1784
|
* Render the name of a definition. Ensures the first segment of the name
|
|
1767
|
-
* is available in the rendered CDL. Otherwise a USING is added.
|
|
1785
|
+
* is available in the rendered CDL. Otherwise, a USING is added.
|
|
1768
1786
|
*
|
|
1769
1787
|
* @param {string} name
|
|
1770
1788
|
* @return {string}
|
|
@@ -1846,6 +1864,62 @@ function csnToCdl( csn, options ) {
|
|
|
1846
1864
|
},
|
|
1847
1865
|
});
|
|
1848
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
|
+
}
|
|
1849
1923
|
}
|
|
1850
1924
|
|
|
1851
1925
|
/**
|
|
@@ -1883,24 +1957,32 @@ function increaseIndent( env ) {
|
|
|
1883
1957
|
}
|
|
1884
1958
|
|
|
1885
1959
|
/**
|
|
1886
|
-
* Quote
|
|
1887
|
-
* `elem` use `
|
|
1960
|
+
* Quote simple path steps with `![]` if necessary. For simple ids such as
|
|
1961
|
+
* `elem` use `quoteNonIdentifierOrKeyword` instead.
|
|
1888
1962
|
*
|
|
1889
|
-
* In contrast to
|
|
1963
|
+
* In contrast to quoteNonIdentifierOrKeyword, does not handle additional keywords,
|
|
1890
1964
|
* because it was not required, yet.
|
|
1891
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
|
+
*
|
|
1892
1969
|
* @param {string} path
|
|
1893
1970
|
* @returns {string}
|
|
1894
|
-
*
|
|
1895
|
-
* @todo For paths such as `E.key`, `key` does not have to be in quotes.
|
|
1896
1971
|
*/
|
|
1897
1972
|
function quotePathIfRequired( path ) {
|
|
1898
|
-
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('.');
|
|
1899
1980
|
}
|
|
1900
1981
|
|
|
1901
1982
|
/**
|
|
1902
1983
|
* Quote the id with `![]` if necessary. For paths such as `E.key` use
|
|
1903
1984
|
* `quotePathIfRequired` instead.
|
|
1985
|
+
* See quoteNonIdentifier() if you want to ignore keywords.
|
|
1904
1986
|
*
|
|
1905
1987
|
* Set additionalKeywords to an array of UPPERCASE keywords
|
|
1906
1988
|
* that also need quoting, e.g. in special functions.
|
|
@@ -1909,13 +1991,30 @@ function quotePathIfRequired( path ) {
|
|
|
1909
1991
|
* @param {string[]} [additionalKeywords]
|
|
1910
1992
|
* @return {string}
|
|
1911
1993
|
*/
|
|
1912
|
-
function
|
|
1994
|
+
function quoteNonIdentifierOrKeyword( id, additionalKeywords ) {
|
|
1913
1995
|
// Quote if required for CDL
|
|
1914
1996
|
if (requiresQuotingForCdl(id, additionalKeywords || []))
|
|
1915
1997
|
return delimitedId(id);
|
|
1916
1998
|
return id;
|
|
1917
1999
|
}
|
|
1918
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
|
+
|
|
1919
2018
|
/**
|
|
1920
2019
|
* Quote an annotation path, e.g. `@My.@Anno.Description` if necessary.
|
|
1921
2020
|
* `anno` can start with `@` but is not required to be.
|
|
@@ -1928,8 +2027,8 @@ function quoteIdIfRequired( id, additionalKeywords ) {
|
|
|
1928
2027
|
function quoteAnnotationPathIfRequired( anno ) {
|
|
1929
2028
|
return anno.split('.').map((segment) => {
|
|
1930
2029
|
if (segment.startsWith('@'))
|
|
1931
|
-
return `@${
|
|
1932
|
-
return
|
|
2030
|
+
return `@${quoteNonIdentifier(segment.slice(1))}`;
|
|
2031
|
+
return quoteNonIdentifier(segment);
|
|
1933
2032
|
}).join('.');
|
|
1934
2033
|
}
|
|
1935
2034
|
|
|
@@ -2154,8 +2253,8 @@ function availableFirstPathSteps( csn ) {
|
|
|
2154
2253
|
function smartId( id, insideFunction = null ) {
|
|
2155
2254
|
insideFunction = insideFunction?.toUpperCase();
|
|
2156
2255
|
if (!insideFunction || !specialFunctions[insideFunction])
|
|
2157
|
-
return
|
|
2158
|
-
return
|
|
2256
|
+
return quoteNonIdentifierOrKeyword(id);
|
|
2257
|
+
return quoteNonIdentifierOrKeyword(id, getAllKeywordsForSpecialFunction(insideFunction));
|
|
2159
2258
|
}
|
|
2160
2259
|
|
|
2161
2260
|
/**
|
package/lib/render/toHdbcds.js
CHANGED