@sap/cds-compiler 2.12.0 → 2.13.6
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 +110 -15
- package/bin/cdsc.js +13 -13
- package/bin/cdsse.js +2 -2
- package/doc/CHANGELOG_BETA.md +13 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +28 -63
- package/lib/api/options.js +3 -3
- 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 +25 -4
- package/lib/base/messages.js +16 -26
- package/lib/base/model.js +2 -63
- package/lib/base/optionProcessorHelper.js +158 -123
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +27 -26
- package/lib/checks/types.js +1 -1
- package/lib/checks/validator.js +4 -7
- package/lib/compiler/assert-consistency.js +5 -3
- package/lib/compiler/builtins.js +8 -6
- package/lib/compiler/checks.js +14 -3
- 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 +32 -13
- 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 +111 -46
- package/lib/compiler/resolve.js +1433 -0
- package/lib/compiler/shared.js +64 -37
- package/lib/compiler/tweak-assocs.js +529 -0
- package/lib/compiler/utils.js +197 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +5 -9
- package/lib/edm/annotations/preprocessAnnotations.js +2 -2
- package/lib/edm/csn2edm.js +9 -8
- package/lib/edm/edm.js +11 -12
- package/lib/edm/edmPreprocessor.js +137 -73
- package/lib/edm/edmUtils.js +116 -22
- package/lib/gen/Dictionary.json +10 -3
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +9 -1
- package/lib/gen/language.tokens +86 -83
- package/lib/gen/languageLexer.interp +10 -1
- package/lib/gen/languageLexer.js +860 -833
- package/lib/gen/languageLexer.tokens +78 -75
- package/lib/gen/languageParser.js +5282 -4265
- package/lib/json/from-csn.js +12 -1
- package/lib/json/to-csn.js +126 -66
- package/lib/language/docCommentParser.js +2 -2
- package/lib/language/genericAntlrParser.js +76 -3
- package/lib/language/language.g4 +297 -130
- package/lib/language/multiLineStringParser.js +5 -5
- package/lib/main.d.ts +468 -59
- package/lib/main.js +35 -9
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +225 -156
- package/lib/model/csnUtils.js +192 -223
- package/lib/model/enrichCsn.js +70 -29
- 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 +5 -4
- package/lib/render/manageConstraints.js +35 -32
- package/lib/render/toCdl.js +73 -288
- package/lib/render/toHdbcds.js +25 -23
- package/lib/render/toSql.js +98 -41
- package/lib/render/utils/common.js +5 -10
- package/lib/render/utils/sql.js +4 -3
- 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/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 +103 -305
- package/lib/transform/db/cdsPersistence.js +2 -2
- package/lib/transform/db/constraints.js +55 -52
- package/lib/transform/db/expansion.js +46 -24
- package/lib/transform/db/flattening.js +553 -102
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/transformExists.js +59 -6
- 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} +6 -5
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +67 -183
- package/lib/transform/forOdataNew.js +17 -171
- package/lib/transform/localized.js +34 -19
- package/lib/transform/odata/generateForeignKeyElements.js +1 -1
- 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 +36 -22
- package/lib/transform/translateAssocsToJoins.js +2 -19
- 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/objectUtils.js +30 -0
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/lib/compiler/definer.js +0 -2361
- package/lib/compiler/resolver.js +0 -3079
- package/lib/transform/universalCsnEnricher.js +0 -237
package/lib/render/toCdl.js
CHANGED
|
@@ -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
|
|
@@ -50,7 +52,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
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
|
});
|
|
@@ -132,7 +134,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
132
134
|
if (csn.extensions) {
|
|
133
135
|
const env = createEnv();
|
|
134
136
|
const sourceStr = renderUnappliedExtensions(csn.extensions, env);
|
|
135
|
-
cdlResult.unappliedExtensions =
|
|
137
|
+
cdlResult.unappliedExtensions = sourceStr;
|
|
136
138
|
}
|
|
137
139
|
|
|
138
140
|
timetrace.stop();
|
|
@@ -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,82 +383,8 @@ function toCdsSourceCsn(csn, options) {
|
|
|
446
383
|
}
|
|
447
384
|
|
|
448
385
|
result += `${env.indent}}`;
|
|
449
|
-
result += `${renderActionsAndFunctions(art, env)
|
|
450
|
-
return result;
|
|
451
|
-
}
|
|
452
|
-
|
|
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}}`;
|
|
386
|
+
result += `${renderActionsAndFunctions(art, env)};\n`;
|
|
513
387
|
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
388
|
}
|
|
526
389
|
|
|
527
390
|
/**
|
|
@@ -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)}`;
|
|
@@ -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
|
|
|
@@ -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
|
}
|
|
@@ -1392,7 +1261,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
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
|
/**
|
|
@@ -1689,23 +1558,6 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1689
1558
|
return absName;
|
|
1690
1559
|
}
|
|
1691
1560
|
|
|
1692
|
-
/**
|
|
1693
|
-
* Render appropriate USING directives for all artifacts used by artifact 'artifactName' in 'env'.
|
|
1694
|
-
*
|
|
1695
|
-
* @param {string} artifactName
|
|
1696
|
-
* @param {CdlRenderEnvironment} env
|
|
1697
|
-
* @return {string}
|
|
1698
|
-
*/
|
|
1699
|
-
function renderUsings(artifactName, env) {
|
|
1700
|
-
const distinct = {};
|
|
1701
|
-
Object.keys(env.topLevelAliases)
|
|
1702
|
-
.forEach((name) => {
|
|
1703
|
-
distinct[`using ${env.topLevelAliases[name].quotedName} as ${env.topLevelAliases[name].quotedAlias};\n`] = '';
|
|
1704
|
-
});
|
|
1705
|
-
|
|
1706
|
-
return Object.keys(distinct).join('');
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
1561
|
/**
|
|
1710
1562
|
* Returns a newly created default environment (which keeps track of indentation, required USING
|
|
1711
1563
|
* declarations and name prefixes.
|
|
@@ -1725,17 +1577,6 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1725
1577
|
};
|
|
1726
1578
|
}
|
|
1727
1579
|
|
|
1728
|
-
/**
|
|
1729
|
-
* Returns a copy of 'env' with (quoted) name prefix 'id' and a dot appended to the current name prefix
|
|
1730
|
-
*
|
|
1731
|
-
* @param {CdlRenderEnvironment} env
|
|
1732
|
-
* @param {string} id
|
|
1733
|
-
* @returns {CdlRenderEnvironment}
|
|
1734
|
-
*/
|
|
1735
|
-
function addNamePrefix(env, id) {
|
|
1736
|
-
return Object.assign({}, env, { namePrefix: `${env.namePrefix + quoteIdIfRequired(id)}.` });
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
1580
|
/**
|
|
1740
1581
|
* Returns a copy of 'env' with increased indentation (and resetted name prefix)
|
|
1741
1582
|
*
|
|
@@ -1821,7 +1662,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1821
1662
|
* of another entity -> Service.E and Service.E.Sub
|
|
1822
1663
|
*
|
|
1823
1664
|
* To handle such cases for hdbcds in quoted/hdbcds, we:
|
|
1824
|
-
* - 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)
|
|
1825
1666
|
* - For Service.E -> E, for Service.E.Sub -> E.Sub
|
|
1826
1667
|
* - Replace all dots in this "real name" with underscores
|
|
1827
1668
|
* - Join with the env prefix
|
|
@@ -1848,14 +1689,14 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1848
1689
|
}
|
|
1849
1690
|
|
|
1850
1691
|
/**
|
|
1851
|
-
* 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
|
|
1852
1693
|
*
|
|
1853
1694
|
* @param {String} artifactName Artifact name to use
|
|
1854
1695
|
* @returns {String} non-prefix part of the artifact name
|
|
1855
1696
|
*/
|
|
1856
1697
|
function getRealName(artifactName) {
|
|
1857
1698
|
const parts = artifactName.split('.');
|
|
1858
|
-
//
|
|
1699
|
+
// Length of 1 -> There can be no prefix
|
|
1859
1700
|
if (parts.length === 1)
|
|
1860
1701
|
return artifactName;
|
|
1861
1702
|
|
|
@@ -1869,7 +1710,7 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1869
1710
|
|
|
1870
1711
|
|
|
1871
1712
|
const art = csn.definitions[seen];
|
|
1872
|
-
if (!art ||
|
|
1713
|
+
if (!art || (art.kind !== 'service' && art.kind !== 'context')) {
|
|
1873
1714
|
// We found a case where the prefix ended
|
|
1874
1715
|
// Return everything following
|
|
1875
1716
|
return parts.slice(i).join('.');
|
|
@@ -1881,13 +1722,9 @@ function toCdsSourceCsn(csn, options) {
|
|
|
1881
1722
|
}
|
|
1882
1723
|
}
|
|
1883
1724
|
|
|
1884
|
-
const controlCharacters = /[\u{0000}-\u{0009}\u{000B}\u{001F}]/u;
|
|
1885
|
-
const highSurrogate = /[\u{D800}-\u{DBFF}]/u;
|
|
1886
|
-
const lowSurrogate = /[\u{DC00}-\u{DFFF}]/u;
|
|
1887
|
-
|
|
1888
1725
|
/**
|
|
1889
1726
|
* Render the given string. Uses back-tick strings.
|
|
1890
|
-
* env is used for indentation
|
|
1727
|
+
* env is used for indentation of three-back-tick strings.
|
|
1891
1728
|
*
|
|
1892
1729
|
* @param str
|
|
1893
1730
|
* @param env
|
|
@@ -1897,90 +1734,33 @@ function renderString(str, env) {
|
|
|
1897
1734
|
if (isSimpleString(str))
|
|
1898
1735
|
return `'${str.replace(/'/g, '\'\'')}'`;
|
|
1899
1736
|
|
|
1900
|
-
const output = [];
|
|
1901
|
-
|
|
1902
1737
|
// We try to work similar to how JavaScript implements JSON.stringify.
|
|
1903
|
-
// JSON.stringify
|
|
1904
|
-
// <https://tc39.es/ecma262/#sec-quotejsonstring>)
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
output.push('\\t');
|
|
1929
|
-
break;
|
|
1930
|
-
case '\b':
|
|
1931
|
-
output.push('\\b');
|
|
1932
|
-
break;
|
|
1933
|
-
// If CR, LS, or PS appear, they have been encoded explicitly. If we don't escape
|
|
1934
|
-
// them, a recompilation may yield different results because of newline normalization.
|
|
1935
|
-
case '\r':
|
|
1936
|
-
output.push('\\r');
|
|
1937
|
-
break;
|
|
1938
|
-
case '\u{2028}':
|
|
1939
|
-
output.push('\\u{2028}');
|
|
1940
|
-
break;
|
|
1941
|
-
case '\u{2029}':
|
|
1942
|
-
output.push('\\u{2029}');
|
|
1943
|
-
break;
|
|
1944
|
-
default: {
|
|
1945
|
-
// Control Characters: C0
|
|
1946
|
-
// See <https://en.wikipedia.org/wiki/C0_and_C1_control_codes#Basic_ASCII_control_codes>
|
|
1947
|
-
//
|
|
1948
|
-
// JSON.stringify is not required to escape all control characters, but if used, files may
|
|
1949
|
-
// be interpreted as binary. Therefore, we replace them.
|
|
1950
|
-
//
|
|
1951
|
-
// We exclude LF from this list (\u000A). Characters with "nice" escapes have been replaced above.
|
|
1952
|
-
if (controlCharacters.test(char)) {
|
|
1953
|
-
const hex = char.codePointAt(0).toString(16);
|
|
1954
|
-
output.push(`\\u{${hex}}`);
|
|
1955
|
-
break;
|
|
1956
|
-
}
|
|
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
|
+
});
|
|
1957
1763
|
|
|
1958
|
-
// Unicode Surrogates
|
|
1959
|
-
// These characters appear in _pairs_. A high surrogate must be followed by a low surrogate.
|
|
1960
|
-
// If this is not the case, either needs to be encoded. This is also done by JSON.
|
|
1961
|
-
// See also <https://docs.microsoft.com/en-us/globalization/encoding/surrogate-pairs>
|
|
1962
|
-
if (highSurrogate.test(char)) {
|
|
1963
|
-
if (i + 1 >= str.length || !lowSurrogate.test(str[i + 1])) {
|
|
1964
|
-
const hex = char.codePointAt(0).toString(16);
|
|
1965
|
-
output.push(`\\u{${hex}}`);
|
|
1966
|
-
}
|
|
1967
|
-
else {
|
|
1968
|
-
output.push(char);
|
|
1969
|
-
++i;
|
|
1970
|
-
output.push(str[i]);
|
|
1971
|
-
}
|
|
1972
|
-
}
|
|
1973
|
-
else if (lowSurrogate.test(char)) {
|
|
1974
|
-
const hex = char.codePointAt(0).toString(16);
|
|
1975
|
-
output.push(`\\u{${hex}}`);
|
|
1976
|
-
}
|
|
1977
|
-
else {
|
|
1978
|
-
output.push(char);
|
|
1979
|
-
}
|
|
1980
|
-
}
|
|
1981
|
-
}
|
|
1982
|
-
}
|
|
1983
|
-
str = output.join('');
|
|
1984
1764
|
// Note: String is normalized, only \n is the line separator.
|
|
1985
1765
|
const lines = str.split('\n');
|
|
1986
1766
|
// We don't know whether a text block was used or not. But if there
|
|
@@ -1994,6 +1774,12 @@ function renderString(str, env) {
|
|
|
1994
1774
|
return `\`${str}\``;
|
|
1995
1775
|
}
|
|
1996
1776
|
|
|
1777
|
+
/** @param {number} codePoint */
|
|
1778
|
+
function hexEscape(codePoint) {
|
|
1779
|
+
const hex = codePoint.toString(16);
|
|
1780
|
+
return `\\u{${hex}}`;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1997
1783
|
/**
|
|
1998
1784
|
* Returns true if the given string can be represented by using single quotes.
|
|
1999
1785
|
* @param {string} str
|
|
@@ -2001,14 +1787,13 @@ function renderString(str, env) {
|
|
|
2001
1787
|
function isSimpleString(str) {
|
|
2002
1788
|
// A single-line string allows everything except certain line separators/breaks.
|
|
2003
1789
|
// See ANTLR grammar for specifics.
|
|
2004
|
-
// Furthermore, if control characters
|
|
1790
|
+
// Furthermore, if control characters are used, we escape them,
|
|
2005
1791
|
// as these are explicitly mentioned in the JSON spec (§9):
|
|
2006
1792
|
// <https://www.ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf>
|
|
2007
|
-
// On top,
|
|
1793
|
+
// On top, because (invalid) surrogate pairs need to be handled, we check for them as well.
|
|
2008
1794
|
// v3: Not a simple string if ' (\u0027) is in string.
|
|
2009
1795
|
return str === '' || (/^[^\u{0000}-\u{001F}\u2028\u2029]+$/u.test(str) &&
|
|
2010
|
-
!
|
|
2011
|
-
!lowSurrogate.test(str));
|
|
1796
|
+
!hasUnpairedUnicodeSurrogate(str));
|
|
2012
1797
|
}
|
|
2013
1798
|
|
|
2014
1799
|
/**
|