@sap/cds-compiler 4.3.2 → 4.4.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 (81) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/lib/api/main.js +14 -24
  3. package/lib/api/options.js +1 -0
  4. package/lib/api/trace.js +38 -0
  5. package/lib/base/location.js +46 -1
  6. package/lib/base/message-registry.js +68 -16
  7. package/lib/base/messages.js +8 -3
  8. package/lib/checks/.eslintrc.json +1 -0
  9. package/lib/checks/actionsFunctions.js +1 -1
  10. package/lib/checks/annotationsOData.js +2 -2
  11. package/lib/checks/selectItems.js +4 -1
  12. package/lib/compiler/assert-consistency.js +3 -2
  13. package/lib/compiler/base.js +1 -1
  14. package/lib/compiler/builtins.js +25 -1
  15. package/lib/compiler/checks.js +6 -5
  16. package/lib/compiler/define.js +12 -10
  17. package/lib/compiler/extend.js +22 -22
  18. package/lib/compiler/finalize-parse-cdl.js +1 -1
  19. package/lib/compiler/generate.js +70 -53
  20. package/lib/compiler/kick-start.js +7 -5
  21. package/lib/compiler/populate.js +31 -22
  22. package/lib/compiler/propagator.js +6 -2
  23. package/lib/compiler/resolve.js +52 -17
  24. package/lib/compiler/shared.js +74 -38
  25. package/lib/compiler/tweak-assocs.js +64 -23
  26. package/lib/compiler/utils.js +40 -23
  27. package/lib/edm/.eslintrc.json +2 -0
  28. package/lib/edm/EdmPrimitiveTypeDefinitions.js +252 -0
  29. package/lib/edm/annotations/edmJson.js +994 -0
  30. package/lib/edm/annotations/genericTranslation.js +75 -421
  31. package/lib/edm/annotations/vocabularyDefinitions.js +160 -0
  32. package/lib/edm/csn2edm.js +12 -5
  33. package/lib/edm/edm.js +14 -73
  34. package/lib/edm/edmPreprocessor.js +6 -0
  35. package/lib/gen/Dictionary.json +187 -16
  36. package/lib/gen/language.checksum +1 -1
  37. package/lib/gen/language.interp +1 -1
  38. package/lib/gen/languageLexer.interp +1 -1
  39. package/lib/gen/languageLexer.js +1129 -671
  40. package/lib/gen/languageParser.js +4285 -4283
  41. package/lib/json/from-csn.js +13 -18
  42. package/lib/json/to-csn.js +11 -6
  43. package/lib/language/antlrParser.js +0 -1
  44. package/lib/language/docCommentParser.js +1 -1
  45. package/lib/language/errorStrategy.js +95 -30
  46. package/lib/language/genericAntlrParser.js +21 -1
  47. package/lib/main.js +13 -3
  48. package/lib/model/csnRefs.js +42 -8
  49. package/lib/model/csnUtils.js +14 -2
  50. package/lib/model/enrichCsn.js +33 -5
  51. package/lib/model/revealInternalProperties.js +5 -0
  52. package/lib/modelCompare/compare.js +76 -14
  53. package/lib/modelCompare/utils/filter.js +19 -12
  54. package/lib/optionProcessor.js +2 -0
  55. package/lib/render/.eslintrc.json +1 -1
  56. package/lib/render/manageConstraints.js +1 -0
  57. package/lib/render/toHdbcds.js +3 -0
  58. package/lib/render/toRename.js +3 -1
  59. package/lib/render/toSql.js +46 -92
  60. package/lib/render/utils/common.js +76 -0
  61. package/lib/render/utils/delta.js +17 -3
  62. package/lib/sql-identifier.js +1 -1
  63. package/lib/transform/db/.eslintrc.json +1 -0
  64. package/lib/transform/db/applyTransformations.js +30 -4
  65. package/lib/transform/db/associations.js +22 -10
  66. package/lib/transform/db/backlinks.js +6 -2
  67. package/lib/transform/db/expansion.js +2 -2
  68. package/lib/transform/db/transformExists.js +13 -39
  69. package/lib/transform/draft/db.js +14 -3
  70. package/lib/transform/draft/odata.js +5 -18
  71. package/lib/transform/effective/associations.js +46 -15
  72. package/lib/transform/effective/main.js +7 -2
  73. package/lib/transform/effective/misc.js +43 -24
  74. package/lib/transform/effective/queries.js +20 -22
  75. package/lib/transform/effective/types.js +6 -2
  76. package/lib/transform/forOdata.js +5 -2
  77. package/lib/transform/localized.js +1 -1
  78. package/lib/transform/parseExpr.js +73 -21
  79. package/lib/transform/translateAssocsToJoins.js +22 -15
  80. package/lib/utils/term.js +2 -2
  81. package/package.json +2 -1
@@ -214,6 +214,7 @@ function check( model ) {
214
214
 
215
215
  // TODO: This check should be moved to localized.js
216
216
  // "key" keyword at localized element in SELECT list.
217
+ // TODO: not in inferred elements, but also inside aspects
217
218
  if (elem.key?.val && elem._main?.query) {
218
219
  // original element is localized but not key, as that would have
219
220
  // already resulted in a warning by localized.js
@@ -459,7 +460,7 @@ function check( model ) {
459
460
  error( 'ref-unexpected-calculated', [ key.location, elem ], { '#': 'fkey' } );
460
461
  }
461
462
  }
462
- if (elem.default) {
463
+ if (elem.default?.val !== undefined) {
463
464
  if (elem.targetAspect || elem.on || fkCount !== 1) {
464
465
  const variant = (elem.targetAspect && 'targetAspect') || (elem.on && 'onCond') || 'multi';
465
466
  error( 'type-unexpected-default', [ elem.default.location, elem ], {
@@ -852,7 +853,7 @@ function check( model ) {
852
853
 
853
854
  // Element must exist in annotation
854
855
  if (!elementDecl) {
855
- warning( null, [ anno.location || anno.name.location, art ],
856
+ warning( null, [ anno.location || anno.name.location, art, anno ],
856
857
  { name: anno.name.id, anno: annoDecl.name.id },
857
858
  'Element $(NAME) not found for annotation $(ANNO)' );
858
859
  return;
@@ -866,11 +867,11 @@ function check( model ) {
866
867
  // Must have literal or path unless it is a boolean
867
868
  if (!anno.literal && !anno.path && elementDecl._effectiveType?.category !== 'boolean') {
868
869
  if (elementDecl.type?._artifact) {
869
- warning( 'anno-expecting-value', [ anno.location || anno.name.location, art ],
870
+ warning( 'anno-expecting-value', [ anno.location || anno.name.location, art, anno ],
870
871
  { '#': 'type', type: elementDecl.type._artifact } );
871
872
  }
872
873
  else {
873
- warning( 'anno-expecting-value', [ anno.location || anno.name.location, art ],
874
+ warning( 'anno-expecting-value', [ anno.location || anno.name.location, art, anno ],
874
875
  { '#': 'std', anno: anno.name.id } );
875
876
  }
876
877
 
@@ -891,7 +892,7 @@ function check( model ) {
891
892
  return;
892
893
 
893
894
  const anno = annoDef.name.id;
894
- const loc = [ value.location || value.name.location, art ];
895
+ const loc = [ value.location || value.name.location, art, annoDef ];
895
896
 
896
897
  // Array expected?
897
898
  if (elementDecl._effectiveType.items) {
@@ -136,7 +136,7 @@ const {
136
136
  storeExtension,
137
137
  dependsOnSilent,
138
138
  pathName,
139
- isDirectComposition,
139
+ targetCantBeAspect,
140
140
  } = require('./utils');
141
141
  const { compareLayer } = require('./moduleLayers');
142
142
  const { initBuiltins, isInReservedNamespace } = require('./builtins');
@@ -313,7 +313,6 @@ function define( model ) {
313
313
  artifacts[using] = {
314
314
  kind: 'using', // !, not namespace - we do not know artifact yet
315
315
  name: { id: using, location, $inferred: 'as' },
316
- // TODO: use global ref (in general - all uses of splitIntoPath)
317
316
  extern: { location, id: absolute },
318
317
  location,
319
318
  $inferred: 'path-prefix',
@@ -564,7 +563,7 @@ function define( model ) {
564
563
  initMembers( art, art, block );
565
564
  initDollarSelf( art ); // $self
566
565
  if (art.params)
567
- initDollarParameters( art );
566
+ initDollarParameters( art ); // $parameters
568
567
 
569
568
  if (!art.query)
570
569
  return;
@@ -1199,7 +1198,7 @@ function define( model ) {
1199
1198
 
1200
1199
  // To be reworked -------------------------------------------------------------
1201
1200
 
1202
- // TODO: make special for extend/annotate
1201
+ // TODO: is only necessary for extensions - make special for extend/annotate
1203
1202
  function checkDefinitions( construct, parent, prop, dict = construct[prop] ) {
1204
1203
  // TODO: do differently, see also annotateMembers() in resolver
1205
1204
  // To have been checked by parsers:
@@ -1227,7 +1226,7 @@ function define( model ) {
1227
1226
  //
1228
1227
  else if (parent.kind === 'action' || parent.kind === 'function') {
1229
1228
  error( 'ext-unexpected-action', [ construct.location, construct ], { '#': parent.kind }, {
1230
- std: 'Actions and functions can\'t be extended, only annotated',
1229
+ std: 'Actions and functions can\'t be extended, only annotated', // TODO: → ext-unsupported
1231
1230
  action: 'Actions can\'t be extended, only annotated',
1232
1231
  function: 'Functions can\'t be extended, only annotated',
1233
1232
  } );
@@ -1277,17 +1276,20 @@ function define( model ) {
1277
1276
 
1278
1277
  /**
1279
1278
  * Return whether the `target` is actually a `targetAspect`
1280
- * TODO: really do that here and not in kick-start.js?
1281
1279
  */
1282
1280
  function targetIsTargetAspect( elem ) {
1283
1281
  const { target } = elem;
1284
- if (target.elements) {
1285
- // TODO: error if CSN has both target.elements and targetAspect.elements
1286
- // -> delete target
1282
+ if (target.elements) // CSN parser ensures: has no targetAspect then
1287
1283
  return true;
1284
+
1285
+ if (elem.targetAspect) {
1286
+ // Ensure that a compiled CSN is parseable - not inside query, only on element
1287
+ return false;
1288
1288
  }
1289
- if (elem.targetAspect || options.parseCdl || !isDirectComposition( elem ))
1289
+ if (targetCantBeAspect( elem ) || options.parseCdl)
1290
1290
  return false;
1291
+ // Compare this check with check in acceptEntity() called by resolvePath()
1292
+ // Remark: do not check `on` and `foreignKeys` here, we want error for those, not the aspect
1291
1293
  const name = resolveUncheckedPath( target, 'target', elem );
1292
1294
  const aspect = name && model.definitions[name];
1293
1295
  return (aspect?.kind === 'aspect' || aspect?.kind === 'type') && // type is sloppy
@@ -2,7 +2,7 @@
2
2
 
3
3
  'use strict';
4
4
 
5
- const { weakLocation } = require('../base/location');
5
+ const { weakRefLocation } = require('../base/location');
6
6
  const { searchName } = require('../base/messages');
7
7
  const {
8
8
  forEachInOrder,
@@ -686,21 +686,15 @@ function extend( model ) {
686
686
  }
687
687
  }
688
688
 
689
- // const unexpected_props = {
690
- // elements: 'anno-unexpected-elements',
691
- // enum: 'anno-unexpected-elements', // TODO
692
- // params: 'anno-unexpected-params',
693
- // actions: 'anno-unexpected-actions',
694
- // };
695
- // const undefined_props = {
696
- // elements: 'anno-undefined-element',
697
- // enum: 'anno-undefined-element', // TODO
698
- // params: 'anno-undefined-param',
699
- // actions: 'anno-undefined-action',
700
- // };
701
-
702
689
  function checkRemainingMemberExtensions( parent, ext, prop, name ) {
703
690
  // console.log('CRME:',prop,name,parent,ext)
691
+
692
+ // TODO: just use `ext-undefined-element` etc also when no elements are there
693
+ // at all (but use an extra text variant and the `{…}` location). Reason: we
694
+ // might allow to add new actions, and an `annotate` on an undefined action
695
+ // should not lead to another message id. We would use and extra message id
696
+ // if we consider this an error or such sub annotates are then ignored
697
+ // (i.e. not put into the "super annotate").
704
698
  const dict = parent[prop];
705
699
  if (!dict) {
706
700
  // TODO: check - for each name? - better locations
@@ -714,6 +708,9 @@ function extend( model ) {
714
708
  { '#': (parent._effectiveType?.kind === 'entity') ? 'entity' : 'std' }, {
715
709
  std: 'Elements only exist in entities, types or typed constructs',
716
710
  entity: 'Elements of entity types can\'t be annotated',
711
+ // TODO: extra msg for 'entity'? → this is some other
712
+ // situation, somehow similar when trying to annotate elements
713
+ // of target entity
717
714
  } );
718
715
  break;
719
716
  case 'params':
@@ -721,8 +718,8 @@ function extend( model ) {
721
718
  'Parameters only exist for actions or functions' );
722
719
  break;
723
720
  case 'actions':
724
- // TODO: check if artifact can have actions, similar to `anno-unexpected-actions`
725
- notFound( 'anno-undefined-action', ext.name.location, ext,
721
+ // TODO: use extra text variant and location of dictionary
722
+ notFound( 'ext-undefined-action', ext.name.location, ext,
726
723
  { '#': 'action', art: parent, name } );
727
724
  break;
728
725
  default:
@@ -737,22 +734,22 @@ function extend( model ) {
737
734
  const art = inReturns || parent;
738
735
  switch (prop) {
739
736
  case 'elements':
740
- notFound( 'anno-undefined-element', ext.name.location, ext,
737
+ notFound( 'ext-undefined-element', ext.name.location, ext,
741
738
  { '#': (inReturns ? 'returns' : 'element'), art, name },
742
739
  parent.elements );
743
740
  break;
744
741
  case 'enum': // TODO: extra msg id?
745
- notFound( 'anno-undefined-element', ext.name.location, ext,
742
+ notFound( 'ext-undefined-element', ext.name.location, ext,
746
743
  { '#': (inReturns ? 'enum-returns' : 'enum'), art, name },
747
744
  parent.enum );
748
745
  break;
749
746
  case 'params':
750
- notFound( 'anno-undefined-param', ext.name.location, ext,
747
+ notFound( 'ext-undefined-param', ext.name.location, ext,
751
748
  { '#': 'param', art: parent, name },
752
749
  parent.params );
753
750
  break;
754
751
  case 'actions':
755
- notFound( 'anno-undefined-action', ext.name.location, ext,
752
+ notFound( 'ext-undefined-action', ext.name.location, ext,
756
753
  { '#': 'action', art: parent, name },
757
754
  parent.actions );
758
755
  break;
@@ -1153,8 +1150,10 @@ function extend( model ) {
1153
1150
  // Warning 'Overwrites definition from include "I" (at elem def)
1154
1151
  const parent = ext === art && art;
1155
1152
  const members = ext[prop];
1156
- if (members)
1153
+ if (members) {
1157
1154
  ext[prop] = Object.create( null );
1155
+ ext[prop][$location] = members[$location];
1156
+ }
1158
1157
  let hasNewElement = false;
1159
1158
 
1160
1159
  for (const ref of ext.includes) {
@@ -1162,6 +1161,7 @@ function extend( model ) {
1162
1161
  if (template) { // be robust
1163
1162
  if (template[prop] && !ext[prop])
1164
1163
  ext[prop] = Object.create( null );
1164
+ const location = weakRefLocation( ref );
1165
1165
  // eslint-disable-next-line no-loop-func
1166
1166
  forEachInOrder( template, prop, ( origin, name ) => {
1167
1167
  if (members && members[name]) {
@@ -1170,7 +1170,7 @@ function extend( model ) {
1170
1170
  return;
1171
1171
  }
1172
1172
  hasNewElement = true;
1173
- const elem = linkToOrigin( origin, name, parent, prop, weakLocation( ref.location ) );
1173
+ const elem = linkToOrigin( origin, name, parent, prop, location );
1174
1174
  setLink( elem, '_block', origin._block );
1175
1175
  if (!parent) // not yet set for EXTEND foo WITH bar => linkToOrigin() did not add it
1176
1176
  dictAdd( ext[prop], name, elem );
@@ -86,7 +86,7 @@ function finalizeParseCdl( model ) {
86
86
  resolveUncheckedPath( include, 'include', main );
87
87
 
88
88
  // define.js takes care that `target` is a ref and
89
- // `targetAspect` is a structure.
89
+ // `targetAspect` is a structure (anonymous aspect) or ref to aspect.
90
90
  if (artifact.target)
91
91
  resolveUncheckedPath( artifact.target, 'target', main );
92
92
  if (artifact.targetAspect)
@@ -17,7 +17,9 @@ const {
17
17
  isDirectComposition,
18
18
  copyExpr,
19
19
  } = require('./utils');
20
- const { weakLocation } = require('../base/location');
20
+ const { weakLocation, weakRefLocation, weakEndLocation } = require('../base/location');
21
+
22
+ const $location = Symbol.for( 'cds.$location' );
21
23
 
22
24
  function generate( model ) {
23
25
  const { options } = model;
@@ -220,6 +222,7 @@ function generate( model ) {
220
222
  warning( 'def-ignoring-localized', [ errpos.location, elem ],
221
223
  { keyword: 'localized' },
222
224
  'Keyword $(KEYWORD) is ignored for primary keys' );
225
+ // continuation semantics as stated: counts as key field in texts entity
223
226
  }
224
227
  }
225
228
  if (textElems.length <= keys)
@@ -277,11 +280,11 @@ function generate( model ) {
277
280
  * @param {boolean} fioriEnabled
278
281
  */
279
282
  function createTextsEntity( base, absolute, textElems, fioriEnabled ) {
280
- const { location } = base.name;
283
+ const location = weakLocation( base.elements[$location] || base.location );
281
284
  const art = {
282
285
  kind: 'entity',
283
286
  name: { id: absolute, location },
284
- location: base.location,
287
+ location,
285
288
  elements: Object.create( null ),
286
289
  $inferred: 'localized-entity',
287
290
  };
@@ -332,30 +335,8 @@ function generate( model ) {
332
335
  // assertUnique array value, first entry is 'locale'
333
336
  const assertUniqueValue = [];
334
337
 
335
- for (const orig of textElems) {
336
- const elem = linkToOrigin( orig, orig.name.id, art, 'elements' );
337
- if (orig.key && orig.key.val) {
338
- // elem.key = { val: fioriEnabled ? null : true, $inferred: 'localized', location };
339
- // TODO: the previous would be better, but currently not supported in toCDL
340
- if (!fioriEnabled) {
341
- elem.key = { val: true, $inferred: 'localized', location };
342
- // If the propagated elements remain key (that is not fiori.draft.enabled)
343
- // they should be omitted from OData containment EDM
344
- setAnnotation( elem, '@odata.containment.ignore', location );
345
- }
346
- else {
347
- // add the former key paths to the unique constraint
348
- assertUniqueValue.push({
349
- path: [ { id: orig.name.id, location: orig.location } ],
350
- location: orig.location,
351
- });
352
- }
353
- }
354
- if (hasTruthyProp( orig, 'localized' )) { // use location of LOCALIZED keyword
355
- const localized = orig.localized || orig.type || orig.name;
356
- elem.localized = { val: null, $inferred: 'localized', location: localized.location };
357
- }
358
- }
338
+ for (const orig of textElems)
339
+ addElementToTextsEntity( orig, art, fioriEnabled, assertUniqueValue );
359
340
 
360
341
  initArtifact( art );
361
342
  if (art.includes) {
@@ -366,7 +347,7 @@ function generate( model ) {
366
347
  if (fioriEnabled) {
367
348
  // The includes mechanism puts TextsAspect's elements before .texts' elements.
368
349
  // Because ID_texts is not copied from TextsAspect, the order is messed
369
- // up. Fix it.
350
+ // up. Fix it. TODO: introduce $includeAfter from Extensions.md
370
351
  const { elements } = art;
371
352
  art.elements = Object.create( null );
372
353
  const names = [ 'ID_texts', 'locale', ...Object.keys( elements ) ];
@@ -385,6 +366,31 @@ function generate( model ) {
385
366
  return art;
386
367
  }
387
368
 
369
+ function addElementToTextsEntity( orig, art, fioriEnabled, assertUniqueValue ) {
370
+ const elem = linkToOrigin( orig, orig.name.id, art, 'elements' );
371
+ // To keep the locations of non-inferred original elements, do not set $inferred:
372
+ if (orig.$inferred)
373
+ elem.$inferred = 'localized-origin';
374
+ const { location } = elem;
375
+ if (orig.key && orig.key.val) {
376
+ // elem.key = { val: fioriEnabled ? null : true, $inferred: 'localized', location };
377
+ // TODO: the previous would be better, but currently not supported in toCDL
378
+ if (!fioriEnabled) {
379
+ elem.key = { val: true, $inferred: 'localized', location };
380
+ // If the propagated elements remain key (that is not fiori.draft.enabled)
381
+ // they should be omitted from OData containment EDM
382
+ setAnnotation( elem, '@odata.containment.ignore', location );
383
+ }
384
+ else {
385
+ // add the former key paths to the unique constraint
386
+ assertUniqueValue.push( { path: [ { id: orig.name.id, location } ], location } );
387
+ }
388
+ }
389
+ if (hasTruthyProp( orig, 'localized' )) { // use location of LOCALIZED keyword
390
+ elem.localized = { val: null, $inferred: 'localized', location };
391
+ }
392
+ }
393
+
388
394
  /**
389
395
  * Enrich the `.texts` entity for the given base artifact.
390
396
  * In contrast to createTextsEntityWithDefaultElements(), this one creates
@@ -400,14 +406,15 @@ function generate( model ) {
400
406
  function enrichTextsEntityWithInclude( art, base, absolute, fioriEnabled ) {
401
407
  const textsAspectName = 'sap.common.TextsAspect';
402
408
  const textsAspect = model.definitions['sap.common.TextsAspect'];
403
- const { location } = art.name;
409
+ const { location } = art;
404
410
 
405
- art.includes = [ createInclude( textsAspectName, base.location ) ];
411
+ art.includes = [ createInclude( textsAspectName, location ) ];
406
412
 
407
413
  if (fioriEnabled) {
408
414
  // "Early" include; only for element `locale`, which has its `key` property
409
415
  // removed (or rather: it is not copied).
410
416
  linkToOrigin( textsAspect.elements.locale, 'locale', art, 'elements', location );
417
+ art.elements.locale.$inferred = 'localized';
411
418
  }
412
419
 
413
420
  if (addTextsLanguageAssoc && art.elements.language)
@@ -425,12 +432,13 @@ function generate( model ) {
425
432
  // If there is a type `sap.common.Locale`, then use it as the type for the element `locale`.
426
433
  // If not, use the default `cds.String` with a length of 14.
427
434
  const hasLocaleType = model.definitions['sap.common.Locale']?.kind === 'type';
428
- const { location } = art.name;
435
+ const { location } = art; // is already a weak location
429
436
  const locale = {
430
437
  name: { location, id: 'locale' },
431
438
  kind: 'element',
432
439
  type: linkMainArtifact( location, hasLocaleType ? 'sap.common.Locale' : 'cds.String' ),
433
440
  location,
441
+ $inferred: 'localized', // $generated in Universal CSN, no $location
434
442
  };
435
443
  if (!hasLocaleType)
436
444
  locale.length = { literal: 'number', val: 14, location };
@@ -449,7 +457,7 @@ function generate( model ) {
449
457
  // texts : Composition of many Books.texts on texts.ID=ID;
450
458
  /** @type {array} */
451
459
  const keys = textElems.filter( e => e.key && e.key.val );
452
- const { location } = art.name;
460
+ const location = weakEndLocation( art.elements[$location] ) || weakLocation( art.location );
453
461
  const texts = {
454
462
  name: { location, id: 'texts' },
455
463
  kind: 'element',
@@ -568,7 +576,7 @@ function generate( model ) {
568
576
  while (origin && !origin.targetAspect && origin._origin)
569
577
  origin = origin._origin;
570
578
  let target = origin.targetAspect;
571
- if (target && target.path)
579
+ if (target?.path)
572
580
  target = resolvePath( origin.targetAspect, 'targetAspect', origin );
573
581
  if (!target || !target.elements)
574
582
  return;
@@ -601,7 +609,7 @@ function generate( model ) {
601
609
  function allowAspectComposition( target, elem, keys, entityName ) {
602
610
  if (!target.elements || Object.values( target.elements ).some( e => e.$duplicates ))
603
611
  return false; // no elements or with redefinitions
604
- const location = elem.target && elem.target.location || elem.location;
612
+ const location = elem.targetAspect?.location || elem.location;
605
613
  if ((elem._main._upperAspects || []).includes( target ))
606
614
  return 0; // circular containment of the same aspect
607
615
 
@@ -648,16 +656,22 @@ function generate( model ) {
648
656
  if (elem.type && !isDirectComposition( elem )) {
649
657
  // Only issue warning for direct usages, not for projections, includes, etc.
650
658
  // TODO: Make it configurable error; v5: error
651
- warning( 'def-expected-comp-aspect', [ elem.type.location, elem ],
652
- { prop: 'Composition of', otherprop: 'Association to' },
653
- 'Expected $(PROP), but found $(OTHERPROP) for composition of aspect' );
659
+ // TODO: move to resolve.js where we test the targetAspect,
660
+ warning( 'type-expecting-composition', [ elem.type.location, elem ],
661
+ { newcode: 'Composition of', code: 'Association to' },
662
+ 'Expecting $(NEWCODE), not $(CODE) for the anonymous target aspect' );
663
+ // auto-correct to avoid additional error 'type-unexpected-target-aspect' if
664
+ // cds.Association:
665
+ const { path, $inferred } = elem.type;
666
+ if (!$inferred && path?.length === 1 && path[0].id === 'cds.Association')
667
+ path[0].id = 'cds.Composition';
654
668
  }
655
669
 
656
670
  return true;
657
671
  }
658
672
 
659
673
  function createTargetEntity( target, elem, keys, entityName, base ) {
660
- const { location } = elem.targetAspect || elem.target || elem;
674
+ const location = weakRefLocation( elem.targetAspect || elem.target || elem );
661
675
  elem.on = {
662
676
  location,
663
677
  op: { val: '=', location },
@@ -673,7 +687,7 @@ function generate( model ) {
673
687
  name: {
674
688
  id: entityName,
675
689
  // for code navigation (e.g. via `extend`s): point to the element's name
676
- location: elem.name.location,
690
+ location: weakLocation( elem.name.location ),
677
691
  },
678
692
  location,
679
693
  elements: Object.create( null ),
@@ -690,18 +704,17 @@ function generate( model ) {
690
704
  }
691
705
 
692
706
  // Since there is no user-written up_ element, use a weak location to the beginning of {…}.
693
- const upLocation = weakLocation( location );
694
707
  const up = { // elements.up_ = ...
695
- name: { location: upLocation, id: 'up_' },
708
+ name: { location, id: 'up_' },
696
709
  kind: 'element',
697
- location: upLocation,
710
+ location,
698
711
  $inferred: 'aspect-composition',
699
- type: linkMainArtifact( upLocation, 'cds.Association' ),
700
- target: linkMainArtifact( upLocation, base.name.id ),
712
+ type: linkMainArtifact( location, 'cds.Association' ),
713
+ target: linkMainArtifact( location, base.name.id ),
701
714
  cardinality: {
702
- targetMin: { val: 1, literal: 'number', location: upLocation },
703
- targetMax: { val: 1, literal: 'number', location: upLocation },
704
- location: upLocation,
715
+ targetMin: { val: 1, literal: 'number', location },
716
+ targetMax: { val: 1, literal: 'number', location },
717
+ location,
705
718
  },
706
719
  };
707
720
  // By default, 'up_' is a managed primary key association.
@@ -710,18 +723,21 @@ function generate( model ) {
710
723
  if (isDeprecatedEnabled( options, '_unmanagedUpInComponent' )) {
711
724
  addProxyElements( art, keys, 'aspect-composition', target.name && location,
712
725
  'up__', '@odata.containment.ignore' );
713
- up.on = augmentEqual( upLocation, 'up_', Object.values( keys ), 'up__' );
726
+ up.on = augmentEqual( location, 'up_', Object.values( keys ), 'up__' );
714
727
  }
715
728
  else {
716
- up.key = { location: upLocation, val: true };
729
+ up.key = { location, val: true };
717
730
  // managed associations must be explicitly set to not null
718
731
  // even if target cardinality is 1..1
719
- up.notNull = { location: upLocation, val: true };
732
+ up.notNull = { location, val: true };
720
733
  }
721
734
 
722
735
  dictAdd( art.elements, 'up_', up );
723
736
  // Only for named aspects, use a new location; otherwise use the origin's one.
724
- addProxyElements( art, target.elements, 'aspect-composition', target.name && location );
737
+
738
+ // To keep the locations of non-inferred original elements, do not set $inferred:
739
+ const enforceLocation = target.name || elem.$inferred;
740
+ addProxyElements( art, target.elements, 'aspect-composition', enforceLocation && location );
725
741
 
726
742
  setLink( art, '_block', model.$internal );
727
743
  model.definitions[entityName] = art;
@@ -742,7 +758,8 @@ function generate( model ) {
742
758
  const origin = elements[name];
743
759
  const proxy = linkToOrigin( origin, pname, null, null, location, true );
744
760
  setLink( proxy, '_block', origin._block );
745
- proxy.$inferred = inferred;
761
+ if (location)
762
+ proxy.$inferred = inferred;
746
763
  if (origin.masked)
747
764
  proxy.masked = Object.assign( { $inferred: 'include' }, origin.masked );
748
765
  if (origin.key)
@@ -784,7 +801,7 @@ function generate( model ) {
784
801
  }
785
802
 
786
803
  function linkMainArtifact( location, absolute ) {
787
- const r = { location };
804
+ const r = { location, path: [ { id: absolute, location } ] };
788
805
  setArtifactLink( r, model.definitions[absolute] );
789
806
  return r;
790
807
  }
@@ -3,7 +3,12 @@
3
3
  'use strict';
4
4
 
5
5
  const { isBetaEnabled, forEachGeneric } = require('../base/model');
6
- const { setLink, annotationVal, annotationIsFalse } = require('./utils');
6
+ const {
7
+ setLink,
8
+ annotationVal,
9
+ annotationIsFalse,
10
+ isDirectComposition,
11
+ } = require('./utils');
7
12
 
8
13
  function kickStart( model ) {
9
14
  const { options } = model;
@@ -122,10 +127,7 @@ function kickStart( model ) {
122
127
  }
123
128
 
124
129
  function tagCompositionTargets( elem ) {
125
- const { type } = elem;
126
- if (elem.target && type &&
127
- (type._artifact === model.definitions['cds.Composition'] ||
128
- type.path?.[0].id === 'cds.Composition')) {
130
+ if (elem.target && isDirectComposition( elem )) {
129
131
  // A target aspect would have already moved to property `targetAspect` in
130
132
  // define.js (hm... more something for kick-start.js...)
131
133
  // TODO: for safety, just use resolveUncheckedPath()