@sap/cds-compiler 3.6.2 → 3.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/README.md +3 -0
- package/bin/cdsc.js +9 -5
- package/doc/CHANGELOG_BETA.md +20 -2
- package/doc/CHANGELOG_DEPRECATED.md +2 -2
- package/lib/api/main.js +2 -1
- package/lib/base/dictionaries.js +10 -0
- package/lib/base/message-registry.js +56 -12
- package/lib/base/messages.js +39 -20
- package/lib/base/model.js +1 -0
- package/lib/base/shuffle.js +2 -1
- package/lib/checks/elements.js +29 -1
- package/lib/checks/{emptyOrOnlyVirtual.js → hasPersistedElements.js} +9 -5
- package/lib/checks/nonexpandableStructured.js +1 -1
- package/lib/checks/onConditions.js +8 -5
- package/lib/checks/types.js +6 -1
- package/lib/checks/validator.js +7 -3
- package/lib/compiler/assert-consistency.js +20 -23
- package/lib/compiler/base.js +1 -2
- package/lib/compiler/builtins.js +2 -2
- package/lib/compiler/checks.js +237 -242
- package/lib/compiler/define.js +63 -75
- package/lib/compiler/extend.js +325 -22
- package/lib/compiler/finalize-parse-cdl.js +1 -55
- package/lib/compiler/kick-start.js +6 -7
- package/lib/compiler/populate.js +284 -288
- package/lib/compiler/propagator.js +15 -13
- package/lib/compiler/resolve.js +136 -306
- package/lib/compiler/shared.js +42 -44
- package/lib/compiler/tweak-assocs.js +29 -27
- package/lib/compiler/utils.js +29 -3
- package/lib/edm/annotations/genericTranslation.js +7 -13
- package/lib/edm/annotations/preprocessAnnotations.js +3 -0
- package/lib/edm/csn2edm.js +0 -4
- package/lib/edm/edm.js +6 -4
- package/lib/edm/edmAnnoPreprocessor.js +1 -0
- package/lib/edm/edmPreprocessor.js +1 -5
- package/lib/gen/Dictionary.json +34 -2
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +2429 -2401
- package/lib/inspect/inspectPropagation.js +2 -0
- package/lib/json/from-csn.js +87 -41
- package/lib/json/to-csn.js +47 -16
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +109 -28
- package/lib/language/language.g4 +20 -4
- package/lib/model/csnRefs.js +1 -1
- package/lib/model/csnUtils.js +1 -0
- package/lib/model/revealInternalProperties.js +1 -2
- package/lib/modelCompare/compare.js +2 -1
- package/lib/render/manageConstraints.js +5 -2
- package/lib/render/toCdl.js +20 -7
- package/lib/render/toHdbcds.js +2 -8
- package/lib/render/toSql.js +4 -3
- package/lib/render/utils/common.js +9 -5
- package/lib/transform/db/assertUnique.js +2 -1
- package/lib/transform/db/expansion.js +2 -0
- package/lib/transform/db/flattening.js +37 -36
- package/lib/transform/db/rewriteCalculatedElements.js +559 -0
- package/lib/transform/db/transformExists.js +4 -0
- package/lib/transform/db/views.js +40 -37
- package/lib/transform/forRelationalDB.js +38 -28
- package/lib/transform/odata/typesExposure.js +50 -15
- package/lib/transform/parseExpr.js +14 -8
- package/lib/transform/transformUtilsNew.js +6 -5
- package/lib/transform/translateAssocsToJoins.js +49 -33
- package/package.json +1 -1
package/lib/compiler/checks.js
CHANGED
|
@@ -26,15 +26,17 @@ function check( model ) { // = XSN
|
|
|
26
26
|
const {
|
|
27
27
|
error, warning, message, info,
|
|
28
28
|
} = model.$messageFunctions;
|
|
29
|
-
|
|
30
|
-
forEachGeneric( model, 'vocabularies', checkAnnotationDefinition );
|
|
29
|
+
|
|
31
30
|
checkSapCommonLocale( model );
|
|
32
31
|
checkSapCommonTextsAspects( model );
|
|
32
|
+
|
|
33
|
+
forEachDefinition( model, checkDefinition );
|
|
34
|
+
forEachGeneric( model, 'vocabularies', checkAnnotationDefinition );
|
|
33
35
|
return;
|
|
34
36
|
|
|
35
|
-
function
|
|
37
|
+
function checkDefinition( art ) {
|
|
36
38
|
checkGenericConstruct( art );
|
|
37
|
-
|
|
39
|
+
forEachMember( art, member => checkMember(member) );
|
|
38
40
|
if (art.$queries)
|
|
39
41
|
art.$queries.forEach( checkQuery );
|
|
40
42
|
}
|
|
@@ -59,36 +61,50 @@ function check( model ) { // = XSN
|
|
|
59
61
|
if (art.kind === 'enum')
|
|
60
62
|
checkEnum( art );
|
|
61
63
|
checkEnumType( art );
|
|
64
|
+
}
|
|
62
65
|
|
|
63
|
-
|
|
66
|
+
function checkMember( member, parentProps = { key: false, virtual: false } ) {
|
|
67
|
+
// To avoid "bubble-up" checks, store required parent properties.
|
|
68
|
+
if (member.key?.val === true)
|
|
69
|
+
parentProps.key = member.key;
|
|
70
|
+
if (member.virtual?.val === true)
|
|
71
|
+
parentProps.virtual = member.virtual;
|
|
72
|
+
|
|
73
|
+
checkGenericConstruct(member);
|
|
74
|
+
|
|
75
|
+
if (member.kind === 'element')
|
|
76
|
+
checkElement( member, parentProps );
|
|
77
|
+
|
|
78
|
+
forEachMember( member, m => checkMember(m, parentProps) );
|
|
64
79
|
}
|
|
65
80
|
|
|
66
|
-
function
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
checkForUnmanagedAssociationsAsKey( elem, elem.key );
|
|
81
|
+
function checkVirtualKey( elem, parentProps ) {
|
|
82
|
+
const isKey = parentProps.key || (elem.key?.val && elem.key);
|
|
83
|
+
const isVirtual = parentProps.virtual?.val || (elem.virtual?.val && elem.virtual);
|
|
84
|
+
if (isKey && isVirtual) {
|
|
85
|
+
error('def-unexpected-key', [ isKey.location, elem ],
|
|
86
|
+
{ '#': 'virtual', name: elem.name.element, prop: 'key' });
|
|
74
87
|
}
|
|
75
|
-
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function checkElement( elem, parentProps ) {
|
|
91
|
+
checkLocalizedSubElement(elem);
|
|
92
|
+
checkVirtualKey(elem, parentProps);
|
|
93
|
+
checkForUnmanagedAssociationsAsKey( elem, parentProps );
|
|
76
94
|
checkLocalizedElement( elem );
|
|
77
|
-
|
|
78
|
-
checkExpression(elem.on, true);
|
|
95
|
+
checkAssociation( elem );
|
|
79
96
|
|
|
80
97
|
if (elem.value) {
|
|
81
|
-
if (elem
|
|
82
|
-
|
|
83
|
-
else
|
|
84
|
-
|
|
98
|
+
if (elem._main.query)
|
|
99
|
+
checkSelectItemValue(elem);
|
|
100
|
+
else if (elem.$syntax === 'calc')
|
|
101
|
+
checkCalculatedElementValue( elem );
|
|
85
102
|
}
|
|
86
103
|
|
|
87
104
|
checkCardinality(elem); // TODO: also for assoc types
|
|
88
|
-
|
|
89
|
-
forEachGeneric( elem, 'elements', checkElement );
|
|
90
105
|
}
|
|
91
106
|
|
|
107
|
+
|
|
92
108
|
function checkName( construct ) { // TODO: move to define.js
|
|
93
109
|
if (model.options.$skipNameCheck)
|
|
94
110
|
return;
|
|
@@ -125,62 +141,30 @@ function check( model ) { // = XSN
|
|
|
125
141
|
}
|
|
126
142
|
}
|
|
127
143
|
|
|
128
|
-
function checkCalculatedElement( elem ) {
|
|
129
|
-
if (elem.value.path) {
|
|
130
|
-
checkExpressionsInPaths(elem.value);
|
|
131
|
-
|
|
132
|
-
const loc = [ elem.value.location, elem ];
|
|
133
|
-
if (isVirtualElement(elem.value._artifact))
|
|
134
|
-
error('ref-unexpected-virtual', loc, { '#': 'expr' });
|
|
135
|
-
else if (isStructuredElement(elem.value._artifact))
|
|
136
|
-
error('ref-unexpected-structured', loc, { '#': 'expr' } );
|
|
137
|
-
else if (elem.value._artifact?.target !== undefined)
|
|
138
|
-
error('ref-unexpected-assoc', loc, { '#': 'expr' });
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
// TODO: The checks above should also be run for each path in expressions.
|
|
142
|
-
checkExpression( elem.value );
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
144
|
function checkQuery( query ) {
|
|
147
145
|
checkNoUnmanagedAssocsInGroupByOrderBy( query );
|
|
148
146
|
// TODO: check too simple (just one source), as most of those in this file
|
|
149
147
|
// Check expressions in the various places where they may occur
|
|
150
148
|
if (query.from)
|
|
151
|
-
|
|
149
|
+
visitSubExpression(query.from, query, checkGenericExpression);
|
|
150
|
+
|
|
151
|
+
if (query.where)
|
|
152
|
+
visitExpression(query.where, query, checkGenericExpression);
|
|
152
153
|
|
|
153
|
-
if (query.where) {
|
|
154
|
-
checkExpression(query.where);
|
|
155
|
-
checkExpressionsInPaths(query.where);
|
|
156
|
-
}
|
|
157
154
|
if (query.groupBy) {
|
|
158
|
-
for (const groupByEntry of query.groupBy)
|
|
159
|
-
|
|
160
|
-
checkExpressionsInPaths(groupByEntry);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
if (query.having) {
|
|
164
|
-
checkExpression(query.having);
|
|
165
|
-
checkExpressionsInPaths(query.having);
|
|
155
|
+
for (const groupByEntry of query.groupBy)
|
|
156
|
+
visitExpression(groupByEntry, query, checkGenericExpression);
|
|
166
157
|
}
|
|
158
|
+
if (query.having)
|
|
159
|
+
visitExpression(query.having, query, checkGenericExpression);
|
|
160
|
+
|
|
167
161
|
if (query.orderBy) {
|
|
168
|
-
for (const orderByEntry of query.orderBy)
|
|
169
|
-
|
|
170
|
-
checkExpressionsInPaths(orderByEntry);
|
|
171
|
-
}
|
|
162
|
+
for (const orderByEntry of query.orderBy)
|
|
163
|
+
visitExpression(orderByEntry, query, checkGenericExpression);
|
|
172
164
|
}
|
|
173
165
|
if (query.mixin) {
|
|
174
|
-
for (const mixinName in query.mixin)
|
|
175
|
-
|
|
176
|
-
checkExpression(query.mixin[mixinName].on, true);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
if (query.elements) {
|
|
180
|
-
for (const elemName in query.elements) {
|
|
181
|
-
checkStructureCasting(query.elements[elemName]);
|
|
182
|
-
checkExpressionsInPaths(query.elements[elemName].value);
|
|
183
|
-
}
|
|
166
|
+
for (const mixinName in query.mixin)
|
|
167
|
+
checkAssociation(query.mixin[mixinName]);
|
|
184
168
|
}
|
|
185
169
|
}
|
|
186
170
|
|
|
@@ -358,25 +342,27 @@ function check( model ) { // = XSN
|
|
|
358
342
|
* Check that a primary key element is not an unmanaged association or
|
|
359
343
|
* contains unmanaged associations
|
|
360
344
|
*
|
|
345
|
+
* TODO: ease check for subelements: using unmanaged assocs is OK there, as
|
|
346
|
+
* long as the whole key is "closed", i.e., no ref in ON refers to element
|
|
347
|
+
* outside.
|
|
348
|
+
*
|
|
361
349
|
* @param {any} element Element to check recursively
|
|
362
350
|
*/
|
|
363
|
-
function checkForUnmanagedAssociationsAsKey( element,
|
|
351
|
+
function checkForUnmanagedAssociationsAsKey( element, parentProps ) {
|
|
352
|
+
if (!parentProps.key?.val && !element.key?.val)
|
|
353
|
+
return;
|
|
364
354
|
if (element.targetAspect) {
|
|
365
355
|
// TODO: bad location / message
|
|
366
|
-
message('composition-as-key', [
|
|
356
|
+
message('composition-as-key', [ parentProps.key.location, element ], {},
|
|
367
357
|
// TODO: give semantics when error downgraded
|
|
368
358
|
'Managed compositions can\'t be used as primary key');
|
|
369
359
|
}
|
|
370
360
|
else if (element.on) {
|
|
371
361
|
// TODO: bad location / message
|
|
372
|
-
message('unmanaged-as-key', [
|
|
362
|
+
message('unmanaged-as-key', [ parentProps.key.location, element ], {},
|
|
373
363
|
// TODO: give semantics when error downgraded
|
|
374
364
|
'Unmanaged associations can\'t be used as primary key');
|
|
375
365
|
}
|
|
376
|
-
// TODO: ease check for subelements: using unmanaged assocs is OK there, as
|
|
377
|
-
// long as the whole key is "closed", i.e., no ref in ON refers to element
|
|
378
|
-
// outside.
|
|
379
|
-
forEachGeneric( element, 'elements', e => checkForUnmanagedAssociationsAsKey( e, keyObj ) );
|
|
380
366
|
}
|
|
381
367
|
|
|
382
368
|
// Check that min and max cardinalities of 'elem' in 'art' have legal values
|
|
@@ -438,27 +424,6 @@ function check( model ) { // = XSN
|
|
|
438
424
|
});
|
|
439
425
|
}
|
|
440
426
|
|
|
441
|
-
// TODO: yes, a check similar to this could make it into the compiler)
|
|
442
|
-
// Check that a structured element ist not casted to a different type
|
|
443
|
-
function checkStructureCasting( elem ) {
|
|
444
|
-
if (elem.type && !elem.type.$inferred) {
|
|
445
|
-
const loc = elem.type.location || elem.location;
|
|
446
|
-
|
|
447
|
-
if (elem._effectiveType && elem._effectiveType.elements) {
|
|
448
|
-
error('type-cast-to-structured', [ loc, elem ], {},
|
|
449
|
-
'Can\'t cast to structured element');
|
|
450
|
-
}
|
|
451
|
-
else if (elem.value && elem.value._artifact && elem.value._artifact._effectiveType &&
|
|
452
|
-
elem.value._artifact._effectiveType.elements) {
|
|
453
|
-
error('type-cast-structured', [ loc, elem ], {},
|
|
454
|
-
'Structured elements can\'t be cast to a different type');
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
if (elem.value && Array.isArray( elem.value.args)) { // TODO named args?
|
|
458
|
-
elem.value.args.forEach(checkStructureCasting);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
427
|
// TODO: make this part of the name resolution in the compiler
|
|
463
428
|
// Check that queries in 'art' do not contain unmanaged associations in GROUP BY or ORDER BY
|
|
464
429
|
function checkNoUnmanagedAssocsInGroupByOrderBy( query ) {
|
|
@@ -481,22 +446,6 @@ function check( model ) { // = XSN
|
|
|
481
446
|
}
|
|
482
447
|
}
|
|
483
448
|
|
|
484
|
-
// Traverses 'node' recursively and applies 'checkExpression' to all expressions
|
|
485
|
-
// found within paths (e.g. filters, parameters, ...)
|
|
486
|
-
function checkExpressionsInPaths( node ) {
|
|
487
|
-
foreachPath(node, (path) => {
|
|
488
|
-
for (const pathStep of path) {
|
|
489
|
-
if (pathStep.where)
|
|
490
|
-
checkExpression(pathStep.where);
|
|
491
|
-
|
|
492
|
-
// FIXME: I can't actually think of a way to make this check fail, because
|
|
493
|
-
// params are limited to actual values and params
|
|
494
|
-
if (pathStep.args)
|
|
495
|
-
checkExpression(pathStep.args);
|
|
496
|
-
}
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
|
|
500
449
|
function checkAssociation( elem ) {
|
|
501
450
|
// TODO: yes, a check similar to this could make it into the compiler)
|
|
502
451
|
// when virtual element is part of association
|
|
@@ -505,80 +454,20 @@ function check( model ) { // = XSN
|
|
|
505
454
|
const key = elem.foreignKeys[k].targetElement;
|
|
506
455
|
if (key && isVirtualElement(key._artifact))
|
|
507
456
|
error('ref-unexpected-virtual', [ key.location, elem ], { '#': 'fkey' });
|
|
457
|
+
else if (key._artifact?.$syntax === 'calc')
|
|
458
|
+
error( 'ref-unexpected-calculated', [ key.location, elem ], { '#': 'fkey' } );
|
|
508
459
|
}
|
|
509
460
|
}
|
|
510
|
-
if (elem.on && !elem.on.$inferred)
|
|
511
|
-
checkAssociationCondition(elem, elem.on);
|
|
512
|
-
}
|
|
513
461
|
|
|
514
|
-
|
|
515
|
-
if (Array.isArray(onCond)) // condition in brackets results an array
|
|
516
|
-
onCond.forEach(Cond => checkAssociationCondition(elem, Cond));
|
|
517
|
-
else
|
|
518
|
-
checkAssociationConditionArgs(elem, onCond.args, getBinaryOp( onCond ));
|
|
462
|
+
checkOnCondition(elem);
|
|
519
463
|
}
|
|
520
464
|
|
|
521
465
|
function getBinaryOp( cond ) {
|
|
522
466
|
const { op, args } = cond;
|
|
523
|
-
return op?.val === 'ixpr' && args
|
|
467
|
+
return op?.val === 'ixpr' && args?.length === 3 && args[1].literal === 'token' &&
|
|
524
468
|
args[1] || op;
|
|
525
469
|
}
|
|
526
470
|
|
|
527
|
-
function checkAssociationConditionArgs( elem, args, op ) {
|
|
528
|
-
if (args)
|
|
529
|
-
args.forEach(Arg => checkAssociationOnCondArg(elem, Arg, op));
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
function checkAssociationOnCondArg( elem, arg, op ) {
|
|
533
|
-
if (Array.isArray(arg)) {
|
|
534
|
-
arg.forEach(Arg => checkAssociationCondition(elem, Arg));
|
|
535
|
-
}
|
|
536
|
-
else {
|
|
537
|
-
checkAssociationCondition(elem, arg);
|
|
538
|
-
singleCheckUnmanagedAssocCondArgumentNoFollowUnmanagedAssoc(elem, arg, op);
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// TODO: make it part of the name resolution in the compiler to check whether
|
|
543
|
-
// associations can be followed (in the ON condition)
|
|
544
|
-
//
|
|
545
|
-
// TODO: this function must be completely reworked, probably even before
|
|
546
|
-
// integration into name resolution - did the first step.
|
|
547
|
-
// It is also incomplete, as associations in structures are not checked.
|
|
548
|
-
// Additionally, `$self.assoc` references are also not found.
|
|
549
|
-
function singleCheckUnmanagedAssocCondArgumentNoFollowUnmanagedAssoc( elem, arg, op ) {
|
|
550
|
-
if (!arg.path)
|
|
551
|
-
return;
|
|
552
|
-
const path0 = arg.path[0];
|
|
553
|
-
if (!path0)
|
|
554
|
-
return;
|
|
555
|
-
if (path0.id === '$self' && arg.path.length === 1) { // $self (backlink) checks
|
|
556
|
-
checkAssociationArgumentStartingWithSelf( op, elem );
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
const argTarget = path0._artifact;
|
|
560
|
-
if (!argTarget) // not resolved
|
|
561
|
-
return;
|
|
562
|
-
// the check is valid for unmanaged associations
|
|
563
|
-
|
|
564
|
-
// TODO clarify if the full resolved path to the target field should
|
|
565
|
-
// consist of managed associations or just the first
|
|
566
|
-
if (argTarget.on) {
|
|
567
|
-
const same = path0._artifact === elem;
|
|
568
|
-
if (!same) {
|
|
569
|
-
error(null, [ path0.location, elem ], {},
|
|
570
|
-
'Unmanaged association condition can\'t follow another unmanaged association');
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
function checkAssociationArgumentStartingWithSelf( op, elem ) {
|
|
576
|
-
if (op?.val === 'xpr') // no check for xpr, would require re-structuring
|
|
577
|
-
return;
|
|
578
|
-
if (op && op.val !== '=')
|
|
579
|
-
error(null, [ op.location, elem ], {}, '$self comparison is only allowed with \'=\'');
|
|
580
|
-
}
|
|
581
|
-
|
|
582
471
|
// A function like this could be part of the compiler
|
|
583
472
|
/**
|
|
584
473
|
* Check that the given type has no conflicts between its `type` property
|
|
@@ -612,19 +501,92 @@ function check( model ) { // = XSN
|
|
|
612
501
|
// Former checkExpressions.js ----------------------------------------------
|
|
613
502
|
|
|
614
503
|
/**
|
|
615
|
-
* Check
|
|
504
|
+
* Check a generic expression (or condition) for semantic validity.
|
|
616
505
|
*
|
|
617
506
|
* @param {any} xpr The expression to check
|
|
618
|
-
* @param {
|
|
507
|
+
* @param {XSN.Artifact} user User for semantic location
|
|
619
508
|
* @returns {void}
|
|
620
509
|
*/
|
|
621
|
-
function
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
if (xpr.op?.val === 'xpr')
|
|
625
|
-
return checkTokenStreamExpression(xpr, allowAssocTail);
|
|
626
|
-
return checkTreeLikeExpression(xpr, allowAssocTail);
|
|
510
|
+
function checkGenericExpression( xpr, user ) {
|
|
511
|
+
checkExpressionNotVirtual(xpr, user);
|
|
512
|
+
checkExpressionAssociationUsage(xpr, user, false);
|
|
627
513
|
}
|
|
514
|
+
|
|
515
|
+
function checkExpressionNotVirtual( xpr, user ) {
|
|
516
|
+
if (xpr._artifact && isVirtualElement(xpr._artifact))
|
|
517
|
+
error('ref-unexpected-virtual', [ xpr.location, user ], { '#': 'expr' });
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function checkOnCondition( elem ) {
|
|
521
|
+
// TODO: Move to checkAssociation
|
|
522
|
+
if (elem.on && !elem.on.$inferred) {
|
|
523
|
+
visitExpression(elem.on, elem, (xpr, user) => {
|
|
524
|
+
checkExpressionNotVirtual(xpr, user);
|
|
525
|
+
checkExpressionAssociationUsage(xpr, user, true);
|
|
526
|
+
if (xpr._artifact?.$syntax === 'calc')
|
|
527
|
+
error( 'ref-unexpected-calculated', [ xpr.location, user ], { '#': 'on' } );
|
|
528
|
+
});
|
|
529
|
+
if (isDollarSelfOrProjectionOperand(elem.on)) {
|
|
530
|
+
// Bare $self usages are not allowed and don't work in A2J.
|
|
531
|
+
error('expr-missing-comparison', [ elem.on.location, elem ], { id: '$self', op: '=' } );
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function checkSelectItemValue( elem ) {
|
|
537
|
+
checkExpressionAssociationUsage(elem.value, elem, false);
|
|
538
|
+
|
|
539
|
+
visitSubExpression(elem.value, elem, (xpr) => {
|
|
540
|
+
checkExpressionNotVirtual(xpr, elem);
|
|
541
|
+
checkExpressionAssociationUsage(xpr, elem, false);
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function checkCalculatedElementValue( elem ) {
|
|
546
|
+
visitExpression(elem.value, elem, (xpr, user) => {
|
|
547
|
+
if (xpr._artifact) { // we only need to check artifact references
|
|
548
|
+
checkExpressionNotVirtual(xpr, user);
|
|
549
|
+
if (isStructuredElement(xpr._artifact))
|
|
550
|
+
error('ref-unexpected-structured', [ xpr.location, elem ], { '#': 'expr' } );
|
|
551
|
+
else if (xpr._artifact.target !== undefined)
|
|
552
|
+
error('ref-unexpected-assoc', [ xpr.location, elem ], { '#': 'expr' });
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
// Calc elements must not refer to keys, because that may lead to another key
|
|
556
|
+
// in an SQL view, which is missing in OData.
|
|
557
|
+
// Following associations does not lead to this issue.
|
|
558
|
+
if (elem.value.path && isKeyElement(elem.value._artifact) &&
|
|
559
|
+
!followsAnAssociation(elem.value.path)) {
|
|
560
|
+
error('ref-unexpected-key', [ elem.value.location, elem ], {},
|
|
561
|
+
'Calculated elements can\'t refer directly to key elements');
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Returns true if any of the path steps follows an association.
|
|
567
|
+
*
|
|
568
|
+
* @param path
|
|
569
|
+
* @return {boolean}
|
|
570
|
+
*/
|
|
571
|
+
function followsAnAssociation( path ) {
|
|
572
|
+
for (const step of path) {
|
|
573
|
+
if (step._artifact?.target)
|
|
574
|
+
return true;
|
|
575
|
+
}
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function isKeyElement( elem ) {
|
|
580
|
+
let parent = elem;
|
|
581
|
+
while (parent) {
|
|
582
|
+
if (parent.key?.val === true)
|
|
583
|
+
return true;
|
|
584
|
+
parent = parent._parent;
|
|
585
|
+
}
|
|
586
|
+
return false;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
|
|
628
590
|
/**
|
|
629
591
|
* Check whether the supplied argument is a virtual element
|
|
630
592
|
*
|
|
@@ -650,60 +612,57 @@ function check( model ) { // = XSN
|
|
|
650
612
|
return !!(elem?._effectiveType || elem)?.elements;
|
|
651
613
|
}
|
|
652
614
|
|
|
653
|
-
/**
|
|
654
|
-
* Check a token-stream expression for semantic validity
|
|
655
|
-
*
|
|
656
|
-
* @param {any} xpr The expression to check
|
|
657
|
-
* @returns {void}
|
|
658
|
-
*/
|
|
659
|
-
function checkTokenStreamExpression( xpr, allowAssocTail ) {
|
|
660
|
-
const args = Array.isArray(xpr.args) ? xpr.args : Object.values(xpr.args || {});
|
|
661
|
-
// Check for illegal argument usage within the expression
|
|
662
|
-
for (const arg of args) {
|
|
663
|
-
if (isVirtualElement(arg._artifact || arg))
|
|
664
|
-
error('ref-unexpected-virtual', arg.location, { '#': 'expr' });
|
|
665
|
-
|
|
666
|
-
// Recursively traverse the argument expression
|
|
667
|
-
checkTokenStreamExpression(arg, allowAssocTail);
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
|
|
671
615
|
/**
|
|
672
616
|
* Check a tree-like expression for semantic validity
|
|
673
617
|
*
|
|
674
618
|
* @param {any} xpr The expression to check
|
|
619
|
+
* @param {XSN.Artifact} user
|
|
620
|
+
* @param {boolean} allowAssocTail
|
|
675
621
|
* @returns {void}
|
|
676
622
|
*/
|
|
677
|
-
function
|
|
678
|
-
|
|
679
|
-
// backlink-like expression (a comparison of $self with an assoc)
|
|
680
|
-
if (isBinaryDollarSelfComparisonWithAssoc(xpr))
|
|
623
|
+
function checkExpressionAssociationUsage( xpr, user, allowAssocTail ) {
|
|
624
|
+
if (!xpr.args)
|
|
681
625
|
return;
|
|
682
626
|
|
|
683
|
-
//
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
627
|
+
// Only check associations and $self if this is not a backlink-like
|
|
628
|
+
// expression (a comparison of $self with an assoc).
|
|
629
|
+
// We don't check token-stream-like 'xpr's.
|
|
630
|
+
const isNotSelfComparison = xpr.op?.val !== 'xpr' &&
|
|
631
|
+
!isBinaryDollarSelfComparisonWithAssoc(xpr);
|
|
632
|
+
const args = Array.isArray(xpr.args) ? xpr.args : Object.values(xpr.args);
|
|
633
|
+
const op = getBinaryOp(xpr);
|
|
634
|
+
|
|
635
|
+
if (isNotSelfComparison) {
|
|
636
|
+
for (const arg of args) {
|
|
637
|
+
if (op?.val !== '=' && isDollarSelfOrProjectionOperand(arg)) {
|
|
638
|
+
// `nary` operators don't have a "good" location; use $self in that case.
|
|
639
|
+
const loc = (op?.location.endLine ? op : arg).location;
|
|
640
|
+
error('expr-invalid-operator', [ loc, user ], { op: '=', id: '$self' });
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
checkExpressionIsNotAssocOrSelf(arg, user, allowAssocTail);
|
|
644
|
+
}
|
|
696
645
|
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
697
648
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
649
|
+
function checkExpressionIsNotAssocOrSelf( arg, user, allowAssocTail ) {
|
|
650
|
+
// Arg must not be an association and not $self
|
|
651
|
+
// Only if path is not approved exists path (that is non-query position)
|
|
652
|
+
if (arg.path && arg.$expected !== undefined) { // not 'approved-exists'
|
|
653
|
+
if (arg.$expected === 'exists')
|
|
654
|
+
error('ref-unexpected-assoc', [ arg.location, user ], { '#': 'expr' } );
|
|
655
|
+
}
|
|
656
|
+
else if (!allowAssocTail && isAssociationOperand(arg)) {
|
|
657
|
+
error('ref-unexpected-assoc', [ arg.location, user ], { '#': 'expr' } );
|
|
658
|
+
}
|
|
702
659
|
|
|
703
|
-
|
|
704
|
-
|
|
660
|
+
if (isDollarSelfOrProjectionOperand(arg)) {
|
|
661
|
+
error(null, [ arg.location, user ], { id: arg.path[0].id },
|
|
662
|
+
'$(ID) can only be used as a value in a comparison to an association');
|
|
705
663
|
}
|
|
706
664
|
}
|
|
665
|
+
|
|
707
666
|
// Return true if 'arg' is an expression argument of type association or composition
|
|
708
667
|
function isAssociationOperand( arg ) {
|
|
709
668
|
if (!arg.path) {
|
|
@@ -715,9 +674,11 @@ function check( model ) { // = XSN
|
|
|
715
674
|
(arg._artifact && arg._artifact._effectiveType && arg._artifact._effectiveType.target);
|
|
716
675
|
}
|
|
717
676
|
|
|
718
|
-
|
|
677
|
+
/**
|
|
678
|
+
* Return true if 'arg' is an expression argument denoting "$self" || "$projection"
|
|
679
|
+
*/
|
|
719
680
|
function isDollarSelfOrProjectionOperand( arg ) {
|
|
720
|
-
return arg.path
|
|
681
|
+
return arg.path?.length === 1 &&
|
|
721
682
|
(arg.path[0].id === '$self' || arg.path[0].id === '$projection');
|
|
722
683
|
}
|
|
723
684
|
|
|
@@ -739,6 +700,11 @@ function check( model ) { // = XSN
|
|
|
739
700
|
return (isAssociationOperand(xpr.args[0]) && isDollarSelfOrProjectionOperand(xpr.args[1]) ||
|
|
740
701
|
isAssociationOperand(xpr.args[1]) && isDollarSelfOrProjectionOperand(xpr.args[0]));
|
|
741
702
|
}
|
|
703
|
+
else if (xpr.args.length === 3 && xpr.args[1].val === '=') {
|
|
704
|
+
// Tree-ish expression from the compiler (not augmented)
|
|
705
|
+
return (isAssociationOperand(xpr.args[0]) && isDollarSelfOrProjectionOperand(xpr.args[2]) ||
|
|
706
|
+
isAssociationOperand(xpr.args[2]) && isDollarSelfOrProjectionOperand(xpr.args[0]));
|
|
707
|
+
}
|
|
742
708
|
|
|
743
709
|
// Nothing else qualifies
|
|
744
710
|
return false;
|
|
@@ -950,12 +916,13 @@ function check( model ) { // = XSN
|
|
|
950
916
|
|
|
951
917
|
// Continue search with next path step
|
|
952
918
|
const nextStepEnv = (from._effectiveType || from).artifacts ||
|
|
953
|
-
from._effectiveType
|
|
919
|
+
from._effectiveType?.elements || [];
|
|
954
920
|
return resolvePathFrom(path.slice(1), nextStepEnv[path[0].id], result);
|
|
955
921
|
}
|
|
956
922
|
|
|
957
|
-
// Return the absolute name of the final type of 'node'. May return 'undefined'
|
|
958
|
-
// anonymous types
|
|
923
|
+
// Return the absolute name of the final type of 'node'. May return 'undefined'
|
|
924
|
+
// for anonymous types. DO NOT USE THIS function, it has several assumptions
|
|
925
|
+
// which are not necessarily true.
|
|
959
926
|
function getFinalTypeNameOf( node ) {
|
|
960
927
|
let type = node._effectiveType;
|
|
961
928
|
if (type.type)
|
|
@@ -1009,21 +976,49 @@ function checkSapCommonLocale( model ) {
|
|
|
1009
976
|
}
|
|
1010
977
|
}
|
|
1011
978
|
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
979
|
+
|
|
980
|
+
/**
|
|
981
|
+
* Visits each expression.
|
|
982
|
+
*
|
|
983
|
+
* TODO: Properly visit expressions; will be improved step by step;
|
|
984
|
+
* Currently only replaces old foreachPath().
|
|
985
|
+
*
|
|
986
|
+
* @param {any} xpr
|
|
987
|
+
* @param {XSN.Artifact} user
|
|
988
|
+
* @param {(xpr: any, user: any, parentExpr: any) => void} callback
|
|
989
|
+
*/
|
|
990
|
+
function visitExpression( xpr, user, callback ) {
|
|
991
|
+
callback( xpr, user, null );
|
|
992
|
+
visitSubExpression( xpr, user, callback );
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* Visits each sub-expression.
|
|
997
|
+
*
|
|
998
|
+
* @param {any} xpr
|
|
999
|
+
* @param {XSN.Artifact} user
|
|
1000
|
+
* @param {(xpr: any, user: any, parentExpr: any) => void} callback
|
|
1001
|
+
*/
|
|
1002
|
+
function visitSubExpression( xpr, user, callback ) {
|
|
1003
|
+
if (xpr.args) {
|
|
1004
|
+
const args = Array.isArray(xpr.args) ? xpr.args : Object.values(xpr.args);
|
|
1005
|
+
// Check for illegal argument usage within the expression
|
|
1006
|
+
for (const arg of args) {
|
|
1007
|
+
callback( arg, user, xpr.args );
|
|
1008
|
+
// Recursively traverse the argument expression
|
|
1009
|
+
visitSubExpression(arg, user, callback);
|
|
1010
|
+
}
|
|
1019
1011
|
}
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1012
|
+
|
|
1013
|
+
if (xpr.path?.length) {
|
|
1014
|
+
for (const arg of xpr.path) {
|
|
1015
|
+
if (arg.where) {
|
|
1016
|
+
callback( arg.where, user, arg );
|
|
1017
|
+
visitSubExpression(arg.where, user, callback);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1026
1020
|
}
|
|
1027
1021
|
}
|
|
1028
1022
|
|
|
1023
|
+
|
|
1029
1024
|
module.exports = check;
|