@sap/cds-compiler 4.4.4 → 4.6.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 (82) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/bin/cdsc.js +18 -11
  3. package/bin/cdsv2m.js +7 -5
  4. package/doc/CHANGELOG_BETA.md +22 -0
  5. package/lib/api/main.js +306 -144
  6. package/lib/api/options.js +18 -6
  7. package/lib/api/validate.js +1 -1
  8. package/lib/base/message-registry.js +45 -10
  9. package/lib/base/messages.js +33 -16
  10. package/lib/base/model.js +4 -0
  11. package/lib/base/optionProcessorHelper.js +45 -176
  12. package/lib/checks/annotationsOData.js +49 -0
  13. package/lib/checks/elements.js +32 -34
  14. package/lib/checks/enricher.js +39 -3
  15. package/lib/checks/validator.js +8 -7
  16. package/lib/compiler/assert-consistency.js +40 -17
  17. package/lib/compiler/builtins.js +30 -53
  18. package/lib/compiler/checks.js +46 -14
  19. package/lib/compiler/cycle-detector.js +1 -4
  20. package/lib/compiler/define.js +35 -10
  21. package/lib/compiler/extend.js +21 -7
  22. package/lib/compiler/generate.js +3 -0
  23. package/lib/compiler/populate.js +5 -1
  24. package/lib/compiler/propagator.js +46 -9
  25. package/lib/compiler/resolve.js +94 -35
  26. package/lib/compiler/shared.js +60 -33
  27. package/lib/compiler/tweak-assocs.js +188 -92
  28. package/lib/compiler/utils.js +11 -1
  29. package/lib/edm/annotations/edmJson.js +41 -66
  30. package/lib/edm/annotations/genericTranslation.js +27 -9
  31. package/lib/edm/annotations/preprocessAnnotations.js +2 -3
  32. package/lib/edm/csn2edm.js +28 -11
  33. package/lib/edm/edmInboundChecks.js +58 -15
  34. package/lib/edm/edmPreprocessor.js +12 -16
  35. package/lib/edm/edmUtils.js +5 -2
  36. package/lib/gen/Dictionary.json +10 -0
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +15 -2
  39. package/lib/gen/language.tokens +1 -0
  40. package/lib/gen/languageParser.js +6557 -5618
  41. package/lib/json/from-csn.js +4 -5
  42. package/lib/json/to-csn.js +29 -4
  43. package/lib/language/antlrParser.js +19 -1
  44. package/lib/language/errorStrategy.js +28 -7
  45. package/lib/language/genericAntlrParser.js +118 -24
  46. package/lib/language/textUtils.js +16 -0
  47. package/lib/main.d.ts +28 -3
  48. package/lib/main.js +3 -0
  49. package/lib/model/csnRefs.js +4 -1
  50. package/lib/model/csnUtils.js +20 -14
  51. package/lib/model/revealInternalProperties.js +5 -2
  52. package/lib/optionProcessor.js +23 -22
  53. package/lib/render/manageConstraints.js +13 -29
  54. package/lib/render/toCdl.js +47 -26
  55. package/lib/render/toHdbcds.js +63 -42
  56. package/lib/render/toRename.js +6 -10
  57. package/lib/render/toSql.js +71 -117
  58. package/lib/render/utils/common.js +41 -6
  59. package/lib/transform/.eslintrc.json +9 -1
  60. package/lib/transform/addTenantFields.js +228 -0
  61. package/lib/transform/db/applyTransformations.js +57 -4
  62. package/lib/transform/db/assertUnique.js +4 -4
  63. package/lib/transform/db/backlinks.js +13 -1
  64. package/lib/transform/db/cdsPersistence.js +1 -1
  65. package/lib/transform/db/expansion.js +24 -3
  66. package/lib/transform/db/flattening.js +70 -71
  67. package/lib/transform/db/killAnnotations.js +37 -0
  68. package/lib/transform/db/rewriteCalculatedElements.js +46 -6
  69. package/lib/transform/db/temporal.js +1 -1
  70. package/lib/transform/draft/db.js +2 -16
  71. package/lib/transform/draft/odata.js +3 -3
  72. package/lib/transform/effective/associations.js +3 -5
  73. package/lib/transform/effective/main.js +6 -9
  74. package/lib/transform/forOdata.js +26 -55
  75. package/lib/transform/forRelationalDB.js +38 -18
  76. package/lib/transform/odata/toFinalBaseType.js +3 -3
  77. package/lib/transform/odata/typesExposure.js +14 -5
  78. package/lib/transform/transformUtils.js +47 -34
  79. package/lib/transform/translateAssocsToJoins.js +45 -11
  80. package/lib/transform/universalCsn/coreComputed.js +1 -1
  81. package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
  82. package/package.json +7 -6
@@ -4,6 +4,7 @@
4
4
 
5
5
  const {
6
6
  forEachGeneric,
7
+ forEachMember,
7
8
  forEachInOrder,
8
9
  } = require('../base/model');
9
10
  const { dictLocation, weakLocation, weakRefLocation } = require('../base/location');
@@ -32,6 +33,7 @@ function tweakAssocs( model ) {
32
33
  info, warning, error,
33
34
  } = model.$messageFunctions;
34
35
  const {
36
+ resolvePath,
35
37
  traverseExpr,
36
38
  checkExpr,
37
39
  checkOnCondition,
@@ -62,8 +64,6 @@ function tweakAssocs( model ) {
62
64
  function rewriteArtifact( art ) {
63
65
  // return;
64
66
  if (!art.query) {
65
- // console.log(message( null, art.location, art, {target:art._target},
66
- // 'Info','RAS').toString())
67
67
  rewriteAssociation( art );
68
68
  forEachGeneric( art, 'elements', rewriteAssociation );
69
69
  }
@@ -78,6 +78,8 @@ function tweakAssocs( model ) {
78
78
  traverseQueryPost( art.query, false, ( query ) => {
79
79
  forEachGeneric( query, 'elements', rewriteAssociationCheck );
80
80
  } );
81
+
82
+ checkForAnnotationRefs( art );
81
83
  }
82
84
 
83
85
  // function rewriteView( view ) {
@@ -122,6 +124,79 @@ function tweakAssocs( model ) {
122
124
  }
123
125
  }
124
126
 
127
+ function checkForAnnotationRefs( art ) {
128
+ // TODO: no check for type inheritance yet
129
+ const origin = art._origin ||
130
+ art.includes && art.includes[art.includes.length - 1]._artifact;
131
+ // Make sure not to waste time if no inherited annotation has checked refs:
132
+ if (!origin?.$contains?.$annotation)
133
+ return;
134
+ for (const prop in origin) {
135
+ const anno = prop.charAt(0) === '@' && !art[prop] && origin[prop];
136
+ // Remark: to be on the academic safe side, we should consider the
137
+ // annotations which are not propagated, but they never have references as
138
+ // value. So “no” for the moment. We also do not perform these checks in
139
+ // propagator.js, as it should go away in compiler v6.
140
+ if (anno.kind) { // i.e. with values refs
141
+ art[prop] = { ...anno, $inferred: 'prop' };
142
+ setLink( art[prop], '_outer', art );
143
+ const errorRef = checkAnnotationForRefs( art[prop], art );
144
+ if (errorRef) {
145
+ const valid = errorRef.path[errorRef.path.length - 1]._artifact;
146
+ error( 'anno-missing-rewrite', [ weakLocation( art.location ), art ], {
147
+ '#': (valid ? 'unrelated' : 'std'),
148
+ anno: prop,
149
+ art: origin,
150
+ elemref: errorRef,
151
+ } );
152
+ }
153
+ art.$contains ??= {};
154
+ art.$contains.$annotation = true;
155
+ }
156
+ }
157
+ forEachMember( art, checkForAnnotationRefs );
158
+ }
159
+
160
+ function checkAnnotationForRefs( expr, user ) {
161
+ if (expr.$tokenTexts)
162
+ return traverseExpr( expr, 'annoRewrite', user, checkAnnotationRef );
163
+ if (expr.literal === 'array')
164
+ return expr.val.find( val => checkAnnotationForRefs( val, user ) );
165
+ if (expr.literal !== 'struct')
166
+ return null;
167
+ const struct = Object.values( expr.struct );
168
+ return struct.find( val => checkAnnotationForRefs( val, user ) );
169
+ }
170
+
171
+ function checkAnnotationRef( ref, refCtx, user ) {
172
+ const origPath = ref.path;
173
+ if (!origPath[origPath.length - 1]._artifact) // already wrong in original
174
+ return null;
175
+ ref = { ...ref, path: [ ...origPath.map( item => ({ ...item }) ) ] };
176
+ if (!resolvePath( ref, refCtx, user ))
177
+ return ref;
178
+ return ref.path.some( isUnrelated ) && ref;
179
+
180
+ function isUnrelated( item, idx ) {
181
+ let elem = item._artifact;
182
+ const orig = origPath[idx]._artifact;
183
+ // With includes, we allow shadowing: an included element might seem to be
184
+ // unrelated.
185
+ if (elem._main?.includes && elem._main === (user._main || user))
186
+ return false;
187
+ if (!elem._effectiveType) // safety
188
+ return false;
189
+ // With redirections, the originally referred object might not be the same
190
+ // or even the direct _origin
191
+ do {
192
+ if (elem === orig)
193
+ return false;
194
+ elem = elem._origin;
195
+ } while (elem);
196
+ return true;
197
+ }
198
+ }
199
+
125
200
  function rewriteAssociationCheck( element ) {
126
201
  const elem = element.items || element; // TODO v5: nested items
127
202
  if (elem.elements)
@@ -287,7 +362,7 @@ function tweakAssocs( model ) {
287
362
  elem[prop] = { $inferred: 'NULL', val: undefined, location };
288
363
  break;
289
364
  }
290
- origin = origin._origin;
365
+ origin = getOrigin( origin );
291
366
  }
292
367
  }
293
368
  }
@@ -303,6 +378,10 @@ function tweakAssocs( model ) {
303
378
  }
304
379
 
305
380
  function rewriteKeys( elem, assoc ) {
381
+ addConditionFromAssocPublishing( elem, assoc, null );
382
+ if (elem.on)
383
+ return; // foreign keys were transformed into ON-condition
384
+
306
385
  // TODO: split this function: create foreign keys without `targetElement`
307
386
  // already in Phase 2: redirectImplicitly()
308
387
  // console.log(message( null, elem.location, elem, {art:assoc,target:assoc.target},
@@ -324,8 +403,6 @@ function tweakAssocs( model ) {
324
403
  } );
325
404
  if (elem.foreignKeys) // Possibly no fk was set
326
405
  elem.foreignKeys[$inferred] = 'rewrite';
327
-
328
- addConditionFromAssocPublishing( elem, assoc );
329
406
  }
330
407
 
331
408
  // TODO: there is no need to rewrite the on condition of non-leading queries,
@@ -340,35 +417,32 @@ function tweakAssocs( model ) {
340
417
  setExpandStatus( elem, 'target' );
341
418
  if (elem._parent?.kind === 'element') {
342
419
  // managed association as sub element not supported yet
343
- error( null, [ elem.location, elem ], {},
344
- // eslint-disable-next-line max-len
345
- 'Rewriting the ON-condition of unmanaged association in sub element is not supported' );
420
+ // TODO: Only report once for multi-include chains, see
421
+ // Associations/SubElements/UnmanagedInSubElement.err.cds
422
+ error( 'type-unsupported-rewrite', [ elem.location, elem ], { '#': 'sub-element' } );
346
423
  return;
347
424
  }
348
425
  const nav = (elem._main?.query && elem.value)
349
426
  ? pathNavigation( elem.value ) // redirected source elem or mixin
350
427
  : { navigation: assoc }; // redirected user-provided
351
- const cond = copyExpr( assoc.on,
428
+ elem.on = copyExpr( assoc.on,
352
429
  // replace location in ON except if from mixin element
353
- nav.tableAlias && elem.name.location );
354
- elem.on = cond;
355
- addConditionFromAssocPublishing( elem, assoc );
356
- // `cond` still points to the original condition; does not include possible assoc filter
430
+ nav.tableAlias && elem.name.location );
357
431
  elem.on.$inferred = 'copy';
358
432
 
359
433
  const { navigation } = nav;
360
434
  if (!navigation) // TODO: what about $projection.assoc as myAssoc ?
361
435
  return; // should not happen: $projection, $magic, or ref to const
436
+
362
437
  // Currently, having an unmanaged association inside a struct is not
363
438
  // supported by this function:
364
439
  if (navigation !== assoc && navigation._origin !== assoc) { // TODO: re-check
365
440
  // For "assoc1.assoc2" and "struct.elem1.assoc2"
366
441
  if (elem._redirected !== null) // null = already reported
367
442
  error( 'rewrite-not-supported', [ elem.target.location, elem ] );
368
- return;
369
443
  }
370
- if (!nav.tableAlias || nav.tableAlias.path) {
371
- traverseExpr( cond, 'rewrite-on', elem,
444
+ else if (!nav.tableAlias || nav.tableAlias.path) {
445
+ traverseExpr( elem.on, 'rewrite-on', elem,
372
446
  expr => rewriteExpr( expr, elem, nav.tableAlias ) );
373
447
  }
374
448
  else {
@@ -376,6 +450,12 @@ function tweakAssocs( model ) {
376
450
  error( null, [ elem.value.location, elem ], {},
377
451
  'Selecting unmanaged associations from a sub query is not supported' );
378
452
  }
453
+
454
+ // filter was copied in original element already
455
+ // TODO: not correct for e.g. composition-of-inline-aspect (#12223)
456
+ if (elem.$inferred !== 'include')
457
+ addConditionFromAssocPublishing( elem, assoc, nav );
458
+
379
459
  elem.on.$inferred = 'rewrite';
380
460
  }
381
461
 
@@ -387,56 +467,67 @@ function tweakAssocs( model ) {
387
467
  *
388
468
  * The added condition (filter) is already rewritten relative to `elem`.
389
469
  */
390
- function addConditionFromAssocPublishing( elem, assoc ) {
391
- const publishAssoc = (elem._main?.query && elem.value?.path?.length > 0);
470
+ function addConditionFromAssocPublishing( elem, assoc, nav ) {
471
+ const publishAssoc = (elem._main?.query || elem.$syntax === 'calc') &&
472
+ elem.value?.path?.length > 0;
392
473
  if (!publishAssoc)
393
474
  return;
394
475
 
476
+ nav ??= (elem._main?.query && elem.value)
477
+ ? pathNavigation( elem.value ) // redirected source elem or mixin
478
+ : { navigation: assoc }; // redirected user-provided
479
+
395
480
  const { location } = elem.name;
396
481
  const lastStep = elem.value.path[elem.value.path.length - 1];
482
+ if (!lastStep || !lastStep.where)
483
+ return;
397
484
 
398
- if (lastStep?.cardinality) {
399
- if (!elem.cardinality)
400
- elem.cardinality = assoc.cardinality || { location };
485
+ if (lastStep.cardinality) {
486
+ elem.cardinality ??= { ...assoc.cardinality };
487
+ elem.cardinality.location = location;
401
488
  for (const card of [ 'sourceMin', 'targetMin', 'targetMax' ]) {
402
489
  if (lastStep.cardinality[card])
403
490
  elem.cardinality[card] = copyExpr( lastStep.cardinality[card], location );
404
491
  }
405
492
  }
406
493
 
407
- if (lastStep?.where) {
408
- // If there are foreign keys, transform them into an ON-condition first.
409
- if (assoc.foreignKeys) {
410
- const cond = foreignKeysToOnCondition( elem );
411
- if (cond) {
412
- elem.on = cond;
413
- elem.foreignKeys = undefined;
414
- }
415
- }
416
-
417
- if (elem.on) {
418
- elem.on = {
419
- op: { val: 'and', location },
420
- args: [
421
- // TODO: Get rid of $parens
422
- { ...elem.on, $parens: [ assoc.location ] },
423
- filterToCondition( lastStep, elem ),
424
- ],
425
- location,
426
- $inferred: 'copy',
427
- };
494
+ // If there are foreign keys, transform them into an ON-condition first.
495
+ if (assoc.foreignKeys) {
496
+ const cond = foreignKeysToOnCondition( elem, assoc, nav );
497
+ if (cond) {
498
+ elem.on = cond;
499
+ elem.foreignKeys = undefined;
428
500
  }
429
501
  }
502
+
503
+ elem.on = {
504
+ op: { val: 'and', location },
505
+ args: [
506
+ // TODO: Get rid of $parens
507
+ { ...elem.on, $parens: [ assoc.location ] },
508
+ filterToCondition( lastStep, elem, nav ),
509
+ ],
510
+ location,
511
+ $inferred: 'copy',
512
+ };
513
+
514
+ elem.$filtered = {
515
+ val: true,
516
+ literal: 'boolean',
517
+ location,
518
+ $inferred: '$generated',
519
+ };
430
520
  }
431
521
 
432
522
  /**
433
- * Transform a filter on `assoc` into an ON-condition.
434
- * Paths inside the filter are rewritten relative to `elem`.
523
+ * Transform a filter on `assocPathStep` into an ON-condition.
524
+ * Paths inside the filter are rewritten relative to `assoc`, so they can be redirected
525
+ * using `rewriteExpr()` later on. `$self` paths remain unchanged.
435
526
  */
436
- function filterToCondition( assoc, elem ) {
437
- const cond = copyExpr( assoc.where );
527
+ function filterToCondition( assocPathStep, elem, nav ) {
528
+ const cond = copyExpr( assocPathStep.where );
438
529
  // TODO: Get rid of $parens
439
- cond.$parens = [ assoc.location ];
530
+ cond.$parens = [ assocPathStep.location ];
440
531
  traverseExpr( cond, 'rewrite-filter', elem, (expr) => {
441
532
  if (!expr.path || expr.path.length === 0)
442
533
  return;
@@ -450,15 +541,17 @@ function tweakAssocs( model ) {
450
541
  }
451
542
  else if (!root.builtin && root.kind !== 'builtin') {
452
543
  expr.path.unshift({
453
- id: elem.name.id,
544
+ id: assocPathStep.id,
454
545
  location: elem.name.location,
455
546
  });
456
- setLink( expr.path[0], '_artifact', elem );
457
- // _navigation link not necessary because this condition is not rewritten
458
- // inside the same view (would otherwise be needed for mixins).
459
-
460
- if (elem.name.id.charAt(0) === '$')
461
- prependSelfToPath( expr.path, elem );
547
+ setLink( expr.path[0], '_artifact', assocPathStep._artifact );
548
+ if (assocPathStep._navigation?.kind === 'mixin') {
549
+ // _navigation link necessary because condition is rewritten
550
+ // inside the same view (needed for mixins).
551
+ setLink( expr.path[0], '_navigation', assocPathStep._navigation );
552
+ }
553
+ // up to here, filter is relative to original association
554
+ rewriteExpr( expr, elem, nav?.tableAlias );
462
555
  }
463
556
  } );
464
557
 
@@ -467,13 +560,12 @@ function tweakAssocs( model ) {
467
560
  }
468
561
 
469
562
  // Caller must ensure ON-condition correctness via rewriteExpr()!
470
- function foreignKeysToOnCondition( elem ) {
471
- const nav = pathNavigation( elem.value );
472
- if (!nav.tableAlias && model.options.testMode && !elem._pathHead)
473
- throw new CompilerAssertion('rewriting keys to ON-condition: no tableAlias but not inline');
563
+ function foreignKeysToOnCondition( elem, assoc, nav ) {
564
+ if (model.options.testMode && !nav.tableAlias && !elem._pathHead && elem.$syntax !== 'calc')
565
+ throw new CompilerAssertion('rewriting keys to cond: no tableAlias but not inline/calc');
474
566
 
475
- if (!nav.tableAlias || elem._parent?.kind === 'element' ||
476
- (nav && nav.item !== elem.value.path[elem.value.path.length - 1])) {
567
+ if ((!nav.tableAlias && elem.$syntax !== 'calc') || elem._parent?.kind === 'element' ||
568
+ (nav && nav.item && nav.item !== elem.value.path[elem.value.path.length - 1])) {
477
569
  // - no nav.tableAlias for mixins or inside inline; mixins can't have managed assocs, though.
478
570
  // - _parent is element for expand
479
571
  // - nav.item is different for multi-path steps e.g. `sub.assoc`, which is not supported, yet
@@ -483,38 +575,39 @@ function tweakAssocs( model ) {
483
575
  }
484
576
 
485
577
  let cond = [];
486
- forEachInOrder( elem, 'foreignKeys', function keyToCond( fKey ) {
578
+ forEachInOrder( assoc, 'foreignKeys', function keyToCond( fKey ) {
487
579
  // Format: lhs = rhs
488
580
  // assoc.id = assoc_id
489
- // lhs and rhs look the same, but have different ids and _artifact links.
490
- // rhs is rewritten further down to ensure that the foreign key is projected.
581
+ // lhs and rhs look the same but are rewritten differently. We must ensure that
582
+ // the rhs is rewritten to a projected element (or it must remain the assoc's
583
+ // foreign key in case of calc elements).
491
584
  const lhs = {
492
585
  path: [
493
- { id: elem.name.id, location: elem.name.location },
586
+ { id: assoc.name.id, location: elem.name.location },
494
587
  ...copyExpr( fKey.targetElement.path ),
495
588
  ],
496
589
  location: elem.name.location,
497
590
  };
498
- setLink( lhs.path[0], '_artifact', elem ); // different to rhs!
499
- setLink( lhs, '_artifact', lhs.path[lhs.path.length - 1] );
591
+ setLink( lhs.path[0], '_artifact', assoc );
592
+ setLink( lhs, '_artifact', lhs.path[lhs.path.length - 1]._artifact );
500
593
 
501
- if (elem.name.id.charAt(0) === '$')
502
- prependSelfToPath( lhs.path, elem );
594
+ rewritePath( lhs, lhs.path[0], assoc, elem, elem.value.location ); // different to rhs!
503
595
 
504
596
  const rhs = {
505
597
  path: [
506
598
  // use origin's name; elem could have alias
507
- { id: elem._origin.name.id, location: elem._origin.name.location },
599
+ { id: assoc.name.id, location: elem.name.location },
508
600
  ...copyExpr( fKey.targetElement.path ),
509
601
  ],
510
602
  location: elem.name.location,
511
603
  };
512
- setLink( rhs.path[0], '_artifact', elem._origin ); // different to lhs!
513
- setLink( rhs, '_artifact', rhs.path[rhs.path.length - 1] );
604
+ setLink( rhs.path[0], '_artifact', assoc );
605
+ setLink( rhs, '_artifact', rhs.path[rhs.path.length - 1]._artifact );
514
606
 
515
- // Can't use rewriteExpr as that would use `assoc[…]` itself as well.
516
- const projectedFk = firstProjectionForPath( rhs.path, nav.tableAlias, elem );
517
- rewritePath( rhs, projectedFk.item, elem, projectedFk.elem, elem.value.location );
607
+ if (elem.$syntax !== 'calc') { // different to lhs!
608
+ const projectedFk = firstProjectionForPath( rhs.path, nav.tableAlias, elem );
609
+ rewritePath( rhs, projectedFk.item, elem, projectedFk.elem, elem.value.location );
610
+ }
518
611
 
519
612
  const fkCond = {
520
613
  op: { val: 'ixpr', location: elem.name.location },
@@ -593,31 +686,34 @@ function tweakAssocs( model ) {
593
686
  nav.item ? nav.item.location : expr.path[0].location );
594
687
  }
595
688
  }
596
- else { // from ON cond of element in included structure
689
+ else { // from ON cond of element that was included (i.e. from included structure)
597
690
  const root = expr.path[0]._navigation || expr.path[0]._artifact;
598
691
  if (root.builtin || root.kind !== '$self' && root.kind !== 'element')
599
692
  return;
600
693
  const item = expr.path[root.kind === '$self' ? 1 : 0];
601
694
  if (!item)
602
- return; // just $self
603
- // corresponding elem in including structure
604
- const elem = (assoc._main.items || assoc._main).elements[item.id];
695
+ return; // just $self
696
+ // corresponding elem in including structure or…
697
+ let elem = (assoc._main.items || assoc._main).elements[item.id];
698
+ if (assoc.$syntax === 'calc' && assoc._origin === elem) {
699
+ // … calc element where "elem" points to the referenced (possibly included)
700
+ // sibling element (association).
701
+ elem = assoc;
702
+ }
605
703
  if (!elem)
606
704
  return; // See #11755
607
- if (!(Array.isArray( elem ) || // no msg for redefs
608
- elem === item._artifact || // redirection for explicit def
705
+ if (!(elem === item._artifact || // redirection for explicit def
609
706
  elem._origin === item._artifact)) {
610
707
  const art = assoc._origin;
611
- warning( 'rewrite-shadowed', [ elem.name.location, elem ],
612
- { art: art && effectiveType( art ) },
613
- {
614
- // eslint-disable-next-line max-len
615
- std: 'This element is not originally referred to in the ON-condition of association $(ART)',
616
- // eslint-disable-next-line max-len
617
- element: 'This element is not originally referred to in the ON-condition of association $(MEMBER) of $(ART)',
618
- } );
708
+ // eslint-disable-next-line max-len
709
+ warning( 'rewrite-shadowed', [ elem.name.location, elem ], { art: art && effectiveType( art ) }, {
710
+ // eslint-disable-next-line max-len
711
+ std: 'This element is not originally referred to in the ON-condition of association $(ART)',
712
+ // eslint-disable-next-line max-len
713
+ element: 'This element is not originally referred to in the ON-condition of association $(MEMBER) of $(ART)',
714
+ } );
619
715
  }
620
- rewritePath( expr, item, assoc, (Array.isArray( elem ) ? false : elem), null );
716
+ rewritePath( expr, item, assoc, elem, null );
621
717
  }
622
718
  }
623
719
 
@@ -680,9 +776,9 @@ function tweakAssocs( model ) {
680
776
 
681
777
  function prependSelfToPath( path, elem ) {
682
778
  const root = { id: '$self', location: path[0].location };
683
- path.unshift( root );
684
779
  setLink( root, '_navigation', elem._parent.$tableAliases.$self );
685
780
  setArtifactLink( root, elem._parent );
781
+ path.unshift( root );
686
782
  }
687
783
 
688
784
  function rewriteItem( elem, item, name, assoc, forKeys ) {
@@ -719,12 +815,12 @@ function tweakAssocs( model ) {
719
815
  env = env.target._artifact?._effectiveType;
720
816
  elem = setArtifactLink( item, env?.elements?.[name] );
721
817
 
722
- if (elem && !Array.isArray( elem ))
818
+ if (elem)
723
819
  return elem;
724
820
  // TODO: better (extra message), TODO: do it
725
821
  error( 'query-undefined-element', [ item.location, assoc ],
726
822
  { id: name || item.id, '#': 'redirected' } );
727
- return (elem) ? false : null;
823
+ return null;
728
824
  }
729
825
  }
730
826
 
@@ -29,7 +29,8 @@ function pushLink( obj, prop, value ) {
29
29
  // for annotations:
30
30
 
31
31
  function annotationVal( anno ) {
32
- return anno && (anno.val === undefined || anno.val); // XSN TODO: set val for anno short form
32
+ // XSN TODO: set val but no location for anno short form
33
+ return anno && (anno.val === undefined || anno.val);
33
34
  }
34
35
  function annotationIsFalse( anno ) { // falsy, but not null (unset)
35
36
  return anno && (anno.val === false || anno.val === 0 || anno.val === '');
@@ -90,6 +91,7 @@ function setArtifactLink( obj, value ) {
90
91
  }
91
92
 
92
93
  function linkToOrigin( origin, name, parent, prop, location, silentDep ) {
94
+ // TODO: should `key` propagation be part of this?
93
95
  location ||= weakLocation( origin.name.location ); // not ??=
94
96
  const elem = {
95
97
  name: { location, id: origin.name.id },
@@ -153,12 +155,19 @@ function setMemberParent( elem, name, parent, prop ) {
153
155
  setLink( elem, '_main', parent._main || parent );
154
156
  }
155
157
 
158
+ function createAndLinkCalcDepElement( elem ) {
159
+ const r = { kind: '$calculation' }; // no name, like /items
160
+ elem.$calcDepElement = r;
161
+ setLink( r, '_outer', elem );
162
+ }
163
+
156
164
  /**
157
165
  * Adds a dependency user -> art with the given location.
158
166
  *
159
167
  * @param {XSN.Artifact} user
160
168
  * @param {XSN.Artifact} art
161
169
  * @param {XSN.Location} location
170
+ * @param {XSN.Artifact} [semanticLoc]
162
171
  */
163
172
  function dependsOn( user, art, location, semanticLoc = undefined ) {
164
173
  while (user._outer && !user.kind)
@@ -683,6 +692,7 @@ module.exports = {
683
692
  dependsOn,
684
693
  dependsOnSilent,
685
694
  setMemberParent,
695
+ createAndLinkCalcDepElement,
686
696
  storeExtension,
687
697
  withAssociation,
688
698
  pathName,