@sap/cds-compiler 2.11.4 → 2.13.8
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 +159 -1
- package/bin/cds_update_identifiers.js +7 -7
- package/bin/cdsc.js +22 -23
- package/bin/cdsse.js +2 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +25 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +30 -63
- package/lib/api/options.js +5 -5
- package/lib/api/validate.js +0 -5
- package/lib/backends.js +15 -23
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +7 -17
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +52 -2
- package/lib/base/messages.js +16 -26
- package/lib/base/model.js +2 -62
- package/lib/base/optionProcessorHelper.js +246 -183
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +94 -0
- package/lib/checks/types.js +1 -1
- package/lib/checks/validator.js +12 -7
- package/lib/compiler/assert-consistency.js +10 -6
- package/lib/compiler/base.js +0 -1
- package/lib/compiler/builtins.js +8 -6
- package/lib/compiler/checks.js +46 -12
- package/lib/compiler/cycle-detector.js +1 -1
- package/lib/compiler/define.js +1103 -0
- package/lib/compiler/extend.js +983 -0
- package/lib/compiler/finalize-parse-cdl.js +231 -0
- package/lib/compiler/index.js +33 -14
- package/lib/compiler/kick-start.js +190 -0
- package/lib/compiler/moduleLayers.js +4 -4
- package/lib/compiler/populate.js +1226 -0
- package/lib/compiler/propagator.js +113 -47
- package/lib/compiler/resolve.js +1433 -0
- package/lib/compiler/shared.js +76 -38
- package/lib/compiler/tweak-assocs.js +529 -0
- package/lib/compiler/utils.js +204 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +38 -25
- package/lib/edm/annotations/preprocessAnnotations.js +3 -3
- package/lib/edm/csn2edm.js +10 -9
- package/lib/edm/edm.js +19 -20
- package/lib/edm/edmPreprocessor.js +166 -95
- package/lib/edm/edmUtils.js +127 -34
- package/lib/gen/Dictionary.json +92 -43
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +11 -1
- package/lib/gen/language.tokens +86 -82
- package/lib/gen/languageLexer.interp +18 -1
- package/lib/gen/languageLexer.js +925 -847
- package/lib/gen/languageLexer.tokens +78 -74
- package/lib/gen/languageParser.js +5434 -4298
- package/lib/json/from-csn.js +59 -17
- package/lib/json/to-csn.js +143 -71
- package/lib/language/antlrParser.js +3 -3
- package/lib/language/docCommentParser.js +3 -3
- package/lib/language/genericAntlrParser.js +144 -54
- package/lib/language/language.g4 +424 -203
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +472 -61
- package/lib/main.js +38 -11
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +321 -204
- package/lib/model/csnUtils.js +224 -263
- package/lib/model/enrichCsn.js +97 -40
- package/lib/model/revealInternalProperties.js +27 -6
- package/lib/model/sortViews.js +2 -1
- package/lib/modelCompare/compare.js +17 -12
- package/lib/optionProcessor.js +7 -6
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +36 -33
- package/lib/render/toCdl.js +174 -275
- package/lib/render/toHdbcds.js +201 -115
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +149 -75
- package/lib/render/utils/common.js +22 -8
- package/lib/render/utils/sql.js +10 -7
- package/lib/render/utils/stringEscapes.js +111 -0
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/.eslintrc.json +5 -0
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/.eslintrc.json +2 -0
- package/lib/transform/db/applyTransformations.js +35 -12
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +187 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +61 -56
- package/lib/transform/db/expansion.js +50 -29
- package/lib/transform/db/flattening.js +552 -105
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +94 -28
- package/lib/transform/db/views.js +5 -4
- package/lib/transform/draft/.eslintrc.json +38 -0
- package/lib/transform/{db/draft.js → draft/db.js} +9 -7
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +94 -801
- package/lib/transform/forOdataNew.js +22 -175
- package/lib/transform/localized.js +36 -32
- package/lib/transform/odata/generateForeignKeyElements.js +3 -3
- package/lib/transform/odata/referenceFlattener.js +95 -89
- package/lib/transform/odata/structureFlattener.js +1 -1
- package/lib/transform/odata/toFinalBaseType.js +86 -12
- package/lib/transform/odata/typesExposure.js +5 -5
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +47 -33
- package/lib/transform/translateAssocsToJoins.js +10 -27
- package/lib/transform/universalCsn/.eslintrc.json +36 -0
- package/lib/transform/universalCsn/coreComputed.js +170 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
- package/lib/transform/universalCsn/utils.js +63 -0
- package/lib/utils/file.js +2 -1
- package/lib/utils/objectUtils.js +30 -0
- package/lib/utils/timetrace.js +8 -2
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/lib/compiler/definer.js +0 -2340
- package/lib/compiler/resolver.js +0 -2988
- package/lib/transform/universalCsnEnricher.js +0 -67
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 {
|
|
@@ -9,12 +9,14 @@ const {
|
|
|
9
9
|
} = require('../model/csnUtils');
|
|
10
10
|
const keywords = require('../base/keywords');
|
|
11
11
|
const { renderFunc, beautifyExprArray, findElement } = require('./utils/common');
|
|
12
|
+
const { escapeString, hasUnpairedUnicodeSurrogate } = require('./utils/stringEscapes');
|
|
12
13
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
13
14
|
const { timetrace } = require('../utils/timetrace');
|
|
14
15
|
const { csnRefs } = require('../model/csnRefs');
|
|
15
16
|
const { forEachDefinition } = require('../model/csnUtils');
|
|
16
|
-
const enrichUniversalCsn = require('../transform/universalCsnEnricher');
|
|
17
|
+
const enrichUniversalCsn = require('../transform/universalCsn/universalCsnEnricher');
|
|
17
18
|
const { isBetaEnabled } = require('../base/model');
|
|
19
|
+
const { ModelError } = require('../base/error');
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* Render the CSN model 'model' to CDS source text. One source is created per
|
|
@@ -40,19 +42,19 @@ function toCdsSourceCsn(csn, options) {
|
|
|
40
42
|
|
|
41
43
|
checkCSNVersion(csn, options);
|
|
42
44
|
|
|
43
|
-
const
|
|
45
|
+
const cdlResult = Object.create(null);
|
|
44
46
|
|
|
45
47
|
const main = 'model';
|
|
46
48
|
|
|
47
|
-
|
|
49
|
+
cdlResult[main] = options.testMode ? '' : `// ${generatedByCompilerVersion()} \n`;
|
|
48
50
|
|
|
49
51
|
const subelementAnnotates = [];
|
|
50
52
|
|
|
51
53
|
forEachDefinition(csn, (artifact, artifactName) => {
|
|
52
54
|
const env = createEnv();
|
|
53
|
-
const sourceStr = renderArtifact(artifactName, artifact, env);
|
|
55
|
+
const sourceStr = renderArtifact(artifactName, artifact, env);
|
|
54
56
|
if (sourceStr !== '')
|
|
55
|
-
|
|
57
|
+
cdlResult[main] += `${sourceStr}\n`;
|
|
56
58
|
});
|
|
57
59
|
|
|
58
60
|
// Apply possible subelement/action return annotations with an "annotate X with"
|
|
@@ -70,7 +72,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
70
72
|
if (elementName) // action returns do not have element name - we need less {} there
|
|
71
73
|
sourceStr += ' }\n';
|
|
72
74
|
sourceStr += '}\n';
|
|
73
|
-
|
|
75
|
+
cdlResult[main] += `${sourceStr}\n`;
|
|
74
76
|
}
|
|
75
77
|
}
|
|
76
78
|
}
|
|
@@ -117,13 +119,13 @@ function toCdsSourceCsn(csn, options) {
|
|
|
117
119
|
sourceStr = renderTypeOrAnnotation(annotationName, anno, env, 'annotation');
|
|
118
120
|
|
|
119
121
|
if (sourceStr !== '')
|
|
120
|
-
|
|
122
|
+
cdlResult[main] += `${sourceStr}\n`;
|
|
121
123
|
}
|
|
122
124
|
}
|
|
123
125
|
|
|
124
126
|
if (csn.namespace) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
+
cdlResult[csn.namespace] = `namespace ${renderArtifactName(csn.namespace)};\n`;
|
|
128
|
+
cdlResult[csn.namespace] += `using from './${main}.cds';`;
|
|
127
129
|
}
|
|
128
130
|
|
|
129
131
|
|
|
@@ -132,11 +134,11 @@ function toCdsSourceCsn(csn, options) {
|
|
|
132
134
|
if (csn.extensions) {
|
|
133
135
|
const env = createEnv();
|
|
134
136
|
const sourceStr = renderUnappliedExtensions(csn.extensions, env);
|
|
135
|
-
|
|
137
|
+
cdlResult.unappliedExtensions = sourceStr;
|
|
136
138
|
}
|
|
137
139
|
|
|
138
140
|
timetrace.stop();
|
|
139
|
-
return
|
|
141
|
+
return cdlResult;
|
|
140
142
|
|
|
141
143
|
/**
|
|
142
144
|
* Render unapplied 'extend' and 'annotate' statements from the 'extensions array'
|
|
@@ -233,7 +235,6 @@ function toCdsSourceCsn(csn, options) {
|
|
|
233
235
|
|
|
234
236
|
switch (art.kind) {
|
|
235
237
|
case 'entity':
|
|
236
|
-
case 'view':
|
|
237
238
|
if (art.query || art.projection)
|
|
238
239
|
return renderView(artifactName, art, env);
|
|
239
240
|
|
|
@@ -242,8 +243,6 @@ function toCdsSourceCsn(csn, options) {
|
|
|
242
243
|
case 'context':
|
|
243
244
|
case 'service':
|
|
244
245
|
return renderContext(artifactName, art, env);
|
|
245
|
-
case 'namespace':
|
|
246
|
-
return renderNamespace(artifactName, art, env);
|
|
247
246
|
case 'type':
|
|
248
247
|
case 'aspect':
|
|
249
248
|
case 'annotation':
|
|
@@ -254,7 +253,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
254
253
|
case 'event':
|
|
255
254
|
return renderEventIfCDLMode(artifactName, art, env);
|
|
256
255
|
default:
|
|
257
|
-
throw new
|
|
256
|
+
throw new ModelError(`Unknown artifact kind: ${art.kind}`);
|
|
258
257
|
}
|
|
259
258
|
}
|
|
260
259
|
|
|
@@ -294,37 +293,6 @@ function toCdsSourceCsn(csn, options) {
|
|
|
294
293
|
return result;
|
|
295
294
|
}
|
|
296
295
|
|
|
297
|
-
/**
|
|
298
|
-
* Return a dictionary with the direct sub-artifacts of the artifact with name 'artifactName' in the csn
|
|
299
|
-
*
|
|
300
|
-
* @param {string} artifactName
|
|
301
|
-
* @return {object}
|
|
302
|
-
*/
|
|
303
|
-
function getSubArtifacts(artifactName) {
|
|
304
|
-
const prefix = `${artifactName}.`;
|
|
305
|
-
const result = Object.create(null);
|
|
306
|
-
for (const name in csn.definitions) {
|
|
307
|
-
// We have a direct child if its name starts with prefix and contains no more dots
|
|
308
|
-
if (name.startsWith(prefix) && !name.substring(prefix.length).includes('.')) {
|
|
309
|
-
result[getLastPartOf(name)] = csn.definitions[name];
|
|
310
|
-
}
|
|
311
|
-
else if (name.startsWith(prefix)) {
|
|
312
|
-
const prefixPlusNextPart = name.substring(0, name.substring(prefix.length).indexOf('.') + prefix.length);
|
|
313
|
-
if (csn.definitions[prefixPlusNextPart]) {
|
|
314
|
-
const art = csn.definitions[prefixPlusNextPart];
|
|
315
|
-
if (![ 'service', 'context', 'namespace' ].includes(art.kind)) {
|
|
316
|
-
const nameWithoutPrefix = name.substring(prefix.length);
|
|
317
|
-
result[nameWithoutPrefix] = csn.definitions[name];
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
else {
|
|
321
|
-
result[name.substring(prefix.length)] = csn.definitions[name];
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
return result;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
296
|
/* FIXME: Not yet required
|
|
329
297
|
// Returns the artifact or element that constitutes the final type of
|
|
330
298
|
// construct 'node', i.e. the object in which we would find type properties for
|
|
@@ -385,37 +353,6 @@ function toCdsSourceCsn(csn, options) {
|
|
|
385
353
|
return `${result} {};\n`;
|
|
386
354
|
}
|
|
387
355
|
|
|
388
|
-
function updatePrefixForDottedName(env, name) {
|
|
389
|
-
let innerEnv = env;
|
|
390
|
-
if (name.indexOf('.') !== -1) {
|
|
391
|
-
const parts = name.split('.');
|
|
392
|
-
for (let i = 0; i < parts.length - 1; i++)
|
|
393
|
-
innerEnv = addNamePrefix(innerEnv, parts[i]);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
return innerEnv;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
/**
|
|
400
|
-
* Render a namespace. Return the resulting source string.
|
|
401
|
-
*
|
|
402
|
-
* @param {string} artifactName
|
|
403
|
-
* @param {CSN.Artifact} art
|
|
404
|
-
* @param {CdlRenderEnvironment} env
|
|
405
|
-
* @return {string}
|
|
406
|
-
*/
|
|
407
|
-
function renderNamespace(artifactName, art, env) {
|
|
408
|
-
// We currently do not render anything for a namespace, we just append its id to
|
|
409
|
-
// the environment's current name prefix and descend into its children
|
|
410
|
-
let result = renderDocComment(art, env);
|
|
411
|
-
const childEnv = addNamePrefix(env, getLastPartOf(artifactName));
|
|
412
|
-
const subArtifacts = getSubArtifacts(artifactName);
|
|
413
|
-
for (const name in subArtifacts)
|
|
414
|
-
result += renderArtifact(`${artifactName}.${name}`, subArtifacts[name], updatePrefixForDottedName(childEnv, name));
|
|
415
|
-
|
|
416
|
-
return result;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
356
|
/**
|
|
420
357
|
* Render a (non-projection, non-view) entity. Return the resulting source string.
|
|
421
358
|
*
|
|
@@ -446,84 +383,10 @@ function toCdsSourceCsn(csn, options) {
|
|
|
446
383
|
}
|
|
447
384
|
|
|
448
385
|
result += `${env.indent}}`;
|
|
449
|
-
result += `${renderActionsAndFunctions(art, env)
|
|
386
|
+
result += `${renderActionsAndFunctions(art, env)};\n`;
|
|
450
387
|
return result;
|
|
451
388
|
}
|
|
452
389
|
|
|
453
|
-
// Render the 'technical configuration { ... }' section 'tc' of an entity.
|
|
454
|
-
// Return the resulting source string.
|
|
455
|
-
function renderTechnicalConfiguration(tc, env) {
|
|
456
|
-
let result = renderDocComment(tc, env);
|
|
457
|
-
const childEnv = increaseIndent(env);
|
|
458
|
-
|
|
459
|
-
if (!tc)
|
|
460
|
-
return result;
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
// FIXME: How to deal with non-HANA technical configurations? We should probably just iterate all entries
|
|
464
|
-
// in 'tc' that we find and render them all (is it syntactically allowed yet to have more than one?)
|
|
465
|
-
tc = tc.hana;
|
|
466
|
-
if (!tc)
|
|
467
|
-
throw new Error('Expecting a HANA technical configuration');
|
|
468
|
-
|
|
469
|
-
result += `\n${env.indent}technical ${tc.calculated ? '' : 'hana '}configuration {\n`;
|
|
470
|
-
|
|
471
|
-
// Store type (must be separate because SQL wants it between 'CREATE' and 'TABLE')
|
|
472
|
-
if (tc.storeType)
|
|
473
|
-
result += `${tc.storeType} store;\n`;
|
|
474
|
-
|
|
475
|
-
// Fixed parts belonging to the table (includes migration, unload prio, extended storage,
|
|
476
|
-
// auto merge, partitioning, ...)
|
|
477
|
-
if (tc.tableSuffix) {
|
|
478
|
-
// Unlike SQL, CDL and HANA CDS require a semicolon after each table-suffix part
|
|
479
|
-
// (e.g. `migration enabled; row store; ...`). In order to keep both
|
|
480
|
-
// the simplicity of "the whole bandwurm is just one expression that can be
|
|
481
|
-
// rendered to SQL without further knowledge" and at the same time telling
|
|
482
|
-
// CDS about the boundaries, the compactor has put each part into its own `xpr`
|
|
483
|
-
// object. Semantically equivalent because a "trivial" SQL renderer would just
|
|
484
|
-
// concatenate them.
|
|
485
|
-
for (const xpr of tc.tableSuffix)
|
|
486
|
-
result += `${childEnv.indent + renderExpr(xpr, childEnv)};\n`;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// Indices and full-text indices
|
|
490
|
-
for (const idxName in tc.indexes || {}) {
|
|
491
|
-
if (Array.isArray(tc.indexes[idxName][0])) {
|
|
492
|
-
// FIXME: Should we allow multiple indices with the same name at all?
|
|
493
|
-
for (const index of tc.indexes[idxName])
|
|
494
|
-
result += `${childEnv.indent + renderExpr(index, childEnv)};\n`;
|
|
495
|
-
}
|
|
496
|
-
else {
|
|
497
|
-
result += `${childEnv.indent + renderExpr(tc.indexes[idxName], childEnv)};\n`;
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
// Fuzzy search indices
|
|
501
|
-
for (const columnName in tc.fzindexes || {}) {
|
|
502
|
-
if (Array.isArray(tc.fzindexes[columnName][0])) {
|
|
503
|
-
// FIXME: Should we allow multiple fuzzy search indices on the same column at all?
|
|
504
|
-
// And if not, why do we wrap this into an array?
|
|
505
|
-
for (const index of tc.fzindexes[columnName])
|
|
506
|
-
result += `${childEnv.indent + renderExpr(fixFuzzyIndex(index, columnName), childEnv)};\n`;
|
|
507
|
-
}
|
|
508
|
-
else {
|
|
509
|
-
result += `${childEnv.indent + renderExpr(fixFuzzyIndex(tc.fzindexes[columnName], columnName), childEnv)};\n`;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
result += `${env.indent}}`;
|
|
513
|
-
return result;
|
|
514
|
-
|
|
515
|
-
// Fuzzy indices are stored in compact CSN as they would appear in SQL after the column name,
|
|
516
|
-
// i.e. the whole line in SQL looks somewhat like this:
|
|
517
|
-
// s nvarchar(10) FUZZY SEARCH INDEX ON FUZZY SEARCH MODE 'ALPHANUM'
|
|
518
|
-
// But in CDL, we don't write fuzzy search indices together with the table column, so we need
|
|
519
|
-
// to insert the name of the column after 'ON' in CDS syntax, making it look like this:
|
|
520
|
-
// fuzzy search mode on (s) search mode 'ALPHANUM'
|
|
521
|
-
// This function expects an array with the original expression and returns an array with the modified expression
|
|
522
|
-
function fixFuzzyIndex(fuzzyIndex, columnName) {
|
|
523
|
-
return fuzzyIndex.map(token => (token === 'on' ? { xpr: [ 'on', '(', { ref: columnName.split('.') }, ')' ] } : token));
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
|
|
527
390
|
/**
|
|
528
391
|
* Render an element (of an entity, type or annotation, not a projection or view).
|
|
529
392
|
* Return the resulting source string.
|
|
@@ -561,7 +424,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
561
424
|
|
|
562
425
|
// Sanity checks
|
|
563
426
|
if (!query.SET || !query.SET.args || !query.SET.args[0])
|
|
564
|
-
throw new
|
|
427
|
+
throw new ModelError(`Expecting set with args in query: ${JSON.stringify(query)}`);
|
|
565
428
|
|
|
566
429
|
return leadingQuerySelect(query.SET.args[0]);
|
|
567
430
|
}
|
|
@@ -620,14 +483,14 @@ function toCdsSourceCsn(csn, options) {
|
|
|
620
483
|
if (!name.startsWith('@'))
|
|
621
484
|
continue;
|
|
622
485
|
|
|
623
|
-
const annotationValue =
|
|
486
|
+
const annotationValue = renderAnnotationAssignment(elem[name], name, childEnv);
|
|
624
487
|
// Skip annotation if column has the same
|
|
625
488
|
if (columnMap[elemName] && columnMap[elemName][name] &&
|
|
626
|
-
|
|
489
|
+
renderAnnotationAssignment(columnMap[elemName][name], name, childEnv) === annotationValue)
|
|
627
490
|
continue;
|
|
628
491
|
|
|
629
492
|
// Annotation names are never flattened
|
|
630
|
-
elemAnnotations +=
|
|
493
|
+
elemAnnotations += annotationValue;
|
|
631
494
|
}
|
|
632
495
|
if (elemAnnotations !== '')
|
|
633
496
|
result += `${elemAnnotations}${childEnv.indent}${quoteOrUppercaseId(elemName)};\n`;
|
|
@@ -703,7 +566,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
703
566
|
function renderAbsolutePath(path, env) {
|
|
704
567
|
// Sanity checks
|
|
705
568
|
if (!path.ref)
|
|
706
|
-
throw new
|
|
569
|
+
throw new ModelError(`Expecting ref in path: ${JSON.stringify(path)}`);
|
|
707
570
|
|
|
708
571
|
|
|
709
572
|
// Determine the absolute name of the first artifact on the path (before any associations or element traversals)
|
|
@@ -909,6 +772,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
909
772
|
* @param {string} syntax The query syntax, either "projection", "entity" or "view"
|
|
910
773
|
* @param {CdlRenderEnvironment} env
|
|
911
774
|
* @param {CSN.Path} [path=[]]
|
|
775
|
+
* @param {object} [elements]
|
|
912
776
|
*/
|
|
913
777
|
function renderQuery(query, isLeadingQuery, syntax, env, path = [], elements = query.elements || Object.create(null)) {
|
|
914
778
|
let result = renderDocComment(query, env);
|
|
@@ -935,7 +799,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
935
799
|
}
|
|
936
800
|
// Otherwise must have a SELECT
|
|
937
801
|
else if (!query.SELECT) {
|
|
938
|
-
throw new
|
|
802
|
+
throw new ModelError(`Unexpected query operation ${JSON.stringify(query)}`);
|
|
939
803
|
}
|
|
940
804
|
const select = query.SELECT;
|
|
941
805
|
const childEnv = increaseIndent(env);
|
|
@@ -945,7 +809,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
945
809
|
else if (syntax === 'view' || syntax === 'entity')
|
|
946
810
|
result += `select from ${renderViewSource(select.from, env)}`;
|
|
947
811
|
else
|
|
948
|
-
throw new
|
|
812
|
+
throw new ModelError(`Unknown query syntax: ${syntax}`);
|
|
949
813
|
|
|
950
814
|
if (select.mixin) {
|
|
951
815
|
let elems = '';
|
|
@@ -980,7 +844,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
980
844
|
result += `${continueIndent(result, env)}where ${renderExpr(select.where, env, true, true)}`;
|
|
981
845
|
|
|
982
846
|
if (select.groupBy)
|
|
983
|
-
result += `${continueIndent(result, env)}group by ${select.groupBy.map(expr => renderExpr(expr, env)).join(', ')}`;
|
|
847
|
+
result += `${continueIndent(result, env)}group by ${select.groupBy.map(expr => renderExpr(expr, env, true, false, true)).join(', ')}`;
|
|
984
848
|
|
|
985
849
|
if (select.having)
|
|
986
850
|
result += `${continueIndent(result, env)}having ${renderExpr(select.having, env, true, true)}`;
|
|
@@ -996,35 +860,35 @@ function toCdsSourceCsn(csn, options) {
|
|
|
996
860
|
/**
|
|
997
861
|
* Utility function to make sure that we continue with the same indentation in WHERE, GROUP BY, ... after a closing curly brace and beyond
|
|
998
862
|
*
|
|
999
|
-
* @param {string}
|
|
1000
|
-
* @param {CdlRenderEnvironment}
|
|
863
|
+
* @param {string} str
|
|
864
|
+
* @param {CdlRenderEnvironment} indentEnv
|
|
1001
865
|
* @return {string}
|
|
1002
866
|
*/
|
|
1003
|
-
function continueIndent(
|
|
1004
|
-
if (
|
|
867
|
+
function continueIndent(str, indentEnv) {
|
|
868
|
+
if (str.endsWith('}') || str.endsWith('})')) {
|
|
1005
869
|
// The preceding clause ended with '}', just append after that
|
|
1006
870
|
return ' ';
|
|
1007
871
|
}
|
|
1008
872
|
// Otherwise, start new line and indent normally
|
|
1009
|
-
return `\n${increaseIndent(
|
|
873
|
+
return `\n${increaseIndent(indentEnv).indent}`;
|
|
1010
874
|
}
|
|
1011
875
|
|
|
1012
876
|
/**
|
|
1013
877
|
* Render a query's LIMIT clause, which may have also have OFFSET.
|
|
1014
878
|
*
|
|
1015
879
|
* @param {CSN.QueryLimit} limit
|
|
1016
|
-
* @param {CdlRenderEnvironment}
|
|
880
|
+
* @param {CdlRenderEnvironment} limitEnv
|
|
1017
881
|
* @return {string}
|
|
1018
882
|
*/
|
|
1019
|
-
function renderLimit(limit,
|
|
1020
|
-
let
|
|
883
|
+
function renderLimit(limit, limitEnv) {
|
|
884
|
+
let limitStr = '';
|
|
1021
885
|
if (limit.rows !== undefined)
|
|
1022
|
-
|
|
886
|
+
limitStr += `limit ${renderExpr(limit.rows, limitEnv)}`;
|
|
1023
887
|
|
|
1024
888
|
if (limit.offset !== undefined)
|
|
1025
|
-
|
|
889
|
+
limitStr += `${limitStr !== '' ? `\n${increaseIndent(limitEnv).indent}` : ''}offset ${renderExpr(limit.offset, limitEnv)}`;
|
|
1026
890
|
|
|
1027
|
-
return
|
|
891
|
+
return limitStr;
|
|
1028
892
|
}
|
|
1029
893
|
}
|
|
1030
894
|
|
|
@@ -1037,7 +901,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1037
901
|
* @return {string}
|
|
1038
902
|
*/
|
|
1039
903
|
function renderOrderByEntry(entry, env) {
|
|
1040
|
-
let result = renderDocComment(entry, env) + renderExpr(entry, env);
|
|
904
|
+
let result = renderDocComment(entry, env) + renderExpr(entry, env, true, false, true);
|
|
1041
905
|
if (entry.sort)
|
|
1042
906
|
result += ` ${entry.sort}`;
|
|
1043
907
|
|
|
@@ -1140,7 +1004,10 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1140
1004
|
}
|
|
1141
1005
|
else {
|
|
1142
1006
|
// Derived type or annotation with non-anonymous type
|
|
1143
|
-
result += ` : ${renderTypeReference(art, env, false)}
|
|
1007
|
+
result += ` : ${renderTypeReference(art, env, false)}`;
|
|
1008
|
+
if (art.default)
|
|
1009
|
+
result += ` default ${renderExpr(art.default, env)}`;
|
|
1010
|
+
result += ';\n';
|
|
1144
1011
|
}
|
|
1145
1012
|
return result;
|
|
1146
1013
|
}
|
|
@@ -1176,7 +1043,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1176
1043
|
// Anonymous structured type
|
|
1177
1044
|
if (!elm.type) {
|
|
1178
1045
|
if (!elm.elements)
|
|
1179
|
-
throw new
|
|
1046
|
+
throw new ModelError(`Missing type of: ${JSON.stringify(elm)}`);
|
|
1180
1047
|
|
|
1181
1048
|
result += '{\n';
|
|
1182
1049
|
const childEnv = increaseIndent(env);
|
|
@@ -1214,7 +1081,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1214
1081
|
result += `${env.indent}}`;
|
|
1215
1082
|
}
|
|
1216
1083
|
else {
|
|
1217
|
-
throw new
|
|
1084
|
+
throw new ModelError('Association/Composition is missing its target! Throwing exception to trigger recompilation.');
|
|
1218
1085
|
}
|
|
1219
1086
|
|
|
1220
1087
|
|
|
@@ -1335,7 +1202,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1335
1202
|
// Primitive: string, number, boolean
|
|
1336
1203
|
|
|
1337
1204
|
// Quote strings, leave all others as they are
|
|
1338
|
-
return (typeof x === 'string') ?
|
|
1205
|
+
return (typeof x === 'string') ? renderString(x, env) : x;
|
|
1339
1206
|
}
|
|
1340
1207
|
|
|
1341
1208
|
/**
|
|
@@ -1346,14 +1213,16 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1346
1213
|
* @param {CdlRenderEnvironment} env
|
|
1347
1214
|
* @param {boolean} [inline=true]
|
|
1348
1215
|
* @param {boolean} [inExpr=false] Whether the expression is already inside another expression
|
|
1216
|
+
* @param {boolean} [alwaysRenderCast=false] Whether to _always_ render SQL-style casts, even if `inExpr === false`
|
|
1217
|
+
* Note: This is a hack for casts() inside groupBy.
|
|
1349
1218
|
*/
|
|
1350
|
-
function renderExpr(x, env, inline = true, inExpr = false) {
|
|
1219
|
+
function renderExpr(x, env, inline = true, inExpr = false, alwaysRenderCast = false) {
|
|
1351
1220
|
// Compound expression
|
|
1352
1221
|
if (Array.isArray(x))
|
|
1353
1222
|
return beautifyExprArray(x.map(item => renderExpr(item, env, inline, inExpr)));
|
|
1354
1223
|
|
|
1355
1224
|
if (typeof x === 'object' && x !== null) {
|
|
1356
|
-
if (inExpr && x.cast && x.cast.type)
|
|
1225
|
+
if ((inExpr || alwaysRenderCast) && x.cast && x.cast.type)
|
|
1357
1226
|
return renderExplicitTypeCast(renderExprObject());
|
|
1358
1227
|
return renderExprObject();
|
|
1359
1228
|
}
|
|
@@ -1385,14 +1254,14 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1385
1254
|
case 'timestamp':
|
|
1386
1255
|
return `${x.literal}'${x.val}'`;
|
|
1387
1256
|
case 'string':
|
|
1388
|
-
return
|
|
1257
|
+
return renderString(x.val, env);
|
|
1389
1258
|
case 'object':
|
|
1390
1259
|
if (x.val === null)
|
|
1391
1260
|
return 'null';
|
|
1392
1261
|
|
|
1393
1262
|
// otherwise fall through to
|
|
1394
1263
|
default:
|
|
1395
|
-
throw new
|
|
1264
|
+
throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
|
|
1396
1265
|
}
|
|
1397
1266
|
}
|
|
1398
1267
|
// Enum symbol
|
|
@@ -1439,7 +1308,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1439
1308
|
return `${renderQuery(x, false, 'view', increaseIndent(env))}`;
|
|
1440
1309
|
}
|
|
1441
1310
|
else {
|
|
1442
|
-
throw new
|
|
1311
|
+
throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
|
|
1443
1312
|
}
|
|
1444
1313
|
}
|
|
1445
1314
|
|
|
@@ -1476,7 +1345,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1476
1345
|
else if (typeof s === 'object') {
|
|
1477
1346
|
// Sanity check
|
|
1478
1347
|
if (!s.func && !s.id)
|
|
1479
|
-
throw new
|
|
1348
|
+
throw new ModelError(`Unknown path step object: ${JSON.stringify(s)}`);
|
|
1480
1349
|
|
|
1481
1350
|
// Not really a path step but an object-like function call
|
|
1482
1351
|
if (s.func)
|
|
@@ -1496,7 +1365,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1496
1365
|
return result;
|
|
1497
1366
|
}
|
|
1498
1367
|
|
|
1499
|
-
throw new
|
|
1368
|
+
throw new ModelError(`Unknown path step: ${JSON.stringify(s)}`);
|
|
1500
1369
|
}
|
|
1501
1370
|
}
|
|
1502
1371
|
|
|
@@ -1513,14 +1382,14 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1513
1382
|
const args = node.args ? node.args : {};
|
|
1514
1383
|
// Positional arguments
|
|
1515
1384
|
if (Array.isArray(args))
|
|
1516
|
-
return args.map(arg => renderExpr(arg, env)).join(', ');
|
|
1385
|
+
return args.map(arg => renderExpr(arg, env, true, false, true)).join(', ');
|
|
1517
1386
|
|
|
1518
1387
|
// Named arguments (object/dict)
|
|
1519
1388
|
else if (typeof args === 'object')
|
|
1520
|
-
return Object.keys(args).map(key => `${quoteOrUppercaseId(key)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
|
|
1389
|
+
return Object.keys(args).map(key => `${quoteOrUppercaseId(key)} ${sep} ${renderExpr(args[key], env, true, false, true)}`).join(', ');
|
|
1521
1390
|
|
|
1522
1391
|
|
|
1523
|
-
throw new
|
|
1392
|
+
throw new ModelError(`Unknown args: ${JSON.stringify(args)}`);
|
|
1524
1393
|
}
|
|
1525
1394
|
|
|
1526
1395
|
/**
|
|
@@ -1642,66 +1511,38 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1642
1511
|
}
|
|
1643
1512
|
|
|
1644
1513
|
/**
|
|
1645
|
-
* Render a single annotation assignment '
|
|
1646
|
-
* We might see variants like 'A.B.C#foo' or even 'A.B#foo.C'.
|
|
1647
|
-
*
|
|
1514
|
+
* Render a single annotation assignment 'anno' with fully qualified name 'name' (no trailing LF).
|
|
1515
|
+
* We might see variants like 'A.B.C#foo' or even 'A.B#foo.C'. The latter needs to be quoted as
|
|
1516
|
+
* dots in the variant are not recognized by the compiler.
|
|
1648
1517
|
*
|
|
1649
|
-
* @param {any}
|
|
1518
|
+
* @param {any} anno Annotation value
|
|
1650
1519
|
* @param {string} name Annotation name, e.g. `@A.B.C#foo.C`
|
|
1651
1520
|
* @param {CdlRenderEnvironment} env
|
|
1652
|
-
* @return {string}
|
|
1521
|
+
* @return {string} Rendered annotation, possibly quoted: `@![A.B.C#foo.C]: value`
|
|
1653
1522
|
*/
|
|
1654
|
-
function renderAnnotationAssignment(
|
|
1655
|
-
|
|
1656
|
-
//
|
|
1657
|
-
const parts = name.
|
|
1658
|
-
const nameBeforeVariant = parts[
|
|
1659
|
-
const variant = parts[
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
result
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
if (variant) {
|
|
1676
|
-
// FIXME: Unfortunately, the compiler does not yet understand an inner variant with proper quoting,
|
|
1677
|
-
// i.e. like "__A"."B"#"foo"."C". As a workaround, we present the '#'-variant as a quoted part of the
|
|
1678
|
-
// previous path step, i.e as "__A"."B#foo"."C" (which yields the same result). This hack is only necessary
|
|
1679
|
-
// for inner '#'-variants, i.e. for those followed by a <nameAfterVariant>.
|
|
1680
|
-
// FIXME: Won't work for inner variants on the top-level artifact, because the USING no longer matches
|
|
1681
|
-
// for something like "__A#foo"."B"."C"
|
|
1682
|
-
if (nameAfterVariant) {
|
|
1683
|
-
const resultSteps = result.split('.');
|
|
1684
|
-
// Take all paths steps from the result (which is now essentially 'nameBeforeVariant' with USING
|
|
1685
|
-
// adaptations) except the last step
|
|
1686
|
-
result = resultSteps.slice(0, -1).join('.');
|
|
1687
|
-
// Append a combination of last path step and '#'-variant (quoted)
|
|
1688
|
-
let lastStep = resultSteps[resultSteps.length - 1];
|
|
1689
|
-
if (lastStep.includes('"')) {
|
|
1690
|
-
// Last step was already quoted - strip off the existing quotes
|
|
1691
|
-
lastStep = lastStep.slice(1, -1);
|
|
1692
|
-
}
|
|
1693
|
-
result += `.${quoteIdIfRequired(`${lastStep}#${variant}`)}`;
|
|
1694
|
-
}
|
|
1695
|
-
else {
|
|
1696
|
-
// No hack required for trailing '#'-variant
|
|
1697
|
-
result += `#${quoteIdIfRequired(variant)}`;
|
|
1698
|
-
}
|
|
1699
|
-
}
|
|
1700
|
-
// Append anything that might have come after the variant
|
|
1701
|
-
if (nameAfterVariant)
|
|
1702
|
-
result += `.${quotePathString(nameAfterVariant)}`;
|
|
1523
|
+
function renderAnnotationAssignment(anno, name, env) {
|
|
1524
|
+
name = name.substring(1);
|
|
1525
|
+
// Take the annotation assignment apart into <nameBeforeVariant>#<variantAndRest>
|
|
1526
|
+
const parts = name.split('#');
|
|
1527
|
+
const nameBeforeVariant = parts[0];
|
|
1528
|
+
const variant = parts[1];
|
|
1529
|
+
|
|
1530
|
+
// Identifier according to our grammar: /[$_a-zA-Z][$_a-zA-Z0-9]*/
|
|
1531
|
+
// We expand this pattern to also include dots after the first character.
|
|
1532
|
+
// If the annotation does not follow this pattern `ident(.@ident)*`, it must be quoted:
|
|
1533
|
+
// `@identifier@identifier` must be quoted but `@identifier.@identifier` should not.
|
|
1534
|
+
const annoRequiresQuoting = !/^[$_a-zA-Z][$_a-zA-Z0-9.]*(?:\.@[$_a-zA-Z][$_a-zA-Z0-9.]*)*$/.test(nameBeforeVariant);
|
|
1535
|
+
// Unfortunately, the compiler does not allow `.` after the first variant identifier,
|
|
1536
|
+
// even though that is the result after flattening.
|
|
1537
|
+
const variantRequiresQuoting = variant && !/^[$_a-zA-Z][$_a-zA-Z0-9]*$/.test(variant);
|
|
1538
|
+
|
|
1539
|
+
let result;
|
|
1540
|
+
if (annoRequiresQuoting || variantRequiresQuoting)
|
|
1541
|
+
result = `${env.indent}@${quote(name)}`;
|
|
1542
|
+
else
|
|
1543
|
+
result = `${env.indent}@${name}`;
|
|
1703
1544
|
|
|
1704
|
-
result += ` : ${renderAnnotationValue(
|
|
1545
|
+
result += ` : ${renderAnnotationValue(anno, env)}`;
|
|
1705
1546
|
return `${result}\n`;
|
|
1706
1547
|
}
|
|
1707
1548
|
|
|
@@ -1717,23 +1558,6 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1717
1558
|
return absName;
|
|
1718
1559
|
}
|
|
1719
1560
|
|
|
1720
|
-
/**
|
|
1721
|
-
* Render appropriate USING directives for all artifacts used by artifact 'artifactName' in 'env'.
|
|
1722
|
-
*
|
|
1723
|
-
* @param {string} artifactName
|
|
1724
|
-
* @param {CdlRenderEnvironment} env
|
|
1725
|
-
* @return {string}
|
|
1726
|
-
*/
|
|
1727
|
-
function renderUsings(artifactName, env) {
|
|
1728
|
-
const distinct = {};
|
|
1729
|
-
Object.keys(env.topLevelAliases)
|
|
1730
|
-
.forEach((name) => {
|
|
1731
|
-
distinct[`using ${env.topLevelAliases[name].quotedName} as ${env.topLevelAliases[name].quotedAlias};\n`] = '';
|
|
1732
|
-
});
|
|
1733
|
-
|
|
1734
|
-
return Object.keys(distinct).join('');
|
|
1735
|
-
}
|
|
1736
|
-
|
|
1737
1561
|
/**
|
|
1738
1562
|
* Returns a newly created default environment (which keeps track of indentation, required USING
|
|
1739
1563
|
* declarations and name prefixes.
|
|
@@ -1748,20 +1572,11 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1748
1572
|
topLevelAliases: Object.create(null),
|
|
1749
1573
|
// Current name prefix (including trailing dot if not empty)
|
|
1750
1574
|
namePrefix: '',
|
|
1575
|
+
artifactName: null,
|
|
1576
|
+
elementName: null,
|
|
1751
1577
|
};
|
|
1752
1578
|
}
|
|
1753
1579
|
|
|
1754
|
-
/**
|
|
1755
|
-
* Returns a copy of 'env' with (quoted) name prefix 'id' and a dot appended to the current name prefix
|
|
1756
|
-
*
|
|
1757
|
-
* @param {CdlRenderEnvironment} env
|
|
1758
|
-
* @param {string} id
|
|
1759
|
-
* @returns {CdlRenderEnvironment}
|
|
1760
|
-
*/
|
|
1761
|
-
function addNamePrefix(env, id) {
|
|
1762
|
-
return Object.assign({}, env, { namePrefix: `${env.namePrefix + quoteIdIfRequired(id)}.` });
|
|
1763
|
-
}
|
|
1764
|
-
|
|
1765
1580
|
/**
|
|
1766
1581
|
* Returns a copy of 'env' with increased indentation (and resetted name prefix)
|
|
1767
1582
|
*
|
|
@@ -1792,11 +1607,21 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1792
1607
|
function quoteIdIfRequired(id) {
|
|
1793
1608
|
// Quote if required for CDL
|
|
1794
1609
|
if (requiresQuotingForCdl(id))
|
|
1795
|
-
return
|
|
1610
|
+
return quote(id);
|
|
1796
1611
|
|
|
1797
1612
|
return id;
|
|
1798
1613
|
}
|
|
1799
1614
|
|
|
1615
|
+
/**
|
|
1616
|
+
* Quotes the identifier using CDL-style ![]-quotes.
|
|
1617
|
+
*
|
|
1618
|
+
* @param id
|
|
1619
|
+
* @returns {string}
|
|
1620
|
+
*/
|
|
1621
|
+
function quote(id) {
|
|
1622
|
+
return `![${id.replace(/]/g, ']]')}]`;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1800
1625
|
/**
|
|
1801
1626
|
* Returns true if 'id' requires quotes for CDL, i.e. if 'id'
|
|
1802
1627
|
* 1. starts with a digit
|
|
@@ -1837,7 +1662,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1837
1662
|
* of another entity -> Service.E and Service.E.Sub
|
|
1838
1663
|
*
|
|
1839
1664
|
* To handle such cases for hdbcds in quoted/hdbcds, we:
|
|
1840
|
-
* - Find the part of the name that is no longer prefix (context/service
|
|
1665
|
+
* - Find the part of the name that is no longer prefix (context/service)
|
|
1841
1666
|
* - For Service.E -> E, for Service.E.Sub -> E.Sub
|
|
1842
1667
|
* - Replace all dots in this "real name" with underscores
|
|
1843
1668
|
* - Join with the env prefix
|
|
@@ -1864,14 +1689,14 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1864
1689
|
}
|
|
1865
1690
|
|
|
1866
1691
|
/**
|
|
1867
|
-
* Get the part that is really the name of this artifact and not just prefix caused by a context/service
|
|
1692
|
+
* Get the part that is really the name of this artifact and not just prefix caused by a context/service
|
|
1868
1693
|
*
|
|
1869
1694
|
* @param {String} artifactName Artifact name to use
|
|
1870
1695
|
* @returns {String} non-prefix part of the artifact name
|
|
1871
1696
|
*/
|
|
1872
1697
|
function getRealName(artifactName) {
|
|
1873
1698
|
const parts = artifactName.split('.');
|
|
1874
|
-
//
|
|
1699
|
+
// Length of 1 -> There can be no prefix
|
|
1875
1700
|
if (parts.length === 1)
|
|
1876
1701
|
return artifactName;
|
|
1877
1702
|
|
|
@@ -1885,7 +1710,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1885
1710
|
|
|
1886
1711
|
|
|
1887
1712
|
const art = csn.definitions[seen];
|
|
1888
|
-
if (!art ||
|
|
1713
|
+
if (!art || (art.kind !== 'service' && art.kind !== 'context')) {
|
|
1889
1714
|
// We found a case where the prefix ended
|
|
1890
1715
|
// Return everything following
|
|
1891
1716
|
return parts.slice(i).join('.');
|
|
@@ -1897,6 +1722,80 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1897
1722
|
}
|
|
1898
1723
|
}
|
|
1899
1724
|
|
|
1725
|
+
/**
|
|
1726
|
+
* Render the given string. Uses back-tick strings.
|
|
1727
|
+
* env is used for indentation of three-back-tick strings.
|
|
1728
|
+
*
|
|
1729
|
+
* @param str
|
|
1730
|
+
* @param env
|
|
1731
|
+
* @returns {string}
|
|
1732
|
+
*/
|
|
1733
|
+
function renderString(str, env) {
|
|
1734
|
+
if (isSimpleString(str))
|
|
1735
|
+
return `'${str.replace(/'/g, '\'\'')}'`;
|
|
1736
|
+
|
|
1737
|
+
// We try to work similar to how JavaScript implements JSON.stringify.
|
|
1738
|
+
// JSON.stringify() also checks for unpaired unicode surrogates (see §25.5.2.2,
|
|
1739
|
+
// <https://tc39.es/ecma262/#sec-quotejsonstring>).
|
|
1740
|
+
str = escapeString(str, {
|
|
1741
|
+
$: '\\$',
|
|
1742
|
+
'`': '\\`',
|
|
1743
|
+
'\\': '\\\\',
|
|
1744
|
+
// Replace commonly known escape sequences for control characters
|
|
1745
|
+
// See lib/language/multiLineStringParser.js
|
|
1746
|
+
'\f': '\\f',
|
|
1747
|
+
'\v': '\\v',
|
|
1748
|
+
'\t': '\\t',
|
|
1749
|
+
'\b': '\\b',
|
|
1750
|
+
// If CR, LS, or PS appear, they have been encoded explicitly. If we don't escape
|
|
1751
|
+
// them, a recompilation may yield different results because of newline normalization.
|
|
1752
|
+
'\r': '\\r',
|
|
1753
|
+
'\u{2028}': '\\u{2028}',
|
|
1754
|
+
'\u{2029}': '\\u{2029}',
|
|
1755
|
+
// Don't encode LF
|
|
1756
|
+
'\n': '\n',
|
|
1757
|
+
// JSON.stringify() is not required to escape all control characters, but if used, files may
|
|
1758
|
+
// be interpreted as binary. Therefore, we replace them.
|
|
1759
|
+
// We exclude LF from this list (\n). Characters with "nice" escapes have been replaced above.
|
|
1760
|
+
control: hexEscape,
|
|
1761
|
+
unpairedSurrogate: hexEscape,
|
|
1762
|
+
});
|
|
1763
|
+
|
|
1764
|
+
// Note: String is normalized, only \n is the line separator.
|
|
1765
|
+
const lines = str.split('\n');
|
|
1766
|
+
// We don't know whether a text block was used or not. But if there
|
|
1767
|
+
// are more than three lines, text blocks with indentation "look nicer".
|
|
1768
|
+
// This value was chosen by personal taste.
|
|
1769
|
+
if (lines.length > 3) {
|
|
1770
|
+
str = lines.join(`\n${env.indent}`);
|
|
1771
|
+
return `\`\`\`\n${env.indent}${str}\n${env.indent}\`\`\``;
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
return `\`${str}\``;
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
/** @param {number} codePoint */
|
|
1778
|
+
function hexEscape(codePoint) {
|
|
1779
|
+
const hex = codePoint.toString(16);
|
|
1780
|
+
return `\\u{${hex}}`;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
/**
|
|
1784
|
+
* Returns true if the given string can be represented by using single quotes.
|
|
1785
|
+
* @param {string} str
|
|
1786
|
+
*/
|
|
1787
|
+
function isSimpleString(str) {
|
|
1788
|
+
// A single-line string allows everything except certain line separators/breaks.
|
|
1789
|
+
// See ANTLR grammar for specifics.
|
|
1790
|
+
// Furthermore, if control characters are used, we escape them,
|
|
1791
|
+
// as these are explicitly mentioned in the JSON spec (§9):
|
|
1792
|
+
// <https://www.ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf>
|
|
1793
|
+
// On top, because (invalid) surrogate pairs need to be handled, we check for them as well.
|
|
1794
|
+
// v3: Not a simple string if ' (\u0027) is in string.
|
|
1795
|
+
return str === '' || (/^[^\u{0000}-\u{001F}\u2028\u2029]+$/u.test(str) &&
|
|
1796
|
+
!hasUnpairedUnicodeSurrogate(str));
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1900
1799
|
/**
|
|
1901
1800
|
* @typedef CdlRenderEnvironment Rendering environment used throughout the render process.
|
|
1902
1801
|
*
|