@sap/cds-compiler 3.6.0 → 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 +58 -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/api/options.js +3 -2
- 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 +30 -2
- package/lib/model/csnUtils.js +1 -0
- package/lib/model/revealInternalProperties.js +1 -2
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +3 -0
- 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 +6 -5
- 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 +15 -6
- package/lib/transform/db/views.js +40 -37
- package/lib/transform/forRelationalDB.js +44 -30
- 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/extend.js
CHANGED
|
@@ -10,20 +10,25 @@ const {
|
|
|
10
10
|
isDeprecatedEnabled,
|
|
11
11
|
forEachGeneric, forEachInOrder, forEachDefinition,
|
|
12
12
|
} = require('../base/model');
|
|
13
|
-
const { dictAdd } = require('../base/dictionaries');
|
|
13
|
+
const { dictAdd, dictAddArray } = require('../base/dictionaries');
|
|
14
14
|
const { kindProperties, dictKinds } = require('./base');
|
|
15
15
|
const {
|
|
16
16
|
setLink,
|
|
17
17
|
setArtifactLink,
|
|
18
|
+
copyExpr,
|
|
18
19
|
annotateWith,
|
|
19
20
|
linkToOrigin,
|
|
20
21
|
setMemberParent,
|
|
21
22
|
dependsOnSilent,
|
|
22
23
|
augmentPath,
|
|
23
|
-
|
|
24
|
+
pathName,
|
|
25
|
+
splitIntoPath,
|
|
26
|
+
isDirectComposition,
|
|
27
|
+
annotationHasEllipsis,
|
|
24
28
|
} = require('./utils');
|
|
25
29
|
const layers = require('./moduleLayers');
|
|
26
30
|
const { typeParameters } = require('./builtins');
|
|
31
|
+
const { CompilerAssertion } = require('../base/error');
|
|
27
32
|
|
|
28
33
|
function extend( model ) {
|
|
29
34
|
const { options } = model;
|
|
@@ -34,9 +39,8 @@ function extend( model ) {
|
|
|
34
39
|
const {
|
|
35
40
|
resolvePath,
|
|
36
41
|
resolveUncheckedPath,
|
|
37
|
-
checkAnnotate,
|
|
38
42
|
initAnnotations,
|
|
39
|
-
|
|
43
|
+
checkAnnotate,
|
|
40
44
|
attachAndEmitValidNames,
|
|
41
45
|
checkDefinitions,
|
|
42
46
|
initArtifact,
|
|
@@ -46,9 +50,10 @@ function extend( model ) {
|
|
|
46
50
|
|
|
47
51
|
Object.assign( model.$functions, {
|
|
48
52
|
lateExtensions,
|
|
49
|
-
layeredAssignments,
|
|
50
|
-
assignmentsOfHighestLayers,
|
|
51
53
|
applyTypeExtensions,
|
|
54
|
+
chooseAnnotationsInArtifact,
|
|
55
|
+
extensionFor,
|
|
56
|
+
copyAnnotationsForExtensions,
|
|
52
57
|
} );
|
|
53
58
|
|
|
54
59
|
applyExtensions();
|
|
@@ -150,7 +155,7 @@ function extend( model ) {
|
|
|
150
155
|
}
|
|
151
156
|
else if (phase === 1
|
|
152
157
|
? extendContext( name, art )
|
|
153
|
-
: extendArtifact( extensionsDict[name], art, phase
|
|
158
|
+
: extendArtifact( extensionsDict[name], art, Array.isArray( phase ) && phase )) {
|
|
154
159
|
delete extensionsDict[name];
|
|
155
160
|
}
|
|
156
161
|
}
|
|
@@ -158,7 +163,7 @@ function extend( model ) {
|
|
|
158
163
|
if (phase === 1)
|
|
159
164
|
phase = 2;
|
|
160
165
|
else if (extNames.length >= length)
|
|
161
|
-
phase =
|
|
166
|
+
phase = Object.keys( extensionsDict ); // = no includes
|
|
162
167
|
}
|
|
163
168
|
}
|
|
164
169
|
|
|
@@ -210,6 +215,16 @@ function extend( model ) {
|
|
|
210
215
|
if (!noIncludes && !(canApplyIncludes( art, art ) &&
|
|
211
216
|
extensions.every( ext => canApplyIncludes(ext, art) )))
|
|
212
217
|
return false;
|
|
218
|
+
if (Array.isArray( noIncludes )) {
|
|
219
|
+
canApplyIncludes( art, art, noIncludes );
|
|
220
|
+
extensions.forEach( ext => canApplyIncludes( ext, art, noIncludes ) );
|
|
221
|
+
}
|
|
222
|
+
else if (!noIncludes &&
|
|
223
|
+
!(canApplyIncludes( art, art ) &&
|
|
224
|
+
extensions.every( ext => canApplyIncludes( ext, art) ))) {
|
|
225
|
+
// console.log( 'FALSE:',art.name, extensions.map( e => e.name ) )
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
213
228
|
if (!art.query) {
|
|
214
229
|
model._entities.push( art ); // add structure with includes in dep order
|
|
215
230
|
art.$entity = ++model.$entity;
|
|
@@ -425,7 +440,7 @@ function extend( model ) {
|
|
|
425
440
|
break;
|
|
426
441
|
}
|
|
427
442
|
|
|
428
|
-
const baseType = art._effectiveType; // may be an ENUM
|
|
443
|
+
const baseType = art._effectiveType; // may be an ENUM -- TODO: not here!
|
|
429
444
|
if (!artType || (art.kind !== 'type' && art.kind !== 'element') || !baseType?.builtin) {
|
|
430
445
|
// Only report first extension for non-scalar base type. Reduces noise.
|
|
431
446
|
error('ref-expected-scalar-type', [ ext.name.location, ext ], { } );
|
|
@@ -547,7 +562,7 @@ function extend( model ) {
|
|
|
547
562
|
// We only care about the highest layer
|
|
548
563
|
const { assignments, issue } = assignmentsOfHighestLayers( extLayers );
|
|
549
564
|
|
|
550
|
-
if (issue) {
|
|
565
|
+
if (issue || assignments.length > 1) { // TODO: allow in same file?
|
|
551
566
|
const id = (issue === 'unrelated')
|
|
552
567
|
? 'ext-duplicate-extend-type-unrelated-layer'
|
|
553
568
|
: 'ext-duplicate-extend-type';
|
|
@@ -665,14 +680,25 @@ function extend( model ) {
|
|
|
665
680
|
* @param {XSN.Artifact} target
|
|
666
681
|
* @returns {boolean}
|
|
667
682
|
*/
|
|
668
|
-
function canApplyIncludes( art, target ) {
|
|
669
|
-
if (art.includes)
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
683
|
+
function canApplyIncludes( art, target, justResolveCyclic ) {
|
|
684
|
+
if (!art.includes)
|
|
685
|
+
return true;
|
|
686
|
+
const isView = !!target.query;
|
|
687
|
+
for (const ref of art.includes) {
|
|
688
|
+
const name = resolveUncheckedPath( ref, 'include', art );
|
|
689
|
+
// console.log('CAI:',justResolveCyclic, name, ref.path, Object.keys(extensionsDict))
|
|
690
|
+
if (justResolveCyclic) {
|
|
691
|
+
if (!justResolveCyclic.includes( name ))
|
|
692
|
+
continue;
|
|
693
|
+
delete ref._artifact;
|
|
675
694
|
}
|
|
695
|
+
else if (name && name in extensionsDict) {
|
|
696
|
+
return false;
|
|
697
|
+
}
|
|
698
|
+
else if (ref._artifact) {
|
|
699
|
+
delete ref._artifact;
|
|
700
|
+
}
|
|
701
|
+
resolvePath( ref, isView ? 'viewInclude' : 'include', art );
|
|
676
702
|
}
|
|
677
703
|
return true;
|
|
678
704
|
}
|
|
@@ -710,7 +736,8 @@ function extend( model ) {
|
|
|
710
736
|
}
|
|
711
737
|
}
|
|
712
738
|
}
|
|
713
|
-
|
|
739
|
+
if (!art.query) // do not set art.elements and art.enums with query entity!
|
|
740
|
+
includeMembers( ext, art, 'elements' );
|
|
714
741
|
includeMembers( ext, art, 'actions' );
|
|
715
742
|
}
|
|
716
743
|
|
|
@@ -743,10 +770,17 @@ function extend( model ) {
|
|
|
743
770
|
elem.masked = Object.assign( { $inferred: 'include' }, origin.masked );
|
|
744
771
|
if (origin.key)
|
|
745
772
|
elem.key = Object.assign( { $inferred: 'include' }, origin.key );
|
|
773
|
+
if (origin.value && origin.$syntax === 'calc') {
|
|
774
|
+
// TODO: If paths become invalid in the new artifact, should we mark
|
|
775
|
+
// all usages in the expressions? Possibly just the first one?
|
|
776
|
+
elem.value = Object.assign( { $inferred: 'include' }, copyExpr( origin.value ));
|
|
777
|
+
elem.$syntax = 'calc';
|
|
778
|
+
}
|
|
746
779
|
// TODO: also complain if elem is just defined in art
|
|
747
780
|
});
|
|
748
781
|
}
|
|
749
782
|
}
|
|
783
|
+
checkRedefinitionThroughIncludes( parent, prop );
|
|
750
784
|
// TODO: expand elements having direct elements (if needed)
|
|
751
785
|
if (members) {
|
|
752
786
|
forEachInOrder( { [prop]: members }, prop, ( elem, name ) => {
|
|
@@ -755,6 +789,24 @@ function extend( model ) {
|
|
|
755
789
|
}
|
|
756
790
|
}
|
|
757
791
|
|
|
792
|
+
/**
|
|
793
|
+
* Report duplicates in parent[prop] that happen due to multiple includes having the
|
|
794
|
+
* same member. Covers `entity G : E, G {};` but not `entity G : E {}; extend G with F;`.
|
|
795
|
+
*
|
|
796
|
+
* TODO(v4): Make this a hard error; see checkRedefinition(); maybe combine both;
|
|
797
|
+
*/
|
|
798
|
+
function checkRedefinitionThroughIncludes( parent, prop ) {
|
|
799
|
+
if (!parent[prop])
|
|
800
|
+
return;
|
|
801
|
+
forEachInOrder(parent, prop, ( member, name ) => {
|
|
802
|
+
if (member.$inferred === 'include' && Array.isArray(member.$duplicates)) {
|
|
803
|
+
const includes = [ member, ...member.$duplicates ].map(dup => dup._origin._main);
|
|
804
|
+
warning( 'ref-duplicate-include-member', [ parent.name.location, member ],
|
|
805
|
+
{ '#': prop, name, sorted_arts: includes } );
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
|
|
758
810
|
// localized texts entities
|
|
759
811
|
|
|
760
812
|
/**
|
|
@@ -1368,6 +1420,242 @@ function extend( model ) {
|
|
|
1368
1420
|
dictAdd( proxyDict.elements, pname, proxy );
|
|
1369
1421
|
}
|
|
1370
1422
|
}
|
|
1423
|
+
|
|
1424
|
+
|
|
1425
|
+
// Phase 4 - annotations ---------------------------------------------------
|
|
1426
|
+
|
|
1427
|
+
function extensionFor( art ) {
|
|
1428
|
+
if (art.kind === 'annotate')
|
|
1429
|
+
return art;
|
|
1430
|
+
if (art._extension)
|
|
1431
|
+
return art._extension;
|
|
1432
|
+
|
|
1433
|
+
// $extension means: already applied
|
|
1434
|
+
const ext = {
|
|
1435
|
+
kind: art.kind, // set kind for setMemberParent()
|
|
1436
|
+
$extension: 'exists',
|
|
1437
|
+
location: art.location, // location( extension to existing art ) = location(art)
|
|
1438
|
+
};
|
|
1439
|
+
const { location } = art.name;
|
|
1440
|
+
if (!art._main) {
|
|
1441
|
+
ext.name = {
|
|
1442
|
+
path: [ { id: art.name.absolute, location } ],
|
|
1443
|
+
location,
|
|
1444
|
+
absolute: art.name.absolute,
|
|
1445
|
+
};
|
|
1446
|
+
if (model.extensions)
|
|
1447
|
+
model.extensions.push(ext);
|
|
1448
|
+
else
|
|
1449
|
+
model.extensions = [ ext ];
|
|
1450
|
+
}
|
|
1451
|
+
else {
|
|
1452
|
+
ext.name = { id: art.name.id, location };
|
|
1453
|
+
const parent = extensionFor( art._parent );
|
|
1454
|
+
const kind = kindProperties[art.kind].normalized || art.kind;
|
|
1455
|
+
// enums would be first in elements
|
|
1456
|
+
if ( parent[kindProperties[kind].dict]?.[art.name.id] )
|
|
1457
|
+
throw new CompilerAssertion(art.name.id);
|
|
1458
|
+
setMemberParent( ext, art.name.id, parent, kindProperties[kind].dict );
|
|
1459
|
+
}
|
|
1460
|
+
ext.kind = 'annotate'; // after setMemberParent()!
|
|
1461
|
+
setLink( art, '_extension', ext );
|
|
1462
|
+
setArtifactLink( ext.name, art );
|
|
1463
|
+
if (art.returns)
|
|
1464
|
+
ext.$syntax = 'returns';
|
|
1465
|
+
return ext;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
/**
|
|
1469
|
+
* Goes through all (applied) annotations in the given artifact and chooses one
|
|
1470
|
+
* if multiple exist according to the module layer.
|
|
1471
|
+
*
|
|
1472
|
+
* @param {XSN.Artifact} art
|
|
1473
|
+
*/
|
|
1474
|
+
function chooseAnnotationsInArtifact( art ) {
|
|
1475
|
+
for (const prop in art) {
|
|
1476
|
+
if (prop.charAt(0) === '@')
|
|
1477
|
+
chooseAssignment( prop, art );
|
|
1478
|
+
}
|
|
1479
|
+
if (art.doc)
|
|
1480
|
+
chooseAssignment( 'doc', art );
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
function chooseAssignment( annoName, art ) {
|
|
1484
|
+
let anno = art[annoName];
|
|
1485
|
+
if (!Array.isArray( anno )) { // just one assignment -> use it
|
|
1486
|
+
if (!annotationHasEllipsis( anno ))
|
|
1487
|
+
return;
|
|
1488
|
+
anno = [ anno ];
|
|
1489
|
+
}
|
|
1490
|
+
// console.log('ASSIGN:',art.name.absolute,annoName)
|
|
1491
|
+
const scheduledAssignments = [];
|
|
1492
|
+
// sort assignment according to layer (define is bottom layer):
|
|
1493
|
+
const layeredAnnos = layeredAssignments( anno );
|
|
1494
|
+
let cont = true;
|
|
1495
|
+
while (cont) {
|
|
1496
|
+
const { assignments, issue } = assignmentsOfHighestLayers( layeredAnnos );
|
|
1497
|
+
// console.log( 'CA:', annoName, issue, assignments)
|
|
1498
|
+
let index = assignments.length;
|
|
1499
|
+
cont = !!index; // safety
|
|
1500
|
+
while (--index >= 0) {
|
|
1501
|
+
const a = assignments[index];
|
|
1502
|
+
scheduledAssignments.push( a );
|
|
1503
|
+
if (!annotationHasEllipsis( a )) {
|
|
1504
|
+
cont = false;
|
|
1505
|
+
break;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
if (issue) {
|
|
1509
|
+
// eslint-disable-next-line no-nested-ternary
|
|
1510
|
+
const msg = (index < 0)
|
|
1511
|
+
? 'anno-unstable-array'
|
|
1512
|
+
: (issue === true)
|
|
1513
|
+
? 'anno-duplicate'
|
|
1514
|
+
: 'anno-duplicate-unrelated-layer';
|
|
1515
|
+
const variant = annoName === 'doc' ? 'doc' : 'std';
|
|
1516
|
+
for (const a of assignments) {
|
|
1517
|
+
if (!a.$errorReported) {
|
|
1518
|
+
message( msg, [ a.name?.location || a.location, art ],
|
|
1519
|
+
{ '#': variant, anno: annoName } );
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
else if (index > 0) { // more than one set (not just ...)
|
|
1524
|
+
const variant = annoName === 'doc' ? 'doc' : 'std';
|
|
1525
|
+
while (index >= 0) { // do not report for trailing [...]
|
|
1526
|
+
const a = assignments[index--];
|
|
1527
|
+
warning( 'anno-duplicate-same-file', [ a.name?.location || a.location, art ],
|
|
1528
|
+
{ '#': variant, anno: annoName } );
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
// Now apply the assignments - all but the first have a '...'
|
|
1533
|
+
let result = null;
|
|
1534
|
+
scheduledAssignments.reverse();
|
|
1535
|
+
for (const a of scheduledAssignments)
|
|
1536
|
+
result = applyAssignment( result, a, art, annoName );
|
|
1537
|
+
art[annoName] = result.name ? result
|
|
1538
|
+
: Object.assign( {}, scheduledAssignments[scheduledAssignments.length - 1], result );
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
function applyAssignment( previousAnno, anno, art, annoName ) {
|
|
1542
|
+
const hasBase = previousAnno?.literal === 'array';
|
|
1543
|
+
if (!previousAnno) {
|
|
1544
|
+
const firstEllipsis = annotationHasEllipsis( anno );
|
|
1545
|
+
if (!firstEllipsis)
|
|
1546
|
+
return anno;
|
|
1547
|
+
if (anno.$priority) { // already complained about with Define
|
|
1548
|
+
const loc = firstEllipsis.location || anno.name.location;
|
|
1549
|
+
message( 'anno-unexpected-ellipsis-layers', [ loc, art ], { code: '...' } );
|
|
1550
|
+
}
|
|
1551
|
+
previousAnno = { val: [] };
|
|
1552
|
+
}
|
|
1553
|
+
else if (previousAnno.literal !== 'array') {
|
|
1554
|
+
// TODO: If we introduce sub-messages, point to the non-array base value.
|
|
1555
|
+
error( 'anno-mismatched-ellipsis', [ anno.name.location, art ], { code: '...' } );
|
|
1556
|
+
previousAnno = { val: [] };
|
|
1557
|
+
}
|
|
1558
|
+
const previousValue = previousAnno.val;
|
|
1559
|
+
let prevPos = 0;
|
|
1560
|
+
const result = [];
|
|
1561
|
+
for (const item of anno.val) {
|
|
1562
|
+
const ell = item && item.literal === 'token' && item.val === '...';
|
|
1563
|
+
if (!ell) {
|
|
1564
|
+
result.push( item );
|
|
1565
|
+
}
|
|
1566
|
+
else {
|
|
1567
|
+
let upToSpec = item.upTo && checkUpToSpec( item.upTo, art, annoName, true );
|
|
1568
|
+
while (prevPos < previousValue.length) {
|
|
1569
|
+
const prevItem = previousValue[prevPos++];
|
|
1570
|
+
result.push( prevItem );
|
|
1571
|
+
if (upToSpec && prevItem && equalUpTo( prevItem, item.upTo)) {
|
|
1572
|
+
upToSpec = false;
|
|
1573
|
+
break;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
if (upToSpec && hasBase) {
|
|
1577
|
+
// non-matched UP TO; if there is no base to apply to, there is already an error.
|
|
1578
|
+
warning( null, [ item.upTo.location, art ], { anno: annoName, code: '... up to' },
|
|
1579
|
+
'The $(CODE) value does not match any item in the base annotation $(ANNO)' );
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
// console.log('TP:',previousValue.map(se),anno.val.map(se),'->',result.map(se))
|
|
1584
|
+
return { val: result, literal: 'array' };
|
|
1585
|
+
}
|
|
1586
|
+
// function se(a) { return a.upTo ? [a.val,a.upTo.val] : a.val ; }
|
|
1587
|
+
|
|
1588
|
+
function checkUpToSpec( upToSpec, art, annoName, isFullUpTo ) {
|
|
1589
|
+
const { literal } = upToSpec;
|
|
1590
|
+
if (!isFullUpTo) { // inside struct of UP TO
|
|
1591
|
+
if (literal !== 'struct' && literal !== 'array' )
|
|
1592
|
+
return true;
|
|
1593
|
+
}
|
|
1594
|
+
else if (literal === 'struct') {
|
|
1595
|
+
return Object.values( upToSpec.struct ).every( v => checkUpToSpec( v, art, annoName ) );
|
|
1596
|
+
}
|
|
1597
|
+
else if (literal !== 'array' && literal !== 'boolean' && literal !== 'null') {
|
|
1598
|
+
return true;
|
|
1599
|
+
}
|
|
1600
|
+
error( null, [ upToSpec.location, art ],
|
|
1601
|
+
{ anno: annoName, code: '... up to', '#': literal },
|
|
1602
|
+
{
|
|
1603
|
+
std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
|
|
1604
|
+
array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
|
|
1605
|
+
// eslint-disable-next-line max-len
|
|
1606
|
+
struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
|
|
1607
|
+
boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
|
|
1608
|
+
null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
|
|
1609
|
+
} );
|
|
1610
|
+
return false;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
function equalUpTo( previousItem, upToSpec ) {
|
|
1614
|
+
if (!previousItem)
|
|
1615
|
+
return false;
|
|
1616
|
+
if ('val' in upToSpec) {
|
|
1617
|
+
if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
|
|
1618
|
+
return true;
|
|
1619
|
+
const typeUpTo = typeof upToSpec.val;
|
|
1620
|
+
const typePrev = typeof previousItem.val;
|
|
1621
|
+
if (typeUpTo === 'number')
|
|
1622
|
+
return typePrev === 'string' && previousItem.val === upToSpec.val.toString();
|
|
1623
|
+
if (typePrev === 'number')
|
|
1624
|
+
return typeUpTo === 'string' && upToSpec.val === previousItem.val.toString();
|
|
1625
|
+
}
|
|
1626
|
+
else if (upToSpec.path) {
|
|
1627
|
+
return previousItem.path && normalizeRef( previousItem ) === normalizeRef( upToSpec );
|
|
1628
|
+
}
|
|
1629
|
+
else if (upToSpec.sym) {
|
|
1630
|
+
return previousItem.sym && previousItem.sym.id === upToSpec.sym.id;
|
|
1631
|
+
}
|
|
1632
|
+
else if (upToSpec.struct && previousItem.struct) {
|
|
1633
|
+
return Object.entries( upToSpec.struct )
|
|
1634
|
+
.every( ([ n, v ]) => equalUpTo( previousItem.struct[n], v ) );
|
|
1635
|
+
}
|
|
1636
|
+
return false;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
function normalizeRef( node ) { // see to-csn.js
|
|
1640
|
+
const ref = pathName( node.path );
|
|
1641
|
+
return node.variant ? `${ ref }#${ node.variant.id }` : ref;
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
// formerly in shared.js: -----------------------------------------------------
|
|
1645
|
+
|
|
1646
|
+
// Copy annotations from `ext` to `art`, overwriting inferred ones.
|
|
1647
|
+
// TODO: move to extend.js if not used anymore in define.js
|
|
1648
|
+
function copyAnnotationsForExtensions( ext, art ) {
|
|
1649
|
+
for (const annoProp in ext) {
|
|
1650
|
+
if (annoProp.charAt(0) === '@' || annoProp === 'doc') {
|
|
1651
|
+
const extAnno = ext[annoProp];
|
|
1652
|
+
if (art[annoProp]?.$inferred)
|
|
1653
|
+
art[annoProp] = extAnno; // overwrite $inferred annos
|
|
1654
|
+
else
|
|
1655
|
+
dictAddArray( art, annoProp, extAnno );
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1371
1659
|
}
|
|
1372
1660
|
|
|
1373
1661
|
/**
|
|
@@ -1399,9 +1687,10 @@ function layeredAssignments( assignments ) {
|
|
|
1399
1687
|
/**
|
|
1400
1688
|
* Return assignments of the highest layers.
|
|
1401
1689
|
* Also returns whether there could be an issue:
|
|
1402
|
-
* - false: there
|
|
1690
|
+
* - false: there are just assignments in one file,
|
|
1403
1691
|
* - 'unrelated': there is just one assignment per layer
|
|
1404
|
-
* - true: there is at least one layer with two or more assignments
|
|
1692
|
+
* - true: there is at least one layer with two or more assignments, and
|
|
1693
|
+
* at least two files are involved
|
|
1405
1694
|
* TODO: make this usable for extend (elements), too.
|
|
1406
1695
|
*
|
|
1407
1696
|
* @param {Record<string, object>} layeredAnnos Structure as returned by layeredAssignments()
|
|
@@ -1414,7 +1703,7 @@ function assignmentsOfHighestLayers( layeredAnnos ) {
|
|
|
1414
1703
|
const name = layerNames[0];
|
|
1415
1704
|
const { assignments } = layeredAnnos[name] || { assignments: [] };
|
|
1416
1705
|
delete layeredAnnos[name];
|
|
1417
|
-
return { assignments, issue: assignments
|
|
1706
|
+
return { assignments, issue: inMoreThanOneFile( assignments ) };
|
|
1418
1707
|
}
|
|
1419
1708
|
|
|
1420
1709
|
// collect all layers which are lower than another layer
|
|
@@ -1436,13 +1725,27 @@ function assignmentsOfHighestLayers( layeredAnnos ) {
|
|
|
1436
1725
|
}
|
|
1437
1726
|
}
|
|
1438
1727
|
assignments.sort( compareAssignments );
|
|
1439
|
-
const good = highest.every( layer => layer.assignments
|
|
1728
|
+
const good = highest.every( layer => !inMoreThanOneFile( layer.assignments ));
|
|
1440
1729
|
// TODO: use layer.file instead
|
|
1441
1730
|
const issue = !good || highest.length > 1 && 'unrelated';
|
|
1442
1731
|
// console.log('HI:',highest.map(l=>l.name),issue,issue&&assignments)
|
|
1443
1732
|
return { assignments, issue };
|
|
1444
1733
|
}
|
|
1445
1734
|
|
|
1735
|
+
function inMoreThanOneFile( assignments ) {
|
|
1736
|
+
if (assignments.length <= 1)
|
|
1737
|
+
return false;
|
|
1738
|
+
// annotations have no location with implicit `true`, doc have no name:
|
|
1739
|
+
const file = (assignments[0].name || assignments[0]).location?.file;
|
|
1740
|
+
return !file || assignments.slice(1).some( a => (a.name || a).location?.file !== file );
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
/**
|
|
1744
|
+
* Compare two assignments which are not comparable via layering:
|
|
1745
|
+
* - via the fs.realpath of the file (not layer!) of the assignments, then
|
|
1746
|
+
* - via the line, then column of the assignments.
|
|
1747
|
+
* Returns <0 if `a`<`b`, >1 if `a`>`b`, i.e. can be used for ascending sort.
|
|
1748
|
+
*/
|
|
1446
1749
|
function compareAssignments( a, b ) {
|
|
1447
1750
|
const fileA = layers.realname( a._block );
|
|
1448
1751
|
const fileB = layers.realname( b._block );
|
|
@@ -13,8 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
'use strict';
|
|
15
15
|
|
|
16
|
-
const {
|
|
17
|
-
const { forEachGeneric, forEachMember } = require('../base/model');
|
|
16
|
+
const { forEachGeneric } = require('../base/model');
|
|
18
17
|
const { setLink, setArtifactLink } = require('./utils');
|
|
19
18
|
|
|
20
19
|
|
|
@@ -42,7 +41,6 @@ function finalizeParseCdl( model ) {
|
|
|
42
41
|
for (const name in extensionsDict) {
|
|
43
42
|
for (const ext of extensionsDict[name]) {
|
|
44
43
|
ext.name.absolute = resolveUncheckedPath( ext.name, 'extend', ext );
|
|
45
|
-
mergeAnnotatesForSameArtifact( ext ); // TODO: should not be necessary anymore
|
|
46
44
|
// Initialize members and define annotations in sub-elements.
|
|
47
45
|
initMembers( ext, ext, ext._block, true );
|
|
48
46
|
extensions.push( ext );
|
|
@@ -96,8 +94,6 @@ function finalizeParseCdl( model ) {
|
|
|
96
94
|
|
|
97
95
|
if (artifact.from) {
|
|
98
96
|
const { from } = artifact;
|
|
99
|
-
// Note: `_from` only contains sources necessary for calculating elements,
|
|
100
|
-
// not e.g. those from the `where exists` clause.
|
|
101
97
|
resolveUncheckedPath(from, 'from', main);
|
|
102
98
|
resolveTypesForParseCdl(from, main);
|
|
103
99
|
}
|
|
@@ -108,11 +104,6 @@ function finalizeParseCdl( model ) {
|
|
|
108
104
|
if (parseCdlSpeciallyHandledXsnProps.includes(prop) || parseCdlIgnoredXsnProps.includes(prop))
|
|
109
105
|
continue;
|
|
110
106
|
|
|
111
|
-
// define.js (and initMembers()) initializes annotations. If there is a duplicate, the
|
|
112
|
-
// annotation is an array in XSN.
|
|
113
|
-
if (prop[0] === '@' && Array.isArray(artifact[prop]))
|
|
114
|
-
chooseAndReportDuplicateAnnotation(artifact, prop);
|
|
115
|
-
|
|
116
107
|
if (artifact[prop] && Object.getPrototypeOf(artifact[prop]) === null)
|
|
117
108
|
// Dictionary in XSN
|
|
118
109
|
forEachGeneric(artifact, prop, art => resolveTypesForParseCdl(art, art));
|
|
@@ -172,12 +163,10 @@ function finalizeParseCdl( model ) {
|
|
|
172
163
|
const name = resolveUncheckedPath( artWithType.type, 'type', user );
|
|
173
164
|
const type = name && model.definitions[name] || { name: { absolute: name } };
|
|
174
165
|
resolveTypeArgumentsUnchecked( artWithType, type, user );
|
|
175
|
-
return;
|
|
176
166
|
}
|
|
177
167
|
else if (!user._main) {
|
|
178
168
|
error( 'ref-undefined-typeof', [ artWithType.type.location, user ], {},
|
|
179
169
|
'Current artifact has no element to refer to as type' );
|
|
180
|
-
return;
|
|
181
170
|
}
|
|
182
171
|
else if (root.id === '$self' || root.id === '$projection') {
|
|
183
172
|
setArtifactLink( root, user._main );
|
|
@@ -206,49 +195,6 @@ function finalizeParseCdl( model ) {
|
|
|
206
195
|
setArtifactLink( root, fake );
|
|
207
196
|
}
|
|
208
197
|
}
|
|
209
|
-
|
|
210
|
-
function chooseAndReportDuplicateAnnotation( artifact, annoName ) {
|
|
211
|
-
for (const anno of artifact[annoName])
|
|
212
|
-
message( 'anno-duplicate', [ anno.name.location, artifact ], { anno: annoName } );
|
|
213
|
-
|
|
214
|
-
// Choose any annotation, doesn't matter because of the error above.
|
|
215
|
-
artifact[annoName] = artifact[annoName][0];
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* For duplicate entries in `annotate {}` blocks, we de-duplicate the entries and merge them.
|
|
220
|
-
* Since it is allowed in "normal" compilations to have e.g.
|
|
221
|
-
* `annotate E with { @anno1 id, @anno2 id }`.
|
|
222
|
-
*
|
|
223
|
-
* @param {object} ext
|
|
224
|
-
*/
|
|
225
|
-
function mergeAnnotatesForSameArtifact( ext ) {
|
|
226
|
-
if (!ext || typeof ext !== 'object')
|
|
227
|
-
return;
|
|
228
|
-
|
|
229
|
-
forEachMember(ext, sub => mergeAnnotatesForSameArtifact(sub));
|
|
230
|
-
|
|
231
|
-
// do not do a complex merge:
|
|
232
|
-
if (isComplexExtension( ext ) ||
|
|
233
|
-
!Array.isArray( ext.$duplicates ) || ext.$duplicates.some( isComplexExtension ))
|
|
234
|
-
return;
|
|
235
|
-
for (const dup of ext.$duplicates) {
|
|
236
|
-
for (const prop in dup) {
|
|
237
|
-
if (prop.charAt(0) === '@')
|
|
238
|
-
dictAddArray( ext, prop, dup[prop] );
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
delete ext.$duplicates;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* We only de-duplicate an extend/annotate `ext` in function
|
|
247
|
-
* mergeAnnotatesForSameArtifact() if the extend/annotate is simple, i.e. has
|
|
248
|
-
* no members like elements.
|
|
249
|
-
*/
|
|
250
|
-
function isComplexExtension( ext ) {
|
|
251
|
-
return ext.kind !== 'annotate' || ext.elements || ext.parameters || ext.actions;
|
|
252
198
|
}
|
|
253
199
|
|
|
254
200
|
module.exports = finalizeParseCdl;
|
|
@@ -33,7 +33,7 @@ function kickStart( model ) {
|
|
|
33
33
|
const art = model.definitions[name];
|
|
34
34
|
if (!('_parent' in art))
|
|
35
35
|
return; // nothing to do for builtins and redefinitions
|
|
36
|
-
if (art.
|
|
36
|
+
if (art.query && !('_ancestors' in art))
|
|
37
37
|
setProjectionAncestors( art );
|
|
38
38
|
|
|
39
39
|
let parent = art._parent;
|
|
@@ -72,13 +72,12 @@ function kickStart( model ) {
|
|
|
72
72
|
// no need to set preferredRedirectionTarget in the while loop as we would
|
|
73
73
|
// use the projection having @cds.redirection.target anyhow instead of
|
|
74
74
|
// `art` anyway (if we do the no-x-service-implicit-redirection TODO above)
|
|
75
|
-
while (art &&
|
|
76
|
-
art.
|
|
77
|
-
(preferredRedirectionTarget || !annotationIsFalse( art['@cds.redirection.target'] ) )
|
|
78
|
-
art.query.op && art.query.op.val === 'SELECT') {
|
|
75
|
+
while (art?.query?.from?.path && // direct select with one source
|
|
76
|
+
art._ancestors !== 0 && // prevent inf-loop
|
|
77
|
+
(preferredRedirectionTarget || !annotationIsFalse( art['@cds.redirection.target'] ) )) {
|
|
79
78
|
chain.push( art );
|
|
80
|
-
setLink( art, '_ancestors',
|
|
81
|
-
const name = resolveUncheckedPath( art.
|
|
79
|
+
setLink( art, '_ancestors', 0 ); // avoid infloop with cyclic from
|
|
80
|
+
const name = resolveUncheckedPath( art.query.from, 'include', art ); // TODO: 'include'?
|
|
82
81
|
art = name && model.definitions[name];
|
|
83
82
|
if (autoexposed)
|
|
84
83
|
break; // only direct projection for auto-exposed
|