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