@sap/cds-compiler 4.0.0 → 4.1.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.
- package/CHANGELOG.md +115 -5
- package/bin/cdsc.js +12 -12
- package/doc/CHANGELOG_BETA.md +11 -0
- package/lib/api/main.js +60 -12
- package/lib/api/validate.js +1 -1
- package/lib/base/location.js +6 -7
- package/lib/base/message-registry.js +84 -38
- package/lib/base/messages.js +11 -10
- package/lib/base/model.js +6 -2
- package/lib/checks/defaultValues.js +6 -6
- package/lib/checks/foreignKeys.js +0 -5
- package/lib/checks/onConditions.js +17 -12
- package/lib/checks/queryNoDbArtifacts.js +132 -72
- package/lib/checks/sql-snippets.js +15 -4
- package/lib/checks/types.js +3 -3
- package/lib/checks/utils.js +1 -1
- package/lib/compiler/assert-consistency.js +44 -16
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +7 -8
- package/lib/compiler/checks.js +274 -197
- package/lib/compiler/classes.js +62 -0
- package/lib/compiler/cycle-detector.js +3 -3
- package/lib/compiler/define.js +63 -50
- package/lib/compiler/extend.js +38 -20
- package/lib/compiler/finalize-parse-cdl.js +2 -1
- package/lib/compiler/generate.js +0 -8
- package/lib/compiler/index.js +9 -7
- package/lib/compiler/kick-start.js +2 -0
- package/lib/compiler/populate.js +139 -110
- package/lib/compiler/propagator.js +4 -3
- package/lib/compiler/resolve.js +157 -126
- package/lib/compiler/shared.js +706 -404
- package/lib/compiler/tweak-assocs.js +21 -10
- package/lib/compiler/utils.js +228 -36
- package/lib/edm/annotations/genericTranslation.js +30 -2
- package/lib/edm/edm.js +4 -1
- package/lib/edm/edmPreprocessor.js +12 -5
- package/lib/edm/edmUtils.js +2 -4
- package/lib/gen/Dictionary.json +34 -10
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +3987 -3963
- package/lib/json/from-csn.js +43 -47
- package/lib/json/to-csn.js +11 -11
- package/lib/language/antlrParser.js +2 -1
- package/lib/language/genericAntlrParser.js +52 -43
- package/lib/language/language.g4 +59 -59
- package/lib/language/multiLineStringParser.js +2 -0
- package/lib/main.d.ts +5 -0
- package/lib/model/csnRefs.js +37 -19
- package/lib/model/csnUtils.js +20 -16
- package/lib/model/revealInternalProperties.js +29 -21
- package/lib/model/sortViews.js +4 -2
- package/lib/modelCompare/compare.js +112 -39
- package/lib/modelCompare/utils/filter.js +54 -24
- package/lib/optionProcessor.js +6 -6
- package/lib/render/manageConstraints.js +20 -17
- package/lib/render/toCdl.js +34 -20
- package/lib/render/toHdbcds.js +2 -2
- package/lib/render/toRename.js +4 -9
- package/lib/render/toSql.js +77 -26
- package/lib/render/utils/common.js +3 -3
- package/lib/render/utils/unique.js +52 -0
- package/lib/transform/db/applyTransformations.js +61 -20
- package/lib/transform/db/assertUnique.js +7 -8
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/cdsPersistence.js +8 -8
- package/lib/transform/db/expansion.js +17 -21
- package/lib/transform/db/flattening.js +23 -23
- package/lib/transform/db/rewriteCalculatedElements.js +20 -14
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/db/transformExists.js +8 -7
- package/lib/transform/db/views.js +73 -33
- package/lib/transform/draft/db.js +11 -9
- package/lib/transform/draft/odata.js +1 -1
- package/lib/transform/{forOdataNew.js → forOdata.js} +56 -42
- package/lib/transform/forRelationalDB.js +69 -75
- package/lib/transform/localized.js +6 -5
- package/lib/transform/odata/toFinalBaseType.js +3 -3
- package/lib/transform/{transformUtilsNew.js → transformUtils.js} +4 -101
- package/lib/transform/translateAssocsToJoins.js +14 -28
- package/package.json +1 -1
- package/share/messages/check-proper-type-of.md +1 -1
- package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
- package/share/messages/message-explanations.json +1 -1
package/lib/compiler/shared.js
CHANGED
|
@@ -13,16 +13,23 @@ const {
|
|
|
13
13
|
pathName,
|
|
14
14
|
userQuery,
|
|
15
15
|
definedViaCdl,
|
|
16
|
+
isDirectComposition,
|
|
17
|
+
pathStartsWithSelf,
|
|
18
|
+
columnRefStartsWithSelf,
|
|
19
|
+
isAssocToPrimaryKeys,
|
|
20
|
+
artifactRefLocation,
|
|
16
21
|
} = require('./utils');
|
|
17
22
|
|
|
23
|
+
const $inferred = Symbol.for('cds.$inferred');
|
|
24
|
+
const $location = Symbol.for('cds.$location');
|
|
25
|
+
|
|
18
26
|
/**
|
|
19
27
|
* Main export function of this file. Attach "resolve" functions shared for phase
|
|
20
28
|
* "define" and "resolve" to `model.$functions`, where argument `model` is the XSN.
|
|
21
29
|
*
|
|
22
30
|
* Before calling `resolvePath`, make sure that the following function
|
|
23
31
|
* in model.$function is set:
|
|
24
|
-
* - `
|
|
25
|
-
* its argument, e.g. a function which returns the dictionary of subartifacts.
|
|
32
|
+
* - `effectiveType`
|
|
26
33
|
*
|
|
27
34
|
* @param {XSN.Model} model
|
|
28
35
|
*/
|
|
@@ -30,7 +37,7 @@ const {
|
|
|
30
37
|
function fns( model ) {
|
|
31
38
|
const { options } = model;
|
|
32
39
|
const {
|
|
33
|
-
info,
|
|
40
|
+
info, error, warning, message,
|
|
34
41
|
} = model.$messageFunctions;
|
|
35
42
|
const Functions = model.$functions;
|
|
36
43
|
|
|
@@ -59,53 +66,68 @@ function fns( model ) {
|
|
|
59
66
|
isMainRef: 'all',
|
|
60
67
|
lexical: userBlock,
|
|
61
68
|
dynamic: modelDefinitions,
|
|
62
|
-
notFound:
|
|
69
|
+
notFound: () => null, // without message
|
|
63
70
|
},
|
|
64
71
|
include: {
|
|
65
72
|
isMainRef: 'no-generated',
|
|
66
73
|
lexical: userBlock,
|
|
67
74
|
dynamic: modelBuiltinsOrDefinitions,
|
|
68
75
|
notFound: undefinedDefinition,
|
|
76
|
+
accept: acceptStructOrBare,
|
|
77
|
+
},
|
|
78
|
+
_include: { // cyclic include: no accept
|
|
79
|
+
isMainRef: 'no-generated',
|
|
80
|
+
lexical: userBlock,
|
|
81
|
+
dynamic: modelBuiltinsOrDefinitions,
|
|
82
|
+
notFound: undefinedDefinition,
|
|
69
83
|
},
|
|
70
|
-
viewInclude: 'include', // TODO: do differently
|
|
71
84
|
target: {
|
|
72
85
|
isMainRef: 'no-autoexposed',
|
|
73
86
|
lexical: userBlock,
|
|
74
87
|
dynamic: modelBuiltinsOrDefinitions,
|
|
75
88
|
notFound: undefinedDefinition,
|
|
76
|
-
|
|
89
|
+
accept: acceptEntity,
|
|
90
|
+
noDep: true,
|
|
91
|
+
// special `scope`s for auto-redirections:
|
|
77
92
|
global: () => ({ isMainRef: 'all', dynamic: modelDefinitions }),
|
|
78
93
|
},
|
|
79
|
-
targetAspect: {
|
|
94
|
+
targetAspect: {
|
|
80
95
|
isMainRef: 'no-autoexposed',
|
|
81
96
|
lexical: userBlock,
|
|
82
97
|
dynamic: modelBuiltinsOrDefinitions,
|
|
83
98
|
notFound: undefinedDefinition,
|
|
99
|
+
accept: acceptAspect,
|
|
84
100
|
},
|
|
85
101
|
from: {
|
|
86
102
|
isMainRef: 'no-autoexposed',
|
|
87
103
|
lexical: userBlock,
|
|
88
104
|
dynamic: modelBuiltinsOrDefinitions,
|
|
89
|
-
notFound: undefinedDefinition,
|
|
90
105
|
navigation: environment,
|
|
106
|
+
notFound: undefinedDefinition,
|
|
107
|
+
accept: acceptEntityOrAssoc,
|
|
108
|
+
noDep: '', // dependency special for from
|
|
91
109
|
},
|
|
92
110
|
type: {
|
|
93
111
|
isMainRef: 'no-autoexposed',
|
|
94
112
|
lexical: userBlock,
|
|
95
113
|
dynamic: modelBuiltinsOrDefinitions,
|
|
114
|
+
navigation: staticTarget,
|
|
96
115
|
notFound: undefinedDefinition,
|
|
97
|
-
|
|
116
|
+
accept: acceptTypeOrElement,
|
|
98
117
|
// special `scope`s for CDL parser - TYPE OF (TODO generated?), cds.Association:
|
|
99
118
|
typeOf: typeOfSemantics,
|
|
100
|
-
global: () => ({
|
|
119
|
+
global: () => ({
|
|
120
|
+
isMainRef: 'no-autoexposed',
|
|
121
|
+
dynamic: modelDefinitions,
|
|
122
|
+
navigation: staticTarget, // TODO: Object.assign() with main
|
|
123
|
+
}),
|
|
101
124
|
},
|
|
102
|
-
actionParamType: 'type', // TODO: do differently
|
|
103
|
-
eventType: 'type', // TODO: do differently
|
|
104
125
|
// element references without lexical scope (except $self/$projection): -----
|
|
105
126
|
targetElement: {
|
|
106
127
|
lexical: null,
|
|
107
128
|
dollar: false,
|
|
108
129
|
dynamic: targetElements,
|
|
130
|
+
navigation: targetNavigation,
|
|
109
131
|
notFound: undefinedTargetElement,
|
|
110
132
|
param: paramSemantics,
|
|
111
133
|
},
|
|
@@ -120,6 +142,7 @@ function fns( model ) {
|
|
|
120
142
|
lexical: justDollarSelf,
|
|
121
143
|
dollar: true,
|
|
122
144
|
dynamic: targetElements,
|
|
145
|
+
navigation: calcElemNavigation,
|
|
123
146
|
notFound: undefinedTargetElement,
|
|
124
147
|
param: paramUnsupported,
|
|
125
148
|
},
|
|
@@ -131,21 +154,33 @@ function fns( model ) {
|
|
|
131
154
|
param: paramUnsupported,
|
|
132
155
|
},
|
|
133
156
|
// general element references -----------------------------------------------
|
|
134
|
-
|
|
157
|
+
where: {
|
|
135
158
|
lexical: tableAliasesIfNotExtendAndSelf,
|
|
136
159
|
dollar: true,
|
|
137
160
|
dynamic: combinedSourcesOrParentElements,
|
|
138
161
|
notFound: undefinedSourceElement,
|
|
162
|
+
check: checkRefInQuery,
|
|
163
|
+
param: paramSemantics,
|
|
164
|
+
},
|
|
165
|
+
having: 'where',
|
|
166
|
+
groupBy: 'where',
|
|
167
|
+
column: {
|
|
168
|
+
lexical: tableAliasesIfNotExtendAndSelf,
|
|
169
|
+
dollar: true,
|
|
170
|
+
dynamic: combinedSourcesOrParentElements,
|
|
171
|
+
notFound: undefinedSourceElement,
|
|
172
|
+
check: checkColumnRef,
|
|
139
173
|
param: paramSemantics,
|
|
140
174
|
nestedColumn: () => ({ // in expand and inline
|
|
141
175
|
lexical: justDollarSelf,
|
|
142
176
|
dollar: true,
|
|
143
177
|
dynamic: nestedElements,
|
|
144
178
|
notFound: undefinedNestedElement,
|
|
179
|
+
check: checkColumnRef,
|
|
145
180
|
param: paramSemantics,
|
|
146
181
|
}),
|
|
147
182
|
},
|
|
148
|
-
'
|
|
183
|
+
'from-args': {
|
|
149
184
|
lexical: null,
|
|
150
185
|
dollar: true,
|
|
151
186
|
dynamic: () => Object.create( null ),
|
|
@@ -156,10 +191,11 @@ function fns( model ) {
|
|
|
156
191
|
lexical: justDollarSelf,
|
|
157
192
|
dollar: true,
|
|
158
193
|
dynamic: parentElements,
|
|
194
|
+
navigation: calcElemNavigation,
|
|
159
195
|
notFound: undefinedParentElement,
|
|
160
196
|
param: paramUnsupported,
|
|
161
197
|
},
|
|
162
|
-
|
|
198
|
+
'join-on': {
|
|
163
199
|
lexical: tableAliasesAndSelf,
|
|
164
200
|
dollar: true,
|
|
165
201
|
dynamic: combinedSourcesOrParentElements, // TODO: source alone...
|
|
@@ -168,256 +204,99 @@ function fns( model ) {
|
|
|
168
204
|
},
|
|
169
205
|
on: { // unmanaged assoc: outside query, redirected or new assoc in column
|
|
170
206
|
lexical: justDollarSelf,
|
|
171
|
-
allowBareSelf: true,
|
|
172
207
|
dollar: true,
|
|
173
208
|
dynamic: parentElements,
|
|
209
|
+
navigation: assocOnNavigation,
|
|
174
210
|
notFound: undefinedParentElement,
|
|
211
|
+
accept: acceptElemOrVarOrSelf,
|
|
212
|
+
check: checkAssocOn,
|
|
175
213
|
param: paramUnsupported,
|
|
176
214
|
nestedColumn: () => ({ // in expand and inline
|
|
177
215
|
lexical: justDollarSelf,
|
|
178
216
|
dollar: true,
|
|
179
217
|
dynamic: parentElements,
|
|
218
|
+
navigation: assocOnNavigation,
|
|
180
219
|
notFound: undefinedParentElement,
|
|
181
220
|
}),
|
|
182
221
|
},
|
|
183
222
|
'mixin-on': {
|
|
184
223
|
lexical: tableAliasesAndSelf,
|
|
185
|
-
allowBareSelf: true,
|
|
186
224
|
dollar: true,
|
|
187
225
|
dynamic: combinedSourcesOrParentElements,
|
|
226
|
+
navigation: assocOnNavigation,
|
|
188
227
|
notFound: undefinedSourceElement,
|
|
228
|
+
accept: acceptElemOrVarOrSelf,
|
|
229
|
+
check: checkAssocOn,
|
|
189
230
|
param: paramSemantics, // TODO: check that assocs containing param in ON is not published
|
|
190
231
|
},
|
|
191
|
-
'
|
|
232
|
+
'orderBy-ref': {
|
|
192
233
|
lexical: tableAliasesAndSelf,
|
|
193
234
|
dollar: true,
|
|
194
235
|
dynamic: parentElements,
|
|
195
236
|
notFound: undefinedOrderByElement,
|
|
237
|
+
check: checkOrderByRef,
|
|
196
238
|
param: paramSemantics,
|
|
197
239
|
},
|
|
198
|
-
'
|
|
240
|
+
'orderBy-expr': {
|
|
199
241
|
lexical: tableAliasesAndSelf,
|
|
200
242
|
dollar: true,
|
|
201
243
|
dynamic: combinedSourcesOrParentElements,
|
|
202
244
|
notFound: undefinedSourceElement,
|
|
245
|
+
check: checkRefInQuery,
|
|
203
246
|
param: paramSemantics,
|
|
204
247
|
},
|
|
205
|
-
'
|
|
248
|
+
'orderBy-set-ref': {
|
|
206
249
|
lexical: tableAliasesAndSelf, // TODO: reject own tab aliases
|
|
207
250
|
dollar: true,
|
|
208
251
|
dynamic: queryElements,
|
|
209
252
|
notFound: undefinedParentElement,
|
|
253
|
+
check: checkOrderByRef,
|
|
210
254
|
param: paramSemantics,
|
|
211
255
|
},
|
|
212
|
-
'
|
|
256
|
+
'orderBy-set-expr': {
|
|
213
257
|
lexical: tableAliasesAndSelf, // TODO: reject own tab aliases
|
|
214
258
|
dollar: true,
|
|
215
259
|
dynamic: () => Object.create( null ),
|
|
216
260
|
notFound: undefinedVariable,
|
|
261
|
+
check: checkRefInQuery,
|
|
217
262
|
param: paramSemantics,
|
|
218
263
|
},
|
|
219
264
|
};
|
|
220
265
|
|
|
221
|
-
// TODO: combine envFn and assoc ?
|
|
222
|
-
const specExpected = {
|
|
223
|
-
using: {}, // for using declaration
|
|
224
|
-
// TODO: artifact references ---------------------------------------------
|
|
225
|
-
extend: {},
|
|
226
|
-
// ref in top-level EXTEND
|
|
227
|
-
annotate: {},
|
|
228
|
-
_extensions: {},
|
|
229
|
-
type: { // TODO: more detailed later (e.g. for enum base type?)
|
|
230
|
-
check: checkTypeRef,
|
|
231
|
-
expectedMsgId: 'ref-expecting-type',
|
|
232
|
-
sloppyMsgId: 'ref-sloppy-type',
|
|
233
|
-
},
|
|
234
|
-
actionParamType: {
|
|
235
|
-
check: checkActionParamTypeRef,
|
|
236
|
-
expectedMsgId: 'ref-expecting-action-param-type',
|
|
237
|
-
sloppyMsgId: 'ref-sloppy-actionparam-type',
|
|
238
|
-
},
|
|
239
|
-
eventType: {
|
|
240
|
-
check: checkEventTypeRef,
|
|
241
|
-
expectedMsgId: 'ref-expecting-event-type',
|
|
242
|
-
sloppyMsgId: 'ref-sloppy-event-type',
|
|
243
|
-
},
|
|
244
|
-
include: {
|
|
245
|
-
check: checkIncludesRef,
|
|
246
|
-
expectedMsgId: 'ref-expecting-struct',
|
|
247
|
-
},
|
|
248
|
-
viewInclude: {
|
|
249
|
-
check: checkViewIncludesRef,
|
|
250
|
-
expectedMsgId: 'ref-expecting-bare-aspect',
|
|
251
|
-
},
|
|
252
|
-
target: {
|
|
253
|
-
check: checkEntityRef,
|
|
254
|
-
expectedMsgId: 'ref-expecting-entity',
|
|
255
|
-
noDep: true,
|
|
256
|
-
},
|
|
257
|
-
targetAspect: {
|
|
258
|
-
check: checkTargetRef,
|
|
259
|
-
expectedMsgId: 'ref-expecting-target',
|
|
260
|
-
sloppyMsgId: 'ref-sloppy-target',
|
|
261
|
-
noDep: 'only-entity',
|
|
262
|
-
},
|
|
263
|
-
from: {
|
|
264
|
-
check: checkSourceRef,
|
|
265
|
-
expectedMsgId: 'ref-expecting-source',
|
|
266
|
-
assoc: 'from',
|
|
267
|
-
argsSpec: 'expr',
|
|
268
|
-
},
|
|
269
|
-
// element references ----------------------------------------------------
|
|
270
|
-
// TODO: dep for (explicit+implicit!) foreign keys
|
|
271
|
-
// TODO: also check that we do not follow associations in foreign key? no args, no filter
|
|
272
|
-
targetElement: { assoc: false },
|
|
273
|
-
filter: {
|
|
274
|
-
escape: 'param',
|
|
275
|
-
},
|
|
276
|
-
'calc-filter': {
|
|
277
|
-
escape: 'param',
|
|
278
|
-
},
|
|
279
|
-
default: {
|
|
280
|
-
check: checkConstRef,
|
|
281
|
-
expectedMsgId: 'ref-expecting-const',
|
|
282
|
-
},
|
|
283
|
-
expr: {
|
|
284
|
-
escape: 'param', assoc: 'nav',
|
|
285
|
-
},
|
|
286
|
-
'param-only': {
|
|
287
|
-
escape: 'param',
|
|
288
|
-
},
|
|
289
|
-
calc: {
|
|
290
|
-
assoc: 'nav',
|
|
291
|
-
},
|
|
292
|
-
joinOn: { // ON condition for JOIN: should be different to 'expr'!
|
|
293
|
-
escape: 'param', assoc: 'nav',
|
|
294
|
-
},
|
|
295
|
-
on: { // TODO: there will also be a 'from-on' (see 'expr')
|
|
296
|
-
noDep: true, // do not set dependency for circular-check
|
|
297
|
-
allowSelf: true,
|
|
298
|
-
}, // TODO: special assoc for only on user
|
|
299
|
-
'mixin-on': {
|
|
300
|
-
escape: 'param', // TODO: extra check that assocs containing param in ON is not published
|
|
301
|
-
noDep: true, // do not set dependency for circular-check
|
|
302
|
-
allowSelf: true,
|
|
303
|
-
}, // TODO: special assoc for only on user
|
|
304
|
-
// ---------marker for getPathRoot replaced-----------
|
|
305
|
-
'order-by-ref': {
|
|
306
|
-
next: '_$next',
|
|
307
|
-
dollar: true,
|
|
308
|
-
escape: 'param',
|
|
309
|
-
assoc: 'nav',
|
|
310
|
-
dynamic: 'query',
|
|
311
|
-
deprecatedSourceRefs: true,
|
|
312
|
-
},
|
|
313
|
-
'order-by-expr': {
|
|
314
|
-
next: '_$next',
|
|
315
|
-
dollar: true,
|
|
316
|
-
escape: 'param',
|
|
317
|
-
assoc: 'nav',
|
|
318
|
-
},
|
|
319
|
-
'order-by-set-ref': {
|
|
320
|
-
next: '_$next',
|
|
321
|
-
dollar: true,
|
|
322
|
-
escape: 'param',
|
|
323
|
-
noDep: true,
|
|
324
|
-
dynamic: 'query',
|
|
325
|
-
lexical: 'next',
|
|
326
|
-
},
|
|
327
|
-
'order-by-set-expr': {
|
|
328
|
-
next: '_$next',
|
|
329
|
-
dollar: true,
|
|
330
|
-
escape: 'param',
|
|
331
|
-
noDep: true,
|
|
332
|
-
dynamic: false,
|
|
333
|
-
lexical: 'next',
|
|
334
|
-
},
|
|
335
|
-
// expr TODO: better - on condition for assoc, other on
|
|
336
|
-
// expr TODO: write dependency, but care for $self
|
|
337
|
-
param: {
|
|
338
|
-
check: checkConstRef,
|
|
339
|
-
expectedMsgId: 'ref-expecting-const',
|
|
340
|
-
},
|
|
341
|
-
};
|
|
342
|
-
|
|
343
266
|
Object.assign( model.$functions, {
|
|
267
|
+
traverseExpr,
|
|
344
268
|
resolveUncheckedPath,
|
|
345
|
-
resolveTypeArgumentsUnchecked,
|
|
269
|
+
resolveTypeArgumentsUnchecked, // TODO: move to some other file
|
|
346
270
|
resolvePath,
|
|
271
|
+
checkExpr,
|
|
272
|
+
checkOnCondition,
|
|
273
|
+
nestedElements,
|
|
347
274
|
attachAndEmitValidNames,
|
|
348
275
|
} );
|
|
349
276
|
return;
|
|
350
277
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
function checkIncludesRef( art ) {
|
|
356
|
-
// We currently disallow using
|
|
357
|
-
// - derived structure types: would have to follow type in extend/include;
|
|
358
|
-
// - entities with params: clarify inheritance, use of param in ON/DEFAULT;
|
|
359
|
-
// - query entities/events: difficult sequence of resolve steps
|
|
360
|
-
// - aspect without elements (useful for actions/annotations)
|
|
361
|
-
return !(art.elements && !art.query && !art.type && !art.params) && art.kind !== 'aspect';
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Returns true, if the given artifact can be included by a query entity / view.
|
|
366
|
-
*
|
|
367
|
-
* We currently allow:
|
|
368
|
-
* - aspects without elements (the aspect may have actions):
|
|
369
|
-
* either no `elements` property or empty dictionary
|
|
370
|
-
*
|
|
371
|
-
* @param {XSN.Artifact} art
|
|
372
|
-
* @return {boolean}
|
|
373
|
-
*/
|
|
374
|
-
function checkViewIncludesRef( art ) {
|
|
375
|
-
return !(art.kind === 'aspect' && (!art.elements || Object.keys(art.elements).length === 0));
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* @returns {boolean|string}
|
|
380
|
-
*/
|
|
381
|
-
function checkTypeRef( art ) {
|
|
382
|
-
if (art.kind === 'type' || art.kind === 'element')
|
|
383
|
-
return false;
|
|
384
|
-
return ![ 'entity', 'aspect', 'event' ].includes( art.kind ) || 'sloppy';
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* @returns {boolean|string}
|
|
389
|
-
*/
|
|
390
|
-
function checkActionParamTypeRef( art ) {
|
|
391
|
-
return !(art.kind === 'entity' && art._service) && checkTypeRef( art );
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* @returns {boolean|string}
|
|
396
|
-
*/
|
|
397
|
-
function checkEventTypeRef( art ) {
|
|
398
|
-
return art.kind !== 'event' && checkActionParamTypeRef( art );
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
function checkEntityRef( art ) {
|
|
402
|
-
return art.kind !== 'entity';
|
|
403
|
-
}
|
|
278
|
+
// Expression traversal function ----------------------------------------------
|
|
279
|
+
function traverseExpr( expr, exprCtx, user, callback ) {
|
|
280
|
+
if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
|
|
281
|
+
return;
|
|
404
282
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
283
|
+
if (expr.path) {
|
|
284
|
+
callback( expr, exprCtx, user );
|
|
285
|
+
// TODO: move arguments and filter traversal to here
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
else if (expr.type || expr.query) {
|
|
289
|
+
callback( expr, exprCtx, user );
|
|
290
|
+
}
|
|
413
291
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
292
|
+
if (expr.args) {
|
|
293
|
+
const args = Array.isArray(expr.args) ? expr.args : Object.values( expr.args );
|
|
294
|
+
// TODO: re-think $expected
|
|
295
|
+
if (!callback.traverse?.( args, exprCtx, user, callback ))
|
|
296
|
+
args.forEach( e => traverseExpr( e, exprCtx, user, callback ) );
|
|
297
|
+
}
|
|
298
|
+
if (expr.suffix) // fn( … ) OVER …
|
|
299
|
+
expr.suffix.forEach( e => traverseExpr( e, exprCtx, user, callback ) );
|
|
421
300
|
}
|
|
422
301
|
|
|
423
302
|
// Return absolute name for unchecked path `ref`. We first try searching for
|
|
@@ -441,7 +320,7 @@ function fns( model ) {
|
|
|
441
320
|
if (Array.isArray( art ))
|
|
442
321
|
art = art[0];
|
|
443
322
|
if (!art)
|
|
444
|
-
return (semantics.
|
|
323
|
+
return (semantics.dynamic !== modelDefinitions) ? art : pathName( path );
|
|
445
324
|
if (path.length === 1)
|
|
446
325
|
return art.name.absolute; // TODO: name.id
|
|
447
326
|
return `${ art.name.absolute }.${ pathName( ref.path.slice(1) ) }`;
|
|
@@ -465,7 +344,7 @@ function fns( model ) {
|
|
|
465
344
|
return setArtifactLink( ref, undefined );
|
|
466
345
|
}
|
|
467
346
|
|
|
468
|
-
const s = referenceSemantics[expected];
|
|
347
|
+
const s = referenceSemantics[expected];
|
|
469
348
|
const semantics = (typeof s === 'string') ? referenceSemantics[s] : s;
|
|
470
349
|
|
|
471
350
|
const r = getPathRoot( ref, semantics, origUser );
|
|
@@ -473,59 +352,38 @@ function fns( model ) {
|
|
|
473
352
|
if (!root)
|
|
474
353
|
return setArtifactLink( ref, root );
|
|
475
354
|
|
|
476
|
-
let spec = specExpected[expected];
|
|
477
|
-
if (ref.scope === 'param') {
|
|
478
|
-
if (!spec.escape)
|
|
479
|
-
throw new CompilerAssertion( 'getPathRoot() should have returned falsy val' );
|
|
480
|
-
spec = specExpected[spec.escape];
|
|
481
|
-
}
|
|
482
|
-
|
|
483
355
|
// how many path items are for artifacts (rest: elements)
|
|
484
356
|
// console.log(expected, ref.path.map(a=>a.id),artItemsCount)
|
|
485
|
-
let art = getPathItem( ref, semantics,
|
|
357
|
+
let art = getPathItem( ref, semantics, user );
|
|
486
358
|
if (!art)
|
|
487
359
|
return setArtifactLink( ref, art );
|
|
488
360
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
if (fail === true) {
|
|
499
|
-
signalNotFound( spec.expectedMsgId, [ ref.location, user ], null );
|
|
500
|
-
return setArtifactLink( ref, false );
|
|
501
|
-
}
|
|
502
|
-
else if (fail) {
|
|
503
|
-
signalNotFound( spec.sloppyMsgId, [ ref.location, user ], null );
|
|
504
|
-
// no return!
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
if (spec.warn) {
|
|
508
|
-
const msgId = spec.warn( art, user );
|
|
509
|
-
if (msgId)
|
|
510
|
-
warning( msgId, [ ref.location, user ] );
|
|
511
|
-
}
|
|
512
|
-
if (user && (!spec.noDep ||
|
|
513
|
-
spec.noDep === 'only-entity' && art.kind !== 'entity')) {
|
|
514
|
-
const { location } = ref; // || combinedLocation( head, path[tail.length] );
|
|
515
|
-
// TODO: location of last path item if not main artifact
|
|
516
|
-
if (spec.assoc === 'from' && art._main) {
|
|
517
|
-
dependsOn( user, art._main, location );
|
|
361
|
+
// TODO: use isMainRef string value here?
|
|
362
|
+
const acceptFn = semantics.accept || (semantics.isMainRef ? a => a : acceptElemOrVar);
|
|
363
|
+
art = setArtifactLink( ref, acceptFn( art, user, ref, semantics ) );
|
|
364
|
+
|
|
365
|
+
// TODO TMP: remove noDep: an association does not depend on the target, only
|
|
366
|
+
// -- on its keys/on, which depend on certain target elements
|
|
367
|
+
if (art && user && !semantics.noDep) {
|
|
368
|
+
const location = artifactRefLocation( ref );
|
|
369
|
+
if (semantics.noDep === '' && art._main) { // assoc in FROM
|
|
518
370
|
environment( art, location, user );
|
|
371
|
+
const target = art._effectiveType?.target?._artifact;
|
|
372
|
+
if (target)
|
|
373
|
+
dependsOn( user._main, target, location, user );
|
|
519
374
|
}
|
|
520
|
-
else if (art.kind !== 'select'
|
|
375
|
+
else if (art._main && art.kind !== 'select' || path[0]._navigation?.kind !== '$self') {
|
|
376
|
+
// no real dependency to bare $self (or actually: the underlaying query)
|
|
521
377
|
dependsOn( user, art, location );
|
|
522
378
|
// Without on-demand resolve, we can simply signal 'undefined "x"'
|
|
523
379
|
// instead of 'illegal cycle' in the following case:
|
|
524
380
|
// element elem: type of elem.x;
|
|
525
381
|
}
|
|
382
|
+
// TODO: really write dependency with expand/inline? write test
|
|
383
|
+
// (removing it is not incompatible => not urgent)
|
|
526
384
|
}
|
|
527
385
|
// TODO: follow FROM here, see csnRef - fromRef
|
|
528
|
-
return
|
|
386
|
+
return art;
|
|
529
387
|
}
|
|
530
388
|
|
|
531
389
|
/**
|
|
@@ -533,7 +391,7 @@ function fns( model ) {
|
|
|
533
391
|
* User is used for semantic message location.
|
|
534
392
|
*
|
|
535
393
|
* For builtins, for each property name `<prop>` in `typeArtifact.parameters`, we move a value
|
|
536
|
-
*
|
|
394
|
+
* from `art.$typeArgs` (a vector of numbers with locations) to `artifact.<prop>`.
|
|
537
395
|
*
|
|
538
396
|
* For non-builtins, we take either one or two arguments and interpret them
|
|
539
397
|
* as `length` or `precision`/`scale`.
|
|
@@ -550,14 +408,12 @@ function fns( model ) {
|
|
|
550
408
|
let args = artifact.$typeArgs || [];
|
|
551
409
|
const parameters = typeArtifact?.parameters || [];
|
|
552
410
|
|
|
553
|
-
if (parameters.length > 0) {
|
|
411
|
+
if (args.length > 0 && parameters.length > 0) {
|
|
554
412
|
// For Builtins
|
|
555
413
|
for (let i = 0; i < parameters.length; ++i) {
|
|
556
|
-
|
|
557
|
-
if (!
|
|
558
|
-
par =
|
|
559
|
-
if (!artifact[par.name] && i < args.length)
|
|
560
|
-
artifact[par.name] = args[i];
|
|
414
|
+
const par = parameters[i].name || parameters[i];
|
|
415
|
+
if (!artifact[par] && i < args.length)
|
|
416
|
+
artifact[par] = args[i];
|
|
561
417
|
}
|
|
562
418
|
args = args.slice(parameters.length);
|
|
563
419
|
}
|
|
@@ -622,7 +478,7 @@ function fns( model ) {
|
|
|
622
478
|
const [ nextProp, dictProp ] = (isMainRef)
|
|
623
479
|
? [ '_block', 'artifacts' ]
|
|
624
480
|
: [ '_$next', '$tableAliases' ];
|
|
625
|
-
// let notApplicable = ...; // for table aliases in JOIN-ON and UNION
|
|
481
|
+
// let notApplicable = ...; // for table aliases in JOIN-ON and UNION orderBy
|
|
626
482
|
for (let env = lexical; env; env = env[nextProp]) {
|
|
627
483
|
const dict = env[dictProp] || Object.create(null);
|
|
628
484
|
const r = dict[head.id];
|
|
@@ -646,8 +502,6 @@ function fns( model ) {
|
|
|
646
502
|
valid.push( dynamicDict );
|
|
647
503
|
else
|
|
648
504
|
valid.push( model.$magicVariables.artifacts, removeDollarNames( dynamicDict ) );
|
|
649
|
-
if (semantics.notFound === false)
|
|
650
|
-
return setArtifactLink( head, null );
|
|
651
505
|
// TODO: streamline function arguments (probably: user, path, semantics )
|
|
652
506
|
const undef = semantics.notFound( ruser, head, valid, dynamicDict,
|
|
653
507
|
!isMainRef && user._user && user._artifact, path );
|
|
@@ -661,7 +515,7 @@ function fns( model ) {
|
|
|
661
515
|
// TODO - think about setting _navigation for all $navElement – the
|
|
662
516
|
// "ref: ['tabAlias']: inline: […]" handling might be easier
|
|
663
517
|
// (no _pathHead consultation for key prop and renaming support)
|
|
664
|
-
function getPathItem( ref, semantics,
|
|
518
|
+
function getPathItem( ref, semantics, user ) {
|
|
665
519
|
// let art = (headArt && headArt.kind === '$tableAlias') ? headArt._origin : headArt;
|
|
666
520
|
const { path } = ref;
|
|
667
521
|
let artItemsCount = 0;
|
|
@@ -671,11 +525,10 @@ function fns( model ) {
|
|
|
671
525
|
(ref.scope ? 1 : path.length);
|
|
672
526
|
}
|
|
673
527
|
let art = null;
|
|
674
|
-
let nav = spec.assoc !== '$keys' && null; // false for '$keys'
|
|
675
|
-
const last = path[path.length - 1];
|
|
676
|
-
// TODO: change elementsEnv via semantics for static versus dynamic assoc navigation
|
|
677
528
|
const elementsEnv = semantics.navigation || environment;
|
|
529
|
+
let index = -1;
|
|
678
530
|
for (const item of path) {
|
|
531
|
+
++index;
|
|
679
532
|
--artItemsCount;
|
|
680
533
|
if (!item?.id) // incomplete AST due to parse error
|
|
681
534
|
return undefined;
|
|
@@ -686,33 +539,24 @@ function fns( model ) {
|
|
|
686
539
|
|
|
687
540
|
const prev = art;
|
|
688
541
|
const envFn = (artItemsCount >= 0) ? artifactsEnv : elementsEnv;
|
|
689
|
-
|
|
690
|
-
|
|
542
|
+
// TOOD: call envFn with location of last item (for dependency error)
|
|
543
|
+
const env = envFn( art, path[index - 1].location, user );
|
|
544
|
+
const found = env && env[item.id]; // not env?.[item.id] ! …we want to keep the 0
|
|
545
|
+
// Reject `$self.$_column_1`:
|
|
546
|
+
art = setArtifactLink( item, (found?.name?.$inferred === '$internal') ? undefined : found );
|
|
691
547
|
|
|
692
548
|
if (!art) {
|
|
693
|
-
// element was not found in environment
|
|
694
|
-
|
|
695
549
|
// TODO (done?): if `env` was 0, we might set a dependency to induce an
|
|
696
550
|
// illegal-cycle error instead of reporting via `errorNotFound`.
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
if (semantics.notFound === false)
|
|
703
|
-
return null;
|
|
704
|
-
// TODO: streamline funtion arguments (probably: user, path, semantics, prev )
|
|
705
|
-
semantics.notFound( user, item, [ env ], null, prev, path );
|
|
706
|
-
}
|
|
707
|
-
else {
|
|
708
|
-
errorNotFound( item, env, prev, spec, user );
|
|
709
|
-
}
|
|
551
|
+
const notFound = (artItemsCount >= 0) ? semantics.notFound : undefinedItemElement;
|
|
552
|
+
// TODO: streamline function arguments (probably: user, path, semantics, prev )
|
|
553
|
+
// false returned by semantics.navigation: no further error:
|
|
554
|
+
if (env !== false)
|
|
555
|
+
notFound( user, item, [ env ], null, prev, path );
|
|
710
556
|
return null;
|
|
711
557
|
}
|
|
712
|
-
// TODO: what what about extra dependencies if we navigate along
|
|
713
|
-
// associations? See also extra args for environment()
|
|
714
|
-
nav = setSomeNavigationLinkForAssoc( nav, item, art, prev?.target, last, user );
|
|
715
558
|
// need to do that here, because we also need to disallow Service.AutoExposed:elem
|
|
559
|
+
// TODO: but Service.AutoExposed.NotAuto should be fine
|
|
716
560
|
if (isMainRef !== 'all' && artItemsCount === 0 &&
|
|
717
561
|
art.$inferred === 'autoexposed' && !user.$inferred) {
|
|
718
562
|
// Depending on the processing sequence, the following could be a
|
|
@@ -721,92 +565,12 @@ function fns( model ) {
|
|
|
721
565
|
error( 'ref-unexpected-autoexposed', [ item.location, user ], { art },
|
|
722
566
|
// eslint-disable-next-line max-len
|
|
723
567
|
'An auto-exposed entity can\'t be referred to - expose entity $(ART) explicitly' );
|
|
724
|
-
|
|
568
|
+
return null; // continuation semantics: like “not found”
|
|
725
569
|
}
|
|
726
570
|
}
|
|
727
|
-
// Final checks on artifact (could be done in an extra function)
|
|
728
|
-
if (art.$requireElementAccess) { // on some CDS variables
|
|
729
|
-
// Path with only one item, but we expect an element, e.g. `$at.from`.
|
|
730
|
-
signalMissingElementAccess(art, [ last.location, user ]);
|
|
731
|
-
}
|
|
732
571
|
return art;
|
|
733
572
|
}
|
|
734
573
|
|
|
735
|
-
// To be analysed what it does exactly, see changes #3660, #3666:
|
|
736
|
-
function setSomeNavigationLinkForAssoc( nav, item, sub, target, last, user ) {
|
|
737
|
-
if (nav) { // we have already "pseudo-followed" a managed association
|
|
738
|
-
// We currently rely on the check that targetElement references do
|
|
739
|
-
// not (pseudo-) follow associations, otherwise potential redirection
|
|
740
|
-
// there had to be considered, too. Also, fk refs to sub elements in
|
|
741
|
-
// combinations with redirections of the target which directly access
|
|
742
|
-
// the potentially renamed sub elements would be really complex.
|
|
743
|
-
// With our restriction, no renaming must be considered for item.id.
|
|
744
|
-
setTargetReferenceKey( item.id );
|
|
745
|
-
}
|
|
746
|
-
// Now set an _navigation link for managed assocs in ON condition etc
|
|
747
|
-
else if (target && nav != null) {
|
|
748
|
-
// Find the original ref for sub and the original foreign key
|
|
749
|
-
// definition. This way, we do not need the foreign keys with
|
|
750
|
-
// rewritten target element path, which might not be available at
|
|
751
|
-
// this point (rewriteKeys in Resolver Phase 5). If we want to
|
|
752
|
-
// follow associations in foreign key definitions, rewriteKeys must
|
|
753
|
-
// be moved to the on-demand Resolver Phase 2.
|
|
754
|
-
let orig; // for the original target element
|
|
755
|
-
for (let o = sub; o; o = o.value && o.value._artifact) // TODO: or use _origin?
|
|
756
|
-
orig = o;
|
|
757
|
-
nav = (orig._effectiveType || orig).$keysNavigation;
|
|
758
|
-
setTargetReferenceKey( orig.name.id );
|
|
759
|
-
}
|
|
760
|
-
return nav;
|
|
761
|
-
|
|
762
|
-
function setTargetReferenceKey( id ) {
|
|
763
|
-
const node = nav && nav[id];
|
|
764
|
-
nav = null;
|
|
765
|
-
if (node) {
|
|
766
|
-
if (node._artifact) {
|
|
767
|
-
// set the original(!) foreign key for the assoc - the "right" ones
|
|
768
|
-
// after rewriteKeys() is the one with the same name.id
|
|
769
|
-
setLink( item, '_navigation', node._artifact );
|
|
770
|
-
if (item === last)
|
|
771
|
-
return;
|
|
772
|
-
}
|
|
773
|
-
else if (item !== last) {
|
|
774
|
-
nav = node.$keysNavigation;
|
|
775
|
-
return;
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
error( null, [ item.location, user ], {},
|
|
779
|
-
// eslint-disable-next-line max-len
|
|
780
|
-
'You can\'t follow associations other than to elements referred to in a managed association\'s key' );
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
function errorNotFound( item, env, art, spec, user ) {
|
|
785
|
-
if (art.name && art.name.select && art.name.select > 1) {
|
|
786
|
-
// TODO: 'The current query has no element $(MEMBER)' with $self.MEMBER
|
|
787
|
-
// and 'The sub query for alias $(ALIAS) has no element $(MEMBER)'
|
|
788
|
-
// TODO: probably not extra messageId, but text variant
|
|
789
|
-
// TODO: views elements are proxies to query-0 elements, not the same
|
|
790
|
-
// TODO: better message text
|
|
791
|
-
signalNotFound( 'query-undefined-element', [ item.location, user ],
|
|
792
|
-
[ env ], { id: item.id } );
|
|
793
|
-
}
|
|
794
|
-
else if (art.kind === '$parameters') {
|
|
795
|
-
signalNotFound( 'ref-undefined-param', [ item.location, user ],
|
|
796
|
-
[ env ], { art: art._main, id: item.id } );
|
|
797
|
-
}
|
|
798
|
-
else {
|
|
799
|
-
const variant = art.kind === 'aspect' && !art.name && 'aspect';
|
|
800
|
-
signalNotFound( spec.undefinedDef || 'ref-undefined-element', [ item.location, user ],
|
|
801
|
-
[ env ], {
|
|
802
|
-
'#': variant,
|
|
803
|
-
art: (variant ? '' : searchName( art, item.id, 'element' )),
|
|
804
|
-
id: item.id,
|
|
805
|
-
} );
|
|
806
|
-
}
|
|
807
|
-
return null;
|
|
808
|
-
}
|
|
809
|
-
|
|
810
574
|
// Helper functions for resolve[Unchecked]Path, getPath{Root,Item}: -----------
|
|
811
575
|
|
|
812
576
|
function acceptLexical( art, path, semantics, user ) {
|
|
@@ -822,7 +586,7 @@ function fns( model ) {
|
|
|
822
586
|
return true;
|
|
823
587
|
}
|
|
824
588
|
// return !art.$internal && art;
|
|
825
|
-
return art
|
|
589
|
+
return art.name?.$inferred !== '$internal'; // not a compiler-generated internal alias
|
|
826
590
|
}
|
|
827
591
|
|
|
828
592
|
function acceptPathRoot( art, ref, semantics, user ) {
|
|
@@ -859,12 +623,6 @@ function fns( model ) {
|
|
|
859
623
|
// TODO: remove again, should be easy enough in to-csn without.
|
|
860
624
|
if (path.length === 1 && art.kind === '$tableAlias')
|
|
861
625
|
user.$noOrigin = true;
|
|
862
|
-
if (path.length === 1 && !semantics.allowBareSelf && !user.expand && !user.inline) {
|
|
863
|
-
// TODO: better ref-invalid-self
|
|
864
|
-
error( 'ref-unexpected-self', [ head.location, user ], { id: head.id } );
|
|
865
|
-
// TODO: reject bare $projection here (new message id, configurable)
|
|
866
|
-
// not really helpful to attach valid names here (would include $self)
|
|
867
|
-
}
|
|
868
626
|
return art;
|
|
869
627
|
}
|
|
870
628
|
case '$parameters': { // TODO: remove from CC
|
|
@@ -902,11 +660,13 @@ function fns( model ) {
|
|
|
902
660
|
|
|
903
661
|
function typeOfSemantics( user, [ head ] ) {
|
|
904
662
|
// `type of` is only allowed for (sub) elements of main artifacts
|
|
663
|
+
while (!user.kind && user._outer)
|
|
664
|
+
user = user._outer;
|
|
905
665
|
let struct = user;
|
|
906
666
|
while (struct.kind === 'element')
|
|
907
667
|
struct = struct._parent;
|
|
908
668
|
if (struct === user._main && struct.kind !== 'annotation')
|
|
909
|
-
return { dynamic: typeOfParentDict };
|
|
669
|
+
return { dynamic: typeOfParentDict, navigation: staticTarget };
|
|
910
670
|
error( 'type-unexpected-typeof', [ head.location, user ],
|
|
911
671
|
{ keyword: 'type of', '#': struct.kind } );
|
|
912
672
|
return false;
|
|
@@ -916,7 +676,7 @@ function fns( model ) {
|
|
|
916
676
|
return { dynamic: artifactParams, notFound: undefinedParam };
|
|
917
677
|
}
|
|
918
678
|
function paramUnsupported( user, _path, location ) {
|
|
919
|
-
error( 'ref-unexpected-scope', [ location, user ],
|
|
679
|
+
error( 'ref-unexpected-scope', [ location, user ], // TODO: ref-unexpected-param
|
|
920
680
|
// why an extra text for calculated elements? or separate for all?
|
|
921
681
|
{ '#': (user.$syntax === 'calc' ? 'calc' : 'std') } );
|
|
922
682
|
return false;
|
|
@@ -979,9 +739,10 @@ function fns( model ) {
|
|
|
979
739
|
}
|
|
980
740
|
|
|
981
741
|
function targetElements( user, pathItemArtifact ) {
|
|
982
|
-
//
|
|
983
|
-
|
|
984
|
-
|
|
742
|
+
// has already been computed - no further `navigationEnv` args needed
|
|
743
|
+
const env = navigationEnv( pathItemArtifact || user._parent );
|
|
744
|
+
// do not use env?.elements: a `0` should stay a `0`:
|
|
745
|
+
return env && env.elements;
|
|
985
746
|
}
|
|
986
747
|
|
|
987
748
|
function combinedSourcesOrParentElements( user ) {
|
|
@@ -999,16 +760,27 @@ function fns( model ) {
|
|
|
999
760
|
}
|
|
1000
761
|
|
|
1001
762
|
function nestedElements( user ) {
|
|
1002
|
-
|
|
1003
|
-
|
|
763
|
+
const colParent = user._pathHead;
|
|
764
|
+
Functions.effectiveType( colParent ); // set _origin
|
|
765
|
+
const path = colParent?.value?.path;
|
|
766
|
+
if (!path?.length)
|
|
767
|
+
return undefined;
|
|
768
|
+
// also set dependency when navigating along assoc → provide location
|
|
769
|
+
return environment( colParent._origin, path[path.length - 1].location, colParent );
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// Function called via semantics.navigation: ----------------------------------
|
|
773
|
+
// default is function `environment`
|
|
774
|
+
|
|
775
|
+
function artifactsEnv( art ) {
|
|
776
|
+
return art._subArtifacts || Object.create(null);
|
|
1004
777
|
}
|
|
1005
778
|
|
|
1006
|
-
function
|
|
1007
|
-
let env =
|
|
1008
|
-
while (env?.target && !env.targetAspect)
|
|
1009
|
-
env = env._origin || env.type?._artifact;
|
|
779
|
+
function staticTarget( prev ) {
|
|
780
|
+
let env = navigationEnv( prev ); // we do not write dependencies for assoc navigation
|
|
1010
781
|
if (env === 0)
|
|
1011
782
|
return 0;
|
|
783
|
+
// Last try - Composition with targetAspect only (in aspect def):
|
|
1012
784
|
const target = env?.targetAspect;
|
|
1013
785
|
if (target) {
|
|
1014
786
|
if (target.elements)
|
|
@@ -1018,8 +790,28 @@ function fns( model ) {
|
|
|
1018
790
|
return env?.elements || Object.create(null);
|
|
1019
791
|
}
|
|
1020
792
|
|
|
1021
|
-
function
|
|
1022
|
-
|
|
793
|
+
function targetNavigation( art, location, user ) {
|
|
794
|
+
const env = navigationEnv( art, location, user, false );
|
|
795
|
+
// do not use env?.elements: a `0`/false should stay a `0`/false:
|
|
796
|
+
return env && env.elements;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function assocOnNavigation( art, location, user ) {
|
|
800
|
+
const env = navigationEnv( art, location, user, null );
|
|
801
|
+
// `null` means: do not write a dependency from target of any association
|
|
802
|
+
// otherwise “following” own assoc would lead to cycle.
|
|
803
|
+
// TODO: disallow navigation other than of own assoc, and to foreign keys
|
|
804
|
+
// This way (not here though, but later in resolve.js)
|
|
805
|
+
if (env === 0)
|
|
806
|
+
return 0;
|
|
807
|
+
return env?.elements || Object.create(null);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function calcElemNavigation( art, location, user ) {
|
|
811
|
+
const env = navigationEnv( art, location, user, 'calc' );
|
|
812
|
+
if (env === 0)
|
|
813
|
+
return 0;
|
|
814
|
+
return env?.elements || Object.create(null);
|
|
1023
815
|
}
|
|
1024
816
|
|
|
1025
817
|
// Return effective search environment provided by artifact `art`, i.e. the
|
|
@@ -1027,13 +819,47 @@ function fns( model ) {
|
|
|
1027
819
|
// chain and resolve the association `target`. View elements are calculated
|
|
1028
820
|
// on demand.
|
|
1029
821
|
// TODO: what about location/user when called from getPath ?
|
|
1030
|
-
// TODO: think of
|
|
822
|
+
// TODO: think of removing `|| Object.create(null)`.
|
|
1031
823
|
// (if not possible, move to second param position)
|
|
1032
|
-
function environment( art, location, user
|
|
1033
|
-
const env =
|
|
824
|
+
function environment( art, location, user ) {
|
|
825
|
+
const env = navigationEnv( art, location, user, 'nav' );
|
|
1034
826
|
if (env === 0)
|
|
1035
827
|
return 0;
|
|
1036
|
-
return env?.elements ||
|
|
828
|
+
return env?.elements || Object.create(null);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function navigationEnv( art, location, user, assocSpec ) {
|
|
832
|
+
// = effectiveType() on from-path, TODO: should actually already part of
|
|
833
|
+
// resolvePath() on FROM
|
|
834
|
+
if (!art)
|
|
835
|
+
return undefined;
|
|
836
|
+
let type = Functions.effectiveType( art );
|
|
837
|
+
while (type?.items) // TODO: disallow navigation to many sometimes
|
|
838
|
+
type = Functions.effectiveType( type.items );
|
|
839
|
+
if (!type?.target)
|
|
840
|
+
return type;
|
|
841
|
+
|
|
842
|
+
if (assocSpec === false) { // TODO: move to getPathItem
|
|
843
|
+
error( null, [ location, user ], {},
|
|
844
|
+
'Following an association is not allowed in an association key definition' );
|
|
845
|
+
return false;
|
|
846
|
+
} // TODO: else warning for assoc usage with falsy assocSpec
|
|
847
|
+
const target = type?.target._artifact;
|
|
848
|
+
if (!target)
|
|
849
|
+
return target;
|
|
850
|
+
// TODO: really write final dependency with expand/inline?
|
|
851
|
+
if (target && assocSpec && user) {
|
|
852
|
+
if (assocSpec !== 'calc')
|
|
853
|
+
dependsOn( user._main || user, target, location || user.location, user );
|
|
854
|
+
else // (TODO: users of) calc elements must depend on navigation target
|
|
855
|
+
dependsOn( user, target, location || user.location );
|
|
856
|
+
// TODO: have some _delayedDeps for calc elements
|
|
857
|
+
}
|
|
858
|
+
const effectiveTarget = Functions.effectiveType( target );
|
|
859
|
+
// if (effectiveTarget === 0 && location)
|
|
860
|
+
// dependsOn( user, user, (user.target || user.type || user.value || user).location );
|
|
861
|
+
// console.log('NT:',assocSpec,!!user,target)
|
|
862
|
+
return effectiveTarget;
|
|
1037
863
|
}
|
|
1038
864
|
|
|
1039
865
|
// Functions called via semantics.notFound: -----------------------------------
|
|
@@ -1072,10 +898,12 @@ function fns( model ) {
|
|
|
1072
898
|
// TODO: avoid message if we have already complained about `(exists …)`?
|
|
1073
899
|
const { id } = head;
|
|
1074
900
|
const isVar = id.charAt( 0 ) === '$' && id !== '$self';
|
|
901
|
+
// TODO: for wrong $self, also use ref-undefined-var, but with extra msg id
|
|
902
|
+
// otherwise, use s/th like ref-unexpected-element
|
|
1075
903
|
signalNotFound( (isVar ? 'ref-undefined-var' : 'ref-expecting-const'),
|
|
1076
904
|
[ head.location, user ],
|
|
1077
905
|
valid, { '#': 'std', id } );
|
|
1078
|
-
// TODO: use s/th better than 'ref-expecting-const'
|
|
906
|
+
// TODO: use s/th better than 'ref-expecting-const' !!
|
|
1079
907
|
}
|
|
1080
908
|
|
|
1081
909
|
function undefinedSourceElement( user, head, valid, dynamicDict ) {
|
|
@@ -1110,7 +938,7 @@ function fns( model ) {
|
|
|
1110
938
|
}
|
|
1111
939
|
else {
|
|
1112
940
|
// TODO: extra msg like ref-rejected-on if elem found in source elements?
|
|
1113
|
-
// also whether users
|
|
941
|
+
// also whether users wrongly tried to refer to aliases/mixins?
|
|
1114
942
|
const msgVar = userQuery( user ) ? 'query' : null;
|
|
1115
943
|
// TODO: better with ON in expand if that is supported
|
|
1116
944
|
signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
|
|
@@ -1132,16 +960,489 @@ function fns( model ) {
|
|
|
1132
960
|
return null;
|
|
1133
961
|
}
|
|
1134
962
|
|
|
1135
|
-
function undefinedNestedElement( user, head, valid ) {
|
|
1136
|
-
// environment( user._pathHead ); // set _origin
|
|
963
|
+
function undefinedNestedElement( user, head, valid, _dict, _art, path ) {
|
|
1137
964
|
const art = user._pathHead._origin;
|
|
1138
|
-
// if (!art) console.log('UNE:',user,user._pathHead)
|
|
1139
965
|
if (!art)
|
|
1140
|
-
return;
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
966
|
+
return null; // no consequential error
|
|
967
|
+
return undefinedItemElement( user, head, valid, null, art, path );
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
function undefinedItemElement( user, item, valid, _dict, art, path ) {
|
|
971
|
+
if (art.name && art.name.select && art.name.select > 1) {
|
|
972
|
+
// TODO: 'The current query has no element $(MEMBER)' with $self.MEMBER
|
|
973
|
+
// and 'The sub query for alias $(ALIAS) has no element $(MEMBER)'
|
|
974
|
+
// both as text variants to ref-undefined-element
|
|
975
|
+
signalNotFound( 'query-undefined-element', [ item.location, user ],
|
|
976
|
+
valid, { id: item.id } );
|
|
977
|
+
}
|
|
978
|
+
else if (art.kind === '$parameters') {
|
|
979
|
+
signalNotFound( 'ref-undefined-param', [ item.location, user ],
|
|
980
|
+
valid, { art: art._main, id: item.id } );
|
|
981
|
+
}
|
|
982
|
+
else if (art.kind === 'builtin') { // magic variable / replacement variable
|
|
983
|
+
const id = (item === path[path.length - 1])
|
|
984
|
+
? item.id
|
|
985
|
+
: pathName( path.slice( path.indexOf( item ) ) );
|
|
986
|
+
signalNotFound( (art.$uncheckedElements ? 'ref-unknown-var' : 'ref-undefined-var'),
|
|
987
|
+
[ item.location, user ], valid,
|
|
988
|
+
{ id: `${ art.name.element }.${ id }` } );
|
|
989
|
+
}
|
|
990
|
+
else {
|
|
991
|
+
const variant = art.kind === 'aspect' && !art.name && 'aspect';
|
|
992
|
+
signalNotFound( 'ref-undefined-element', [ item.location, user ],
|
|
993
|
+
valid, {
|
|
994
|
+
'#': variant,
|
|
995
|
+
art: (variant ? '' : searchName( art, item.id, 'element' )),
|
|
996
|
+
id: item.id,
|
|
997
|
+
} );
|
|
998
|
+
}
|
|
999
|
+
return null;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Functions called via semantics.accept: -------------------------------------
|
|
1003
|
+
// function arguments ( art, user, ref, semantics ),
|
|
1004
|
+
// default (for elements only): acceptElemOrVar
|
|
1005
|
+
|
|
1006
|
+
function acceptElemOrVarOrSelf( art, user, ref ) {
|
|
1007
|
+
// TODO: make $self._artifact point to the $self alias, not the entity
|
|
1008
|
+
return (!(art._main && art.kind !== 'select') && ref.path[0]._navigation?.kind === '$self')
|
|
1009
|
+
? art
|
|
1010
|
+
: acceptElemOrVar( art, user, ref );
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
function acceptElemOrVar( art, user, ref ) {
|
|
1014
|
+
const { path } = ref;
|
|
1015
|
+
if (art.kind === 'builtin') {
|
|
1016
|
+
if (user.expand || user.inline) {
|
|
1017
|
+
const location = (user.expand || user.inline)[$location];
|
|
1018
|
+
const code = (user.expand) ? '{ ‹expand› }' : '.{ ‹inline› }';
|
|
1019
|
+
message( 'def-unexpected-nested-proj', [ location, user ], { '#': 'var', code } );
|
|
1020
|
+
}
|
|
1021
|
+
else if (art.$requireElementAccess) { // on some CDS variables
|
|
1022
|
+
// Path with only one item, but we expect an element, e.g. `$at.from`.
|
|
1023
|
+
signalMissingElementAccess(art, [ path[0].location, user ]);
|
|
1024
|
+
return null;
|
|
1025
|
+
}
|
|
1026
|
+
else if (art.$autoElement) {
|
|
1027
|
+
const { location } = path[0];
|
|
1028
|
+
const step = { id: art.$autoElement, $inferred: '$autoElement', location };
|
|
1029
|
+
path.push( step );
|
|
1030
|
+
art = art.elements[step.id];
|
|
1031
|
+
return setArtifactLink( step, art );
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
// TODO: combine $requireElementAccess/$autoElement to $bareRoot ?
|
|
1035
|
+
else if (!user.expand && !user.inline && // $self._artifact to main artifact
|
|
1036
|
+
!(art._main && art.kind !== 'select') && ref.path[0]._navigation?.kind === '$self') {
|
|
1037
|
+
// TODO: better ref-invalid-self
|
|
1038
|
+
const { location, id } = path[0];
|
|
1039
|
+
error( 'ref-unexpected-self', [ location, user ], { id } );
|
|
1040
|
+
// TODO: reject bare $projection here (new message id, configurable)
|
|
1041
|
+
// TODO: should we also attach valid names? Probably not...
|
|
1042
|
+
// TODO: return false; ??
|
|
1043
|
+
// return false;
|
|
1044
|
+
}
|
|
1045
|
+
return art;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
function acceptStructOrBare( art, user, ref ) { // for includes[]
|
|
1049
|
+
// It had been checked before that `includes` is already forbidden for
|
|
1050
|
+
// non-entity/aspect/type/event.
|
|
1051
|
+
//
|
|
1052
|
+
// We currently disallow as include:
|
|
1053
|
+
// - non-structured types or derived type of structured:
|
|
1054
|
+
// would have to follow type in extend/include;
|
|
1055
|
+
// - entities with params: clarify inheritance, use of param in ON/DEFAULT;
|
|
1056
|
+
// - query entities/events: difficult sequence of resolve steps
|
|
1057
|
+
// - aspect with one ore more elements on query entities / events
|
|
1058
|
+
// - aspect with `elements` property on non-structured types
|
|
1059
|
+
|
|
1060
|
+
// TODO: adapt `user` if it is an `extend`? NOTE: we cannot call
|
|
1061
|
+
// effectiveType() on user - it might be in the process of being computed!
|
|
1062
|
+
// Also, it is not clear whether `art.elements` has been completed → testing
|
|
1063
|
+
// its length might be processing-sequence dependent, see #11346. We must
|
|
1064
|
+
// ensure that an include does not add the `elements` property!
|
|
1065
|
+
const base = (user.kind === 'extend' ? user.name._artifact : user);
|
|
1066
|
+
if (!base)
|
|
1067
|
+
return art;
|
|
1068
|
+
if (base.query || base.type || !base.elements) {
|
|
1069
|
+
// Remark: it is not necessary to test for user.elements[$inferred], because
|
|
1070
|
+
// the type could only have inferred elements if it has a type expression.
|
|
1071
|
+
// Including aspects with elements is forbidden for aspects without the
|
|
1072
|
+
// `elements` property (TODO: ensure that, see #11346). Testing for the length of
|
|
1073
|
+
// `art.elements` requires that we have applied potential `includes` of
|
|
1074
|
+
// `art` before!
|
|
1075
|
+
if (art.kind === 'aspect' &&
|
|
1076
|
+
(!art.elements || base.query && !Object.keys( art.elements ).length))
|
|
1077
|
+
return art;
|
|
1078
|
+
signalNotFound( 'ref-invalid-include', [ ref.location, user ], null,
|
|
1079
|
+
{ '#': 'bare' } );
|
|
1080
|
+
}
|
|
1081
|
+
else {
|
|
1082
|
+
if (!art.query && !art.type && !art.params && (art.elements || art.kind === 'aspect'))
|
|
1083
|
+
return art;
|
|
1084
|
+
signalNotFound( 'ref-invalid-include', [ ref.location, user ], null );
|
|
1085
|
+
}
|
|
1086
|
+
return false;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// Remember: an aspect should have already been moved to XSN targetAspect, but
|
|
1090
|
+
// the error messages should still talk about potential aspects
|
|
1091
|
+
function acceptEntity( art, user, ref ) { // for target
|
|
1092
|
+
if (art.kind === 'entity')
|
|
1093
|
+
return art;
|
|
1094
|
+
// Extra msg text with Composition of NeitherEntityNorAspect:
|
|
1095
|
+
const bare = !art.elements || art.elements[$inferred];
|
|
1096
|
+
const std = (art.targetAspect || !isDirectComposition( user ) || user.kind === 'mixin');
|
|
1097
|
+
const msg = std && 'std' || (bare && art.kind === 'aspect' ? 'bare' : 'composition');
|
|
1098
|
+
signalNotFound( 'ref-invalid-target', [ ref.location, user ], null,
|
|
1099
|
+
{ '#': msg } );
|
|
1100
|
+
return false;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
function acceptAspect( art, user, ref ) { // for targetAspect
|
|
1104
|
+
const bare = !art.elements || art.elements[$inferred];
|
|
1105
|
+
if (!bare) {
|
|
1106
|
+
if (art.kind === 'aspect')
|
|
1107
|
+
return art;
|
|
1108
|
+
if (art.kind === 'type') { // v4: Warning → config Error
|
|
1109
|
+
signalNotFound( 'ref-sloppy-target', [ ref.location, user ], null );
|
|
1110
|
+
return art;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
signalNotFound( 'ref-invalid-target', [ ref.location, user ], null,
|
|
1114
|
+
{ '#': (bare ? 'bare' : 'aspect'), prop: 'targetAspect' } );
|
|
1115
|
+
return false;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
function acceptEntityOrAssoc( art, user, ref ) { // for FROM
|
|
1119
|
+
const { path, scope } = ref;
|
|
1120
|
+
// see getPathItem(): how many path items are for the main artifact ref?
|
|
1121
|
+
const artItemsCount = (typeof scope === 'number' && scope) || (scope ? 1 : path.length);
|
|
1122
|
+
// at least the last main definition should be an entity
|
|
1123
|
+
// an additional check for target would need effectiveType()
|
|
1124
|
+
const source = path[artItemsCount - 1]._artifact;
|
|
1125
|
+
if (source.kind !== 'entity') {
|
|
1126
|
+
signalNotFound( 'ref-invalid-source', [ ref.location, user ], null );
|
|
1127
|
+
return false;
|
|
1128
|
+
}
|
|
1129
|
+
if (source === art)
|
|
1130
|
+
return art;
|
|
1131
|
+
const assoc = Functions.effectiveType( art );
|
|
1132
|
+
if (assoc.target)
|
|
1133
|
+
return art; // TODO: use target here
|
|
1134
|
+
signalNotFound( 'ref-invalid-source', [ ref.location, user ], null );
|
|
1135
|
+
return false;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
function acceptTypeOrElement( art, user, ref ) { // for type
|
|
1139
|
+
// was ['action', 'function'].includes( user._parent?.kind ))
|
|
1140
|
+
while (user._outer)
|
|
1141
|
+
user = user._outer;
|
|
1142
|
+
const kind = (user.kind !== 'param' || user._parent?.kind !== 'entity')
|
|
1143
|
+
? user.kind
|
|
1144
|
+
: 'entity-param';
|
|
1145
|
+
switch (art.kind) {
|
|
1146
|
+
case 'type':
|
|
1147
|
+
case 'element':
|
|
1148
|
+
return art;
|
|
1149
|
+
case 'entity':
|
|
1150
|
+
if (kind === 'param' && art._service)
|
|
1151
|
+
return art;
|
|
1152
|
+
// FALLTHROUGH
|
|
1153
|
+
case 'event':
|
|
1154
|
+
if (kind === 'event')
|
|
1155
|
+
return art;
|
|
1156
|
+
break;
|
|
1157
|
+
default:
|
|
1158
|
+
break;
|
|
1159
|
+
}
|
|
1160
|
+
signalNotFound( 'ref-invalid-type', [ ref.location, user ], null, { '#': kind } );
|
|
1161
|
+
return false;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// Functions called via semantics.check by checkExpr(): -----------------------
|
|
1165
|
+
//
|
|
1166
|
+
// function arguments: ( expr, exprCtx, user )
|
|
1167
|
+
// default: tbd (nothing for main artifac ref)
|
|
1168
|
+
|
|
1169
|
+
// Performs checks which would be too early to do via semantics.accept. It is
|
|
1170
|
+
// actually assumed that the foreign-keys / ON-condition rewrite has already
|
|
1171
|
+
// been done.
|
|
1172
|
+
|
|
1173
|
+
// Main check area "navigation" (see also semantics.navigation):
|
|
1174
|
+
// - navigation along any assoc
|
|
1175
|
+
// - navigation only along foreign keys
|
|
1176
|
+
// - (no navigation already via semantics.navigation for target refs of foreign keys)
|
|
1177
|
+
// - special (ON-condition of unmanaged associations)
|
|
1178
|
+
|
|
1179
|
+
// Main check area: checks on the referred artifact
|
|
1180
|
+
// - all artifacts are allowed
|
|
1181
|
+
// - all except unmanaged associations
|
|
1182
|
+
// - ...
|
|
1183
|
+
|
|
1184
|
+
function checkExpr( expr, exprCtx, user ) {
|
|
1185
|
+
if (!expr)
|
|
1186
|
+
return;
|
|
1187
|
+
const s = referenceSemantics[exprCtx];
|
|
1188
|
+
const semantics = (typeof s === 'string') ? referenceSemantics[s] : s;
|
|
1189
|
+
const checkFn = semantics.check; // || !semantics.isMainRef && checkElementStd;
|
|
1190
|
+
|
|
1191
|
+
if (checkFn)
|
|
1192
|
+
traverseExpr( expr, exprCtx, user, checkFn );
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
// TODO: Don't allow path args and filter!
|
|
1196
|
+
function checkOnCondition( expr, exprCtx, user ) {
|
|
1197
|
+
if (!expr || expr.$inferred)
|
|
1198
|
+
return;
|
|
1199
|
+
const { op } = expr;
|
|
1200
|
+
let { args } = expr;
|
|
1201
|
+
if (!op || !args) {
|
|
1202
|
+
checkExpr( expr, exprCtx, user );
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
if (op?.val === '=') // TMP
|
|
1206
|
+
args = [ args[0], { val: '=', literal: 'token' }, args[1] ];
|
|
1207
|
+
|
|
1208
|
+
for (let index = 0; index < args.length; ++index) {
|
|
1209
|
+
const item = args[index];
|
|
1210
|
+
const eq = args[index + 1];
|
|
1211
|
+
if (eq?.val === '=' && eq.literal === 'token' && item.path && !item.scope) {
|
|
1212
|
+
const right = args[index + 2];
|
|
1213
|
+
if (right?.path && !right.scope &&
|
|
1214
|
+
(isDollarSelfPair( item, right, user ) || isDollarSelfPair( right, item, user ))) {
|
|
1215
|
+
checkAssocOnSelf( item, exprCtx, user );
|
|
1216
|
+
checkAssocOnSelf( right, exprCtx, user );
|
|
1217
|
+
index += 2;
|
|
1218
|
+
continue;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
checkOnCondition( item, exprCtx, user );
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// // standard element reference check;
|
|
1226
|
+
// function checkElementStd( art, _user, ref, semantics ) {
|
|
1227
|
+
// // No further checks on navigation: nothing to do
|
|
1228
|
+
// // Must not end with any association (TODO: allow managed?):
|
|
1229
|
+
// if (art.target) { // && art.on
|
|
1230
|
+
// // error
|
|
1231
|
+
// }
|
|
1232
|
+
// }
|
|
1233
|
+
|
|
1234
|
+
function checkColumnRef( expr, exprCtx, user ) {
|
|
1235
|
+
if (!expr.path)
|
|
1236
|
+
return;
|
|
1237
|
+
if (expr === user.value && // is already a syntax error with non-ref expression
|
|
1238
|
+
(user.expand || user.inline))
|
|
1239
|
+
checkExpandInlineRef( expr._artifact, user, expr );
|
|
1240
|
+
|
|
1241
|
+
const self = pathStartsWithSelf( expr );
|
|
1242
|
+
// console.log('NAV:',expr.path.map(r=>r.id),self)
|
|
1243
|
+
if (self || self == null && columnRefStartsWithSelf( user )) {
|
|
1244
|
+
checkOnlyForeignKeyNavigation( user, expr.path, 0, 'self-' );
|
|
1245
|
+
checkNoUnmanaged( expr, user, 'self-unmanaged' );
|
|
1246
|
+
}
|
|
1247
|
+
// TODO: set navigation dependencies later to avoid both ref-cyclic and
|
|
1248
|
+
// ref-invalid-navigation/ref-unexpected-assoc
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
function checkOrderByRef( expr, exprCtx, user ) {
|
|
1252
|
+
const { path } = expr;
|
|
1253
|
+
if (!path)
|
|
1254
|
+
return;
|
|
1255
|
+
if (path?.[0]?._navigation?.kind !== '$tableAlias')
|
|
1256
|
+
checkOnlyForeignKeyNavigation( user, expr.path );
|
|
1257
|
+
checkNoUnmanaged( expr, user );
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
function checkRefInQuery( expr, exprCtx, user ) {
|
|
1261
|
+
const { path } = expr;
|
|
1262
|
+
if (!path)
|
|
1263
|
+
return;
|
|
1264
|
+
if (pathStartsWithSelf( expr ))
|
|
1265
|
+
checkOnlyForeignKeyNavigation( user, expr.path, 0, 'self-' );
|
|
1266
|
+
checkNoUnmanaged( expr, user );
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
function checkExpandInlineRef( art, user, ref ) {
|
|
1270
|
+
if (!art || !art._main || // $self has entity as _artifact
|
|
1271
|
+
art.kind === 'builtin') // no repeated error for CDS variables
|
|
1272
|
+
return;
|
|
1273
|
+
const effective = art._effectiveType;
|
|
1274
|
+
if (!effective || effective.target || effective.elements)
|
|
1275
|
+
return;
|
|
1276
|
+
const { path } = ref;
|
|
1277
|
+
const location = (user.expand || user.inline)[$location];
|
|
1278
|
+
// mention `table alias` in text only with initial single path item ref,
|
|
1279
|
+
// but do not mention that $self { … } is allowed, shouldn't be advertised:
|
|
1280
|
+
const txt = (path.length > 1 || user._pathHead) ? 'struct' : 'init';
|
|
1281
|
+
const code = (user.expand) ? '{ ‹expand› }' : '.{ ‹inline› }';
|
|
1282
|
+
message( 'def-unexpected-nested-proj', [ location, user ], { '#': txt, code } );
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
function isDollarSelfPair( left, right, user ) {
|
|
1286
|
+
if (!left.path || !right.path || left.scope || right.scope)
|
|
1287
|
+
return false; // param ref `:$self` is not $self
|
|
1288
|
+
// $self in entity (TODO: mixin? new assoc in col? in aspect?)
|
|
1289
|
+
const kind = right._artifact?.kind;
|
|
1290
|
+
if (kind !== 'entity' && kind !== 'aspect' && kind !== 'select')
|
|
1291
|
+
return false; // (ok, this would also return `false` for `:$self`)
|
|
1292
|
+
const { path } = left;
|
|
1293
|
+
// TODO: we might return true and issue an extra error here
|
|
1294
|
+
return userTargetElementPathIndex( user, path ) > 0;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
function checkAssocOn( ref, exprCtx, user ) {
|
|
1298
|
+
const { path } = ref;
|
|
1299
|
+
if (!path)
|
|
1300
|
+
return;
|
|
1301
|
+
if (path.length === 1 && path[0]._navigation?.kind === '$self') {
|
|
1302
|
+
// resolvePath() for `on` allowed bare $self: disallow except in checkAssocOnSelf
|
|
1303
|
+
const { location, id } = path[0];
|
|
1304
|
+
error( 'ref-unexpected-self', [ location, user ], { '#': 'on', id } );
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
const index = userTargetElementPathIndex( user, path );
|
|
1308
|
+
checkOnlyForeignKeyNavigation( user, path, index );
|
|
1309
|
+
if (ref._artifact?.on) {
|
|
1310
|
+
const target = index > 0 && index < path.length && ref._artifact?.target;
|
|
1311
|
+
const msg = (target?._artifact === user._main) ? 'self' : 'unmanaged';
|
|
1312
|
+
const last = path[path.length - 1];
|
|
1313
|
+
error( 'ref-unexpected-assoc', [ last.location, user ],
|
|
1314
|
+
{ '#': msg, code: '= $self' } );
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
function checkAssocOnSelf( ref, exprCtx, user ) {
|
|
1319
|
+
// TODO: fully specify what `‹current_assoc›.‹backlink› = $self` means if the
|
|
1320
|
+
// target of ‹backlink› is not the current entity.
|
|
1321
|
+
// - what does it mean in an aspect
|
|
1322
|
+
// - how about auto-redirections/rewrite
|
|
1323
|
+
const { path } = ref;
|
|
1324
|
+
if (path.length === 1 && path[0]._navigation?.kind === '$self') {
|
|
1325
|
+
const query = userQuery( user );
|
|
1326
|
+
const main = query?._main;
|
|
1327
|
+
if (query && query !== main._leadingQuery) {
|
|
1328
|
+
const { txt, op } = getQueryOperatorName( query );
|
|
1329
|
+
const { location, id } = path[0];
|
|
1330
|
+
error( 'ref-unexpected-self', [ location, user ], { '#': txt, id, op } );
|
|
1331
|
+
}
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
const index = userTargetElementPathIndex( user, path );
|
|
1335
|
+
checkOnlyForeignKeyNavigation( user, path, index );
|
|
1336
|
+
const target = index > 0 && index < path.length && ref._artifact?.target;
|
|
1337
|
+
if (!target) {
|
|
1338
|
+
const last = path[path.length - 1];
|
|
1339
|
+
error( 'ref-expecting-target-assoc', [ last.location, user ], { id: '$self' },
|
|
1340
|
+
'Only an association of the target side can be compared to $(ID)' );
|
|
1341
|
+
}
|
|
1342
|
+
// in entity: target must match
|
|
1343
|
+
// in aspect: must end with assoc, TODO: target must include aspect (+ add/ check)
|
|
1344
|
+
else if (target._artifact && target._artifact !== user._main && user._main.kind === 'entity') {
|
|
1345
|
+
const last = path[path.length - 1];
|
|
1346
|
+
warning( 'ref-invalid-backlink', [ last.location, user ], { art: target, id: '$self' },
|
|
1347
|
+
// eslint-disable-next-line max-len
|
|
1348
|
+
'The target $(ART) of the association is not the current entity represented by $(ID)' );
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
|
|
1353
|
+
// right of union: parent = main → get correct operator
|
|
1354
|
+
// FROM subquery: parent = tab alias of outer query
|
|
1355
|
+
// other sub query: parent = outer query
|
|
1356
|
+
function getQueryOperatorName( query ) {
|
|
1357
|
+
if (query._parent !== query._main)
|
|
1358
|
+
return { txt: 'subQuery', op: '' };
|
|
1359
|
+
|
|
1360
|
+
for (let set = query._main.query; set.op; set = set.args[0]) {
|
|
1361
|
+
const right = set.args[1];
|
|
1362
|
+
if (query.name.select >= (right._leadingQuery || right).name.select)
|
|
1363
|
+
return { txt: 'setQuery', op: set.op.val };
|
|
1364
|
+
}
|
|
1365
|
+
throw new CompilerAssertion( 'Did we pass the leading query as argument?' );
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
function userTargetElementPathIndex( user, path ) {
|
|
1369
|
+
const head = path[0];
|
|
1370
|
+
if (head._artifact === user) // standard case
|
|
1371
|
+
return 1;
|
|
1372
|
+
if (head._navigation?.kind !== '$self')
|
|
1373
|
+
return 0;
|
|
1374
|
+
for (let index = 1; index < path.length; ++index) {
|
|
1375
|
+
const assoc = path[index]?._artifact;
|
|
1376
|
+
if (assoc?.target)
|
|
1377
|
+
return (assoc === user) ? index + 1 : 0;
|
|
1378
|
+
}
|
|
1379
|
+
return 0;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
function checkOnlyForeignKeyNavigation( user, path, startIndex = 0, msgPrefix = '' ) {
|
|
1383
|
+
// has to be run after foreign-key rewrite
|
|
1384
|
+
const outer = user._pathHead?._origin;
|
|
1385
|
+
let assoc = outer?.foreignKeys &&
|
|
1386
|
+
pathStartsWithSelf( { path } ) == null && // not $self or CDS var like $now
|
|
1387
|
+
outer;
|
|
1388
|
+
for (let index = startIndex; index < path.length; ++index) {
|
|
1389
|
+
if (assoc?.target) {
|
|
1390
|
+
if (!assoc.foreignKeys) {
|
|
1391
|
+
error( 'ref-unexpected-assoc', [ path[index - 1].location, user ],
|
|
1392
|
+
{ '#': `${ msgPrefix }unmanaged`, alias: '$self' } );
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
if (!assoc.$keysNavigation)
|
|
1396
|
+
Functions.addForeignKeyNavigations( assoc, true );
|
|
1397
|
+
index = checkCoveredByForeignKey( assoc, path, index, user, msgPrefix );
|
|
1398
|
+
}
|
|
1399
|
+
assoc = path[index]?._artifact;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
function checkCoveredByForeignKey( nav, path, index, user, msgPrefix = '' ) {
|
|
1404
|
+
const assoc = nav;
|
|
1405
|
+
while (index < path.length) {
|
|
1406
|
+
const item = path[index];
|
|
1407
|
+
nav = nav.$keysNavigation?.[item.id];
|
|
1408
|
+
if (!nav)
|
|
1409
|
+
break;
|
|
1410
|
+
if (nav._artifact)
|
|
1411
|
+
return index;
|
|
1412
|
+
++index;
|
|
1413
|
+
}
|
|
1414
|
+
const last = path[index] || path[index - 1];
|
|
1415
|
+
// TODO: or just location of `last`?
|
|
1416
|
+
// TODO: extra text variant if the foreign keys are the key elements !
|
|
1417
|
+
// eslint-disable-next-line no-nested-ternary
|
|
1418
|
+
const txt = index >= path.length
|
|
1419
|
+
? 'complete'
|
|
1420
|
+
: (isAssocToPrimaryKeys( assoc ) ? 'keys' : 'std');
|
|
1421
|
+
// eslint-disable-next-line max-len
|
|
1422
|
+
error( 'ref-invalid-navigation', [ last.location, user ], {
|
|
1423
|
+
'#': msgPrefix + txt, art: assoc, name: last.id, alias: '$self',
|
|
1424
|
+
}, {
|
|
1425
|
+
std: 'Can follow association $(ART) only to its foreign key references, not to $(NAME)',
|
|
1426
|
+
keys: 'Can follow managed association $(ART) only to the keys of its target, not to $(NAME)',
|
|
1427
|
+
complete: 'The reference must cover a full foreign key reference of association $(ART)',
|
|
1428
|
+
// eslint-disable-next-line max-len
|
|
1429
|
+
'self-std': 'In column ref starting with $(ALIAS), we can follow association $(ART) only to its foreign key references, not to $(NAME)',
|
|
1430
|
+
// eslint-disable-next-line max-len
|
|
1431
|
+
'self-keys': 'In column ref starting with $(ALIAS), we can follow managed association $(ART) only to the keys of its target, not to $(NAME)',
|
|
1432
|
+
// eslint-disable-next-line max-len
|
|
1433
|
+
'self-complete': 'The column reference starting with $(ALIAS) must cover a full foreign key reference of association $(ART)',
|
|
1434
|
+
} );
|
|
1435
|
+
// TODO later: mention allowed ones
|
|
1436
|
+
return path.length;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
function checkNoUnmanaged( ref, user, messageVariant = 'unmanaged' ) {
|
|
1440
|
+
if (ref._artifact?.on && !ref.$expected) {
|
|
1441
|
+
const { path } = ref;
|
|
1442
|
+
const last = path[path.length - 1];
|
|
1443
|
+
error( 'ref-unexpected-assoc', [ last.location, user ],
|
|
1444
|
+
{ '#': messageVariant, alias: '$self' } );
|
|
1445
|
+
}
|
|
1145
1446
|
}
|
|
1146
1447
|
|
|
1147
1448
|
// Low-level functions --------------------------------------------------------
|
|
@@ -1155,7 +1456,7 @@ function fns( model ) {
|
|
|
1155
1456
|
* @param {object} [textParams]
|
|
1156
1457
|
*/
|
|
1157
1458
|
function signalNotFound( msgId, location, valid, textParams ) {
|
|
1158
|
-
if (location.$notFound)
|
|
1459
|
+
if (location.$notFound) // TODO: still necessary?
|
|
1159
1460
|
return;
|
|
1160
1461
|
location.$notFound = true;
|
|
1161
1462
|
/** @type {object} */
|
|
@@ -1177,6 +1478,7 @@ function fns( model ) {
|
|
|
1177
1478
|
* @param {any} location
|
|
1178
1479
|
*/
|
|
1179
1480
|
function signalMissingElementAccess( art, location ) {
|
|
1481
|
+
// TODO: ref-undefined-var ?
|
|
1180
1482
|
const err = message( 'ref-expected-element', location,
|
|
1181
1483
|
{ '#': 'magicVar', id: art.name.id } );
|
|
1182
1484
|
// Mapping for better valid names: from -> $at.from
|
|
@@ -1205,7 +1507,7 @@ function fns( model ) {
|
|
|
1205
1507
|
const art = valid[name];
|
|
1206
1508
|
// ignore internal types such as cds.Association, ignore names with dot for
|
|
1207
1509
|
// CDL references to main artifacts:
|
|
1208
|
-
if (!art.internal && !art.deprecated && art
|
|
1510
|
+
if (!art.internal && !art.deprecated && art.name?.$inferred !== '$internal' &&
|
|
1209
1511
|
(viaCdl ? art._main || !name.includes('.') : art.kind !== 'namespace'))
|
|
1210
1512
|
msg.validNames[name] = art;
|
|
1211
1513
|
}
|