@sap/cds-compiler 4.4.4 → 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/bin/cdsc.js +5 -0
  3. package/bin/cdsv2m.js +7 -5
  4. package/doc/CHANGELOG_BETA.md +16 -0
  5. package/lib/api/main.js +68 -47
  6. package/lib/api/options.js +10 -6
  7. package/lib/api/validate.js +1 -1
  8. package/lib/base/message-registry.js +28 -6
  9. package/lib/base/messages.js +18 -13
  10. package/lib/base/model.js +3 -0
  11. package/lib/checks/annotationsOData.js +49 -0
  12. package/lib/checks/validator.js +6 -4
  13. package/lib/compiler/assert-consistency.js +38 -16
  14. package/lib/compiler/builtins.js +10 -49
  15. package/lib/compiler/checks.js +16 -8
  16. package/lib/compiler/cycle-detector.js +1 -4
  17. package/lib/compiler/define.js +4 -1
  18. package/lib/compiler/extend.js +21 -7
  19. package/lib/compiler/generate.js +3 -0
  20. package/lib/compiler/populate.js +5 -1
  21. package/lib/compiler/propagator.js +46 -9
  22. package/lib/compiler/resolve.js +68 -14
  23. package/lib/compiler/shared.js +44 -27
  24. package/lib/compiler/tweak-assocs.js +158 -37
  25. package/lib/compiler/utils.js +9 -0
  26. package/lib/edm/annotations/edmJson.js +35 -61
  27. package/lib/edm/annotations/genericTranslation.js +13 -5
  28. package/lib/edm/annotations/preprocessAnnotations.js +2 -3
  29. package/lib/edm/csn2edm.js +4 -1
  30. package/lib/edm/edmInboundChecks.js +59 -15
  31. package/lib/edm/edmPreprocessor.js +1 -7
  32. package/lib/gen/Dictionary.json +8 -0
  33. package/lib/gen/language.checksum +1 -1
  34. package/lib/gen/language.interp +12 -2
  35. package/lib/gen/languageParser.js +6095 -5195
  36. package/lib/json/from-csn.js +4 -5
  37. package/lib/json/to-csn.js +22 -3
  38. package/lib/language/errorStrategy.js +7 -3
  39. package/lib/language/genericAntlrParser.js +120 -24
  40. package/lib/language/textUtils.js +16 -0
  41. package/lib/model/csnUtils.js +9 -8
  42. package/lib/model/revealInternalProperties.js +5 -2
  43. package/lib/optionProcessor.js +2 -3
  44. package/lib/render/toCdl.js +31 -13
  45. package/lib/render/toHdbcds.js +20 -30
  46. package/lib/render/toSql.js +33 -54
  47. package/lib/render/utils/common.js +24 -6
  48. package/lib/transform/db/applyTransformations.js +59 -2
  49. package/lib/transform/db/backlinks.js +13 -1
  50. package/lib/transform/db/expansion.js +24 -3
  51. package/lib/transform/db/flattening.js +2 -2
  52. package/lib/transform/db/killAnnotations.js +37 -0
  53. package/lib/transform/db/rewriteCalculatedElements.js +46 -6
  54. package/lib/transform/forOdata.js +13 -46
  55. package/lib/transform/forRelationalDB.js +2 -1
  56. package/lib/transform/translateAssocsToJoins.js +13 -4
  57. package/lib/transform/universalCsn/coreComputed.js +1 -1
  58. package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
  59. package/package.json +7 -6
@@ -38,6 +38,7 @@ const {
38
38
  annotationLocation,
39
39
  linkToOrigin,
40
40
  setMemberParent,
41
+ dependsOn,
41
42
  proxyCopyMembers,
42
43
  setExpandStatus,
43
44
  setExpandStatusAnnotate,
@@ -855,6 +856,9 @@ function populate( model ) {
855
856
  const elemLocation = !query._main.$inferred && location;
856
857
  const origin = envParent ? navElem : navElem._origin;
857
858
  const elem = linkToOrigin( origin, name, query, null, elemLocation );
859
+ if (origin.$calcDepElement) // TODO: this will be changed in the next PR
860
+ dependsOn( elem, origin.$calcDepElement, location );
861
+
858
862
  // TODO: check assocToMany { * }
859
863
  dictAdd( elements, name, elem, ( _name, loc ) => {
860
864
  // there can be a definition from a previous inline with the same name:
@@ -1183,7 +1187,7 @@ function populate( model ) {
1183
1187
  function isDirectProjection( proj, base ) {
1184
1188
  return proj.kind === 'entity' && // not event
1185
1189
  // direct proj (TODO: or should we add them to another list?)
1186
- // TODO: delete ENTITY._from
1190
+ // TODO: delete ENTITY._from - maybe not...
1187
1191
  proj.query && proj.query.op && proj.query.op.val === 'SELECT' &&
1188
1192
  proj._from && proj._from.length === 1 &&
1189
1193
  base === resolvePath( proj._from[0], 'from', proj.query );
@@ -21,7 +21,7 @@ const {
21
21
  viewFromPrimary,
22
22
  } = require('./utils');
23
23
  const $inferred = Symbol.for( 'cds.$inferred' );
24
- // const { refString } = require( '../base/messages')
24
+ // const { ref } = require( '../model/revealInternalProperties' )
25
25
 
26
26
  // Note that propagation here is also used for deep-copying (function `onlyViaParent`)
27
27
  function propagate( model ) {
@@ -59,7 +59,7 @@ function propagate( model ) {
59
59
  srid: always,
60
60
  localized: always,
61
61
  target: notWithExpand,
62
- targetAspect: notWithExpand,
62
+ targetAspect,
63
63
  cardinality: notWithExpand,
64
64
  on: notWithExpand,
65
65
  foreignKeys: expensive, // includes "notWithExpand", dictionary copy
@@ -68,6 +68,7 @@ function propagate( model ) {
68
68
  enum: expensive,
69
69
  params: expensive, // actually only with parent action
70
70
  returns,
71
+ $filtered: annotation,
71
72
  };
72
73
  const { options } = model;
73
74
  // eslint-disable-next-line max-len
@@ -90,7 +91,7 @@ function propagate( model ) {
90
91
  runMembers( art );
91
92
  return;
92
93
  }
93
- // console.log('RUN:', art.name, art.elements ? Object.keys(art.elements) : 0)
94
+ // if (!art.builtin)console.log('RUN:', ref(art))
94
95
 
95
96
  const chain = [];
96
97
  let targets = [ art ];
@@ -109,6 +110,7 @@ function propagate( model ) {
109
110
  if (target.value?._artifact.$inferred !== 'include') {
110
111
  // If the referred to element is not inferred, it is a new one and not the original.
111
112
  // The new one was not originally referred to => error;
113
+ // TODO: no messages in propagator.js
112
114
  warning( 'ref-unexpected-override', [ target.name.location, target ],
113
115
  { id: target.name.id, target: target.value?._artifact },
114
116
  'Calculated element $(ID) does not originally refer to $(TARGET)' );
@@ -134,11 +136,11 @@ function propagate( model ) {
134
136
  chain.reverse();
135
137
  chain.forEach( step );
136
138
  runMembers( art );
137
- // console.log('DONE:', art.name, art.elements ? Object.keys(art.elements) : 0);
139
+ // if(!art.builtin)console.log('DONE:',ref(art),art.elements?Object.keys(art.elements):0);
138
140
  }
139
141
 
140
142
  function runMembers( art ) {
141
- // console.log('MEMBERS:',refString(art), art.elements ? Object.keys(art.elements) : 0)
143
+ // if(!art.builtin)console.log('MEMBERS:',ref(art))
142
144
  forEachMember( art, run ); // after propagation in parent!
143
145
  // propagate to sub query elements even if not requested:
144
146
  if (art.$queries)
@@ -150,16 +152,29 @@ function propagate( model ) {
150
152
  }
151
153
  if (obj.items)
152
154
  run( obj.items );
155
+ obj = obj.targetAspect;
156
+ // if(obj)console.log('TA:',ref(art),!!getOrigin( obj ))
157
+ if (obj && isAnonymousAspect( obj ))
158
+ run( obj );
153
159
  setLink( art, '_status', 'propagated' );
154
160
  }
155
161
 
162
+ function isAnonymousAspect( aspect ) {
163
+ while (aspect) {
164
+ if (aspect.elements)
165
+ return true;
166
+ aspect = getOrigin( aspect );
167
+ }
168
+ return false;
169
+ }
170
+
156
171
  function step({ target, source }) {
157
- // console.log('PROPS:',source&&source.name,'->',target.name)
158
172
  const viaType = target.type && // TODO: falsy $inferred value instead of 'cast'?
159
173
  (!target.type.$inferred || target.type.$inferred === 'cast');
160
174
  const keys = Object.keys( source );
175
+ // console.log('PROPS:',ref(source),'->',ref(target),keys.join('+'))
161
176
  for (const prop of keys) {
162
- // TODO: warning with competing props from multi-includes
177
+ // TODO: warning with competing props from multi-includes, but not in propagator.js
163
178
  if (target[prop] !== undefined || source[prop] === undefined)
164
179
  continue;
165
180
  const transformer = props[prop] || props[prop.charAt(0)];
@@ -227,7 +242,7 @@ function propagate( model ) {
227
242
  // * `enum`: an enum cannot be used with `expand`
228
243
  // * `keys`: should also not be propagated with `expand`
229
244
  function expensive( prop, target, source ) {
230
- // console.log(prop,source.name,'->',target.kind,target.name);
245
+ // console.log('EXP:',prop,ref(source),'->',ref(target));
231
246
  if (source.kind === 'builtin')
232
247
  return;
233
248
  if (target.expand) // do not propagate `keys` with expand
@@ -245,8 +260,12 @@ function propagate( model ) {
245
260
  target._outer && target._outer.location;
246
261
  const dict = source[prop];
247
262
  target[prop] = Object.create( null ); // also propagate empty elements
263
+ const propagateKey = target.kind === 'aspect'; // anonymous aspect
248
264
  for (const name in dict) {
249
- const member = linkToOrigin( dict[name], name, target, prop, location );
265
+ const origin = dict[name];
266
+ const member = linkToOrigin( origin, name, target, prop, location );
267
+ if (propagateKey && origin.key)
268
+ member.key = Object.assign( { $inferred: 'expanded' }, origin.key );
250
269
  member.$inferred = 'proxy';
251
270
  if (prop === 'foreignKeys')
252
271
  setLink( member, '_effectiveType', member );
@@ -262,6 +281,24 @@ function propagate( model ) {
262
281
  always( prop, target, source );
263
282
  }
264
283
 
284
+ function targetAspect( prop, target, source ) {
285
+ if (target.targetAspect)
286
+ return;
287
+ const ta = source.targetAspect;
288
+ if (!ta.elements && !ta._origin) { // _origin set for elements in source
289
+ notWithExpand( prop, target, source );
290
+ }
291
+ else {
292
+ const tat = { location: ta.location, $inferred: 'prop', kind: 'aspect' };
293
+ setLink( tat, '_origin', ta );
294
+ setLink( tat, '_outer', target );
295
+ setLink( tat, '_parent', target._parent );
296
+ setLink( tat, '_main', null );
297
+ target.targetAspect = tat;
298
+ // console.log('TAC:',ref(tat),'via',ref(ta))
299
+ }
300
+ }
301
+
265
302
  function notWithExpand( prop, target, source ) {
266
303
  if (!target.expand || prop === 'type' && source.elements)
267
304
  always( prop, target, source );
@@ -44,6 +44,7 @@ const {
44
44
  forEachGeneric,
45
45
  forEachInOrder,
46
46
  isDeprecatedEnabled,
47
+ isBetaEnabled,
47
48
  } = require('../base/model');
48
49
  const { dictAdd } = require('../base/dictionaries');
49
50
  const { dictLocation, weakLocation } = require('../base/location');
@@ -132,7 +133,9 @@ function resolve( model ) {
132
133
  if (location) {
133
134
  model.$assert = null;
134
135
  const msg = semanticLoc && 'target';
135
- error( 'ref-cyclic', [ location, semanticLoc || user ], { art, '#': msg }, {
136
+ error( 'ref-cyclic', [ location, semanticLoc || user ], {
137
+ art, '#': msg,
138
+ }, {
136
139
  std: 'Illegal circular reference to $(ART)',
137
140
  element: 'Illegal circular reference to element $(MEMBER) of $(ART)',
138
141
  target: 'Illegal circular reference to target $(ART)',
@@ -638,8 +641,11 @@ function resolve( model ) {
638
641
 
639
642
  // If cardinality is not specified, the compiler uses the inferred one.
640
643
  if (specifiedElement.cardinality) {
644
+ // Users can change the origin's cardinality via filter: We can't rely on the origin.
645
+ const ref = inferredElement.value?.path;
646
+ const assocFilterCardinality = ref?.[ref.length - 1]?.cardinality;
641
647
  const sCardinality = specifiedElement.cardinality;
642
- const iCardinality = getInferredPropFromOrigin( 'cardinality' );
648
+ const iCardinality = assocFilterCardinality || getInferredCardinality();
643
649
  if (!iCardinality) {
644
650
  error( 'query-mismatched-element', [
645
651
  sCardinality.location || specifiedElement.location, user,
@@ -747,6 +753,20 @@ function resolve( model ) {
747
753
  }
748
754
  return element[prop];
749
755
  }
756
+
757
+ function getInferredCardinality() {
758
+ let element = inferredElement;
759
+ if (element._effectiveType !== 0) {
760
+ while (getOrigin( element )) {
761
+ const ref = element.value?.path;
762
+ if (element.cardinality || ref?.[ref.length - 1]?.cardinality)
763
+ break;
764
+ element = getOrigin( element );
765
+ }
766
+ }
767
+ const ref = element.value?.path;
768
+ return element.cardinality || ref?.[ref.length - 1]?.cardinality;
769
+ }
750
770
  }
751
771
 
752
772
 
@@ -1353,18 +1373,50 @@ function resolve( model ) {
1353
1373
  }
1354
1374
  }
1355
1375
 
1356
- function resolveAnnoExpr( expr, art ) {
1357
- if (expr.$tokenTexts)
1358
- resolveExpr( expr, 'annotation', art );
1359
- else if (expr.literal === 'array')
1360
- expr.val.forEach( val => resolveAnnoExpr( val, art ) );
1361
- else if (expr.literal === 'struct')
1362
- Object.values( expr.struct ).forEach( val => resolveAnnoExpr( val, art ) );
1376
+ function resolveAnnoExpr( expr, art, anno = expr ) {
1377
+ if (expr.$tokenTexts) {
1378
+ if (!anno.kind)
1379
+ initAnnotationForExpression( anno, art );
1380
+ resolveExpr( expr, 'annotation', anno );
1381
+ reportUnsupportedAnnoExpr( expr );
1382
+ }
1383
+ else if (expr.literal === 'array') {
1384
+ expr.val.forEach( val => resolveAnnoExpr( val, art, anno ) );
1385
+ }
1386
+ else if (expr.literal === 'struct') {
1387
+ Object.values( expr.struct ).forEach( val => resolveAnnoExpr( val, art, anno ) );
1388
+ }
1389
+ }
1390
+
1391
+ function reportUnsupportedAnnoExpr( expr ) {
1392
+ if (isBetaEnabled( model.options, 'annotationExpressions' ))
1393
+ return;
1394
+ const alreadyReported = model.$messageFunctions.messages
1395
+ .find(msg => msg.messageId === 'anno-experimental-expressions');
1396
+ if (!alreadyReported) {
1397
+ info( 'anno-experimental-expressions', [ expr.location ], {
1398
+ option: 'annotationExpressions',
1399
+ // eslint-disable-next-line max-len
1400
+ }, 'Expressions in annotation values are a beta feature. Use at your own risk. (This message can be suppressed with beta flag $(OPTION))' );
1401
+ }
1402
+ }
1403
+
1404
+ // for faster processing, mark artifacts and annotations which contain anno expressions
1405
+ function initAnnotationForExpression( anno, art ) {
1406
+ anno.kind = '$annotation';
1407
+ setLink( anno, '_outer', art );
1408
+ while (!art.$contains?.$annotation) {
1409
+ art.$contains ??= {};
1410
+ art.$contains.$annotation = true; // TODO: extra values for elem and $self refs
1411
+ if (!art._main)
1412
+ break;
1413
+ art = art._parent; // TODO: really go up?
1414
+ }
1363
1415
  }
1364
1416
 
1365
1417
  function resolveExpr( expr, exprCtx, user ) {
1366
1418
  // console.log(expr?.location.line,exprCtx)
1367
- traverseExpr( expr, exprCtx, user, (e, c, u) => resolveExprItem( e, c, u ) );
1419
+ traverseExpr( expr, exprCtx, user, resolveExprItem );
1368
1420
  }
1369
1421
 
1370
1422
  function resolveExprItem( expr, expected, user ) {
@@ -1374,8 +1426,10 @@ function resolve( model ) {
1374
1426
  if (expr.path) {
1375
1427
  // TODO: re-think this $expected: 'exists' thing
1376
1428
  if (expr.$expected === 'exists') {
1377
- error( 'expr-unexpected-exists', [ expr.location, user ], {},
1378
- 'An EXISTS predicate is not expected here' );
1429
+ if (expected !== 'annotation') { // `exists e[…]` allowed in annotation expressions
1430
+ error( 'expr-unexpected-exists', [ expr.location, user ], {},
1431
+ 'An EXISTS predicate is not expected here' );
1432
+ }
1379
1433
  // We complain about the EXISTS before, as EXISTS subquery is also not supported
1380
1434
  // TODO: location of EXISTS, TODO: really do this in define.js
1381
1435
  expr.$expected = 'approved-exists'; // only complain once
@@ -1414,8 +1468,8 @@ function resolve( model ) {
1414
1468
  if (step.args)
1415
1469
  resolveParams( step.args, art, entity, expected, user, step.location );
1416
1470
 
1417
- // Publishing an association with filters is ok in column
1418
- const publishAssoc = art.kind === 'entity' && expected === 'column';
1471
+ // Publishing an association with filters is ok in columns and also ok in calculated elements.
1472
+ const publishAssoc = art.kind === 'entity' && (expected === 'column' || expected === 'calc');
1419
1473
 
1420
1474
  if (entity || publishAssoc) {
1421
1475
  if (step.where) {
@@ -286,7 +286,6 @@ function fns( model ) {
286
286
  'ref-undefined-element': 'anno-undefined-element',
287
287
  'ref-undefined-param': 'anno-undefined-param',
288
288
  },
289
- // check: checkAssocOn,
290
289
  param: paramSemantics,
291
290
  nestedColumn: () => ({ // in expand and inline - TODO
292
291
  lexical: justDollarSelf,
@@ -297,6 +296,17 @@ function fns( model ) {
297
296
  rewriteProjectionToSelf: true,
298
297
  }),
299
298
  },
299
+ // TODO: introduce some kind of inheritance
300
+ annoRewrite: { // annotation assignments
301
+ lexical: justDollarSelf, // TODO: forbid $projection
302
+ dollar: true,
303
+ dynamic: parentElements,
304
+ navigation: assocOnNavigation,
305
+ noDep: true,
306
+ notFound: null, // no error, just falsy links
307
+ param: paramSemantics,
308
+ // nestedColumn: // in expand and inline - TODO
309
+ },
300
310
  };
301
311
 
302
312
  Object.assign( model.$functions, {
@@ -314,25 +324,25 @@ function fns( model ) {
314
324
  // Expression traversal function ----------------------------------------------
315
325
  function traverseExpr( expr, exprCtx, user, callback ) {
316
326
  if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
317
- return;
327
+ return null;
318
328
 
319
329
  if (expr.path) {
320
- callback( expr, exprCtx, user );
321
330
  // TODO: move arguments and filter traversal to here
322
- return;
323
- }
324
- else if (expr.type || expr.query) {
325
- callback( expr, exprCtx, user );
331
+ return callback( expr, exprCtx, user );
326
332
  }
333
+ let first = (expr.type || expr.query) && callback( expr, exprCtx, user );
327
334
 
328
- if (expr.args) {
335
+ if (expr.args && !first) {
329
336
  const args = Array.isArray( expr.args ) ? expr.args : Object.values( expr.args );
330
337
  // TODO: re-think $expected
331
- if (!callback.traverse?.( args, exprCtx, user, callback ))
332
- args.forEach( e => traverseExpr( e, exprCtx, user, callback ) );
338
+ first = callback.traverse // TODO: still in use?
339
+ ? callback.traverse( args, exprCtx, user, callback )
340
+ : args.find( e => traverseExpr( e, exprCtx, user, callback ) );
333
341
  }
334
- if (expr.suffix) // fn( … ) OVER …
335
- expr.suffix.forEach( e => traverseExpr( e, exprCtx, user, callback ) );
342
+
343
+ first ??= expr.suffix && // fn( ) OVER
344
+ expr.suffix.find( e => traverseExpr( e, exprCtx, user, callback ) );
345
+ return first;
336
346
  }
337
347
 
338
348
  // Return absolute name for unchecked path `ref`. We first try searching for
@@ -395,7 +405,6 @@ function fns( model ) {
395
405
  return setArtifactLink( ref, root );
396
406
 
397
407
  // how many path items are for artifacts (rest: elements)
398
- // console.log(expected, ref.path.map(a=>a.id),artItemsCount)
399
408
  let art = getPathItem( ref, semantics, user );
400
409
  if (!art)
401
410
  return setArtifactLink( ref, art );
@@ -413,14 +422,19 @@ function fns( model ) {
413
422
  const target = art._effectiveType?.target?._artifact;
414
423
  if (target)
415
424
  dependsOn( user._main, target, location, user );
425
+ if (target?.$calcDepElement)
426
+ dependsOn( user._main, target.$calcDepElement, location, user );
416
427
  }
417
428
  else if (art._main && art.kind !== 'select' || path[0]._navigation?.kind !== '$self') {
418
429
  // no real dependency to bare $self (or actually: the underlying query)
419
430
  dependsOn( user, art, location );
431
+ if (art.$calcDepElement)
432
+ dependsOn( user, art.$calcDepElement, location );
420
433
  // Without on-demand resolve, we can simply signal 'undefined "x"'
421
- // instead of 'illegal cycle' in the following case:
422
- // element elem: type of elem.x;
434
+ // instead of 'illegal cycle' in the following case:
435
+ // element elem: type of elem.x;
423
436
  }
437
+
424
438
  // TODO: really write dependency with expand/inline? write test
425
439
  // (removing it is not incompatible => not urgent)
426
440
  }
@@ -498,7 +512,9 @@ function fns( model ) {
498
512
  return undefined; // parse error
499
513
  if (head._artifact !== undefined)
500
514
  return head._artifact;
501
- const ruser = user._user || user; // TODO: nicer name if we keep this
515
+ let ruser = user._user || user; // TODO: nicer name if we keep this
516
+ if (ruser.kind === '$annotation')
517
+ ruser = ruser._outer;
502
518
 
503
519
  // Handle expand/inline, `type of`, :param, global (internally for CDL):
504
520
  if (user._pathHead && !semantics.isMainRef) // in expand/inline
@@ -545,8 +561,9 @@ function fns( model ) {
545
561
  else
546
562
  valid.push( model.$magicVariables.elements, removeDollarNames( dynamicDict ) );
547
563
  // TODO: streamline function arguments (probably: user, path, semantics )
548
- const undef = semantics.notFound( ruser, head, valid, dynamicDict,
549
- !isMainRef && user._user && user._artifact, path, semantics );
564
+ const undef = semantics.notFound?.( user._user || user, head, valid, dynamicDict,
565
+ !isMainRef && user._user && user._artifact,
566
+ path, semantics );
550
567
  return setArtifactLink( head, undef || null );
551
568
  }
552
569
 
@@ -913,9 +930,8 @@ function fns( model ) {
913
930
  if (target && assocSpec && user) {
914
931
  if (assocSpec !== 'calc')
915
932
  dependsOn( user._main || user, target, location || user.location, user );
916
- else // (TODO: users of) calc elements must depend on navigation target
917
- dependsOn( user, target, location || user.location );
918
- // TODO: have some _delayedDeps for calc elements
933
+ else
934
+ dependsOn( user.$calcDepElement, target, location || user.location, user );
919
935
  }
920
936
  const effectiveTarget = Functions.effectiveType( target );
921
937
  // if (effectiveTarget === 0 && location)
@@ -960,11 +976,11 @@ function fns( model ) {
960
976
  }
961
977
 
962
978
  function undefinedTargetElement( user, head, valid, _dict, pathItemArtifact ) {
963
- // is only called if there is a target, targetElements() returns falsy otherwise
964
- const { target } = pathItemArtifact?._effectiveType || user._parent;
979
+ // `art.target` may not set in case target entities `myEntity[unknown > 2]`
980
+ const art = pathItemArtifact?._effectiveType || user._parent;
965
981
  // TODO: better with $refs in filter conditions
966
982
  signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
967
- { '#': 'target', art: target, id: head.id } );
983
+ { '#': 'target', art: art.target || art, id: head.id } );
968
984
  }
969
985
 
970
986
  function undefinedVariable( user, head, valid ) {
@@ -1033,14 +1049,16 @@ function fns( model ) {
1033
1049
  return null;
1034
1050
  }
1035
1051
 
1036
- function undefinedNestedElement( user, head, valid, _dict, _art, path ) {
1052
+ function undefinedNestedElement( user, head, valid, _dict, _art, path, semantics ) {
1037
1053
  const art = user._pathHead._origin;
1038
1054
  if (!art)
1039
1055
  return null; // no consequential error
1040
- return undefinedItemElement( user, head, valid, null, art, path );
1056
+ return undefinedItemElement( user, head, valid, null, art, path, semantics );
1041
1057
  }
1042
1058
 
1043
1059
  function undefinedItemElement( user, item, valid, _dict, art, path, semantics ) {
1060
+ if (semantics.notFound === null)
1061
+ return;
1044
1062
  const query = userQuery( art );
1045
1063
  if (query?.name?.id > 1) {
1046
1064
  const root = userQuery( user ) !== query && path[0]._navigation;
@@ -1084,7 +1102,6 @@ function fns( model ) {
1084
1102
  { '#': variant, art: a, id: item.id }, semantics );
1085
1103
  }
1086
1104
  }
1087
- return null;
1088
1105
  }
1089
1106
 
1090
1107
  // Functions called via semantics.accept: -------------------------------------