@sap/cds-compiler 3.4.0 → 3.4.4
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 +21 -1
- package/bin/cdsc.js +3 -4
- package/bin/cdshi.js +19 -6
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/lib/api/main.js +6 -3
- package/lib/base/message-registry.js +61 -38
- package/lib/base/messages.js +7 -3
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/compiler/.eslintrc.json +4 -1
- package/lib/compiler/assert-consistency.js +8 -6
- package/lib/compiler/builtins.js +13 -13
- package/lib/compiler/checks.js +50 -33
- package/lib/compiler/define.js +9 -6
- package/lib/compiler/extend.js +83 -55
- package/lib/compiler/finalize-parse-cdl.js +3 -3
- package/lib/compiler/populate.js +16 -5
- package/lib/compiler/propagator.js +22 -29
- package/lib/compiler/resolve.js +2 -15
- package/lib/compiler/shared.js +4 -4
- package/lib/compiler/utils.js +14 -0
- package/lib/edm/annotations/genericTranslation.js +68 -56
- package/lib/edm/csn2edm.js +214 -174
- package/lib/edm/edmAnnoPreprocessor.js +5 -5
- package/lib/edm/edmInboundChecks.js +2 -2
- package/lib/edm/edmPreprocessor.js +1 -1
- package/lib/edm/edmUtils.js +3 -3
- package/lib/gen/Dictionary.json +176 -8
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +2 -1
- package/lib/gen/languageParser.js +4776 -4513
- package/lib/json/from-csn.js +21 -16
- package/lib/json/to-csn.js +37 -41
- package/lib/language/.eslintrc.json +4 -1
- package/lib/language/antlrParser.js +5 -2
- package/lib/language/docCommentParser.js +6 -6
- package/lib/language/errorStrategy.js +43 -23
- package/lib/language/genericAntlrParser.js +54 -95
- package/lib/language/language.g4 +92 -66
- package/lib/language/multiLineStringParser.js +2 -2
- package/lib/language/textUtils.js +2 -2
- package/lib/model/csnRefs.js +5 -0
- package/lib/modelCompare/compare.js +2 -2
- package/lib/modelCompare/utils/.eslintrc.json +22 -0
- package/lib/modelCompare/utils/filter.js +99 -0
- package/lib/render/.eslintrc.json +1 -0
- package/lib/render/toCdl.js +96 -127
- package/lib/render/toHdbcds.js +38 -35
- package/lib/render/toSql.js +75 -161
- package/lib/render/utils/common.js +133 -83
- package/lib/render/utils/delta.js +227 -0
- package/lib/transform/db/.eslintrc.json +2 -0
- package/lib/transform/draft/.eslintrc.json +1 -35
- package/lib/transform/forOdataNew.js +33 -22
- package/lib/transform/forRelationalDB.js +1 -1
- package/lib/transform/localized.js +9 -8
- package/lib/transform/odata/typesExposure.js +26 -4
- package/lib/transform/transformUtilsNew.js +15 -8
- package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
- package/package.json +2 -3
- package/lib/modelCompare/filter.js +0 -83
package/lib/compiler/checks.js
CHANGED
|
@@ -23,9 +23,10 @@ const { pathName } = require('./utils');
|
|
|
23
23
|
|
|
24
24
|
function check( model ) { // = XSN
|
|
25
25
|
const {
|
|
26
|
-
error, warning, message,
|
|
26
|
+
error, warning, message, info,
|
|
27
27
|
} = model.$messageFunctions;
|
|
28
28
|
forEachDefinition( model, checkArtifact );
|
|
29
|
+
forEachGeneric( model, 'vocabularies', checkAnnotationDefinition );
|
|
29
30
|
checkSapCommonLocale( model, model.$messageFunctions );
|
|
30
31
|
return;
|
|
31
32
|
|
|
@@ -36,6 +37,11 @@ function check( model ) { // = XSN
|
|
|
36
37
|
art.$queries.forEach( checkQuery );
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
function checkAnnotationDefinition( art ) {
|
|
41
|
+
checkEnumType( art );
|
|
42
|
+
// TODO: Should we check elements similar to definition-elements as well?
|
|
43
|
+
}
|
|
44
|
+
|
|
39
45
|
function checkGenericConstruct( art ) {
|
|
40
46
|
checkName( art );
|
|
41
47
|
if (model.vocabularies) {
|
|
@@ -80,14 +86,15 @@ function check( model ) { // = XSN
|
|
|
80
86
|
}
|
|
81
87
|
}
|
|
82
88
|
|
|
83
|
-
function checkLocalizedElement(elem) {
|
|
89
|
+
function checkLocalizedElement( elem ) {
|
|
84
90
|
// if it is directly a localized element
|
|
85
91
|
if (elem.localized && elem.localized.val) {
|
|
86
92
|
const type = elem._effectiveType;
|
|
87
93
|
// See discussion issue #6520: should we allow all scalar types?
|
|
88
94
|
if (!type || !type.builtin || type.category !== 'string') {
|
|
89
|
-
|
|
90
|
-
|
|
95
|
+
info('ref-expecting-localized-string', [ elem.type?.location, elem ],
|
|
96
|
+
{ keyword: 'localized' },
|
|
97
|
+
'Expecting a string type in combination with keyword $(KEYWORD)');
|
|
91
98
|
}
|
|
92
99
|
}
|
|
93
100
|
// "key" keyword at localized element in SELECT list.
|
|
@@ -151,7 +158,7 @@ function check( model ) { // = XSN
|
|
|
151
158
|
*
|
|
152
159
|
* @param {XSN.Artifact} enumNode
|
|
153
160
|
*/
|
|
154
|
-
function checkEnum(enumNode) {
|
|
161
|
+
function checkEnum( enumNode ) {
|
|
155
162
|
if (!enumNode.value)
|
|
156
163
|
return;
|
|
157
164
|
|
|
@@ -167,7 +174,7 @@ function check( model ) { // = XSN
|
|
|
167
174
|
}
|
|
168
175
|
}
|
|
169
176
|
|
|
170
|
-
function checkEnumType(enumNode) {
|
|
177
|
+
function checkEnumType( enumNode ) {
|
|
171
178
|
// Either the type is an enum or an arrayed enum. We are only interested in
|
|
172
179
|
// the enum and don't care whether the enum is arrayed.
|
|
173
180
|
enumNode = enumNode.enum ? enumNode : enumNode.items;
|
|
@@ -222,7 +229,7 @@ function check( model ) { // = XSN
|
|
|
222
229
|
*
|
|
223
230
|
* @param {XSN.Definition} enumNode
|
|
224
231
|
*/
|
|
225
|
-
function checkEnumValue(enumNode) {
|
|
232
|
+
function checkEnumValue( enumNode ) {
|
|
226
233
|
const type = enumNode.type && enumNode.type._artifact &&
|
|
227
234
|
enumNode.type._artifact._effectiveType;
|
|
228
235
|
if (!enumNode.enum || !type || !type.builtin)
|
|
@@ -293,7 +300,7 @@ function check( model ) { // = XSN
|
|
|
293
300
|
*
|
|
294
301
|
* @param {XSN.Artifact} element
|
|
295
302
|
*/
|
|
296
|
-
function checkLocalizedSubElement(element) {
|
|
303
|
+
function checkLocalizedSubElement( element ) {
|
|
297
304
|
if (element._parent.kind !== 'element')
|
|
298
305
|
return;
|
|
299
306
|
|
|
@@ -310,7 +317,7 @@ function check( model ) { // = XSN
|
|
|
310
317
|
return;
|
|
311
318
|
|
|
312
319
|
// TODO: Recursive check
|
|
313
|
-
function isTypeLocalized(type) {
|
|
320
|
+
function isTypeLocalized( type ) {
|
|
314
321
|
return (type && type.localized && type.localized.val);
|
|
315
322
|
}
|
|
316
323
|
}
|
|
@@ -321,7 +328,7 @@ function check( model ) { // = XSN
|
|
|
321
328
|
*
|
|
322
329
|
* @param {any} element Element to check recursively
|
|
323
330
|
*/
|
|
324
|
-
function checkForUnmanagedAssociations(element, keyObj) {
|
|
331
|
+
function checkForUnmanagedAssociations( element, keyObj ) {
|
|
325
332
|
if (element.targetAspect) {
|
|
326
333
|
// TODO: bad location / message
|
|
327
334
|
message('composition-as-key', [ keyObj.location, element ], {},
|
|
@@ -342,7 +349,7 @@ function check( model ) { // = XSN
|
|
|
342
349
|
|
|
343
350
|
// Check that min and max cardinalities of 'elem' in 'art' have legal values
|
|
344
351
|
// TODO: move to define.js or parsers
|
|
345
|
-
function checkCardinality(elem) {
|
|
352
|
+
function checkCardinality( elem ) {
|
|
346
353
|
if (!elem.cardinality)
|
|
347
354
|
return;
|
|
348
355
|
|
|
@@ -444,7 +451,7 @@ function check( model ) { // = XSN
|
|
|
444
451
|
|
|
445
452
|
// Traverses 'node' recursively and applies 'checkExpression' to all expressions
|
|
446
453
|
// found within paths (e.g. filters, parameters, ...)
|
|
447
|
-
function checkExpressionsInPaths(node) {
|
|
454
|
+
function checkExpressionsInPaths( node ) {
|
|
448
455
|
foreachPath(node, (path) => {
|
|
449
456
|
for (const pathStep of path) {
|
|
450
457
|
if (pathStep.where)
|
|
@@ -458,7 +465,7 @@ function check( model ) { // = XSN
|
|
|
458
465
|
});
|
|
459
466
|
}
|
|
460
467
|
|
|
461
|
-
function checkAssociation(elem) {
|
|
468
|
+
function checkAssociation( elem ) {
|
|
462
469
|
// TODO: yes, a check similar to this could make it into the compiler)
|
|
463
470
|
// when virtual element is part of association
|
|
464
471
|
if (elem.foreignKeys) {
|
|
@@ -474,19 +481,19 @@ function check( model ) { // = XSN
|
|
|
474
481
|
checkAssociationCondition(elem, elem.on);
|
|
475
482
|
}
|
|
476
483
|
|
|
477
|
-
function checkAssociationCondition(elem, onCond) {
|
|
484
|
+
function checkAssociationCondition( elem, onCond ) {
|
|
478
485
|
if (Array.isArray(onCond)) // condition in brackets results an array
|
|
479
486
|
onCond.forEach(Cond => checkAssociationCondition(elem, Cond));
|
|
480
487
|
else
|
|
481
488
|
checkAssociationConditionArgs(elem, onCond.args, onCond.op);
|
|
482
489
|
}
|
|
483
490
|
|
|
484
|
-
function checkAssociationConditionArgs(elem, args, op) {
|
|
491
|
+
function checkAssociationConditionArgs( elem, args, op ) {
|
|
485
492
|
if (args)
|
|
486
493
|
args.forEach(Arg => checkAssociationOnCondArg(elem, Arg, op));
|
|
487
494
|
}
|
|
488
495
|
|
|
489
|
-
function checkAssociationOnCondArg(elem, arg, op) {
|
|
496
|
+
function checkAssociationOnCondArg( elem, arg, op ) {
|
|
490
497
|
if (Array.isArray(arg)) {
|
|
491
498
|
arg.forEach(Arg => checkAssociationOnCondArg(elem, Arg, op));
|
|
492
499
|
}
|
|
@@ -503,7 +510,7 @@ function check( model ) { // = XSN
|
|
|
503
510
|
// integration into name resolution - did the first step.
|
|
504
511
|
// It is also incomplete, as associations in structures are not checked.
|
|
505
512
|
// Additionally, `$self.assoc` references are also not found.
|
|
506
|
-
function singleCheckUnmanagedAssocCondArgumentNoFollowUnmanagedAssoc(elem, arg, op) {
|
|
513
|
+
function singleCheckUnmanagedAssocCondArgumentNoFollowUnmanagedAssoc( elem, arg, op ) {
|
|
507
514
|
if (!arg.path)
|
|
508
515
|
return;
|
|
509
516
|
const path0 = arg.path[0];
|
|
@@ -545,7 +552,7 @@ function check( model ) { // = XSN
|
|
|
545
552
|
*
|
|
546
553
|
* @param {XSN.Artifact} artifact
|
|
547
554
|
*/
|
|
548
|
-
function checkTypeStructure(artifact) {
|
|
555
|
+
function checkTypeStructure( artifact ) {
|
|
549
556
|
// Just a basic check. We do not check that the inner structure of `items`
|
|
550
557
|
// is the same as the type but only that all are arrayed or structured.
|
|
551
558
|
if (artifact.type && artifact.type._artifact) {
|
|
@@ -575,7 +582,7 @@ function check( model ) { // = XSN
|
|
|
575
582
|
* @param {Boolean} allowAssocTail
|
|
576
583
|
* @returns {void}
|
|
577
584
|
*/
|
|
578
|
-
function checkExpression(xpr, allowAssocTail = false) {
|
|
585
|
+
function checkExpression( xpr, allowAssocTail = false ) {
|
|
579
586
|
// Since the checks for tree-like and token-stream expressions differ,
|
|
580
587
|
// check here what kind of expression we are looking at
|
|
581
588
|
if (xpr.op && xpr.op.val === 'xpr')
|
|
@@ -590,7 +597,7 @@ function check( model ) { // = XSN
|
|
|
590
597
|
* @param {any} arg Argument to check (part of an expression)
|
|
591
598
|
* @returns {Boolean}
|
|
592
599
|
*/
|
|
593
|
-
function isVirtualElement(arg) {
|
|
600
|
+
function isVirtualElement( arg ) {
|
|
594
601
|
return arg.path &&
|
|
595
602
|
arg._artifact && arg._artifact.virtual && arg._artifact.virtual.val === true &&
|
|
596
603
|
arg._artifact.kind && arg._artifact.kind === 'element';
|
|
@@ -602,7 +609,7 @@ function check( model ) { // = XSN
|
|
|
602
609
|
* @param {any} xpr The expression to check
|
|
603
610
|
* @returns {void}
|
|
604
611
|
*/
|
|
605
|
-
function checkTokenStreamExpression(xpr, allowAssocTail) {
|
|
612
|
+
function checkTokenStreamExpression( xpr, allowAssocTail ) {
|
|
606
613
|
const args = Array.isArray(xpr.args) ? xpr.args : Object.values(xpr.args || {});
|
|
607
614
|
// Check for illegal argument usage within the expression
|
|
608
615
|
for (const arg of args) {
|
|
@@ -621,7 +628,7 @@ function check( model ) { // = XSN
|
|
|
621
628
|
* @param {any} xpr The expression to check
|
|
622
629
|
* @returns {void}
|
|
623
630
|
*/
|
|
624
|
-
function checkTreeLikeExpression(xpr, allowAssocTail) {
|
|
631
|
+
function checkTreeLikeExpression( xpr, allowAssocTail ) {
|
|
625
632
|
// No further checks regarding associations and $self required if this is a
|
|
626
633
|
// backlink-like expression (a comparison of $self with an assoc)
|
|
627
634
|
if (isBinaryDollarSelfComparisonWithAssoc(xpr))
|
|
@@ -655,7 +662,7 @@ function check( model ) { // = XSN
|
|
|
655
662
|
}
|
|
656
663
|
}
|
|
657
664
|
// Return true if 'arg' is an expression argument of type association or composition
|
|
658
|
-
function isAssociationOperand(arg) {
|
|
665
|
+
function isAssociationOperand( arg ) {
|
|
659
666
|
if (!arg.path) {
|
|
660
667
|
// Not a path, hence not an association (literal, expression, function, whatever ...)
|
|
661
668
|
return false;
|
|
@@ -666,7 +673,7 @@ function check( model ) { // = XSN
|
|
|
666
673
|
}
|
|
667
674
|
|
|
668
675
|
// Return true if 'arg' is an expression argument denoting "$self" || "$projection"
|
|
669
|
-
function isDollarSelfOrProjectionOperand(arg) {
|
|
676
|
+
function isDollarSelfOrProjectionOperand( arg ) {
|
|
670
677
|
return arg.path && arg.path.length === 1 &&
|
|
671
678
|
(arg.path[0].id === '$self' || arg.path[0].id === '$projection');
|
|
672
679
|
}
|
|
@@ -677,7 +684,7 @@ function check( model ) { // = XSN
|
|
|
677
684
|
* @param {any} xpr The expression to check
|
|
678
685
|
* @returns {Boolean}
|
|
679
686
|
*/
|
|
680
|
-
function isBinaryDollarSelfComparisonWithAssoc(xpr) {
|
|
687
|
+
function isBinaryDollarSelfComparisonWithAssoc( xpr ) {
|
|
681
688
|
// Must be an expression with arguments
|
|
682
689
|
if (!xpr.op || !xpr.args)
|
|
683
690
|
return false;
|
|
@@ -735,7 +742,7 @@ function check( model ) { // = XSN
|
|
|
735
742
|
// Perform checks for annotation assignment 'anno', using corresponding annotation declaration,
|
|
736
743
|
// made of 'annoDecl' (artifact or undefined) and 'elementDecl' (annotation or element
|
|
737
744
|
// or undefined). Report errors on 'options.messages.
|
|
738
|
-
function checkAnnotationAssignment(anno, annoDecl, elementDecl, art) {
|
|
745
|
+
function checkAnnotationAssignment( anno, annoDecl, elementDecl, art ) {
|
|
739
746
|
// Nothing to check if no actual annotation declaration was found
|
|
740
747
|
if (!annoDecl || annoDecl.artifacts && !elementDecl)
|
|
741
748
|
return;
|
|
@@ -779,7 +786,7 @@ function check( model ) { // = XSN
|
|
|
779
786
|
// Check that annotation assignment 'value' (having 'path or 'literal' and
|
|
780
787
|
// 'val') is potentially assignable to element 'element'. Complain on 'loc'
|
|
781
788
|
// if not
|
|
782
|
-
function checkValueAssignableTo(value, elementDecl, art) {
|
|
789
|
+
function checkValueAssignableTo( value, elementDecl, art ) {
|
|
783
790
|
// FIXME: We currently do not have any element declaration that could match
|
|
784
791
|
// a 'path' value, so we simply leave those alone
|
|
785
792
|
if (value.path)
|
|
@@ -840,7 +847,7 @@ function check( model ) { // = XSN
|
|
|
840
847
|
else if (builtins.isRelationTypeName(type) || builtins.isGeoTypeName(type)) {
|
|
841
848
|
warning(null, loc, { type }, 'Type $(TYPE) can\'t be assigned a value');
|
|
842
849
|
}
|
|
843
|
-
else {
|
|
850
|
+
else if (!elementDecl._effectiveType.enum) {
|
|
844
851
|
throw new CompilerAssertion(`Unknown primitive type name: ${ type }`);
|
|
845
852
|
}
|
|
846
853
|
|
|
@@ -861,13 +868,23 @@ function check( model ) { // = XSN
|
|
|
861
868
|
}
|
|
862
869
|
else if (expectedEnum) {
|
|
863
870
|
// Enum symbol not provided but expected
|
|
864
|
-
|
|
871
|
+
const hasValidValue = Object.keys(expectedEnum)
|
|
872
|
+
.some(symbol => getEnumValue(expectedEnum[symbol]) === value.val);
|
|
873
|
+
if (!hasValidValue) {
|
|
865
874
|
// ... and none of the valid enum symbols matches the value
|
|
866
875
|
warning(null, loc, {}, 'An enum value is required here');
|
|
867
876
|
}
|
|
868
877
|
}
|
|
869
878
|
}
|
|
870
879
|
|
|
880
|
+
function getEnumValue( enumSymbol ) {
|
|
881
|
+
if (enumSymbol.value)
|
|
882
|
+
return enumSymbol.value?.val;
|
|
883
|
+
if (enumSymbol._effectiveType)
|
|
884
|
+
return enumSymbol._effectiveType?.value?.val;
|
|
885
|
+
return null;
|
|
886
|
+
}
|
|
887
|
+
|
|
871
888
|
// TODO: remove the following
|
|
872
889
|
|
|
873
890
|
// Return the artifact (and possibly, its element) found by following 'path'
|
|
@@ -877,7 +894,7 @@ function check( model ) { // = XSN
|
|
|
877
894
|
// represented by the full path (or 'undefined' if not found). Note that
|
|
878
895
|
// only elements and artifacts are considered for path traversal (no actions,
|
|
879
896
|
// functions, parameters etc.)
|
|
880
|
-
function resolvePathFrom(path, from, result = {}) {
|
|
897
|
+
function resolvePathFrom( path, from, result = {} ) {
|
|
881
898
|
// Keep last encountered artifacts
|
|
882
899
|
if (from && !from._main)
|
|
883
900
|
result.artifact = from;
|
|
@@ -896,11 +913,11 @@ function check( model ) { // = XSN
|
|
|
896
913
|
|
|
897
914
|
// Return the absolute name of the final type of 'node'. May return 'undefined' for
|
|
898
915
|
// anonymous types
|
|
899
|
-
function getFinalTypeNameOf(node) {
|
|
916
|
+
function getFinalTypeNameOf( node ) {
|
|
900
917
|
let type = node._effectiveType;
|
|
901
918
|
if (type.type)
|
|
902
919
|
type = type.type._artifact;
|
|
903
|
-
return type
|
|
920
|
+
return type?.name?.absolute;
|
|
904
921
|
}
|
|
905
922
|
}
|
|
906
923
|
|
|
@@ -928,7 +945,7 @@ function checkSapCommonLocale( model, messageFunctions ) {
|
|
|
928
945
|
// For each property named 'path' in 'node' (recursively), call callback(path, node)
|
|
929
946
|
//
|
|
930
947
|
// TODO: remove - this is not a good way to traverse expressions
|
|
931
|
-
function foreachPath(node, callback) {
|
|
948
|
+
function foreachPath( node, callback ) {
|
|
932
949
|
if (node === null || typeof node !== 'object') {
|
|
933
950
|
// Primitive node
|
|
934
951
|
return;
|
package/lib/compiler/define.js
CHANGED
|
@@ -672,14 +672,17 @@ function define( model ) {
|
|
|
672
672
|
setLink( col, '_block', parent._block );
|
|
673
673
|
initAnnotations( col, parent._block );
|
|
674
674
|
if (col.inline) { // `@anno elem.{ * }` does not work
|
|
675
|
-
if (col.doc)
|
|
676
|
-
warning( 'syntax-ignoring-anno', [ col.doc.location, col ],
|
|
677
|
-
|
|
675
|
+
if (col.doc) {
|
|
676
|
+
warning( 'syntax-ignoring-anno', [ col.doc.location, col ],
|
|
677
|
+
{ '#': 'doc', code: '.{ ‹inline› }' } );
|
|
678
|
+
}
|
|
678
679
|
// col.$annotations no available for CSN input, have to search.
|
|
679
680
|
// Warning about first annotation should be enough to avoid spam.
|
|
680
681
|
const firstAnno = Object.keys(col).find(key => key.startsWith('@'));
|
|
681
|
-
if (firstAnno)
|
|
682
|
-
warning( 'syntax-ignoring-anno', [ col[firstAnno].name.location, col ]
|
|
682
|
+
if (firstAnno) {
|
|
683
|
+
warning( 'syntax-ignoring-anno', [ col[firstAnno].name.location, col ],
|
|
684
|
+
{ code: '.{ ‹inline› }' } );
|
|
685
|
+
}
|
|
683
686
|
}
|
|
684
687
|
// TODO: allow sub queries? at least in top-level expand without parallel ref
|
|
685
688
|
if (columns)
|
|
@@ -738,7 +741,7 @@ function define( model ) {
|
|
|
738
741
|
*
|
|
739
742
|
* @param {object} exprOrPathElement starts w/ an expr but then subelem from .path or .where.args
|
|
740
743
|
*/
|
|
741
|
-
function approveExistsInChildren(exprOrPathElement) {
|
|
744
|
+
function approveExistsInChildren( exprOrPathElement ) {
|
|
742
745
|
if (!exprOrPathElement) // may be null in case of parse error
|
|
743
746
|
return;
|
|
744
747
|
if (exprOrPathElement.$expected === 'exists')
|
package/lib/compiler/extend.js
CHANGED
|
@@ -53,10 +53,8 @@ function extend( model ) {
|
|
|
53
53
|
|
|
54
54
|
applyExtensions();
|
|
55
55
|
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
const addTextsLanguageAssoc = !!(commonLanguagesEntity && commonLanguagesEntity.elements &&
|
|
59
|
-
commonLanguagesEntity.elements.code);
|
|
56
|
+
const addTextsLanguageAssoc = checkTextsLanguageAssocOption(model, options);
|
|
57
|
+
|
|
60
58
|
Object.keys( model.definitions ).forEach( processArtifact );
|
|
61
59
|
|
|
62
60
|
lateExtensions( false );
|
|
@@ -107,7 +105,7 @@ function extend( model ) {
|
|
|
107
105
|
const processed = new WeakSet();
|
|
108
106
|
forEachDefinition(model, processCompositionPersistence);
|
|
109
107
|
|
|
110
|
-
function processCompositionPersistence(def) {
|
|
108
|
+
function processCompositionPersistence( def ) {
|
|
111
109
|
if (def.$inferred === 'composition-entity' && !processed.has(def)) {
|
|
112
110
|
if (def._parent)
|
|
113
111
|
processCompositionPersistence(def._parent);
|
|
@@ -207,7 +205,7 @@ function extend( model ) {
|
|
|
207
205
|
* @param {XSN.Definition} art
|
|
208
206
|
* @param {boolean|'gen'} [noIncludes=false]
|
|
209
207
|
*/
|
|
210
|
-
function extendArtifact( extensions, art, noIncludes = false) {
|
|
208
|
+
function extendArtifact( extensions, art, noIncludes = false ) {
|
|
211
209
|
if (!noIncludes && !(canApplyIncludes( art, art ) &&
|
|
212
210
|
extensions.every( ext => canApplyIncludes(ext, art) )))
|
|
213
211
|
return false;
|
|
@@ -332,7 +330,7 @@ function extend( model ) {
|
|
|
332
330
|
*
|
|
333
331
|
* @param art
|
|
334
332
|
*/
|
|
335
|
-
function applyTypeExtensions(art) {
|
|
333
|
+
function applyTypeExtensions( art ) {
|
|
336
334
|
/**
|
|
337
335
|
* Contains the previous extension for each property that was applied
|
|
338
336
|
* successfully.
|
|
@@ -620,15 +618,17 @@ function extend( model ) {
|
|
|
620
618
|
return;
|
|
621
619
|
}
|
|
622
620
|
|
|
623
|
-
if (!art.
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
const
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
if (template
|
|
630
|
-
|
|
631
|
-
|
|
621
|
+
if (!art.query) {
|
|
622
|
+
if (!art._ancestors)
|
|
623
|
+
setLink( art, '_ancestors', [] ); // recursive array of includes
|
|
624
|
+
for (const ref of ext.includes) {
|
|
625
|
+
const template = ref._artifact;
|
|
626
|
+
// !template -> non-includable, e.g. scalar type, or cyclic
|
|
627
|
+
if (template) {
|
|
628
|
+
if (template._ancestors)
|
|
629
|
+
art._ancestors.push( ...template._ancestors );
|
|
630
|
+
art._ancestors.push( template );
|
|
631
|
+
}
|
|
632
632
|
}
|
|
633
633
|
}
|
|
634
634
|
includeMembers( ext, art, 'elements' );
|
|
@@ -740,7 +740,7 @@ function extend( model ) {
|
|
|
740
740
|
|
|
741
741
|
if (isKey && isLocalized) { // key with localized is wrong - ignore localized
|
|
742
742
|
const errpos = elem.localized || elem.type || elem.name;
|
|
743
|
-
warning( 'localized-key', [ errpos.location, elem ], { keyword: 'localized' },
|
|
743
|
+
warning( 'def-ignoring-localized-key', [ errpos.location, elem ], { keyword: 'localized' },
|
|
744
744
|
'Keyword $(KEYWORD) is ignored for primary keys' );
|
|
745
745
|
}
|
|
746
746
|
}
|
|
@@ -748,7 +748,7 @@ function extend( model ) {
|
|
|
748
748
|
return false;
|
|
749
749
|
|
|
750
750
|
if (!keys) {
|
|
751
|
-
warning(
|
|
751
|
+
warning( 'def-expecting-key', [ art.name.location, art ], {},
|
|
752
752
|
'No texts entity can be created when no key element exists' );
|
|
753
753
|
return false;
|
|
754
754
|
}
|
|
@@ -798,6 +798,48 @@ function extend( model ) {
|
|
|
798
798
|
* @param {boolean} fioriEnabled
|
|
799
799
|
*/
|
|
800
800
|
function createTextsEntity( base, absolute, textElems, fioriEnabled ) {
|
|
801
|
+
const art = createTextsEntityWithDefaultElements( base, absolute, textElems, fioriEnabled );
|
|
802
|
+
|
|
803
|
+
const { location } = base.name;
|
|
804
|
+
const { locale } = art.elements;
|
|
805
|
+
|
|
806
|
+
// assertUnique array value, first entry is 'locale'
|
|
807
|
+
const assertUniqueValue = [ {
|
|
808
|
+
path: [ { id: locale.name.id, location: locale.location } ],
|
|
809
|
+
location: locale.location,
|
|
810
|
+
} ];
|
|
811
|
+
|
|
812
|
+
for (const orig of textElems) {
|
|
813
|
+
const elem = linkToOrigin( orig, orig.name.id, art, 'elements' );
|
|
814
|
+
if (orig.key && orig.key.val) {
|
|
815
|
+
// elem.key = { val: fioriEnabled ? null : true, $inferred: 'localized', location };
|
|
816
|
+
// TODO: the previous would be better, but currently not supported in toCDL
|
|
817
|
+
if (!fioriEnabled) {
|
|
818
|
+
elem.key = { val: true, $inferred: 'localized', location };
|
|
819
|
+
// If the propagated elements remain key (that is not fiori.draft.enabled)
|
|
820
|
+
// they should be omitted from OData containment EDM
|
|
821
|
+
annotateWith( elem, '@odata.containment.ignore', location );
|
|
822
|
+
}
|
|
823
|
+
else {
|
|
824
|
+
// add the former key paths to the unique constraint
|
|
825
|
+
assertUniqueValue.push({
|
|
826
|
+
path: [ { id: orig.name.id, location: orig.location } ],
|
|
827
|
+
location: orig.location,
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
if (hasTruthyProp( orig, 'localized' )) { // use location of LOCALIZED keyword
|
|
832
|
+
const localized = orig.localized || orig.type || orig.name;
|
|
833
|
+
elem.localized = { val: null, $inferred: 'localized', location: localized.location };
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
if (fioriEnabled)
|
|
837
|
+
annotateWith( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' );
|
|
838
|
+
|
|
839
|
+
return art;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function createTextsEntityWithDefaultElements( base, absolute, textElems, fioriEnabled ) {
|
|
801
843
|
const elements = Object.create(null);
|
|
802
844
|
const { location } = base.name;
|
|
803
845
|
const art = {
|
|
@@ -809,8 +851,7 @@ function extend( model ) {
|
|
|
809
851
|
};
|
|
810
852
|
// If there is a type `sap.common.Locale`, then use it as the type for the element `locale`.
|
|
811
853
|
// If not, use the default `cds.String` with a length of 14.
|
|
812
|
-
const hasLocaleType = model.definitions['sap.common.Locale']
|
|
813
|
-
model.definitions['sap.common.Locale'].kind === 'type';
|
|
854
|
+
const hasLocaleType = model.definitions['sap.common.Locale']?.kind === 'type';
|
|
814
855
|
const locale = {
|
|
815
856
|
name: { location, id: 'locale' },
|
|
816
857
|
kind: 'element',
|
|
@@ -861,39 +902,6 @@ function extend( model ) {
|
|
|
861
902
|
model.definitions[absolute] = art;
|
|
862
903
|
initArtifact( art );
|
|
863
904
|
|
|
864
|
-
// assertUnique array value, first entry is 'locale'
|
|
865
|
-
const assertUniqueValue = [ {
|
|
866
|
-
path: [ { id: locale.name.id, location: locale.location } ],
|
|
867
|
-
location: locale.location,
|
|
868
|
-
} ];
|
|
869
|
-
|
|
870
|
-
for (const orig of textElems) {
|
|
871
|
-
const elem = linkToOrigin( orig, orig.name.id, art, 'elements' );
|
|
872
|
-
if (orig.key && orig.key.val) {
|
|
873
|
-
// elem.key = { val: fioriEnabled ? null : true, $inferred: 'localized', location };
|
|
874
|
-
// TODO: the previous would be better, but currently not supported in toCDL
|
|
875
|
-
if (!fioriEnabled) {
|
|
876
|
-
elem.key = { val: true, $inferred: 'localized', location };
|
|
877
|
-
// If the propagated elements remain key (that is not fiori.draft.enabled)
|
|
878
|
-
// they should be omitted from OData containment EDM
|
|
879
|
-
annotateWith( elem, '@odata.containment.ignore', location );
|
|
880
|
-
}
|
|
881
|
-
else {
|
|
882
|
-
// add the former key paths to the unique constraint
|
|
883
|
-
assertUniqueValue.push({
|
|
884
|
-
path: [ { id: orig.name.id, location: orig.location } ],
|
|
885
|
-
location: orig.location,
|
|
886
|
-
});
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
if (hasTruthyProp( orig, 'localized' )) { // use location of LOCALIZED keyword
|
|
890
|
-
const localized = orig.localized || orig.type || orig.name;
|
|
891
|
-
elem.localized = { val: null, $inferred: 'localized', location: localized.location };
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
if (fioriEnabled)
|
|
895
|
-
annotateWith( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' );
|
|
896
|
-
|
|
897
905
|
return art;
|
|
898
906
|
}
|
|
899
907
|
|
|
@@ -1266,7 +1274,7 @@ function compareAssignments( a, b ) {
|
|
|
1266
1274
|
* @param {object} source
|
|
1267
1275
|
* @param {CSN.Options} options
|
|
1268
1276
|
*/
|
|
1269
|
-
function copyPersistenceAnnotations(target, source, options) {
|
|
1277
|
+
function copyPersistenceAnnotations( target, source, options ) {
|
|
1270
1278
|
if (!source)
|
|
1271
1279
|
return;
|
|
1272
1280
|
|
|
@@ -1275,7 +1283,7 @@ function copyPersistenceAnnotations(target, source, options) {
|
|
|
1275
1283
|
copy( '@cds.persistence.exists' );
|
|
1276
1284
|
copy( '@cds.persistence.skip' );
|
|
1277
1285
|
|
|
1278
|
-
function copy(anno) {
|
|
1286
|
+
function copy( anno ) {
|
|
1279
1287
|
if ( source[anno] && !target[anno] )
|
|
1280
1288
|
target[anno] = { ...source[anno], $inferred: 'parent-origin' };
|
|
1281
1289
|
}
|
|
@@ -1323,4 +1331,24 @@ function storeTypeExtension( ext, art ) {
|
|
|
1323
1331
|
art._extendType.push( ext );
|
|
1324
1332
|
}
|
|
1325
1333
|
|
|
1334
|
+
|
|
1335
|
+
function checkTextsLanguageAssocOption( model, options ) {
|
|
1336
|
+
const languages = model.definitions['sap.common.Languages'];
|
|
1337
|
+
const commonLanguagesEntity = options.addTextsLanguageAssoc && languages?.elements?.code;
|
|
1338
|
+
|
|
1339
|
+
if (options.addTextsLanguageAssoc && !commonLanguagesEntity) {
|
|
1340
|
+
const variant = !languages ? 'std' : 'code';
|
|
1341
|
+
const loc = model.definitions['sap.common.Languages']?.name?.location || null;
|
|
1342
|
+
model.$messageFunctions.info('api-ignoring-language-assoc', loc, {
|
|
1343
|
+
'#': variant, option: 'addTextsLanguageAssoc', art: 'sap.common.Languages', name: 'code',
|
|
1344
|
+
}, {
|
|
1345
|
+
std: 'Ignoring option $(OPTION) because entity $(ART) is missing',
|
|
1346
|
+
code: 'Ignoring option $(OPTION) because entity $(ART) is missing element $(NAME)',
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
return !!commonLanguagesEntity;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
|
|
1326
1354
|
module.exports = extend;
|
|
@@ -67,7 +67,7 @@ function finalizeParseCdl( model ) {
|
|
|
67
67
|
* @param {*} artifact
|
|
68
68
|
* @param {XSN.Artifact} main
|
|
69
69
|
*/
|
|
70
|
-
function resolveTypesForParseCdl(artifact, main) {
|
|
70
|
+
function resolveTypesForParseCdl( artifact, main ) {
|
|
71
71
|
if (!artifact || typeof artifact !== 'object')
|
|
72
72
|
return;
|
|
73
73
|
|
|
@@ -160,7 +160,7 @@ function finalizeParseCdl( model ) {
|
|
|
160
160
|
* @param {object} artWithType
|
|
161
161
|
* @param {XSN.Artifact} user
|
|
162
162
|
*/
|
|
163
|
-
function resolveTypeUnchecked(artWithType, user) {
|
|
163
|
+
function resolveTypeUnchecked( artWithType, user ) {
|
|
164
164
|
const root = artWithType.type.path && artWithType.type.path[0];
|
|
165
165
|
if (!root) // parse error
|
|
166
166
|
return;
|
|
@@ -202,7 +202,7 @@ function finalizeParseCdl( model ) {
|
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
function chooseAndReportDuplicateAnnotation(artifact, annoName) {
|
|
205
|
+
function chooseAndReportDuplicateAnnotation( artifact, annoName ) {
|
|
206
206
|
for (const anno of artifact[annoName])
|
|
207
207
|
message( 'anno-duplicate', [ anno.name.location, artifact ], { anno: annoName } );
|
|
208
208
|
|
package/lib/compiler/populate.js
CHANGED
|
@@ -45,6 +45,7 @@ const {
|
|
|
45
45
|
dependsOn,
|
|
46
46
|
traverseQueryPost,
|
|
47
47
|
setExpandStatus,
|
|
48
|
+
setExpandStatusAnnotate,
|
|
48
49
|
} = require('./utils');
|
|
49
50
|
|
|
50
51
|
const $inferred = Symbol.for('cds.$inferred');
|
|
@@ -163,7 +164,6 @@ function populate( model ) {
|
|
|
163
164
|
const chain = [];
|
|
164
165
|
while (art && !('_effectiveType' in art) &&
|
|
165
166
|
(art.type || art._origin || art.value?.path || art.value?.type) &&
|
|
166
|
-
// TODO: really stop at art.enum? See #8942
|
|
167
167
|
!art.target && !art.enum && !art.elements && !art.items) {
|
|
168
168
|
chain.push( art );
|
|
169
169
|
setLink( art, '_effectiveType', 0 ); // initial setting in case of cycles
|
|
@@ -192,7 +192,6 @@ function populate( model ) {
|
|
|
192
192
|
// collect the "latest" cardinality (calculate lazily if necessary)
|
|
193
193
|
let cardinality = art.cardinality ||
|
|
194
194
|
art._effectiveType && (() => getCardinality( art._effectiveType ));
|
|
195
|
-
let prev = art;
|
|
196
195
|
for (const a of chain) {
|
|
197
196
|
if (a.cardinality)
|
|
198
197
|
cardinality = a.cardinality;
|
|
@@ -201,8 +200,10 @@ function populate( model ) {
|
|
|
201
200
|
art.elements && expandElements( a, art ) ||
|
|
202
201
|
art.items && expandItems( a, art ))
|
|
203
202
|
art = a;
|
|
204
|
-
|
|
205
|
-
|
|
203
|
+
|
|
204
|
+
else if (art.enum && expandEnum( a, art ))
|
|
205
|
+
art = a;
|
|
206
|
+
|
|
206
207
|
setLink( a, '_effectiveType', art );
|
|
207
208
|
}
|
|
208
209
|
}
|
|
@@ -469,11 +470,21 @@ function populate( model ) {
|
|
|
469
470
|
'Element $(ID) is missing in specified elements' );
|
|
470
471
|
}
|
|
471
472
|
else {
|
|
473
|
+
let wasAnnotated = false;
|
|
472
474
|
for (const prop in selem) {
|
|
473
475
|
// just annotation assignments and doc comments for the moment
|
|
474
|
-
if (prop.charAt(0) === '@' || prop === 'doc')
|
|
476
|
+
if (prop.charAt(0) === '@' || prop === 'doc') {
|
|
475
477
|
ielem[prop] = selem[prop];
|
|
478
|
+
// required for gensrc mode of to-csn.js, otherwise the annotation
|
|
479
|
+
// may be lost during recompilation.
|
|
480
|
+
ielem[prop].$priority = 'annotate';
|
|
481
|
+
wasAnnotated = true;
|
|
482
|
+
}
|
|
476
483
|
}
|
|
484
|
+
|
|
485
|
+
if (wasAnnotated)
|
|
486
|
+
setExpandStatusAnnotate(art, 'annotate');
|
|
487
|
+
|
|
477
488
|
selem.$replacement = true;
|
|
478
489
|
if (selem.elements) {
|
|
479
490
|
setLink(ielem, 'elements$', selem.elements);
|