@sap/cds-compiler 3.4.2 → 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 +8 -0
- 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 +60 -37
- 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 +71 -45
- package/lib/compiler/finalize-parse-cdl.js +3 -3
- package/lib/compiler/populate.js +16 -5
- 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 +26 -19
- package/lib/transform/localized.js +9 -8
- package/lib/transform/odata/typesExposure.js +26 -4
- package/lib/transform/transformUtilsNew.js +15 -8
- 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.
|
|
@@ -742,7 +740,7 @@ function extend( model ) {
|
|
|
742
740
|
|
|
743
741
|
if (isKey && isLocalized) { // key with localized is wrong - ignore localized
|
|
744
742
|
const errpos = elem.localized || elem.type || elem.name;
|
|
745
|
-
warning( 'localized-key', [ errpos.location, elem ], { keyword: 'localized' },
|
|
743
|
+
warning( 'def-ignoring-localized-key', [ errpos.location, elem ], { keyword: 'localized' },
|
|
746
744
|
'Keyword $(KEYWORD) is ignored for primary keys' );
|
|
747
745
|
}
|
|
748
746
|
}
|
|
@@ -750,7 +748,7 @@ function extend( model ) {
|
|
|
750
748
|
return false;
|
|
751
749
|
|
|
752
750
|
if (!keys) {
|
|
753
|
-
warning(
|
|
751
|
+
warning( 'def-expecting-key', [ art.name.location, art ], {},
|
|
754
752
|
'No texts entity can be created when no key element exists' );
|
|
755
753
|
return false;
|
|
756
754
|
}
|
|
@@ -800,6 +798,48 @@ function extend( model ) {
|
|
|
800
798
|
* @param {boolean} fioriEnabled
|
|
801
799
|
*/
|
|
802
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 ) {
|
|
803
843
|
const elements = Object.create(null);
|
|
804
844
|
const { location } = base.name;
|
|
805
845
|
const art = {
|
|
@@ -811,8 +851,7 @@ function extend( model ) {
|
|
|
811
851
|
};
|
|
812
852
|
// If there is a type `sap.common.Locale`, then use it as the type for the element `locale`.
|
|
813
853
|
// If not, use the default `cds.String` with a length of 14.
|
|
814
|
-
const hasLocaleType = model.definitions['sap.common.Locale']
|
|
815
|
-
model.definitions['sap.common.Locale'].kind === 'type';
|
|
854
|
+
const hasLocaleType = model.definitions['sap.common.Locale']?.kind === 'type';
|
|
816
855
|
const locale = {
|
|
817
856
|
name: { location, id: 'locale' },
|
|
818
857
|
kind: 'element',
|
|
@@ -863,39 +902,6 @@ function extend( model ) {
|
|
|
863
902
|
model.definitions[absolute] = art;
|
|
864
903
|
initArtifact( art );
|
|
865
904
|
|
|
866
|
-
// assertUnique array value, first entry is 'locale'
|
|
867
|
-
const assertUniqueValue = [ {
|
|
868
|
-
path: [ { id: locale.name.id, location: locale.location } ],
|
|
869
|
-
location: locale.location,
|
|
870
|
-
} ];
|
|
871
|
-
|
|
872
|
-
for (const orig of textElems) {
|
|
873
|
-
const elem = linkToOrigin( orig, orig.name.id, art, 'elements' );
|
|
874
|
-
if (orig.key && orig.key.val) {
|
|
875
|
-
// elem.key = { val: fioriEnabled ? null : true, $inferred: 'localized', location };
|
|
876
|
-
// TODO: the previous would be better, but currently not supported in toCDL
|
|
877
|
-
if (!fioriEnabled) {
|
|
878
|
-
elem.key = { val: true, $inferred: 'localized', location };
|
|
879
|
-
// If the propagated elements remain key (that is not fiori.draft.enabled)
|
|
880
|
-
// they should be omitted from OData containment EDM
|
|
881
|
-
annotateWith( elem, '@odata.containment.ignore', location );
|
|
882
|
-
}
|
|
883
|
-
else {
|
|
884
|
-
// add the former key paths to the unique constraint
|
|
885
|
-
assertUniqueValue.push({
|
|
886
|
-
path: [ { id: orig.name.id, location: orig.location } ],
|
|
887
|
-
location: orig.location,
|
|
888
|
-
});
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
if (hasTruthyProp( orig, 'localized' )) { // use location of LOCALIZED keyword
|
|
892
|
-
const localized = orig.localized || orig.type || orig.name;
|
|
893
|
-
elem.localized = { val: null, $inferred: 'localized', location: localized.location };
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
if (fioriEnabled)
|
|
897
|
-
annotateWith( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' );
|
|
898
|
-
|
|
899
905
|
return art;
|
|
900
906
|
}
|
|
901
907
|
|
|
@@ -1268,7 +1274,7 @@ function compareAssignments( a, b ) {
|
|
|
1268
1274
|
* @param {object} source
|
|
1269
1275
|
* @param {CSN.Options} options
|
|
1270
1276
|
*/
|
|
1271
|
-
function copyPersistenceAnnotations(target, source, options) {
|
|
1277
|
+
function copyPersistenceAnnotations( target, source, options ) {
|
|
1272
1278
|
if (!source)
|
|
1273
1279
|
return;
|
|
1274
1280
|
|
|
@@ -1325,4 +1331,24 @@ function storeTypeExtension( ext, art ) {
|
|
|
1325
1331
|
art._extendType.push( ext );
|
|
1326
1332
|
}
|
|
1327
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
|
+
|
|
1328
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);
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -63,6 +63,7 @@ const {
|
|
|
63
63
|
storeExtension,
|
|
64
64
|
dependsOn,
|
|
65
65
|
dependsOnSilent,
|
|
66
|
+
setExpandStatusAnnotate,
|
|
66
67
|
testExpr,
|
|
67
68
|
targetMaxNotOne,
|
|
68
69
|
traverseQueryPost,
|
|
@@ -618,20 +619,6 @@ function resolve( model ) {
|
|
|
618
619
|
}
|
|
619
620
|
}
|
|
620
621
|
|
|
621
|
-
function setExpandStatusAnnotate( elem, status ) {
|
|
622
|
-
for (;;) {
|
|
623
|
-
if (elem.$expand === status)
|
|
624
|
-
return; // already set
|
|
625
|
-
elem.$expand = status; // meaning: expanded, containing annos
|
|
626
|
-
for (let line = elem.items; line; line = line.items)
|
|
627
|
-
line.$expand = status; // to-csn just uses the innermost $expand
|
|
628
|
-
if (!elem._main)
|
|
629
|
-
return;
|
|
630
|
-
elem = elem._parent;
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
|
|
635
622
|
function expandParameters( action ) {
|
|
636
623
|
// see also expandElements()
|
|
637
624
|
if (!effectiveType( action ))
|
|
@@ -1291,7 +1278,7 @@ function resolve( model ) {
|
|
|
1291
1278
|
}
|
|
1292
1279
|
}
|
|
1293
1280
|
|
|
1294
|
-
function resolveExpr( expr, expected, user, extDict, expandOrInline) {
|
|
1281
|
+
function resolveExpr( expr, expected, user, extDict, expandOrInline ) {
|
|
1295
1282
|
// TODO: when we have rewritten the resolvePath functions,
|
|
1296
1283
|
// define a traverseExpr() in ./utils.js
|
|
1297
1284
|
// TODO: extra "expected" 'expand'/'inline' instead o param `expandOrInline`
|