@sap/cds-compiler 6.4.6 → 6.5.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 -1156
- package/README.md +1 -10
- 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 +23 -0
- 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 +244 -459
- package/lib/compiler/extend.js +297 -11
- package/lib/compiler/finalize-parse-cdl.js +2 -10
- package/lib/compiler/generate.js +29 -1
- package/lib/compiler/populate.js +21 -63
- package/lib/compiler/propagator.js +1 -2
- package/lib/compiler/resolve.js +2 -12
- package/lib/compiler/shared.js +18 -5
- package/lib/compiler/tweak-assocs.js +13 -9
- package/lib/compiler/utils.js +97 -0
- 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 +13 -4
- package/lib/model/enrichCsn.js +4 -2
- package/lib/optionProcessor.js +8 -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,7 +862,7 @@ 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
867
|
const inReturns = parent._parent?.returns && parent._parent;
|
|
857
868
|
const art = inReturns || parent;
|
|
@@ -859,28 +870,28 @@ function extend( model ) {
|
|
|
859
870
|
case 'elements':
|
|
860
871
|
if (canBeDraftMember( name, parent, draftElements ))
|
|
861
872
|
break;
|
|
862
|
-
notFound(
|
|
873
|
+
notFound( `ext-undefined-element${ securityRelevant }`, ext.name.location, ext,
|
|
863
874
|
{ '#': (inReturns ? 'returns' : 'element'), art, name },
|
|
864
875
|
parent.elements );
|
|
865
876
|
break;
|
|
866
877
|
case 'enum': // TODO: extra msg id?
|
|
867
|
-
notFound(
|
|
878
|
+
notFound( `ext-undefined-element${ securityRelevant }`, ext.name.location, ext,
|
|
868
879
|
{ '#': (inReturns ? 'enum-returns' : 'enum'), art, name },
|
|
869
880
|
parent.enum );
|
|
870
881
|
break;
|
|
871
882
|
case 'foreignKeys':
|
|
872
|
-
notFound(
|
|
883
|
+
notFound( `ext-undefined-key${ securityRelevant }`, ext.name.location, ext,
|
|
873
884
|
{ name }, parent.foreignKeys );
|
|
874
885
|
break;
|
|
875
886
|
case 'params':
|
|
876
|
-
notFound(
|
|
887
|
+
notFound( `ext-undefined-param${ securityRelevant }`, ext.name.location, ext,
|
|
877
888
|
{ '#': 'param', art: parent, name },
|
|
878
889
|
parent.params );
|
|
879
890
|
break;
|
|
880
891
|
case 'actions':
|
|
881
892
|
if (canBeDraftMember( name, parent, draftBoundActions ))
|
|
882
893
|
break;
|
|
883
|
-
notFound(
|
|
894
|
+
notFound( `ext-undefined-action${ securityRelevant }`, ext.name.location, ext,
|
|
884
895
|
{ '#': 'action', art: parent, name },
|
|
885
896
|
parent.actions );
|
|
886
897
|
break;
|
|
@@ -924,8 +935,16 @@ function extend( model ) {
|
|
|
924
935
|
forEachMember( annotate, createSuperAnnotate );
|
|
925
936
|
}
|
|
926
937
|
|
|
938
|
+
function hasSecurityAnno( ext ) {
|
|
939
|
+
return ext['@restrict'] || ext['@requires'] ||
|
|
940
|
+
Object.keys( ext ).some( prop => prop.startsWith( '@ams.' ) );
|
|
941
|
+
}
|
|
942
|
+
|
|
927
943
|
function checkRemainingMainExtensions( art, ext ) {
|
|
928
|
-
|
|
944
|
+
const refCtx = ext.kind === 'annotate' && hasSecurityAnno( ext )
|
|
945
|
+
? 'annotate-sec'
|
|
946
|
+
: ext.kind;
|
|
947
|
+
if (!resolvePath( ext.name, refCtx, ext )) // error for extend, info for annotate
|
|
929
948
|
return;
|
|
930
949
|
|
|
931
950
|
if (art?.builtin) {
|
|
@@ -1186,6 +1205,273 @@ function extend( model ) {
|
|
|
1186
1205
|
}
|
|
1187
1206
|
}
|
|
1188
1207
|
|
|
1208
|
+
// TODO TMP: copied from ./define.js: -----------------------------------------
|
|
1209
|
+
|
|
1210
|
+
/**
|
|
1211
|
+
* Set property `_parent` for all elements in `parent` to `parent` and do so
|
|
1212
|
+
* recursively for all sub elements.
|
|
1213
|
+
*
|
|
1214
|
+
* If not for extensions: construct === parent
|
|
1215
|
+
*
|
|
1216
|
+
* Param `initExtensions` is for parse.cdl - TODO delete
|
|
1217
|
+
*
|
|
1218
|
+
* TODO: separate extension!
|
|
1219
|
+
*/
|
|
1220
|
+
function initMembers( construct, parent, block, initExtensions = false ) {
|
|
1221
|
+
// TODO: split extend from init
|
|
1222
|
+
const main = parent._main || parent;
|
|
1223
|
+
const isQueryExtension = construct.kind === 'extend' && main.query;
|
|
1224
|
+
let obj = initItemsLinks( construct, block );
|
|
1225
|
+
initExprAnnoBlock( construct, block );
|
|
1226
|
+
if (obj.target && targetIsTargetAspect( obj )) {
|
|
1227
|
+
obj.targetAspect = obj.target;
|
|
1228
|
+
delete obj.target;
|
|
1229
|
+
}
|
|
1230
|
+
const { targetAspect } = obj;
|
|
1231
|
+
if (targetAspect) {
|
|
1232
|
+
if (obj.foreignKeys) {
|
|
1233
|
+
error( 'type-unexpected-foreign-keys', [ obj.foreignKeys[$location], construct ] );
|
|
1234
|
+
delete obj.foreignKeys; // continuation semantics: not specified
|
|
1235
|
+
}
|
|
1236
|
+
if (obj.on && !obj.target) {
|
|
1237
|
+
error( 'type-unexpected-on-condition', [ obj.on.location, construct ] );
|
|
1238
|
+
delete obj.on; // continuation semantics: not specified
|
|
1239
|
+
}
|
|
1240
|
+
if (targetAspect.elements)
|
|
1241
|
+
initAnonymousAspect();
|
|
1242
|
+
}
|
|
1243
|
+
if (obj !== parent && obj.elements && parent.enum) { // applying the extension
|
|
1244
|
+
initElementsAsEnum();
|
|
1245
|
+
}
|
|
1246
|
+
else {
|
|
1247
|
+
if (checkDefinitions( construct, parent, 'elements', obj.elements || false ))
|
|
1248
|
+
forEachInOrder( obj, 'elements', init );
|
|
1249
|
+
if (checkDefinitions( construct, parent, 'enum', obj.enum || false ))
|
|
1250
|
+
forEachGeneric( obj, 'enum', init );
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
if (obj.foreignKeys)
|
|
1254
|
+
forEachInOrder( obj, 'foreignKeys', init );
|
|
1255
|
+
if (checkDefinitions( construct, parent, 'actions' ))
|
|
1256
|
+
forEachGeneric( construct, 'actions', init );
|
|
1257
|
+
if (checkDefinitions( construct, parent, 'params' ))
|
|
1258
|
+
forEachInOrder( construct, 'params', init );
|
|
1259
|
+
const { returns } = construct;
|
|
1260
|
+
if (returns) {
|
|
1261
|
+
const { kind } = construct;
|
|
1262
|
+
returns.kind = (kind === 'extend' || kind === 'annotate') ? kind : 'param';
|
|
1263
|
+
init( returns, '' ); // '' is special name for returns parameter
|
|
1264
|
+
}
|
|
1265
|
+
return;
|
|
1266
|
+
|
|
1267
|
+
function initElementsAsEnum() {
|
|
1268
|
+
// in extensions, extended enums are represented as elements
|
|
1269
|
+
let hasElement = false;
|
|
1270
|
+
for (const n in obj.elements) {
|
|
1271
|
+
const e = obj.elements[n];
|
|
1272
|
+
if (e.kind === 'extend')
|
|
1273
|
+
continue;
|
|
1274
|
+
const noVal = e.value?.val === undefined && e.value?.sym === undefined;
|
|
1275
|
+
// TODO: forbid #symbol as enum value
|
|
1276
|
+
if (e.$syntax === 'element' || // `extend … with elements` or `extend with { element … }`
|
|
1277
|
+
noVal && e.$syntax !== 'enum' || // no value in CDL input
|
|
1278
|
+
e.virtual || e.key || e.masked || e.type || e.elements || e.items || e.stored) {
|
|
1279
|
+
// We do not want to complain separately about all element properties:
|
|
1280
|
+
error( 'ext-unexpected-element', [ e.location, construct ],
|
|
1281
|
+
{ name: e.name.id, code: 'extend … with enum' },
|
|
1282
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1283
|
+
'Unexpected elements like $(NAME) in an extension for an enum. Additionally, use $(CODE) when extending enums' );
|
|
1284
|
+
// Don't emit 'ext-expecting-enum' if this error is emitted.
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
e.kind = 'enum';
|
|
1288
|
+
if (noVal || e.$syntax !== 'enum')
|
|
1289
|
+
hasElement = true; // warning with CDL input or `name: {}` in CSN input
|
|
1290
|
+
}
|
|
1291
|
+
if (hasElement) {
|
|
1292
|
+
// This message is similar to the one above. In v6, we could probably
|
|
1293
|
+
// turn this warning into an error, remove `$syntax: 'element',
|
|
1294
|
+
// and use the above `ext-unexpected-element` only for CSN input.
|
|
1295
|
+
warning( 'ext-expecting-enum', [ obj.elements[$location], construct ],
|
|
1296
|
+
{ code: 'extend … with enum' }, 'Use $(CODE) when extending enums' );
|
|
1297
|
+
}
|
|
1298
|
+
forEachGeneric( { enum: obj.elements }, 'enum', init );
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
function initAnonymousAspect() {
|
|
1302
|
+
// TODO: main?
|
|
1303
|
+
const inEntity = parent._main?.kind === 'entity';
|
|
1304
|
+
// TODO: also allow indirectly (component in component in entity)?
|
|
1305
|
+
setLink( targetAspect, '_outer', obj );
|
|
1306
|
+
setLink( targetAspect, '_parent', parent._parent );
|
|
1307
|
+
setLink( targetAspect, '_main', null ); // for name resolution
|
|
1308
|
+
|
|
1309
|
+
parent = targetAspect;
|
|
1310
|
+
construct = parent; // avoid extension behavior
|
|
1311
|
+
targetAspect.kind = 'aspect'; // TODO: probably '$aspect' to detect
|
|
1312
|
+
setLink( targetAspect, '_block', block );
|
|
1313
|
+
initDollarSelf( targetAspect );
|
|
1314
|
+
// allow ref of up_ in anonymous aspect inside entity
|
|
1315
|
+
// (TODO: complain if used and the managed composition is included into
|
|
1316
|
+
// another entity - might induce auto-redirection):
|
|
1317
|
+
if (inEntity && !targetAspect.elements.up_) {
|
|
1318
|
+
const up = {
|
|
1319
|
+
name: { id: 'up_' },
|
|
1320
|
+
kind: '$navElement',
|
|
1321
|
+
location: obj.location,
|
|
1322
|
+
};
|
|
1323
|
+
setLink( up, '_parent', targetAspect );
|
|
1324
|
+
setLink( up, '_main', targetAspect ); // used on main artifact
|
|
1325
|
+
// recompilation case: both target and targetAspect → allow up_ in that case, too:
|
|
1326
|
+
const name = obj.target && resolveUncheckedPath( obj.target, 'target', obj );
|
|
1327
|
+
const entity = name && model.definitions[name];
|
|
1328
|
+
if (entity && entity.elements)
|
|
1329
|
+
setLink( up, '_origin', entity.elements.up_ );
|
|
1330
|
+
// processAspectComposition/expand() sets _origin to element of
|
|
1331
|
+
// generated target entity
|
|
1332
|
+
targetAspect.$tableAliases.up_ = up;
|
|
1333
|
+
}
|
|
1334
|
+
obj = targetAspect;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
function init( elem, name, prop ) {
|
|
1338
|
+
if (!elem.kind) // wrong CSN input
|
|
1339
|
+
elem.kind = dictKinds[prop];
|
|
1340
|
+
if (!elem.name && !elem._outer) {
|
|
1341
|
+
const ref = elem.targetElement || elem.kind === 'element' && elem.value;
|
|
1342
|
+
if (ref && ref.path) {
|
|
1343
|
+
elem.name = Object.assign( { $inferred: 'as' },
|
|
1344
|
+
ref.path[ref.path.length - 1] );
|
|
1345
|
+
}
|
|
1346
|
+
else { // RETURNS, parser robustness
|
|
1347
|
+
elem.name = { id: name, location: elem.location };
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
// if (!kindProperties[ elem.kind ]) console.log(elem.kind,elem.name)
|
|
1351
|
+
if ((elem.kind === 'extend' || elem.kind === 'annotate') && !initExtensions) {
|
|
1352
|
+
storeExtension( elem, name, prop, parent, block );
|
|
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 bl = elem._block || block;
|
|
1363
|
+
setLink( elem, '_block', bl );
|
|
1364
|
+
const existing = parent[prop]?.[name];
|
|
1365
|
+
const add = construct !== parent && (!existing || elem.$inferred !== 'include');
|
|
1366
|
+
// don't dump with `entity T {}; extend T with { extend e {}; e {}; e {} };`:
|
|
1367
|
+
if (elem.$duplicates === true && add)
|
|
1368
|
+
elem.$duplicates = null;
|
|
1369
|
+
setMemberParent( elem, name, parent, add && prop );
|
|
1370
|
+
checkRedefinition( elem );
|
|
1371
|
+
initMembers( elem, elem, bl, initExtensions );
|
|
1372
|
+
if (elem.kind === 'action' || elem.kind === 'function')
|
|
1373
|
+
initBoundSelfParam( elem.params, elem._main );
|
|
1374
|
+
|
|
1375
|
+
// for a correct home path, setMemberParent needed to be called
|
|
1376
|
+
|
|
1377
|
+
if (!elem.value || elem.kind !== 'element' ||
|
|
1378
|
+
elem.$syntax === 'enum' && parent.kind === 'extend') // ambiguous in parse-cdl
|
|
1379
|
+
return;
|
|
1380
|
+
// -> it's a calculated element
|
|
1381
|
+
if (!elem.type && elem.value?.type) { // top-level CAST( expr AS type )
|
|
1382
|
+
if (!elem.target)
|
|
1383
|
+
elem.type = { ...elem.value.type, $inferred: 'cast' };
|
|
1384
|
+
}
|
|
1385
|
+
elem.$syntax = 'calc';
|
|
1386
|
+
// TODO: it is not just "syntax" - maybe better test for `$calcDepElement`?
|
|
1387
|
+
createAndLinkCalcDepElement( elem );
|
|
1388
|
+
|
|
1389
|
+
// Special case (hack) for calculated elements that use composition+filter:
|
|
1390
|
+
// See "Notes on `$enclosed`" in `ExposingAssocWithFilter.md` for details.
|
|
1391
|
+
if (elem.target && elem.value.path?.[elem.value.path.length - 1]?.where) {
|
|
1392
|
+
delete elem.type;
|
|
1393
|
+
delete elem.on;
|
|
1394
|
+
delete elem.target;
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
// TODO: is only necessary for extensions - make special for extend/annotate
|
|
1400
|
+
function checkDefinitions( construct, parent, prop, dict = construct[prop] ) {
|
|
1401
|
+
// TODO: do differently, see also annotateMembers() in resolver
|
|
1402
|
+
// To have been checked by parsers:
|
|
1403
|
+
// - artifacts (CDL-only anyway) only inside [extend] context|service
|
|
1404
|
+
if (!dict)
|
|
1405
|
+
return false;
|
|
1406
|
+
const feature = kindProperties[parent.kind ?? 'element'][prop];
|
|
1407
|
+
if (feature &&
|
|
1408
|
+
(feature === true || construct.kind !== 'extend' || feature( prop, parent )))
|
|
1409
|
+
return true;
|
|
1410
|
+
const location = dict[$location];
|
|
1411
|
+
|
|
1412
|
+
// TODO: a bit inconsistent = not a simple switch on `prop`…
|
|
1413
|
+
if (prop === 'actions') {
|
|
1414
|
+
if (Object.keys( dict ).length) {
|
|
1415
|
+
error( 'def-unexpected-actions', [ location, construct ], {}, // TODO: ext-
|
|
1416
|
+
'Actions and functions only exist top-level and for entities' ); // or aspects
|
|
1417
|
+
}
|
|
1418
|
+
else {
|
|
1419
|
+
warning( 'ext-ignoring-actions', [ location, construct ], {},
|
|
1420
|
+
'Actions and functions only exist top-level and for entities' );
|
|
1421
|
+
return false;
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
else if (parent.kind === 'action' || parent.kind === 'function') {
|
|
1425
|
+
error( 'ext-unexpected-action', [ construct.location, construct ], { '#': parent.kind }, {
|
|
1426
|
+
std: 'Actions and functions can\'t be extended, only annotated', // TODO: → ext-unsupported
|
|
1427
|
+
action: 'Actions can\'t be extended, only annotated',
|
|
1428
|
+
function: 'Functions can\'t be extended, only annotated',
|
|
1429
|
+
} );
|
|
1430
|
+
}
|
|
1431
|
+
else if (prop === 'params') {
|
|
1432
|
+
if (!feature) {
|
|
1433
|
+
// Note: This error can't be triggered at the moment. But as we likely want to
|
|
1434
|
+
// allow extensions with params in the future, we keep the code.
|
|
1435
|
+
error( 'def-unexpected-params', [ location, construct ], {},
|
|
1436
|
+
'Parameters only exist for entities, actions or functions' );
|
|
1437
|
+
}
|
|
1438
|
+
else {
|
|
1439
|
+
// remark: we could allow this
|
|
1440
|
+
error( 'extend-with-params', [ location, construct ], {},
|
|
1441
|
+
'Extending artifacts with parameters is not supported' );
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
else if (feature) { // allowed in principle, but not with extend
|
|
1445
|
+
if (!Object.keys( dict ).length) {
|
|
1446
|
+
warning( 'ext-ignoring-elements', [ location, construct ], {},
|
|
1447
|
+
'Only structures with directly specified elements can be extended by elements' );
|
|
1448
|
+
return false;
|
|
1449
|
+
}
|
|
1450
|
+
else if (parent.$inferred === 'include') { // special case for better error message
|
|
1451
|
+
const variant = (construct.enum || construct.elements) ? 'elements' : 'std';
|
|
1452
|
+
error( 'ref-expected-direct-structure', [ location, construct ],
|
|
1453
|
+
{ '#': variant, art: parent } );
|
|
1454
|
+
}
|
|
1455
|
+
else {
|
|
1456
|
+
error( 'extend-type', [ location, construct ], {},
|
|
1457
|
+
'Only structures or enum types can be extended with elements/enums' );
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
else if (prop === 'elements') {
|
|
1461
|
+
error( 'def-unexpected-elements', [ location, construct ], {},
|
|
1462
|
+
'Elements only exist in entities, types or typed constructs' );
|
|
1463
|
+
}
|
|
1464
|
+
else if (prop === 'columns') {
|
|
1465
|
+
error( 'extend-columns', [ location, construct ], { art: construct } );
|
|
1466
|
+
}
|
|
1467
|
+
else { // if (prop === 'enum') {
|
|
1468
|
+
error( 'def-unexpected-enum', [ location, construct ], {},
|
|
1469
|
+
'Enum symbols can only be defined for types or typed constructs' );
|
|
1470
|
+
}
|
|
1471
|
+
return construct === parent;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
|
|
1189
1475
|
// includes ----------------------------------------------------------------
|
|
1190
1476
|
|
|
1191
1477
|
/**
|
|
@@ -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, true );
|
|
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
|
@@ -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
|
|
@@ -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
|
}
|
|
@@ -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;
|
package/lib/compiler/populate.js
CHANGED
|
@@ -716,8 +716,7 @@ function populate( model ) {
|
|
|
716
716
|
return initFromColumns( elem, elem.expand );
|
|
717
717
|
}
|
|
718
718
|
|
|
719
|
-
// TODO: make this function shorter
|
|
720
|
-
// parent/name) also be part of definer.js
|
|
719
|
+
// TODO: make this function shorter
|
|
721
720
|
// TODO: query is actually the elemParent, where the new elements are added to
|
|
722
721
|
// top-level: ( query, query.columns )
|
|
723
722
|
// inline: ( queryOrColParent, col.inline, col )
|
|
@@ -730,14 +729,13 @@ function populate( model ) {
|
|
|
730
729
|
query._main.elements = elemsParent.elements;
|
|
731
730
|
}
|
|
732
731
|
|
|
733
|
-
const isExpand = (query.expand === columns);
|
|
734
732
|
if (!columns)
|
|
735
733
|
columns = [ { val: '*' } ];
|
|
736
734
|
|
|
737
735
|
for (let i = 0; i < columns.length; ++i) {
|
|
738
736
|
const col = columns[i];
|
|
739
737
|
if (col.val === '*') {
|
|
740
|
-
const siblings = wildcardSiblings( columns
|
|
738
|
+
const siblings = wildcardSiblings( columns );
|
|
741
739
|
expandWildcard( col, siblings, inlineHead, query );
|
|
742
740
|
}
|
|
743
741
|
// If neither expression (value), expand, new virtual nor new association.
|
|
@@ -748,6 +746,7 @@ function populate( model ) {
|
|
|
748
746
|
q.$inlines.push( col );
|
|
749
747
|
col.kind = '$inline';
|
|
750
748
|
col.name = { id: `.${ q.$inlines.length }`, $inferred: '$internal' };
|
|
749
|
+
// TODO: use a name already set in define.js
|
|
751
750
|
// TODO: really use $inferred: '$internal', not '$inline' ? Re-check.
|
|
752
751
|
// a name for this internal symtab entry (e.g. '.2' to avoid clashes
|
|
753
752
|
// with real elements) is only relevant for `cdsc -R`/debugging
|
|
@@ -755,14 +754,15 @@ function populate( model ) {
|
|
|
755
754
|
// (is also relevant for the semantic location - only use positive)
|
|
756
755
|
dependsOnSilent( q, col );
|
|
757
756
|
// or use userQuery( query ) in the following, too?
|
|
758
|
-
setMemberParent( col, null, query );
|
|
757
|
+
setMemberParent( col, null, query ); // TODO: really set _parent?
|
|
759
758
|
initFromColumns( query, col.inline, col );
|
|
760
759
|
}
|
|
761
760
|
else if (!col.$replacement) {
|
|
762
|
-
const id =
|
|
761
|
+
const { id } = col.name;
|
|
763
762
|
col.kind = 'element';
|
|
764
|
-
dictAdd( elemsParent.elements, id, col, ( name, location ) => {
|
|
765
|
-
|
|
763
|
+
dictAdd( elemsParent.elements, id, col, ( name, location, c ) => {
|
|
764
|
+
if (c.name.$inferred !== '$internal')
|
|
765
|
+
error( 'duplicate-definition', [ location, query ], { name, '#': 'element' } );
|
|
766
766
|
} );
|
|
767
767
|
setMemberParent( col, id, query );
|
|
768
768
|
}
|
|
@@ -771,54 +771,6 @@ function populate( model ) {
|
|
|
771
771
|
return true;
|
|
772
772
|
}
|
|
773
773
|
|
|
774
|
-
/**
|
|
775
|
-
* TODO: probably do this already in definer.js
|
|
776
|
-
*
|
|
777
|
-
* @param col
|
|
778
|
-
* @param {number} colIndex
|
|
779
|
-
* @param query
|
|
780
|
-
* @param {boolean} insideExpand
|
|
781
|
-
* Whether the column is inside 'expand'.
|
|
782
|
-
* Anonymous 'expands' don't have a column parent, hence why we need to know this explicitly.
|
|
783
|
-
*/
|
|
784
|
-
function ensureColumnName( col, colIndex, query, insideExpand ) {
|
|
785
|
-
if (col.name)
|
|
786
|
-
return col.name.id;
|
|
787
|
-
if (col.inline || col.val === '*' || col.val === '**') // '**' = duplicate '*'
|
|
788
|
-
return '';
|
|
789
|
-
const path = col.value &&
|
|
790
|
-
(col.value.path || !col.value.args && col.value.func?.path);
|
|
791
|
-
if (path) {
|
|
792
|
-
const last = path.length && !path.broken && path[path.length - 1];
|
|
793
|
-
if (last) {
|
|
794
|
-
col.name = { id: last.id || '', location: last.location, $inferred: 'as' };
|
|
795
|
-
return col.name.id;
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
else if (insideExpand || col.expand ||
|
|
799
|
-
col.value && (col._columnParent || query._parent.kind !== 'select')) {
|
|
800
|
-
// _columnParent => inline/expand with path head; _parent -> only allowed in sub-selects
|
|
801
|
-
error( 'query-req-name', [ col.value?.location || col.location, query ], {},
|
|
802
|
-
'Alias name is required for this select item' );
|
|
803
|
-
}
|
|
804
|
-
else if (col.value) {
|
|
805
|
-
col.name = {
|
|
806
|
-
// NOTE: If the alias is changed, corresponding name-clash tests must be updated as well!
|
|
807
|
-
id: `$_column_${ colIndex + 1 }`,
|
|
808
|
-
location: col.value.location || col.location,
|
|
809
|
-
$inferred: '$internal',
|
|
810
|
-
};
|
|
811
|
-
return col.name.id;
|
|
812
|
-
}
|
|
813
|
-
// invent a name for code completion in expression, see also #10596
|
|
814
|
-
col.name = {
|
|
815
|
-
id: '',
|
|
816
|
-
location: col.value?.location || col.location,
|
|
817
|
-
$inferred: 'none',
|
|
818
|
-
};
|
|
819
|
-
return '';
|
|
820
|
-
}
|
|
821
|
-
|
|
822
774
|
function initElem( elem ) {
|
|
823
775
|
// TODO: we could share code with initMembers/init() in define.js
|
|
824
776
|
if (elem.type && !elem.type.$inferred)
|
|
@@ -844,23 +796,21 @@ function populate( model ) {
|
|
|
844
796
|
|
|
845
797
|
// col ($replacement set before *)
|
|
846
798
|
// false if two cols have same name
|
|
847
|
-
function wildcardSiblings( columns
|
|
799
|
+
function wildcardSiblings( columns ) {
|
|
848
800
|
const siblings = Object.create( null );
|
|
849
801
|
if (!columns)
|
|
850
802
|
return siblings;
|
|
851
803
|
|
|
852
804
|
let seenWildcard = null;
|
|
853
|
-
let colIndex = 0;
|
|
854
805
|
for (const col of columns) {
|
|
855
|
-
const
|
|
856
|
-
if (
|
|
806
|
+
const { name } = col;
|
|
807
|
+
if (name) {
|
|
857
808
|
col.$replacement = !seenWildcard;
|
|
858
|
-
siblings[id] = !(id in siblings) && col;
|
|
809
|
+
siblings[name.id] = !(name.id in siblings) && col;
|
|
859
810
|
}
|
|
860
811
|
else if (col.val === '*') {
|
|
861
812
|
seenWildcard = true;
|
|
862
813
|
}
|
|
863
|
-
++colIndex;
|
|
864
814
|
}
|
|
865
815
|
return siblings;
|
|
866
816
|
}
|
|
@@ -975,7 +925,14 @@ function populate( model ) {
|
|
|
975
925
|
if (!sibling.target || sibling.target.$inferred || // not explicit REDIRECTED TO
|
|
976
926
|
path && path[path.length - 1].id !== sibling.name.id) { // or renamed
|
|
977
927
|
const { id } = sibling.name;
|
|
978
|
-
if (
|
|
928
|
+
if (sibling.name.$inferred === '$internal') {
|
|
929
|
+
error( 'query-req-name',
|
|
930
|
+
// TODO: message function: `query` should work directly
|
|
931
|
+
[ (sibling.value || sibling).location, query ],
|
|
932
|
+
{},
|
|
933
|
+
'Alias name is required for this select item' );
|
|
934
|
+
}
|
|
935
|
+
else if (Array.isArray( navElem )) {
|
|
979
936
|
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
980
937
|
info( 'wildcard-excluding-many', [ sibling.name.location, query ],
|
|
981
938
|
{ id, keyword: 'excluding' },
|
|
@@ -1259,6 +1216,7 @@ function populate( model ) {
|
|
|
1259
1216
|
// Auto-exposure -----------------------------------------------------------
|
|
1260
1217
|
|
|
1261
1218
|
// TODO: do something in kick-start.js ?
|
|
1219
|
+
// Remark: With includes, the compiler propagates @cds.autoexpose early.
|
|
1262
1220
|
function isAutoExposed( target ) {
|
|
1263
1221
|
if (target.$autoexpose !== undefined)
|
|
1264
1222
|
return target.$autoexpose;
|
|
@@ -12,7 +12,6 @@ const {
|
|
|
12
12
|
forEachDefinition,
|
|
13
13
|
forEachMember,
|
|
14
14
|
forEachGeneric,
|
|
15
|
-
isBetaEnabled,
|
|
16
15
|
} = require( '../base/model');
|
|
17
16
|
const {
|
|
18
17
|
setLink,
|
|
@@ -297,7 +296,7 @@ function propagate( model ) {
|
|
|
297
296
|
else if (destination.kind === 'element' &&
|
|
298
297
|
destination._main?.query && // query element
|
|
299
298
|
!destination.$calc && origin.$calc !== true &&
|
|
300
|
-
|
|
299
|
+
!model.options.noDollarCalc ) {
|
|
301
300
|
destination.$calc
|
|
302
301
|
= Object.assign( copyExpr( origin[prop] ), { $inferred: 'prop' } );
|
|
303
302
|
if (rewriteRefsInExpression( destination, origin, '$calc' ))
|