@sap/cds-compiler 4.0.2 → 4.2.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 +200 -5
- package/bin/cdsc.js +18 -15
- package/doc/CHANGELOG_BETA.md +16 -0
- package/doc/CHANGELOG_DEPRECATED.md +15 -0
- package/lib/api/main.js +33 -13
- package/lib/api/options.js +2 -2
- package/lib/api/validate.js +25 -25
- package/lib/base/location.js +6 -7
- package/lib/base/message-registry.js +123 -42
- package/lib/base/messages.js +18 -10
- package/lib/base/model.js +43 -10
- package/lib/checks/defaultValues.js +6 -6
- package/lib/checks/elements.js +11 -10
- package/lib/checks/foreignKeys.js +0 -5
- package/lib/checks/manyNavigations.js +33 -0
- package/lib/checks/onConditions.js +22 -14
- package/lib/checks/queryNoDbArtifacts.js +132 -73
- package/lib/checks/selectItems.js +4 -55
- package/lib/checks/sql-snippets.js +15 -4
- package/lib/checks/types.js +3 -3
- package/lib/checks/utils.js +4 -3
- package/lib/checks/validator.js +3 -1
- package/lib/compiler/.eslintrc.json +2 -1
- package/lib/compiler/assert-consistency.js +71 -40
- package/lib/compiler/base.js +7 -2
- package/lib/compiler/builtins.js +40 -41
- package/lib/compiler/checks.js +415 -367
- package/lib/compiler/classes.js +62 -0
- package/lib/compiler/cycle-detector.js +9 -9
- package/lib/compiler/define.js +124 -90
- package/lib/compiler/extend.js +115 -88
- package/lib/compiler/finalize-parse-cdl.js +26 -25
- package/lib/compiler/generate.js +57 -49
- package/lib/compiler/index.js +56 -56
- package/lib/compiler/kick-start.js +10 -7
- package/lib/compiler/moduleLayers.js +1 -1
- package/lib/compiler/populate.js +180 -144
- package/lib/compiler/propagator.js +10 -9
- package/lib/compiler/resolve.js +321 -246
- package/lib/compiler/shared.js +812 -433
- package/lib/compiler/tweak-assocs.js +114 -50
- package/lib/compiler/utils.js +241 -46
- package/lib/edm/.eslintrc.json +40 -1
- package/lib/edm/annotations/genericTranslation.js +721 -707
- package/lib/edm/annotations/preprocessAnnotations.js +88 -77
- package/lib/edm/csn2edm.js +389 -378
- package/lib/edm/edm.js +679 -770
- package/lib/edm/edmAnnoPreprocessor.js +132 -146
- package/lib/edm/edmInboundChecks.js +29 -27
- package/lib/edm/edmPreprocessor.js +689 -648
- package/lib/edm/edmUtils.js +279 -300
- 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 +2857 -2856
- package/lib/json/from-csn.js +77 -51
- package/lib/json/to-csn.js +15 -15
- package/lib/language/antlrParser.js +2 -1
- package/lib/language/genericAntlrParser.js +52 -43
- package/lib/language/language.g4 +61 -64
- package/lib/language/multiLineStringParser.js +2 -0
- package/lib/main.d.ts +65 -0
- package/lib/model/csnRefs.js +37 -19
- package/lib/model/csnUtils.js +51 -18
- package/lib/model/revealInternalProperties.js +30 -22
- package/lib/modelCompare/compare.js +149 -41
- package/lib/modelCompare/utils/filter.js +55 -25
- package/lib/optionProcessor.js +21 -9
- package/lib/render/manageConstraints.js +20 -17
- package/lib/render/toCdl.js +63 -23
- package/lib/render/toHdbcds.js +2 -2
- package/lib/render/toRename.js +4 -9
- package/lib/render/toSql.js +82 -35
- package/lib/render/utils/common.js +11 -9
- package/lib/render/utils/unique.js +52 -0
- package/lib/transform/db/applyTransformations.js +62 -21
- package/lib/transform/db/assertUnique.js +7 -8
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/cdsPersistence.js +9 -9
- package/lib/transform/db/constraints.js +47 -17
- package/lib/transform/db/expansion.js +138 -68
- package/lib/transform/db/flattening.js +98 -30
- 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} +10 -7
- package/lib/transform/forRelationalDB.js +148 -136
- package/lib/transform/localized.js +92 -54
- package/lib/transform/odata/toFinalBaseType.js +3 -3
- package/lib/transform/{transformUtilsNew.js → transformUtils.js} +13 -111
- package/lib/transform/translateAssocsToJoins.js +14 -28
- package/lib/utils/file.js +7 -7
- package/lib/utils/moduleResolve.js +210 -121
- package/lib/utils/objectUtils.js +1 -1
- package/package.json +5 -5
- 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
|
|
|
@@ -54,58 +61,74 @@ function fns( model ) {
|
|
|
54
61
|
lexical: userBlock,
|
|
55
62
|
dynamic: modelDefinitions,
|
|
56
63
|
notFound: undefinedDefinition,
|
|
64
|
+
accept: acceptRealArtifact,
|
|
57
65
|
},
|
|
58
66
|
_extensions: {
|
|
59
67
|
isMainRef: 'all',
|
|
60
68
|
lexical: userBlock,
|
|
61
69
|
dynamic: modelDefinitions,
|
|
62
|
-
notFound:
|
|
70
|
+
notFound: () => null, // without message
|
|
63
71
|
},
|
|
64
72
|
include: {
|
|
65
73
|
isMainRef: 'no-generated',
|
|
66
74
|
lexical: userBlock,
|
|
67
75
|
dynamic: modelBuiltinsOrDefinitions,
|
|
68
76
|
notFound: undefinedDefinition,
|
|
77
|
+
accept: acceptStructOrBare,
|
|
78
|
+
},
|
|
79
|
+
_include: { // cyclic include: no accept
|
|
80
|
+
isMainRef: 'no-generated',
|
|
81
|
+
lexical: userBlock,
|
|
82
|
+
dynamic: modelBuiltinsOrDefinitions,
|
|
83
|
+
notFound: undefinedDefinition,
|
|
69
84
|
},
|
|
70
|
-
viewInclude: 'include', // TODO: do differently
|
|
71
85
|
target: {
|
|
72
86
|
isMainRef: 'no-autoexposed',
|
|
73
87
|
lexical: userBlock,
|
|
74
88
|
dynamic: modelBuiltinsOrDefinitions,
|
|
75
89
|
notFound: undefinedDefinition,
|
|
76
|
-
|
|
90
|
+
accept: acceptEntity,
|
|
91
|
+
noDep: true,
|
|
92
|
+
// special `scope`s for auto-redirections:
|
|
77
93
|
global: () => ({ isMainRef: 'all', dynamic: modelDefinitions }),
|
|
78
94
|
},
|
|
79
|
-
targetAspect: {
|
|
95
|
+
targetAspect: {
|
|
80
96
|
isMainRef: 'no-autoexposed',
|
|
81
97
|
lexical: userBlock,
|
|
82
98
|
dynamic: modelBuiltinsOrDefinitions,
|
|
83
99
|
notFound: undefinedDefinition,
|
|
100
|
+
accept: acceptAspect,
|
|
84
101
|
},
|
|
85
102
|
from: {
|
|
86
103
|
isMainRef: 'no-autoexposed',
|
|
87
104
|
lexical: userBlock,
|
|
88
105
|
dynamic: modelBuiltinsOrDefinitions,
|
|
89
|
-
notFound: undefinedDefinition,
|
|
90
106
|
navigation: environment,
|
|
107
|
+
notFound: undefinedDefinition,
|
|
108
|
+
accept: acceptEntityOrAssoc,
|
|
109
|
+
noDep: '', // dependency special for from
|
|
91
110
|
},
|
|
92
111
|
type: {
|
|
93
112
|
isMainRef: 'no-autoexposed',
|
|
94
113
|
lexical: userBlock,
|
|
95
114
|
dynamic: modelBuiltinsOrDefinitions,
|
|
115
|
+
navigation: staticTarget,
|
|
96
116
|
notFound: undefinedDefinition,
|
|
97
|
-
|
|
117
|
+
accept: acceptTypeOrElement,
|
|
98
118
|
// special `scope`s for CDL parser - TYPE OF (TODO generated?), cds.Association:
|
|
99
119
|
typeOf: typeOfSemantics,
|
|
100
|
-
global: () => ({
|
|
120
|
+
global: () => ({
|
|
121
|
+
isMainRef: 'no-autoexposed',
|
|
122
|
+
dynamic: modelDefinitions,
|
|
123
|
+
navigation: staticTarget, // TODO: Object.assign() with main
|
|
124
|
+
}),
|
|
101
125
|
},
|
|
102
|
-
actionParamType: 'type', // TODO: do differently
|
|
103
|
-
eventType: 'type', // TODO: do differently
|
|
104
126
|
// element references without lexical scope (except $self/$projection): -----
|
|
105
127
|
targetElement: {
|
|
106
128
|
lexical: null,
|
|
107
129
|
dollar: false,
|
|
108
130
|
dynamic: targetElements,
|
|
131
|
+
navigation: targetNavigation,
|
|
109
132
|
notFound: undefinedTargetElement,
|
|
110
133
|
param: paramSemantics,
|
|
111
134
|
},
|
|
@@ -120,6 +143,7 @@ function fns( model ) {
|
|
|
120
143
|
lexical: justDollarSelf,
|
|
121
144
|
dollar: true,
|
|
122
145
|
dynamic: targetElements,
|
|
146
|
+
navigation: calcElemNavigation,
|
|
123
147
|
notFound: undefinedTargetElement,
|
|
124
148
|
param: paramUnsupported,
|
|
125
149
|
},
|
|
@@ -130,22 +154,42 @@ function fns( model ) {
|
|
|
130
154
|
notFound: undefinedVariable,
|
|
131
155
|
param: paramUnsupported,
|
|
132
156
|
},
|
|
157
|
+
'limit-rows': {
|
|
158
|
+
lexical: null,
|
|
159
|
+
dollar: true,
|
|
160
|
+
dynamic: () => Object.create( null ),
|
|
161
|
+
notFound: undefinedVariable,
|
|
162
|
+
param: paramSemantics,
|
|
163
|
+
},
|
|
164
|
+
'limit-offset': 'limit-rows',
|
|
133
165
|
// general element references -----------------------------------------------
|
|
134
|
-
|
|
166
|
+
where: {
|
|
167
|
+
lexical: tableAliasesAndSelf,
|
|
168
|
+
dollar: true,
|
|
169
|
+
dynamic: combinedSourcesOrParentElements,
|
|
170
|
+
notFound: undefinedSourceElement,
|
|
171
|
+
check: checkRefInQuery,
|
|
172
|
+
param: paramSemantics,
|
|
173
|
+
},
|
|
174
|
+
having: 'where',
|
|
175
|
+
groupBy: 'where',
|
|
176
|
+
column: {
|
|
135
177
|
lexical: tableAliasesIfNotExtendAndSelf,
|
|
136
178
|
dollar: true,
|
|
137
179
|
dynamic: combinedSourcesOrParentElements,
|
|
138
180
|
notFound: undefinedSourceElement,
|
|
181
|
+
check: checkColumnRef,
|
|
139
182
|
param: paramSemantics,
|
|
140
183
|
nestedColumn: () => ({ // in expand and inline
|
|
141
184
|
lexical: justDollarSelf,
|
|
142
185
|
dollar: true,
|
|
143
186
|
dynamic: nestedElements,
|
|
144
187
|
notFound: undefinedNestedElement,
|
|
188
|
+
check: checkColumnRef,
|
|
145
189
|
param: paramSemantics,
|
|
146
190
|
}),
|
|
147
191
|
},
|
|
148
|
-
'
|
|
192
|
+
'from-args': {
|
|
149
193
|
lexical: null,
|
|
150
194
|
dollar: true,
|
|
151
195
|
dynamic: () => Object.create( null ),
|
|
@@ -156,268 +200,115 @@ function fns( model ) {
|
|
|
156
200
|
lexical: justDollarSelf,
|
|
157
201
|
dollar: true,
|
|
158
202
|
dynamic: parentElements,
|
|
203
|
+
navigation: calcElemNavigation,
|
|
159
204
|
notFound: undefinedParentElement,
|
|
160
205
|
param: paramUnsupported,
|
|
161
206
|
},
|
|
162
|
-
|
|
207
|
+
'join-on': {
|
|
163
208
|
lexical: tableAliasesAndSelf,
|
|
164
209
|
dollar: true,
|
|
165
|
-
dynamic: combinedSourcesOrParentElements,
|
|
210
|
+
dynamic: combinedSourcesOrParentElements,
|
|
211
|
+
rejectRoot: rejectOwnExceptVisibleAliases,
|
|
166
212
|
notFound: undefinedSourceElement,
|
|
167
213
|
param: paramSemantics,
|
|
168
214
|
},
|
|
169
215
|
on: { // unmanaged assoc: outside query, redirected or new assoc in column
|
|
170
216
|
lexical: justDollarSelf,
|
|
171
|
-
allowBareSelf: true,
|
|
172
217
|
dollar: true,
|
|
173
218
|
dynamic: parentElements,
|
|
219
|
+
navigation: assocOnNavigation,
|
|
174
220
|
notFound: undefinedParentElement,
|
|
221
|
+
accept: acceptElemOrVarOrSelf,
|
|
222
|
+
check: checkAssocOn,
|
|
175
223
|
param: paramUnsupported,
|
|
176
224
|
nestedColumn: () => ({ // in expand and inline
|
|
177
225
|
lexical: justDollarSelf,
|
|
178
226
|
dollar: true,
|
|
179
227
|
dynamic: parentElements,
|
|
228
|
+
navigation: assocOnNavigation,
|
|
180
229
|
notFound: undefinedParentElement,
|
|
181
230
|
}),
|
|
182
231
|
},
|
|
183
232
|
'mixin-on': {
|
|
184
233
|
lexical: tableAliasesAndSelf,
|
|
185
|
-
allowBareSelf: true,
|
|
186
234
|
dollar: true,
|
|
187
235
|
dynamic: combinedSourcesOrParentElements,
|
|
236
|
+
navigation: assocOnNavigation,
|
|
188
237
|
notFound: undefinedSourceElement,
|
|
238
|
+
accept: acceptElemOrVarOrSelf,
|
|
239
|
+
check: checkAssocOn,
|
|
189
240
|
param: paramSemantics, // TODO: check that assocs containing param in ON is not published
|
|
190
241
|
},
|
|
191
|
-
'
|
|
242
|
+
'orderBy-ref': {
|
|
192
243
|
lexical: tableAliasesAndSelf,
|
|
193
244
|
dollar: true,
|
|
194
245
|
dynamic: parentElements,
|
|
195
246
|
notFound: undefinedOrderByElement,
|
|
247
|
+
check: checkOrderByRef,
|
|
196
248
|
param: paramSemantics,
|
|
197
249
|
},
|
|
198
|
-
'
|
|
250
|
+
'orderBy-expr': {
|
|
199
251
|
lexical: tableAliasesAndSelf,
|
|
200
252
|
dollar: true,
|
|
201
253
|
dynamic: combinedSourcesOrParentElements,
|
|
202
254
|
notFound: undefinedSourceElement,
|
|
255
|
+
check: checkRefInQuery,
|
|
203
256
|
param: paramSemantics,
|
|
204
257
|
},
|
|
205
|
-
'
|
|
258
|
+
'orderBy-set-ref': {
|
|
206
259
|
lexical: tableAliasesAndSelf, // TODO: reject own tab aliases
|
|
207
260
|
dollar: true,
|
|
208
261
|
dynamic: queryElements,
|
|
262
|
+
rejectRoot: rejectOwnAliasesAndMixins,
|
|
209
263
|
notFound: undefinedParentElement,
|
|
264
|
+
check: checkOrderByRef,
|
|
210
265
|
param: paramSemantics,
|
|
211
266
|
},
|
|
212
|
-
'
|
|
267
|
+
'orderBy-set-expr': {
|
|
213
268
|
lexical: tableAliasesAndSelf, // TODO: reject own tab aliases
|
|
214
269
|
dollar: true,
|
|
215
270
|
dynamic: () => Object.create( null ),
|
|
271
|
+
rejectRoot: rejectAllOwn,
|
|
216
272
|
notFound: undefinedVariable,
|
|
273
|
+
check: checkRefInQuery,
|
|
217
274
|
param: paramSemantics,
|
|
218
275
|
},
|
|
219
276
|
};
|
|
220
277
|
|
|
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
278
|
Object.assign( model.$functions, {
|
|
279
|
+
traverseExpr,
|
|
344
280
|
resolveUncheckedPath,
|
|
345
|
-
resolveTypeArgumentsUnchecked,
|
|
281
|
+
resolveTypeArgumentsUnchecked, // TODO: move to some other file
|
|
346
282
|
resolvePath,
|
|
283
|
+
checkExpr,
|
|
284
|
+
checkOnCondition,
|
|
285
|
+
nestedElements,
|
|
347
286
|
attachAndEmitValidNames,
|
|
348
287
|
} );
|
|
349
288
|
return;
|
|
350
289
|
|
|
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
|
-
}
|
|
290
|
+
// Expression traversal function ----------------------------------------------
|
|
291
|
+
function traverseExpr( expr, exprCtx, user, callback ) {
|
|
292
|
+
if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
|
|
293
|
+
return;
|
|
404
294
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
295
|
+
if (expr.path) {
|
|
296
|
+
callback( expr, exprCtx, user );
|
|
297
|
+
// TODO: move arguments and filter traversal to here
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
else if (expr.type || expr.query) {
|
|
301
|
+
callback( expr, exprCtx, user );
|
|
302
|
+
}
|
|
413
303
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
304
|
+
if (expr.args) {
|
|
305
|
+
const args = Array.isArray( expr.args ) ? expr.args : Object.values( expr.args );
|
|
306
|
+
// TODO: re-think $expected
|
|
307
|
+
if (!callback.traverse?.( args, exprCtx, user, callback ))
|
|
308
|
+
args.forEach( e => traverseExpr( e, exprCtx, user, callback ) );
|
|
309
|
+
}
|
|
310
|
+
if (expr.suffix) // fn( … ) OVER …
|
|
311
|
+
expr.suffix.forEach( e => traverseExpr( e, exprCtx, user, callback ) );
|
|
421
312
|
}
|
|
422
313
|
|
|
423
314
|
// Return absolute name for unchecked path `ref`. We first try searching for
|
|
@@ -441,16 +332,18 @@ function fns( model ) {
|
|
|
441
332
|
if (Array.isArray( art ))
|
|
442
333
|
art = art[0];
|
|
443
334
|
if (!art)
|
|
444
|
-
return (semantics.
|
|
335
|
+
return (semantics.dynamic !== modelDefinitions) ? art : pathName( path );
|
|
445
336
|
if (path.length === 1)
|
|
446
337
|
return art.name.absolute; // TODO: name.id
|
|
447
338
|
return `${ art.name.absolute }.${ pathName( ref.path.slice(1) ) }`;
|
|
448
339
|
}
|
|
449
340
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
341
|
+
/**
|
|
342
|
+
* Return artifact or element referred by the path in `ref`. The first
|
|
343
|
+
* environment we search in is `env`. If no such artifact or element exist,
|
|
344
|
+
* complain with message and return `undefined`. Record a dependency from
|
|
345
|
+
* `user` to the found artifact if `user` is provided.
|
|
346
|
+
*/
|
|
454
347
|
function resolvePath( ref, expected, user ) {
|
|
455
348
|
const origUser = user;
|
|
456
349
|
user = user._user || user;
|
|
@@ -465,67 +358,46 @@ function fns( model ) {
|
|
|
465
358
|
return setArtifactLink( ref, undefined );
|
|
466
359
|
}
|
|
467
360
|
|
|
468
|
-
const s = referenceSemantics[expected];
|
|
361
|
+
const s = referenceSemantics[expected];
|
|
469
362
|
const semantics = (typeof s === 'string') ? referenceSemantics[s] : s;
|
|
470
363
|
|
|
471
364
|
const r = getPathRoot( ref, semantics, origUser );
|
|
472
|
-
const root = r && acceptPathRoot( r, ref, semantics,
|
|
365
|
+
const root = r && acceptPathRoot( r, ref, semantics, origUser );
|
|
473
366
|
if (!root)
|
|
474
367
|
return setArtifactLink( ref, root );
|
|
475
368
|
|
|
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
369
|
// how many path items are for artifacts (rest: elements)
|
|
484
370
|
// console.log(expected, ref.path.map(a=>a.id),artItemsCount)
|
|
485
|
-
let art = getPathItem( ref, semantics,
|
|
371
|
+
let art = getPathItem( ref, semantics, user );
|
|
486
372
|
if (!art)
|
|
487
373
|
return setArtifactLink( ref, art );
|
|
488
374
|
|
|
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 );
|
|
375
|
+
// TODO: use isMainRef string value here?
|
|
376
|
+
const acceptFn = semantics.accept || (semantics.isMainRef ? a => a : acceptElemOrVar);
|
|
377
|
+
art = setArtifactLink( ref, acceptFn( art, user, ref, semantics ) );
|
|
378
|
+
|
|
379
|
+
// TODO TMP: remove noDep: an association does not depend on the target, only
|
|
380
|
+
// -- on its keys/on, which depend on certain target elements
|
|
381
|
+
if (art && user && !semantics.noDep) {
|
|
382
|
+
const location = artifactRefLocation( ref );
|
|
383
|
+
if (semantics.noDep === '' && art._main) { // assoc in FROM
|
|
518
384
|
environment( art, location, user );
|
|
385
|
+
const target = art._effectiveType?.target?._artifact;
|
|
386
|
+
if (target)
|
|
387
|
+
dependsOn( user._main, target, location, user );
|
|
519
388
|
}
|
|
520
|
-
else if (art.kind !== 'select'
|
|
389
|
+
else if (art._main && art.kind !== 'select' || path[0]._navigation?.kind !== '$self') {
|
|
390
|
+
// no real dependency to bare $self (or actually: the underlying query)
|
|
521
391
|
dependsOn( user, art, location );
|
|
522
392
|
// Without on-demand resolve, we can simply signal 'undefined "x"'
|
|
523
393
|
// instead of 'illegal cycle' in the following case:
|
|
524
394
|
// element elem: type of elem.x;
|
|
525
395
|
}
|
|
396
|
+
// TODO: really write dependency with expand/inline? write test
|
|
397
|
+
// (removing it is not incompatible => not urgent)
|
|
526
398
|
}
|
|
527
399
|
// TODO: follow FROM here, see csnRef - fromRef
|
|
528
|
-
return
|
|
400
|
+
return art;
|
|
529
401
|
}
|
|
530
402
|
|
|
531
403
|
/**
|
|
@@ -533,7 +405,7 @@ function fns( model ) {
|
|
|
533
405
|
* User is used for semantic message location.
|
|
534
406
|
*
|
|
535
407
|
* For builtins, for each property name `<prop>` in `typeArtifact.parameters`, we move a value
|
|
536
|
-
*
|
|
408
|
+
* from `art.$typeArgs` (a vector of numbers with locations) to `artifact.<prop>`.
|
|
537
409
|
*
|
|
538
410
|
* For non-builtins, we take either one or two arguments and interpret them
|
|
539
411
|
* as `length` or `precision`/`scale`.
|
|
@@ -550,16 +422,14 @@ function fns( model ) {
|
|
|
550
422
|
let args = artifact.$typeArgs || [];
|
|
551
423
|
const parameters = typeArtifact?.parameters || [];
|
|
552
424
|
|
|
553
|
-
if (parameters.length > 0) {
|
|
425
|
+
if (args.length > 0 && parameters.length > 0) {
|
|
554
426
|
// For Builtins
|
|
555
427
|
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];
|
|
428
|
+
const par = parameters[i].name || parameters[i];
|
|
429
|
+
if (!artifact[par] && i < args.length)
|
|
430
|
+
artifact[par] = args[i];
|
|
561
431
|
}
|
|
562
|
-
args = args.slice(parameters.length);
|
|
432
|
+
args = args.slice( parameters.length );
|
|
563
433
|
}
|
|
564
434
|
else if (args.length > 0 && !typeArtifact?.builtin) {
|
|
565
435
|
// One or two arguments are interpreted as either length or precision/scale.
|
|
@@ -622,9 +492,9 @@ function fns( model ) {
|
|
|
622
492
|
const [ nextProp, dictProp ] = (isMainRef)
|
|
623
493
|
? [ '_block', 'artifacts' ]
|
|
624
494
|
: [ '_$next', '$tableAliases' ];
|
|
625
|
-
// let notApplicable = ...; // for table aliases in JOIN-ON and UNION
|
|
495
|
+
// let notApplicable = ...; // for table aliases in JOIN-ON and UNION orderBy
|
|
626
496
|
for (let env = lexical; env; env = env[nextProp]) {
|
|
627
|
-
const dict = env[dictProp] || Object.create(null);
|
|
497
|
+
const dict = env[dictProp] || Object.create( null );
|
|
628
498
|
const r = dict[head.id];
|
|
629
499
|
if (acceptLexical( r, path, semantics, user ))
|
|
630
500
|
return setArtifactLink( head, r );
|
|
@@ -646,8 +516,6 @@ function fns( model ) {
|
|
|
646
516
|
valid.push( dynamicDict );
|
|
647
517
|
else
|
|
648
518
|
valid.push( model.$magicVariables.artifacts, removeDollarNames( dynamicDict ) );
|
|
649
|
-
if (semantics.notFound === false)
|
|
650
|
-
return setArtifactLink( head, null );
|
|
651
519
|
// TODO: streamline function arguments (probably: user, path, semantics )
|
|
652
520
|
const undef = semantics.notFound( ruser, head, valid, dynamicDict,
|
|
653
521
|
!isMainRef && user._user && user._artifact, path );
|
|
@@ -661,7 +529,7 @@ function fns( model ) {
|
|
|
661
529
|
// TODO - think about setting _navigation for all $navElement – the
|
|
662
530
|
// "ref: ['tabAlias']: inline: […]" handling might be easier
|
|
663
531
|
// (no _pathHead consultation for key prop and renaming support)
|
|
664
|
-
function getPathItem( ref, semantics,
|
|
532
|
+
function getPathItem( ref, semantics, user ) {
|
|
665
533
|
// let art = (headArt && headArt.kind === '$tableAlias') ? headArt._origin : headArt;
|
|
666
534
|
const { path } = ref;
|
|
667
535
|
let artItemsCount = 0;
|
|
@@ -671,11 +539,10 @@ function fns( model ) {
|
|
|
671
539
|
(ref.scope ? 1 : path.length);
|
|
672
540
|
}
|
|
673
541
|
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
542
|
const elementsEnv = semantics.navigation || environment;
|
|
543
|
+
let index = -1;
|
|
678
544
|
for (const item of path) {
|
|
545
|
+
++index;
|
|
679
546
|
--artItemsCount;
|
|
680
547
|
if (!item?.id) // incomplete AST due to parse error
|
|
681
548
|
return undefined;
|
|
@@ -686,33 +553,24 @@ function fns( model ) {
|
|
|
686
553
|
|
|
687
554
|
const prev = art;
|
|
688
555
|
const envFn = (artItemsCount >= 0) ? artifactsEnv : elementsEnv;
|
|
689
|
-
|
|
690
|
-
|
|
556
|
+
// TOOD: call envFn with location of last item (for dependency error)
|
|
557
|
+
const env = envFn( art, path[index - 1].location, user );
|
|
558
|
+
const found = env && env[item.id]; // not env?.[item.id] ! …we want to keep the 0
|
|
559
|
+
// Reject `$self.$_column_1`: TODO: necessary to do here again?
|
|
560
|
+
art = setArtifactLink( item, (found?.name?.$inferred === '$internal') ? undefined : found );
|
|
691
561
|
|
|
692
562
|
if (!art) {
|
|
693
|
-
// element was not found in environment
|
|
694
|
-
|
|
695
563
|
// TODO (done?): if `env` was 0, we might set a dependency to induce an
|
|
696
564
|
// 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
|
-
}
|
|
565
|
+
const notFound = (artItemsCount >= 0) ? semantics.notFound : undefinedItemElement;
|
|
566
|
+
// TODO: streamline function arguments (probably: user, path, semantics, prev )
|
|
567
|
+
// false returned by semantics.navigation: no further error:
|
|
568
|
+
if (env !== false)
|
|
569
|
+
notFound( user, item, [ env ], null, prev, path );
|
|
710
570
|
return null;
|
|
711
571
|
}
|
|
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
572
|
// need to do that here, because we also need to disallow Service.AutoExposed:elem
|
|
573
|
+
// TODO: but Service.AutoExposed.NotAuto should be fine
|
|
716
574
|
if (isMainRef !== 'all' && artItemsCount === 0 &&
|
|
717
575
|
art.$inferred === 'autoexposed' && !user.$inferred) {
|
|
718
576
|
// Depending on the processing sequence, the following could be a
|
|
@@ -721,92 +579,12 @@ function fns( model ) {
|
|
|
721
579
|
error( 'ref-unexpected-autoexposed', [ item.location, user ], { art },
|
|
722
580
|
// eslint-disable-next-line max-len
|
|
723
581
|
'An auto-exposed entity can\'t be referred to - expose entity $(ART) explicitly' );
|
|
724
|
-
|
|
582
|
+
return null; // continuation semantics: like “not found”
|
|
725
583
|
}
|
|
726
584
|
}
|
|
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
585
|
return art;
|
|
733
586
|
}
|
|
734
587
|
|
|
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
588
|
// Helper functions for resolve[Unchecked]Path, getPath{Root,Item}: -----------
|
|
811
589
|
|
|
812
590
|
function acceptLexical( art, path, semantics, user ) {
|
|
@@ -815,14 +593,13 @@ function fns( model ) {
|
|
|
815
593
|
// Non-global lexical are table aliases, mixins and $self, $projection, $parameters,
|
|
816
594
|
// Do not accept a lonely table alias and `$projection`
|
|
817
595
|
// TODO: test table alias and mixin named `$projection`
|
|
818
|
-
if (path.length
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
return art.$inferred !== '$internal';
|
|
596
|
+
if (path.length !== 1 || user.expand || user.inline)
|
|
597
|
+
return art.name?.$inferred !== '$internal'; // not a compiler-generated internal alias
|
|
598
|
+
|
|
599
|
+
// allow mixins, $self, and `up_` in anonymous target aspect (is $navElement):
|
|
600
|
+
return art.kind === 'mixin' ||
|
|
601
|
+
art.kind === '$self' && path[0].id === '$self' ||
|
|
602
|
+
art.kind === '$navElement';
|
|
826
603
|
}
|
|
827
604
|
|
|
828
605
|
function acceptPathRoot( art, ref, semantics, user ) {
|
|
@@ -830,6 +607,9 @@ function fns( model ) {
|
|
|
830
607
|
const [ head ] = path;
|
|
831
608
|
if (Array.isArray( art ))
|
|
832
609
|
return getAmbiguousRefLink( art, head, user );
|
|
610
|
+
if (semantics.rejectRoot?.( art, user, ref, semantics ))
|
|
611
|
+
return null;
|
|
612
|
+
|
|
833
613
|
switch (art.kind) {
|
|
834
614
|
case 'using': {
|
|
835
615
|
const def = model.definitions[art.name.absolute];
|
|
@@ -843,8 +623,8 @@ function fns( model ) {
|
|
|
843
623
|
return setLink( head, '_navigation', art );
|
|
844
624
|
}
|
|
845
625
|
case '$navElement': {
|
|
846
|
-
if (head.id === user.$extended)
|
|
847
|
-
path.$prefix =
|
|
626
|
+
if (head.id === (user._user || user).$extended)
|
|
627
|
+
path.$prefix = head.id;
|
|
848
628
|
setLink( head, '_navigation', art );
|
|
849
629
|
return setArtifactLink( head, art._origin );
|
|
850
630
|
}
|
|
@@ -858,13 +638,7 @@ function fns( model ) {
|
|
|
858
638
|
// that the corresponding entity should not be put as $origin into the CSN.
|
|
859
639
|
// TODO: remove again, should be easy enough in to-csn without.
|
|
860
640
|
if (path.length === 1 && art.kind === '$tableAlias')
|
|
861
|
-
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
|
-
}
|
|
641
|
+
(user._user || user).$noOrigin = true;
|
|
868
642
|
return art;
|
|
869
643
|
}
|
|
870
644
|
case '$parameters': { // TODO: remove from CC
|
|
@@ -886,14 +660,14 @@ function fns( model ) {
|
|
|
886
660
|
return false;
|
|
887
661
|
// only complain about ambiguous source elements if we do not have
|
|
888
662
|
// duplicate table aliases, only mention non-ambiguous source elems
|
|
889
|
-
const uniqueNames = arr.filter( e => !e.$duplicates);
|
|
663
|
+
const uniqueNames = arr.filter( e => !e.$duplicates );
|
|
890
664
|
if (uniqueNames.length) {
|
|
891
665
|
const names = uniqueNames.filter( e => e._parent.$inferred !== '$internal' )
|
|
892
666
|
.map( e => `${ e.name.alias }.${ e.name.element }` );
|
|
893
667
|
let variant = names.length === uniqueNames.length ? 'std' : 'few';
|
|
894
668
|
if (names.length === 0)
|
|
895
669
|
variant = 'none';
|
|
896
|
-
error( 'ref-ambiguous', [ head.location, user ], { '#': variant, id: head.id, names });
|
|
670
|
+
error( 'ref-ambiguous', [ head.location, user ], { '#': variant, id: head.id, names } );
|
|
897
671
|
}
|
|
898
672
|
return false;
|
|
899
673
|
}
|
|
@@ -902,11 +676,13 @@ function fns( model ) {
|
|
|
902
676
|
|
|
903
677
|
function typeOfSemantics( user, [ head ] ) {
|
|
904
678
|
// `type of` is only allowed for (sub) elements of main artifacts
|
|
679
|
+
while (!user.kind && user._outer)
|
|
680
|
+
user = user._outer;
|
|
905
681
|
let struct = user;
|
|
906
682
|
while (struct.kind === 'element')
|
|
907
683
|
struct = struct._parent;
|
|
908
684
|
if (struct === user._main && struct.kind !== 'annotation')
|
|
909
|
-
return { dynamic: typeOfParentDict };
|
|
685
|
+
return { dynamic: typeOfParentDict, navigation: staticTarget };
|
|
910
686
|
error( 'type-unexpected-typeof', [ head.location, user ],
|
|
911
687
|
{ keyword: 'type of', '#': struct.kind } );
|
|
912
688
|
return false;
|
|
@@ -916,7 +692,7 @@ function fns( model ) {
|
|
|
916
692
|
return { dynamic: artifactParams, notFound: undefinedParam };
|
|
917
693
|
}
|
|
918
694
|
function paramUnsupported( user, _path, location ) {
|
|
919
|
-
error( 'ref-unexpected-scope', [ location, user ],
|
|
695
|
+
error( 'ref-unexpected-scope', [ location, user ], // TODO: ref-unexpected-param
|
|
920
696
|
// why an extra text for calculated elements? or separate for all?
|
|
921
697
|
{ '#': (user.$syntax === 'calc' ? 'calc' : 'std') } );
|
|
922
698
|
return false;
|
|
@@ -933,7 +709,7 @@ function fns( model ) {
|
|
|
933
709
|
return user._main || user;
|
|
934
710
|
// query.$tableAliases contains both aliases and $self/$projection
|
|
935
711
|
const aliases = query.$tableAliases;
|
|
936
|
-
const r = Object.create(null);
|
|
712
|
+
const r = Object.create( null );
|
|
937
713
|
if (aliases.$self.kind === '$self')
|
|
938
714
|
r.$self = aliases.$self;
|
|
939
715
|
// TODO: disallow $projection for ON conditions all together
|
|
@@ -979,9 +755,10 @@ function fns( model ) {
|
|
|
979
755
|
}
|
|
980
756
|
|
|
981
757
|
function targetElements( user, pathItemArtifact ) {
|
|
982
|
-
//
|
|
983
|
-
|
|
984
|
-
|
|
758
|
+
// has already been computed - no further `navigationEnv` args needed
|
|
759
|
+
const env = navigationEnv( pathItemArtifact || user._parent );
|
|
760
|
+
// do not use env?.elements: a `0` should stay a `0`:
|
|
761
|
+
return env && env.elements;
|
|
985
762
|
}
|
|
986
763
|
|
|
987
764
|
function combinedSourcesOrParentElements( user ) {
|
|
@@ -999,27 +776,58 @@ function fns( model ) {
|
|
|
999
776
|
}
|
|
1000
777
|
|
|
1001
778
|
function nestedElements( user ) {
|
|
1002
|
-
|
|
1003
|
-
|
|
779
|
+
const colParent = user._pathHead;
|
|
780
|
+
Functions.effectiveType( colParent ); // set _origin
|
|
781
|
+
const path = colParent?.value?.path;
|
|
782
|
+
if (!path?.length)
|
|
783
|
+
return undefined;
|
|
784
|
+
// also set dependency when navigating along assoc → provide location
|
|
785
|
+
return environment( colParent._origin, path[path.length - 1].location, colParent );
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// Function called via semantics.navigation: ----------------------------------
|
|
789
|
+
// default is function `environment`
|
|
790
|
+
|
|
791
|
+
function artifactsEnv( art ) {
|
|
792
|
+
return art._subArtifacts || Object.create( null );
|
|
1004
793
|
}
|
|
1005
794
|
|
|
1006
|
-
function
|
|
1007
|
-
let env =
|
|
1008
|
-
while (env?.target && !env.targetAspect)
|
|
1009
|
-
env = env._origin || env.type?._artifact;
|
|
795
|
+
function staticTarget( prev ) {
|
|
796
|
+
let env = navigationEnv( prev ); // we do not write dependencies for assoc navigation
|
|
1010
797
|
if (env === 0)
|
|
1011
798
|
return 0;
|
|
799
|
+
// Last try - Composition with targetAspect only (in aspect def):
|
|
1012
800
|
const target = env?.targetAspect;
|
|
1013
801
|
if (target) {
|
|
1014
802
|
if (target.elements)
|
|
1015
803
|
return target.elements;
|
|
1016
804
|
env = resolvePath( env.targetAspect, 'targetAspect', env );
|
|
1017
805
|
}
|
|
1018
|
-
return env?.elements || Object.create(null);
|
|
806
|
+
return env?.elements || Object.create( null );
|
|
1019
807
|
}
|
|
1020
808
|
|
|
1021
|
-
function
|
|
1022
|
-
|
|
809
|
+
function targetNavigation( art, location, user ) {
|
|
810
|
+
const env = navigationEnv( art, location, user, false );
|
|
811
|
+
// do not use env?.elements: a `0`/false should stay a `0`/false:
|
|
812
|
+
return env && env.elements;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
function assocOnNavigation( art, location, user ) {
|
|
816
|
+
const env = navigationEnv( art, location, user, null );
|
|
817
|
+
// `null` means: do not write a dependency from target of any association
|
|
818
|
+
// otherwise “following” own assoc would lead to cycle.
|
|
819
|
+
// TODO: disallow navigation other than of own assoc, and to foreign keys
|
|
820
|
+
// This way (not here though, but later in resolve.js)
|
|
821
|
+
if (env === 0)
|
|
822
|
+
return 0;
|
|
823
|
+
return env?.elements || Object.create( null );
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function calcElemNavigation( art, location, user ) {
|
|
827
|
+
const env = navigationEnv( art, location, user, 'calc' );
|
|
828
|
+
if (env === 0)
|
|
829
|
+
return 0;
|
|
830
|
+
return env?.elements || Object.create( null );
|
|
1023
831
|
}
|
|
1024
832
|
|
|
1025
833
|
// Return effective search environment provided by artifact `art`, i.e. the
|
|
@@ -1027,13 +835,47 @@ function fns( model ) {
|
|
|
1027
835
|
// chain and resolve the association `target`. View elements are calculated
|
|
1028
836
|
// on demand.
|
|
1029
837
|
// TODO: what about location/user when called from getPath ?
|
|
1030
|
-
// TODO: think of
|
|
838
|
+
// TODO: think of removing `|| Object.create(null)`.
|
|
1031
839
|
// (if not possible, move to second param position)
|
|
1032
|
-
function environment( art, location, user
|
|
1033
|
-
const env =
|
|
840
|
+
function environment( art, location, user ) {
|
|
841
|
+
const env = navigationEnv( art, location, user, 'nav' );
|
|
1034
842
|
if (env === 0)
|
|
1035
843
|
return 0;
|
|
1036
|
-
return env?.elements ||
|
|
844
|
+
return env?.elements || Object.create( null );
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function navigationEnv( art, location, user, assocSpec ) {
|
|
848
|
+
// = effectiveType() on from-path, TODO: should actually already part of
|
|
849
|
+
// resolvePath() on FROM
|
|
850
|
+
if (!art)
|
|
851
|
+
return undefined;
|
|
852
|
+
let type = Functions.effectiveType( art );
|
|
853
|
+
while (type?.items) // TODO: disallow navigation to many sometimes
|
|
854
|
+
type = Functions.effectiveType( type.items );
|
|
855
|
+
if (!type?.target)
|
|
856
|
+
return type;
|
|
857
|
+
|
|
858
|
+
if (assocSpec === false) { // TODO: move to getPathItem
|
|
859
|
+
error( null, [ location, user ], {},
|
|
860
|
+
'Following an association is not allowed in an association key definition' );
|
|
861
|
+
return false;
|
|
862
|
+
} // TODO: else warning for assoc usage with falsy assocSpec
|
|
863
|
+
const target = type?.target._artifact;
|
|
864
|
+
if (!target)
|
|
865
|
+
return target;
|
|
866
|
+
// TODO: really write final dependency with expand/inline?
|
|
867
|
+
if (target && assocSpec && user) {
|
|
868
|
+
if (assocSpec !== 'calc')
|
|
869
|
+
dependsOn( user._main || user, target, location || user.location, user );
|
|
870
|
+
else // (TODO: users of) calc elements must depend on navigation target
|
|
871
|
+
dependsOn( user, target, location || user.location );
|
|
872
|
+
// TODO: have some _delayedDeps for calc elements
|
|
873
|
+
}
|
|
874
|
+
const effectiveTarget = Functions.effectiveType( target );
|
|
875
|
+
// if (effectiveTarget === 0 && location)
|
|
876
|
+
// dependsOn( user, user, (user.target || user.type || user.value || user).location );
|
|
877
|
+
// console.log('NT:',assocSpec,!!user,target)
|
|
878
|
+
return effectiveTarget;
|
|
1037
879
|
}
|
|
1038
880
|
|
|
1039
881
|
// Functions called via semantics.notFound: -----------------------------------
|
|
@@ -1072,10 +914,12 @@ function fns( model ) {
|
|
|
1072
914
|
// TODO: avoid message if we have already complained about `(exists …)`?
|
|
1073
915
|
const { id } = head;
|
|
1074
916
|
const isVar = id.charAt( 0 ) === '$' && id !== '$self';
|
|
1075
|
-
|
|
917
|
+
// TODO: for wrong $self, also use ref-undefined-var, but with extra msg id
|
|
918
|
+
// otherwise, use s/th like ref-unexpected-element
|
|
919
|
+
signalNotFound( ( isVar ? 'ref-undefined-var' : 'ref-expecting-const'),
|
|
1076
920
|
[ head.location, user ],
|
|
1077
921
|
valid, { '#': 'std', id } );
|
|
1078
|
-
// TODO: use s/th better than 'ref-expecting-const'
|
|
922
|
+
// TODO: use s/th better than 'ref-expecting-const' !!
|
|
1079
923
|
}
|
|
1080
924
|
|
|
1081
925
|
function undefinedSourceElement( user, head, valid, dynamicDict ) {
|
|
@@ -1110,7 +954,7 @@ function fns( model ) {
|
|
|
1110
954
|
}
|
|
1111
955
|
else {
|
|
1112
956
|
// TODO: extra msg like ref-rejected-on if elem found in source elements?
|
|
1113
|
-
// also whether users
|
|
957
|
+
// also whether users wrongly tried to refer to aliases/mixins?
|
|
1114
958
|
const msgVar = userQuery( user ) ? 'query' : null;
|
|
1115
959
|
// TODO: better with ON in expand if that is supported
|
|
1116
960
|
signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
|
|
@@ -1121,7 +965,7 @@ function fns( model ) {
|
|
|
1121
965
|
function undefinedOrderByElement( user, head, valid, dynamicDict, _art, path ) {
|
|
1122
966
|
const { id } = head;
|
|
1123
967
|
const src = id.charAt( 0 ) !== '$' && user._combined?.[id];
|
|
1124
|
-
if (src && !Array.isArray(src)) {
|
|
968
|
+
if (src && !Array.isArray( src )) {
|
|
1125
969
|
path.$prefix = src.name.alias; // pushing it to path directly could be problematic
|
|
1126
970
|
// configurable error:
|
|
1127
971
|
signalNotFound( 'ref-deprecated-orderby', [ head.location, user ], valid,
|
|
@@ -1132,16 +976,550 @@ function fns( model ) {
|
|
|
1132
976
|
return null;
|
|
1133
977
|
}
|
|
1134
978
|
|
|
1135
|
-
function undefinedNestedElement( user, head, valid ) {
|
|
1136
|
-
// environment( user._pathHead ); // set _origin
|
|
979
|
+
function undefinedNestedElement( user, head, valid, _dict, _art, path ) {
|
|
1137
980
|
const art = user._pathHead._origin;
|
|
1138
|
-
// if (!art) console.log('UNE:',user,user._pathHead)
|
|
1139
981
|
if (!art)
|
|
1140
|
-
return;
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
982
|
+
return null; // no consequential error
|
|
983
|
+
return undefinedItemElement( user, head, valid, null, art, path );
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function undefinedItemElement( user, item, valid, _dict, art, path ) {
|
|
987
|
+
if (art.name && art.name.select && art.name.select > 1) {
|
|
988
|
+
// TODO: 'The current query has no element $(MEMBER)' with $self.MEMBER
|
|
989
|
+
// and 'The sub query for alias $(ALIAS) has no element $(MEMBER)'
|
|
990
|
+
// both as text variants to ref-undefined-element
|
|
991
|
+
signalNotFound( 'query-undefined-element', [ item.location, user ],
|
|
992
|
+
valid, { id: item.id } );
|
|
993
|
+
}
|
|
994
|
+
else if (art.kind === '$parameters') {
|
|
995
|
+
signalNotFound( 'ref-undefined-param', [ item.location, user ],
|
|
996
|
+
valid, { art: art._main, id: item.id } );
|
|
997
|
+
}
|
|
998
|
+
else if (art.kind === 'builtin') { // magic variable / replacement variable
|
|
999
|
+
const id = (item === path[path.length - 1])
|
|
1000
|
+
? item.id
|
|
1001
|
+
: pathName( path.slice( path.indexOf( item ) ) );
|
|
1002
|
+
signalNotFound( ( art.$uncheckedElements ? 'ref-unknown-var' : 'ref-undefined-var'),
|
|
1003
|
+
[ item.location, user ], valid,
|
|
1004
|
+
{ id: `${ art.name.element }.${ id }` } );
|
|
1005
|
+
}
|
|
1006
|
+
else {
|
|
1007
|
+
const variant = art.kind === 'aspect' && !art.name && 'aspect';
|
|
1008
|
+
signalNotFound( 'ref-undefined-element', [ item.location, user ],
|
|
1009
|
+
valid, {
|
|
1010
|
+
'#': variant,
|
|
1011
|
+
art: (variant ? '' : searchName( art, item.id, 'element' )),
|
|
1012
|
+
id: item.id,
|
|
1013
|
+
} );
|
|
1014
|
+
}
|
|
1015
|
+
return null;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// Functions called via semantics.accept: -------------------------------------
|
|
1019
|
+
// function arguments ( art, user, ref, semantics ),
|
|
1020
|
+
// default (for elements only): acceptElemOrVar
|
|
1021
|
+
|
|
1022
|
+
function rejectOwnAliasesAndMixins( art, user, ref, semantics ) { // orderBy-set-ref
|
|
1023
|
+
switch (art.kind) {
|
|
1024
|
+
case '$tableAlias':
|
|
1025
|
+
case 'mixin':
|
|
1026
|
+
if (art._parent !== user)
|
|
1027
|
+
return false;
|
|
1028
|
+
break;
|
|
1029
|
+
case '$self':
|
|
1030
|
+
if (!semantics) // orderBy-set-expr
|
|
1031
|
+
break;
|
|
1032
|
+
// FALLTHROUGH
|
|
1033
|
+
default:
|
|
1034
|
+
return false;
|
|
1035
|
+
}
|
|
1036
|
+
error( 'ref-invalid-element', [ ref.path[0].location, user._user ],
|
|
1037
|
+
{ '#': art.kind, id: art.name.id } );
|
|
1038
|
+
return true;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
function rejectAllOwn( art, user, ref ) { // orderBy-set-expr
|
|
1042
|
+
return rejectOwnAliasesAndMixins( art, user, ref, null );
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function rejectOwnExceptVisibleAliases( art, user, ref ) { // for join-on
|
|
1046
|
+
switch (art.kind) {
|
|
1047
|
+
case '$navElement':
|
|
1048
|
+
art = art._parent;
|
|
1049
|
+
// FALLTHROUGH
|
|
1050
|
+
case '$tableAlias':
|
|
1051
|
+
case 'mixin':
|
|
1052
|
+
if (art._parent !== user._user || user.$tableAliases[art.name.id])
|
|
1053
|
+
return false;
|
|
1054
|
+
break;
|
|
1055
|
+
case '$self':
|
|
1056
|
+
// in the SQL backend, the $self.elem references are replaced by the
|
|
1057
|
+
// corresponding column expression; this might have references to elements
|
|
1058
|
+
// of invisible table aliases; at least one stakeholder uses this,
|
|
1059
|
+
// so it can't be an error (yet).
|
|
1060
|
+
warning( 'ref-deprecated-self-element', [ ref.path[0].location, user._user ], {},
|
|
1061
|
+
// eslint-disable-next-line max-len
|
|
1062
|
+
'Referring to the query\'s own elements here might lead to invalid SQL references; use source elements only' );
|
|
1063
|
+
return false;
|
|
1064
|
+
default:
|
|
1065
|
+
return false;
|
|
1066
|
+
}
|
|
1067
|
+
error( 'ref-invalid-element', [ ref.path[0].location, user._user ],
|
|
1068
|
+
{ '#': art.kind, id: art.name.id } );
|
|
1069
|
+
return true;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
function acceptElemOrVarOrSelf( art, user, ref ) {
|
|
1073
|
+
// TODO: make $self._artifact point to the $self alias, not the entity
|
|
1074
|
+
return (!(art._main && art.kind !== 'select') && ref.path[0]._navigation?.kind === '$self')
|
|
1075
|
+
? art
|
|
1076
|
+
: acceptElemOrVar( art, user, ref );
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
function acceptElemOrVar( art, user, ref ) {
|
|
1080
|
+
const { path } = ref;
|
|
1081
|
+
if (art.kind === 'builtin') {
|
|
1082
|
+
if (user.expand || user.inline) {
|
|
1083
|
+
const location = (user.expand || user.inline)[$location];
|
|
1084
|
+
const code = (user.expand) ? '{ ‹expand› }' : '.{ ‹inline› }';
|
|
1085
|
+
message( 'def-unexpected-nested-proj', [ location, user ], { '#': 'var', code } );
|
|
1086
|
+
}
|
|
1087
|
+
else if (art.$requireElementAccess) { // on some CDS variables
|
|
1088
|
+
// Path with only one item, but we expect an element, e.g. `$at.from`.
|
|
1089
|
+
signalMissingElementAccess( art, [ path[0].location, user ] );
|
|
1090
|
+
return null;
|
|
1091
|
+
}
|
|
1092
|
+
else if (art.$autoElement) {
|
|
1093
|
+
const { location } = path[0];
|
|
1094
|
+
const step = { id: art.$autoElement, $inferred: '$autoElement', location };
|
|
1095
|
+
path.push( step );
|
|
1096
|
+
art = art.elements[step.id];
|
|
1097
|
+
return setArtifactLink( step, art );
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
// TODO: combine $requireElementAccess/$autoElement to $bareRoot ?
|
|
1101
|
+
else if (!user.expand && !user.inline && // $self._artifact to main artifact
|
|
1102
|
+
!(art._main && art.kind !== 'select') && ref.path[0]._navigation?.kind === '$self') {
|
|
1103
|
+
// TODO: better ref-invalid-self
|
|
1104
|
+
const { location, id } = path[0];
|
|
1105
|
+
error( 'ref-unexpected-self', [ location, user ], { id } );
|
|
1106
|
+
// TODO: reject bare $projection here (new message id, configurable)
|
|
1107
|
+
// TODO: should we also attach valid names? Probably not...
|
|
1108
|
+
// TODO: return false; ??
|
|
1109
|
+
// return false;
|
|
1110
|
+
}
|
|
1111
|
+
return art;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
function acceptRealArtifact( art, user, ref ) {
|
|
1115
|
+
// For compatibility, we accept `extend Unknown` without elements/actions/includes
|
|
1116
|
+
if (art.kind !== 'namespace' || !(user.elements || user.actions || user.includes))
|
|
1117
|
+
return art;
|
|
1118
|
+
const { location } = ref.path[ref.path.length - 1];
|
|
1119
|
+
signalNotFound( 'ref-undefined-def', [ location, user ], null, { art } );
|
|
1120
|
+
return false;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
function acceptStructOrBare( art, user, ref ) { // for includes[]
|
|
1124
|
+
// It had been checked before that `includes` is already forbidden for
|
|
1125
|
+
// non-entity/aspect/type/event.
|
|
1126
|
+
//
|
|
1127
|
+
// We currently disallow as include:
|
|
1128
|
+
// - non-structured types or derived type of structured:
|
|
1129
|
+
// would have to follow type in extend/include;
|
|
1130
|
+
// - entities with params: clarify inheritance, use of param in ON/DEFAULT;
|
|
1131
|
+
// - query entities/events: difficult sequence of resolve steps
|
|
1132
|
+
// - aspect with one ore more elements on query entities / events
|
|
1133
|
+
// - aspect with `elements` property on non-structured types
|
|
1134
|
+
|
|
1135
|
+
// TODO: adapt `user` if it is an `extend`? NOTE: we cannot call
|
|
1136
|
+
// effectiveType() on user - it might be in the process of being computed!
|
|
1137
|
+
// Also, it is not clear whether `art.elements` has been completed → testing
|
|
1138
|
+
// its length might be processing-sequence dependent, see #11346. We must
|
|
1139
|
+
// ensure that an include does not add the `elements` property!
|
|
1140
|
+
const base = (user.kind === 'extend' ? user.name._artifact : user);
|
|
1141
|
+
if (!base)
|
|
1142
|
+
return art;
|
|
1143
|
+
if (base.query || base.type || !base.elements) {
|
|
1144
|
+
// Remark: it is not necessary to test for user.elements[$inferred], because
|
|
1145
|
+
// the type could only have inferred elements if it has a type expression.
|
|
1146
|
+
// Including aspects with elements is forbidden for aspects without the
|
|
1147
|
+
// `elements` property. Testing for the length of `art.elements` requires
|
|
1148
|
+
// that we have applied potential `includes` of `art` before!
|
|
1149
|
+
// We might allow includes with elements in the future, they'd probably
|
|
1150
|
+
// count as specified elements with lower priority, i.e. annos, types, key
|
|
1151
|
+
// etc on columns beat those inherited from the include.
|
|
1152
|
+
if (art.kind === 'aspect' &&
|
|
1153
|
+
(!art.elements || base.query && !Object.keys( art.elements ).length))
|
|
1154
|
+
return art;
|
|
1155
|
+
signalNotFound( 'ref-invalid-include', [ ref.location, user ], null,
|
|
1156
|
+
{ '#': 'bare' } );
|
|
1157
|
+
}
|
|
1158
|
+
else {
|
|
1159
|
+
if (!art.query && !art.type && !art.params && (art.elements || art.kind === 'aspect'))
|
|
1160
|
+
return art;
|
|
1161
|
+
signalNotFound( 'ref-invalid-include', [ ref.location, user ], null );
|
|
1162
|
+
}
|
|
1163
|
+
return false;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// Remember: an aspect should have already been moved to XSN targetAspect, but
|
|
1167
|
+
// the error messages should still talk about potential aspects
|
|
1168
|
+
function acceptEntity( art, user, ref ) { // for target
|
|
1169
|
+
if (art.kind === 'entity')
|
|
1170
|
+
return art;
|
|
1171
|
+
// Extra msg text with Composition of NeitherEntityNorAspect:
|
|
1172
|
+
const bare = !art.elements || art.elements[$inferred];
|
|
1173
|
+
const std = (art.targetAspect || !isDirectComposition( user ) || user.kind === 'mixin');
|
|
1174
|
+
const msg = std && 'std' || (bare && art.kind === 'aspect' ? 'bare' : 'composition');
|
|
1175
|
+
signalNotFound( 'ref-invalid-target', [ ref.location, user ], null,
|
|
1176
|
+
{ '#': msg } );
|
|
1177
|
+
return false;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
function acceptAspect( art, user, ref ) { // for targetAspect
|
|
1181
|
+
const bare = !art.elements || art.elements[$inferred];
|
|
1182
|
+
if (!bare) {
|
|
1183
|
+
if (art.kind === 'aspect')
|
|
1184
|
+
return art;
|
|
1185
|
+
if (art.kind === 'type') { // v4: Warning → config Error
|
|
1186
|
+
signalNotFound( 'ref-sloppy-target', [ ref.location, user ], null );
|
|
1187
|
+
return art;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
signalNotFound( 'ref-invalid-target', [ ref.location, user ], null,
|
|
1191
|
+
{ '#': (bare ? 'bare' : 'aspect'), prop: 'targetAspect' } );
|
|
1192
|
+
return false;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
function acceptEntityOrAssoc( art, user, ref ) { // for FROM
|
|
1196
|
+
const { path, scope } = ref;
|
|
1197
|
+
// see getPathItem(): how many path items are for the main artifact ref?
|
|
1198
|
+
const artItemsCount = (typeof scope === 'number' && scope) || (scope ? 1 : path.length);
|
|
1199
|
+
// at least the last main definition should be an entity
|
|
1200
|
+
// an additional check for target would need effectiveType()
|
|
1201
|
+
const source = path[artItemsCount - 1]._artifact;
|
|
1202
|
+
if (source.kind !== 'entity') {
|
|
1203
|
+
signalNotFound( 'ref-invalid-source', [ ref.location, user ], null );
|
|
1204
|
+
return false;
|
|
1205
|
+
}
|
|
1206
|
+
if (source === art)
|
|
1207
|
+
return art;
|
|
1208
|
+
const assoc = Functions.effectiveType( art );
|
|
1209
|
+
if (assoc.target)
|
|
1210
|
+
return art; // TODO: use target here
|
|
1211
|
+
signalNotFound( 'ref-invalid-source', [ ref.location, user ], null );
|
|
1212
|
+
return false;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
function acceptTypeOrElement( art, user, ref ) { // for type
|
|
1216
|
+
// was ['action', 'function'].includes( user._parent?.kind ))
|
|
1217
|
+
while (user._outer)
|
|
1218
|
+
user = user._outer;
|
|
1219
|
+
const kind = (user.kind !== 'param' || user._parent?.kind !== 'entity')
|
|
1220
|
+
? user.kind
|
|
1221
|
+
: 'entity-param';
|
|
1222
|
+
switch (art.kind) {
|
|
1223
|
+
case 'type':
|
|
1224
|
+
case 'element':
|
|
1225
|
+
return art;
|
|
1226
|
+
case 'entity':
|
|
1227
|
+
if (kind === 'param' && art._service)
|
|
1228
|
+
return art;
|
|
1229
|
+
// FALLTHROUGH
|
|
1230
|
+
case 'event':
|
|
1231
|
+
if (kind === 'event')
|
|
1232
|
+
return art;
|
|
1233
|
+
break;
|
|
1234
|
+
default:
|
|
1235
|
+
break;
|
|
1236
|
+
}
|
|
1237
|
+
signalNotFound( 'ref-invalid-type', [ ref.location, user ], null, { '#': kind } );
|
|
1238
|
+
return false;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// Functions called via semantics.check by checkExpr(): -----------------------
|
|
1242
|
+
//
|
|
1243
|
+
// function arguments: ( expr, exprCtx, user )
|
|
1244
|
+
// default: tbd (nothing for main artifac ref)
|
|
1245
|
+
|
|
1246
|
+
// Performs checks which would be too early to do via semantics.accept. It is
|
|
1247
|
+
// actually assumed that the foreign-keys / ON-condition rewrite has already
|
|
1248
|
+
// been done.
|
|
1249
|
+
|
|
1250
|
+
// Main check area "navigation" (see also semantics.navigation):
|
|
1251
|
+
// - navigation along any assoc
|
|
1252
|
+
// - navigation only along foreign keys
|
|
1253
|
+
// - (no navigation already via semantics.navigation for target refs of foreign keys)
|
|
1254
|
+
// - special (ON-condition of unmanaged associations)
|
|
1255
|
+
|
|
1256
|
+
// Main check area: checks on the referred artifact
|
|
1257
|
+
// - all artifacts are allowed
|
|
1258
|
+
// - all except unmanaged associations
|
|
1259
|
+
// - ...
|
|
1260
|
+
|
|
1261
|
+
function checkExpr( expr, exprCtx, user ) {
|
|
1262
|
+
if (!expr)
|
|
1263
|
+
return;
|
|
1264
|
+
const s = referenceSemantics[exprCtx];
|
|
1265
|
+
const semantics = (typeof s === 'string') ? referenceSemantics[s] : s;
|
|
1266
|
+
const checkFn = semantics.check; // || !semantics.isMainRef && checkElementStd;
|
|
1267
|
+
|
|
1268
|
+
if (checkFn)
|
|
1269
|
+
traverseExpr( expr, exprCtx, user, checkFn );
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
// TODO: Don't allow path args and filter!
|
|
1273
|
+
function checkOnCondition( expr, exprCtx, user ) {
|
|
1274
|
+
if (!expr || expr.$inferred)
|
|
1275
|
+
return;
|
|
1276
|
+
const { op } = expr;
|
|
1277
|
+
let { args } = expr;
|
|
1278
|
+
if (!op || !args) {
|
|
1279
|
+
checkExpr( expr, exprCtx, user );
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
if (op?.val === '=') // TMP
|
|
1283
|
+
args = [ args[0], { val: '=', literal: 'token' }, args[1] ];
|
|
1284
|
+
|
|
1285
|
+
for (let index = 0; index < args.length; ++index) {
|
|
1286
|
+
const item = args[index];
|
|
1287
|
+
const eq = args[index + 1];
|
|
1288
|
+
if (eq?.val === '=' && eq.literal === 'token' && item.path && !item.scope) {
|
|
1289
|
+
const right = args[index + 2];
|
|
1290
|
+
if (right?.path && !right.scope &&
|
|
1291
|
+
(isDollarSelfPair( item, right, user ) || isDollarSelfPair( right, item, user ))) {
|
|
1292
|
+
checkAssocOnSelf( item, exprCtx, user );
|
|
1293
|
+
checkAssocOnSelf( right, exprCtx, user );
|
|
1294
|
+
index += 2;
|
|
1295
|
+
continue;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
checkOnCondition( item, exprCtx, user );
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
// // standard element reference check;
|
|
1303
|
+
// function checkElementStd( art, _user, ref, semantics ) {
|
|
1304
|
+
// // No further checks on navigation: nothing to do
|
|
1305
|
+
// // Must not end with any association (TODO: allow managed?):
|
|
1306
|
+
// if (art.target) { // && art.on
|
|
1307
|
+
// // error
|
|
1308
|
+
// }
|
|
1309
|
+
// }
|
|
1310
|
+
|
|
1311
|
+
function checkColumnRef( expr, exprCtx, user ) {
|
|
1312
|
+
if (!expr.path)
|
|
1313
|
+
return;
|
|
1314
|
+
if (expr === user.value && // is already a syntax error with non-ref expression
|
|
1315
|
+
(user.expand || user.inline))
|
|
1316
|
+
checkExpandInlineRef( expr._artifact, user, expr );
|
|
1317
|
+
|
|
1318
|
+
const self = pathStartsWithSelf( expr );
|
|
1319
|
+
// console.log('NAV:',expr.path.map(r=>r.id),self)
|
|
1320
|
+
if (self || self == null && columnRefStartsWithSelf( user )) {
|
|
1321
|
+
checkOnlyForeignKeyNavigation( user, expr.path, 0, 'self-' );
|
|
1322
|
+
checkNoUnmanaged( expr, user, 'self-unmanaged' );
|
|
1323
|
+
}
|
|
1324
|
+
// TODO: set navigation dependencies later to avoid both ref-cyclic and
|
|
1325
|
+
// ref-invalid-navigation/ref-unexpected-assoc
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
function checkOrderByRef( expr, exprCtx, user ) {
|
|
1329
|
+
const { path } = expr;
|
|
1330
|
+
if (!path)
|
|
1331
|
+
return;
|
|
1332
|
+
if (path?.[0]?._navigation?.kind !== '$tableAlias')
|
|
1333
|
+
checkOnlyForeignKeyNavigation( user, expr.path );
|
|
1334
|
+
checkNoUnmanaged( expr, user );
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
function checkRefInQuery( expr, exprCtx, user ) {
|
|
1338
|
+
const { path } = expr;
|
|
1339
|
+
if (!path)
|
|
1340
|
+
return;
|
|
1341
|
+
if (pathStartsWithSelf( expr ))
|
|
1342
|
+
checkOnlyForeignKeyNavigation( user, expr.path, 0, 'self-' );
|
|
1343
|
+
checkNoUnmanaged( expr, user );
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
function checkExpandInlineRef( art, user, ref ) {
|
|
1347
|
+
if (!art || !art._main || // $self has entity as _artifact
|
|
1348
|
+
art.kind === 'builtin') // no repeated error for CDS variables
|
|
1349
|
+
return;
|
|
1350
|
+
const effective = art._effectiveType;
|
|
1351
|
+
if (!effective || effective.target || effective.elements)
|
|
1352
|
+
return;
|
|
1353
|
+
const { path } = ref;
|
|
1354
|
+
const location = (user.expand || user.inline)[$location];
|
|
1355
|
+
// mention `table alias` in text only with initial single path item ref,
|
|
1356
|
+
// but do not mention that $self { … } is allowed, shouldn't be advertised:
|
|
1357
|
+
const txt = (path.length > 1 || user._pathHead) ? 'struct' : 'init';
|
|
1358
|
+
const code = (user.expand) ? '{ ‹expand› }' : '.{ ‹inline› }';
|
|
1359
|
+
message( 'def-unexpected-nested-proj', [ location, user ], { '#': txt, code } );
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
function isDollarSelfPair( left, right, user ) {
|
|
1363
|
+
if (!left.path || !right.path || left.scope || right.scope)
|
|
1364
|
+
return false; // param ref `:$self` is not $self
|
|
1365
|
+
// $self in entity (TODO: mixin? new assoc in col? in aspect?)
|
|
1366
|
+
const kind = right._artifact?.kind;
|
|
1367
|
+
if (kind !== 'entity' && kind !== 'aspect' && kind !== 'select')
|
|
1368
|
+
return false; // (ok, this would also return `false` for `:$self`)
|
|
1369
|
+
const { path } = left;
|
|
1370
|
+
// TODO: we might return true and issue an extra error here
|
|
1371
|
+
return userTargetElementPathIndex( user, path ) > 0;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
function checkAssocOn( ref, exprCtx, user ) {
|
|
1375
|
+
const { path } = ref;
|
|
1376
|
+
if (!path)
|
|
1377
|
+
return;
|
|
1378
|
+
if (path.length === 1 && path[0]._navigation?.kind === '$self') {
|
|
1379
|
+
// resolvePath() for `on` allowed bare $self: disallow except in checkAssocOnSelf
|
|
1380
|
+
const { location, id } = path[0];
|
|
1381
|
+
error( 'ref-unexpected-self', [ location, user ], { '#': 'on', id } );
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
const index = userTargetElementPathIndex( user, path );
|
|
1385
|
+
checkOnlyForeignKeyNavigation( user, path, index );
|
|
1386
|
+
if (ref._artifact?.on) {
|
|
1387
|
+
const target = index > 0 && index < path.length && ref._artifact?.target;
|
|
1388
|
+
const msg = (target?._artifact === user._main) ? 'self' : 'unmanaged';
|
|
1389
|
+
const last = path[path.length - 1];
|
|
1390
|
+
error( 'ref-unexpected-assoc', [ last.location, user ],
|
|
1391
|
+
{ '#': msg, code: '= $self' } );
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
function checkAssocOnSelf( ref, exprCtx, user ) {
|
|
1396
|
+
// TODO: fully specify what `‹current_assoc›.‹backlink› = $self` means if the
|
|
1397
|
+
// target of ‹backlink› is not the current entity.
|
|
1398
|
+
// - what does it mean in an aspect
|
|
1399
|
+
// - how about auto-redirections/rewrite
|
|
1400
|
+
const { path } = ref;
|
|
1401
|
+
if (path.length === 1 && path[0]._navigation?.kind === '$self') {
|
|
1402
|
+
const query = userQuery( user );
|
|
1403
|
+
const main = query?._main;
|
|
1404
|
+
if (query && query !== main._leadingQuery) {
|
|
1405
|
+
const { txt, op } = getQueryOperatorName( query );
|
|
1406
|
+
const { location, id } = path[0];
|
|
1407
|
+
error( 'ref-unexpected-self', [ location, user ], { '#': txt, id, op } );
|
|
1408
|
+
}
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
const index = userTargetElementPathIndex( user, path );
|
|
1412
|
+
checkOnlyForeignKeyNavigation( user, path, index );
|
|
1413
|
+
const target = index > 0 && index < path.length && ref._artifact?.target;
|
|
1414
|
+
if (!target) {
|
|
1415
|
+
const last = path[path.length - 1];
|
|
1416
|
+
error( 'ref-expecting-target-assoc', [ last.location, user ], { id: '$self' },
|
|
1417
|
+
'Only an association of the target side can be compared to $(ID)' );
|
|
1418
|
+
}
|
|
1419
|
+
// in entity: target must match
|
|
1420
|
+
// in aspect: must end with assoc, TODO: target must include aspect (+ add/ check)
|
|
1421
|
+
else if (target._artifact && target._artifact !== user._main && user._main.kind === 'entity') {
|
|
1422
|
+
const last = path[path.length - 1];
|
|
1423
|
+
warning( 'ref-invalid-backlink', [ last.location, user ], { art: target, id: '$self' },
|
|
1424
|
+
// eslint-disable-next-line max-len
|
|
1425
|
+
'The target $(ART) of the association is not the current entity represented by $(ID)' );
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
|
|
1430
|
+
// right of union: parent = main → get correct operator
|
|
1431
|
+
// FROM subquery: parent = tab alias of outer query
|
|
1432
|
+
// other sub query: parent = outer query
|
|
1433
|
+
function getQueryOperatorName( query ) {
|
|
1434
|
+
if (query._parent !== query._main)
|
|
1435
|
+
return { txt: 'subQuery', op: '' };
|
|
1436
|
+
|
|
1437
|
+
for (let set = query._main.query; set.op; set = set.args[0]) {
|
|
1438
|
+
const right = set.args[1];
|
|
1439
|
+
if (query.name.select >= (right._leadingQuery || right).name.select)
|
|
1440
|
+
return { txt: 'setQuery', op: set.op.val };
|
|
1441
|
+
}
|
|
1442
|
+
throw new CompilerAssertion( 'Did we pass the leading query as argument?' );
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
function userTargetElementPathIndex( user, path ) {
|
|
1446
|
+
const head = path[0];
|
|
1447
|
+
if (head._artifact === user) // standard case
|
|
1448
|
+
return 1;
|
|
1449
|
+
if (head._navigation?.kind !== '$self')
|
|
1450
|
+
return 0;
|
|
1451
|
+
for (let index = 1; index < path.length; ++index) {
|
|
1452
|
+
const assoc = path[index]?._artifact;
|
|
1453
|
+
if (assoc?.target)
|
|
1454
|
+
return (assoc === user) ? index + 1 : 0;
|
|
1455
|
+
}
|
|
1456
|
+
return 0;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
function checkOnlyForeignKeyNavigation( user, path, startIndex = 0, msgPrefix = '' ) {
|
|
1460
|
+
// has to be run after foreign-key rewrite
|
|
1461
|
+
const outer = user._pathHead?._origin;
|
|
1462
|
+
let assoc = outer?.foreignKeys &&
|
|
1463
|
+
pathStartsWithSelf( { path } ) == null && // not $self or CDS var like $now
|
|
1464
|
+
outer;
|
|
1465
|
+
for (let index = startIndex; index < path.length; ++index) {
|
|
1466
|
+
if (assoc?.target) {
|
|
1467
|
+
if (!assoc.foreignKeys) {
|
|
1468
|
+
error( 'ref-unexpected-assoc', [ path[index - 1].location, user ],
|
|
1469
|
+
{ '#': `${ msgPrefix }unmanaged`, alias: '$self' } );
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1472
|
+
if (!assoc.$keysNavigation)
|
|
1473
|
+
Functions.addForeignKeyNavigations( assoc, true );
|
|
1474
|
+
index = checkCoveredByForeignKey( assoc, path, index, user, msgPrefix );
|
|
1475
|
+
}
|
|
1476
|
+
assoc = path[index]?._artifact;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
function checkCoveredByForeignKey( nav, path, index, user, msgPrefix = '' ) {
|
|
1481
|
+
const assoc = nav;
|
|
1482
|
+
while (index < path.length) {
|
|
1483
|
+
const item = path[index];
|
|
1484
|
+
nav = nav.$keysNavigation?.[item.id];
|
|
1485
|
+
if (!nav)
|
|
1486
|
+
break;
|
|
1487
|
+
if (nav._artifact)
|
|
1488
|
+
return index;
|
|
1489
|
+
++index;
|
|
1490
|
+
}
|
|
1491
|
+
const last = path[index] || path[index - 1];
|
|
1492
|
+
// TODO: or just location of `last`?
|
|
1493
|
+
// TODO: extra text variant if the foreign keys are the key elements !
|
|
1494
|
+
// eslint-disable-next-line no-nested-ternary
|
|
1495
|
+
const txt = index >= path.length
|
|
1496
|
+
? 'complete'
|
|
1497
|
+
: (isAssocToPrimaryKeys( assoc ) ? 'keys' : 'std');
|
|
1498
|
+
// eslint-disable-next-line max-len
|
|
1499
|
+
error( 'ref-invalid-navigation', [ last.location, user ], {
|
|
1500
|
+
'#': msgPrefix + txt, art: assoc, name: last.id, alias: '$self',
|
|
1501
|
+
}, {
|
|
1502
|
+
std: 'Can follow association $(ART) only to its foreign key references, not to $(NAME)',
|
|
1503
|
+
keys: 'Can follow managed association $(ART) only to the keys of its target, not to $(NAME)',
|
|
1504
|
+
complete: 'The reference must cover a full foreign key reference of association $(ART)',
|
|
1505
|
+
// eslint-disable-next-line max-len
|
|
1506
|
+
'self-std': 'In column ref starting with $(ALIAS), we can follow association $(ART) only to its foreign key references, not to $(NAME)',
|
|
1507
|
+
// eslint-disable-next-line max-len
|
|
1508
|
+
'self-keys': 'In column ref starting with $(ALIAS), we can follow managed association $(ART) only to the keys of its target, not to $(NAME)',
|
|
1509
|
+
// eslint-disable-next-line max-len
|
|
1510
|
+
'self-complete': 'The column reference starting with $(ALIAS) must cover a full foreign key reference of association $(ART)',
|
|
1511
|
+
} );
|
|
1512
|
+
// TODO later: mention allowed ones
|
|
1513
|
+
return path.length;
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
function checkNoUnmanaged( ref, user, messageVariant = 'unmanaged' ) {
|
|
1517
|
+
if (ref._artifact?.on && !ref.$expected) {
|
|
1518
|
+
const { path } = ref;
|
|
1519
|
+
const last = path[path.length - 1];
|
|
1520
|
+
error( 'ref-unexpected-assoc', [ last.location, user ],
|
|
1521
|
+
{ '#': messageVariant, alias: '$self' } );
|
|
1522
|
+
}
|
|
1145
1523
|
}
|
|
1146
1524
|
|
|
1147
1525
|
// Low-level functions --------------------------------------------------------
|
|
@@ -1155,7 +1533,7 @@ function fns( model ) {
|
|
|
1155
1533
|
* @param {object} [textParams]
|
|
1156
1534
|
*/
|
|
1157
1535
|
function signalNotFound( msgId, location, valid, textParams ) {
|
|
1158
|
-
if (location.$notFound)
|
|
1536
|
+
if (location.$notFound) // TODO: still necessary?
|
|
1159
1537
|
return;
|
|
1160
1538
|
location.$notFound = true;
|
|
1161
1539
|
/** @type {object} */
|
|
@@ -1177,13 +1555,14 @@ function fns( model ) {
|
|
|
1177
1555
|
* @param {any} location
|
|
1178
1556
|
*/
|
|
1179
1557
|
function signalMissingElementAccess( art, location ) {
|
|
1558
|
+
// TODO: ref-undefined-var ?
|
|
1180
1559
|
const err = message( 'ref-expected-element', location,
|
|
1181
1560
|
{ '#': 'magicVar', id: art.name.id } );
|
|
1182
1561
|
// Mapping for better valid names: from -> $at.from
|
|
1183
|
-
const valid = Object.keys(art.elements || {}).reduce((prev, curr) => {
|
|
1562
|
+
const valid = Object.keys( art.elements || {} ).reduce( (prev, curr) => {
|
|
1184
1563
|
prev[`${ art.name.id }.${ curr }`] = true;
|
|
1185
1564
|
return prev;
|
|
1186
|
-
}, Object.create(null));
|
|
1565
|
+
}, Object.create( null ) );
|
|
1187
1566
|
attachAndEmitValidNames( err, valid );
|
|
1188
1567
|
}
|
|
1189
1568
|
|
|
@@ -1205,15 +1584,15 @@ function fns( model ) {
|
|
|
1205
1584
|
const art = valid[name];
|
|
1206
1585
|
// ignore internal types such as cds.Association, ignore names with dot for
|
|
1207
1586
|
// CDL references to main artifacts:
|
|
1208
|
-
if (!art.internal && !art.deprecated && art
|
|
1209
|
-
(viaCdl ? art._main || !name.includes('.') : art.kind !== 'namespace'))
|
|
1587
|
+
if (!art.internal && !art.deprecated && art.name?.$inferred !== '$internal' &&
|
|
1588
|
+
(viaCdl ? art._main || !name.includes( '.' ) : art.kind !== 'namespace'))
|
|
1210
1589
|
msg.validNames[name] = art;
|
|
1211
1590
|
}
|
|
1212
1591
|
|
|
1213
1592
|
if (options.testMode && !options.$recompile) {
|
|
1214
1593
|
// no semantic location => either first of [loc, semantic loc] pair or just location.
|
|
1215
1594
|
const loc = msg.$location[0] || msg.$location;
|
|
1216
|
-
const names = Object.keys(msg.validNames);
|
|
1595
|
+
const names = Object.keys( msg.validNames );
|
|
1217
1596
|
names.sort();
|
|
1218
1597
|
if (names.length > 22) {
|
|
1219
1598
|
names.length = 20;
|
|
@@ -1221,7 +1600,7 @@ function fns( model ) {
|
|
|
1221
1600
|
}
|
|
1222
1601
|
info( null, [ loc, null ],
|
|
1223
1602
|
{ '#': !names.length ? 'zero' : 'std' },
|
|
1224
|
-
{ std: `Valid: ${ names.join(', ') }`, zero: 'No valid names' });
|
|
1603
|
+
{ std: `Valid: ${ names.join( ', ' ) }`, zero: 'No valid names' } );
|
|
1225
1604
|
}
|
|
1226
1605
|
}
|
|
1227
1606
|
}
|