@sap/cds-compiler 6.3.6 → 6.4.6

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 (62) hide show
  1. package/CHANGELOG.md +101 -3
  2. package/LICENSE +32 -0
  3. package/README.md +14 -2
  4. package/bin/cdsse.js +0 -3
  5. package/doc/CHANGELOG_BETA.md +1 -1
  6. package/doc/CHANGELOG_DEPRECATED.md +1 -1
  7. package/lib/base/message-registry.js +9 -2
  8. package/lib/base/messages.js +1 -1
  9. package/lib/base/model.js +2 -0
  10. package/lib/checks/existsExpressionsOnlyForeignKeys.js +16 -10
  11. package/lib/checks/existsMustEndInAssoc.js +1 -1
  12. package/lib/checks/existsMustNotStartWithDollarSelf.js +31 -0
  13. package/lib/checks/validator.js +4 -2
  14. package/lib/compiler/assert-consistency.js +3 -2
  15. package/lib/compiler/builtins.js +5 -6
  16. package/lib/compiler/checks.js +37 -26
  17. package/lib/compiler/define.js +1 -1
  18. package/lib/compiler/extend.js +39 -50
  19. package/lib/compiler/finalize-parse-cdl.js +1 -1
  20. package/lib/compiler/lsp-api.js +1 -1
  21. package/lib/compiler/populate.js +2 -2
  22. package/lib/compiler/propagator.js +29 -6
  23. package/lib/compiler/resolve.js +13 -3
  24. package/lib/compiler/shared.js +157 -133
  25. package/lib/compiler/tweak-assocs.js +87 -29
  26. package/lib/compiler/xpr-rewrite.js +164 -160
  27. package/lib/edm/annotations/edmJson.js +206 -37
  28. package/lib/edm/csn2edm.js +13 -0
  29. package/lib/edm/edmUtils.js +2 -2
  30. package/lib/gen/BaseParser.js +106 -72
  31. package/lib/gen/CdlGrammar.checksum +1 -1
  32. package/lib/gen/CdlParser.js +1501 -1509
  33. package/lib/json/to-csn.js +8 -5
  34. package/lib/language/genericAntlrParser.js +0 -0
  35. package/lib/main.js +19 -16
  36. package/lib/model/csnRefs.js +589 -521
  37. package/lib/model/csnUtils.js +8 -5
  38. package/lib/model/enrichCsn.js +1 -0
  39. package/lib/parsers/AstBuildingParser.js +73 -28
  40. package/lib/render/toCdl.js +2 -1
  41. package/lib/render/toHdbcds.js +6 -3
  42. package/lib/render/toSql.js +5 -0
  43. package/lib/transform/db/applyTransformations.js +1 -1
  44. package/lib/transform/db/assertUnique.js +4 -1
  45. package/lib/transform/db/assocsToQueries/transformExists.js +3 -10
  46. package/lib/transform/db/assocsToQueries/utils.js +0 -5
  47. package/lib/transform/db/cdsPersistence.js +17 -18
  48. package/lib/transform/db/expansion.js +179 -3
  49. package/lib/transform/db/flattening.js +16 -5
  50. package/lib/transform/db/rewriteCalculatedElements.js +79 -283
  51. package/lib/transform/effective/main.js +8 -1
  52. package/lib/transform/forOdata.js +1 -1
  53. package/lib/transform/forRelationalDB.js +21 -80
  54. package/lib/transform/localized.js +75 -127
  55. package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +89 -63
  56. package/lib/transform/transformUtils.js +23 -21
  57. package/lib/transform/translateAssocsToJoins.js +7 -5
  58. package/lib/transform/tupleExpansion.js +16 -3
  59. package/package.json +3 -3
  60. package/doc/DeprecatedOptions_v2.md +0 -150
  61. package/doc/NameResolution.md +0 -837
  62. package/lib/transform/parseExpr.js +0 -415
@@ -72,10 +72,10 @@
72
72
  // the path up to the last path step in the type-of.
73
73
  // - an element definition, `$self.sub.elem` needs to be replaced by the element name.
74
74
  // Paths without `$self` on the "type-of element" itself need to have
75
- // the first path step be replaced by the target element name.
75
+ // the first path step be replaced by the destination element name.
76
76
  // - a parameter, `$self.sub.elem` needs to be replaced by the parameter name.
77
77
  // Paths without `$self` on the "type-of element" itself need to have
78
- // the first path step be replaced by the target parameter name.
78
+ // the first path step be replaced by the destination parameter name.
79
79
  // - a return parameter, `$self` needs to be rejected, because there is no way
80
80
  // to refer to `returns`. Paths without `$self` on the "type-of element" itself
81
81
  // (not sub-elements), need to be rejected as well.
@@ -108,7 +108,7 @@
108
108
  // was projected or if a prefix was projected (e.g. for structures or associations).
109
109
  // The same rules as for ON-condition rewriting apply.
110
110
  //
111
- // Furthermore, as the target is a select item, and this select item belongs to a table
111
+ // Furthermore, as the destination is a select item, and this select item belongs to a table
112
112
  // alias, we should rewrite all annotation paths only to projected elements of that
113
113
  // table alias. Cross-rewriting between table aliases should not be done.
114
114
  // This is the same we do for association rewriting.
@@ -157,14 +157,13 @@ const { isSimpleCdlIdentifier } = require('../parsers/identifiers');
157
157
  // Config object passed around all "rewrite" functions.
158
158
  class AnnoRewriteConfig {
159
159
  anno;
160
- target;
161
- targetRoot;
160
+ destination;
161
+ destinationRoot;
162
162
  origin;
163
- fromTargetType;
163
+ fromDestinationType;
164
164
  fromCalcElement;
165
165
  expandedRoot;
166
166
  expandedRootType;
167
- isInFilter;
168
167
  tokenExpr;
169
168
  }
170
169
 
@@ -181,71 +180,87 @@ function xprRewriteFns( model ) {
181
180
 
182
181
  return {
183
182
  rewriteAnnotationsRefs,
183
+ rewriteRefsInExpression,
184
184
  };
185
185
 
186
186
  /**
187
+ * Report anno-missing-rewrite if rewrite for annotations.
188
+ * Returns traverseExpr.STOP.
189
+ *
187
190
  * @param expr
188
191
  * @param {AnnoRewriteConfig} config
189
192
  * @param {string} [variant]
190
193
  */
191
194
  function reportAnnoRewriteError( expr, config, variant = 'std' ) {
192
- return error('anno-missing-rewrite', [
193
- weakLocation( config.target.location ), config.target,
194
- ], {
195
- '#': variant,
196
- anno: config.anno,
197
- art: config.origin,
198
- elemref: expr,
199
- });
195
+ if (config.tokenExpr) {
196
+ error( 'anno-missing-rewrite', [
197
+ weakLocation( config.destination.location ), config.destination,
198
+ ], {
199
+ '#': variant,
200
+ anno: config.anno,
201
+ art: config.origin,
202
+ elemref: expr,
203
+ });
204
+ }
205
+ return traverseExpr.STOP;
200
206
  }
201
207
 
202
208
  /**
203
- * Rewrite the propagated annotation relative to the target.
209
+ * Rewrite the propagated annotation relative to the destination.
204
210
  *
205
- * @param {XSN.Artifact} target
211
+ * @param {XSN.Artifact} destination
206
212
  * @param {XSN.Artifact} origin
207
213
  * @param {string} annoName
208
214
  */
209
- function rewriteAnnotationsRefs( target, origin, annoName ) {
215
+ function rewriteAnnotationsRefs( destination, origin, annoName ) {
210
216
  // Make sure not to waste time if no inherited annotation has references:
211
217
  if (!origin?.$contains?.$annotation?.$path)
212
218
  return;
213
219
 
214
- const anno = target[annoName];
220
+ const anno = destination[annoName];
215
221
  // only annotations with expressions have a kind
216
222
  // also, don't report errors twice
217
223
  if (!anno.kind || anno.$invalidPaths)
218
224
  return;
219
225
 
220
- // Annotation comes from the target's type. That's important to know, because
226
+ const hasError = rewriteRefsInExpression( destination, origin, annoName );
227
+ destination.$contains ??= {};
228
+ destination.$contains.$annotation ??= {};
229
+ destination.$contains.$annotation.$path ||= origin.$contains.$annotation.$path;
230
+ destination.$contains.$annotation.$self ||= origin.$contains.$annotation.$self;
231
+ if (hasError)
232
+ anno.$invalidPaths = true; // avoid subsequent errors
233
+ }
234
+
235
+ function rewriteRefsInExpression( destination, origin, propName ) {
236
+ // Annotation comes from the destination's type. That's important to know, because
221
237
  // path prefixes need to be adapted.
222
- const fromTargetType = target.type?._artifact === origin;
223
- // Annotation comes from the target's calculated element. A special case propagation rule, e.g
238
+ const fromDestinationType = destination.type?._artifact === origin;
239
+ // Annotation comes from the destination's calculated element.
240
+ // A special case propagation rule, e.g
224
241
  // for `calcString: String = str;`. We also need to adapt path prefixes.
225
- const fromCalcElement = !fromTargetType && target.$calcDepElement &&
226
- target.value?._artifact === origin;
242
+ const fromCalcElement = !fromDestinationType && destination.$calcDepElement &&
243
+ destination.value?._artifact === origin;
227
244
 
228
- const { expandedRoot, expandedRootType } = !fromTargetType && getExpandRoot( target ) || {};
245
+ const { expandedRoot, expandedRootType }
246
+ = !fromDestinationType && getExpandRoot( destination ) || {};
229
247
 
230
248
  const config = {
231
249
  __proto__: AnnoRewriteConfig.prototype,
232
- anno: annoName,
233
- target,
234
- targetRoot: annoRootArt( target ),
250
+ anno: propName,
251
+ destination,
252
+ destinationRoot: annoRootArt( destination ),
235
253
  origin,
236
- fromTargetType,
254
+ fromDestinationType,
237
255
  fromCalcElement,
238
256
  expandedRoot,
239
257
  expandedRootType,
240
258
  };
241
259
 
242
- const hasError = rewriteAnnotationExpr( target[annoName], config );
243
- target.$contains ??= {};
244
- target.$contains.$annotation ??= {};
245
- target.$contains.$annotation.$path ||= origin.$contains.$annotation.$path;
246
- target.$contains.$annotation.$self ||= origin.$contains.$annotation.$self;
247
- if (hasError)
248
- anno.$invalidPaths = true; // avoid subsequent errors
260
+ if (propName.charAt( 0 ) === '@')
261
+ return rewriteAnnotationExpr( destination[propName], config );
262
+ return traverseExpr( destination[propName], 'annoRewrite', destination,
263
+ rewriteAnnoExpr.bind( config ) );
249
264
  }
250
265
 
251
266
  /**
@@ -264,36 +279,37 @@ function xprRewriteFns( model ) {
264
279
  else if (expr.$tokenTexts) {
265
280
  // used to set `$tokenText` to true in case of rewritten annotation
266
281
  config.tokenExpr = expr;
267
- return traverseExpr.STOP === traverseExpr(
268
- expr, 'annoRewrite', config.target,
269
- // eslint-disable-next-line @stylistic/max-len, @stylistic/function-paren-newline
270
- (e, refCtx) => (rewriteAnnoExpr( e, config, refCtx ) ? traverseExpr.STOP : traverseExpr.SKIP) );
282
+ return traverseExpr( expr, 'annoRewrite', config.destination,
283
+ rewriteAnnoExpr.bind( config ) );
271
284
  }
272
285
  return false;
273
286
  }
274
287
 
275
288
  /**
276
- * @param {XSN.Expression} expr
277
- * @param {AnnoRewriteConfig} config
278
- * @param {string} refCtx
279
- * @returns {null|true} Returns true if the expression couldn't be rewritten.
289
+ * Rewrite the annotation expression `expr`.
290
+ * Returns traverseExpr.STOP if the expression couldn't be rewritten.
280
291
  */
281
- function rewriteAnnoExpr( expr, config, refCtx ) {
292
+ function rewriteAnnoExpr( expr, refCtx, user ) {
293
+ const config = this; // we use rewriteAnnoExpr.bind( config )
282
294
  const root = expr.path && (expr.path[0]?._navigation || expr.path[0]?._artifact);
283
295
  if (!root || !expr._artifact)
284
296
  return null; // invalid path
285
297
 
286
- const { target } = config;
298
+ if (refCtx === 'filter')
299
+ return rewriteGenericAnnoPath( expr, refCtx, config, user );
287
300
 
288
301
  // Report obsolete $parameters; parameters on non-actions not supported, yet.
289
- if (root.kind === '$parameters' || (root.kind === 'param' && root._parent.kind !== 'action' &&
290
- root._parent.kind !== 'function'))
302
+ if (root.kind === '$parameters' ||
303
+ (root.kind === 'param' && root._parent.kind !== 'action' &&
304
+ root._parent.kind !== 'function'))
291
305
  return reportAnnoRewriteError( expr, config, 'unsupported' );
292
306
 
293
307
  if (root.kind === 'key') {
294
- // Foreign keys can't be renamed and since we don't have absolute references to foreign keys,
295
- // i.e. `$self.assoc.target_id` always refers to the target side, we don't have to rewrite
296
- // them.
308
+ // References starting with a foreign key name (only possible in an
309
+ // annotation on a foreign key) have length 1 + the foreign key cannot be
310
+ // renamed → nothing to do. Otherwise, foreign keys cannot be directly
311
+ // addressed, except via `annotate`. (Even as annotation value for
312
+ // foreign keys, `$self.assoc.target_id` always refers to the target side.)
297
313
  return null;
298
314
  }
299
315
 
@@ -303,37 +319,15 @@ function xprRewriteFns( model ) {
303
319
  return null;
304
320
 
305
321
  let hasError = false;
306
- if (config.fromTargetType || config.fromCalcElement)
307
- hasError = adaptPathPrefixViaType( expr, config );
322
+ if (config.fromDestinationType || config.fromCalcElement)
323
+ hasError = adaptPathPrefixViaType( expr, config, user );
308
324
  else if (config.expandedRoot)
309
- hasError = adaptPathPrefixViaTypeExpansion( expr, config );
325
+ hasError = adaptPathPrefixViaTypeExpansion( expr, config, user );
310
326
 
311
- hasError ||= rewriteGenericAnnoPath( expr, config, refCtx );
327
+ hasError ||= rewriteGenericAnnoPath( expr, refCtx, config, user );
312
328
 
313
329
  if (hasError)
314
- return true;
315
-
316
- // TODO: Remove extra loop once filter traversal is added to traverseExpr (#12068)
317
- for (const step of expr.path) {
318
- if (step?._artifact && step.where && !Array.isArray( step._artifact ) ) {
319
- // We must not prefix `$`-renamed variables with `$self`, as it would
320
- // change meaning, see (#11775). Also, the path's target changes.
321
- const assocTarget = step._artifact.target._artifact;
322
- if (target) {
323
- const filterConfig = { ...config, target: assocTarget, isInFilter: true };
324
- if (traverseExpr.STOP === traverseExpr(
325
- step.where, 'filter', step,
326
- // eslint-disable-next-line @stylistic/max-len
327
- (e, ctx) => expr.path && (rewriteGenericAnnoPath( e, filterConfig, ctx ) ? traverseExpr.STOP : traverseExpr.SKIP)
328
- ))
329
- return true;
330
- }
331
- else {
332
- // can't happen: rejected earlier by compiler
333
- return reportAnnoRewriteError( expr, config, 'unsupported' );
334
- }
335
- }
336
- }
330
+ return traverseExpr.STOP;
337
331
 
338
332
  if (expr.$tokenTexts === true) {
339
333
  // TODO: do not do with Universal-CSN (and gensrc, but that does not matter)
@@ -341,53 +335,53 @@ function xprRewriteFns( model ) {
341
335
  // only understand the string representation. But we only do so for simple strings, which
342
336
  // is the case if this path expression has $tokenTexts. It then is of the form `@a: (path)`.
343
337
  const isSimpleStep = step => !step.where && !step.args && isSimpleCdlIdentifier( step.id );
344
- if (expr.path.every(isSimpleStep))
345
- expr.$tokenTexts = expr.path.map( step => step.id ).join('.');
338
+ if (expr.path.every(isSimpleStep)) {
339
+ expr.$tokenTexts = (expr.scope === 'param' ? ':' : '') +
340
+ expr.path.map( step => step.id ).join('.');
341
+ }
346
342
  }
347
343
 
348
344
  if (model.options.testMode) {
349
345
  // re-resolve the modified path; all paths steps must match what we rewrote
350
346
  const ref = { ...expr, path: [ ...expr.path.map(item => ({ ...item })) ] };
351
- if (!resolvePath( ref, refCtx, target ))
347
+ if (!resolvePath( ref, refCtx, user ))
352
348
  throw new CompilerAssertion(`rewritten anno path must be resolvable: ${ JSON.stringify(ref.path) }`);
353
-
354
- for (let i = 0; i < ref.path.length; ++i) {
355
- const actual = ref.path[i];
356
- const expected = ref.path[i];
357
- if (actual._artifact !== expected._artifact) {
358
- throw new CompilerAssertion(`rewritten anno path contains incorrect artifact links: ${
359
- JSON.stringify(ref.path) }; step ${ i }`);
360
- }
361
- else if (actual._navigation !== undefined && actual._navigation !== expected._navigation) {
362
- throw new CompilerAssertion(`rewritten anno path contains incorrect navigation links: ${
363
- JSON.stringify(ref.path) }; step ${ i }`);
364
- }
365
- }
366
349
  }
367
-
368
350
  return false;
369
351
  }
370
352
 
371
353
  /**
372
- * @param {XSN.Expression} expr
373
- * @param {AnnoRewriteConfig} config
374
- * @returns {*}
354
+ * Return the destination node of the expr rewrite (stored in param `config`), or
355
+ * if we are in a filter expression, the association target of the corresponding
356
+ * path item (provided via param `user`).
357
+ *
358
+ * TODO: in the end, we should directly use the standard expression parameter
359
+ * `user` in all functions below. Probably if we unify this module with
360
+ * rewriteCondition() of tweak-assocs.js.
375
361
  */
376
- function getRootEnv( expr, config ) {
377
- const { target } = config;
362
+ function destinationOrAssocTarget( config, user ) {
363
+ return (user.id && user.where)
364
+ ? user._artifact.target._artifact
365
+ : config.destination;
366
+ }
378
367
 
368
+ /**
369
+ * TODO: what exactly is a root environment?
370
+ */
371
+ function getRootEnv( expr, config, destination ) {
379
372
  if (expr.scope === 'param') // path is absolute
380
- return navigationEnv( config.targetRoot, null, null, 'nav' );
373
+ return navigationEnv( config.destinationRoot, null, null, 'nav' );
381
374
 
382
375
  // On select items, use navigation elements or table alias
383
376
  // TODO: Expand/inline paths don't have a `_navigation` property on their last
384
377
  // path step, yet. We need to implement expand/inline.
385
- const isSimpleSelectItem = target.value?.path && target._main?.query && !target._columnParent;
378
+ const isSimpleSelectItem = destination.value?.path && destination._main?.query &&
379
+ !destination._columnParent;
386
380
  if (isSimpleSelectItem) {
387
381
  const isSelfPath = (expr.path[0]?._navigation?.kind === '$self');
388
382
  if (isSelfPath) {
389
383
  // Path is absolute, use table alias to resolve it.
390
- let tableAlias = target.value.path[0]._navigation;
384
+ let tableAlias = destination.value.path[0]._navigation;
391
385
  while (tableAlias && tableAlias.kind === '$navElement')
392
386
  tableAlias = tableAlias._parent;
393
387
  if (tableAlias)
@@ -395,37 +389,35 @@ function xprRewriteFns( model ) {
395
389
  }
396
390
  else {
397
391
  // Path is relative
398
- const nav = target.value.path[target.value.path.length - 1]._navigation?._parent;
392
+ const nav = destination.value.path[destination.value.path.length - 1]._navigation?._parent;
399
393
  if (nav)
400
394
  return nav;
401
395
  }
402
396
  }
403
397
 
404
398
  if (isSimpleSelectItem && model.options.testMode)
405
- throw new CompilerAssertion(`select item has no table alias: ${ JSON.stringify(target.value.path) }`);
399
+ throw new CompilerAssertion(`select item has no table alias: ${ JSON.stringify(destination.value.path) }`);
406
400
 
407
401
  if (isAnnoPathAbsolute( expr ))
408
- return navigationEnv( config.targetRoot, null, null, 'nav' );
402
+ return navigationEnv( config.destinationRoot, null, null, 'nav' );
409
403
 
410
404
  // anno path is relative / element reference (others were already rejected)
411
- // if the target is a root artifact, use it. Otherwise, use its parent.
412
- return navigationEnv( isAnnoRootArt( target ) ? target : target._parent, null, null, 'nav' );
405
+ // if the destination is a root artifact, use it. Otherwise, use its parent.
406
+ return navigationEnv( isAnnoRootArt( destination ) ? destination : destination._parent,
407
+ null, null, 'nav' );
413
408
  }
414
409
 
415
- /**
416
- * @param {XSN.Expression} expr
417
- * @param {AnnoRewriteConfig} config
418
- * @param {string} refCtx
419
- * @returns {boolean}
420
- */
421
- function rewriteGenericAnnoPath( expr, config, refCtx ) {
410
+ function rewriteGenericAnnoPath( expr, refCtx, config, user ) {
411
+ if (expr._artifact?.kind === 'builtin')
412
+ return false;
413
+ const destination = destinationOrAssocTarget( config, user );
422
414
  const isAbsolute = isAnnoPathAbsolute( expr );
423
415
  const startIndex = isAbsolute ? 1 : 0;
424
416
 
425
417
  // We get the root environment now, even though below we resolve the root item
426
418
  // again if it was absolute (e.g. $self). We do so, because for queries, we
427
419
  // want to respect the select item's corresponding table alias.
428
- const rootEnv = getRootEnv( expr, config );
420
+ const rootEnv = getRootEnv( expr, config, destination );
429
421
 
430
422
  // reset artifact link; we'll set it again if there are no errors
431
423
  setArtifactLink( expr, null );
@@ -437,7 +429,7 @@ function xprRewriteFns( model ) {
437
429
  delete expr.path[0]._navigation;
438
430
  // TODO: What about `up_`? Shouldn't we set `_navigation` as well?
439
431
  // TODO: Can we handle `$self` of anonymous-composition-of-aspect?
440
- const root = resolvePathRoot( expr, refCtx, config.target );
432
+ const root = resolvePathRoot( expr, refCtx, destination );
441
433
  if (!root)
442
434
  return reportAnnoRewriteError( expr, config );
443
435
  }
@@ -454,8 +446,10 @@ function xprRewriteFns( model ) {
454
446
 
455
447
  for (let i = startIndex; i < expr.path.length; ++i) {
456
448
  if (i > startIndex && art.target) {
457
- // if the current artifact is an association, we need to respect the redirection
458
- // chain from original target to new one.
449
+ // If the current artifact is an association, we need to respect the redirection
450
+ // chain from original target to new one. We need to use '_originalArtifact' due
451
+ // to secondary associations and their redirection chains. See comment in
452
+ // test3/Redirections/SecondaryAssocs/RedirectedPathRewriteOne.cds
459
453
  // FIXME: Won't work with associations in projected structures.
460
454
  const origTarget = expr.path[i - 1]?._originalArtifact?.target?._artifact;
461
455
  const chain = cachedRedirectionChain( art, origTarget );
@@ -476,48 +470,50 @@ function xprRewriteFns( model ) {
476
470
  setArtifactLink( expr, art );
477
471
 
478
472
  if (startIndex === 0 && expr.path[0].id.startsWith('$')) {
479
- if (config.isInFilter) {
473
+ if (refCtx === 'filter') {
480
474
  // In filters, we must not prepend `$self`, as that would change its meaning.
481
475
  // We must reject it. See #11775
482
476
  return reportAnnoRewriteError( expr, config );
483
477
  }
484
478
  // After rewriting, if an element starts with `$` -> add root prefix
485
- prependRootPath( config.origin, config.targetRoot, expr );
479
+ prependRootPath( config.origin, config.destinationRoot, expr );
486
480
  }
487
481
 
488
482
  return false;
489
483
  }
490
484
 
491
485
  /**
492
- * Rewrite an expression that came via type propagation.
486
+ * Rewrite the initial part of a path in an expression that came via type
487
+ * propagation or via propagation with ref-only calculated element.
493
488
  *
494
- * @returns {boolean} Returns the expression if it couldn't be rewritten.
489
+ * @returns {boolean} Returns true if it couldn't be rewritten.
495
490
  */
496
- function adaptPathPrefixViaType( expr, config ) {
497
- const { target, origin } = config;
498
- if (!target._main && !origin._main)
491
+ function adaptPathPrefixViaType( expr, config, user ) {
492
+ const { destination, origin } = config;
493
+ if (!destination._main && !origin._main)
499
494
  return false; // no need to rewrite; both are top-level
500
495
 
501
- if (rejectOuterReference( expr, origin, config ))
496
+ if (rejectOuterReference( expr, origin, config, user ))
502
497
  return true;
503
498
 
504
499
  // $self-paths via types from/to non-main artifacts always need to be rewritten.
505
- config.tokenExpr.$tokenTexts = true;
500
+ if (config.tokenExpr)
501
+ config.tokenExpr.$tokenTexts = true;
506
502
 
507
503
  const wasAbsolute = isAnnoPathAbsolute( expr );
508
504
  stripAbsolutePathPrefix( expr, origin );
509
505
 
510
506
  if (wasAbsolute) {
511
- prependRootPath( origin, target, expr );
507
+ prependRootPath( origin, destination, expr );
512
508
  }
513
- else if (!isAnnoRootArt( target )) { // target is element
514
- const item = { id: target.name.id };
515
- setArtifactLink( item, target );
509
+ else if (!isAnnoRootArt( destination )) { // destination is element
510
+ const item = { id: destination.name.id };
511
+ setArtifactLink( item, destination );
516
512
  prependToStrippedPath( origin, expr, [ item ] );
517
513
  }
518
- else if (target.kind === 'param') {
514
+ else if (destination.kind === 'param') {
519
515
  // annotations on parameters need a `:prefix`
520
- prependRootPath( origin, target, expr );
516
+ prependRootPath( origin, destination, expr );
521
517
  }
522
518
  else {
523
519
  prependToStrippedPath( origin, expr, [ ] );
@@ -526,7 +522,16 @@ function xprRewriteFns( model ) {
526
522
  return false;
527
523
  }
528
524
 
529
- function adaptPathPrefixViaTypeExpansion( expr, config ) {
525
+ /**
526
+ * Rewrite the initial part of a path in an expression starting with $self on an
527
+ * expanded element:
528
+ *
529
+ * - nothing to do when not starting with $self (TODO: param?)
530
+ * - otherwise error if options.beta.rewriteAnnotationExpressionsViaType is not set
531
+ *
532
+ * @returns {boolean} Returns true if it couldn't be rewritten.
533
+ */
534
+ function adaptPathPrefixViaTypeExpansion( expr, config, user ) {
530
535
  const root = expr.path[0]?._navigation;
531
536
  if (root?.kind !== '$self') {
532
537
  // non-self paths are always valid in expanded artifacts
@@ -539,16 +544,17 @@ function xprRewriteFns( model ) {
539
544
  if (!isBetaEnabled( model.options, 'rewriteAnnotationExpressionsViaType' ))
540
545
  return reportAnnoRewriteError( expr, config, 'unsupported' );
541
546
 
542
- if (rejectOuterReference( expr, config.expandedRootType, config ))
547
+ if (rejectOuterReference( expr, config.expandedRootType, config, user ))
543
548
  return true;
544
549
 
545
550
  stripAbsolutePathPrefix( expr, config.expandedRootType );
546
551
  prependRootPath( config.expandedRootType, config.expandedRoot, expr );
547
- setExpandStatusAnnotate( config.target, 'annotate' );
552
+ setExpandStatusAnnotate( config.destination, 'annotate' );
548
553
 
549
- config.target[config.anno].$inferred = 'anno-rewrite';
554
+ config.destination[config.anno].$inferred = 'anno-rewrite';
550
555
  // $self-paths via type expansion always need to be rewritten.
551
- config.tokenExpr.$tokenTexts = true;
556
+ if (config.tokenExpr)
557
+ config.tokenExpr.$tokenTexts = true;
552
558
 
553
559
  return false;
554
560
  }
@@ -586,36 +592,33 @@ function xprRewriteFns( model ) {
586
592
  if (relativeRoot >= 1)
587
593
  expr.path = expr.path.slice(relativeRoot);
588
594
  else if (relativeRoot === -1)
589
- throw new CompilerAssertion('Error while rewriting annotation');
595
+ throw new CompilerAssertion( 'Error while rewriting annotation' );
590
596
  }
591
597
 
592
598
  /**
593
- * Returns false if the path can be propagated to the target without referring
594
- * to any "outer" elements. It differentiates between the target being a main
599
+ * Returns false if the path can be propagated to the destination without referring
600
+ * to any "outer" elements. It differentiates between the destination being a main
595
601
  * artifact and elements, because an element annotation referring to itself can't
596
602
  * be propagated to a type:
597
603
  *
598
604
  * type T1 : { @a: (elem) elem: String; };
599
605
  * type T2 : T1:elem; // invalid
600
606
  *
601
- * Also considers other targets such as `returns`, etc.
602
- *
603
- * @param {XSN.Expression} expr
604
- * @param {XSN.Artifact} origin
605
- * @param {AnnoRewriteConfig} config
606
- * @returns {boolean}
607
+ * Also considers other destinations such as `returns`, etc.
607
608
  */
608
- function rejectOuterReference( expr, origin, config ) {
609
+ function rejectOuterReference( expr, origin, config, user ) {
609
610
  if (!isAnnoPathAbsolute( expr ) && !origin._main)
610
611
  return false;
611
612
 
612
613
  const root = expr.path[0]?._navigation;
613
614
  const found = findRelativeRoot( expr, origin );
615
+ const destination = destinationOrAssocTarget( config, user );
614
616
  const isInvalid = (found === -1) ||
615
617
  // Can't use paths with `$self` in `returns`.
616
- (root?.kind === '$self' && isReturnParam( config.targetRoot )) ||
618
+ (root?.kind === '$self' && isReturnParam( config.destinationRoot )) ||
617
619
  // siblings are allowed for non-main artifacts, except for 'returns'
618
- (!config.target._main || isReturnParam( config.target )) && (expr.path.length - found) <= 1;
620
+ (!destination._main || isReturnParam( destination )) &&
621
+ (expr.path.length - found) <= 1;
619
622
 
620
623
  if (isInvalid)
621
624
  return reportAnnoRewriteError( expr, config );
@@ -632,7 +635,7 @@ function xprRewriteFns( model ) {
632
635
  * @returns {number}
633
636
  */
634
637
  function findRelativeRoot( expr, origin ) {
635
- if (!origin._main) // main artifacts can't have outer references
638
+ if (!origin._main) // main artifacts can't have outer references: $self → 0, -1 otherwise
636
639
  return expr.path[0]?._artifact === origin ? 0 : -1;
637
640
 
638
641
  const { path } = expr;
@@ -689,7 +692,7 @@ function xprRewriteFns( model ) {
689
692
  * @returns {*|null}
690
693
  */
691
694
  function rewriteItem( expr, config, env, index ) {
692
- const rewriteTarget = findRewriteTarget( expr, index, env, config.target );
695
+ const rewriteTarget = findRewriteTarget( expr, index, env, config.destination );
693
696
  const found = setArtifactLink( expr.path[index], rewriteTarget[0] );
694
697
  if (!found)
695
698
  return null;
@@ -703,7 +706,8 @@ function xprRewriteFns( model ) {
703
706
  const item = expr.path[index];
704
707
  if (item.id !== found.name.id || (rewriteTarget[1] - index) !== 0) {
705
708
  // Path was rewritten; original token text string is no longer accurate
706
- config.tokenExpr.$tokenTexts = true;
709
+ if (config.tokenExpr)
710
+ config.tokenExpr.$tokenTexts = true;
707
711
  item.id = found.name.id;
708
712
  }
709
713
 
@@ -752,20 +756,20 @@ function isReturnParam( art ) {
752
756
  }
753
757
 
754
758
  /**
755
- * Gets the artifact (e.g. element) that was expanded. `target` is a sub-artifact of that root and
756
- * is an expanded element.
759
+ * Gets the artifact (e.g. element) that was expanded.
760
+ * `destination` is a sub-artifact of that root and is an expanded element.
757
761
  *
758
762
  * - expandedRoot: Top-most structure that was expanded.
759
763
  * - expandedRootType: The type of expandedRoot.
760
764
  *
761
- * @param {XSN.Artifact} target
765
+ * @param {XSN.Artifact} destination
762
766
  * @returns { {expandedRoot: XSN.Artifact, expandedRootType: XSN.Artifact}}
763
767
  */
764
- function getExpandRoot( target ) {
765
- if (target.$inferred !== 'expanded' && target.$inferred !== 'rewrite')
768
+ function getExpandRoot( destination ) {
769
+ if (destination.$inferred !== 'expanded' && destination.$inferred !== 'rewrite')
766
770
  return { expandedRoot: null, expandedRootType: null };
767
771
 
768
- let expandedRoot = target;
772
+ let expandedRoot = destination;
769
773
 
770
774
  // 'expanded' for structures, 'rewrite' for foreign keys
771
775
  while (expandedRoot.$inferred === 'expanded' || expandedRoot.$inferred === 'rewrite')