@sap/cds-compiler 6.4.6 → 6.5.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 +42 -1156
- package/README.md +1 -10
- package/bin/cdsc.js +1 -1
- package/doc/IncompatibleChanges_v5.md +436 -0
- package/doc/IncompatibleChanges_v6.md +659 -0
- package/doc/Versioning.md +3 -7
- package/lib/api/main.js +1 -0
- package/lib/api/options.js +5 -0
- package/lib/api/validate.js +3 -0
- package/lib/base/message-registry.js +31 -8
- package/lib/base/messages.js +1 -1
- package/lib/base/model.js +3 -2
- package/lib/checks/actionsFunctions.js +6 -3
- package/lib/checks/existsInForbiddenPlaces.js +32 -0
- package/lib/checks/validator.js +2 -0
- package/lib/compiler/assert-consistency.js +3 -5
- package/lib/compiler/checks.js +4 -8
- package/lib/compiler/define.js +330 -558
- package/lib/compiler/extend.js +302 -18
- package/lib/compiler/finalize-parse-cdl.js +2 -10
- package/lib/compiler/generate.js +33 -5
- package/lib/compiler/kick-start.js +8 -7
- package/lib/compiler/populate.js +25 -70
- package/lib/compiler/propagator.js +1 -2
- package/lib/compiler/resolve.js +4 -13
- package/lib/compiler/shared.js +18 -5
- package/lib/compiler/tweak-assocs.js +13 -9
- package/lib/compiler/utils.js +98 -2
- package/lib/compiler/xpr-rewrite.js +2 -1
- package/lib/edm/annotations/edmJson.js +9 -6
- package/lib/edm/annotations/genericTranslation.js +8 -4
- package/lib/edm/csn2edm.js +3 -4
- package/lib/edm/edmInboundChecks.js +1 -2
- package/lib/edm/edmPreprocessor.js +3 -3
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +1 -1
- package/lib/gen/Dictionary.json +16 -1
- package/lib/json/from-csn.js +4 -6
- package/lib/json/to-csn.js +3 -3
- package/lib/model/csnRefs.js +19 -5
- package/lib/model/enrichCsn.js +4 -2
- package/lib/optionProcessor.js +8 -4
- package/lib/render/toSql.js +0 -4
- package/lib/render/utils/sql.js +3 -2
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/assertUnique.js +3 -3
- package/lib/transform/db/assocsToQueries/normalizeFrom.js +33 -0
- package/lib/transform/db/assocsToQueries/transformExists.js +14 -3
- package/lib/transform/db/assocsToQueries/utils.js +1 -1
- package/lib/transform/db/backlinks.js +4 -4
- package/lib/transform/db/cdsPersistence.js +4 -4
- package/lib/transform/db/constraints.js +4 -4
- package/lib/transform/db/expansion.js +5 -5
- package/lib/transform/db/flattening.js +4 -5
- package/lib/transform/db/rewriteCalculatedElements.js +3 -3
- package/lib/transform/db/temporal.js +11 -11
- package/lib/transform/draft/db.js +2 -0
- package/lib/transform/draft/odata.js +5 -7
- package/lib/transform/effective/flattening.js +1 -2
- package/lib/transform/forOdata.js +3 -3
- package/lib/transform/forRelationalDB.js +1 -1
- package/lib/transform/odata/createForeignKeys.js +1 -2
- package/lib/transform/odata/flattening.js +1 -2
- package/lib/transform/odata/toFinalBaseType.js +52 -55
- package/lib/transform/transformUtils.js +3 -4
- package/package.json +1 -1
- package/doc/CHANGELOG_BETA.md +0 -464
- package/doc/CHANGELOG_DEPRECATED.md +0 -235
package/lib/compiler/extend.js
CHANGED
|
@@ -19,8 +19,14 @@ const {
|
|
|
19
19
|
copyExpr,
|
|
20
20
|
setExpandStatusAnnotate,
|
|
21
21
|
linkToOrigin,
|
|
22
|
+
initItemsLinks,
|
|
23
|
+
setMemberParent,
|
|
22
24
|
createAndLinkCalcDepElement,
|
|
25
|
+
initExprAnnoBlock,
|
|
26
|
+
initDollarSelf,
|
|
27
|
+
initBoundSelfParam,
|
|
23
28
|
dependsOnSilent,
|
|
29
|
+
storeExtension,
|
|
24
30
|
pathName,
|
|
25
31
|
annotationHasEllipsis,
|
|
26
32
|
} = require('./utils');
|
|
@@ -62,7 +68,8 @@ function extend( model ) {
|
|
|
62
68
|
resolveTypeArgumentsUnchecked,
|
|
63
69
|
resolveDefinitionName,
|
|
64
70
|
attachAndEmitValidNames,
|
|
65
|
-
|
|
71
|
+
targetIsTargetAspect,
|
|
72
|
+
checkRedefinition,
|
|
66
73
|
initSelectItems,
|
|
67
74
|
} = model.$functions;
|
|
68
75
|
|
|
@@ -730,12 +737,15 @@ function extend( model ) {
|
|
|
730
737
|
|
|
731
738
|
for (const ext of extensions) {
|
|
732
739
|
if (!artReturns && art.kind !== 'annotate') {
|
|
733
|
-
|
|
740
|
+
const msgId = ext.returns && hasSecurityAnno( ext.returns )
|
|
741
|
+
? 'ext-unexpected-returns-sec'
|
|
742
|
+
: 'ext-unexpected-returns';
|
|
743
|
+
message( msgId, [ ext.returns.location, ext ], {
|
|
734
744
|
'#': (isAction ? art.kind : 'std'), keyword: 'returns',
|
|
735
745
|
}, {
|
|
736
746
|
std: 'Unexpected $(KEYWORD); only actions and functions have return parameters',
|
|
737
747
|
action: 'Unexpected $(KEYWORD) for action without return parameter',
|
|
738
|
-
// function without `returns` can happen via CSN input!
|
|
748
|
+
// function without `returns` can happen via CSN input! TODO: check in parser
|
|
739
749
|
function: 'Unexpected $(KEYWORD) for function without return parameter',
|
|
740
750
|
} );
|
|
741
751
|
// Do not put completely wrong returns into a “super annotate” statement;
|
|
@@ -816,7 +826,8 @@ function extend( model ) {
|
|
|
816
826
|
// if we consider this an error or such sub annotates are then ignored
|
|
817
827
|
// (i.e. not put into the "super annotate").
|
|
818
828
|
const dict = parent[prop];
|
|
819
|
-
|
|
829
|
+
const securityRelevant = hasSecurityAnno( ext ) ? '-sec' : '';
|
|
830
|
+
if (!dict && !securityRelevant) {
|
|
820
831
|
// TODO: check - for each name? - better locations
|
|
821
832
|
const location = ext._parent?.[prop]?.[$location] || ext.name.location;
|
|
822
833
|
// Remark: no `elements` dict location with `annotate Main:elem`
|
|
@@ -851,37 +862,36 @@ function extend( model ) {
|
|
|
851
862
|
}
|
|
852
863
|
return false;
|
|
853
864
|
}
|
|
854
|
-
else if (!dict[name]) {
|
|
865
|
+
else if (!dict?.[name]) {
|
|
855
866
|
// TODO: make variant `returns` an auto-variant for ($ART) ?
|
|
856
|
-
const inReturns = parent._parent?.returns
|
|
857
|
-
const art = inReturns || parent;
|
|
867
|
+
const inReturns = parent._parent?.returns;
|
|
858
868
|
switch (prop) {
|
|
859
869
|
case 'elements':
|
|
860
870
|
if (canBeDraftMember( name, parent, draftElements ))
|
|
861
871
|
break;
|
|
862
|
-
notFound(
|
|
863
|
-
{ '#': (inReturns ? 'returns' : 'element'),
|
|
872
|
+
notFound( `ext-undefined-element${ securityRelevant }`, ext.name.location, ext,
|
|
873
|
+
{ '#': (inReturns ? 'returns' : 'element'), name },
|
|
864
874
|
parent.elements );
|
|
865
875
|
break;
|
|
866
876
|
case 'enum': // TODO: extra msg id?
|
|
867
|
-
notFound(
|
|
868
|
-
{ '#': (inReturns ? 'enum-returns' : 'enum'),
|
|
877
|
+
notFound( `ext-undefined-element${ securityRelevant }`, ext.name.location, ext,
|
|
878
|
+
{ '#': (inReturns ? 'enum-returns' : 'enum'), name },
|
|
869
879
|
parent.enum );
|
|
870
880
|
break;
|
|
871
881
|
case 'foreignKeys':
|
|
872
|
-
notFound(
|
|
882
|
+
notFound( `ext-undefined-key${ securityRelevant }`, ext.name.location, ext,
|
|
873
883
|
{ name }, parent.foreignKeys );
|
|
874
884
|
break;
|
|
875
885
|
case 'params':
|
|
876
|
-
notFound(
|
|
877
|
-
{ '#': 'param',
|
|
886
|
+
notFound( `ext-undefined-param${ securityRelevant }`, ext.name.location, ext,
|
|
887
|
+
{ '#': 'param', name },
|
|
878
888
|
parent.params );
|
|
879
889
|
break;
|
|
880
890
|
case 'actions':
|
|
881
891
|
if (canBeDraftMember( name, parent, draftBoundActions ))
|
|
882
892
|
break;
|
|
883
|
-
notFound(
|
|
884
|
-
{ '#': 'action',
|
|
893
|
+
notFound( `ext-undefined-action${ securityRelevant }`, ext.name.location, ext,
|
|
894
|
+
{ '#': 'action', name },
|
|
885
895
|
parent.actions );
|
|
886
896
|
break;
|
|
887
897
|
default:
|
|
@@ -924,8 +934,16 @@ function extend( model ) {
|
|
|
924
934
|
forEachMember( annotate, createSuperAnnotate );
|
|
925
935
|
}
|
|
926
936
|
|
|
937
|
+
function hasSecurityAnno( ext ) {
|
|
938
|
+
return ext['@restrict'] || ext['@requires'] ||
|
|
939
|
+
Object.keys( ext ).some( prop => prop.startsWith( '@ams.' ) );
|
|
940
|
+
}
|
|
941
|
+
|
|
927
942
|
function checkRemainingMainExtensions( art, ext ) {
|
|
928
|
-
|
|
943
|
+
const refCtx = ext.kind === 'annotate' && hasSecurityAnno( ext )
|
|
944
|
+
? 'annotate-sec'
|
|
945
|
+
: ext.kind;
|
|
946
|
+
if (!resolvePath( ext.name, refCtx, ext )) // error for extend, info for annotate
|
|
929
947
|
return;
|
|
930
948
|
|
|
931
949
|
if (art?.builtin) {
|
|
@@ -1186,6 +1204,272 @@ function extend( model ) {
|
|
|
1186
1204
|
}
|
|
1187
1205
|
}
|
|
1188
1206
|
|
|
1207
|
+
// TODO TMP: copied from ./define.js: -----------------------------------------
|
|
1208
|
+
|
|
1209
|
+
/**
|
|
1210
|
+
* Set property `_parent` for all elements in `parent` to `parent` and do so
|
|
1211
|
+
* recursively for all sub elements.
|
|
1212
|
+
*
|
|
1213
|
+
* If not for extensions: construct === parent
|
|
1214
|
+
*
|
|
1215
|
+
* TODO: separate extension!
|
|
1216
|
+
*/
|
|
1217
|
+
function initMembers( construct, parent, block ) {
|
|
1218
|
+
// TODO: split extend from init
|
|
1219
|
+
const main = parent._main || parent;
|
|
1220
|
+
const isQueryExtension = construct.kind === 'extend' && main.query;
|
|
1221
|
+
let obj = initItemsLinks( construct, block );
|
|
1222
|
+
initExprAnnoBlock( construct, block );
|
|
1223
|
+
if (obj.target && targetIsTargetAspect( obj )) {
|
|
1224
|
+
obj.targetAspect = obj.target;
|
|
1225
|
+
delete obj.target;
|
|
1226
|
+
}
|
|
1227
|
+
const { targetAspect } = obj;
|
|
1228
|
+
if (targetAspect) {
|
|
1229
|
+
if (obj.foreignKeys) {
|
|
1230
|
+
error( 'type-unexpected-foreign-keys', [ obj.foreignKeys[$location], construct ] );
|
|
1231
|
+
delete obj.foreignKeys; // continuation semantics: not specified
|
|
1232
|
+
}
|
|
1233
|
+
if (obj.on && !obj.target) {
|
|
1234
|
+
error( 'type-unexpected-on-condition', [ obj.on.location, construct ] );
|
|
1235
|
+
delete obj.on; // continuation semantics: not specified
|
|
1236
|
+
}
|
|
1237
|
+
if (targetAspect.elements)
|
|
1238
|
+
initAnonymousAspect();
|
|
1239
|
+
}
|
|
1240
|
+
if (obj !== parent && obj.elements && parent.enum) { // applying the extension
|
|
1241
|
+
initElementsAsEnum();
|
|
1242
|
+
}
|
|
1243
|
+
else {
|
|
1244
|
+
if (checkDefinitions( construct, parent, 'elements', obj.elements || false ))
|
|
1245
|
+
forEachInOrder( obj, 'elements', init );
|
|
1246
|
+
if (checkDefinitions( construct, parent, 'enum', obj.enum || false ))
|
|
1247
|
+
forEachGeneric( obj, 'enum', init );
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
if (obj.foreignKeys)
|
|
1251
|
+
forEachInOrder( obj, 'foreignKeys', init );
|
|
1252
|
+
if (checkDefinitions( construct, parent, 'actions' ))
|
|
1253
|
+
forEachGeneric( construct, 'actions', init );
|
|
1254
|
+
if (checkDefinitions( construct, parent, 'params' ))
|
|
1255
|
+
forEachInOrder( construct, 'params', init );
|
|
1256
|
+
const { returns } = construct;
|
|
1257
|
+
if (returns) {
|
|
1258
|
+
const { kind } = construct;
|
|
1259
|
+
returns.kind = (kind === 'extend' || kind === 'annotate') ? kind : 'param';
|
|
1260
|
+
init( returns, '' ); // '' is special name for returns parameter
|
|
1261
|
+
}
|
|
1262
|
+
return;
|
|
1263
|
+
|
|
1264
|
+
function initElementsAsEnum() {
|
|
1265
|
+
// in extensions, extended enums are represented as elements
|
|
1266
|
+
let hasElement = false;
|
|
1267
|
+
for (const n in obj.elements) {
|
|
1268
|
+
const e = obj.elements[n];
|
|
1269
|
+
if (e.kind === 'extend')
|
|
1270
|
+
continue;
|
|
1271
|
+
const noVal = e.value?.val === undefined && e.value?.sym === undefined;
|
|
1272
|
+
// TODO: forbid #symbol as enum value
|
|
1273
|
+
if (e.$syntax === 'element' || // `extend … with elements` or `extend with { element … }`
|
|
1274
|
+
noVal && e.$syntax !== 'enum' || // no value in CDL input
|
|
1275
|
+
e.virtual || e.key || e.masked || e.type || e.elements || e.items || e.stored) {
|
|
1276
|
+
// We do not want to complain separately about all element properties:
|
|
1277
|
+
error( 'ext-unexpected-element', [ e.location, construct ],
|
|
1278
|
+
{ name: e.name.id, code: 'extend … with enum' },
|
|
1279
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1280
|
+
'Unexpected elements like $(NAME) in an extension for an enum. Additionally, use $(CODE) when extending enums' );
|
|
1281
|
+
// Don't emit 'ext-expecting-enum' if this error is emitted.
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
e.kind = 'enum';
|
|
1285
|
+
if (noVal || e.$syntax !== 'enum')
|
|
1286
|
+
hasElement = true; // warning with CDL input or `name: {}` in CSN input
|
|
1287
|
+
}
|
|
1288
|
+
if (hasElement) {
|
|
1289
|
+
// This message is similar to the one above. In v6, we could probably
|
|
1290
|
+
// turn this warning into an error, remove `$syntax: 'element',
|
|
1291
|
+
// and use the above `ext-unexpected-element` only for CSN input.
|
|
1292
|
+
warning( 'ext-expecting-enum', [ obj.elements[$location], construct ],
|
|
1293
|
+
{ code: 'extend … with enum' }, 'Use $(CODE) when extending enums' );
|
|
1294
|
+
}
|
|
1295
|
+
forEachGeneric( { enum: obj.elements }, 'enum', init );
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
function initAnonymousAspect() {
|
|
1299
|
+
// TODO: main?
|
|
1300
|
+
const inEntity = parent._main?.kind === 'entity';
|
|
1301
|
+
// TODO: also allow indirectly (component in component in entity)?
|
|
1302
|
+
setLink( targetAspect, '_outer', obj );
|
|
1303
|
+
setLink( targetAspect, '_parent', parent._parent );
|
|
1304
|
+
setLink( targetAspect, '_main', null ); // for name resolution
|
|
1305
|
+
|
|
1306
|
+
parent = targetAspect;
|
|
1307
|
+
construct = parent; // avoid extension behavior
|
|
1308
|
+
targetAspect.kind = 'aspect'; // TODO: probably '$aspect' to detect
|
|
1309
|
+
setLink( targetAspect, '_block', block );
|
|
1310
|
+
initDollarSelf( targetAspect );
|
|
1311
|
+
// allow ref of up_ in anonymous aspect inside entity
|
|
1312
|
+
// (TODO: complain if used and the managed composition is included into
|
|
1313
|
+
// another entity - might induce auto-redirection):
|
|
1314
|
+
if (inEntity && !targetAspect.elements.up_) {
|
|
1315
|
+
const up = {
|
|
1316
|
+
name: { id: 'up_' },
|
|
1317
|
+
kind: '$navElement',
|
|
1318
|
+
location: obj.location,
|
|
1319
|
+
};
|
|
1320
|
+
setLink( up, '_parent', targetAspect );
|
|
1321
|
+
setLink( up, '_main', targetAspect ); // used on main artifact
|
|
1322
|
+
// recompilation case: both target and targetAspect → allow up_ in that case, too:
|
|
1323
|
+
const name = obj.target && resolveUncheckedPath( obj.target, 'target', obj );
|
|
1324
|
+
const entity = name && model.definitions[name];
|
|
1325
|
+
if (entity && entity.elements)
|
|
1326
|
+
setLink( up, '_origin', entity.elements.up_ );
|
|
1327
|
+
// processAspectComposition/expand() sets _origin to element of
|
|
1328
|
+
// generated target entity
|
|
1329
|
+
targetAspect.$tableAliases.up_ = up;
|
|
1330
|
+
}
|
|
1331
|
+
obj = targetAspect;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
function init( elem, name, prop ) {
|
|
1335
|
+
if (!elem.kind) // wrong CSN input
|
|
1336
|
+
elem.kind = dictKinds[prop];
|
|
1337
|
+
if (!elem.name && !elem._outer) {
|
|
1338
|
+
const ref = elem.targetElement || elem.kind === 'element' && elem.value;
|
|
1339
|
+
if (ref && ref.path) {
|
|
1340
|
+
elem.name = Object.assign( { $inferred: 'as' },
|
|
1341
|
+
ref.path[ref.path.length - 1] );
|
|
1342
|
+
}
|
|
1343
|
+
else { // RETURNS, parser robustness
|
|
1344
|
+
elem.name = { id: name, location: elem.location };
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
if (!elem._block)
|
|
1349
|
+
setLink( elem, '_block', block );
|
|
1350
|
+
// if (!kindProperties[ elem.kind ]) console.log(elem.kind,elem.name)
|
|
1351
|
+
if ((elem.kind === 'extend' || elem.kind === 'annotate')) {
|
|
1352
|
+
storeExtension( elem, name, prop, parent );
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
if (isQueryExtension && elem.kind === 'element') {
|
|
1356
|
+
error( 'extend-query', [ elem.location, construct ], // TODO: searchName ?
|
|
1357
|
+
{ code: 'extend projection' },
|
|
1358
|
+
'Use $(CODE) to add select items to the query entity' );
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
const existing = parent[prop]?.[name];
|
|
1363
|
+
const add = construct !== parent && (!existing || elem.$inferred !== 'include');
|
|
1364
|
+
// don't dump with `entity T {}; extend T with { extend e {}; e {}; e {} };`:
|
|
1365
|
+
if (elem.$duplicates === true && add)
|
|
1366
|
+
elem.$duplicates = null;
|
|
1367
|
+
setMemberParent( elem, name, parent, add && prop );
|
|
1368
|
+
checkRedefinition( elem );
|
|
1369
|
+
initMembers( elem, elem, elem._block );
|
|
1370
|
+
if (elem.kind === 'action' || elem.kind === 'function')
|
|
1371
|
+
initBoundSelfParam( elem.params, elem._main );
|
|
1372
|
+
|
|
1373
|
+
// for a correct home path, setMemberParent needed to be called
|
|
1374
|
+
|
|
1375
|
+
if (!elem.value || elem.kind !== 'element' ||
|
|
1376
|
+
elem.$syntax === 'enum' && parent.kind === 'extend') // ambiguous in parse-cdl
|
|
1377
|
+
return;
|
|
1378
|
+
// -> it's a calculated element
|
|
1379
|
+
if (!elem.type && elem.value?.type) { // top-level CAST( expr AS type )
|
|
1380
|
+
if (!elem.target)
|
|
1381
|
+
elem.type = { ...elem.value.type, $inferred: 'cast' };
|
|
1382
|
+
}
|
|
1383
|
+
elem.$syntax = 'calc';
|
|
1384
|
+
// TODO: it is not just "syntax" - maybe better test for `$calcDepElement`?
|
|
1385
|
+
createAndLinkCalcDepElement( elem );
|
|
1386
|
+
|
|
1387
|
+
// Special case (hack) for calculated elements that use composition+filter:
|
|
1388
|
+
// See "Notes on `$enclosed`" in `ExposingAssocWithFilter.md` for details.
|
|
1389
|
+
if (elem.target && elem.value.path?.[elem.value.path.length - 1]?.where) {
|
|
1390
|
+
delete elem.type;
|
|
1391
|
+
delete elem.on;
|
|
1392
|
+
delete elem.target;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// TODO: is only necessary for extensions - make special for extend/annotate
|
|
1398
|
+
function checkDefinitions( construct, parent, prop, dict = construct[prop] ) {
|
|
1399
|
+
// TODO: do differently, see also annotateMembers() in resolver
|
|
1400
|
+
// To have been checked by parsers:
|
|
1401
|
+
// - artifacts (CDL-only anyway) only inside [extend] context|service
|
|
1402
|
+
if (!dict)
|
|
1403
|
+
return false;
|
|
1404
|
+
const feature = kindProperties[parent.kind ?? 'element'][prop];
|
|
1405
|
+
if (feature &&
|
|
1406
|
+
(feature === true || construct.kind !== 'extend' || feature( prop, parent )))
|
|
1407
|
+
return true;
|
|
1408
|
+
const location = dict[$location];
|
|
1409
|
+
|
|
1410
|
+
// TODO: a bit inconsistent = not a simple switch on `prop`…
|
|
1411
|
+
if (prop === 'actions') {
|
|
1412
|
+
if (Object.keys( dict ).length) {
|
|
1413
|
+
error( 'def-unexpected-actions', [ location, construct ], {}, // TODO: ext-
|
|
1414
|
+
'Actions and functions only exist top-level and for entities' ); // or aspects
|
|
1415
|
+
}
|
|
1416
|
+
else {
|
|
1417
|
+
warning( 'ext-ignoring-actions', [ location, construct ], {},
|
|
1418
|
+
'Actions and functions only exist top-level and for entities' );
|
|
1419
|
+
return false;
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
else if (parent.kind === 'action' || parent.kind === 'function') {
|
|
1423
|
+
error( 'ext-unexpected-action', [ construct.location, construct ], { '#': parent.kind }, {
|
|
1424
|
+
std: 'Actions and functions can\'t be extended, only annotated', // TODO: → ext-unsupported
|
|
1425
|
+
action: 'Actions can\'t be extended, only annotated',
|
|
1426
|
+
function: 'Functions can\'t be extended, only annotated',
|
|
1427
|
+
} );
|
|
1428
|
+
}
|
|
1429
|
+
else if (prop === 'params') {
|
|
1430
|
+
if (!feature) {
|
|
1431
|
+
// Note: This error can't be triggered at the moment. But as we likely want to
|
|
1432
|
+
// allow extensions with params in the future, we keep the code.
|
|
1433
|
+
error( 'def-unexpected-params', [ location, construct ], {},
|
|
1434
|
+
'Parameters only exist for entities, actions or functions' );
|
|
1435
|
+
}
|
|
1436
|
+
else {
|
|
1437
|
+
// remark: we could allow this
|
|
1438
|
+
error( 'extend-with-params', [ location, construct ], {},
|
|
1439
|
+
'Extending artifacts with parameters is not supported' );
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
else if (feature) { // allowed in principle, but not with extend
|
|
1443
|
+
if (!Object.keys( dict ).length) {
|
|
1444
|
+
warning( 'ext-ignoring-elements', [ location, construct ], {},
|
|
1445
|
+
'Only structures with directly specified elements can be extended by elements' );
|
|
1446
|
+
return false;
|
|
1447
|
+
}
|
|
1448
|
+
else if (parent.$inferred === 'include') { // special case for better error message
|
|
1449
|
+
const variant = (construct.enum || construct.elements) ? 'elements' : 'std';
|
|
1450
|
+
error( 'ref-expected-direct-structure', [ location, construct ],
|
|
1451
|
+
{ '#': variant, art: parent } );
|
|
1452
|
+
}
|
|
1453
|
+
else {
|
|
1454
|
+
error( 'extend-type', [ location, construct ], {},
|
|
1455
|
+
'Only structures or enum types can be extended with elements/enums' );
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
else if (prop === 'elements') {
|
|
1459
|
+
error( 'def-unexpected-elements', [ location, construct ], {},
|
|
1460
|
+
'Elements only exist in entities, types or typed constructs' );
|
|
1461
|
+
}
|
|
1462
|
+
else if (prop === 'columns') {
|
|
1463
|
+
error( 'extend-columns', [ location, construct ], { art: construct } );
|
|
1464
|
+
}
|
|
1465
|
+
else { // if (prop === 'enum') {
|
|
1466
|
+
error( 'def-unexpected-enum', [ location, construct ], {},
|
|
1467
|
+
'Enum symbols can only be defined for types or typed constructs' );
|
|
1468
|
+
}
|
|
1469
|
+
return construct === parent;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
|
|
1189
1473
|
// includes ----------------------------------------------------------------
|
|
1190
1474
|
|
|
1191
1475
|
/**
|
|
@@ -1228,7 +1512,7 @@ function extend( model ) {
|
|
|
1228
1512
|
* Sets `_ancestors` links on `art`.
|
|
1229
1513
|
*
|
|
1230
1514
|
* TODO: try to set `_ancestors` only to entities (but beware “intermediate”
|
|
1231
|
-
* non-entities).
|
|
1515
|
+
* non-entities - TODO: make intermediate non-entities stop chain).
|
|
1232
1516
|
*
|
|
1233
1517
|
* Examples:
|
|
1234
1518
|
* ext === art: `entity E : F {}` => add elements of F to E
|
|
@@ -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
|
}
|
|
@@ -117,15 +117,7 @@ function finalizeParseCdl( model ) {
|
|
|
117
117
|
// containing it. Otherwise some type's aren't properly resolved.
|
|
118
118
|
// TODO: If resolveTypeUnchecked is reworked, we may be able to simplify this coding.
|
|
119
119
|
(artifact.$queries || []).forEach( art => resolveTypesForParseCdl( art, artifact ) );
|
|
120
|
-
(artifact.columns || []).forEach( (col)
|
|
121
|
-
// TODO: Can we use "ensureColumnName" of populate.js? It depends on column indices
|
|
122
|
-
// _after_ wildcards were expanded, though.
|
|
123
|
-
if (!col.name && col.value?.path) {
|
|
124
|
-
const last = col.value.path.at(-1);
|
|
125
|
-
col.name = { id: last?.id || '', location: last?.location, $inferred: 'as' };
|
|
126
|
-
}
|
|
127
|
-
resolveTypesForParseCdl( col, artifact );
|
|
128
|
-
} );
|
|
120
|
+
(artifact.columns || []).forEach( col => resolveTypesForParseCdl( col, artifact ) );
|
|
129
121
|
forEachGeneric( artifact, 'mixin', art => resolveTypesForParseCdl( art, artifact ) );
|
|
130
122
|
|
|
131
123
|
// For better error messages for `type of`s in `returns`, we pass the object as the new main.
|
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;
|
|
@@ -340,7 +340,7 @@ function generate( model ) {
|
|
|
340
340
|
for (const orig of textElems)
|
|
341
341
|
addElementToTextsEntity( orig, art, fioriEnabled, assertUniqueValue );
|
|
342
342
|
|
|
343
|
-
|
|
343
|
+
initMainArtifact( art );
|
|
344
344
|
if (art.includes) {
|
|
345
345
|
// add elements `locale`, etc. which are required below.
|
|
346
346
|
applyIncludes( art, art ); // TODO: rethink - can we avoid this if only new extend?
|
|
@@ -411,6 +411,8 @@ function generate( model ) {
|
|
|
411
411
|
const { location } = art;
|
|
412
412
|
|
|
413
413
|
art.includes = [ createInclude( textsAspectName, location ) ];
|
|
414
|
+
propagateEarly( art, '@cds.autoexpose' );
|
|
415
|
+
propagateEarly( art, '@fiori.draft.enabled' );
|
|
414
416
|
|
|
415
417
|
if (fioriEnabled) {
|
|
416
418
|
// "Early" include; only for element `locale`, which has its `key` property
|
|
@@ -490,6 +492,7 @@ function generate( model ) {
|
|
|
490
492
|
|
|
491
493
|
/**
|
|
492
494
|
* Create a structure that can be used as an item in `includes`.
|
|
495
|
+
* TODO: replace by linkMainArtifact()
|
|
493
496
|
*
|
|
494
497
|
* @param {string} name
|
|
495
498
|
* @param {XSN.Location} location
|
|
@@ -638,7 +641,7 @@ function generate( model ) {
|
|
|
638
641
|
'An aspect $(TARGET) with an element named $(NAME) can\'t be used as target' );
|
|
639
642
|
return false;
|
|
640
643
|
}
|
|
641
|
-
if (model.definitions[entityName]) {
|
|
644
|
+
if (model.definitions[entityName]) { // TODO: allow a gap (namespace)?
|
|
642
645
|
error( null, [ location, elem ], { art: entityName },
|
|
643
646
|
// eslint-disable-next-line @stylistic/max-len
|
|
644
647
|
'Target entity $(ART) can\'t be created as there is another definition with this name' );
|
|
@@ -696,8 +699,11 @@ function generate( model ) {
|
|
|
696
699
|
$inferred: 'composition-entity',
|
|
697
700
|
};
|
|
698
701
|
if (target.name) { // named target aspect
|
|
699
|
-
if (!isDeprecatedEnabled( options, 'noCompositionIncludes' ))
|
|
702
|
+
if (!isDeprecatedEnabled( options, 'noCompositionIncludes' )) {
|
|
700
703
|
art.includes = [ createInclude( target.name.id, location ) ];
|
|
704
|
+
propagateEarly( art, '@cds.autoexpose' );
|
|
705
|
+
propagateEarly( art, '@fiori.draft.enabled' );
|
|
706
|
+
}
|
|
701
707
|
setLink( art, '_origin', target );
|
|
702
708
|
setLink( art, '_upperAspects', [ target, ...(elem._main._upperAspects || []) ] );
|
|
703
709
|
}
|
|
@@ -736,7 +742,7 @@ function generate( model ) {
|
|
|
736
742
|
|
|
737
743
|
setLink( art, '_block', model.$internal );
|
|
738
744
|
model.definitions[entityName] = art;
|
|
739
|
-
|
|
745
|
+
initMainArtifact( art );
|
|
740
746
|
|
|
741
747
|
// Apply annotations to generated artifact, prepare (not apply!) element
|
|
742
748
|
// annotations (remark: adding elements is not allowed for generated artifacts):
|
|
@@ -858,4 +864,26 @@ function checkTextsLanguageAssocOption( model, options ) {
|
|
|
858
864
|
}
|
|
859
865
|
|
|
860
866
|
|
|
867
|
+
/**
|
|
868
|
+
* Propagate the given `prop` (e.g. annotation) early, i.e. copy it from all `.includes`
|
|
869
|
+
* if they have the property.
|
|
870
|
+
* TEMPORARY copy from ./extend.js
|
|
871
|
+
*
|
|
872
|
+
* @param {XSN.Definition} art
|
|
873
|
+
* @param {string} prop
|
|
874
|
+
*/
|
|
875
|
+
function propagateEarly( art, prop ) {
|
|
876
|
+
if (art[prop])
|
|
877
|
+
return;
|
|
878
|
+
for (const ref of art.includes) {
|
|
879
|
+
const aspect = ref._artifact;
|
|
880
|
+
if (aspect) {
|
|
881
|
+
const anno = aspect[prop];
|
|
882
|
+
if (anno && (anno.val !== null || !art[prop]))
|
|
883
|
+
art[prop] = Object.assign( { $inferred: 'include' }, anno );
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
|
|
861
889
|
module.exports = generate;
|
|
@@ -32,6 +32,9 @@ function kickStart( model ) {
|
|
|
32
32
|
* - service: the artifact of the embedding service
|
|
33
33
|
* This function must be called ordered: parent first
|
|
34
34
|
*
|
|
35
|
+
* Remark: _service links are not already set in define.js, because we might
|
|
36
|
+
* have a @cds.redirection.service in the future
|
|
37
|
+
*
|
|
35
38
|
* @param {string} name Artifact name
|
|
36
39
|
*/
|
|
37
40
|
function setAncestorsAndService( name ) {
|
|
@@ -50,16 +53,12 @@ function kickStart( model ) {
|
|
|
50
53
|
return;
|
|
51
54
|
// To be removed when nested services are allowed
|
|
52
55
|
if (!isBetaEnabled( options, 'nestedServices' ) && art.kind === 'service') {
|
|
53
|
-
|
|
54
|
-
parent = parent._parent;
|
|
55
|
-
message( 'service-nested-service', [ art.name.location, art ], { art: parent },
|
|
56
|
+
message( 'service-nested-service', [ art.name.location, art ], { art: service },
|
|
56
57
|
'A service can\'t be nested within a service $(ART)' );
|
|
57
58
|
}
|
|
58
59
|
else if (art.kind === 'context') {
|
|
59
|
-
while (parent.kind !== 'service')
|
|
60
|
-
parent = parent._parent;
|
|
61
60
|
// TODO: remove this error
|
|
62
|
-
message( 'service-nested-context', [ art.name.location, art ], { art:
|
|
61
|
+
message( 'service-nested-context', [ art.name.location, art ], { art: service },
|
|
63
62
|
'A context can\'t be nested within a service $(ART)' );
|
|
64
63
|
}
|
|
65
64
|
}
|
|
@@ -73,7 +72,9 @@ function kickStart( model ) {
|
|
|
73
72
|
// Service1.E = projection on E
|
|
74
73
|
|
|
75
74
|
// Remark: _ancestors are also set with includes, and there also for aspects,
|
|
76
|
-
// types and events
|
|
75
|
+
// types and events (TODO: entity only)
|
|
76
|
+
//
|
|
77
|
+
// Remark: _ancestors are also tested in populate.js for minmal exposure
|
|
77
78
|
const chain = [];
|
|
78
79
|
const autoexposed = annotationVal( art['@cds.autoexposed'] );
|
|
79
80
|
// no need to set preferredRedirectionTarget in the while loop as we would
|