@sap/cds-compiler 6.6.0 → 6.7.1

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 (45) hide show
  1. package/CHANGELOG.md +34 -1
  2. package/bin/cdsc.js +2 -0
  3. package/bin/cdsse.js +1 -1
  4. package/lib/base/message-registry.js +6 -7
  5. package/lib/base/model.js +0 -72
  6. package/lib/checks/elements.js +1 -1
  7. package/lib/checks/featureFlags.js +2 -2
  8. package/lib/compiler/assert-consistency.js +3 -4
  9. package/lib/compiler/base.js +8 -0
  10. package/lib/compiler/builtins.js +8 -9
  11. package/lib/compiler/checks.js +27 -6
  12. package/lib/compiler/cycle-detector.js +4 -4
  13. package/lib/compiler/define.js +65 -83
  14. package/lib/compiler/extend.js +357 -325
  15. package/lib/compiler/finalize-parse-cdl.js +3 -4
  16. package/lib/compiler/generate.js +205 -203
  17. package/lib/compiler/kick-start.js +34 -49
  18. package/lib/compiler/populate.js +95 -28
  19. package/lib/compiler/propagator.js +3 -5
  20. package/lib/compiler/resolve.js +17 -13
  21. package/lib/compiler/shared.js +47 -19
  22. package/lib/compiler/tweak-assocs.js +2 -4
  23. package/lib/compiler/utils.js +84 -31
  24. package/lib/gen/BaseParser.js +924 -1055
  25. package/lib/gen/CdlGrammar.checksum +1 -1
  26. package/lib/gen/CdlParser.js +5 -2
  27. package/lib/json/from-csn.js +25 -16
  28. package/lib/main.d.ts +13 -0
  29. package/lib/model/revealInternalProperties.js +18 -0
  30. package/lib/parsers/AstBuildingParser.js +22 -5
  31. package/lib/render/toHdbcds.js +2 -2
  32. package/lib/render/utils/sql.js +2 -2
  33. package/lib/render/utils/standardDatabaseFunctions.js +2 -2
  34. package/lib/transform/db/constraints.js +3 -4
  35. package/lib/transform/db/killAnnotations.js +1 -1
  36. package/lib/transform/db/processSqlServices.js +10 -11
  37. package/lib/transform/effective/associations.js +1 -1
  38. package/lib/transform/forOdata.js +7 -124
  39. package/lib/transform/odata/fioriTreeViews.js +173 -0
  40. package/lib/transform/odata/flattening.js +2 -2
  41. package/lib/transform/translateAssocsToJoins.js +7 -4
  42. package/package.json +1 -1
  43. package/share/messages/message-explanations.json +0 -2
  44. package/share/messages/type-unexpected-foreign-keys.md +0 -52
  45. package/share/messages/type-unexpected-on-condition.md +0 -52
@@ -2,10 +2,9 @@
2
2
 
3
3
  'use strict';
4
4
 
5
- const {
6
- isDeprecatedEnabled,
7
- forEachGeneric, forEachDefinition,
8
- } = require('../base/model');
5
+ /* eslint-disable no-nested-ternary */
6
+
7
+ const { isDeprecatedEnabled } = require('../base/model');
9
8
  const { dictAdd } = require('../base/dictionaries');
10
9
  const {
11
10
  setLink,
@@ -17,6 +16,7 @@ const {
17
16
  augmentPath,
18
17
  isDirectComposition,
19
18
  copyExpr,
19
+ forEachGeneric,
20
20
  } = require('./utils');
21
21
  const { weakLocation, weakRefLocation, weakEndLocation } = require('../base/location');
22
22
 
@@ -28,6 +28,7 @@ function generate( model ) {
28
28
  const {
29
29
  error, warning, info,
30
30
  } = model.$messageFunctions;
31
+ const Functions = model.$functions;
31
32
  const {
32
33
  resolvePath,
33
34
  resolveUncheckedPath,
@@ -35,14 +36,17 @@ function generate( model ) {
35
36
  extendArtifactBefore,
36
37
  applyIncludes,
37
38
  } = model.$functions;
38
- model.$functions.hasTruthyProp = hasTruthyProp;
39
-
40
- const addTextsLanguageAssoc = checkTextsLanguageAssocOption( model, options );
41
- const useTextsAspect = checkTextsAspect();
42
-
43
- Object.keys( model.definitions ).forEach( processArtifact );
44
-
45
- compositionChildPersistence();
39
+ Object.assign( model.$functions, {
40
+ hasTruthyProp,
41
+ generateForEntity,
42
+ populateGeneratedEntity,
43
+ checkGenerateConditions,
44
+ } );
45
+
46
+ const commonLanguages = model.definitions['sap.common.Languages'];
47
+ const textsAspect = model.definitions['sap.common.TextsAspect'];
48
+ let enableLanguageAssoc = null; // → addLanguageAssoc()
49
+ let enableTextsAspect = null; // → useTextsAspect()
46
50
  return;
47
51
 
48
52
  /**
@@ -50,33 +54,45 @@ function generate( model ) {
50
54
  *
51
55
  * @param {string} name
52
56
  */
53
- function processArtifact( name ) {
54
- const art = model.definitions[name];
55
- if (!(art.$duplicates)) {
57
+ function generateForEntity( art ) {
58
+ // TODO: write dependency on base entity for final generateForEntity()
59
+ if (!art.$duplicates && art.elements) {
56
60
  processAspectComposition( art );
57
- if (art.kind === 'entity' && !art.query && art.elements)
58
- // check potential entity parse error
59
- processLocalizedData( art );
61
+ processLocalizedData( art );
60
62
  }
61
63
  }
62
64
 
63
- /**
64
- * Copy `@cds.persistence.skip` and `@cds.persistence.skip` from parent to child
65
- * for managed compositions. This needs to be done after extensions, i.e. annotations,
66
- * have been applied or `annotate E.comp` would not have an effect on `E.comp.subComp`.
67
- */
68
- function compositionChildPersistence() {
69
- const processed = new WeakSet();
70
- forEachDefinition( model, processCompositionPersistence );
71
-
72
- function processCompositionPersistence( def ) {
73
- if (def.$inferred === 'composition-entity' && !processed.has( def )) {
74
- if (def._parent)
75
- processCompositionPersistence( def._parent );
76
- copyPersistenceAnnotations( def, def._parent );
77
- processed.add( def );
78
- }
65
+ function addLanguageAssoc( report = false ) {
66
+ if (!options.addTextsLanguageAssoc)
67
+ return false;
68
+ if (!report && enableLanguageAssoc != null)
69
+ return enableLanguageAssoc;
70
+
71
+ const codeElement = Functions.effectiveType( commonLanguages )?.elements?.code;
72
+ // eslint-disable no-nested-ternary
73
+ const err = (!commonLanguages || commonLanguages.$inferred )
74
+ ? 'missing'
75
+ : (commonLanguages.kind !== 'entity' || commonLanguages.query)
76
+ ? 'kind'
77
+ : (!codeElement || codeElement.$inferred)
78
+ ? 'code'
79
+ : null; // TODO: better config of eslint @stylistic/indent
80
+ if (report && err) {
81
+ const loc = commonLanguages?.name?.location || null;
82
+ warning( 'api-ignoring-language-assoc', [ loc, commonLanguages ], {
83
+ '#': err,
84
+ option: 'addTextsLanguageAssoc',
85
+ art: 'sap.common.Languages',
86
+ name: 'code',
87
+ }, {
88
+ std: 'Ignoring option $(OPTION)',
89
+ missing: 'Ignoring option $(OPTION) because entity $(ART) is missing',
90
+ kind: 'Ignoring option $(OPTION) because entity $(ART) is no entity',
91
+ code: 'Ignoring option $(OPTION) because entity $(ART) is missing direct element $(NAME)',
92
+ } );
79
93
  }
94
+ enableLanguageAssoc = (err == null);
95
+ return enableLanguageAssoc;
80
96
  }
81
97
 
82
98
  /**
@@ -86,56 +102,46 @@ function generate( model ) {
86
102
  *
87
103
  * @return {boolean}
88
104
  */
89
- function checkTextsAspect() {
90
- const textsAspect = model.definitions['sap.common.TextsAspect'];
91
- if (!textsAspect)
92
- return false;
93
-
94
- const specialElements = { locale: { key: true } };
105
+ function useTextsAspect( report = false ) {
106
+ if (!report && enableTextsAspect != null)
107
+ return enableTextsAspect;
108
+
109
+ const locale = Functions.effectiveType( textsAspect )?.elements?.locale;
110
+ const err = (!textsAspect || textsAspect.$inferred )
111
+ ? false
112
+ : (textsAspect.kind !== 'aspect' || !textsAspect.elements)
113
+ ? 'no-aspect'
114
+ : (!locale || locale.$inferred)
115
+ ? 'missing'
116
+ : (locale.key?.val ? null : 'key'); // TODO: better eslint @stylistic/indent
117
+ if (report && err) {
118
+ const loc = (err === 'key' ? locale : textsAspect)?.name?.location || null;
119
+ error( 'def-invalid-texts-aspect', [ loc, (err === 'key' ? locale : textsAspect) ],
120
+ { '#': err, art: textsAspect, name: 'locale' } );
121
+ }
122
+ enableTextsAspect = (err == null);
123
+ return enableTextsAspect;
124
+ }
95
125
 
96
- if (textsAspect.kind !== 'aspect' || !textsAspect.elements) {
97
- error( 'def-invalid-texts-aspect', [ textsAspect.name.location, textsAspect ],
98
- { '#': 'no-aspect', art: textsAspect } );
99
- return false;
100
- }
126
+ function checkGenerateConditions() {
127
+ addLanguageAssoc( true );
128
+ if (!useTextsAspect( true ))
129
+ return;
101
130
 
102
- let hasError = false;
103
- if (addTextsLanguageAssoc && textsAspect.elements.language) {
131
+ if (enableLanguageAssoc && textsAspect.elements.language) {
104
132
  const lang = textsAspect.elements.language;
105
133
  error( 'def-unexpected-element', [ lang.name.location, lang ],
106
134
  { option: 'addTextsLanguageAssoc', art: textsAspect, name: 'language' },
107
- // eslint-disable-next-line @stylistic/max-len
108
- '$(ART) is not used because option $(OPTION) conflicts with existing element $(NAME); remove either option or element' );
109
- hasError = true;
135
+ 'Element $(NAME) of $(ART) is not used because option $(OPTION) is set' );
110
136
  }
111
137
 
112
- for (const name in specialElements) {
113
- const expected = specialElements[name];
114
- const elem = textsAspect.elements[name];
115
- if (!elem) {
116
- error( 'def-invalid-texts-aspect', [ textsAspect.name.location, textsAspect ],
117
- { '#': 'missing', art: textsAspect, name } );
118
- hasError = true;
119
- }
120
- else if (expected.key !== undefined && !!elem.key?.val !== expected.key) {
121
- const loc = elem.key?.location || elem.name?.location || textsAspect.name.location;
122
- error( 'def-invalid-texts-aspect', [ loc, elem ],
123
- { '#': expected.key ? 'key' : 'no-key', art: elem } );
124
- hasError = true;
125
- }
126
- }
127
-
128
- if (hasError) // avoid subsequent errors, if the special elements are already wrong
129
- return false;
130
-
131
138
  for (const name in textsAspect.elements) {
132
139
  const elem = textsAspect.elements[name];
133
140
  const include = elem.$inferred === 'include';
134
- if (!specialElements[name] && elem.key) {
141
+ if (elem.key && name !== 'locale') {
135
142
  const loc = include ? elem.location : elem.key.location;
136
143
  error( 'def-unexpected-key', [ loc, elem ],
137
144
  { '#': !include ? 'std' : 'include', art: textsAspect } );
138
- hasError = true;
139
145
  }
140
146
  else if (hasTruthyProp( elem, 'localized' )) {
141
147
  // TODO: T:loc, i.e. "localized" from other type (needs resolver?)
@@ -143,17 +149,13 @@ function generate( model ) {
143
149
  const loc = elem.localized?.location || elem.location;
144
150
  error( 'def-unexpected-localized', [ loc, elem ],
145
151
  { '#': !include ? 'elements' : 'include', art: textsAspect } );
146
- hasError = true;
147
152
  }
148
153
  else if (elem.targetAspect) {
149
154
  error( 'def-unexpected-composition', [ elem.targetAspect.location, elem ],
150
155
  { art: textsAspect },
151
156
  '$(ART) can\'t have composition of aspects' );
152
- hasError = true;
153
157
  }
154
158
  }
155
-
156
- return !hasError;
157
159
  }
158
160
 
159
161
  // localized texts entities ---------------------------------------------------
@@ -165,8 +167,13 @@ function generate( model ) {
165
167
  * @param {XSN.Artifact} art
166
168
  */
167
169
  function processLocalizedData( art ) {
170
+ // do not create texts entity for a texts entity (might be induced by erroneous
171
+ // sap.common.TextsAspect):
172
+ if (art.$inferred === 'localized-entity')
173
+ return;
174
+ // TODO: test with `annotate … with @fiori.draft.enabled`
168
175
  const fioriAnno = art['@fiori.draft.enabled'];
169
- const fioriEnabled = fioriAnno && (fioriAnno.val === undefined || fioriAnno.val);
176
+ const fioriEnabled = fioriAnno && (fioriAnno.val === undefined || !!fioriAnno.val);
170
177
 
171
178
  const textsName = `${ art.name.id }.texts`;
172
179
  const textsEntity = model.definitions[textsName];
@@ -198,7 +205,7 @@ function generate( model ) {
198
205
  const protectedElements = [ 'locale', 'texts', 'localized' ];
199
206
  if (fioriEnabled)
200
207
  protectedElements.push( 'ID_texts' );
201
- if (addTextsLanguageAssoc)
208
+ if (addLanguageAssoc())
202
209
  protectedElements.push( 'language' );
203
210
 
204
211
  for (const name in art.elements) {
@@ -286,19 +293,16 @@ function generate( model ) {
286
293
  */
287
294
  function createTextsEntity( base, absolute, textElems, fioriEnabled ) {
288
295
  const location = weakLocation( base.elements[$location] || base.location );
289
- let art = {
296
+ const art = Functions.registerGeneratedEntity( {
290
297
  kind: 'entity',
291
298
  name: { id: absolute, location },
292
299
  location,
293
300
  elements: Object.create( null ),
294
301
  $inferred: 'localized-entity',
295
- };
296
- const gap = model.definitions[absolute];
297
- if (gap)
298
- art = Object.assign( gap, art );
299
- else
300
- model.definitions[absolute] = art;
302
+ } );
301
303
  setLink( art, '_block', model.$internal );
304
+
305
+ // console.log('Texts:',require('../model/revealInternalProperties').ref(art))
302
306
  extendArtifactBefore( art ); // having extensions here would be wrong
303
307
 
304
308
  if (!fioriEnabled) {
@@ -313,15 +317,22 @@ function generate( model ) {
313
317
  type: linkMainArtifact( location, 'cds.UUID' ),
314
318
  location,
315
319
  };
316
- dictAdd( art.elements, 'ID_texts', textId );
320
+ art.elements.ID_texts = textId;
317
321
  }
322
+ // _service links and "consider as composition target" must be set already here
323
+ setLink( art, '_service', base._service );
324
+ model.$compositionTargets[absolute] = true;
325
+ setGenExtensions( art, { _base: base, _textElems: textElems, fioriEnabled } );
326
+ }
318
327
 
319
- const enrich = useTextsAspect
328
+ function populateTextsEntity( art, base, textElems, fioriEnabled ) {
329
+ const { location } = art;
330
+ const enrich = useTextsAspect()
320
331
  ? enrichTextsEntityWithInclude
321
332
  : enrichTextsEntityWithDefaultElements;
322
- enrich( art, base, absolute, fioriEnabled );
333
+ enrich( art, fioriEnabled );
323
334
 
324
- if (addTextsLanguageAssoc) {
335
+ if (addLanguageAssoc()) {
325
336
  const language = {
326
337
  name: { location, id: 'language' },
327
338
  kind: 'element',
@@ -350,12 +361,12 @@ function generate( model ) {
350
361
  initMainArtifact( art );
351
362
  // do the kick-start relevant stuff: _service, there are no _ancestors,
352
363
  // _descendants would have been set already for a gap artifact
353
- setLink( art, '_service', art._parent._service );
354
- model.$compositionTargets[absolute] = true;
355
364
 
356
- if (art.includes) {
365
+ if (art.includes) { // i.e. sap.common.TextsAspect
366
+ // do not insert sap.common.TextsAspect elements before `locale`:
357
367
  // add elements `locale`, etc. which are required below.
358
- applyIncludes( art, art ); // TODO: rethink - can we avoid this if only new extend?
368
+ art.includes.$original = []; // do not apply via extendArtifactAdd()
369
+ applyIncludes( art, art ); // apply now
359
370
  }
360
371
 
361
372
  if (fioriEnabled) {
@@ -376,6 +387,7 @@ function generate( model ) {
376
387
  setAnnotation( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' );
377
388
  }
378
389
 
390
+ // TODO: first copy persistence annos from cds.TextsAspect ?
379
391
  copyPersistenceAnnotations( art, base );
380
392
  return art;
381
393
  }
@@ -413,13 +425,10 @@ function generate( model ) {
413
425
  * Does NOT apply the include!
414
426
  *
415
427
  * @param {XSN.Artifact} art
416
- * @param {XSN.Artifact} base
417
- * @param {string} absolute
418
428
  * @param {boolean} fioriEnabled
419
429
  */
420
- function enrichTextsEntityWithInclude( art, base, absolute, fioriEnabled ) {
430
+ function enrichTextsEntityWithInclude( art, fioriEnabled ) {
421
431
  const textsAspectName = 'sap.common.TextsAspect';
422
- const textsAspect = model.definitions['sap.common.TextsAspect'];
423
432
  const { location } = art;
424
433
 
425
434
  art.includes = [ createInclude( textsAspectName, location ) ];
@@ -433,18 +442,16 @@ function generate( model ) {
433
442
  art.elements.locale.$inferred = 'localized';
434
443
  }
435
444
 
436
- if (addTextsLanguageAssoc && art.elements.language)
445
+ if (addLanguageAssoc() && art.elements.language)
437
446
  art.elements.language = undefined; // TODO: Message? Ignore?
438
447
  // TODO: what is this necessary? We do not create a text entity in this case
439
448
  }
440
449
 
441
450
  /**
442
451
  * @param {XSN.Artifact} art
443
- * @param {XSN.Artifact} base
444
- * @param {string} absolute
445
452
  * @param {boolean} fioriEnabled
446
453
  */
447
- function enrichTextsEntityWithDefaultElements( art, base, absolute, fioriEnabled ) {
454
+ function enrichTextsEntityWithDefaultElements( art, fioriEnabled ) {
448
455
  // If there is a type `sap.common.Locale`, then use it as the type for the element `locale`.
449
456
  // If not, use the default `cds.String` with a length of 14.
450
457
  const hasLocaleType = model.definitions['sap.common.Locale']?.kind === 'type';
@@ -590,11 +597,15 @@ function generate( model ) {
590
597
  return;
591
598
  let origin = elem;
592
599
  // included element do not have target aspect directly
600
+ // (remark: this will get far more complex with compositions of aspects
601
+ // in sub elements)
593
602
  while (origin && !origin.targetAspect && origin._origin)
594
603
  origin = origin._origin;
595
604
  let target = origin.targetAspect;
596
- if (target?.path)
605
+ if (target?.path) {
597
606
  target = resolvePath( origin.targetAspect, 'targetAspect', origin );
607
+ Functions.effectiveType( target ); // should have been good!
608
+ }
598
609
  if (!target || !target.elements)
599
610
  return;
600
611
  const entityName = `${ base.name.id }.${ elem.name.id }`;
@@ -614,8 +625,6 @@ function generate( model ) {
614
625
  if (up_)
615
626
  setLink( up_, '_origin', entity.elements.up_ );
616
627
  model.$compositionTargets[entity.name.id] = true;
617
- processAspectComposition( entity );
618
- processLocalizedData( entity );
619
628
  }
620
629
  }
621
630
  }
@@ -637,22 +646,6 @@ function generate( model ) {
637
646
  'An aspect $(TARGET) can\'t be used as target in an entity without keys' );
638
647
  return false;
639
648
  }
640
- // if (keys.up_) { // only to be tested if we allow to provide a prefix, which could be ''
641
- // // Cannot be in an "inner aspect-compositions" as it would already be wrong before
642
- // // TODO: if anonymous type, use location of "up_" element
643
- // // FUTURE: add sub info with location of "up_" element
644
- // message( 'id', [location, elem], { target, name: 'up_' }, 'Error',
645
- // 'An aspect $(TARGET) can't be used as target in an entity with a key named $(NAME)' );
646
- // return false;
647
- // }
648
- if (target.elements.up_) {
649
- // TODO: for "inner aspect-compositions", signal already in type
650
- // TODO: if anonymous type, use location of "up_" element
651
- // FUTURE: if named type, add sub info with location of "up_" element
652
- error( null, [ location, elem ], { target, name: 'up_' },
653
- 'An aspect $(TARGET) with an element named $(NAME) can\'t be used as target' );
654
- return false;
655
- }
656
649
  const place = model.definitions[entityName];
657
650
  if (place && place.kind !== 'namespace') {
658
651
  error( null, [ location, elem ], { art: entityName },
@@ -660,16 +653,6 @@ function generate( model ) {
660
653
  'Target entity $(ART) can\'t be created as there is another definition with this name' );
661
654
  return false;
662
655
  }
663
- const names = Object.keys( target.elements )
664
- .filter( n => n.startsWith( 'up__' ) && keyNames.includes( n.substring(4) ) );
665
- if (names.length) {
666
- // FUTURE: if named type, add sub info with location of "up_" element
667
- error( null, [ location, elem ], { target: entityName, names }, {
668
- std: 'Key elements $(NAMES) can\'t be added to $(TARGET) as these already exist',
669
- one: 'Key element $(NAMES) can\'t be added to $(TARGET) as it already exist',
670
- } );
671
- return false;
672
- }
673
656
 
674
657
  if (elem.type && !isDirectComposition( elem )) {
675
658
  // Only issue warning for direct usages, not for projections, includes, etc.
@@ -689,8 +672,10 @@ function generate( model ) {
689
672
  }
690
673
 
691
674
  function createTargetEntity( target, elem, keys, entityName, base ) {
675
+ // Remark: keys for v2 option deprecated.unmanagedUpInComponent
692
676
  const location = weakRefLocation( elem.targetAspect || elem.target || elem );
693
- elem.on = {
677
+ // Since there is no user-written up_ element, use a weak location to the beginning of {…}.
678
+ elem.on = { // element on base entity
694
679
  location,
695
680
  op: { val: '=', location },
696
681
  args: [
@@ -700,43 +685,14 @@ function generate( model ) {
700
685
  $inferred: 'aspect-composition',
701
686
  };
702
687
 
703
- let art = {
704
- kind: 'entity',
705
- name: {
706
- id: entityName,
707
- // for code navigation (e.g. via `extend`s): point to the element's name
708
- location: weakLocation( elem.name.location ),
709
- },
710
- location,
711
- elements: Object.create( null ),
712
- $inferred: 'composition-entity',
713
- };
714
- const gap = model.definitions[entityName];
715
- if (gap)
716
- art = Object.assign( gap, art );
717
- else
718
- model.definitions[entityName] = art;
719
-
720
- if (target.name) { // named target aspect
721
- if (!isDeprecatedEnabled( options, 'noCompositionIncludes' )) {
722
- art.includes = [ createInclude( target.name.id, location ) ];
723
- propagateEarly( art, '@cds.autoexpose' );
724
- propagateEarly( art, '@fiori.draft.enabled' );
725
- }
726
- setLink( art, '_origin', target );
727
- setLink( art, '_upperAspects', [ target, ...(elem._main._upperAspects || []) ] );
728
- }
729
- else {
730
- setLink( art, '_origin', target );
731
- // TODO: do we need to give the anonymous target aspect a kind and name?
732
- setLink( art, '_upperAspects', elem._main._upperAspects || [] );
733
- }
734
-
735
- // Since there is no user-written up_ element, use a weak location to the beginning of {…}.
736
- const up = { // elements.up_ = ...
688
+ const up_ = { // elements.up_ = ...
737
689
  name: { location, id: 'up_' },
738
690
  kind: 'element',
739
691
  location,
692
+ key: { location, val: true },
693
+ notNull: { location, val: true },
694
+ // managed associations must be explicitly set to not null
695
+ // even if target cardinality is 1..1
740
696
  $inferred: 'aspect-composition',
741
697
  type: linkMainArtifact( location, 'cds.Association' ),
742
698
  target: linkMainArtifact( location, base.name.id ),
@@ -746,40 +702,87 @@ function generate( model ) {
746
702
  location,
747
703
  },
748
704
  };
749
-
750
- up.key = { location, val: true };
751
- // managed associations must be explicitly set to not null
752
- // even if target cardinality is 1..1
753
- up.notNull = { location, val: true };
754
-
755
- dictAdd( art.elements, 'up_', up );
756
- // Only for named aspects, use a new location; otherwise use the origin's one.
757
-
758
- // To keep the locations of non-inferred original elements, do not set $inferred:
759
- const enforceLocation = target.name || elem.$inferred;
760
- addProxyElements( art, target.elements, 'aspect-composition', enforceLocation && location );
761
-
705
+ const art = Functions.registerGeneratedEntity( {
706
+ kind: 'entity',
707
+ name: {
708
+ id: entityName,
709
+ // for code navigation (e.g. via `extend`s): point to the element's name
710
+ location: weakLocation( elem.name.location ),
711
+ },
712
+ location,
713
+ elements: { __proto__: null, up_ },
714
+ $inferred: 'composition-entity',
715
+ } );
716
+ // console.log('Composition:',require('../model/revealInternalProperties').ref(art))
762
717
  setLink( art, '_block', model.$internal );
763
- initMainArtifact( art );
764
718
 
765
719
  // do the kick-start relevant stuff: _service, there are no _ancestors,
766
720
  // _descendants would have been set already for a gap artifact
767
- setLink( art, '_service', art._parent._service );
721
+ setLink( art, '_service', base._service );
768
722
  model.$compositionTargets[entityName] = true;
769
723
 
770
724
  // Apply annotations to generated artifact, prepare (not apply!) element
771
725
  // annotations (remark: adding elements is not allowed for generated artifacts):
772
726
  extendArtifactBefore( art );
773
- // Copy persistence annotations from aspect.
774
- copyPersistenceAnnotations( art, target ); // after extendArtifactBefore()
727
+ setGenExtensions( art, { _composition: elem } );
728
+ setLink( art, '_origin', target ); // TODO: does this hurt?
729
+ return art;
730
+ }
731
+
732
+ function populateTargetEntity( art, elem ) {
733
+ const location = weakRefLocation( elem.targetAspect || elem.target || elem );
734
+ const targetAspect = art._origin;
735
+
736
+ Functions.effectiveType( targetAspect );
737
+ const { elements } = targetAspect;
775
738
 
776
- if (!isDeprecatedEnabled( options, 'noCompositionIncludes' ) && art.includes)
777
- applyIncludes( art, art ); // for actions
739
+ if (elements.up_) {
740
+ // TODO: for "inner aspect-compositions", signal already in type
741
+ // TODO: if anonymous type, use location of "up_" element
742
+ // FUTURE: if named type, add sub info with location of "up_" element
743
+ error( null, [ location, elem ], { target: targetAspect, name: 'up_' },
744
+ 'An aspect $(TARGET) with an element named $(NAME) can\'t be used as target' );
745
+ delete elements.up_; // continuation semantics: don't use up_ from aspect
746
+ }
747
+
748
+ if (targetAspect.name) { // named target aspect
749
+ if (!isDeprecatedEnabled( options, 'noCompositionIncludes' )) {
750
+ art.includes = [ createInclude( targetAspect.name.id, location ) ];
751
+ art.includes.$origin = []; // included elements after up_
752
+ // TODO: propagate in effectiveType()
753
+ propagateEarly( art, '@cds.autoexpose' );
754
+ propagateEarly( art, '@fiori.draft.enabled' );
755
+ }
756
+ setLink( art, '_upperAspects', [ targetAspect, ...(elem._main._upperAspects || []) ] );
757
+ }
758
+ else {
759
+ // TODO: do we need to give the anonymous target aspect a kind and name?
760
+ setLink( art, '_upperAspects', elem._main._upperAspects || [] );
761
+ // TODO: _upperAspects can probably now be removed
762
+ }
763
+ Functions.effectiveType( elem );
764
+ // To keep the locations of non-inferred original elements, do not set $inferred:
765
+ const enforceLocation = targetAspect.name || elem.$inferred;
766
+ addProxyElements( art, elements, 'aspect-composition', enforceLocation && location );
767
+ initMainArtifact( art );
768
+
769
+ // Copy persistence annotations from aspect.
770
+ if (targetAspect.kind === 'aspect') // proper aspect
771
+ copyPersistenceAnnotations( art, targetAspect ); // after extendArtifactBefore()
772
+ copyPersistenceAnnotations( art, elem._parent );
778
773
  return art;
779
774
  }
780
775
 
776
+ function populateGeneratedEntity( art ) {
777
+ const gen = art._extensions?.$gen;
778
+ if (gen?._composition)
779
+ populateTargetEntity( art, gen._composition );
780
+ else if (gen?._textElems)
781
+ populateTextsEntity( art, gen._base, gen._textElems, gen.fioriEnabled );
782
+ }
783
+
781
784
  function addProxyElements( proxyDict, elements, inferred, location, prefix = '', anno = '' ) {
782
- // TODO: also use for includeMembers()? Both are similar. Combine?
785
+ // TODO: also use for includeMembers()? Both are similar. Combine!
783
786
  for (const name in elements) {
784
787
  const pname = `${ prefix }${ name }`;
785
788
  const origin = elements[name];
@@ -811,6 +814,8 @@ function generate( model ) {
811
814
  * Copy relevant annotations from
812
815
  * source to target if present on source but not target.
813
816
  *
817
+ * Persistence annos from target/text aspect have precedence.
818
+ *
814
819
  * @param {object} target
815
820
  * @param {object} source
816
821
  */
@@ -837,7 +842,7 @@ function generate( model ) {
837
842
  }
838
843
 
839
844
  function linkMainArtifact( location, absolute ) {
840
- const r = { location, path: [ { id: absolute, location } ] };
845
+ const r = { location, path: [ { id: absolute, location } ], scope: 'global' };
841
846
  setArtifactLink( r, model.definitions[absolute] );
842
847
  return r;
843
848
  }
@@ -868,24 +873,6 @@ function augmentEqual( location, assocname, relations, prefix = '' ) {
868
873
  }
869
874
  }
870
875
 
871
- function checkTextsLanguageAssocOption( model, options ) {
872
- const languages = model.definitions['sap.common.Languages'];
873
- const commonLanguagesEntity = options.addTextsLanguageAssoc && languages?.elements?.code;
874
-
875
- if (options.addTextsLanguageAssoc && !commonLanguagesEntity) {
876
- const variant = !languages ? 'std' : 'code';
877
- const loc = model.definitions['sap.common.Languages']?.name?.location || null;
878
- model.$messageFunctions.info( 'api-ignoring-language-assoc', loc, {
879
- '#': variant, option: 'addTextsLanguageAssoc', art: 'sap.common.Languages', name: 'code',
880
- }, {
881
- std: 'Ignoring option $(OPTION) because entity $(ART) is missing',
882
- code: 'Ignoring option $(OPTION) because entity $(ART) is missing element $(NAME)',
883
- } );
884
- }
885
-
886
- return !!commonLanguagesEntity;
887
- }
888
-
889
876
 
890
877
  /**
891
878
  * Propagate the given `prop` (e.g. annotation) early, i.e. copy it from all `.includes`
@@ -908,5 +895,20 @@ function propagateEarly( art, prop ) {
908
895
  }
909
896
  }
910
897
 
898
+ function setGenExtensions( art, gen ) {
899
+ const $gen = {};
900
+ for (const prop of Object.keys( gen )) {
901
+ Object.defineProperty( $gen, prop, {
902
+ configurable: true,
903
+ enumerable: prop.charAt( 0 ) !== '_',
904
+ value: gen[prop],
905
+ writable: true,
906
+ } );
907
+ }
908
+ if (art._extensions)
909
+ art._extensions.$gen = $gen;
910
+ else
911
+ art._extensions = { $gen };
912
+ }
911
913
 
912
914
  module.exports = generate;