@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.
Files changed (65) hide show
  1. package/CHANGELOG.md +34 -1156
  2. package/README.md +1 -10
  3. package/doc/IncompatibleChanges_v5.md +436 -0
  4. package/doc/IncompatibleChanges_v6.md +659 -0
  5. package/doc/Versioning.md +3 -7
  6. package/lib/api/main.js +1 -0
  7. package/lib/api/options.js +5 -0
  8. package/lib/api/validate.js +3 -0
  9. package/lib/base/message-registry.js +23 -0
  10. package/lib/base/messages.js +1 -1
  11. package/lib/base/model.js +3 -2
  12. package/lib/checks/actionsFunctions.js +6 -3
  13. package/lib/checks/existsInForbiddenPlaces.js +32 -0
  14. package/lib/checks/validator.js +2 -0
  15. package/lib/compiler/assert-consistency.js +3 -5
  16. package/lib/compiler/checks.js +4 -8
  17. package/lib/compiler/define.js +244 -459
  18. package/lib/compiler/extend.js +297 -11
  19. package/lib/compiler/finalize-parse-cdl.js +2 -10
  20. package/lib/compiler/generate.js +29 -1
  21. package/lib/compiler/populate.js +21 -63
  22. package/lib/compiler/propagator.js +1 -2
  23. package/lib/compiler/resolve.js +2 -12
  24. package/lib/compiler/shared.js +18 -5
  25. package/lib/compiler/tweak-assocs.js +13 -9
  26. package/lib/compiler/utils.js +97 -0
  27. package/lib/compiler/xpr-rewrite.js +2 -1
  28. package/lib/edm/annotations/edmJson.js +9 -6
  29. package/lib/edm/annotations/genericTranslation.js +8 -4
  30. package/lib/edm/csn2edm.js +3 -4
  31. package/lib/edm/edmInboundChecks.js +1 -2
  32. package/lib/edm/edmPreprocessor.js +3 -3
  33. package/lib/gen/CdlGrammar.checksum +1 -1
  34. package/lib/gen/CdlParser.js +1 -1
  35. package/lib/gen/Dictionary.json +16 -1
  36. package/lib/json/from-csn.js +4 -6
  37. package/lib/json/to-csn.js +3 -3
  38. package/lib/model/csnRefs.js +13 -4
  39. package/lib/model/enrichCsn.js +4 -2
  40. package/lib/optionProcessor.js +8 -4
  41. package/lib/render/utils/sql.js +3 -2
  42. package/lib/transform/db/applyTransformations.js +1 -1
  43. package/lib/transform/db/assertUnique.js +3 -3
  44. package/lib/transform/db/assocsToQueries/normalizeFrom.js +33 -0
  45. package/lib/transform/db/assocsToQueries/transformExists.js +14 -3
  46. package/lib/transform/db/assocsToQueries/utils.js +1 -1
  47. package/lib/transform/db/backlinks.js +4 -4
  48. package/lib/transform/db/cdsPersistence.js +4 -4
  49. package/lib/transform/db/constraints.js +4 -4
  50. package/lib/transform/db/expansion.js +5 -5
  51. package/lib/transform/db/flattening.js +4 -5
  52. package/lib/transform/db/rewriteCalculatedElements.js +3 -3
  53. package/lib/transform/db/temporal.js +11 -11
  54. package/lib/transform/draft/db.js +2 -0
  55. package/lib/transform/draft/odata.js +5 -7
  56. package/lib/transform/effective/flattening.js +1 -2
  57. package/lib/transform/forOdata.js +3 -3
  58. package/lib/transform/forRelationalDB.js +1 -1
  59. package/lib/transform/odata/createForeignKeys.js +1 -2
  60. package/lib/transform/odata/flattening.js +1 -2
  61. package/lib/transform/odata/toFinalBaseType.js +52 -55
  62. package/lib/transform/transformUtils.js +3 -4
  63. package/package.json +1 -1
  64. package/doc/CHANGELOG_BETA.md +0 -464
  65. package/doc/CHANGELOG_DEPRECATED.md +0 -235
@@ -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
- initMembers,
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
- warning( 'ext-unexpected-returns', [ ext.returns.location, ext ], {
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
- if (!dict) {
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( 'ext-undefined-element', ext.name.location, ext,
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( 'ext-undefined-element', ext.name.location, ext,
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( 'ext-undefined-key', ext.name.location, ext,
883
+ notFound( `ext-undefined-key${ securityRelevant }`, ext.name.location, ext,
873
884
  { name }, parent.foreignKeys );
874
885
  break;
875
886
  case 'params':
876
- notFound( 'ext-undefined-param', ext.name.location, ext,
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( 'ext-undefined-action', ext.name.location, ext,
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
- if (!resolvePath( ext.name, ext.kind, ext )) // error for extend, info for annotate
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, ext, ext._block, true );
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.
@@ -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;
@@ -716,8 +716,7 @@ function populate( model ) {
716
716
  return initFromColumns( elem, elem.expand );
717
717
  }
718
718
 
719
- // TODO: make this function shorter - make part of this (e.g. setting
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, query );
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 = ensureColumnName( col, i, query, isExpand );
761
+ const { id } = col.name;
763
762
  col.kind = 'element';
764
- dictAdd( elemsParent.elements, id, col, ( name, location ) => {
765
- error( 'duplicate-definition', [ location, query ], { name, '#': 'element' } );
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, query ) {
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 id = ensureColumnName( col, colIndex, query, false );
856
- if (id) {
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 (Array.isArray( navElem )) {
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
- isBetaEnabled( model.options, '$calcForDraft' )) {
299
+ !model.options.noDollarCalc ) {
301
300
  destination.$calc
302
301
  = Object.assign( copyExpr( origin[prop] ), { $inferred: 'prop' } );
303
302
  if (rewriteRefsInExpression( destination, origin, '$calc' ))