@sap/cds-compiler 4.3.2 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/lib/api/main.js +14 -24
  3. package/lib/api/options.js +1 -0
  4. package/lib/api/trace.js +38 -0
  5. package/lib/base/location.js +46 -1
  6. package/lib/base/message-registry.js +68 -16
  7. package/lib/base/messages.js +8 -3
  8. package/lib/checks/.eslintrc.json +1 -0
  9. package/lib/checks/actionsFunctions.js +1 -1
  10. package/lib/checks/annotationsOData.js +2 -2
  11. package/lib/checks/selectItems.js +4 -1
  12. package/lib/compiler/assert-consistency.js +3 -2
  13. package/lib/compiler/base.js +1 -1
  14. package/lib/compiler/builtins.js +25 -1
  15. package/lib/compiler/checks.js +6 -5
  16. package/lib/compiler/define.js +12 -10
  17. package/lib/compiler/extend.js +22 -22
  18. package/lib/compiler/finalize-parse-cdl.js +1 -1
  19. package/lib/compiler/generate.js +70 -53
  20. package/lib/compiler/kick-start.js +7 -5
  21. package/lib/compiler/populate.js +31 -22
  22. package/lib/compiler/propagator.js +6 -2
  23. package/lib/compiler/resolve.js +52 -17
  24. package/lib/compiler/shared.js +74 -38
  25. package/lib/compiler/tweak-assocs.js +64 -23
  26. package/lib/compiler/utils.js +40 -23
  27. package/lib/edm/.eslintrc.json +2 -0
  28. package/lib/edm/EdmPrimitiveTypeDefinitions.js +252 -0
  29. package/lib/edm/annotations/edmJson.js +994 -0
  30. package/lib/edm/annotations/genericTranslation.js +75 -421
  31. package/lib/edm/annotations/vocabularyDefinitions.js +160 -0
  32. package/lib/edm/csn2edm.js +12 -5
  33. package/lib/edm/edm.js +14 -73
  34. package/lib/edm/edmPreprocessor.js +6 -0
  35. package/lib/gen/Dictionary.json +187 -16
  36. package/lib/gen/language.checksum +1 -1
  37. package/lib/gen/language.interp +1 -1
  38. package/lib/gen/languageLexer.interp +1 -1
  39. package/lib/gen/languageLexer.js +1129 -671
  40. package/lib/gen/languageParser.js +4285 -4283
  41. package/lib/json/from-csn.js +13 -18
  42. package/lib/json/to-csn.js +11 -6
  43. package/lib/language/antlrParser.js +0 -1
  44. package/lib/language/docCommentParser.js +1 -1
  45. package/lib/language/errorStrategy.js +95 -30
  46. package/lib/language/genericAntlrParser.js +21 -1
  47. package/lib/main.js +13 -3
  48. package/lib/model/csnRefs.js +42 -8
  49. package/lib/model/csnUtils.js +14 -2
  50. package/lib/model/enrichCsn.js +33 -5
  51. package/lib/model/revealInternalProperties.js +5 -0
  52. package/lib/modelCompare/compare.js +76 -14
  53. package/lib/modelCompare/utils/filter.js +19 -12
  54. package/lib/optionProcessor.js +2 -0
  55. package/lib/render/.eslintrc.json +1 -1
  56. package/lib/render/manageConstraints.js +1 -0
  57. package/lib/render/toHdbcds.js +3 -0
  58. package/lib/render/toRename.js +3 -1
  59. package/lib/render/toSql.js +46 -92
  60. package/lib/render/utils/common.js +76 -0
  61. package/lib/render/utils/delta.js +17 -3
  62. package/lib/sql-identifier.js +1 -1
  63. package/lib/transform/db/.eslintrc.json +1 -0
  64. package/lib/transform/db/applyTransformations.js +30 -4
  65. package/lib/transform/db/associations.js +22 -10
  66. package/lib/transform/db/backlinks.js +6 -2
  67. package/lib/transform/db/expansion.js +2 -2
  68. package/lib/transform/db/transformExists.js +13 -39
  69. package/lib/transform/draft/db.js +14 -3
  70. package/lib/transform/draft/odata.js +5 -18
  71. package/lib/transform/effective/associations.js +46 -15
  72. package/lib/transform/effective/main.js +7 -2
  73. package/lib/transform/effective/misc.js +43 -24
  74. package/lib/transform/effective/queries.js +20 -22
  75. package/lib/transform/effective/types.js +6 -2
  76. package/lib/transform/forOdata.js +5 -2
  77. package/lib/transform/localized.js +1 -1
  78. package/lib/transform/parseExpr.js +73 -21
  79. package/lib/transform/translateAssocsToJoins.js +22 -15
  80. package/lib/utils/term.js +2 -2
  81. package/package.json +2 -1
@@ -26,7 +26,7 @@ const {
26
26
  const {
27
27
  dictAdd, dictAddArray, dictFirst, dictForEach,
28
28
  } = require('../base/dictionaries');
29
- const { weakLocation } = require('../base/location');
29
+ const { weakLocation, weakRefLocation } = require('../base/location');
30
30
  const { CompilerAssertion } = require('../base/error');
31
31
 
32
32
  const { kindProperties } = require('./base');
@@ -36,7 +36,6 @@ const {
36
36
  annotationVal,
37
37
  annotationIsFalse,
38
38
  annotationLocation,
39
- augmentPath,
40
39
  linkToOrigin,
41
40
  setMemberParent,
42
41
  proxyCopyMembers,
@@ -113,6 +112,7 @@ function populate( model ) {
113
112
 
114
113
  /** Make sure that effectiveType() is called on all members and items */
115
114
  function traverseElementEnvironments( art ) {
115
+ // TODO: we could leave out foreign keys (but they are traversed via forEachMember)
116
116
  let type = effectiveType( art );
117
117
  while (type?.items)
118
118
  type = effectiveType( type.items );
@@ -376,8 +376,7 @@ function populate( model ) {
376
376
  art.items = 0; // circular
377
377
  return true;
378
378
  }
379
- const ref = art.type || art.value || art.name;
380
- const location = ref && ref.location || art.location;
379
+ const location = weakRefLocation( art.type || art.value ) || weakLocation( art.location );
381
380
  art.items = { $inferred: 'expanded', location };
382
381
  setLink( art.items, '_outer', art );
383
382
  setLink( art.items, '_parent', art._parent );
@@ -403,10 +402,10 @@ function populate( model ) {
403
402
  return true;
404
403
  }
405
404
  const ref = art.type || art.value || art.name;
406
- const location = ref && ref.location || art.location;
405
+ const location = weakRefLocation( ref ) || weakLocation( art.location );
407
406
  // console.log( message( null, location, art, {target:struct,art}, 'Info','EXPAND-ELEM')
408
407
  // .toString(), Object.keys(struct.elements))
409
- proxyCopyMembers( art, 'elements', struct.elements, weakLocation( location ),
408
+ proxyCopyMembers( art, 'elements', struct.elements, location,
410
409
  null, isDeprecatedEnabled( options, 'noKeyPropagationWithExpansions' ) );
411
410
  // Set elements expansion status (the if condition is always true, as no
412
411
  // elements expansion will take place on artifact with existing other
@@ -421,8 +420,8 @@ function populate( model ) {
421
420
  if (art.enum)
422
421
  return false;
423
422
  const ref = art.type || art.value || art.name;
424
- const location = weakLocation( ref && ref.location || art.location );
425
- proxyCopyMembers( art, 'enum', origin.enum, weakLocation( location ) );
423
+ const location = weakRefLocation( ref ) || weakLocation( art.location );
424
+ proxyCopyMembers( art, 'enum', origin.enum, location );
426
425
  // Set elements expansion status (the if condition is always true, as no
427
426
  // elements expansion will take place on artifact with existing other
428
427
  // member property):
@@ -521,6 +520,9 @@ function populate( model ) {
521
520
  for (const id in (art.elements || art.enum)) {
522
521
  const ielem = art.elements ? art.elements[id] : art.enum[id]; // inferred element
523
522
  const selem = art.elements$ ? art.elements$[id] : art.enum$[id]; // specified element
523
+ // TODO: the positions are very strange, at least for enums
524
+ // see e.g. for test3/Queries/SpecifiedElements/SpecifiedElements.err.csn
525
+ // better to complain at the end position of the enum dict
524
526
  if (!selem) {
525
527
  info( 'query-missing-element', [ ielem.name.location, art ], {
526
528
  '#': ielem.kind === 'enum' ? 'enum' : 'std', id,
@@ -799,7 +801,9 @@ function populate( model ) {
799
801
  // TODO: disallow $self.elem.* and $self.*, toSelf.* (circular dependency)
800
802
  function expandWildcard( wildcard, siblingElements, colParent, query ) {
801
803
  const { elements } = query.items || query;
802
- let location = wildcard.location || query.from && query.from.location || query.location;
804
+ let location = wildcard.location ||
805
+ weakRefLocation( query.from ) ||
806
+ weakLocation( query.location );
803
807
  const inferred = query._main.$inferred;
804
808
  const excludingDict = (colParent || query).excludingDict || Object.create( null );
805
809
 
@@ -845,19 +849,24 @@ function populate( model ) {
845
849
  }
846
850
  else {
847
851
  location = weakLocation( location );
852
+ // Usually, the location of a `*`-inferred element is the location of the `*`.
853
+ // For inferred entities, it is the location of the corresponding source elem
854
+ // (from all generated entities, only auto-exposed are “wildcard projections”):
855
+ const elemLocation = !query._main.$inferred && location;
848
856
  const origin = envParent ? navElem : navElem._origin;
849
- const elem = linkToOrigin( origin, name, query, null, location );
857
+ const elem = linkToOrigin( origin, name, query, null, elemLocation );
850
858
  // TODO: check assocToMany { * }
851
859
  dictAdd( elements, name, elem, ( _name, loc ) => {
852
860
  // there can be a definition from a previous inline with the same name:
853
861
  error( 'duplicate-definition', [ loc, query ], { name, '#': 'element' } );
854
862
  } );
855
- elem.$inferred = '*';
856
- elem.name.$inferred = '*';
863
+ if (!query._main.$inferred || origin.$inferred)
864
+ elem.$inferred = '*';
865
+ elem.name.$inferred = '*'; // matters for A2J
857
866
  if (envParent)
858
867
  setWildcardExpandInline( elem, envParent, origin, name, location );
859
868
  else
860
- setElementOrigin( elem, navElem, name, location );
869
+ setElementOrigin( elem, navElem, name, elem.location );
861
870
  }
862
871
  }
863
872
  if (envParent || query.kind !== 'select') {
@@ -1293,12 +1302,12 @@ function populate( model ) {
1293
1302
  return target;
1294
1303
  }
1295
1304
  // console.log(absolute)
1296
- const { location } = target.name;
1297
- const from = augmentPath( location, target.name.id );
1305
+ const location = weakRefLocation( target.name );
1306
+ const from = { path: [ { id: target.name.id, location } ], location };
1298
1307
  let art = {
1299
1308
  kind: 'entity',
1300
1309
  name: { location, id: absolute },
1301
- location: target.location,
1310
+ location,
1302
1311
  query: { location, op: { val: 'SELECT', location }, from },
1303
1312
  $syntax: 'projection',
1304
1313
  $inferred: 'autoexposed',
@@ -1311,14 +1320,14 @@ function populate( model ) {
1311
1320
  if (target.params) {
1312
1321
  art.params = Object.create( null );
1313
1322
  // is art.query.from.path[0].$syntax: ':' required?
1314
- art.query.from.path[0].args = Object.create( null );
1323
+ from.path[0].args = Object.create( null );
1315
1324
  forEachGeneric( target, 'params', (p, pn) => {
1316
- art.params[pn] = linkToOrigin( p, pn, art, 'params', p.location );
1317
- art.query.from.path[0].args[pn] = {
1318
- name: { id: p.name.id, location: p.location },
1319
- location: p.location,
1325
+ art.params[pn] = linkToOrigin( p, pn, art, 'params' );
1326
+ from.path[0].args[pn] = {
1327
+ name: { id: p.name.id, location },
1328
+ location,
1320
1329
  scope: 'param',
1321
- path: [ { id: pn, location: p.location } ],
1330
+ path: [ { id: pn, location } ],
1322
1331
  };
1323
1332
  } );
1324
1333
  }
@@ -48,7 +48,7 @@ function propagate( model ) {
48
48
  virtual,
49
49
  notNull,
50
50
  targetElement: onlyViaParent, // in foreign keys
51
- value: onlyViaParent, // enum symbol value
51
+ value: onlyViaParent, // enum symbol value, calculated element
52
52
  // masked: special = done in definer
53
53
  // key: special = done in resolver
54
54
  // actions: struct includes & primary source = in definer/resolver
@@ -248,7 +248,10 @@ function propagate( model ) {
248
248
  for (const name in dict) {
249
249
  const member = linkToOrigin( dict[name], name, target, prop, location );
250
250
  member.$inferred = 'proxy';
251
- setEffectiveType( member, dict[name] );
251
+ if (prop === 'foreignKeys')
252
+ setLink( member, '_effectiveType', member );
253
+ else
254
+ setEffectiveType( member, dict[name] );
252
255
  }
253
256
  target[prop][$inferred] = 'prop';
254
257
  }
@@ -380,6 +383,7 @@ function checkAndSetStatus( art ) {
380
383
  }
381
384
 
382
385
  function setEffectiveType( target, source ) {
386
+ // TODO: when is this already set?
383
387
  if (source._effectiveType !== undefined)
384
388
  setLink( target, '_effectiveType', source._effectiveType );
385
389
  }
@@ -62,6 +62,8 @@ const {
62
62
  targetMaxNotOne,
63
63
  traverseQueryPost,
64
64
  linkToOrigin,
65
+ compositionTextVariant,
66
+ targetCantBeAspect,
65
67
  } = require('./utils');
66
68
 
67
69
  const detectCycles = require('./cycle-detector');
@@ -383,17 +385,31 @@ function resolve( model ) {
383
385
  }
384
386
  }
385
387
 
386
- if (art.targetAspect && !(allowedInMain && isTopLevelElement)) {
387
- message( 'type-managed-composition', [ art.targetAspect.location, art ],
388
- { '#': allowedInMain ? 'sub' : 'std' } );
389
- }
388
+ if (art.targetAspect && targetCantBeAspect( art, true )) {
389
+ // If not for anonymous aspect, this message can only occur for CSN input →
390
+ // we are more CSN specific (we could add more text variants, but this is
391
+ // CSN input with an undocumented CSN property…) For an anonymous target
392
+ // aspect, we could have more text variants, though…
393
+ const msg = art.targetAspect.elements
394
+ ? 'anonymous'
395
+ : (art.target || !art._parent?.query && art._parent?.kind !== 'event') && 'std';
396
+ error( 'type-unexpected-target-aspect', [ art.targetAspect.location, art ],
397
+ { '#': msg || 'target', prop: 'targetAspect', otherprop: 'target' },
398
+ {
399
+ std: 'Unexpected property $(PROP)',
400
+ anonymous: 'Unexpected anonymous target aspect',
401
+ target: 'Unexpected property $(PROP), adding property $(OTHERPROP) might help',
402
+ } );
403
+ } // TODO: else resolvePath() + test for cds.Composition?
404
+
390
405
  if (art.includes && !allowedInMain) {
406
+ // TODO: make this a check function for shared.js / or make it part of extend.js
391
407
  for (const include of art.includes) {
392
408
  const struct = include._artifact;
393
409
  if (struct && struct.kind !== 'type' && struct.elements &&
394
410
  Object.values( struct.elements ).some( e => e.targetAspect )) {
395
- message( 'type-managed-composition', [ include.location, art ],
396
- { '#': struct.kind, art: struct } );
411
+ error( 'type-managed-composition', [ include.location, art ],
412
+ { '#': struct.kind, art: struct } );
397
413
  }
398
414
  }
399
415
  }
@@ -425,16 +441,15 @@ function resolve( model ) {
425
441
  const elemtype = obj.type._artifact;
426
442
  if (elemtype && effectiveType( elemtype )) {
427
443
  const assocType = getAssocSpec( elemtype ) || {};
428
- if (assocType.on && !obj.on)
444
+ if ((assocType.on || assocType.$assocFilter) && !obj.on)
429
445
  obj.on = { $inferred: 'rewrite' }; // TODO: no extra rewrite here
430
446
  if (assocType.targetAspect) {
431
447
  error( 'composition-as-type-of', [ obj.type.location, art ], {},
432
448
  'A managed aspect composition element can\'t be used as type' );
433
449
  return;
434
450
  }
435
- else if (assocType.on) {
436
- error( 'assoc-as-type-of', [ obj.type.location, art ], {},
437
- 'An unmanaged association can\'t be used as type' );
451
+ else if (assocType.on || assocType.$assocFilter) {
452
+ error( 'type-unexpected-assoc', [ obj.type.location, art ] );
438
453
  return;
439
454
  }
440
455
 
@@ -505,6 +520,7 @@ function resolve( model ) {
505
520
  checkStructureCast( art );
506
521
  }
507
522
 
523
+ resolveExprInAnnotations( art );
508
524
  forEachMember( art, resolveRefs, art.targetAspect );
509
525
  // After the resolving of foreign keys (and adding implicit ones):
510
526
  if (obj.target?.$inferred === '')
@@ -831,10 +847,12 @@ function resolve( model ) {
831
847
  function getAssocSpec( type ) {
832
848
  let unmanaged = null;
833
849
  while (type) {
834
- if (type.on) // if unmanaged, continue trying to find targetAspect
850
+ if (type.on) // if unmanaged, continue trying to find targetAspect
835
851
  unmanaged = type;
836
852
  else if (type.foreignKeys || type.targetAspect)
837
853
  return type;
854
+ else if (type.value?.path?.[type.value.path.length - 1]?.where)
855
+ return { $assocFilter: true }; // filter -> always unmanaged
838
856
  type = getOrigin( type );
839
857
  }
840
858
  return unmanaged;
@@ -953,10 +971,8 @@ function resolve( model ) {
953
971
  // TODO: test of .items a bit unclear - we should somehow restrict the
954
972
  // use of unmanaged assocs in MANY, at least with $self
955
973
  // TODO: $self usage in anonymous aspects to be corrected in Core Compiler
956
- const isComposition = obj.type && obj.type.path && obj.type.path[0] &&
957
- obj.type.path[0].id === 'cds.Composition';
958
974
  message( 'assoc-as-type', [ obj.on.location, art ],
959
- { '#': isComposition ? 'comp' : 'std' }, {
975
+ { '#': compositionTextVariant( obj, 'comp' ) }, {
960
976
  std: 'An unmanaged association can\'t be defined as type',
961
977
  comp: 'An unmanaged composition can\'t be defined as type',
962
978
  } );
@@ -1073,9 +1089,9 @@ function resolve( model ) {
1073
1089
  obj.foreignKeys = Object.create( null );
1074
1090
  forEachInOrder( target, 'elements', ( elem, name ) => {
1075
1091
  if (elem.key && elem.key.val) {
1076
- const { location } = obj.target;
1092
+ const location = weakLocation( obj.target.location );
1077
1093
  const key = {
1078
- name: { location, id: elem.name.id, $inferred: 'keys' }, // more by setMemberParent()
1094
+ name: { location, id: elem.name.id, $inferred: 'keys' },
1079
1095
  kind: 'key',
1080
1096
  targetElement: { path: [ { id: elem.name.id, location } ], location },
1081
1097
  location,
@@ -1086,7 +1102,9 @@ function resolve( model ) {
1086
1102
  // the following should be done automatically, since we run resolveRefs after that
1087
1103
  setArtifactLink( key.targetElement, elem );
1088
1104
  setArtifactLink( key.targetElement.path[0], elem );
1089
- setLink( key, '_effectiveType', effectiveType( elem ) );
1105
+ // _origin/_effectiveType like we do in effectiveType() getOriginRaw():
1106
+ setLink( key, '_origin', '' );
1107
+ setLink( key, '_effectiveType', key );
1090
1108
  dependsOn( key, elem, location );
1091
1109
  // TODO TMP: instead, make managed composition of aspects and unmanaged
1092
1110
  // assocs not depend on their `on` condition (empty `_deps` after resolve)
@@ -1328,7 +1346,24 @@ function resolve( model ) {
1328
1346
  resolveTypeArgumentsUnchecked( art, typeArt, user );
1329
1347
  }
1330
1348
 
1349
+ function resolveExprInAnnotations( art ) {
1350
+ for (const anno in art) {
1351
+ if (anno.charAt(0) === '@')
1352
+ resolveAnnoExpr( art[anno], art );
1353
+ }
1354
+ }
1355
+
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 ) );
1363
+ }
1364
+
1331
1365
  function resolveExpr( expr, exprCtx, user ) {
1366
+ // console.log(expr?.location.line,exprCtx)
1332
1367
  traverseExpr( expr, exprCtx, user, (e, c, u) => resolveExprItem( e, c, u ) );
1333
1368
  }
1334
1369
 
@@ -13,7 +13,7 @@ const {
13
13
  pathName,
14
14
  userQuery,
15
15
  definedViaCdl,
16
- isDirectComposition,
16
+ targetCantBeAspect,
17
17
  pathStartsWithSelf,
18
18
  columnRefStartsWithSelf,
19
19
  isAssocToPrimaryKeys,
@@ -275,6 +275,28 @@ function fns( model ) {
275
275
  check: checkRefInQuery,
276
276
  param: paramSemantics,
277
277
  },
278
+ annotation: { // annotation assignments
279
+ lexical: justDollarSelf, // TODO: forbid $projection
280
+ dollar: true,
281
+ dynamic: parentElements,
282
+ navigation: assocOnNavigation,
283
+ noDep: true,
284
+ notFound: undefinedParentElement,
285
+ messageMap: {
286
+ 'ref-undefined-element': 'anno-undefined-element',
287
+ 'ref-undefined-param': 'anno-undefined-param',
288
+ },
289
+ // check: checkAssocOn,
290
+ param: paramSemantics,
291
+ nestedColumn: () => ({ // in expand and inline - TODO
292
+ lexical: justDollarSelf,
293
+ dollar: true,
294
+ dynamic: parentElements,
295
+ navigation: assocOnNavigation,
296
+ notFound: undefinedParentElement,
297
+ rewriteProjectionToSelf: true,
298
+ }),
299
+ },
278
300
  };
279
301
 
280
302
  Object.assign( model.$functions, {
@@ -482,7 +504,7 @@ function fns( model ) {
482
504
  if (user._pathHead && !semantics.isMainRef) // in expand/inline
483
505
  semantics = semantics.nestedColumn();
484
506
  if (typeof scope === 'string') { // typeOf, param, global
485
- semantics = semantics?.[scope] && semantics[scope]( ruser, path, location );
507
+ semantics = semantics?.[scope] && semantics[scope]( ruser, path, location, semantics );
486
508
  if (!semantics) {
487
509
  if (semantics == null)
488
510
  throw new CompilerAssertion( `Scope ${ scope } is not expected here` );
@@ -524,7 +546,7 @@ function fns( model ) {
524
546
  valid.push( model.$magicVariables.elements, removeDollarNames( dynamicDict ) );
525
547
  // TODO: streamline function arguments (probably: user, path, semantics )
526
548
  const undef = semantics.notFound( ruser, head, valid, dynamicDict,
527
- !isMainRef && user._user && user._artifact, path );
549
+ !isMainRef && user._user && user._artifact, path, semantics );
528
550
  return setArtifactLink( head, undef || null );
529
551
  }
530
552
 
@@ -572,7 +594,7 @@ function fns( model ) {
572
594
  // TODO: streamline function arguments (probably: user, path, semantics, prev )
573
595
  // false returned by semantics.navigation: no further error:
574
596
  if (env !== false)
575
- notFound( user, item, [ env ], null, prev, path );
597
+ notFound( user, item, [ env ], null, prev, path, semantics );
576
598
  return null;
577
599
  }
578
600
  // need to do that here, because we also need to disallow Service.AutoExposed:elem
@@ -699,9 +721,14 @@ function fns( model ) {
699
721
  return false;
700
722
  }
701
723
 
702
- function paramSemantics() {
703
- return { dynamic: artifactParams, notFound: undefinedParam };
724
+ function paramSemantics( _user, _path, _loction, semantics ) {
725
+ return {
726
+ messageMap: semantics.messageMap,
727
+ dynamic: artifactParams,
728
+ notFound: undefinedParam,
729
+ };
704
730
  }
731
+
705
732
  function paramUnsupported( user, _path, location ) {
706
733
  error( 'ref-unexpected-scope', [ location, user ], // TODO: ref-unexpected-param
707
734
  // why an extra text for calculated elements? or separate for all?
@@ -754,9 +781,17 @@ function fns( model ) {
754
781
  }
755
782
 
756
783
  function artifactParams( user ) {
757
- const lexical = (user._main || user).$tableAliases;
758
784
  // TODO: already report error here if no parameters?
759
- return lexical?.$parameters?.elements || Object.create( null );
785
+ return boundActionOrMain( user ).params || Object.create( null );
786
+ }
787
+
788
+ function boundActionOrMain( art ) {
789
+ while (art._main) {
790
+ if (art.kind === 'action' || art.kind === 'function')
791
+ return art;
792
+ art = art._parent;
793
+ }
794
+ return art;
760
795
  }
761
796
 
762
797
  function typeOfParentDict( user ) {
@@ -902,15 +937,16 @@ function fns( model ) {
902
937
  function undefinedForAnnotate( user, item, valid, _dict, prev ) {
903
938
  // in a CSN source, only one env was tested (valid.length 1):
904
939
  const art = (!prev) ? item.id : searchName( prev, item.id, 'absolute' );
905
- signalNotFound( (valid.length > 1 ? 'anno-undefined-art' : 'anno-undefined-def'),
940
+ signalNotFound( (valid.length > 1 ? 'ext-undefined-art' : 'ext-undefined-def'),
906
941
  // TODO: ext-undefined-xyz
907
942
  [ item.location, user ], valid, { art } );
908
943
  }
909
944
 
910
- function undefinedParam( user, head, valid ) {
911
- // TODO: text variant if there are no parameters, or in artifactParameters()?
945
+ function undefinedParam( user, head, valid, _dict, _art, _path, semantics ) {
946
+ // TODO: text variant if there are no parameters, or in artifactParameters()
947
+ // TODO: use prepared message variants
912
948
  signalNotFound( 'ref-undefined-param', [ head.location, user ], valid,
913
- { art: user._main || user, id: head.id } );
949
+ { art: boundActionOrMain( user ), id: head.id }, semantics );
914
950
  }
915
951
 
916
952
  function undefinedTargetElement( user, head, valid, _dict, pathItemArtifact ) {
@@ -953,7 +989,7 @@ function fns( model ) {
953
989
  }
954
990
  }
955
991
 
956
- function undefinedParentElement( user, head, valid, dynamicDict ) {
992
+ function undefinedParentElement( user, head, valid, dynamicDict, _art, _path, semantics ) {
957
993
  // TODO: we might mention both the "direct" and the "effective" type and
958
994
  // always just mentioned one identifier as not found
959
995
  const { id } = head;
@@ -969,7 +1005,7 @@ function fns( model ) {
969
1005
  const msgVar = userQuery( user ) ? 'query' : null;
970
1006
  // TODO: better with ON in expand if that is supported
971
1007
  signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
972
- { '#': msgVar, art: head.id } );
1008
+ { '#': msgVar, art: head.id }, semantics );
973
1009
  }
974
1010
  }
975
1011
 
@@ -994,18 +1030,21 @@ function fns( model ) {
994
1030
  return undefinedItemElement( user, head, valid, null, art, path );
995
1031
  }
996
1032
 
997
- function undefinedItemElement( user, item, valid, _dict, art, path ) {
1033
+ function undefinedItemElement( user, item, valid, _dict, art, path, semantics ) {
998
1034
  const query = userQuery( art );
999
1035
  if (query?.name?.id > 1) {
1000
- // TODO: 'The current query has no element $(MEMBER)' with $self.MEMBER
1001
- // and 'The sub query for alias $(ALIAS) has no element $(MEMBER)'
1002
- // both as text variants to ref-undefined-element
1003
- signalNotFound( 'query-undefined-element', [ item.location, user ],
1004
- valid, { id: item.id } );
1036
+ const root = userQuery( user ) !== query && path[0]._navigation;
1037
+ const alias = (root?.kind === '$navElement')
1038
+ ? root._parent
1039
+ : root?.kind === '$tableAlias' && root;
1040
+ // TODO: improve alias retrieval if inside expand/inline
1041
+ signalNotFound( 'ref-undefined-element', [ item.location, user ], valid,
1042
+ { '#': (alias ? 'alias' : 'query'), art: item.id, alias: alias?.name?.id },
1043
+ semantics );
1005
1044
  }
1006
1045
  else if (art.kind === '$parameters') {
1007
1046
  signalNotFound( 'ref-undefined-param', [ item.location, user ],
1008
- valid, { art: art._main, id: item.id } );
1047
+ valid, { art: art._main, id: item.id }, semantics );
1009
1048
  }
1010
1049
  else if (art.kind === 'builtin') { // magic variable / replacement variable
1011
1050
  // $magic.{ var } is a configurable error,
@@ -1015,27 +1054,24 @@ function fns( model ) {
1015
1054
  // eslint-disable-next-line no-cond-assign
1016
1055
  while ((head = head?._parent) && head.kind === 'builtin')
1017
1056
  id = `${ head.name.id }.${ id }`;
1018
- signalNotFound( ( art.$uncheckedElements ? 'ref-unknown-var' : 'ref-undefined-var'),
1019
- [ item.location, user ], valid, { id } );
1057
+ const msgId = (art.$uncheckedElements) ? 'ref-unknown-var' : 'ref-undefined-var';
1058
+ signalNotFound( msgId, [ item.location, user ], valid, { id }, semantics );
1020
1059
  }
1021
- else if (art.kind === 'aspect' && !art.name) { // anonymous target aspect
1022
- signalNotFound( 'ref-undefined-element', [ item.location, user ],
1023
- valid, { '#': 'aspect', id: item.id } );
1060
+ else if (art.kind === 'aspect' && !art.name) { // anonymous target aspect - TODO: still?
1061
+ signalNotFound( 'ref-undefined-element', [ item.location, user ], valid,
1062
+ { '#': 'aspect', id: item.id }, semantics );
1024
1063
  }
1025
1064
  else {
1026
1065
  const target = art._effectiveType?.target;
1027
1066
  if (target?._artifact) {
1028
1067
  signalNotFound( 'ref-undefined-element', [ item.location, user ], valid,
1029
- { '#': 'target', art: target, id: item.id } );
1068
+ { '#': 'target', art: target, id: item.id }, semantics );
1030
1069
  }
1031
1070
  else if (!target) {
1032
1071
  const variant = art.kind === 'aspect' && !art.name && 'aspect';
1033
- signalNotFound( 'ref-undefined-element', [ item.location, user ],
1034
- valid, {
1035
- '#': variant,
1036
- art: (variant ? '' : searchName( art, item.id, 'element' )),
1037
- id: item.id,
1038
- } );
1072
+ const a = (variant) ? '' : searchName( art, item.id, 'element' );
1073
+ signalNotFound( 'ref-undefined-element', [ item.location, user ], valid,
1074
+ { '#': variant, art: a, id: item.id }, semantics );
1039
1075
  }
1040
1076
  }
1041
1077
  return null;
@@ -1189,15 +1225,15 @@ function fns( model ) {
1189
1225
  return false;
1190
1226
  }
1191
1227
 
1192
- // Remember: an aspect should have already been moved to XSN targetAspect, but
1228
+ // Remember: a valid aspect should have already been moved to XSN targetAspect, but
1193
1229
  // the error messages should still talk about potential aspects
1194
1230
  function acceptEntity( art, user, ref ) { // for target
1195
1231
  if (art.kind === 'entity')
1196
1232
  return art;
1197
1233
  // Extra msg text with Composition of NeitherEntityNorAspect:
1198
1234
  const bare = !art.elements || art.elements[$inferred];
1199
- const std = (art.targetAspect || !isDirectComposition( user ) || user.kind === 'mixin');
1200
- const msg = std && 'std' || (bare && art.kind === 'aspect' ? 'bare' : 'composition');
1235
+ const std = targetCantBeAspect( user );
1236
+ const msg = std || (bare && art.kind === 'aspect' ? 'bare' : 'composition');
1201
1237
  signalNotFound( 'ref-invalid-target', [ ref.location, user ], null,
1202
1238
  { '#': msg } );
1203
1239
  return false;
@@ -1558,12 +1594,12 @@ function fns( model ) {
1558
1594
  * @param {object[]} valid
1559
1595
  * @param {object} [textParams]
1560
1596
  */
1561
- function signalNotFound( msgId, location, valid, textParams ) {
1597
+ function signalNotFound( msgId, location, valid, textParams, semantics ) {
1562
1598
  if (location.$notFound) // TODO: still necessary?
1563
1599
  return;
1564
1600
  location.$notFound = true;
1565
1601
  /** @type {object} */
1566
- const err = message( msgId, location, textParams );
1602
+ const err = message( semantics?.messageMap?.[msgId] || msgId, location, textParams );
1567
1603
  if (valid) {
1568
1604
  const user = Array.isArray( location ) && location[1];
1569
1605
  err.validNames = (user && definedViaCdl( user )); // viaCdl -> '.'?