@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.
Files changed (70) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +3 -0
  3. package/bin/cdsc.js +9 -5
  4. package/doc/CHANGELOG_BETA.md +20 -2
  5. package/doc/CHANGELOG_DEPRECATED.md +2 -2
  6. package/lib/api/main.js +2 -1
  7. package/lib/api/options.js +3 -2
  8. package/lib/base/dictionaries.js +10 -0
  9. package/lib/base/message-registry.js +56 -12
  10. package/lib/base/messages.js +39 -20
  11. package/lib/base/model.js +1 -0
  12. package/lib/base/shuffle.js +2 -1
  13. package/lib/checks/elements.js +29 -1
  14. package/lib/checks/{emptyOrOnlyVirtual.js → hasPersistedElements.js} +9 -5
  15. package/lib/checks/nonexpandableStructured.js +1 -1
  16. package/lib/checks/onConditions.js +8 -5
  17. package/lib/checks/types.js +6 -1
  18. package/lib/checks/validator.js +7 -3
  19. package/lib/compiler/assert-consistency.js +20 -23
  20. package/lib/compiler/base.js +1 -2
  21. package/lib/compiler/builtins.js +2 -2
  22. package/lib/compiler/checks.js +237 -242
  23. package/lib/compiler/define.js +63 -75
  24. package/lib/compiler/extend.js +325 -22
  25. package/lib/compiler/finalize-parse-cdl.js +1 -55
  26. package/lib/compiler/kick-start.js +6 -7
  27. package/lib/compiler/populate.js +284 -288
  28. package/lib/compiler/propagator.js +15 -13
  29. package/lib/compiler/resolve.js +136 -306
  30. package/lib/compiler/shared.js +42 -44
  31. package/lib/compiler/tweak-assocs.js +29 -27
  32. package/lib/compiler/utils.js +29 -3
  33. package/lib/edm/annotations/genericTranslation.js +7 -13
  34. package/lib/edm/annotations/preprocessAnnotations.js +3 -0
  35. package/lib/edm/csn2edm.js +0 -4
  36. package/lib/edm/edm.js +6 -4
  37. package/lib/edm/edmAnnoPreprocessor.js +1 -0
  38. package/lib/edm/edmPreprocessor.js +1 -5
  39. package/lib/gen/Dictionary.json +34 -2
  40. package/lib/gen/language.checksum +1 -1
  41. package/lib/gen/language.interp +1 -1
  42. package/lib/gen/languageParser.js +2429 -2401
  43. package/lib/inspect/inspectPropagation.js +2 -0
  44. package/lib/json/from-csn.js +87 -41
  45. package/lib/json/to-csn.js +47 -16
  46. package/lib/language/errorStrategy.js +1 -0
  47. package/lib/language/genericAntlrParser.js +109 -28
  48. package/lib/language/language.g4 +20 -4
  49. package/lib/model/csnRefs.js +30 -2
  50. package/lib/model/csnUtils.js +1 -0
  51. package/lib/model/revealInternalProperties.js +1 -2
  52. package/lib/modelCompare/compare.js +2 -1
  53. package/lib/optionProcessor.js +3 -0
  54. package/lib/render/manageConstraints.js +5 -2
  55. package/lib/render/toCdl.js +20 -7
  56. package/lib/render/toHdbcds.js +2 -8
  57. package/lib/render/toSql.js +6 -5
  58. package/lib/render/utils/common.js +9 -5
  59. package/lib/transform/db/assertUnique.js +2 -1
  60. package/lib/transform/db/expansion.js +2 -0
  61. package/lib/transform/db/flattening.js +37 -36
  62. package/lib/transform/db/rewriteCalculatedElements.js +559 -0
  63. package/lib/transform/db/transformExists.js +15 -6
  64. package/lib/transform/db/views.js +40 -37
  65. package/lib/transform/forRelationalDB.js +44 -30
  66. package/lib/transform/odata/typesExposure.js +50 -15
  67. package/lib/transform/parseExpr.js +14 -8
  68. package/lib/transform/transformUtilsNew.js +6 -5
  69. package/lib/transform/translateAssocsToJoins.js +49 -33
  70. package/package.json +1 -1
@@ -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
- splitIntoPath, isDirectComposition,
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
- copyAnnotationsForExtensions,
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 > 2 )) { // >2: no self-include
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 = 3;
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
- const isView = !!target.query;
671
- for (const ref of art.includes) {
672
- const template = resolvePath( ref, isView ? 'viewInclude' : 'include', art );
673
- if (template && template.name.absolute in extensionsDict)
674
- return false;
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
- includeMembers( ext, art, 'elements' );
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 is just one assignment
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.length > 1 };
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.length === 1 );
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 { dictAddArray } = require('../base/dictionaries');
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._from && !('_ancestors' in 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 && !('_ancestors' in art) &&
76
- art._from && art._from.length === 1 &&
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', null ); // avoid infloop with cyclic from
81
- const name = resolveUncheckedPath( art._from[0], 'include', art ); // TODO: 'include'?
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