@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.
- package/CHANGELOG.md +29 -4
- package/bin/cds_remove_invalid_whitespace.js +135 -0
- package/bin/cds_update_annotations.js +180 -0
- package/bin/cds_update_identifiers.js +3 -4
- package/bin/cdsc.js +14 -1
- package/doc/CHANGELOG_BETA.md +19 -0
- package/lib/api/main.js +59 -24
- package/lib/api/options.js +12 -1
- package/lib/api/validate.js +1 -5
- package/lib/base/builtins.js +27 -0
- package/lib/base/message-registry.js +32 -19
- package/lib/base/messages.js +50 -19
- package/lib/base/model.js +4 -5
- package/lib/checks/actionsFunctions.js +2 -2
- package/lib/checks/annotationsOData.js +3 -0
- package/lib/checks/defaultValues.js +5 -2
- package/lib/checks/queryNoDbArtifacts.js +3 -2
- package/lib/checks/validator.js +2 -34
- package/lib/compiler/assert-consistency.js +8 -2
- package/lib/compiler/checks.js +44 -18
- package/lib/compiler/define.js +34 -22
- package/lib/compiler/extend.js +33 -10
- package/lib/compiler/index.js +0 -1
- package/lib/compiler/lsp-api.js +5 -0
- package/lib/compiler/propagator.js +21 -18
- package/lib/compiler/resolve.js +44 -28
- package/lib/compiler/shared.js +60 -20
- package/lib/compiler/tweak-assocs.js +13 -88
- package/lib/compiler/xpr-rewrite.js +689 -0
- package/lib/edm/annotations/genericTranslation.js +80 -60
- package/lib/edm/edm.js +4 -4
- package/lib/edm/edmInboundChecks.js +33 -0
- package/lib/edm/edmPreprocessor.js +9 -6
- package/lib/gen/Dictionary.json +129 -14
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +1523 -1518
- package/lib/json/from-csn.js +13 -4
- package/lib/json/to-csn.js +10 -11
- package/lib/language/genericAntlrParser.js +14 -6
- package/lib/main.d.ts +67 -14
- package/lib/main.js +1 -0
- package/lib/model/cloneCsn.js +6 -3
- package/lib/model/csnRefs.js +12 -7
- package/lib/model/csnUtils.js +13 -7
- package/lib/model/enrichCsn.js +3 -1
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/model/sortViews.js +14 -6
- package/lib/modelCompare/compare.js +33 -34
- package/lib/optionProcessor.js +27 -2
- package/lib/render/DuplicateChecker.js +6 -6
- package/lib/render/manageConstraints.js +1 -0
- package/lib/render/toCdl.js +3 -1
- package/lib/transform/db/applyTransformations.js +33 -0
- package/lib/transform/db/constraints.js +1 -1
- package/lib/transform/db/expansion.js +8 -3
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/temporal.js +6 -3
- package/lib/transform/db/transformExists.js +2 -2
- package/lib/transform/effective/annotations.js +194 -0
- package/lib/transform/effective/main.js +6 -8
- package/lib/transform/effective/misc.js +31 -10
- package/lib/transform/forOdata.js +23 -7
- package/lib/transform/forRelationalDB.js +1 -1
- package/lib/transform/localized.js +7 -6
- package/lib/transform/odata/flattening.js +189 -106
- package/lib/transform/odata/toFinalBaseType.js +1 -1
- package/lib/transform/odata/typesExposure.js +15 -12
- package/lib/transform/parseExpr.js +4 -4
- package/lib/transform/transformUtils.js +40 -37
- package/lib/transform/translateAssocsToJoins.js +47 -47
- package/lib/transform/universalCsn/universalCsnEnricher.js +12 -16
- package/package.json +1 -1
- package/share/messages/anno-missing-rewrite.md +45 -0
- package/share/messages/message-explanations.json +1 -0
- package/bin/.eslintrc.json +0 -17
- package/lib/api/.eslintrc.json +0 -37
- package/lib/checks/.eslintrc.json +0 -31
- package/lib/compiler/.eslintrc.json +0 -8
- package/lib/edm/.eslintrc.json +0 -46
- package/lib/inspect/.eslintrc.json +0 -4
- package/lib/json/.eslintrc.json +0 -4
- package/lib/language/.eslintrc.json +0 -4
- package/lib/model/.eslintrc.json +0 -13
- package/lib/modelCompare/utils/.eslintrc.json +0 -22
- package/lib/render/.eslintrc.json +0 -22
- package/lib/transform/.eslintrc.json +0 -13
- package/lib/transform/db/.eslintrc.json +0 -41
- package/lib/transform/draft/.eslintrc.json +0 -4
- package/lib/transform/effective/.eslintrc.json +0 -4
- package/lib/transform/universalCsn/.eslintrc.json +0 -37
- 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
|
+
};
|