@sap/cds-compiler 6.5.0 → 6.6.0
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 +34 -0
- package/bin/cdsc.js +1 -1
- package/lib/api/main.js +0 -3
- package/lib/base/builtins.js +2 -1
- package/lib/base/message-registry.js +13 -14
- package/lib/base/model.js +0 -1
- package/lib/checks/sql-snippets.js +8 -0
- package/lib/checks/validator.js +4 -2
- package/lib/compiler/define.js +102 -115
- package/lib/compiler/extend.js +67 -37
- package/lib/compiler/finalize-parse-cdl.js +1 -1
- package/lib/compiler/generate.js +34 -11
- package/lib/compiler/index.js +2 -2
- package/lib/compiler/kick-start.js +26 -35
- package/lib/compiler/populate.js +4 -7
- package/lib/compiler/propagator.js +20 -1
- package/lib/compiler/resolve.js +26 -1
- package/lib/compiler/shared.js +49 -9
- package/lib/compiler/utils.js +2 -2
- package/lib/edm/annotations/edmJson.js +111 -37
- package/lib/edm/annotations/genericTranslation.js +3 -1
- package/lib/main.d.ts +0 -3
- package/lib/main.js +2 -0
- package/lib/model/csnRefs.js +6 -1
- package/lib/render/toSql.js +0 -4
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/associations.js +24 -15
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/views.js +0 -39
- package/lib/transform/effective/associations.js +5 -55
- package/lib/transform/effective/main.js +4 -2
- package/lib/transform/effective/misc.js +1 -1
- package/lib/transform/effective/types.js +36 -12
- package/lib/transform/forOdata.js +126 -3
- package/lib/transform/forRelationalDB.js +13 -4
- package/lib/transform/transformUtils.js +51 -1
- package/lib/transform/translateAssocsToJoins.js +43 -19
- package/package.json +1 -1
package/lib/compiler/extend.js
CHANGED
|
@@ -78,6 +78,7 @@ function extend( model ) {
|
|
|
78
78
|
extendArtifactBefore,
|
|
79
79
|
extendArtifactAfter,
|
|
80
80
|
extendForeignKeys,
|
|
81
|
+
withLocalizedData,
|
|
81
82
|
applyIncludes, // TODO: re-check
|
|
82
83
|
} );
|
|
83
84
|
|
|
@@ -864,19 +865,18 @@ function extend( model ) {
|
|
|
864
865
|
}
|
|
865
866
|
else if (!dict?.[name]) {
|
|
866
867
|
// TODO: make variant `returns` an auto-variant for ($ART) ?
|
|
867
|
-
const inReturns = parent._parent?.returns
|
|
868
|
-
const art = inReturns || parent;
|
|
868
|
+
const inReturns = parent._parent?.returns;
|
|
869
869
|
switch (prop) {
|
|
870
870
|
case 'elements':
|
|
871
871
|
if (canBeDraftMember( name, parent, draftElements ))
|
|
872
872
|
break;
|
|
873
873
|
notFound( `ext-undefined-element${ securityRelevant }`, ext.name.location, ext,
|
|
874
|
-
{ '#': (inReturns ? 'returns' : 'element'),
|
|
874
|
+
{ '#': (inReturns ? 'returns' : 'element'), name },
|
|
875
875
|
parent.elements );
|
|
876
876
|
break;
|
|
877
877
|
case 'enum': // TODO: extra msg id?
|
|
878
878
|
notFound( `ext-undefined-element${ securityRelevant }`, ext.name.location, ext,
|
|
879
|
-
{ '#': (inReturns ? 'enum-returns' : 'enum'),
|
|
879
|
+
{ '#': (inReturns ? 'enum-returns' : 'enum'), name },
|
|
880
880
|
parent.enum );
|
|
881
881
|
break;
|
|
882
882
|
case 'foreignKeys':
|
|
@@ -885,14 +885,14 @@ function extend( model ) {
|
|
|
885
885
|
break;
|
|
886
886
|
case 'params':
|
|
887
887
|
notFound( `ext-undefined-param${ securityRelevant }`, ext.name.location, ext,
|
|
888
|
-
{ '#': 'param',
|
|
888
|
+
{ '#': 'param', name },
|
|
889
889
|
parent.params );
|
|
890
890
|
break;
|
|
891
891
|
case 'actions':
|
|
892
892
|
if (canBeDraftMember( name, parent, draftBoundActions ))
|
|
893
893
|
break;
|
|
894
894
|
notFound( `ext-undefined-action${ securityRelevant }`, ext.name.location, ext,
|
|
895
|
-
{ '#': 'action',
|
|
895
|
+
{ '#': 'action', name },
|
|
896
896
|
parent.actions );
|
|
897
897
|
break;
|
|
898
898
|
default:
|
|
@@ -941,32 +941,28 @@ function extend( model ) {
|
|
|
941
941
|
}
|
|
942
942
|
|
|
943
943
|
function checkRemainingMainExtensions( art, ext ) {
|
|
944
|
-
const refCtx =
|
|
945
|
-
? 'annotate-sec'
|
|
946
|
-
: ext.kind;
|
|
944
|
+
const refCtx = extensionRefContext( ext );
|
|
947
945
|
if (!resolvePath( ext.name, refCtx, ext )) // error for extend, info for annotate
|
|
948
946
|
return;
|
|
949
947
|
|
|
950
|
-
if (art?.builtin) {
|
|
948
|
+
if (art?.builtin) { // TODO: do via accept
|
|
951
949
|
info( 'anno-builtin', [ ext.name.location, ext ], {} ); // TODO: better location?
|
|
952
950
|
}
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
//
|
|
963
|
-
//
|
|
964
|
-
if (
|
|
965
|
-
|
|
966
|
-
'#': 'namespace', art: ext,
|
|
967
|
-
} );
|
|
968
|
-
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
function extensionRefContext( ext ) {
|
|
954
|
+
if (ext.kind === 'annotate') {
|
|
955
|
+
if (hasSecurityAnno( ext ))
|
|
956
|
+
return 'annotate-sec';
|
|
957
|
+
}
|
|
958
|
+
else if (ext.artifacts || // extend … with definitions
|
|
959
|
+
ext._block.$frontend === 'json' && !ext.elements && !ext.actions) {
|
|
960
|
+
// TODO: not for `extend context` and `extend service` → !ext.expectedKind
|
|
961
|
+
// TODO v7: also fully with CSN input
|
|
962
|
+
if (!ext.doc && !Object.keys( ext ).some( a => a.charAt(0) === '@') )
|
|
963
|
+
return 'annotate'; // TODO: or an extra refCtx ?
|
|
969
964
|
}
|
|
965
|
+
return ext.kind;
|
|
970
966
|
}
|
|
971
967
|
|
|
972
968
|
// Issue messages for annotations on namespaces and builtins
|
|
@@ -1213,11 +1209,9 @@ function extend( model ) {
|
|
|
1213
1209
|
*
|
|
1214
1210
|
* If not for extensions: construct === parent
|
|
1215
1211
|
*
|
|
1216
|
-
* Param `initExtensions` is for parse.cdl - TODO delete
|
|
1217
|
-
*
|
|
1218
1212
|
* TODO: separate extension!
|
|
1219
1213
|
*/
|
|
1220
|
-
function initMembers( construct, parent, block
|
|
1214
|
+
function initMembers( construct, parent, block ) {
|
|
1221
1215
|
// TODO: split extend from init
|
|
1222
1216
|
const main = parent._main || parent;
|
|
1223
1217
|
const isQueryExtension = construct.kind === 'extend' && main.query;
|
|
@@ -1347,9 +1341,12 @@ function extend( model ) {
|
|
|
1347
1341
|
elem.name = { id: name, location: elem.location };
|
|
1348
1342
|
}
|
|
1349
1343
|
}
|
|
1344
|
+
|
|
1345
|
+
if (!elem._block)
|
|
1346
|
+
setLink( elem, '_block', block );
|
|
1350
1347
|
// if (!kindProperties[ elem.kind ]) console.log(elem.kind,elem.name)
|
|
1351
|
-
if ((elem.kind === 'extend' || elem.kind === 'annotate')
|
|
1352
|
-
storeExtension( elem, name, prop, parent
|
|
1348
|
+
if ((elem.kind === 'extend' || elem.kind === 'annotate')) {
|
|
1349
|
+
storeExtension( elem, name, prop, parent );
|
|
1353
1350
|
return;
|
|
1354
1351
|
}
|
|
1355
1352
|
if (isQueryExtension && elem.kind === 'element') {
|
|
@@ -1359,8 +1356,6 @@ function extend( model ) {
|
|
|
1359
1356
|
return;
|
|
1360
1357
|
}
|
|
1361
1358
|
|
|
1362
|
-
const bl = elem._block || block;
|
|
1363
|
-
setLink( elem, '_block', bl );
|
|
1364
1359
|
const existing = parent[prop]?.[name];
|
|
1365
1360
|
const add = construct !== parent && (!existing || elem.$inferred !== 'include');
|
|
1366
1361
|
// don't dump with `entity T {}; extend T with { extend e {}; e {}; e {} };`:
|
|
@@ -1368,7 +1363,7 @@ function extend( model ) {
|
|
|
1368
1363
|
elem.$duplicates = null;
|
|
1369
1364
|
setMemberParent( elem, name, parent, add && prop );
|
|
1370
1365
|
checkRedefinition( elem );
|
|
1371
|
-
initMembers( elem, elem,
|
|
1366
|
+
initMembers( elem, elem, elem._block );
|
|
1372
1367
|
if (elem.kind === 'action' || elem.kind === 'function')
|
|
1373
1368
|
initBoundSelfParam( elem.params, elem._main );
|
|
1374
1369
|
|
|
@@ -1514,7 +1509,7 @@ function extend( model ) {
|
|
|
1514
1509
|
* Sets `_ancestors` links on `art`.
|
|
1515
1510
|
*
|
|
1516
1511
|
* TODO: try to set `_ancestors` only to entities (but beware “intermediate”
|
|
1517
|
-
* non-entities).
|
|
1512
|
+
* non-entities - TODO: make intermediate non-entities stop chain).
|
|
1518
1513
|
*
|
|
1519
1514
|
* Examples:
|
|
1520
1515
|
* ext === art: `entity E : F {}` => add elements of F to E
|
|
@@ -1532,10 +1527,11 @@ function extend( model ) {
|
|
|
1532
1527
|
|
|
1533
1528
|
if (!art._ancestors && !art.query)
|
|
1534
1529
|
setLink( art, '_ancestors', [] ); // recursive array of includes
|
|
1530
|
+
const shouldSetAncestors = art.kind === 'entity' && !art.query;
|
|
1535
1531
|
for (const ref of ext.includes) {
|
|
1536
1532
|
const template = resolvePath( ref, 'include', art );
|
|
1537
1533
|
// !template -> non-includable, e.g. scalar type, or cyclic
|
|
1538
|
-
if (template &&
|
|
1534
|
+
if (template?.kind === 'entity' && shouldSetAncestors) {
|
|
1539
1535
|
if (template._ancestors)
|
|
1540
1536
|
art._ancestors.push( ...template._ancestors );
|
|
1541
1537
|
art._ancestors.push( template );
|
|
@@ -1583,8 +1579,17 @@ function extend( model ) {
|
|
|
1583
1579
|
if (template[prop] && !ext[prop])
|
|
1584
1580
|
ext[prop] = Object.create( null );
|
|
1585
1581
|
const location = weakRefLocation( ref );
|
|
1582
|
+
|
|
1583
|
+
// prevent recompilation issue when localized entity is included in aspect or
|
|
1584
|
+
// entity without keys (via shadowing):
|
|
1585
|
+
const checkForLocalized = prop === 'elements' &&
|
|
1586
|
+
template.kind === 'entity' && !template.query &&
|
|
1587
|
+
withLocalizedData( template, ext );
|
|
1588
|
+
|
|
1586
1589
|
// eslint-disable-next-line no-loop-func
|
|
1587
1590
|
forEachInOrder( template, prop, ( origin, name ) => {
|
|
1591
|
+
if (checkForLocalized && (name === 'texts' || name === 'localized'))
|
|
1592
|
+
return;
|
|
1588
1593
|
if (members && members[name]) {
|
|
1589
1594
|
if (!includesNonShadowedFirst && !ext[prop][name])
|
|
1590
1595
|
dictAdd( ext[prop], name, members[name] ); // to keep order
|
|
@@ -1603,7 +1608,7 @@ function extend( model ) {
|
|
|
1603
1608
|
if (origin.value && origin.$syntax === 'calc') {
|
|
1604
1609
|
// TODO: If paths become invalid in the new artifact, should we mark
|
|
1605
1610
|
// all usages in the expressions? Possibly just the first one?
|
|
1606
|
-
// TODO: Unify with
|
|
1611
|
+
// TODO: Unify with other code in extend.js
|
|
1607
1612
|
elem.value = Object.assign( { $inferred: 'include' }, copyExpr( origin.value ));
|
|
1608
1613
|
elem.$syntax = 'calc';
|
|
1609
1614
|
createAndLinkCalcDepElement( elem );
|
|
@@ -1630,6 +1635,31 @@ function extend( model ) {
|
|
|
1630
1635
|
}
|
|
1631
1636
|
}
|
|
1632
1637
|
|
|
1638
|
+
/**
|
|
1639
|
+
* Return true if include (with `name` and `elements`) in `ext` highly likely
|
|
1640
|
+
* contain elements `texts` and `localized` which are compiler-generated for
|
|
1641
|
+
* localized data. Due to recompilation, we cannot just check the `$inferred`
|
|
1642
|
+
* property in the XSN, but need to apply an heuristics.
|
|
1643
|
+
*
|
|
1644
|
+
* It is as follows: the elements `texts` and `localized` in entity `‹E›` and
|
|
1645
|
+
* the entity `‹E›.texts` are considered compiler-generated for Localized Data if
|
|
1646
|
+
*
|
|
1647
|
+
* - `‹E›.texts` has a key element `locale`
|
|
1648
|
+
* - `texts` of `‹E›` is an unmanaged composition to `‹E›.texts`
|
|
1649
|
+
* - `localized` of `‹E›` is an unmanaged association to `‹E›.texts`
|
|
1650
|
+
*/
|
|
1651
|
+
function withLocalizedData( { name, elements }, ext ) {
|
|
1652
|
+
const textsEntityName = `${ name.id }.texts`;
|
|
1653
|
+
if (!model.definitions[textsEntityName]?.elements?.locale?.key?.val)
|
|
1654
|
+
return false;
|
|
1655
|
+
const { texts, localized } = elements ?? {};
|
|
1656
|
+
return texts?.target && localized?.target && texts.on && localized.on &&
|
|
1657
|
+
resolveUncheckedPath( texts.target, 'target', ext ) === textsEntityName &&
|
|
1658
|
+
resolveUncheckedPath( localized.target, 'target', ext ) === textsEntityName &&
|
|
1659
|
+
resolveUncheckedPath( texts.type, 'type', ext ) === 'cds.Composition' &&
|
|
1660
|
+
resolveUncheckedPath( localized.type, 'type', ext ) === 'cds.Association';
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1633
1663
|
/**
|
|
1634
1664
|
* Report duplicates in parent[prop] that happen due to multiple includes having the
|
|
1635
1665
|
* same member. Covers `entity G : E, G {};` but not `entity G : E {}; extend G with F;`.
|
|
@@ -42,7 +42,7 @@ function finalizeParseCdl( model ) {
|
|
|
42
42
|
for (const ext of late[name]._extensions) {
|
|
43
43
|
ext.name.id = resolveUncheckedPath( ext.name, '_uncheckedExtension', ext );
|
|
44
44
|
// Initialize members and define annotations in sub-elements.
|
|
45
|
-
initMembers( ext
|
|
45
|
+
initMembers( ext );
|
|
46
46
|
extensions.push( ext );
|
|
47
47
|
}
|
|
48
48
|
}
|
package/lib/compiler/generate.js
CHANGED
|
@@ -31,7 +31,7 @@ function generate( model ) {
|
|
|
31
31
|
const {
|
|
32
32
|
resolvePath,
|
|
33
33
|
resolveUncheckedPath,
|
|
34
|
-
|
|
34
|
+
initMainArtifact,
|
|
35
35
|
extendArtifactBefore,
|
|
36
36
|
applyIncludes,
|
|
37
37
|
} = model.$functions;
|
|
@@ -173,8 +173,8 @@ function generate( model ) {
|
|
|
173
173
|
const localized = localizedData( art, textsEntity, fioriEnabled );
|
|
174
174
|
if (!localized)
|
|
175
175
|
return;
|
|
176
|
-
if (textsEntity
|
|
177
|
-
return;
|
|
176
|
+
if (textsEntity && textsEntity.kind !== 'namespace')
|
|
177
|
+
return; // expanded texts entity in source -> nothing to do
|
|
178
178
|
createTextsEntity( art, textsName, localized, fioriEnabled );
|
|
179
179
|
addTextsAssociations( art, textsName, localized );
|
|
180
180
|
}
|
|
@@ -236,7 +236,10 @@ function generate( model ) {
|
|
|
236
236
|
return false;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
if (textsEntity) {
|
|
239
|
+
if (textsEntity?.kind === 'namespace') { // namespace Base.texts
|
|
240
|
+
textsEntity = null;
|
|
241
|
+
}
|
|
242
|
+
else if (textsEntity) {
|
|
240
243
|
if (textsEntity.$duplicates)
|
|
241
244
|
return false;
|
|
242
245
|
if (textsEntity.kind !== 'entity' || textsEntity.query ||
|
|
@@ -283,15 +286,19 @@ function generate( model ) {
|
|
|
283
286
|
*/
|
|
284
287
|
function createTextsEntity( base, absolute, textElems, fioriEnabled ) {
|
|
285
288
|
const location = weakLocation( base.elements[$location] || base.location );
|
|
286
|
-
|
|
289
|
+
let art = {
|
|
287
290
|
kind: 'entity',
|
|
288
291
|
name: { id: absolute, location },
|
|
289
292
|
location,
|
|
290
293
|
elements: Object.create( null ),
|
|
291
294
|
$inferred: 'localized-entity',
|
|
292
295
|
};
|
|
296
|
+
const gap = model.definitions[absolute];
|
|
297
|
+
if (gap)
|
|
298
|
+
art = Object.assign( gap, art );
|
|
299
|
+
else
|
|
300
|
+
model.definitions[absolute] = art;
|
|
293
301
|
setLink( art, '_block', model.$internal );
|
|
294
|
-
model.definitions[absolute] = art;
|
|
295
302
|
extendArtifactBefore( art ); // having extensions here would be wrong
|
|
296
303
|
|
|
297
304
|
if (!fioriEnabled) {
|
|
@@ -340,7 +347,12 @@ function generate( model ) {
|
|
|
340
347
|
for (const orig of textElems)
|
|
341
348
|
addElementToTextsEntity( orig, art, fioriEnabled, assertUniqueValue );
|
|
342
349
|
|
|
343
|
-
|
|
350
|
+
initMainArtifact( art );
|
|
351
|
+
// do the kick-start relevant stuff: _service, there are no _ancestors,
|
|
352
|
+
// _descendants would have been set already for a gap artifact
|
|
353
|
+
setLink( art, '_service', art._parent._service );
|
|
354
|
+
model.$compositionTargets[absolute] = true;
|
|
355
|
+
|
|
344
356
|
if (art.includes) {
|
|
345
357
|
// add elements `locale`, etc. which are required below.
|
|
346
358
|
applyIncludes( art, art ); // TODO: rethink - can we avoid this if only new extend?
|
|
@@ -641,7 +653,8 @@ function generate( model ) {
|
|
|
641
653
|
'An aspect $(TARGET) with an element named $(NAME) can\'t be used as target' );
|
|
642
654
|
return false;
|
|
643
655
|
}
|
|
644
|
-
|
|
656
|
+
const place = model.definitions[entityName];
|
|
657
|
+
if (place && place.kind !== 'namespace') {
|
|
645
658
|
error( null, [ location, elem ], { art: entityName },
|
|
646
659
|
// eslint-disable-next-line @stylistic/max-len
|
|
647
660
|
'Target entity $(ART) can\'t be created as there is another definition with this name' );
|
|
@@ -687,7 +700,7 @@ function generate( model ) {
|
|
|
687
700
|
$inferred: 'aspect-composition',
|
|
688
701
|
};
|
|
689
702
|
|
|
690
|
-
|
|
703
|
+
let art = {
|
|
691
704
|
kind: 'entity',
|
|
692
705
|
name: {
|
|
693
706
|
id: entityName,
|
|
@@ -698,6 +711,12 @@ function generate( model ) {
|
|
|
698
711
|
elements: Object.create( null ),
|
|
699
712
|
$inferred: 'composition-entity',
|
|
700
713
|
};
|
|
714
|
+
const gap = model.definitions[entityName];
|
|
715
|
+
if (gap)
|
|
716
|
+
art = Object.assign( gap, art );
|
|
717
|
+
else
|
|
718
|
+
model.definitions[entityName] = art;
|
|
719
|
+
|
|
701
720
|
if (target.name) { // named target aspect
|
|
702
721
|
if (!isDeprecatedEnabled( options, 'noCompositionIncludes' )) {
|
|
703
722
|
art.includes = [ createInclude( target.name.id, location ) ];
|
|
@@ -741,8 +760,12 @@ function generate( model ) {
|
|
|
741
760
|
addProxyElements( art, target.elements, 'aspect-composition', enforceLocation && location );
|
|
742
761
|
|
|
743
762
|
setLink( art, '_block', model.$internal );
|
|
744
|
-
|
|
745
|
-
|
|
763
|
+
initMainArtifact( art );
|
|
764
|
+
|
|
765
|
+
// do the kick-start relevant stuff: _service, there are no _ancestors,
|
|
766
|
+
// _descendants would have been set already for a gap artifact
|
|
767
|
+
setLink( art, '_service', art._parent._service );
|
|
768
|
+
model.$compositionTargets[entityName] = true;
|
|
746
769
|
|
|
747
770
|
// Apply annotations to generated artifact, prepare (not apply!) element
|
|
748
771
|
// annotations (remark: adding elements is not allowed for generated artifacts):
|
package/lib/compiler/index.js
CHANGED
|
@@ -495,8 +495,8 @@ function compileDoXSync( model ) {
|
|
|
495
495
|
return model;
|
|
496
496
|
}
|
|
497
497
|
extend( model );
|
|
498
|
-
generate( model );
|
|
499
498
|
kickStart( model );
|
|
499
|
+
generate( model );
|
|
500
500
|
populate( model );
|
|
501
501
|
|
|
502
502
|
model.definitions = model.$functions.shuffleDict( model.definitions );
|
|
@@ -546,7 +546,7 @@ async function compileDoX( model ) {
|
|
|
546
546
|
return model;
|
|
547
547
|
}
|
|
548
548
|
|
|
549
|
-
for (const phase of [ extend,
|
|
549
|
+
for (const phase of [ extend, kickStart, generate, populate ]) {
|
|
550
550
|
phase( model );
|
|
551
551
|
// eslint-disable-next-line no-await-in-loop
|
|
552
552
|
await checkAsyncAbortFlag( options.abortSignal );
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
|
+
const { builtinLocation } = require('../base/location');
|
|
5
6
|
const { isBetaEnabled, forEachGeneric } = require('../base/model');
|
|
6
7
|
const {
|
|
7
8
|
setLink,
|
|
@@ -14,14 +15,12 @@ function kickStart( model ) {
|
|
|
14
15
|
const { options } = model;
|
|
15
16
|
const { message } = model.$messageFunctions;
|
|
16
17
|
|
|
17
|
-
const { resolveUncheckedPath,
|
|
18
|
+
const { resolveUncheckedPath, initMainArtifact } = model.$functions;
|
|
18
19
|
|
|
19
20
|
// Set _service link (sorted to set it on parent first). Could be set
|
|
20
21
|
// directly, but beware a namespace becoming a service later.
|
|
21
22
|
Object.keys( model.definitions ).sort().forEach( setAncestorsAndService );
|
|
22
23
|
forEachGeneric( model, 'definitions', postProcessArtifact );
|
|
23
|
-
|
|
24
|
-
forEachGeneric( model, 'sources', resolveUsings );
|
|
25
24
|
return;
|
|
26
25
|
|
|
27
26
|
|
|
@@ -32,6 +31,9 @@ function kickStart( model ) {
|
|
|
32
31
|
* - service: the artifact of the embedding service
|
|
33
32
|
* This function must be called ordered: parent first
|
|
34
33
|
*
|
|
34
|
+
* Remark: _service links are not already set in define.js, because we might
|
|
35
|
+
* have a @cds.redirection.service in the future
|
|
36
|
+
*
|
|
35
37
|
* @param {string} name Artifact name
|
|
36
38
|
*/
|
|
37
39
|
function setAncestorsAndService( name ) {
|
|
@@ -50,16 +52,12 @@ function kickStart( model ) {
|
|
|
50
52
|
return;
|
|
51
53
|
// To be removed when nested services are allowed
|
|
52
54
|
if (!isBetaEnabled( options, 'nestedServices' ) && art.kind === 'service') {
|
|
53
|
-
|
|
54
|
-
parent = parent._parent;
|
|
55
|
-
message( 'service-nested-service', [ art.name.location, art ], { art: parent },
|
|
55
|
+
message( 'service-nested-service', [ art.name.location, art ], { art: service },
|
|
56
56
|
'A service can\'t be nested within a service $(ART)' );
|
|
57
57
|
}
|
|
58
58
|
else if (art.kind === 'context') {
|
|
59
|
-
while (parent.kind !== 'service')
|
|
60
|
-
parent = parent._parent;
|
|
61
59
|
// TODO: remove this error
|
|
62
|
-
message( 'service-nested-context', [ art.name.location, art ], { art:
|
|
60
|
+
message( 'service-nested-context', [ art.name.location, art ], { art: service },
|
|
63
61
|
'A context can\'t be nested within a service $(ART)' );
|
|
64
62
|
}
|
|
65
63
|
}
|
|
@@ -73,7 +71,9 @@ function kickStart( model ) {
|
|
|
73
71
|
// Service1.E = projection on E
|
|
74
72
|
|
|
75
73
|
// Remark: _ancestors are also set with includes, and there also for aspects,
|
|
76
|
-
// types and events
|
|
74
|
+
// types and events (TODO: entity only)
|
|
75
|
+
//
|
|
76
|
+
// Remark: _ancestors are also tested in populate.js for minmal exposure
|
|
77
77
|
const chain = [];
|
|
78
78
|
const autoexposed = annotationVal( art['@cds.autoexposed'] );
|
|
79
79
|
// no need to set preferredRedirectionTarget in the while loop as we would
|
|
@@ -84,7 +84,7 @@ function kickStart( model ) {
|
|
|
84
84
|
chain.push( art );
|
|
85
85
|
setLink( art, '_ancestors', 0 ); // avoid infloop with cyclic from
|
|
86
86
|
const name = resolveUncheckedPath( art.query.from, 'from', art );
|
|
87
|
-
art = name && model.definitions[name];
|
|
87
|
+
art = name && (model.definitions[name] || createGapArtifact( name ));
|
|
88
88
|
if (autoexposed)
|
|
89
89
|
break; // only direct projection for auto-exposed
|
|
90
90
|
}
|
|
@@ -97,6 +97,18 @@ function kickStart( model ) {
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
function createGapArtifact( name, location = builtinLocation() ) {
|
|
101
|
+
// TODO: make it probably part of define.js
|
|
102
|
+
// TODO: make it work without location (or value undefined/null)
|
|
103
|
+
// TODO: change the location later if overwritten
|
|
104
|
+
const art = {
|
|
105
|
+
kind: 'namespace', name: { id: name, location }, location,
|
|
106
|
+
};
|
|
107
|
+
model.definitions[name] = art;
|
|
108
|
+
initMainArtifact( art );
|
|
109
|
+
return art;
|
|
110
|
+
}
|
|
111
|
+
|
|
100
112
|
function postProcessArtifact( art ) {
|
|
101
113
|
tagCompositionTargets( art );
|
|
102
114
|
if (art.$queries) {
|
|
@@ -127,40 +139,19 @@ function kickStart( model ) {
|
|
|
127
139
|
}
|
|
128
140
|
|
|
129
141
|
function tagCompositionTargets( elem ) {
|
|
142
|
+
// TODO: together with test for targetIsTargetAspect()
|
|
130
143
|
if (elem.target && isDirectComposition( elem )) {
|
|
131
144
|
// A target aspect would have already moved to property `targetAspect` in
|
|
132
145
|
// define.js (hm... more something for kick-start.js...)
|
|
133
146
|
// TODO: for safety, just use resolveUncheckedPath()
|
|
134
|
-
const target =
|
|
147
|
+
const target = resolveUncheckedPath( elem.target, 'target', elem );
|
|
135
148
|
if (target)
|
|
136
|
-
model.$compositionTargets[target
|
|
149
|
+
model.$compositionTargets[target] = true;
|
|
137
150
|
}
|
|
138
151
|
if (elem.targetAspect?.elements)
|
|
139
152
|
elem = elem.targetAspect;
|
|
140
153
|
forEachGeneric( elem, 'elements', tagCompositionTargets );
|
|
141
154
|
}
|
|
142
|
-
|
|
143
|
-
// Resolve the using declarations in `using`. Issue
|
|
144
|
-
// error message if the referenced artifact does not exist.
|
|
145
|
-
// TODO: think of moving this to resolve.js
|
|
146
|
-
function resolveUsings( src, topLevel ) {
|
|
147
|
-
if (!src.usings)
|
|
148
|
-
return;
|
|
149
|
-
for (const def of src.usings) {
|
|
150
|
-
if (def.usings) // using {...}
|
|
151
|
-
resolveUsings( def );
|
|
152
|
-
if (!def.name || !def.name.id)
|
|
153
|
-
continue; // using {...}, parse error
|
|
154
|
-
const art = model.definitions[def.name.absolute];
|
|
155
|
-
if (art && art.$duplicates)
|
|
156
|
-
continue;
|
|
157
|
-
const ref = def.extern;
|
|
158
|
-
const user = (topLevel ? def : src);
|
|
159
|
-
const from = user.fileDep;
|
|
160
|
-
if (art || !from || from.realname) // no error for non-existing ref with non-existing module
|
|
161
|
-
resolvePath( ref, 'using', def ); // TODO: consider FROM for validNames
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
155
|
}
|
|
165
156
|
|
|
166
157
|
module.exports = kickStart;
|
package/lib/compiler/populate.js
CHANGED
|
@@ -75,7 +75,7 @@ function populate( model ) {
|
|
|
75
75
|
resolvePath,
|
|
76
76
|
nestedElements,
|
|
77
77
|
attachAndEmitValidNames,
|
|
78
|
-
|
|
78
|
+
initMainArtifact,
|
|
79
79
|
extendArtifactBefore,
|
|
80
80
|
extendArtifactAfter,
|
|
81
81
|
} = model.$functions;
|
|
@@ -1113,9 +1113,7 @@ function populate( model ) {
|
|
|
1113
1113
|
// To avoid repeated messages: if already tried to do autoexposure, return
|
|
1114
1114
|
// auto-exposed entity when successful, or `target` otherwise (no/failed autoexposure)
|
|
1115
1115
|
function minimalExposure( target, service, elemScope ) {
|
|
1116
|
-
const descendants = scopedExposure( target._descendants
|
|
1117
|
-
target._descendants[service.name.id] ||
|
|
1118
|
-
[],
|
|
1116
|
+
const descendants = scopedExposure( target._descendants?.[service.name.id] || [],
|
|
1119
1117
|
elemScope, target );
|
|
1120
1118
|
const preferred = descendants.filter( d => annotationVal( d['@cds.redirection.target'] ) );
|
|
1121
1119
|
const exposed = preferred.length ? preferred : descendants;
|
|
@@ -1172,7 +1170,7 @@ function populate( model ) {
|
|
|
1172
1170
|
kind: 'namespace', name: { id: autoScopeName, location }, location,
|
|
1173
1171
|
};
|
|
1174
1172
|
model.definitions[autoScopeName] = nullScope;
|
|
1175
|
-
|
|
1173
|
+
initMainArtifact( nullScope );
|
|
1176
1174
|
return nullScope;
|
|
1177
1175
|
}
|
|
1178
1176
|
|
|
@@ -1207,7 +1205,6 @@ function populate( model ) {
|
|
|
1207
1205
|
function isDirectProjection( proj, base ) {
|
|
1208
1206
|
return proj.kind === 'entity' && // not event
|
|
1209
1207
|
// direct proj (TODO: or should we add them to another list?)
|
|
1210
|
-
// TODO: delete ENTITY._from - maybe not...
|
|
1211
1208
|
proj.query && proj.query.op && proj.query.op.val === 'SELECT' &&
|
|
1212
1209
|
proj._from && proj._from.length === 1 &&
|
|
1213
1210
|
base === resolvePath( proj._from[0], 'from', proj.query );
|
|
@@ -1354,7 +1351,7 @@ function populate( model ) {
|
|
|
1354
1351
|
}
|
|
1355
1352
|
setLink( art, '_service', service );
|
|
1356
1353
|
setLink( art, '_block', model.$internal );
|
|
1357
|
-
|
|
1354
|
+
initMainArtifact( art, !!autoexposed );
|
|
1358
1355
|
effectiveType( art );
|
|
1359
1356
|
// TODO: try to set locations of elements locations of orig target elements
|
|
1360
1357
|
newAutoExposed.push( art );
|
|
@@ -47,7 +47,7 @@ function propagate( model ) {
|
|
|
47
47
|
precision: always,
|
|
48
48
|
scale: always,
|
|
49
49
|
srid: always,
|
|
50
|
-
localized
|
|
50
|
+
localized,
|
|
51
51
|
target: notWithExpand,
|
|
52
52
|
targetAspect,
|
|
53
53
|
cardinality: notWithExpand,
|
|
@@ -78,6 +78,7 @@ function propagate( model ) {
|
|
|
78
78
|
const { rewriteAnnotationsRefs, rewriteRefsInExpression } = xprRewriteFns( model );
|
|
79
79
|
|
|
80
80
|
const { message, throwWithError } = model.$messageFunctions;
|
|
81
|
+
const { withLocalizedData } = model.$functions;
|
|
81
82
|
|
|
82
83
|
forEachDefinition( model, run );
|
|
83
84
|
forEachGeneric( model, 'vocabularies', run );
|
|
@@ -336,6 +337,24 @@ function propagate( model ) {
|
|
|
336
337
|
annotation( prop, target, source );
|
|
337
338
|
}
|
|
338
339
|
|
|
340
|
+
/**
|
|
341
|
+
* Propagate `localized`, but not for a texts entity
|
|
342
|
+
* (as `null` in an Universal CSN).
|
|
343
|
+
*/
|
|
344
|
+
function localized( prop, target, source ) {
|
|
345
|
+
const main = target._main;
|
|
346
|
+
if (target.kind === 'element' && main.kind === 'entity' && !main.query) {
|
|
347
|
+
const { id } = main.name;
|
|
348
|
+
const base = id.endsWith( '.texts' ) &&
|
|
349
|
+
model.definitions[id.slice( 0, -'.texts'.length )];
|
|
350
|
+
if (base && withLocalizedData( base, main )) {
|
|
351
|
+
target.localized = { $inferred: 'NULL', val: undefined }; // null in UCSN
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
withKind( prop, target, source );
|
|
356
|
+
}
|
|
357
|
+
|
|
339
358
|
function withKind( prop, target, source ) {
|
|
340
359
|
if (target.kind === 'param' && source.kind === 'entity')
|
|
341
360
|
return; // Don't propagate from entity types to parameters (+ return type).
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -108,8 +108,32 @@ function resolve( model ) {
|
|
|
108
108
|
const ignoreSpecifiedElements
|
|
109
109
|
= isDeprecatedEnabled( options, 'ignoreSpecifiedQueryElements' );
|
|
110
110
|
|
|
111
|
+
forEachGeneric( model, 'sources', resolveUsings );
|
|
111
112
|
return doResolve();
|
|
112
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Resolve the using declarations in `using`.
|
|
116
|
+
* Issue error message if the referenced artifact does not exist.
|
|
117
|
+
*/
|
|
118
|
+
function resolveUsings( src, topLevel ) {
|
|
119
|
+
if (!src.usings)
|
|
120
|
+
return;
|
|
121
|
+
for (const def of src.usings) {
|
|
122
|
+
if (def.usings) // using {...}
|
|
123
|
+
resolveUsings( def );
|
|
124
|
+
if (!def.name || !def.name.id)
|
|
125
|
+
continue; // using {...}, parse error
|
|
126
|
+
const art = model.definitions[def.name.absolute];
|
|
127
|
+
if (art && art.$duplicates)
|
|
128
|
+
continue;
|
|
129
|
+
const ref = def.extern;
|
|
130
|
+
const user = (topLevel ? def : src);
|
|
131
|
+
const from = user.fileDep;
|
|
132
|
+
if (art || !from || from.realname) // no error for non-existing ref with non-existing module
|
|
133
|
+
resolvePath( ref, 'using', def ); // TODO: consider FROM for validNames
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
113
137
|
function doResolve() {
|
|
114
138
|
// Phase 1: check paths in `usings` has been moved to kick-start.js Phase 2:
|
|
115
139
|
// calculate/init view elements & collect views in order:
|
|
@@ -1669,7 +1693,8 @@ function resolve( model ) {
|
|
|
1669
1693
|
}
|
|
1670
1694
|
const symbols = type && type.enum;
|
|
1671
1695
|
if (!symbols) {
|
|
1672
|
-
if (user.kind !== '$annotation') {
|
|
1696
|
+
if ((user.kind ?? user._outer?.kind) !== '$annotation') {
|
|
1697
|
+
// TODO: better type deduction for annotations
|
|
1673
1698
|
const msg = (user.kind === 'enum') ? 'symbolDef' : type && 'invalidType';
|
|
1674
1699
|
warning( 'ref-unexpected-enum', [ expr.location, user ],
|
|
1675
1700
|
{ '#': msg || 'untyped', enum: sym.id, type: type || '' } );
|