@sap/cds-compiler 4.8.0 → 4.9.2

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 (95) hide show
  1. package/CHANGELOG.md +38 -4
  2. package/bin/cds_remove_invalid_whitespace.js +135 -0
  3. package/bin/cds_update_annotations.js +180 -0
  4. package/bin/cds_update_identifiers.js +3 -4
  5. package/bin/cdsc.js +30 -17
  6. package/doc/CHANGELOG_BETA.md +19 -0
  7. package/lib/api/main.js +59 -24
  8. package/lib/api/options.js +12 -1
  9. package/lib/api/validate.js +1 -5
  10. package/lib/base/builtins.js +27 -0
  11. package/lib/base/message-registry.js +38 -21
  12. package/lib/base/messages.js +51 -20
  13. package/lib/base/model.js +4 -5
  14. package/lib/checks/actionsFunctions.js +2 -2
  15. package/lib/checks/annotationsOData.js +3 -0
  16. package/lib/checks/defaultValues.js +5 -2
  17. package/lib/checks/queryNoDbArtifacts.js +3 -2
  18. package/lib/checks/validator.js +2 -34
  19. package/lib/compiler/assert-consistency.js +10 -3
  20. package/lib/compiler/checks.js +44 -18
  21. package/lib/compiler/define.js +38 -30
  22. package/lib/compiler/extend.js +33 -10
  23. package/lib/compiler/index.js +0 -1
  24. package/lib/compiler/lsp-api.js +5 -0
  25. package/lib/compiler/populate.js +0 -2
  26. package/lib/compiler/propagator.js +23 -19
  27. package/lib/compiler/resolve.js +48 -29
  28. package/lib/compiler/shared.js +60 -20
  29. package/lib/compiler/tweak-assocs.js +72 -116
  30. package/lib/compiler/xpr-rewrite.js +762 -0
  31. package/lib/edm/annotations/edmJson.js +24 -7
  32. package/lib/edm/annotations/genericTranslation.js +81 -61
  33. package/lib/edm/edm.js +4 -4
  34. package/lib/edm/edmInboundChecks.js +33 -0
  35. package/lib/edm/edmPreprocessor.js +9 -6
  36. package/lib/gen/Dictionary.json +129 -14
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +1 -1
  39. package/lib/gen/languageParser.js +1523 -1518
  40. package/lib/json/from-csn.js +13 -4
  41. package/lib/json/to-csn.js +12 -12
  42. package/lib/language/genericAntlrParser.js +14 -6
  43. package/lib/main.d.ts +67 -14
  44. package/lib/main.js +1 -0
  45. package/lib/model/cloneCsn.js +6 -3
  46. package/lib/model/csnRefs.js +23 -11
  47. package/lib/model/csnUtils.js +13 -7
  48. package/lib/model/enrichCsn.js +3 -1
  49. package/lib/model/revealInternalProperties.js +2 -1
  50. package/lib/model/sortViews.js +14 -6
  51. package/lib/modelCompare/compare.js +33 -34
  52. package/lib/optionProcessor.js +27 -2
  53. package/lib/render/DuplicateChecker.js +6 -6
  54. package/lib/render/manageConstraints.js +1 -0
  55. package/lib/render/toCdl.js +3 -1
  56. package/lib/transform/db/applyTransformations.js +33 -0
  57. package/lib/transform/db/constraints.js +75 -28
  58. package/lib/transform/db/expansion.js +8 -3
  59. package/lib/transform/db/flattening.js +2 -2
  60. package/lib/transform/db/groupByOrderBy.js +2 -2
  61. package/lib/transform/db/temporal.js +6 -3
  62. package/lib/transform/db/transformExists.js +2 -2
  63. package/lib/transform/effective/annotations.js +194 -0
  64. package/lib/transform/effective/main.js +6 -8
  65. package/lib/transform/effective/misc.js +31 -10
  66. package/lib/transform/forOdata.js +23 -7
  67. package/lib/transform/forRelationalDB.js +3 -3
  68. package/lib/transform/localized.js +7 -6
  69. package/lib/transform/odata/flattening.js +221 -124
  70. package/lib/transform/odata/toFinalBaseType.js +1 -1
  71. package/lib/transform/odata/typesExposure.js +15 -12
  72. package/lib/transform/parseExpr.js +4 -4
  73. package/lib/transform/transformUtils.js +47 -42
  74. package/lib/transform/translateAssocsToJoins.js +47 -47
  75. package/lib/transform/universalCsn/universalCsnEnricher.js +16 -19
  76. package/package.json +1 -1
  77. package/share/messages/anno-missing-rewrite.md +45 -0
  78. package/share/messages/message-explanations.json +1 -0
  79. package/bin/.eslintrc.json +0 -17
  80. package/lib/api/.eslintrc.json +0 -37
  81. package/lib/checks/.eslintrc.json +0 -31
  82. package/lib/compiler/.eslintrc.json +0 -8
  83. package/lib/edm/.eslintrc.json +0 -46
  84. package/lib/inspect/.eslintrc.json +0 -4
  85. package/lib/json/.eslintrc.json +0 -4
  86. package/lib/language/.eslintrc.json +0 -4
  87. package/lib/model/.eslintrc.json +0 -13
  88. package/lib/modelCompare/utils/.eslintrc.json +0 -22
  89. package/lib/render/.eslintrc.json +0 -22
  90. package/lib/transform/.eslintrc.json +0 -13
  91. package/lib/transform/db/.eslintrc.json +0 -41
  92. package/lib/transform/draft/.eslintrc.json +0 -4
  93. package/lib/transform/effective/.eslintrc.json +0 -4
  94. package/lib/transform/universalCsn/.eslintrc.json +0 -37
  95. package/lib/utils/.eslintrc.json +0 -7
@@ -0,0 +1,762 @@
1
+ // Rewrite paths in annotation expressions.
2
+ //
3
+ // This module rewrites paths in expressions of propagated annotations.
4
+ // To properly rewrite paths, we need to consider where the annotation originates
5
+ // and where it is propagated to.
6
+ //
7
+ // Paths may need:
8
+ // 1. to have their prefix changed.
9
+ // This affects e.g. propagation due to type-of, where a prefix `$self.a` needs to
10
+ // be replaced by `$self.sub.elem` or even simple type references at parameters
11
+ // where `$self` needs to be replaced by `:P`.
12
+ // 2. to be rewritten due to projections
13
+ // This affects all paths that contain association steps, as their target may
14
+ // have been redirected, but also all annotations on projections.
15
+ //
16
+ // References referring to parameters are never rewritten.
17
+ //
18
+ // Via Includes
19
+ // ============
20
+ // Path prefixes don't change. However, we may need to reject the annotation if
21
+ // an included element was overridden and the type has changed.
22
+ //
23
+ // The path then needs to be rewritten due to associations.
24
+ // See section "Associations".
25
+ //
26
+ // Via Type
27
+ // ========
28
+ // If an annotation was written at a type or at an element of a type, we may need to
29
+ // adapt path prefixes at the type usage position.
30
+ //
31
+ // If an annotation is propagated from a type definition and the type is used at:
32
+ //
33
+ // - another (type) definition, no prefixes need to change.
34
+ // - another (type) definition as an include, see "Includes".
35
+ // - an element definition, `$self` needs to be replaced by the element name.
36
+ // Paths without `$self` on the type itself (not sub-elements), need to have
37
+ // the element name prepended.
38
+ // - a parameter, `$self` needs to be replaced by the parameter name.
39
+ // Paths without `$self` on the type itself (not sub-elements), need to have
40
+ // the parameter name prepended.
41
+ // - a return parameter, `$self` needs to be rejected, because there is no way
42
+ // to refer to `returns`. Paths without `$self` on the type itself (not sub-elements),
43
+ // need to be rejected as well.
44
+ //
45
+ // If elements in a structured type use `$self`, they, too, will need to be rewritten.
46
+ // The same rules as above apply. Because this would always end up in element
47
+ // expansion, this case is rejected and only possible with a beta flag.
48
+ // If no `$self` is used, no prefixes need to change, as the paths are already relative.
49
+ // Parameter references in types do not exist.
50
+ //
51
+ // The path then needs to be rewritten due to associations.
52
+ // See section "Associations".
53
+ //
54
+ // Via Type-Of
55
+ // ===========
56
+ // For type-ofs such as `E:sub.elem`, similar rules as for "type" are required, but
57
+ // before rewriting the paths, we need to check whether the path is valid.
58
+ //
59
+ // For `E:sub.elem`, all paths at element `elem` need to refer to sub-elements of
60
+ // `elem` or `elem` itself only. If siblings of `elem` or siblings of `sub` are
61
+ // referred to, the path can't be rewritten at the type-of usage location.
62
+ //
63
+ // Because non-relative references such as `$self` inside structures would always
64
+ // end up in element expansion, they are rejected and are only possible with a beta
65
+ // flag.
66
+ //
67
+ // If an annotation is propagated from an element `sub.elem` and the type-of is used at:
68
+ //
69
+ // - another type definition, the path may also not refer to element `sub.elem`
70
+ // itself, as it can't be rewritten to `$self` at a type definition.
71
+ // Paths starting with `$self.sub.elem` must be replaced by `$self`, i.e.
72
+ // the path up to the last path step in the type-of.
73
+ // - an element definition, `$self.sub.elem` needs to be replaced by the element name.
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.
76
+ // - a parameter, `$self.sub.elem` needs to be replaced by the parameter name.
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.
79
+ // - a return parameter, `$self` needs to be rejected, because there is no way
80
+ // to refer to `returns`. Paths without `$self` on the "type-of element" itself
81
+ // (not sub-elements), need to be rejected as well.
82
+ //
83
+ // The path then needs to be rewritten due to associations.
84
+ // See section "Associations".
85
+ //
86
+ // Associations
87
+ // ============
88
+ // All paths containing associations may need to be rewritten. Due to auto-exposure
89
+ // and auto-redirection, associations may be redirected to projections of their
90
+ // original targets. And those projections may rename elements or leave them out
91
+ // altogether. Therefore, all paths with associations need to be rewritten
92
+ // according to the rules in section "In Queries".
93
+ //
94
+ // In Queries
95
+ // ==========
96
+ // Both, propagation from source entity to query, but also from element to select item,
97
+ // need to respect renamed select items.
98
+ //
99
+ // Select Item via Origin
100
+ // ----------------------
101
+ // A bare select item of path length one, that gets an annotation via propagation from
102
+ // its origin, behaves similar to an element that gets it via an include.
103
+ // However, elements may have been renamed or may not be available at all.
104
+ // On top of that, they may be inside nested projections (expand).
105
+ // Or even simpler: sub-elements may have been selected.
106
+ //
107
+ // Instead of changing the path prefix, we need to check if the referenced path
108
+ // was projected or if a prefix was projected (e.g. for structures or associations).
109
+ // The same rules as for ON-condition rewriting apply.
110
+ //
111
+ // Furthermore, as the target is a select item, and this select item belongs to a table
112
+ // alias, we should rewrite all annotation paths only to projected elements of that
113
+ // table alias. Cross-rewriting between table aliases should not be done.
114
+ // This is the same we do for association rewriting.
115
+ //
116
+ // TODO:
117
+ // For now, we do not rewrite sub-structure elements. The whole structure needs
118
+ // to be projected or the select item isn't considered. That is, `expand {*}`
119
+ // is not considered, yet.
120
+ //
121
+ // Query Source
122
+ // ------------
123
+ // For propagation from query sources to the query, the same rules as for select
124
+ // items apply.
125
+ //
126
+ // Via Calculated Element Origin
127
+ // =============================
128
+ // Calculated elements behave just like `type-of`.
129
+ //
130
+ // Notes on $self
131
+ // ==============
132
+ // Because `$self` handling is complicated and will always result in type-expansion
133
+ // if used on/in a type definition, we reject it at such places.
134
+ // This module still resolves and rewrites them properly, though, if beta flag
135
+ // `rewriteAnnotationExpressionsViaType` is used.
136
+ //
137
+ // Notes on Propagator
138
+ // ===================
139
+ // If the compiler expands all elements (including those in `targetAspect`), then we
140
+ // can move the call to rewriteAnnotationRefs from the propagator into tweak-assocs.
141
+ // There, we need to go through _all_ definitions, not just `model._entities`.
142
+ // But until then, we rely on the propagator to properly propagate annotations.
143
+
144
+
145
+ 'use strict';
146
+
147
+ const { weakLocation } = require('../base/location');
148
+ const {
149
+ setArtifactLink,
150
+ setLink,
151
+ setExpandStatusAnnotate,
152
+ } = require('./utils');
153
+ const { CompilerAssertion } = require('../base/error');
154
+ const { isBetaEnabled } = require('../base/model');
155
+
156
+ // Config object passed around all "rewrite" functions.
157
+ class AnnoRewriteConfig {
158
+ anno;
159
+ target;
160
+ targetRoot;
161
+ origin;
162
+ isViaType;
163
+ isViaCalcElement;
164
+ viaExpand;
165
+ viaExpandType;
166
+ isInFilter;
167
+ tokenExpr;
168
+ }
169
+
170
+ function xprRewriteFns( model ) {
171
+ const { error } = model.$messageFunctions;
172
+ const {
173
+ traverseExpr,
174
+ resolvePath,
175
+ navigationEnv,
176
+ resolvePathRoot,
177
+ firstProjectionForPath,
178
+ } = model.$functions;
179
+
180
+ return {
181
+ rewriteAnnotationsRefs,
182
+ };
183
+
184
+ /**
185
+ * @param expr
186
+ * @param {AnnoRewriteConfig} config
187
+ * @param {string} [variant]
188
+ */
189
+ function reportAnnoRewriteError( expr, config, variant = 'std' ) {
190
+ return error('anno-missing-rewrite', [
191
+ weakLocation( config.target.location ), config.target,
192
+ ], {
193
+ '#': variant,
194
+ anno: config.anno,
195
+ art: config.origin,
196
+ elemref: expr,
197
+ });
198
+ }
199
+
200
+ /**
201
+ * Rewrite the propagated annotation relative to the target.
202
+ *
203
+ * @param {XSN.Artifact} target
204
+ * @param {XSN.Artifact} origin
205
+ * @param {string} annoName
206
+ */
207
+ function rewriteAnnotationsRefs( target, origin, annoName ) {
208
+ // Make sure not to waste time if no inherited annotation has references:
209
+ if (!origin?.$contains?.$annotation?.$path)
210
+ return;
211
+
212
+ const anno = target[annoName];
213
+ // only annotations with expressions have a kind
214
+ // also, don't report errors twice
215
+ if (!anno.kind || anno.$invalidPaths)
216
+ return;
217
+
218
+ const isViaType = target.type?._artifact === origin;
219
+ const [ viaExpand, viaExpandType ] = !isViaType && getExpandRoot( target ) || [ null, null ];
220
+ const isViaCalcElement = !isViaType && target.$calcDepElement &&
221
+ target.value?._artifact === origin;
222
+
223
+ const config = {
224
+ __proto__: AnnoRewriteConfig.prototype,
225
+ anno: annoName,
226
+ target,
227
+ targetRoot: annoRootArt( target ),
228
+ origin,
229
+ isViaType,
230
+ isViaCalcElement,
231
+ viaExpand,
232
+ viaExpandType,
233
+ };
234
+
235
+ const hasError = rewriteAnnotationExpr( target[annoName], config );
236
+ target.$contains ??= {};
237
+ target.$contains.$annotation ??= {};
238
+ target.$contains.$annotation.$path ||= origin.$contains.$annotation.$path;
239
+ target.$contains.$annotation.$self ||= origin.$contains.$annotation.$self;
240
+ if (hasError)
241
+ anno.$invalidPaths = true; // avoid subsequent errors
242
+ }
243
+
244
+ /**
245
+ * @param {XSN.Expression} expr
246
+ * @param {AnnoRewriteConfig} config
247
+ * @returns {boolean}
248
+ */
249
+ function rewriteAnnotationExpr( expr, config ) {
250
+ if (expr.literal === 'array') {
251
+ return !!expr.val.find(val => rewriteAnnotationExpr( val, config ));
252
+ }
253
+ else if (expr.literal === 'struct') {
254
+ const struct = Object.values(expr.struct);
255
+ return !!struct.find(val => rewriteAnnotationExpr( val, config ));
256
+ }
257
+ else if (expr.$tokenTexts) {
258
+ // used to set `$tokenText` to true in case of rewritten annotation
259
+ config.tokenExpr = expr;
260
+ return !!traverseExpr(
261
+ expr, 'annoRewrite', config.target,
262
+ (e, refCtx) => rewriteAnnoExpr( e, config, refCtx )
263
+ );
264
+ }
265
+ return false;
266
+ }
267
+
268
+ /**
269
+ * @param {XSN.Expression} expr
270
+ * @param {AnnoRewriteConfig} config
271
+ * @param {string} refCtx
272
+ * @returns {null} Returns true if the expression couldn't be rewritten.
273
+ */
274
+ function rewriteAnnoExpr( expr, config, refCtx ) {
275
+ const root = expr.path && (expr.path[0]?._navigation || expr.path[0]?._artifact);
276
+ if (!root || !expr._artifact)
277
+ return null; // invalid path
278
+
279
+ const { target } = config;
280
+
281
+ // Report obsolete $parameters; parameters on non-actions not supported, yet.
282
+ if (root.kind === '$parameters' || (root.kind === 'param' && root._parent.kind !== 'action' &&
283
+ root._parent.kind !== 'function'))
284
+ return reportAnnoRewriteError( expr, config, 'unsupported' );
285
+
286
+ // magic variables / replacement variables are never rewritten; they can't
287
+ // have filters nor can they point to elements.
288
+ if (expr._artifact?.kind === 'builtin')
289
+ return null;
290
+
291
+ let hasError = false;
292
+ if (config.isViaType || config.isViaCalcElement)
293
+ hasError = adaptPathPrefixViaType( expr, config );
294
+ else if (config.viaExpand)
295
+ hasError = adaptPathPrefixViaTypeExpansion( expr, config );
296
+
297
+ hasError ||= rewriteGenericAnnoPath( expr, config, refCtx );
298
+
299
+ if (hasError)
300
+ return true;
301
+
302
+ // TODO: Remove extra loop once filter traversal is added to traverseExpr (#12068)
303
+ for (const step of expr.path) {
304
+ if (step?._artifact && step.where && !Array.isArray( step._artifact ) ) {
305
+ // We must not prefix `$`-renamed variables with `$self`, as it would
306
+ // change meaning, see (#11775). Also, the path's target changes.
307
+ const assocTarget = step._artifact.target._artifact;
308
+ if (target) {
309
+ const filterConfig = { ...config, target: assocTarget, isInFilter: true };
310
+ if (traverseExpr( step.where, 'filter', step,
311
+ (e, ctx) => expr.path && rewriteGenericAnnoPath( e, filterConfig, ctx )))
312
+ return true;
313
+ }
314
+ else {
315
+ // can't happen: rejected earlier by compiler
316
+ return reportAnnoRewriteError( expr, config, 'unsupported' );
317
+ }
318
+ }
319
+ }
320
+
321
+ if (model.options.testMode) {
322
+ // re-resolve the modified path; all paths steps must match what we rewrote
323
+ const ref = { ...expr, path: [ ...expr.path.map(item => ({ ...item })) ] };
324
+ if (!resolvePath( ref, refCtx, target ))
325
+ throw new CompilerAssertion(`rewritten anno path must be resolvable: ${ JSON.stringify(ref.path) }`);
326
+
327
+ for (let i = 0; i < ref.path.length; ++i) {
328
+ const actual = ref.path[i];
329
+ const expected = ref.path[i];
330
+ if (actual._artifact !== expected._artifact) {
331
+ throw new CompilerAssertion(`rewritten anno path contains incorrect artifact links: ${
332
+ JSON.stringify(ref.path) }; step ${ i }`);
333
+ }
334
+ else if (actual._navigation !== undefined && actual._navigation !== expected._navigation) {
335
+ throw new CompilerAssertion(`rewritten anno path contains incorrect navigation links: ${
336
+ JSON.stringify(ref.path) }; step ${ i }`);
337
+ }
338
+ }
339
+ }
340
+
341
+ return false;
342
+ }
343
+
344
+ /**
345
+ * @param {XSN.Expression} expr
346
+ * @param {AnnoRewriteConfig} config
347
+ * @returns {*}
348
+ */
349
+ function getRootEnv( expr, config ) {
350
+ const { target } = config;
351
+
352
+ if (expr.scope === 'param') // path is absolute
353
+ return navigationEnv( config.targetRoot, null, null, 'nav' );
354
+
355
+ // On select items, use navigation elements or table alias
356
+ // TODO: Expand/inline paths don't have a `_navigation` property on their last
357
+ // path step, yet. We need to implement expand/inline.
358
+ const isSimpleSelectItem = target.value?.path && target._main?.query && !target._pathHead;
359
+ if (isSimpleSelectItem) {
360
+ const isSelfPath = (expr.path[0]?._navigation?.kind === '$self');
361
+ if (isSelfPath) {
362
+ // Path is absolute, use table alias to resolve it.
363
+ let tableAlias = target.value.path[0]._navigation;
364
+ while (tableAlias && tableAlias.kind === '$navElement')
365
+ tableAlias = tableAlias._parent;
366
+ if (tableAlias)
367
+ return tableAlias;
368
+ }
369
+ else {
370
+ // Path is relative
371
+ const nav = target.value.path[target.value.path.length - 1]._navigation?._parent;
372
+ if (nav)
373
+ return nav;
374
+ }
375
+ }
376
+
377
+ if (isSimpleSelectItem && model.options.testMode)
378
+ throw new CompilerAssertion(`select item has no table alias: ${ JSON.stringify(target.value.path) }`);
379
+
380
+ if (isAnnoPathAbsolute( expr ))
381
+ return navigationEnv( config.targetRoot, null, null, 'nav' );
382
+
383
+ // anno path is relative / element reference (others were already rejected)
384
+ // if the target is a root artifact, use it. Otherwise, use its parent.
385
+ return navigationEnv( isAnnoRootArt( target ) ? target : target._parent, null, null, 'nav' );
386
+ }
387
+
388
+ /**
389
+ * @param {XSN.Expression} expr
390
+ * @param {AnnoRewriteConfig} config
391
+ * @param {string} refCtx
392
+ * @returns {boolean}
393
+ */
394
+ function rewriteGenericAnnoPath( expr, config, refCtx ) {
395
+ const isAbsolute = isAnnoPathAbsolute( expr );
396
+ const rootIndex = isAbsolute ? 1 : 0;
397
+
398
+ // We get the root environment now, even though below we resolve the root item
399
+ // again if it was absolute (e.g. $self). We do so, because for queries, we
400
+ // want to respect the select item's corresponding table alias.
401
+ const rootEnv = getRootEnv( expr, config );
402
+
403
+ // reset artifact link; we'll set it again
404
+ setArtifactLink( expr, null );
405
+
406
+ // Adapt root path, as it isn't rewritten in rewriteItem
407
+ const rootItem = expr.path[0];
408
+ if (isAbsolute) {
409
+ delete rootItem._artifact;
410
+ delete rootItem._navigation;
411
+ // TODO: What about `up_`? Shouldn't we set `_navigation` as well?
412
+ // TODO: Can we handle `$self` of anonymous-composition-of-aspect?
413
+ const root = resolvePathRoot( expr, refCtx, config.target );
414
+ if (!root)
415
+ return reportAnnoRewriteError( expr, config );
416
+ }
417
+
418
+ let env = rootEnv;
419
+ let art = rootItem._artifact;
420
+ for (let i = rootIndex; i < expr.path.length; ++i) {
421
+ art = rewriteItem( expr, config, env, i );
422
+ if (!art)
423
+ return reportAnnoRewriteError( expr, config );
424
+ env = navigationEnv( art, null, null, 'nav' );
425
+ }
426
+ setArtifactLink( expr, art );
427
+
428
+ if (rootIndex === 0 && rootItem.id.startsWith('$')) {
429
+ if (config.isInFilter) {
430
+ // In filters, we must not prepend `$self`, as that would change its meaning.
431
+ // We must reject it. See #11775
432
+ return reportAnnoRewriteError( expr, config );
433
+ }
434
+ // After rewriting, an element starts with `$` -> add root prefix
435
+ prependRootPath( config.origin, config.targetRoot, expr );
436
+ }
437
+
438
+ return false;
439
+ }
440
+
441
+ /**
442
+ * Rewrite an expression that came via type propagation.
443
+ *
444
+ * @returns {boolean} Returns the expression if it couldn't be rewritten.
445
+ */
446
+ function adaptPathPrefixViaType( expr, config ) {
447
+ const { target, origin } = config;
448
+ if (!target._main && !origin._main)
449
+ return false; // no need to rewrite; both are top-level
450
+
451
+ if (rejectOuterReference( expr, origin, config ))
452
+ return true;
453
+
454
+ // $self-paths via types from/to non-main artifacts always need to be rewritten.
455
+ config.tokenExpr.$tokenTexts = true;
456
+
457
+ const wasAbsolute = isAnnoPathAbsolute( expr );
458
+ stripPrefixToNewRoot( expr, target, origin );
459
+
460
+ if (wasAbsolute) {
461
+ prependRootPath( origin, target, expr );
462
+ }
463
+ else if (!isAnnoRootArt( target )) { // target is element
464
+ const item = { id: target.name.id };
465
+ setArtifactLink( item, target );
466
+ prependToStrippedPath( origin, expr, [ item ] );
467
+ }
468
+ else if (target.kind === 'param') {
469
+ // annotations on parameters need a `:prefix`
470
+ prependRootPath( origin, target, expr );
471
+ }
472
+ else {
473
+ prependToStrippedPath( origin, expr, [ ] );
474
+ }
475
+
476
+ return false;
477
+ }
478
+
479
+ function adaptPathPrefixViaTypeExpansion( expr, config ) {
480
+ const root = expr.path[0]?._navigation;
481
+ if (root?.kind !== '$self') {
482
+ // non-self paths are always valid in expanded artifacts
483
+ // TODO: What about parameter references? Are they already always rejected?
484
+ return false;
485
+ }
486
+
487
+ // $self-paths via type expansion always need to be rewritten.
488
+ config.tokenExpr.$tokenTexts = true;
489
+
490
+ const { target } = config;
491
+ // We reject $self-paths because they need to be rewritten.
492
+ // However, with a special flag, we allow rewriting it for testing purposes.
493
+ if (!isBetaEnabled( model.options, 'rewriteAnnotationExpressionsViaType' ))
494
+ return reportAnnoRewriteError( expr, config, 'unsupported' );
495
+
496
+ if (rejectOuterReference( expr, config.viaExpandType, config ))
497
+ return true;
498
+
499
+ stripPrefixToNewRoot( expr, target, config.viaExpandType );
500
+ prependRootPath( config.viaExpandType, config.viaExpand, expr );
501
+ setExpandStatusAnnotate( target, 'annotate' );
502
+ config.target[config.anno].$inferred = 'anno-rewrite';
503
+ return false;
504
+ }
505
+
506
+ /**
507
+ * Prepend a path to `expr.path` or replace the root item.
508
+ * The path needs to have been run through exprstripPrefixToNewRoot(…)`.
509
+ * It is prepended if the root item is not the origin.
510
+ * Replaced otherwise.
511
+ *
512
+ * @param origin
513
+ * @param {XSN.Expression} expr
514
+ * @param path
515
+ */
516
+ function prependToStrippedPath( origin, expr, path ) {
517
+ // If origin is a definition, we need to _prepend_ the path.
518
+ // Otherwise, we need to replace the root's name.
519
+ const rootArt = expr.path[0]._artifact;
520
+ if (rootArt === origin)
521
+ expr.path.shift();
522
+ expr.path.unshift(...path);
523
+ }
524
+
525
+ /**
526
+ * Strips a prefix path from `expr.path`. The prefix is defined
527
+ * by where `newRootArt` appears in the path.
528
+ *
529
+ * @param {XSN.Expression} expr
530
+ * @param {XSN.Artifact} target
531
+ * @param {XSN.Artifact} newRootArt
532
+ */
533
+ function stripPrefixToNewRoot( expr, target, newRootArt ) {
534
+ const relativeRoot = findRelativeRoot( expr, newRootArt );
535
+ if (relativeRoot === -1 && isAnnoRootArt( newRootArt ))
536
+ return; // no $self; root item is element
537
+ if (relativeRoot >= 1)
538
+ expr.path = expr.path.slice(relativeRoot);
539
+ else if (relativeRoot === -1)
540
+ throw new CompilerAssertion('Error while rewriting annotation');
541
+ }
542
+
543
+ /**
544
+ * Returns false if the path can be propagated to the target without referring
545
+ * to any "outer" elements. It differentiates between the target being a main
546
+ * artifact and elements, because an element annotation referring to itself can't
547
+ * be propagated to a type:
548
+ *
549
+ * type T1 : { @a: (elem) elem: String; };
550
+ * type T2 : T1:elem; // invalid
551
+ *
552
+ * Also considers other targets such as `returns`, etc.
553
+ *
554
+ * @param {XSN.Expression} expr
555
+ * @param {XSN.Artifact} origin
556
+ * @param {AnnoRewriteConfig} config
557
+ * @returns {boolean}
558
+ */
559
+ function rejectOuterReference( expr, origin, config ) {
560
+ if (!isAnnoPathAbsolute( expr ) && !origin._main)
561
+ return false;
562
+
563
+ const root = expr.path[0]?._navigation;
564
+ const found = findRelativeRoot( expr, origin );
565
+ const isInvalid = (found === -1) ||
566
+ // Can't use paths with `$self` in `returns`.
567
+ (root?.kind === '$self' && isReturnParam( config.targetRoot )) ||
568
+ // siblings are allowed for non-main artifacts, except for 'returns'
569
+ (!config.target._main || isReturnParam( config.target )) && (expr.path.length - found) <= 1;
570
+
571
+ if (isInvalid)
572
+ return reportAnnoRewriteError( expr, config );
573
+ return false;
574
+ }
575
+
576
+ /**
577
+ * Finds the path segment in expr which starts at `origin`.
578
+ * For example, for a path `$self.elem.b.c` on an element `b`, it will return 2.
579
+ * Returns -1 if `origin` isn't found in the path.
580
+ *
581
+ * @param {XSN.Expression} expr
582
+ * @param {XSN.Artifact} newRootArt
583
+ * @returns {number}
584
+ */
585
+ function findRelativeRoot( expr, newRootArt ) {
586
+ if (!newRootArt._main) // main artifacts can't have outer references
587
+ return expr.path[0]?._artifact === newRootArt ? 0 : -1;
588
+
589
+ const { path } = expr;
590
+ for (let i = 0; i < path.length; ++i) {
591
+ const item = path[i];
592
+ if (item._artifact === newRootArt)
593
+ return i;
594
+ }
595
+ return -1;
596
+ }
597
+
598
+ function prependRootPath( origin, art, expr ) {
599
+ const path = [];
600
+ while (!isAnnoRootArt( art )) {
601
+ const item = { id: art.name.id };
602
+ setArtifactLink( item, art );
603
+ do
604
+ art = art._parent;
605
+ while (art.kind === 'select');
606
+ path.push(item);
607
+ }
608
+
609
+ if (art.kind === 'param') {
610
+ const param = { id: art.name.id };
611
+ setArtifactLink( param, art );
612
+ path.push(param);
613
+ expr.scope = 'param';
614
+ }
615
+ else {
616
+ const self = makeDollarSelfItem( art );
617
+ path.push(self);
618
+ }
619
+ path.reverse();
620
+
621
+ prependToStrippedPath( origin, expr, path );
622
+ }
623
+
624
+ function makeDollarSelfItem( art ) {
625
+ const self = { id: '$self' };
626
+ setLink( self, '_artifact', art );
627
+ setLink( self, '_navigation', art.$tableAliases.$self );
628
+ return self;
629
+ }
630
+
631
+ /**
632
+ * Rewrite the item in `expr.path` at the given index.
633
+ * This function may splice the array if more than one path segment
634
+ * is replaced by a single item (e.g. in queries).
635
+ *
636
+ * @param {XSN.Expression} expr
637
+ * @param {AnnoRewriteConfig} config
638
+ * @param {object} env
639
+ * @param {number} index
640
+ * @returns {*|null}
641
+ */
642
+ function rewriteItem( expr, config, env, index ) {
643
+ const item = expr.path[index];
644
+ const rewriteTarget = findRewriteTarget( expr, index, env, config.target );
645
+ const found = setArtifactLink( item, rewriteTarget[0] );
646
+ if (!found)
647
+ return null;
648
+
649
+ if (item.id !== found.name.id) {
650
+ // Path was rewritten; original token text string is no longer accurate
651
+ config.tokenExpr.$tokenTexts = true;
652
+ item.id = found.name.id;
653
+ }
654
+
655
+ if (rewriteTarget[1] > index)
656
+ expr.path.splice(index + 1, rewriteTarget[1] - index);
657
+
658
+ return rewriteTarget[0];
659
+ }
660
+
661
+ function findRewriteTarget( expr, index, env, target ) {
662
+ if (env.kind === '$navElement' || env.kind === '$tableAlias') {
663
+ const r = firstProjectionForPath( expr.path, index, env, target );
664
+ return [ r.elem, r.index ];
665
+ }
666
+
667
+ const item = expr.path[index];
668
+ // Not a query -> no $navElement -> use `elements`
669
+ if (!env.query && env.kind !== 'select') {
670
+ if (env.elements?.[item.id])
671
+ return [ env.elements[item.id], index ];
672
+ return [ null, expr.path.length ];
673
+ }
674
+ const items = (env._leadingQuery || env)._combined?.[item.id];
675
+ const allNavs = !items || Array.isArray(items) ? items : [ items ];
676
+
677
+ // If the annotation target itself has a table alias, require projections of that
678
+ // table alias. Of course, that only works if we're talking about the same query.
679
+ const tableAlias = (target._main?._origin === item._artifact._main &&
680
+ target.value?.path[0]?._navigation?.kind === '$tableAlias')
681
+ ? target.value.path[0]._navigation : null;
682
+
683
+ // Look at all table aliase that could project `item` and only select
684
+ // those that have actual projections.
685
+ const navs = allNavs?.filter(p => p._origin === item._artifact &&
686
+ (!tableAlias || tableAlias === p._parent));
687
+ if (!navs || navs.length === 0)
688
+ return [ null, expr.path.length ];
689
+
690
+ // If there are multiple navigations for the element, just use the first that matches.
691
+ // In case of table aliases, it's just one.
692
+ for (const nav of navs) {
693
+ const r = firstProjectionForPath( expr.path, index, nav._parent, target );
694
+ if (r.elem)
695
+ return [ r.elem, r.index ];
696
+ }
697
+
698
+ return [ null, expr.path.length ];
699
+ }
700
+ }
701
+
702
+ /**
703
+ * @param {XSN.Expression} expr
704
+ * @returns {boolean}
705
+ */
706
+ function isAnnoPathAbsolute( expr ) {
707
+ return expr.path[0]?._navigation?.kind === '$self' || expr.scope === 'param';
708
+ }
709
+
710
+ /**
711
+ * Returns true if the given artifact is a root artifact in terms of annotation paths.
712
+ * E.g. an element is never a root, but an entity is, as it can be referred to as `$self`,
713
+ * but also a param, as it can be referred to as `:P`.
714
+ *
715
+ * @param {XSN.Artifact} art
716
+ * @returns {boolean}
717
+ */
718
+ function isAnnoRootArt( art ) {
719
+ return !art._parent || !art._main || art.kind === 'param';
720
+ }
721
+
722
+ /**
723
+ * Get the root artifact according to the rules of isAnnoRootArt(art).
724
+ *
725
+ * @param {XSN.Artifact} art
726
+ * @returns {XSN.Artifact}
727
+ */
728
+ function annoRootArt( art ) {
729
+ while (art && !isAnnoRootArt( art ))
730
+ art = art._parent;
731
+ return art;
732
+ }
733
+
734
+ /**
735
+ * @param {XSN.Artifact} art
736
+ * @returns {boolean}
737
+ */
738
+ function isReturnParam( art ) {
739
+ return art?.kind === 'param' && art.name.id === '';
740
+ }
741
+
742
+ /**
743
+ * Returns the target's parent which is expanded and the type from which the elements originate.
744
+ *
745
+ * @param {XSN.Artifact} target
746
+ * @returns {[XSN.Artifact, XSN.Artifact]}
747
+ */
748
+ function getExpandRoot( target ) {
749
+ let viaExpand = target;
750
+ while (viaExpand.$inferred === 'expanded')
751
+ viaExpand = viaExpand._parent;
752
+ const viaExpandType = viaExpand
753
+ ? (viaExpand.type?._artifact || viaExpand.items?.type?._artifact)
754
+ : null;
755
+ const viaInclude = viaExpand?.$inferred === 'include' || false;
756
+ viaExpand = !viaInclude && viaExpandType ? viaExpand : false;
757
+ return [ viaExpand, viaExpandType ];
758
+ }
759
+
760
+ module.exports = {
761
+ xprRewriteFns,
762
+ };