@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
@@ -4,14 +4,8 @@
4
4
 
5
5
  const { weakRefLocation } = require('../base/location');
6
6
  const { searchName } = require('../base/messages');
7
- const {
8
- forEachInOrder,
9
- forEachDefinition,
10
- forEachMember,
11
- forEachGeneric,
12
- isDeprecatedEnabled,
13
- } = require('../base/model');
14
- const { dictAdd, pushToDict } = require('../base/dictionaries');
7
+ const { isDeprecatedEnabled } = require('../base/model');
8
+ const { dictAdd, pushToDict, dictForEach } = require('../base/dictionaries');
15
9
  const { kindProperties, dictKinds } = require('./base');
16
10
  const {
17
11
  setLink,
@@ -26,9 +20,14 @@ const {
26
20
  initDollarSelf,
27
21
  initBoundSelfParam,
28
22
  dependsOnSilent,
29
- storeExtension,
30
23
  pathName,
31
24
  annotationHasEllipsis,
25
+ forEachInOrder,
26
+ forEachDefinition,
27
+ forEachMember,
28
+ forEachGeneric,
29
+ isDirectComposition,
30
+ targetCantBeAspect,
32
31
  } = require('./utils');
33
32
  const layers = require('./moduleLayers');
34
33
  const { CompilerAssertion } = require('../base/error');
@@ -36,8 +35,9 @@ const { Location } = require('../base/location');
36
35
  const { typeParameters } = require('./builtins');
37
36
 
38
37
  const $location = Symbol.for( 'cds.$location' );
38
+ const $inferred = Symbol.for( 'cds.$inferred' ); // TODO: no $inferred yet?
39
39
 
40
- // attach stupid location - TODO: remove in v6
40
+ // attach stupid location - TODO: remove in v7
41
41
  const genLocation = new Location( '' );
42
42
 
43
43
  const draftElements = [
@@ -68,15 +68,14 @@ function extend( model ) {
68
68
  resolveTypeArgumentsUnchecked,
69
69
  resolveDefinitionName,
70
70
  attachAndEmitValidNames,
71
- targetIsTargetAspect,
72
71
  checkRedefinition,
73
- initSelectItems,
74
72
  } = model.$functions;
75
73
 
76
74
  Object.assign( model.$functions, {
77
75
  createRemainingAnnotateStatements,
78
76
  extendArtifactBefore,
79
77
  extendArtifactAfter,
78
+ extendArtifactAdd,
80
79
  extendForeignKeys,
81
80
  withLocalizedData,
82
81
  applyIncludes, // TODO: re-check
@@ -85,20 +84,56 @@ function extend( model ) {
85
84
  const includesNonShadowedFirst
86
85
  = isDeprecatedEnabled( model.options, '_includesNonShadowedFirst' );
87
86
 
87
+ forEachGeneric( model, 'definitions', tagCompositionTargets );
88
+ dictForEach( model.$collectedExtensions, e => e._extensions.forEach( tagCompositionTargets ) );
89
+ // remark: tagging on extensions works _before_ running extendArtifactBefore() on each artifact
88
90
  sortModelSources();
89
- const extensionsDict = Object.create( null ); // TODO TMP
90
- forEachDefinition( model, tagIncludes ); // TODO TMP
91
91
 
92
+ // Set annotations on user-provided artifacts, but they are not propagated yet!
93
+ // Use them in the main compiler phase (and before) with extra care:
92
94
  forEachDefinition( model, extendArtifactBefore );
93
- applyExtensions(); // old-style
94
95
  return;
95
96
 
96
- // TMP:
97
- function tagIncludes( art ) {
98
- if (art.includes)
99
- extensionsDict[art.name.id] = [];
97
+ // Tag composition targets: ---------------------------------------------------
98
+
99
+ function tagCompositionTargets( elem ) {
100
+ if (elem.$inferred) // TODO: probably no $inferred yet
101
+ return;
102
+ if (elem.targetAspect?.elements)
103
+ elem = elem.targetAspect;
104
+ if (elem.elements) {
105
+ forEachGeneric( elem, 'elements', tagCompositionTargets );
106
+ }
107
+ else if (elem.columns) { // `elem` is query or extension
108
+ elem.columns.forEach( tagCompositionTargets );
109
+ }
110
+ else if (elem.$queries) {
111
+ for (const query of elem.$queries) {
112
+ if (query.mixin)
113
+ forEachGeneric( query, 'mixin', tagCompositionTargets );
114
+ if (query.columns)
115
+ query.columns.forEach( tagCompositionTargets );
116
+ // Remark: no directly published expand/inline column yet
117
+ }
118
+ }
119
+ else if (elem.target && isDirectComposition( elem )) {
120
+ const name = resolveUncheckedPath( elem.target, 'target', elem );
121
+ if (!name)
122
+ return;
123
+ const target = model.definitions[name];
124
+ // move target aspect in `target` to `targetAspect` (in define.js, we only
125
+ // do it with anonymous aspect)
126
+ if (target?.kind in { aspect: 1, type: 1 } && // type is sloppy
127
+ target.elements && !target.elements[$inferred] &&
128
+ !targetCantBeAspect( elem, false, model.definitions )) { // tests `!elem.targetAspect`
129
+ elem.targetAspect = elem.target;
130
+ delete elem.target;
131
+ }
132
+ model.$compositionTargets[name] = true; // does not hurt if set on aspect
133
+ }
100
134
  }
101
135
 
136
+
102
137
  //-----------------------------------------------------------------------------
103
138
  // Extensions: general algorithm
104
139
  //-----------------------------------------------------------------------------
@@ -110,6 +145,9 @@ function extend( model ) {
110
145
  * if multiple exist according to the module layer.
111
146
  * TODO: update comment if extension algorithm is finished
112
147
  *
148
+ * Called at the beginning for each main artifact, and must not call
149
+ * resolvePath() then. It is later called via effectiveType().
150
+ *
113
151
  * @param {XSN.Artifact} art
114
152
  */
115
153
  function extendArtifactBefore( art ) {
@@ -126,6 +164,9 @@ function extend( model ) {
126
164
  // for now, we do that at the end of createRemainingAnnotateStatements()
127
165
  }
128
166
  }
167
+ if (art.kind === 'entity' && art.includes && !art._entityIncludes)
168
+ setEntityIncludes( art, art );
169
+
129
170
  if (art._extensions) {
130
171
  // TODO: the following function can now be simplified
131
172
  // if (art.$inferred) console.log('CAI:', art.name, art.$inferred,art._extensions)
@@ -135,19 +176,39 @@ function extend( model ) {
135
176
  if (art.$inferred)
136
177
  setExpandStatusAnnotate( art, 'annotate' );
137
178
  if (Array.isArray( art._extensions )) {
138
- checkExtensionsKind( art._extensions, art ); // TODO: check with builtins
179
+ checkExtensionsKind( art._extensions, art );
139
180
  transformArtifactExtensions( art );
140
181
  }
141
- applyAllExtensions( art );
182
+ // console.log('EA:',require('../model/revealInternalProperties')
183
+ // .ref(art),Object.keys(art._extensions))
184
+ for (const prop in art._extensions) {
185
+ if (Object.hasOwn( art._extensions, prop ) &&
186
+ // remark: if we change the array, consider whether to delete the artifact
187
+ ![ 'elements', 'actions', 'params', '$gen' ].includes( prop ))
188
+ applyPropertyExtensions( art, prop );
189
+ }
142
190
  }
143
191
  }
144
192
 
145
- // TODO: assert that we have not yet transformed/used _extensions on sub elements
146
- // TODO necessary(?): transformArtifactExtensions must ensure that each annotate
147
- // is in either returns,items,elements,enum
193
+ /**
194
+ * Push down extensions on member properties like `elements` to the individual
195
+ * members (setting their `_extensions` property).
196
+ * Currently, definitions in `extend` members are ignored,
197
+ * because they are handled a-priori by the old-style extension mechanism.
198
+ *
199
+ * Outside this file: only called in effectiveType() and indirectly in
200
+ * tweak-assocs.js (for foreign keys and super-annotates).
201
+ */
148
202
  function extendArtifactAfter( art ) {
203
+ // TODO: assert that we have not yet transformed/used _extensions on sub elements
204
+ // TODO necessary(?): transformArtifactExtensions must ensure that each annotate
205
+ // is in either returns,items,elements,enum
206
+ if (art.builtin) // builtin members handled via "super annotate"
207
+ return;
208
+ // If elements are added in the future via includes or extend, it should be done here
209
+
149
210
  const extensionsMap = art._extensions;
150
- if (!extensionsMap || art.builtin) // builtin members handled via "super annotate"
211
+ if (!extensionsMap) // builtin members handled via "super annotate"
151
212
  return;
152
213
  // type extensions after having “populated” the artifact ($typeArgs -> length,
153
214
  // …, TODO: do that there) and setting an _effectiveType:
@@ -165,38 +226,39 @@ function extend( model ) {
165
226
  delete art.$typeExts;
166
227
  }
167
228
 
168
- if (art.kind === 'annotate' && !art.returns && extensionsMap.returns && !art._parent?.returns)
169
- annotateCreate( art, '', art, 'returns' );
170
-
171
229
  moveDictExtensions( art, extensionsMap, 'actions' );
172
230
  moveDictExtensions( art, extensionsMap, 'params' );
173
- moveReturnsExtensions( art, extensionsMap );
174
-
175
- if (art.returns) {
176
- ensureArtifactNotProcessed( art.returns );
177
- pushToDict( art.returns, '_extensions', ...extensionsMap.elements || [] );
178
- pushToDict( art.returns, '_extensions', ...extensionsMap.enum || [] );
179
- if (art.kind !== 'annotate') {
180
- extendHandleReturns( extensionsMap.elements, art );
181
- extendHandleReturns( extensionsMap.enum, art );
182
- return;
231
+ if (!extensionsMap.elements)
232
+ return;
233
+
234
+ // after populateArtifact, it is clear which properties the artifact has:
235
+ const artProp = art.kind === 'action' || art.kind === 'function'
236
+ ? 'returns'
237
+ : [ 'returns', 'targetAspect', 'items' ].find( p => art[p] );
238
+ if (artProp) {
239
+ const returnsDict = artProp === 'returns' && !art.returns &&
240
+ annotateFor( art, 'params', '' ); // create anno for non-existing returns
241
+ for (const ext of extensionsMap.elements) {
242
+ if (!ext.returns) {
243
+ pushToDict( returnsDict || art[artProp], '_extensions', ext );
244
+ if (artProp === 'returns')
245
+ extendHandleReturns( ext, art );
246
+ }
247
+ else {
248
+ pushToDict( returnsDict || art[artProp], '_extensions', ext.returns );
249
+ if (!art.returns)
250
+ checkReturnsExtension( ext, art );
251
+ }
183
252
  }
253
+ // TODO: what about `many many Type` (via CSN)?
184
254
  }
185
- const sub = art.items || art.targetAspect?.elements && art.targetAspect;
186
- if (sub) {
187
- ensureArtifactNotProcessed( sub );
188
- pushToDict( sub, '_extensions', ...extensionsMap.elements || [] );
189
- pushToDict( sub, '_extensions', ...extensionsMap.enum || [] );
190
- }
191
- else {
192
- let elementsProp = 'elements';
193
- if (art.kind !== 'annotate')
194
- elementsProp = art.enum && 'enum' || art.target && 'foreignKeys' || 'elements';
195
-
196
- // keys are handled in tweak-assocs.js; don't push them down; see extendForeignKeys()
197
- if (elementsProp !== 'foreignKeys')
198
- moveDictExtensions( art, extensionsMap, elementsProp, 'elements' );
199
- moveDictExtensions( art, extensionsMap, 'enum' );
255
+ else if (!art.target) { // TODO: foreign keys currently handled specially
256
+ for (const ext of extensionsMap.elements) {
257
+ if (ext.returns)
258
+ checkReturnsExtension( ext, art );
259
+ }
260
+ // if (art.elements || art.enum || art.kind === 'annotate')
261
+ moveDictExtensions( art, extensionsMap, (art.enum ? 'enum' : 'elements'), 'elements' );
200
262
  }
201
263
  }
202
264
 
@@ -281,6 +343,9 @@ function extend( model ) {
281
343
 
282
344
  // For extendArtifactBefore(): ------------------------------------------------
283
345
 
346
+ /**
347
+ * Complain about invalid `extend service` and `extend context`.
348
+ */
284
349
  function checkExtensionsKind( extensions, art ) {
285
350
  for (const ext of extensions) {
286
351
  const kind = ext.expectedKind?.val;
@@ -295,7 +360,7 @@ function extend( model ) {
295
360
  code: 'extend … with definitions',
296
361
  keyword: 'extend service',
297
362
  };
298
- // TODO(v6): Discuss: make this an error?
363
+ // TODO v7: Discuss: make this an error?
299
364
  warning( 'ext-invalid-kind', [ loc, ext ], msgArgs, {
300
365
  std: 'Artifact $(ART) is not of kind $(KIND), use $(CODE) instead',
301
366
  annotate: 'There is no artifact $(ART), use $(CODE) instead',
@@ -308,26 +373,63 @@ function extend( model ) {
308
373
  }
309
374
  }
310
375
 
311
- // TODO: if extensions has more than one of returns,items,elements,enum, delete all those props
376
+ /**
377
+ * Transform `art._extensions` from an array of extensions into an object
378
+ * `{ prop: relevantExtensions, … }` where `relevantExtensions` are the extensions
379
+ * which are relevant for the value of `art[prop]` after the application of extensions.
380
+ *
381
+ * Remark: it is not necessarily clear at this moment whether `art` has
382
+ * `elements`, `enums`, `items`, etc.
383
+ */
312
384
  function transformArtifactExtensions( art ) {
313
- const hasOnlySubExtensions = art._outer; // items, anonymous aspects
314
- const dict = Object.create( null );
385
+ // TODO: if extensions has more than one of returns,items,elements,enum, delete all those props
386
+ const dict = {};
315
387
  for (const ext of art._extensions) {
388
+ // `annotate SomeFunction with @action { r @elem };` would later be moved to
389
+ // `someFunction.returns._extensions` due to `elements` → @action not relevant then:
390
+ // (TODO: should we use some “already applied” flag instead?)
391
+ const isAutoItemsOrReturns = art._outer || // in items or targetAspect
392
+ art._parent?.returns === art && !ext._parent?.returns;
316
393
  for (const prop in ext) {
317
- if (ext[prop] === undefined) // deleted property
394
+ if (!Object.hasOwn( ext, prop ) || ext[prop] === undefined) // deleted property
318
395
  continue;
319
396
  // TODO: do this check nicer (after complete move to new extensions mechanism)
320
- if (prop.charAt(0) === '@' || prop === 'doc' ||
321
- prop === 'includes' || prop === 'columns' ||
397
+ if (prop === 'includes') {
398
+ pushToDict( dict, prop, ext );
399
+ pushTo$add( dict, ext );
400
+ }
401
+ else if (prop.charAt(0) === '@' || prop === 'doc' || prop === 'columns' ||
322
402
  prop === 'length' || prop === 'scale' || prop === 'precision' || prop === 'srid') {
323
- if (!hasOnlySubExtensions)
403
+ if (!isAutoItemsOrReturns)
324
404
  pushToDict( dict, prop, ext );
325
405
  }
326
- else if (prop === 'elements' || prop === 'enum' || prop === 'actions' ||
327
- prop === 'params' || prop === 'returns') {
328
- if (ext.kind === 'extend')
329
- pushToDict( dict, 'includes', ext );
406
+ else if (prop === 'elements' || prop === 'enum') {
407
+ if (ext.returns) { // TODO: currently, an annotate can have both
408
+ // if this is a hard error, we could use syntax-unexpected-property#sibling
409
+ message( 'syntax-unexpected-with-returns',
410
+ [ ext[prop][$location] || ext.location, ext ],
411
+ { prop, siblingprop: 'returns' },
412
+ // eslint-disable-next-line @stylistic/max-len
413
+ 'Property $(PROP) of an annotate statement is ignored when it also has a property $(SIBLINGPROP)' );
414
+ }
415
+ else {
416
+ pushToDict( dict, 'elements', ext ); // yes, enum → elements here
417
+ if (ext.kind === 'extend' && !isAutoItemsOrReturns)
418
+ pushTo$add( dict, ext );
419
+ }
420
+ }
421
+ else if (prop === 'returns') {
422
+ pushToDict( dict, 'elements', ext );
423
+ // create 'returns' for the super annotate, store in elements anyway
424
+ if (!art.returns && art.kind === 'annotate')
425
+ annotateCreate( art, '', art, 'returns' );
426
+ if (ext.kind === 'extend' && !isAutoItemsOrReturns)
427
+ pushTo$add( dict, ext );
428
+ }
429
+ else if (prop === 'actions' || prop === 'params') {
330
430
  pushToDict( dict, prop, ext );
431
+ if (ext.kind === 'extend' && prop === 'actions' && !isAutoItemsOrReturns)
432
+ pushTo$add( dict, ext );
331
433
  }
332
434
  }
333
435
  }
@@ -360,40 +462,49 @@ function extend( model ) {
360
462
  setLink( model, '_sortedSources', scheduled );
361
463
  }
362
464
 
363
- function applyAllExtensions( art ) {
465
+ /**
466
+ * For all `prop → extensions` in `art._extensions`, apply the extensions.
467
+ * Currently only for annotations, the `doc` property, `columns` and type properties,
468
+ * not for `elements` and other members.
469
+ */
470
+ function applyPropertyExtensions( art, prop ) {
364
471
  const extensions = art._extensions;
365
- for (const prop in extensions) {
366
- // TODO: do the following `if` in a nicer way
367
- if ([ 'elements', 'enum', 'actions', 'params', 'returns' ].includes( prop ))
368
- continue; // currently just annotates on sub elements - TODO: error here
369
- // annotations, `doc`, `includes`, `columns`, `length`, ...
370
- const scheduled = [];
371
- // sort extensions according to layer (specified elements are bottom layer):
372
- const layered = layeredExtensions( extensions[prop] );
373
-
374
- let cont = true;
375
- while (cont) {
376
- const { highest, issue } = extensionsOfHighestLayers( layered );
377
- // console.log( 'CA:', annoName, issue, extensions)
378
- let index = highest.length;
379
- cont = !!index; // safety
380
- while (--index >= 0) {
381
- const ext = highest[index];
382
- scheduled.push( ext );
383
- if (extensionOverwrites( ext, prop )) {
384
- cont = false;
385
- break;
386
- }
472
+ // annotations, `doc`, `includes`, `columns`, `length`, ...
473
+ const scheduled = [];
474
+ // sort extensions according to layer (specified elements are bottom layer):
475
+ const layered = layeredExtensions( extensions[prop] );
476
+
477
+ let cont = true;
478
+ while (cont) {
479
+ const { highest, issue } = extensionsOfHighestLayers( layered );
480
+ // console.log( 'CA:', annoName, issue, extensions)
481
+ let index = highest.length;
482
+ cont = !!index; // safety
483
+ while (--index >= 0) {
484
+ const ext = highest[index];
485
+ scheduled.push( ext );
486
+ if (extensionOverwrites( ext, prop )) {
487
+ cont = false;
488
+ break;
387
489
  }
388
- if (issue || index > 0)
389
- reportDuplicateExtensions( highest, prop, issue, index, art );
390
490
  }
391
- // Now apply the relevant extensions
392
- scheduled.reverse();
393
- for (const ext of scheduled)
394
- applySingleExtension( art, ext, prop );
395
- delete extensions[prop];
396
- }
491
+ if (issue || index > 0)
492
+ reportDuplicateExtensions( highest, prop, issue, index, art );
493
+ }
494
+ // Now apply the relevant extensions
495
+ scheduled.reverse();
496
+ if (prop === 'includes' && !art.includes?.$original) {
497
+ const $original = art.includes ?? [];
498
+ art.includes = [ ...$original ];
499
+ art.includes.$original = $original;
500
+ }
501
+ if (prop === '$add') {
502
+ extensions[prop] = scheduled;
503
+ return; // the $add is applied in extendArtifactAdd()
504
+ }
505
+ for (const ext of scheduled)
506
+ applySingleExtension( art, ext, prop );
507
+ delete extensions[prop];
397
508
  }
398
509
 
399
510
  function extensionOverwrites( ext, prop ) {
@@ -405,9 +516,10 @@ function extend( model ) {
405
516
  // TODO: still a bit annotation assignment specific
406
517
  function reportDuplicateExtensions( extensions, prop, issue, index, art ) {
407
518
  // TODO: think about messages for these
408
- if (prop === 'elements' || prop === 'enum' || prop === 'actions' || prop === 'columns' ||
409
- prop === 'params' || prop === 'returns' || prop === 'includes' )
519
+ if (prop === 'elements' || prop === 'actions' || prop === 'columns' ||
520
+ prop === 'params' || prop === '$add' )
410
521
  return; // extensions currently handled extra
522
+ // TODO: columns?
411
523
  if (issue) {
412
524
  // eslint-disable-next-line no-nested-ternary
413
525
  let msg = (index < 0)
@@ -421,6 +533,7 @@ function extend( model ) {
421
533
  : 'ext-duplicate-extend-type-unrelated-layer';
422
534
  // not sure whether to repeat the extended artifact in the message (we
423
535
  // have the semantic location, after all)
536
+ // extend-repeated-intralayer / extend-unrelated-layer
424
537
  }
425
538
  const variant = prop === 'doc' ? 'doc' : 'std';
426
539
  for (const ext of extensions) {
@@ -447,14 +560,16 @@ function extend( model ) {
447
560
 
448
561
  function applySingleExtension( art, ext, prop ) {
449
562
  if (prop === 'includes') {
450
- if (ext.kind === 'extend' && art.$inferred) {
451
- error( 'extend-for-generated', [ ext.name.location, ext ], { art, keyword: 'extend' },
452
- 'You can\'t use $(KEYWORD) on the generated $(ART)' );
453
- }
454
- else if (art.kind !== 'annotate' && !art._outer) { // not with elem extension in targetAspect
455
- const { id } = art.name;
456
- const dict = extensionsDict[id] || (extensionsDict[id] = []);
457
- dict.push( ext ); // TODO: change
563
+ if (art.kind !== 'annotate' && !art._outer) { // TODO: why this check?
564
+ // not with elem extension in targetAspect
565
+ art.includes.push( ...ext.includes );
566
+ // head of ref in in ext.includes must be set in _block of ext; for
567
+ // remaining path items, effectiveType() has `art` as user
568
+ // (alternatively, we could set some _user on `includes` items):
569
+ for (const ref of ext.includes)
570
+ resolveUncheckedPath( ref, 'include', ext );
571
+ if (art.kind === 'entity')
572
+ setEntityIncludes( ext, art );
458
573
  // console.log( 'ASI:',prop,art.name,ext,extensionsDict[id])
459
574
  }
460
575
  // art[prop] = (art[prop]) ? art[prop].concat( ext[prop] ) : ext[prop];
@@ -476,7 +591,7 @@ function extend( model ) {
476
591
  query.columns = [ { location: query.from.location, val: '*' }, ...ext.columns ];
477
592
  else
478
593
  query.columns.push( ...ext.columns );
479
- initSelectItems( query, ext.columns, query, true );
594
+ ext.columns.forEach( col => changeParentLinks( col, query ) );
480
595
  }
481
596
  else if (typeParameters.list.includes( prop )) {
482
597
  const typeExts = art.$typeExts || (art.$typeExts = {});
@@ -488,6 +603,24 @@ function extend( model ) {
488
603
  }
489
604
  }
490
605
 
606
+ function changeParentLinks( art, queryOrMain ) {
607
+ // TODO: we might also change the implicit name (if name.id is a number,
608
+ // adding the previous column lenght - 1) for better error messages
609
+ const parent = art._parent;
610
+ if (!art._parent)
611
+ return;
612
+ if (parent.kind === 'extend')
613
+ art._parent = queryOrMain;
614
+ if (art._main.kind === 'extend') // TODO: probably always
615
+ art._main = queryOrMain._main;
616
+ if (art._columnParent?.kind === 'extend')
617
+ art._columnParent = queryOrMain;
618
+ const subColumns = art.expand || art.inline;
619
+ if (subColumns)
620
+ subColumns.forEach( a => changeParentLinks( a, queryOrMain ) );
621
+ forEachMember( art, a => changeParentLinks( a, queryOrMain ) );
622
+ }
623
+
491
624
  function applyAssignment( previousAnno, anno, art, annoName ) {
492
625
  const firstEllipsis = annotationHasEllipsis( anno );
493
626
  if (!firstEllipsis)
@@ -683,17 +816,16 @@ function extend( model ) {
683
816
  /**
684
817
  * If the target artifact has both precision and scale set, then extensions on it must also
685
818
  * provide both to avoid user errors for subsequent `extend` statements.
819
+ * There is already a syntax error [syntax-missing-type-property] for `scale` without `precision`.
686
820
  *
687
821
  * @param {XSN.Artifact} art
688
822
  * @param {object} exts
689
823
  */
690
824
  function checkPrecisionScaleExtension( art, exts ) {
691
825
  if (art.precision && art.scale) {
692
- if ((exts.precision || exts.scale) && !(exts.precision && exts.scale)) {
693
- const missing = exts.precision ? 'scale' : 'precision';
694
- const prop = exts.precision ? 'precision' : 'scale';
695
- error( 'ext-missing-type-property', [ exts[prop].location, exts[prop] ],
696
- { art, prop, otherprop: missing } );
826
+ if (exts.precision && !exts.scale) {
827
+ error( 'ext-missing-type-property', [ exts.precision.location, exts.precision ],
828
+ { art, prop: 'precision', otherprop: 'scale' } );
697
829
  }
698
830
  }
699
831
  }
@@ -711,58 +843,22 @@ function extend( model ) {
711
843
  if (!extensions)
712
844
  return;
713
845
 
714
- const artDict = art[artProp] || annotateFor( art, extProp ); // no auto-correction in annotate
715
-
716
846
  for (const ext of extensions) {
717
847
  let dictCheck = (art.kind !== 'annotate'); // no check in super annotate statement
718
- forEachGeneric(ext, extProp, (elemExt, name) => {
848
+ forEachGeneric( ext, extProp, ( elemExt, name ) => {
719
849
  if (elemExt.kind !== 'annotate' && elemExt.kind !== 'extend') // TODO: specified elems
720
850
  return; // definitions inside extend, already handled
721
851
  dictCheck = dictCheck && checkRemainingMemberExtensions( art, elemExt, artProp, name );
722
- const elem = artDict[name] || annotateFor( art, extProp, name );
852
+ const elem = art[artProp]?.[name] || annotateFor( art, extProp, name );
723
853
  setLink( elemExt.name, '_artifact', (elem.kind !== 'annotate' ? elem : null ) );
854
+ // TODO: why null for annotate?
724
855
  ensureArtifactNotProcessed( elem );
725
- if (elem.$duplicates !== true)
856
+ if (elem.$duplicates !== true) // TODO: re-check
726
857
  pushToDict( elem, '_extensions', elemExt );
727
858
  });
728
859
  }
729
860
  }
730
861
 
731
- function moveReturnsExtensions( art, extensionsMap ) {
732
- const extensions = extensionsMap.returns;
733
- if (!extensions)
734
- return;
735
- const artReturns = art.returns;
736
- let extReturns = artReturns;
737
- const isAction = art.kind === 'action' || art.kind === 'function';
738
-
739
- for (const ext of extensions) {
740
- if (!artReturns && art.kind !== 'annotate') {
741
- const msgId = ext.returns && hasSecurityAnno( ext.returns )
742
- ? 'ext-unexpected-returns-sec'
743
- : 'ext-unexpected-returns';
744
- message( msgId, [ ext.returns.location, ext ], {
745
- '#': (isAction ? art.kind : 'std'), keyword: 'returns',
746
- }, {
747
- std: 'Unexpected $(KEYWORD); only actions and functions have return parameters',
748
- action: 'Unexpected $(KEYWORD) for action without return parameter',
749
- // function without `returns` can happen via CSN input! TODO: check in parser
750
- function: 'Unexpected $(KEYWORD) for function without return parameter',
751
- } );
752
- // Do not put completely wrong returns into a “super annotate” statement;
753
- // this could induce consequential errors with [..., …]:
754
- if (!isAction)
755
- continue; // do not put into 'extensions'
756
- // add to 'extensions' for action/function without returns:
757
- extReturns ??= annotateFor( art, 'params', '' );
758
- }
759
- if (extReturns) {
760
- setLink( ext.name, '_artifact', (isAction ? artReturns : null ) );
761
- pushToDict( extReturns, '_extensions', ext.returns );
762
- }
763
- }
764
- }
765
-
766
862
  function annotateFor( art, prop, name ) {
767
863
  const base = annotateBase( art );
768
864
  if (name === '' && prop === 'params')
@@ -805,16 +901,33 @@ function extend( model ) {
805
901
  return annotate;
806
902
  }
807
903
 
808
- function extendHandleReturns( extensions, art ) {
809
- for (const ext of extensions || []) {
810
- warning( 'ext-expecting-returns', [ ext.name.location, ext ], {
811
- '#': art.kind, keyword: 'returns', code: 'annotate ‹name› with returns { … }',
812
- }, {
813
- std: 'Expected $(CODE)', // unused variant
814
- action: 'Expected $(KEYWORD) when annotating action return structure, i.e. $(CODE)',
815
- function: 'Expected $(KEYWORD) when annotating function return structure, i.e. $(CODE)',
816
- } );
817
- }
904
+ function checkReturnsExtension( ext, art ) {
905
+ const msgId = hasSecurityAnno( ext.returns )
906
+ ? 'ext-unexpected-returns-sec'
907
+ : 'ext-unexpected-returns';
908
+ message( msgId, [ ext.returns.location, ext ],
909
+ { '#': art.kind, keyword: 'returns' },
910
+ {
911
+ std: 'Unexpected $(KEYWORD); only actions and functions have return parameters',
912
+ action: 'Unexpected $(KEYWORD) for action without return parameter',
913
+ // function without `returns` can happen via CSN input! TODO: check in parser
914
+ function: 'Unexpected $(KEYWORD) for function without return parameter',
915
+ } );
916
+ // Do not put completely wrong returns into a “super annotate” statement;
917
+ // this could induce consequential errors with [..., …]:
918
+ return art.kind === 'action' || art.kind === 'function';
919
+ }
920
+
921
+ function extendHandleReturns( ext, art ) {
922
+ warning( 'ext-expecting-returns', [ ext.name.location, ext ], {
923
+ '#': art.kind, keyword: 'returns', code: 'annotate ‹name› with returns { … }',
924
+ }, {
925
+ std: 'Expected $(CODE)', // unused variant
926
+ action: 'Expected $(KEYWORD) when annotating action return structure, i.e. $(CODE)',
927
+ function: 'Expected $(KEYWORD) when annotating function return structure, i.e. $(CODE)',
928
+ // eslint-disable-next-line @stylistic/max-len
929
+ annotate: 'Expected $(KEYWORD) when annotating a return structure of an unknown action or function, i.e. $(CODE)',
930
+ } );
818
931
  }
819
932
 
820
933
  function checkRemainingMemberExtensions( parent, ext, prop, name ) {
@@ -915,7 +1028,7 @@ function extend( model ) {
915
1028
  if (extensions && !annotate._main) {
916
1029
  const art = model.definitions[annotate.name.id];
917
1030
  for (const ext of extensions)
918
- checkRemainingMainExtensions( art, ext );
1031
+ checkRemainingMainExtensions( art, ext ); // induce messages for extension path
919
1032
  if (art?.builtin && art.kind !== 'namespace') { // TODO: do not set `builtin` on cds, cds.hana
920
1033
  setLink( annotate, '_extensions', art._extensions ); // for messages and member extensions
921
1034
  // direct annotations on builtins or on the builtins for propagation, and
@@ -991,28 +1104,22 @@ function extend( model ) {
991
1104
 
992
1105
  // extend, mainly old-style ---------------------------------------------------
993
1106
 
994
- /**
995
- * Apply the extensions inside the extensionsDict on the model.
996
- *
997
- * First try normally: extends with structure includes; with remaining cyclic
998
- * includes, do so without includes.
999
- */
1000
- function applyExtensions() {
1001
- let cyclicIncludeNames = false;
1002
- let extNames = Object.keys( extensionsDict ).sort();
1003
-
1004
- while (extNames.length) {
1005
- const { length } = extNames;
1006
- for (const name of extNames) {
1007
- const art = model.definitions[name];
1008
- if (art && art.kind !== 'namespace' &&
1009
- extendArtifact( extensionsDict[name], art, cyclicIncludeNames ))
1010
- delete extensionsDict[name];
1011
- }
1012
- extNames = Object.keys( extensionsDict ); // no sort() required anymore
1013
- if (extNames.length >= length)
1014
- cyclicIncludeNames = Object.keys( extensionsDict ); // = no includes
1107
+ function extendArtifactAdd( art ) {
1108
+ const { includes } = art;
1109
+ if (includes) {
1110
+ if (includes.$original) // if extensions with includes:
1111
+ art.includes = includes.$original; // original includes have been stored
1112
+ if (art.includes?.length)
1113
+ applyIncludes( art, art );
1114
+ art.includes = includes;
1115
+ // early propagation of specific annotation assignments
1116
+ // TODO: propagate in effectiveType() ?
1117
+ propagateEarly( art, '@cds.autoexpose' );
1118
+ propagateEarly( art, '@fiori.draft.enabled' );
1015
1119
  }
1120
+ if (art._extensions?.$add)
1121
+ extendArtifact( art._extensions.$add, art );
1122
+ // TODO: for proper shadowing: first collect defined element & action names
1016
1123
  }
1017
1124
 
1018
1125
  /**
@@ -1028,40 +1135,13 @@ function extend( model ) {
1028
1135
  * @param {XSN.Definition} art
1029
1136
  * @param {String[]|false} [cyclicIncludeNames=false]
1030
1137
  */
1031
- function extendArtifact( extensions, art, cyclicIncludeNames = null ) {
1032
- if (!cyclicIncludeNames && !(canApplyIncludes( art, art ) &&
1033
- extensions.every( ext => canApplyIncludes( ext, art ) )))
1034
- return false;
1035
- if (cyclicIncludeNames) {
1036
- canApplyIncludes( art, art, cyclicIncludeNames );
1037
- extensions.forEach( ext => canApplyIncludes( ext, art, cyclicIncludeNames ) );
1038
- }
1039
- else if (!(canApplyIncludes( art, art ) &&
1040
- extensions.every( ext => canApplyIncludes( ext, art ) ))) {
1041
- // console.log( 'FALSE:',art.name, extensions.map( e => e.name ) )
1042
- return false;
1043
- }
1044
- if (!art.query) {
1138
+ function extendArtifact( extensions, art ) {
1139
+ if (!art.query && !art._main && !art._outer) { // TODO: remove _entities
1045
1140
  model._entities.push( art ); // add structure with includes in dep order
1046
- art.$entity = ++model.$entity;
1047
- }
1048
- if (art.includes) {
1049
- if (!cyclicIncludeNames) {
1050
- applyIncludes( art, art );
1051
- }
1052
- else {
1053
- // resolve artifacts to induce errors: either ref-invalid-include or ref-cyclic
1054
- for (const ref of art.includes)
1055
- resolvePath( ref, 'include', art );
1056
- }
1057
1141
  }
1142
+ // TODO: complain if $inferred
1058
1143
  // checkExtensionsKind( extensions, art );
1059
1144
  extendMembers( extensions, art );
1060
- if (!cyclicIncludeNames && art.includes) {
1061
- // early propagation of specific annotation assignments
1062
- propagateEarly( art, '@cds.autoexpose' );
1063
- propagateEarly( art, '@fiori.draft.enabled' );
1064
- }
1065
1145
  // TODO: complain about element extensions inside projection
1066
1146
  return true;
1067
1147
  }
@@ -1069,32 +1149,29 @@ function extend( model ) {
1069
1149
  function extendMembers( extensions, art ) {
1070
1150
  // TODO: do the whole extension stuff lazily if the elements are requested
1071
1151
  const elemExtensions = [];
1072
- if (art._main) // extensions already sorted for main artifacts
1073
- extensions.sort( layers.compareLayer );
1152
+ // if (art._main) // extensions already sorted for main artifacts
1153
+ // extensions.sort( layers.compareLayer );
1074
1154
  // TODO: use same sequence as in chooseAssignment() - better: use common code with that fn
1075
1155
  // console.log('EM:',art.name,extensions,art._extensions)
1076
1156
  for (const ext of extensions) { // those in extMap.includes
1157
+ if (art.$inferred) {
1158
+ error( 'extend-for-generated', [ ext.name.location, ext ], { art, keyword: 'extend' },
1159
+ 'You can\'t use $(KEYWORD) on the generated $(ART)' ); // or with inferred elements
1160
+ }
1161
+
1162
+
1077
1163
  // console.log(message( 'id', [ext.location, ext], { art: ext.name._artifact },
1078
1164
  // 'Info', 'EXT').toString())
1079
- if (ext.name._artifact === undefined) { // not already applied
1080
- setArtifactLink( ext.name, art );
1081
- if (ext.includes) {
1082
- // TODO: currently, re-compiling from gensrc does not give the exact
1083
- // element sequence - we need something like
1084
- // includes = ['Base1',3,'Base2']
1085
- // where 3 means adding the next 3 elements before applying include 'Base2'
1086
- if (art.includes)
1087
- art.includes.push( ...ext.includes );
1088
- else
1089
- art.includes = [ ...ext.includes ];
1090
- applyIncludes( ext, art );
1091
- }
1092
- // console.log(ext,art)
1093
- checkAnnotate( ext, art );
1094
- // TODO: do we allow to add elements with array of {...}? If yes, adapt
1095
- initMembers( ext, art, ext._block ); // might set _extend, _annotate
1096
- dependsOnSilent( art, ext ); // art depends silently on ext (inverse to normal dep!)
1097
- }
1165
+ setArtifactLink( ext.name, art ); // TODO: probably already done
1166
+ if (ext.includes)
1167
+ applyIncludes( ext, art );
1168
+
1169
+ // console.log(ext,art)
1170
+ checkAnnotate( ext, art );
1171
+ // TODO: do we allow to add elements with array of {...}? If yes, adapt
1172
+ initMembers( ext, art, ext._block ); // might set _extend, _annotate
1173
+ dependsOnSilent( art, ext ); // art depends silently on ext (inverse to normal dep!)
1174
+
1098
1175
  for (const name in ext.elements) {
1099
1176
  const elem = ext.elements[name];
1100
1177
  if (elem.kind === 'element') { // i.e. not extend or annotate
@@ -1104,7 +1181,7 @@ function extend( model ) {
1104
1181
  }
1105
1182
  }
1106
1183
  if (elemExtensions.length > 1)
1107
- reportUnstableExtensions( elemExtensions );
1184
+ reportUnstableExtensions( elemExtensions ); // TODO: still?
1108
1185
 
1109
1186
  // This whole function will be removed with a next change - no need to have nice code here:
1110
1187
  const dict = Object.create( null );
@@ -1128,8 +1205,6 @@ function extend( model ) {
1128
1205
  const member = validDict && validDict[name];
1129
1206
  if (!member)
1130
1207
  extendNothing( dict[name], 'elements', name, art, validDict );
1131
- else if (!(member.$duplicates))
1132
- extendMembers( dict[name], member );
1133
1208
  }
1134
1209
  }
1135
1210
 
@@ -1217,23 +1292,10 @@ function extend( model ) {
1217
1292
  const isQueryExtension = construct.kind === 'extend' && main.query;
1218
1293
  let obj = initItemsLinks( construct, block );
1219
1294
  initExprAnnoBlock( construct, block );
1220
- if (obj.target && targetIsTargetAspect( obj )) {
1221
- obj.targetAspect = obj.target;
1222
- delete obj.target;
1223
- }
1224
1295
  const { targetAspect } = obj;
1225
- if (targetAspect) {
1226
- if (obj.foreignKeys) {
1227
- error( 'type-unexpected-foreign-keys', [ obj.foreignKeys[$location], construct ] );
1228
- delete obj.foreignKeys; // continuation semantics: not specified
1229
- }
1230
- if (obj.on && !obj.target) {
1231
- error( 'type-unexpected-on-condition', [ obj.on.location, construct ] );
1232
- delete obj.on; // continuation semantics: not specified
1233
- }
1234
- if (targetAspect.elements)
1235
- initAnonymousAspect();
1236
- }
1296
+ if (targetAspect?.elements)
1297
+ initAnonymousAspect();
1298
+
1237
1299
  if (obj !== parent && obj.elements && parent.enum) { // applying the extension
1238
1300
  initElementsAsEnum();
1239
1301
  }
@@ -1258,6 +1320,8 @@ function extend( model ) {
1258
1320
  }
1259
1321
  return;
1260
1322
 
1323
+ // We allow `extend MyEnum with {…}` and not just `extend MyEnum with enum {…}`
1324
+ // for compatibility, with a warning.
1261
1325
  function initElementsAsEnum() {
1262
1326
  // in extensions, extended enums are represented as elements
1263
1327
  let hasElement = false;
@@ -1265,11 +1329,7 @@ function extend( model ) {
1265
1329
  const e = obj.elements[n];
1266
1330
  if (e.kind === 'extend')
1267
1331
  continue;
1268
- const noVal = e.value?.val === undefined && e.value?.sym === undefined;
1269
- // TODO: forbid #symbol as enum value
1270
- if (e.$syntax === 'element' || // `extend … with elements` or `extend with { element … }`
1271
- noVal && e.$syntax !== 'enum' || // no value in CDL input
1272
- e.virtual || e.key || e.masked || e.type || e.elements || e.items || e.stored) {
1332
+ if (e.$syntax !== 'or-enum') { // parser might set $syntax: 'or-enum'
1273
1333
  // We do not want to complain separately about all element properties:
1274
1334
  error( 'ext-unexpected-element', [ e.location, construct ],
1275
1335
  { name: e.name.id, code: 'extend … with enum' },
@@ -1279,13 +1339,9 @@ function extend( model ) {
1279
1339
  return;
1280
1340
  }
1281
1341
  e.kind = 'enum';
1282
- if (noVal || e.$syntax !== 'enum')
1283
- hasElement = true; // warning with CDL input or `name: {}` in CSN input
1342
+ hasElement = true; // warning with CDL input or `name: {}` in CSN input
1284
1343
  }
1285
1344
  if (hasElement) {
1286
- // This message is similar to the one above. In v6, we could probably
1287
- // turn this warning into an error, remove `$syntax: 'element',
1288
- // and use the above `ext-unexpected-element` only for CSN input.
1289
1345
  warning( 'ext-expecting-enum', [ obj.elements[$location], construct ],
1290
1346
  { code: 'extend … with enum' }, 'Use $(CODE) when extending enums' );
1291
1347
  }
@@ -1329,8 +1385,6 @@ function extend( model ) {
1329
1385
  }
1330
1386
 
1331
1387
  function init( elem, name, prop ) {
1332
- if (!elem.kind) // wrong CSN input
1333
- elem.kind = dictKinds[prop];
1334
1388
  if (!elem.name && !elem._outer) {
1335
1389
  const ref = elem.targetElement || elem.kind === 'element' && elem.value;
1336
1390
  if (ref && ref.path) {
@@ -1345,10 +1399,9 @@ function extend( model ) {
1345
1399
  if (!elem._block)
1346
1400
  setLink( elem, '_block', block );
1347
1401
  // if (!kindProperties[ elem.kind ]) console.log(elem.kind,elem.name)
1348
- if ((elem.kind === 'extend' || elem.kind === 'annotate')) {
1349
- storeExtension( elem, name, prop, parent );
1402
+ if ((elem.kind === 'extend' || elem.kind === 'annotate'))
1350
1403
  return;
1351
- }
1404
+
1352
1405
  if (isQueryExtension && elem.kind === 'element') {
1353
1406
  error( 'extend-query', [ elem.location, construct ], // TODO: searchName ?
1354
1407
  { code: 'extend projection' },
@@ -1359,19 +1412,21 @@ function extend( model ) {
1359
1412
  const existing = parent[prop]?.[name];
1360
1413
  const add = construct !== parent && (!existing || elem.$inferred !== 'include');
1361
1414
  // don't dump with `entity T {}; extend T with { extend e {}; e {}; e {} };`:
1362
- if (elem.$duplicates === true && add)
1415
+ const { $duplicates } = elem;
1416
+ if ($duplicates === true && add)
1363
1417
  elem.$duplicates = null;
1364
1418
  setMemberParent( elem, name, parent, add && prop );
1365
- checkRedefinition( elem );
1419
+ if (!$duplicates) // not already reported
1420
+ checkRedefinition( elem );
1366
1421
  initMembers( elem, elem, elem._block );
1367
1422
  if (elem.kind === 'action' || elem.kind === 'function')
1368
1423
  initBoundSelfParam( elem.params, elem._main );
1369
1424
 
1370
1425
  // for a correct home path, setMemberParent needed to be called
1371
1426
 
1372
- if (!elem.value || elem.kind !== 'element' ||
1373
- elem.$syntax === 'enum' && parent.kind === 'extend') // ambiguous in parse-cdl
1427
+ if (!elem.value || elem.kind !== 'element')
1374
1428
  return;
1429
+ // remark: potential enum elements have already been turned into enums
1375
1430
  // -> it's a calculated element
1376
1431
  if (!elem.type && elem.value?.type) { // top-level CAST( expr AS type )
1377
1432
  if (!elem.target)
@@ -1469,47 +1524,13 @@ function extend( model ) {
1469
1524
 
1470
1525
  // includes ----------------------------------------------------------------
1471
1526
 
1472
- /**
1473
- * Returns true, if `art.includes` can be applied on `target`.
1474
- * They can't be applied if any of the artifacts referenced in
1475
- * `art.includes` are yet to be extended.
1476
- * `art !== target` if `art` is an extension.
1477
- *
1478
- * @param {XSN.Definition} art
1479
- * @param {XSN.Artifact} target
1480
- * @param {string[]} [cyclicIncludeNames]
1481
- * @returns {boolean}
1482
- */
1483
- function canApplyIncludes( art, target, cyclicIncludeNames ) {
1484
- if (!art.includes)
1485
- return true;
1486
- for (const ref of art.includes) {
1487
- const name = resolveUncheckedPath( ref, 'include', art );
1488
- // console.log('CAI:',cyclicIncludeNames, name, ref.path, Object.keys(extensionsDict))
1489
- if (cyclicIncludeNames) {
1490
- if (!cyclicIncludeNames.includes( name ))
1491
- continue;
1492
- delete ref._artifact;
1493
- }
1494
- else if (name && name in extensionsDict) {
1495
- // one of the includes has itself extensions that need to be applied first
1496
- return false;
1497
- }
1498
- else if (ref._artifact) {
1499
- delete ref._artifact;
1500
- }
1501
- }
1502
- return true;
1503
- }
1504
-
1505
1527
  /**
1506
1528
  * Apply all includes of `ext` on `ext`. Checks that `art` allows includes.
1507
1529
  * If `ext === art`, then includes of the artifact itself are applied.
1508
1530
  * If `ext !== art`, applies includes on the extensions, not artifact.
1509
- * Sets `_ancestors` links on `art`.
1510
- *
1511
- * TODO: try to set `_ancestors` only to entities (but beware “intermediate”
1512
- * non-entities - TODO: make intermediate non-entities stop chain).
1531
+
1532
+ * Sets `_ancestors` links on `art` if it is an entity, collecting the entity
1533
+ * includes with their ancestors.
1513
1534
  *
1514
1535
  * Examples:
1515
1536
  * ext === art: `entity E : F {}` => add elements of F to E
@@ -1518,25 +1539,24 @@ function extend( model ) {
1518
1539
  * @param {XSN.Extension} ext
1519
1540
  * @param {XSN.Artifact} art
1520
1541
  */
1542
+ function setEntityIncludes( ext, art ) {
1543
+ setLink( art, '_entityIncludes', [] );
1544
+ for (const ref of ext.includes) {
1545
+ const name = resolveUncheckedPath( ref, 'include', ext );
1546
+ const template = name && model.definitions[name];
1547
+ if (template?.kind === 'entity')
1548
+ art._entityIncludes.push( template );
1549
+ }
1550
+ }
1551
+
1521
1552
  function applyIncludes( ext, art ) {
1522
1553
  if (kindProperties[art.kind].include !== true) {
1523
1554
  error( 'extend-unexpected-include', [ ext.includes[0]?.location, ext ],
1524
1555
  { meta: art.kind } );
1525
1556
  return;
1526
1557
  }
1527
-
1528
- if (!art._ancestors && !art.query)
1529
- setLink( art, '_ancestors', [] ); // recursive array of includes
1530
- const shouldSetAncestors = art.kind === 'entity' && !art.query;
1531
- for (const ref of ext.includes) {
1532
- const template = resolvePath( ref, 'include', art );
1533
- // !template -> non-includable, e.g. scalar type, or cyclic
1534
- if (template?.kind === 'entity' && shouldSetAncestors) {
1535
- if (template._ancestors)
1536
- art._ancestors.push( ...template._ancestors );
1537
- art._ancestors.push( template );
1538
- }
1539
- }
1558
+ // console.log( (ext===art?'D-INCL:':'E-INCL:'), art.kind, art.name.id,
1559
+ // ...ext.includes.map( r => r._artifact?.name.id ));
1540
1560
  if (!art.query && art.elements) // do not set art.elements and art.enums with query entity!
1541
1561
  includeMembers( ext, art, 'elements' );
1542
1562
  if (art.kind !== 'type') {
@@ -1567,6 +1587,7 @@ function extend( model ) {
1567
1587
  // Warning 'Overwrites definition from include "I" (at elem def)
1568
1588
  const parent = ext === art && art;
1569
1589
  const members = ext[prop];
1590
+ // if (members)console.log( 'EXT:', prop, art.kind, art.name.id, ...Object.keys(members));
1570
1591
  if (members) {
1571
1592
  ext[prop] = Object.create( null );
1572
1593
  ext[prop][$location] = members[$location];
@@ -1609,7 +1630,8 @@ function extend( model ) {
1609
1630
  // TODO: If paths become invalid in the new artifact, should we mark
1610
1631
  // all usages in the expressions? Possibly just the first one?
1611
1632
  // TODO: Unify with other code in extend.js
1612
- elem.value = Object.assign( { $inferred: 'include' }, copyExpr( origin.value ));
1633
+ elem.value = Object.assign( { $inferred: 'include' },
1634
+ copyExpr( origin.value, location, true ) );
1613
1635
  elem.$syntax = 'calc';
1614
1636
  createAndLinkCalcDepElement( elem );
1615
1637
  setLink( elem, '_calcOrigin', origin._calcOrigin || origin );
@@ -1640,17 +1662,21 @@ function extend( model ) {
1640
1662
  * contain elements `texts` and `localized` which are compiler-generated for
1641
1663
  * localized data. Due to recompilation, we cannot just check the `$inferred`
1642
1664
  * property in the XSN, but need to apply an heuristics.
1665
+ * (We could still use $inferred as speed-up if run without `testMode`.)
1643
1666
  *
1644
1667
  * It is as follows: the elements `texts` and `localized` in entity `‹E›` and
1645
1668
  * the entity `‹E›.texts` are considered compiler-generated for Localized Data if
1646
1669
  *
1647
1670
  * - `‹E›.texts` has a key element `locale`
1671
+ * (TODO: currently weakend to “entity exists”)
1648
1672
  * - `texts` of `‹E›` is an unmanaged composition to `‹E›.texts`
1649
1673
  * - `localized` of `‹E›` is an unmanaged association to `‹E›.texts`
1650
1674
  */
1651
1675
  function withLocalizedData( { name, elements }, ext ) {
1652
1676
  const textsEntityName = `${ name.id }.texts`;
1653
- if (!model.definitions[textsEntityName]?.elements?.locale?.key?.val)
1677
+ // if we test (again) for element `locale` - it might come from an extend or
1678
+ // TextsAspect include, which might not have yet applied
1679
+ if (!model.definitions[textsEntityName])
1654
1680
  return false;
1655
1681
  const { texts, localized } = elements ?? {};
1656
1682
  return texts?.target && localized?.target && texts.on && localized.on &&
@@ -1791,5 +1817,11 @@ function propagateEarly( art, prop ) {
1791
1817
  }
1792
1818
  }
1793
1819
 
1820
+ function pushTo$add( extensions, ext ) {
1821
+ if (!extensions.$add)
1822
+ extensions.$add = [ ext ];
1823
+ else if (extensions.$add.at( -1 ) !== ext)
1824
+ extensions.$add.push( ext );
1825
+ }
1794
1826
 
1795
1827
  module.exports = extend;