@sap/cds-compiler 3.9.4 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +92 -4
- package/README.md +0 -1
- package/bin/cdsc.js +11 -23
- package/bin/cdsse.js +3 -3
- package/doc/API.md +5 -0
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +17 -1
- package/doc/CHANGELOG_DEPRECATED.md +28 -0
- package/lib/api/.eslintrc.json +1 -1
- package/lib/api/main.js +26 -8
- package/lib/api/options.js +2 -0
- package/lib/base/error.js +2 -0
- package/lib/base/message-registry.js +143 -64
- package/lib/base/messages.js +213 -107
- package/lib/base/model.js +11 -11
- package/lib/checks/.eslintrc.json +1 -1
- package/lib/checks/annotationsOData.js +2 -2
- package/lib/checks/elements.js +1 -1
- package/lib/checks/enricher.js +26 -3
- package/lib/checks/onConditions.js +67 -12
- package/lib/checks/queryNoDbArtifacts.js +106 -105
- package/lib/checks/sql-snippets.js +2 -0
- package/lib/checks/types.js +12 -6
- package/lib/checks/validator.js +2 -2
- package/lib/compiler/assert-consistency.js +10 -8
- package/lib/compiler/builtins.js +8 -2
- package/lib/compiler/checks.js +52 -35
- package/lib/compiler/define.js +31 -26
- package/lib/compiler/extend.js +120 -65
- package/lib/compiler/finalize-parse-cdl.js +12 -43
- package/lib/compiler/generate.js +16 -5
- package/lib/compiler/index.js +8 -5
- package/lib/compiler/kick-start.js +4 -3
- package/lib/compiler/populate.js +96 -95
- package/lib/compiler/propagator.js +7 -8
- package/lib/compiler/resolve.js +377 -103
- package/lib/compiler/shared.js +794 -517
- package/lib/compiler/tweak-assocs.js +8 -6
- package/lib/compiler/utils.js +44 -0
- package/lib/edm/annotations/genericTranslation.js +12 -4
- package/lib/edm/csn2edm.js +34 -32
- package/lib/edm/edm.js +34 -31
- package/lib/edm/edmAnnoPreprocessor.js +0 -23
- package/lib/edm/edmInboundChecks.js +7 -2
- package/lib/edm/edmPreprocessor.js +18 -17
- package/lib/edm/edmUtils.js +8 -4
- package/lib/gen/Dictionary.json +18 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +4 -2
- package/lib/gen/languageParser.js +5006 -4582
- package/lib/json/from-csn.js +157 -112
- package/lib/json/to-csn.js +60 -89
- package/lib/language/antlrParser.js +17 -13
- package/lib/language/docCommentParser.js +11 -1
- package/lib/language/genericAntlrParser.js +13 -10
- package/lib/language/language.g4 +168 -97
- package/lib/main.d.ts +128 -36
- package/lib/main.js +1 -1
- package/lib/model/csnRefs.js +24 -5
- package/lib/model/csnUtils.js +9 -8
- package/lib/model/revealInternalProperties.js +7 -12
- package/lib/modelCompare/compare.js +1 -1
- package/lib/modelCompare/utils/filter.js +40 -2
- package/lib/optionProcessor.js +0 -3
- package/lib/render/toCdl.js +247 -214
- package/lib/render/toHdbcds.js +197 -181
- package/lib/render/toSql.js +325 -289
- package/lib/render/utils/common.js +42 -4
- package/lib/render/utils/delta.js +1 -1
- package/lib/render/utils/sql.js +3 -3
- package/lib/transform/braceExpression.js +2 -2
- package/lib/transform/db/.eslintrc.json +1 -1
- package/lib/transform/db/applyTransformations.js +3 -3
- package/lib/transform/db/associations.js +24 -12
- package/lib/transform/db/expansion.js +17 -18
- package/lib/transform/db/flattening.js +17 -21
- package/lib/transform/db/rewriteCalculatedElements.js +171 -64
- package/lib/transform/db/views.js +3 -4
- package/lib/transform/draft/db.js +21 -12
- package/lib/transform/draft/odata.js +4 -0
- package/lib/transform/forOdataNew.js +11 -10
- package/lib/transform/forRelationalDB.js +12 -7
- package/lib/transform/localized.js +4 -2
- package/lib/transform/odata/toFinalBaseType.js +5 -5
- package/lib/transform/odata/typesExposure.js +3 -3
- package/lib/transform/parseExpr.js +3 -0
- package/lib/transform/transformUtilsNew.js +43 -23
- package/lib/transform/translateAssocsToJoins.js +7 -6
- package/lib/transform/universalCsn/.eslintrc.json +1 -1
- package/lib/transform/universalCsn/coreComputed.js +7 -5
- package/lib/transform/universalCsn/universalCsnEnricher.js +12 -12
- package/package.json +2 -2
- package/share/messages/{duplicate-autoexposed.md → def-duplicate-autoexposed.md} +5 -1
- package/share/messages/message-explanations.json +1 -1
package/lib/compiler/shared.js
CHANGED
|
@@ -3,27 +3,25 @@
|
|
|
3
3
|
|
|
4
4
|
'use strict';
|
|
5
5
|
|
|
6
|
+
const { CompilerAssertion } = require('../base/error');
|
|
6
7
|
const { searchName } = require('../base/messages');
|
|
7
|
-
const { isDeprecatedEnabled } = require('../base/model');
|
|
8
8
|
|
|
9
9
|
const {
|
|
10
10
|
setLink,
|
|
11
11
|
setArtifactLink,
|
|
12
12
|
dependsOn,
|
|
13
13
|
pathName,
|
|
14
|
+
userQuery,
|
|
15
|
+
definedViaCdl,
|
|
14
16
|
} = require('./utils');
|
|
15
17
|
|
|
16
|
-
function artifactsEnv( art ) {
|
|
17
|
-
return art._subArtifacts || Object.create(null);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
18
|
/**
|
|
21
19
|
* Main export function of this file. Attach "resolve" functions shared for phase
|
|
22
20
|
* "define" and "resolve" to `model.$functions`, where argument `model` is the XSN.
|
|
23
21
|
*
|
|
24
|
-
* Before calling
|
|
25
|
-
* in model.$
|
|
26
|
-
* - `
|
|
22
|
+
* Before calling `resolvePath`, make sure that the following function
|
|
23
|
+
* in model.$function is set:
|
|
24
|
+
* - `navigationEnv`: a function which returns the search environment defined by
|
|
27
25
|
* its argument, e.g. a function which returns the dictionary of subartifacts.
|
|
28
26
|
*
|
|
29
27
|
* @param {XSN.Model} model
|
|
@@ -34,153 +32,314 @@ function fns( model ) {
|
|
|
34
32
|
const {
|
|
35
33
|
info, warning, error, message,
|
|
36
34
|
} = model.$messageFunctions;
|
|
35
|
+
const Functions = model.$functions;
|
|
36
|
+
|
|
37
|
+
const referenceSemantics = {
|
|
38
|
+
// global: ------------------------------------------------------------------
|
|
39
|
+
using: { // only used to produce error message
|
|
40
|
+
isMainRef: 'all',
|
|
41
|
+
lexical: null,
|
|
42
|
+
dynamic: modelDefinitions,
|
|
43
|
+
notFound: undefinedDefinition,
|
|
44
|
+
},
|
|
45
|
+
// only used for the main annotate/extend statements, not inner ones:
|
|
46
|
+
annotate: {
|
|
47
|
+
isMainRef: 'all',
|
|
48
|
+
lexical: userBlock,
|
|
49
|
+
dynamic: modelDefinitions,
|
|
50
|
+
notFound: undefinedForAnnotate,
|
|
51
|
+
},
|
|
52
|
+
extend: {
|
|
53
|
+
isMainRef: 'no-generated',
|
|
54
|
+
lexical: userBlock,
|
|
55
|
+
dynamic: modelDefinitions,
|
|
56
|
+
notFound: undefinedDefinition,
|
|
57
|
+
},
|
|
58
|
+
_extensions: {
|
|
59
|
+
isMainRef: 'all',
|
|
60
|
+
lexical: userBlock,
|
|
61
|
+
dynamic: modelDefinitions,
|
|
62
|
+
notFound: false, // without message
|
|
63
|
+
},
|
|
64
|
+
include: {
|
|
65
|
+
isMainRef: 'no-generated',
|
|
66
|
+
lexical: userBlock,
|
|
67
|
+
dynamic: modelBuiltinsOrDefinitions,
|
|
68
|
+
notFound: undefinedDefinition,
|
|
69
|
+
},
|
|
70
|
+
viewInclude: 'include', // TODO: do differently
|
|
71
|
+
target: {
|
|
72
|
+
isMainRef: 'no-autoexposed',
|
|
73
|
+
lexical: userBlock,
|
|
74
|
+
dynamic: modelBuiltinsOrDefinitions,
|
|
75
|
+
notFound: undefinedDefinition,
|
|
76
|
+
// special `scope`s for redirections:
|
|
77
|
+
global: () => ({ isMainRef: 'all', dynamic: modelDefinitions }),
|
|
78
|
+
},
|
|
79
|
+
targetAspect: { // TODO: do differently
|
|
80
|
+
isMainRef: 'no-autoexposed',
|
|
81
|
+
lexical: userBlock,
|
|
82
|
+
dynamic: modelBuiltinsOrDefinitions,
|
|
83
|
+
notFound: undefinedDefinition,
|
|
84
|
+
},
|
|
85
|
+
from: {
|
|
86
|
+
isMainRef: 'no-autoexposed',
|
|
87
|
+
lexical: userBlock,
|
|
88
|
+
dynamic: modelBuiltinsOrDefinitions,
|
|
89
|
+
notFound: undefinedDefinition,
|
|
90
|
+
navigation: environment,
|
|
91
|
+
},
|
|
92
|
+
type: {
|
|
93
|
+
isMainRef: 'no-autoexposed',
|
|
94
|
+
lexical: userBlock,
|
|
95
|
+
dynamic: modelBuiltinsOrDefinitions,
|
|
96
|
+
notFound: undefinedDefinition,
|
|
97
|
+
navigation: targetAspectOnly,
|
|
98
|
+
// special `scope`s for CDL parser - TYPE OF (TODO generated?), cds.Association:
|
|
99
|
+
typeOf: typeOfSemantics,
|
|
100
|
+
global: () => ({ isMainRef: 'no-autoexposed', dynamic: modelDefinitions }),
|
|
101
|
+
},
|
|
102
|
+
actionParamType: 'type', // TODO: do differently
|
|
103
|
+
eventType: 'type', // TODO: do differently
|
|
104
|
+
// element references without lexical scope (except $self/$projection): -----
|
|
105
|
+
targetElement: {
|
|
106
|
+
lexical: null,
|
|
107
|
+
dollar: false,
|
|
108
|
+
dynamic: targetElements,
|
|
109
|
+
notFound: undefinedTargetElement,
|
|
110
|
+
param: paramSemantics,
|
|
111
|
+
},
|
|
112
|
+
filter: {
|
|
113
|
+
lexical: justDollarSelf,
|
|
114
|
+
dollar: true,
|
|
115
|
+
dynamic: targetElements,
|
|
116
|
+
notFound: undefinedTargetElement,
|
|
117
|
+
param: paramSemantics,
|
|
118
|
+
},
|
|
119
|
+
'calc-filter': {
|
|
120
|
+
lexical: justDollarSelf,
|
|
121
|
+
dollar: true,
|
|
122
|
+
dynamic: targetElements,
|
|
123
|
+
notFound: undefinedTargetElement,
|
|
124
|
+
param: paramUnsupported,
|
|
125
|
+
},
|
|
126
|
+
default: {
|
|
127
|
+
lexical: null,
|
|
128
|
+
dollar: true,
|
|
129
|
+
dynamic: () => Object.create( null ),
|
|
130
|
+
notFound: undefinedVariable,
|
|
131
|
+
param: paramUnsupported,
|
|
132
|
+
},
|
|
133
|
+
// general element references -----------------------------------------------
|
|
134
|
+
expr: { // TODO: this is too general -> column
|
|
135
|
+
lexical: tableAliasesIfNotExtendAndSelf,
|
|
136
|
+
dollar: true,
|
|
137
|
+
dynamic: combinedSourcesOrParentElements,
|
|
138
|
+
notFound: undefinedSourceElement,
|
|
139
|
+
param: paramSemantics,
|
|
140
|
+
nestedColumn: () => ({ // in expand and inline
|
|
141
|
+
lexical: justDollarSelf,
|
|
142
|
+
dollar: true,
|
|
143
|
+
dynamic: nestedElements,
|
|
144
|
+
notFound: undefinedNestedElement,
|
|
145
|
+
param: paramSemantics,
|
|
146
|
+
}),
|
|
147
|
+
},
|
|
148
|
+
'param-only': {
|
|
149
|
+
lexical: null,
|
|
150
|
+
dollar: true,
|
|
151
|
+
dynamic: () => Object.create( null ),
|
|
152
|
+
notFound: undefinedVariable,
|
|
153
|
+
param: paramSemantics,
|
|
154
|
+
},
|
|
155
|
+
calc: {
|
|
156
|
+
lexical: justDollarSelf,
|
|
157
|
+
dollar: true,
|
|
158
|
+
dynamic: parentElements,
|
|
159
|
+
notFound: undefinedParentElement,
|
|
160
|
+
param: paramUnsupported,
|
|
161
|
+
},
|
|
162
|
+
joinOn: {
|
|
163
|
+
lexical: tableAliasesAndSelf,
|
|
164
|
+
dollar: true,
|
|
165
|
+
dynamic: combinedSourcesOrParentElements, // TODO: source alone...
|
|
166
|
+
notFound: undefinedSourceElement,
|
|
167
|
+
param: paramSemantics,
|
|
168
|
+
},
|
|
169
|
+
on: { // unmanaged assoc: outside query, redirected or new assoc in column
|
|
170
|
+
lexical: justDollarSelf,
|
|
171
|
+
allowBareSelf: true,
|
|
172
|
+
dollar: true,
|
|
173
|
+
dynamic: parentElements,
|
|
174
|
+
notFound: undefinedParentElement,
|
|
175
|
+
param: paramUnsupported,
|
|
176
|
+
nestedColumn: () => ({ // in expand and inline
|
|
177
|
+
lexical: justDollarSelf,
|
|
178
|
+
dollar: true,
|
|
179
|
+
dynamic: parentElements,
|
|
180
|
+
notFound: undefinedParentElement,
|
|
181
|
+
}),
|
|
182
|
+
},
|
|
183
|
+
'mixin-on': {
|
|
184
|
+
lexical: tableAliasesAndSelf,
|
|
185
|
+
allowBareSelf: true,
|
|
186
|
+
dollar: true,
|
|
187
|
+
dynamic: combinedSourcesOrParentElements,
|
|
188
|
+
notFound: undefinedSourceElement,
|
|
189
|
+
param: paramSemantics, // TODO: check that assocs containing param in ON is not published
|
|
190
|
+
},
|
|
191
|
+
'order-by-ref': {
|
|
192
|
+
lexical: tableAliasesAndSelf,
|
|
193
|
+
dollar: true,
|
|
194
|
+
dynamic: parentElements,
|
|
195
|
+
notFound: undefinedOrderByElement,
|
|
196
|
+
param: paramSemantics,
|
|
197
|
+
},
|
|
198
|
+
'order-by-expr': {
|
|
199
|
+
lexical: tableAliasesAndSelf,
|
|
200
|
+
dollar: true,
|
|
201
|
+
dynamic: combinedSourcesOrParentElements,
|
|
202
|
+
notFound: undefinedSourceElement,
|
|
203
|
+
param: paramSemantics,
|
|
204
|
+
},
|
|
205
|
+
'order-by-set-ref': {
|
|
206
|
+
lexical: tableAliasesAndSelf, // TODO: reject own tab aliases
|
|
207
|
+
dollar: true,
|
|
208
|
+
dynamic: queryElements,
|
|
209
|
+
notFound: undefinedParentElement,
|
|
210
|
+
param: paramSemantics,
|
|
211
|
+
},
|
|
212
|
+
'order-by-set-expr': {
|
|
213
|
+
lexical: tableAliasesAndSelf, // TODO: reject own tab aliases
|
|
214
|
+
dollar: true,
|
|
215
|
+
dynamic: () => Object.create( null ),
|
|
216
|
+
notFound: undefinedVariable,
|
|
217
|
+
param: paramSemantics,
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
|
|
37
221
|
// TODO: combine envFn and assoc ?
|
|
38
222
|
const specExpected = {
|
|
39
|
-
|
|
40
|
-
envFn: artifactsEnv,
|
|
41
|
-
artItemsCount: Number.MAX_SAFE_INTEGER,
|
|
42
|
-
useDefinitions: true,
|
|
43
|
-
global: 'definitions',
|
|
44
|
-
},
|
|
45
|
-
// TODO: re-check --------------------------------------------------------
|
|
46
|
-
annotation: { useDefinitions: true, noMessage: true, global: 'vocabularies' },
|
|
223
|
+
using: {}, // for using declaration
|
|
47
224
|
// TODO: artifact references ---------------------------------------------
|
|
48
|
-
extend: {
|
|
49
|
-
useDefinitions: true,
|
|
50
|
-
envFn: artifactsEnv,
|
|
51
|
-
artItemsCount: Number.MAX_SAFE_INTEGER,
|
|
52
|
-
},
|
|
225
|
+
extend: {},
|
|
53
226
|
// ref in top-level EXTEND
|
|
54
|
-
annotate: {
|
|
55
|
-
|
|
56
|
-
envFn: artifactsEnv,
|
|
57
|
-
artItemsCount: Number.MAX_SAFE_INTEGER,
|
|
58
|
-
undefinedDef: 'anno-undefined-def',
|
|
59
|
-
undefinedArt: 'anno-undefined-art',
|
|
60
|
-
allowAutoexposed: true, // TODO: think about Info/Warning
|
|
61
|
-
noMessageForLocalized: true, // TODO: should we issue a Debug message for code completion?
|
|
62
|
-
},
|
|
227
|
+
annotate: {},
|
|
228
|
+
_extensions: {},
|
|
63
229
|
type: { // TODO: more detailed later (e.g. for enum base type?)
|
|
64
|
-
envFn: artifactsEnv,
|
|
65
230
|
check: checkTypeRef,
|
|
66
|
-
expectedMsgId: '
|
|
231
|
+
expectedMsgId: 'ref-expecting-type',
|
|
67
232
|
sloppyMsgId: 'ref-sloppy-type',
|
|
68
|
-
deprecateSmart: true,
|
|
69
233
|
},
|
|
70
234
|
actionParamType: {
|
|
71
|
-
envFn: artifactsEnv,
|
|
72
235
|
check: checkActionParamTypeRef,
|
|
73
|
-
expectedMsgId: '
|
|
236
|
+
expectedMsgId: 'ref-expecting-action-param-type',
|
|
74
237
|
sloppyMsgId: 'ref-sloppy-actionparam-type',
|
|
75
|
-
deprecateSmart: true,
|
|
76
238
|
},
|
|
77
239
|
eventType: {
|
|
78
|
-
envFn: artifactsEnv,
|
|
79
240
|
check: checkEventTypeRef,
|
|
80
|
-
expectedMsgId: '
|
|
241
|
+
expectedMsgId: 'ref-expecting-event-type',
|
|
81
242
|
sloppyMsgId: 'ref-sloppy-event-type',
|
|
82
|
-
deprecateSmart: true,
|
|
83
243
|
},
|
|
84
244
|
include: {
|
|
85
245
|
check: checkIncludesRef,
|
|
86
|
-
expectedMsgId: '
|
|
87
|
-
envFn: artifactsEnv,
|
|
246
|
+
expectedMsgId: 'ref-expecting-struct',
|
|
88
247
|
},
|
|
89
248
|
viewInclude: {
|
|
90
249
|
check: checkViewIncludesRef,
|
|
91
250
|
expectedMsgId: 'ref-expecting-bare-aspect',
|
|
92
|
-
envFn: artifactsEnv,
|
|
93
251
|
},
|
|
94
252
|
target: {
|
|
95
253
|
check: checkEntityRef,
|
|
96
|
-
expectedMsgId: '
|
|
254
|
+
expectedMsgId: 'ref-expecting-entity',
|
|
97
255
|
noDep: true,
|
|
98
|
-
envFn: artifactsEnv,
|
|
99
256
|
},
|
|
100
|
-
|
|
257
|
+
targetAspect: {
|
|
101
258
|
check: checkTargetRef,
|
|
102
|
-
expectedMsgId: '
|
|
259
|
+
expectedMsgId: 'ref-expecting-target',
|
|
103
260
|
sloppyMsgId: 'ref-sloppy-target',
|
|
104
261
|
noDep: 'only-entity',
|
|
105
|
-
envFn: artifactsEnv,
|
|
106
262
|
},
|
|
107
263
|
from: {
|
|
108
|
-
envFn: artifactsEnv,
|
|
109
264
|
check: checkSourceRef,
|
|
110
|
-
expectedMsgId: '
|
|
265
|
+
expectedMsgId: 'ref-expecting-source',
|
|
111
266
|
assoc: 'from',
|
|
112
267
|
argsSpec: 'expr',
|
|
113
|
-
deprecateSmart: true,
|
|
114
268
|
},
|
|
115
269
|
// element references ----------------------------------------------------
|
|
116
|
-
// if we want to disallow assoc nav for TYPE, do not do it here
|
|
117
|
-
typeOf: { next: '_$next', dollar: true }, // TODO: disallow in var
|
|
118
270
|
// TODO: dep for (explicit+implicit!) foreign keys
|
|
119
|
-
|
|
271
|
+
// TODO: also check that we do not follow associations in foreign key? no args, no filter
|
|
272
|
+
targetElement: { assoc: false },
|
|
120
273
|
filter: {
|
|
121
|
-
|
|
274
|
+
escape: 'param',
|
|
275
|
+
},
|
|
276
|
+
'calc-filter': {
|
|
277
|
+
escape: 'param',
|
|
122
278
|
},
|
|
123
279
|
default: {
|
|
124
|
-
next: '_$next',
|
|
125
|
-
dollar: true,
|
|
126
280
|
check: checkConstRef,
|
|
127
|
-
expectedMsgId: '
|
|
281
|
+
expectedMsgId: 'ref-expecting-const',
|
|
128
282
|
},
|
|
129
|
-
expr: {
|
|
130
|
-
|
|
283
|
+
expr: {
|
|
284
|
+
escape: 'param', assoc: 'nav',
|
|
285
|
+
},
|
|
286
|
+
'param-only': {
|
|
287
|
+
escape: 'param',
|
|
131
288
|
},
|
|
132
|
-
|
|
133
|
-
|
|
289
|
+
calc: {
|
|
290
|
+
assoc: 'nav',
|
|
134
291
|
},
|
|
135
|
-
|
|
136
|
-
|
|
292
|
+
joinOn: { // ON condition for JOIN: should be different to 'expr'!
|
|
293
|
+
escape: 'param', assoc: 'nav',
|
|
137
294
|
},
|
|
138
295
|
on: { // TODO: there will also be a 'from-on' (see 'expr')
|
|
139
|
-
noAliasOrMixin: true, // TODO: some headReject or similar
|
|
140
|
-
next: '_$next', // TODO: lexical: ... how to find the (next) lexical environment
|
|
141
|
-
dollar: true,
|
|
142
|
-
rootEnv: 'elements', // the final environment for the path root
|
|
143
296
|
noDep: true, // do not set dependency for circular-check
|
|
144
297
|
allowSelf: true,
|
|
145
298
|
}, // TODO: special assoc for only on user
|
|
146
299
|
'mixin-on': {
|
|
147
300
|
escape: 'param', // TODO: extra check that assocs containing param in ON is not published
|
|
148
|
-
next: '_$next', // TODO: lexical: ... how to find the (next) lexical environment
|
|
149
|
-
dollar: true,
|
|
150
301
|
noDep: true, // do not set dependency for circular-check
|
|
151
302
|
allowSelf: true,
|
|
152
303
|
}, // TODO: special assoc for only on user
|
|
153
|
-
|
|
304
|
+
// ---------marker for getPathRoot replaced-----------
|
|
305
|
+
'order-by-ref': {
|
|
154
306
|
next: '_$next',
|
|
155
307
|
dollar: true,
|
|
156
308
|
escape: 'param',
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
},
|
|
161
|
-
'order-by': {
|
|
309
|
+
assoc: 'nav',
|
|
310
|
+
dynamic: 'query',
|
|
311
|
+
deprecatedSourceRefs: true,
|
|
312
|
+
},
|
|
313
|
+
'order-by-expr': {
|
|
162
314
|
next: '_$next',
|
|
163
315
|
dollar: true,
|
|
164
316
|
escape: 'param',
|
|
165
317
|
assoc: 'nav',
|
|
166
|
-
deprecatedSourceRefs: true,
|
|
167
318
|
},
|
|
168
|
-
'order-by-
|
|
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': {
|
|
169
328
|
next: '_$next',
|
|
170
329
|
dollar: true,
|
|
171
330
|
escape: 'param',
|
|
172
331
|
noDep: true,
|
|
173
|
-
|
|
332
|
+
dynamic: false,
|
|
333
|
+
lexical: 'next',
|
|
174
334
|
},
|
|
175
335
|
// expr TODO: better - on condition for assoc, other on
|
|
176
336
|
// expr TODO: write dependency, but care for $self
|
|
177
337
|
param: {
|
|
178
338
|
check: checkConstRef,
|
|
179
|
-
expectedMsgId: '
|
|
339
|
+
expectedMsgId: 'ref-expecting-const',
|
|
180
340
|
},
|
|
181
341
|
};
|
|
182
342
|
|
|
183
|
-
const VolatileFns = model.$volatileFunctions;
|
|
184
343
|
Object.assign( model.$functions, {
|
|
185
344
|
resolveUncheckedPath,
|
|
186
345
|
resolveTypeArgumentsUnchecked,
|
|
@@ -267,174 +426,63 @@ function fns( model ) {
|
|
|
267
426
|
// the return value. Otherwise, complain if `unchecked` is false, and set
|
|
268
427
|
// `ref.absolute` to the path name of `ref`.
|
|
269
428
|
// Used for collecting artifact extension, and annotation assignments.
|
|
270
|
-
|
|
271
|
-
|
|
429
|
+
//
|
|
430
|
+
// Return '' if the ref is good, but points to an element.
|
|
431
|
+
function resolveUncheckedPath( ref, refCtx, user ) {
|
|
432
|
+
const { path } = ref;
|
|
433
|
+
if (!path || path.broken) // incomplete type AST
|
|
272
434
|
return undefined;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
if (art
|
|
280
|
-
art =
|
|
281
|
-
|
|
282
|
-
return (
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
return art.name.absolute;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
function userQuery( user ) {
|
|
290
|
-
// TODO: we need _query links set by the definer
|
|
291
|
-
while (user._main) {
|
|
292
|
-
if (user.kind === 'select' || user.kind === '$join')
|
|
293
|
-
return user;
|
|
294
|
-
user = user._parent;
|
|
295
|
-
}
|
|
296
|
-
return null;
|
|
435
|
+
|
|
436
|
+
const semantics = referenceSemantics[refCtx];
|
|
437
|
+
let art = getPathRoot( ref, semantics, user );
|
|
438
|
+
if (ref.scope && ref.scope !== 'global')
|
|
439
|
+
return ''; // TYPE OF, Main:elem
|
|
440
|
+
|
|
441
|
+
if (Array.isArray( art ))
|
|
442
|
+
art = art[0];
|
|
443
|
+
if (!art)
|
|
444
|
+
return (semantics.notFound) ? art : pathName( path );
|
|
445
|
+
if (path.length === 1)
|
|
446
|
+
return art.name.absolute; // TODO: name.id
|
|
447
|
+
return `${ art.name.absolute }.${ pathName( ref.path.slice(1) ) }`;
|
|
297
448
|
}
|
|
298
449
|
|
|
299
450
|
// Return artifact or element referred by the path in `ref`. The first
|
|
300
451
|
// environment we search in is `env`. If no such artifact or element exist,
|
|
301
452
|
// complain with message and return `undefined`. Record a dependency from
|
|
302
453
|
// `user` to the found artifact if `user` is provided.
|
|
303
|
-
function resolvePath( ref, expected, user
|
|
454
|
+
function resolvePath( ref, expected, user ) {
|
|
455
|
+
const origUser = user;
|
|
456
|
+
user = user._user || user;
|
|
304
457
|
if (ref == null) // no references -> nothing to do
|
|
305
458
|
return undefined;
|
|
306
459
|
if (ref._artifact !== undefined)
|
|
307
460
|
return ref._artifact;
|
|
308
|
-
|
|
461
|
+
|
|
462
|
+
const { path } = ref;
|
|
463
|
+
if (!path || path.broken || !path.length) {
|
|
309
464
|
// incomplete type AST or empty env (already reported)
|
|
310
465
|
return setArtifactLink( ref, undefined );
|
|
311
466
|
}
|
|
312
467
|
|
|
313
|
-
|
|
314
|
-
const
|
|
315
|
-
const head = path[0];
|
|
316
|
-
// message(null,head.location,{art:user,expected, id: head.id},
|
|
317
|
-
// 'Info','User $(ART), $(EXPECTED) $(ID)')
|
|
318
|
-
let env = user._block; // artifact references: block
|
|
468
|
+
const s = referenceSemantics[expected]; // TODO: temp indirection
|
|
469
|
+
const semantics = (typeof s === 'string') ? referenceSemantics[s] : s;
|
|
319
470
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
325
|
-
if (user.$syntax === 'calc')
|
|
326
|
-
error('ref-unexpected-scope', [ ref.location, user ], { '#': 'calc' } );
|
|
471
|
+
const r = getPathRoot( ref, semantics, origUser );
|
|
472
|
+
const root = r && acceptPathRoot( r, ref, semantics, user );
|
|
473
|
+
if (!root)
|
|
474
|
+
return setArtifactLink( ref, root );
|
|
327
475
|
|
|
476
|
+
let spec = specExpected[expected];
|
|
477
|
+
if (ref.scope === 'param') {
|
|
478
|
+
if (!spec.escape)
|
|
479
|
+
throw new CompilerAssertion( 'getPathRoot() should have returned falsy val' );
|
|
328
480
|
spec = specExpected[spec.escape];
|
|
329
|
-
// In queries and query entities, the first lexical search environment
|
|
330
|
-
// are the parameters, otherwise the block. It is currently ensured that
|
|
331
|
-
// _block in queries is the same as _block of the query entity:
|
|
332
|
-
const lexical = (user._main || user).$tableAliases; // queries (but also query entities)
|
|
333
|
-
env = lexical && lexical.$parameters || user._block;
|
|
334
|
-
extDict = null; // let getPathRoot() choose it
|
|
335
|
-
}
|
|
336
|
-
else if (spec.next === '__none_') {
|
|
337
|
-
env = {};
|
|
338
|
-
}
|
|
339
|
-
else if (spec.next) { // TODO: combine spec.next / spec.lexical to spec.lexical
|
|
340
|
-
// TODO: SIMPLIFY this function
|
|
341
|
-
const query = (spec.lexical === 'main') ? user._main : userQuery( user );
|
|
342
|
-
// in path filter, just $magic (and $parameters)
|
|
343
|
-
env = (spec.lexical === 'from') ? query._parent : query || user._main || user;
|
|
344
|
-
// queries: first table aliases, then $magic - value refs: first $self, then $magic
|
|
345
|
-
if (!extDict && !spec.noExt) {
|
|
346
|
-
// TODO: change to name restriction for $joins, not own environments
|
|
347
|
-
extDict = query && spec.rootEnv !== 'elements' &&
|
|
348
|
-
// first step: only use _combined of real query - TODO:
|
|
349
|
-
// reject if not visible, but not allow more (!)
|
|
350
|
-
(query._combined || query._parent._combined) ||
|
|
351
|
-
VolatileFns.environment( user._main ? user._parent : user );
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// 'global' for CSN later in value paths, CDL for Association/Composition:
|
|
356
|
-
let art = (ref.scope === 'global' || spec.global)
|
|
357
|
-
? getPathRoot( path, spec, user, {}, model[spec.global || 'definitions'] )
|
|
358
|
-
: getPathRoot( path, spec, user, env, extDict, msgArt || 0 );
|
|
359
|
-
if (!art) {
|
|
360
|
-
return setArtifactLink( ref, art );
|
|
361
|
-
}
|
|
362
|
-
else if (!spec.envFn && user._pathHead) {
|
|
363
|
-
if (art.kind === '$self') {
|
|
364
|
-
const headEnv = VolatileFns.environment( user._pathHead ) &&
|
|
365
|
-
user._pathHead._origin &&
|
|
366
|
-
VolatileFns.environment( user._pathHead._origin );
|
|
367
|
-
rejectBareSelf( spec, path, user, headEnv );
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
else if (art.kind === 'using') {
|
|
371
|
-
const def = model.definitions[art.name.absolute];
|
|
372
|
-
if (!def) {
|
|
373
|
-
// It could be that the artifact was removed and that the using-proxy needs to be reported.
|
|
374
|
-
// The check for $inferred is required to avoid consequential errors for cases such as:
|
|
375
|
-
// using unknown.abc;
|
|
376
|
-
// entity P as projection on abc; // <-- no consequential error here
|
|
377
|
-
if (art.$inferred === 'path-prefix') {
|
|
378
|
-
// head._artifact referred to the `using`. Remove the reference,
|
|
379
|
-
// so that getPathItem() below emits an error.
|
|
380
|
-
setArtifactLink( head, false );
|
|
381
|
-
setArtifactLink( ref, false );
|
|
382
|
-
}
|
|
383
|
-
else {
|
|
384
|
-
return setArtifactLink( ref, false );
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
else if (def.$duplicates) { // redefined art referenced by using proxy
|
|
388
|
-
return setArtifactLink( ref, false );
|
|
389
|
-
}
|
|
390
|
-
else {
|
|
391
|
-
setArtifactLink( head, def ); // we do not want to see the using
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
else if (art.kind === 'mixin') {
|
|
395
|
-
if (spec.noAliasOrMixin) {
|
|
396
|
-
// TODO: good enough for now - change later to not search for table aliases at all
|
|
397
|
-
signalNotFound( 'ref-rejected-on', [ head.location, user ], extDict && [ extDict ],
|
|
398
|
-
{ '#': 'mixin', id: head.id } );
|
|
399
|
-
// also set link on head?
|
|
400
|
-
return setArtifactLink( ref, false );
|
|
401
|
-
}
|
|
402
|
-
// console.log(message( null, art.location, art, {}, 'Info','MIX').toString())
|
|
403
|
-
setLink( head, '_navigation', art );
|
|
404
|
-
}
|
|
405
|
-
else if (art.kind === '$navElement') {
|
|
406
|
-
setLink( head, '_navigation', art );
|
|
407
|
-
setArtifactLink( head, art._origin );
|
|
408
|
-
// TODO: set art?
|
|
409
|
-
}
|
|
410
|
-
else if (art.kind === '$tableAlias' || art.kind === '$self') {
|
|
411
|
-
if (art.kind === '$self') {
|
|
412
|
-
rejectBareSelf( spec, path, user, extDict );
|
|
413
|
-
}
|
|
414
|
-
else if (spec.noAliasOrMixin) {
|
|
415
|
-
// TODO: good enough for now - change later to not search for table aliases at all
|
|
416
|
-
signalNotFound( 'ref-rejected-on', [ head.location, user ], extDict && [ extDict ],
|
|
417
|
-
{ '#': 'alias', id: head.id } );
|
|
418
|
-
// also set link on head?
|
|
419
|
-
return setArtifactLink( ref, false );
|
|
420
|
-
}
|
|
421
|
-
setLink( head, '_navigation', art );
|
|
422
|
-
setArtifactLink( head, art._origin ); // query source or leading query in FROM
|
|
423
|
-
// require('../model/revealInternalProperties').log(model, 'foo.bar.S.V1a')
|
|
424
|
-
if (!art._origin)
|
|
425
|
-
return setArtifactLink( ref, art._origin );
|
|
426
|
-
// if just table alias (with expand), mark `user` with `$noOrigin` to indicate
|
|
427
|
-
// that the corresponding entity should not be put as $origin into the CSN
|
|
428
|
-
if (path.length === 1 && user && art.kind === '$tableAlias')
|
|
429
|
-
user.$noOrigin = true;
|
|
430
481
|
}
|
|
431
482
|
|
|
432
483
|
// how many path items are for artifacts (rest: elements)
|
|
433
|
-
const artItemsCount = (typeof ref.scope === 'number')
|
|
434
|
-
? ref.scope || Number.MAX_SAFE_INTEGER
|
|
435
|
-
: spec.artItemsCount || 1;
|
|
436
484
|
// console.log(expected, ref.path.map(a=>a.id),artItemsCount)
|
|
437
|
-
art = getPathItem(
|
|
485
|
+
let art = getPathItem( ref, semantics, spec, user );
|
|
438
486
|
if (!art)
|
|
439
487
|
return setArtifactLink( ref, art );
|
|
440
488
|
|
|
@@ -445,7 +493,7 @@ function fns( model ) {
|
|
|
445
493
|
setArtifactLink( step, art );
|
|
446
494
|
path.push( step );
|
|
447
495
|
}
|
|
448
|
-
if (spec.check) {
|
|
496
|
+
if (spec.check && !ref.$expected) { // do not check with exists path (is already error)
|
|
449
497
|
const fail = spec.check( art, path );
|
|
450
498
|
if (fail === true) {
|
|
451
499
|
signalNotFound( spec.expectedMsgId, [ ref.location, user ], null );
|
|
@@ -467,7 +515,7 @@ function fns( model ) {
|
|
|
467
515
|
// TODO: location of last path item if not main artifact
|
|
468
516
|
if (spec.assoc === 'from' && art._main) {
|
|
469
517
|
dependsOn( user, art._main, location );
|
|
470
|
-
|
|
518
|
+
environment( art, location, user );
|
|
471
519
|
}
|
|
472
520
|
else if (art.kind !== 'select') { // no real dependency to bare $self
|
|
473
521
|
dependsOn( user, art, location );
|
|
@@ -476,45 +524,10 @@ function fns( model ) {
|
|
|
476
524
|
// element elem: type of elem.x;
|
|
477
525
|
}
|
|
478
526
|
}
|
|
479
|
-
// Warning for CDL TYPE OF references without ':' or shifted ':'
|
|
480
|
-
if (spec.deprecateSmart && typeof ref.scope === 'number' &&
|
|
481
|
-
!(env.$frontend && env.$frontend !== 'cdl'))
|
|
482
|
-
deprecateSmart( ref, art, user );
|
|
483
527
|
// TODO: follow FROM here, see csnRef - fromRef
|
|
484
528
|
return setArtifactLink( ref, art );
|
|
485
529
|
}
|
|
486
530
|
|
|
487
|
-
function rejectBareSelf( spec, path, user, extDict ) {
|
|
488
|
-
if (path.length === 1 && !spec.allowSelf && !user.expand && !user.inline) {
|
|
489
|
-
const head = path[0];
|
|
490
|
-
// TODO: extra text variant for JOIN-ON (if we have an extra `expected`)
|
|
491
|
-
signalNotFound( 'ref-unexpected-self', [ head.location, user ], extDict && [ extDict ],
|
|
492
|
-
{ id: head.id } );
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// Issue errors for "smart" element-in-artifact references
|
|
497
|
-
// without a colon; and errors for misplaced colons in references.
|
|
498
|
-
// This function likely disappears again in cds-compiler v2.x.
|
|
499
|
-
function deprecateSmart( ref, art, user ) {
|
|
500
|
-
const { path } = ref;
|
|
501
|
-
const scope = path.findIndex( i => i._artifact._main );
|
|
502
|
-
if (ref.scope) { // provided a ':' in the ref path
|
|
503
|
-
if (scope === ref.scope) // correctly between main artifact and element
|
|
504
|
-
return;
|
|
505
|
-
const item = path[ref.scope];
|
|
506
|
-
error( 'ref-unexpected-colon', [ item.location, user ], { id: item.id },
|
|
507
|
-
'Replace the colon before $(ID) by a dot' );
|
|
508
|
-
ref.scope = 0; // correct (otherwise CSN refs are wrong)
|
|
509
|
-
}
|
|
510
|
-
if (scope >= 0) { // we have a element-in-artifact reference
|
|
511
|
-
const item = path[scope];
|
|
512
|
-
error( 'ref-missing-colon', [ item.location, user ], { id: item.id },
|
|
513
|
-
'Replace the dot before $(ID) by a colon' );
|
|
514
|
-
ref.scope = scope; // no need to recalculate in to-csn.js
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
531
|
/**
|
|
519
532
|
* Resolve the type arguments of `artifact` according to the type `typeArtifact`.
|
|
520
533
|
* User is used for semantic message location.
|
|
@@ -527,13 +540,15 @@ function fns( model ) {
|
|
|
527
540
|
*
|
|
528
541
|
* Left-over arguments are errors for non-builtins and warnings for builtins.
|
|
529
542
|
*
|
|
543
|
+
* TODO: move to define.js (and probably rename), rewrite (consider syntax-unexpected-argument)
|
|
544
|
+
*
|
|
530
545
|
* @param {object} artifact
|
|
531
546
|
* @param {object} typeArtifact
|
|
532
547
|
* @param {CSN.Artifact} user
|
|
533
548
|
*/
|
|
534
549
|
function resolveTypeArgumentsUnchecked( artifact, typeArtifact, user ) {
|
|
535
550
|
let args = artifact.$typeArgs || [];
|
|
536
|
-
const parameters = typeArtifact
|
|
551
|
+
const parameters = typeArtifact?.parameters || [];
|
|
537
552
|
|
|
538
553
|
if (parameters.length > 0) {
|
|
539
554
|
// For Builtins
|
|
@@ -546,7 +561,7 @@ function fns( model ) {
|
|
|
546
561
|
}
|
|
547
562
|
args = args.slice(parameters.length);
|
|
548
563
|
}
|
|
549
|
-
else if (args.length > 0 && !typeArtifact
|
|
564
|
+
else if (args.length > 0 && !typeArtifact?.builtin) {
|
|
550
565
|
// One or two arguments are interpreted as either length or precision/scale.
|
|
551
566
|
// For builtins, we know what arguments are expected, and we do not need this mapping.
|
|
552
567
|
// Also, we expect non-structured types.
|
|
@@ -566,188 +581,77 @@ function fns( model ) {
|
|
|
566
581
|
|
|
567
582
|
// Warn about left-over arguments.
|
|
568
583
|
if (args.length > 0) {
|
|
569
|
-
const loc = [ args[
|
|
570
|
-
if (typeArtifact
|
|
584
|
+
const loc = [ args[0].location, user ];
|
|
585
|
+
if (typeArtifact?.builtin) {
|
|
571
586
|
message( 'type-ignoring-argument', loc, { art: typeArtifact } );
|
|
572
|
-
|
|
573
|
-
|
|
587
|
+
}
|
|
588
|
+
else if (options.testMode) {
|
|
589
|
+
// Ensure: parser reports error and sets $typeArgs to undefined if more than 2 parameter
|
|
590
|
+
throw new CompilerAssertion( 'More than 2 type arguments set by parser' );
|
|
591
|
+
}
|
|
574
592
|
}
|
|
575
593
|
artifact.$typeArgs = undefined;
|
|
576
594
|
}
|
|
577
595
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
* we search in is `env`. If `unchecked` is equal to `true`, do not report an error
|
|
581
|
-
* if the artifact does not exist. Return a "fresh" artifact for
|
|
582
|
-
* non-existing external using references if `unchecked` is truthy.
|
|
583
|
-
*/
|
|
584
|
-
function getPathRoot( path, spec, user, env, extDict, msgArt ) {
|
|
596
|
+
function getPathRoot( { path, scope, location }, semantics, user ) {
|
|
597
|
+
// TODO: use string value of isMainRef?
|
|
585
598
|
const head = path[0];
|
|
586
|
-
if (!head || !head.id
|
|
599
|
+
if (!head || !head.id)
|
|
587
600
|
return undefined; // parse error
|
|
588
|
-
if (!spec.envFn && user._pathHead && head.id.charAt(0) !== '$') {
|
|
589
|
-
if (spec.rootEnv === 'elements') { // ON condition in expand/inline
|
|
590
|
-
let root = user._pathHead;
|
|
591
|
-
while (root.kind === '$inline')
|
|
592
|
-
root = root._parent;
|
|
593
|
-
return root;
|
|
594
|
-
}
|
|
595
|
-
VolatileFns.environment( user._pathHead ); // make sure _origin is set
|
|
596
|
-
return user._pathHead._origin;
|
|
597
|
-
// const { _origin } = user._pathHead;
|
|
598
|
-
// return (_origin && _origin.kind === '$tableAlias') ? _origin._origin : _origin;
|
|
599
|
-
}
|
|
600
|
-
// if (head.id === 'k') {console.log(Object.keys(user));
|
|
601
|
-
// throw new CompilerAssertion(JSON.stringify(user.name))}
|
|
602
|
-
// if head._artifact is set or is null then it was already computed once
|
|
603
601
|
if (head._artifact !== undefined)
|
|
604
|
-
return
|
|
605
|
-
|
|
606
|
-
// (spec.useDefinitions || env.$frontend === 'json' || env))
|
|
607
|
-
if (!spec.next && !extDict) {
|
|
608
|
-
// CSN artifact paths are always fully qualified so we use
|
|
609
|
-
// model.definitions for the JSON frontend.
|
|
610
|
-
extDict = (spec.useDefinitions || env.$frontend && env.$frontend !== 'cdl')
|
|
611
|
-
? model.definitions
|
|
612
|
-
: model.$builtins;
|
|
613
|
-
}
|
|
614
|
-
const nodollar = !spec.dollar && spec.next;
|
|
615
|
-
const nextProp = spec.next || '_block';
|
|
616
|
-
for (let art = env; art; art = art[nextProp]) {
|
|
617
|
-
if (nodollar && !art._main) // $self stored in main.$tableAliases
|
|
618
|
-
break; // TODO: probably remove _$next link
|
|
619
|
-
const e = art.artifacts || art.$tableAliases || Object.create(null);
|
|
620
|
-
const r = e[head.id];
|
|
621
|
-
if (r && r.$inferred !== '$internal') {
|
|
622
|
-
if (Array.isArray(r)) { // redefinitions
|
|
623
|
-
setArtifactLink( head, r );
|
|
624
|
-
return false;
|
|
625
|
-
}
|
|
626
|
-
// if (head.$delimited && r.kind !== '$tableAlias' && r.kind !== 'mixin')
|
|
627
|
-
// TODO: warning for delimited special - or directly in parser
|
|
628
|
-
if (r.kind === '$parameters') {
|
|
629
|
-
if (!head.$delimited && path.length > 1) {
|
|
630
|
-
message( 'ref-obsolete-parameters', [ head.location, user ],
|
|
631
|
-
{ code: `$parameters.${ path[1].id }`, newcode: `:${ path[1].id }` },
|
|
632
|
-
'Obsolete $(CODE) - replace by $(NEWCODE)' );
|
|
633
|
-
// TODO: replace it in to-csn correspondingly !!!
|
|
634
|
-
return setArtifactLink( head, r );
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
else if (r.kind === '$self') {
|
|
638
|
-
// TODO: handle $delimited differently
|
|
639
|
-
// TODO: $projection only if not delimited _and_ length > 1
|
|
640
|
-
return setArtifactLink( head, r );
|
|
641
|
-
}
|
|
642
|
-
else if (r.kind !== '$tableAlias' || path.length > 1 || user.expand || user.inline) {
|
|
643
|
-
// except "real" table aliases (not $self) with path len 1
|
|
644
|
-
// TODO: $projection only if not delimited _and_ length > 1
|
|
645
|
-
return setArtifactLink( head, r );
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
if (extDict && (!spec.dollar || head.id[0] !== '$')) {
|
|
650
|
-
const r = extDict[head.id];
|
|
651
|
-
if (Array.isArray(r)) {
|
|
652
|
-
if (r[0].kind === '$navElement' && r.every( e => !e._parent.$duplicates )) {
|
|
653
|
-
// only complain about ambiguous source elements if we do not have
|
|
654
|
-
// duplicate table aliases, only mention non-ambiguous source elems
|
|
655
|
-
const uniqueNames = r.filter( e => !e.$duplicates);
|
|
656
|
-
if (uniqueNames.length) {
|
|
657
|
-
const names = uniqueNames.filter( e => e._parent.$inferred !== '$internal' )
|
|
658
|
-
.map( e => `${ e.name.alias }.${ e.name.element }` );
|
|
659
|
-
let variant = names.length === uniqueNames.length ? 'std' : 'few';
|
|
660
|
-
if (names.length === 0)
|
|
661
|
-
variant = 'none';
|
|
662
|
-
error( 'ref-ambiguous', [ head.location, user ], { '#': variant, id: head.id, names });
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
setArtifactLink( head, r );
|
|
666
|
-
return false;
|
|
667
|
-
}
|
|
668
|
-
else if (r) {
|
|
669
|
-
return setArtifactLink( head, r );
|
|
670
|
-
}
|
|
671
|
-
else if (spec.deprecatedSourceRefs && env._combined &&
|
|
672
|
-
isDeprecatedEnabled( options, 'autoCorrectOrderBySourceRefs' )) {
|
|
673
|
-
// User has provided a source element without table alias where a query
|
|
674
|
-
// element is expected. Possible on many DBs (and compiler v1), in CAP
|
|
675
|
-
// only with table alias. Auto-correct it if no duplicate.
|
|
676
|
-
// TODO: we could use that info also in messages when the deprecated flag is not set
|
|
677
|
-
const s = env._combined[head.id];
|
|
678
|
-
if (s && !Array.isArray(s)) {
|
|
679
|
-
path.$prefix = s.name.alias; // pushing it to path directly could be problematic
|
|
680
|
-
warning( null, [ head.location, user ],
|
|
681
|
-
{ id: head.id, newcode: `${ s.name.alias }.${ head.id }` },
|
|
682
|
-
'Replace source element reference $(ID) by $(NEWCODE); auto-corrected' );
|
|
683
|
-
return setArtifactLink( head, s );
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
if (spec.noMessage || msgArt === true && extDict === model.definitions)
|
|
688
|
-
return null;
|
|
602
|
+
return head._artifact;
|
|
603
|
+
const ruser = user._user || user; // TODO: nicer name if we keep this
|
|
689
604
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
if (extDict !== model.definitions) {
|
|
700
|
-
for (const name in extDict) {
|
|
701
|
-
if (!spec.dollar || name[0] !== '$')
|
|
702
|
-
e[name] = extDict[name];
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
else {
|
|
706
|
-
for (const name in extDict) {
|
|
707
|
-
if (!name.includes('.') && (!spec.dollar || name[0] !== '$'))
|
|
708
|
-
e[name] = extDict[name];
|
|
709
|
-
}
|
|
605
|
+
// Handle expand/inline, `type of`, :param, global (internally for CDL):
|
|
606
|
+
if (user._pathHead && !semantics.isMainRef) // in expand/inline
|
|
607
|
+
semantics = semantics.nestedColumn();
|
|
608
|
+
if (typeof scope === 'string') { // typeOf, param, global
|
|
609
|
+
semantics = semantics?.[scope] && semantics[scope]( ruser, path, location );
|
|
610
|
+
if (!semantics) {
|
|
611
|
+
if (semantics == null)
|
|
612
|
+
throw new CompilerAssertion( `Scope ${ scope } is not expected here` );
|
|
613
|
+
return setArtifactLink( head, null );
|
|
710
614
|
}
|
|
711
|
-
valid.push( e );
|
|
712
615
|
}
|
|
616
|
+
const valid = [];
|
|
713
617
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
const
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
alias,
|
|
729
|
-
id: head.id,
|
|
730
|
-
} );
|
|
731
|
-
}
|
|
732
|
-
else {
|
|
733
|
-
const isVirtual = (user.name?.id === head.id && user.virtual?.val);
|
|
734
|
-
const code = isVirtual ? 'virtual null as ‹name›' : '';
|
|
735
|
-
signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
|
|
736
|
-
{ art: head.id, '#': isVirtual ? 'virtual' : 'std', code } );
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
else if (env.$frontend && env.$frontend !== 'cdl' || spec.global) {
|
|
740
|
-
// IDE can inspect <model>.definitions - provide null for valid
|
|
741
|
-
if (!spec.noMessageForLocalized || !head.id.startsWith( 'localized.' )) {
|
|
742
|
-
signalNotFound( spec.undefinedDef || 'ref-undefined-def', [ head.location, user ],
|
|
743
|
-
valid, { art: head.id } );
|
|
618
|
+
// Search in lexical environments, including $self/$projection:
|
|
619
|
+
const { isMainRef } = semantics;
|
|
620
|
+
const lexical = semantics.lexical?.( ruser ); // TODO: _pathHead?
|
|
621
|
+
if (lexical) {
|
|
622
|
+
const [ nextProp, dictProp ] = (isMainRef)
|
|
623
|
+
? [ '_block', 'artifacts' ]
|
|
624
|
+
: [ '_$next', '$tableAliases' ];
|
|
625
|
+
// let notApplicable = ...; // for table aliases in JOIN-ON and UNION order-by
|
|
626
|
+
for (let env = lexical; env; env = env[nextProp]) {
|
|
627
|
+
const dict = env[dictProp] || Object.create(null);
|
|
628
|
+
const r = dict[head.id];
|
|
629
|
+
if (acceptLexical( r, path, semantics, user ))
|
|
630
|
+
return setArtifactLink( head, r );
|
|
631
|
+
valid.push( dict );
|
|
744
632
|
}
|
|
745
633
|
}
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
634
|
+
|
|
635
|
+
// Search in $special (ex $self/$projection) and dynamic environment:
|
|
636
|
+
const dynamicDict = semantics.dynamic( ruser, user._user && user._artifact );
|
|
637
|
+
if (!dynamicDict) // avoid consequential errors
|
|
638
|
+
return setArtifactLink( head, null );
|
|
639
|
+
const isVar = (semantics.dollar && head.id.charAt( 0 ) === '$');
|
|
640
|
+
const dict = (isVar) ? model.$magicVariables.artifacts : dynamicDict;
|
|
641
|
+
const r = dict[head.id];
|
|
642
|
+
if (r)
|
|
643
|
+
return setArtifactLink( head, r );
|
|
644
|
+
|
|
645
|
+
if (!semantics.dollar)
|
|
646
|
+
valid.push( dynamicDict );
|
|
647
|
+
else
|
|
648
|
+
valid.push( model.$magicVariables.artifacts, removeDollarNames( dynamicDict ) );
|
|
649
|
+
if (semantics.notFound === false)
|
|
650
|
+
return setArtifactLink( head, null );
|
|
651
|
+
// TODO: streamline function arguments (probably: user, path, semantics )
|
|
652
|
+
const undef = semantics.notFound( ruser, head, valid, dynamicDict,
|
|
653
|
+
!isMainRef && user._user && user._artifact, path );
|
|
654
|
+
return setArtifactLink( head, undef || null );
|
|
751
655
|
}
|
|
752
656
|
|
|
753
657
|
// Return artifact or element referred by path (array of ids) `tail`. The
|
|
@@ -757,84 +661,105 @@ function fns( model ) {
|
|
|
757
661
|
// TODO - think about setting _navigation for all $navElement – the
|
|
758
662
|
// "ref: ['tabAlias']: inline: […]" handling might be easier
|
|
759
663
|
// (no _pathHead consultation for key prop and renaming support)
|
|
760
|
-
function getPathItem(
|
|
664
|
+
function getPathItem( ref, semantics, spec, user ) {
|
|
761
665
|
// let art = (headArt && headArt.kind === '$tableAlias') ? headArt._origin : headArt;
|
|
762
|
-
|
|
666
|
+
const { path } = ref;
|
|
667
|
+
let artItemsCount = 0;
|
|
668
|
+
const { isMainRef } = semantics;
|
|
669
|
+
if (isMainRef) {
|
|
670
|
+
artItemsCount = (typeof ref.scope === 'number' && ref.scope) ||
|
|
671
|
+
(ref.scope ? 1 : path.length);
|
|
672
|
+
}
|
|
673
|
+
let art = null;
|
|
763
674
|
let nav = spec.assoc !== '$keys' && null; // false for '$keys'
|
|
764
675
|
const last = path[path.length - 1];
|
|
676
|
+
// TODO: change elementsEnv via semantics for static versus dynamic assoc navigation
|
|
677
|
+
const elementsEnv = semantics.navigation || environment;
|
|
765
678
|
for (const item of path) {
|
|
766
679
|
--artItemsCount;
|
|
767
|
-
if (!item
|
|
680
|
+
if (!item?.id) // incomplete AST due to parse error
|
|
768
681
|
return undefined;
|
|
769
|
-
if (item._artifact) { // should be there on first path element
|
|
682
|
+
if (item._artifact) { // should be there on first path element
|
|
770
683
|
art = item._artifact;
|
|
771
|
-
if (Array.isArray(art))
|
|
772
|
-
return false;
|
|
773
|
-
if (art.$requireElementAccess && path.length === 1)
|
|
774
|
-
// Path with only one item, but we expect an element, e.g. `$at.from`.
|
|
775
|
-
signalMissingElementAccess(art, [ item.location, user ]);
|
|
776
684
|
continue;
|
|
777
685
|
}
|
|
778
686
|
|
|
779
|
-
const
|
|
780
|
-
const
|
|
781
|
-
const
|
|
687
|
+
const prev = art;
|
|
688
|
+
const envFn = (artItemsCount >= 0) ? artifactsEnv : elementsEnv;
|
|
689
|
+
const env = envFn( art, item.location, user, spec.assoc );
|
|
690
|
+
art = setArtifactLink( item, env?.[item.id] );
|
|
782
691
|
|
|
783
|
-
if (!
|
|
692
|
+
if (!art) {
|
|
784
693
|
// element was not found in environment
|
|
785
694
|
|
|
786
695
|
// TODO (done?): if `env` was 0, we might set a dependency to induce an
|
|
787
696
|
// illegal-cycle error instead of reporting via `errorNotFound`.
|
|
788
|
-
if (
|
|
697
|
+
if (prev.$uncheckedElements) { // magic variable / replacement variable
|
|
789
698
|
signalNotFound( 'ref-unknown-var', [ item.location, user ], [ env ],
|
|
790
699
|
{ id: pathName( path ) } );
|
|
791
700
|
}
|
|
701
|
+
else if (isMainRef && artItemsCount >= 0) { // artifact ref
|
|
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
|
+
}
|
|
792
707
|
else {
|
|
793
|
-
errorNotFound( item, env );
|
|
708
|
+
errorNotFound( item, env, prev, spec, user );
|
|
794
709
|
}
|
|
795
710
|
return null;
|
|
796
711
|
}
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
if (
|
|
802
|
-
|
|
803
|
-
// not (pseudo-) follow associations, otherwise potential redirection
|
|
804
|
-
// there had to be considered, too. Also, fk refs to sub elements in
|
|
805
|
-
// combinations with redirections of the target which directly access
|
|
806
|
-
// the potentially renamed sub elements would be really complex.
|
|
807
|
-
// With our restriction, no renaming must be considered for item.id.
|
|
808
|
-
setTargetReferenceKey( item.id, item );
|
|
809
|
-
}
|
|
810
|
-
// Now set an _navigation link for managed assocs in ON condition etc
|
|
811
|
-
else if (art && art.target && nav != null) {
|
|
812
|
-
// Find the original ref for sub and the original foreign key
|
|
813
|
-
// definition. This way, we do not need the foreign keys with
|
|
814
|
-
// rewritten target element path, which might not be available at
|
|
815
|
-
// this point (rewriteKeys in Resolver Phase 5). If we want to
|
|
816
|
-
// follow associations in foreign key definitions, rewriteKeys must
|
|
817
|
-
// be moved to the on-demand Resolver Phase 2.
|
|
818
|
-
let orig; // for the original target element
|
|
819
|
-
for (let o = sub; o; o = o.value && o.value._artifact) // TODO: or use _origin?
|
|
820
|
-
orig = o;
|
|
821
|
-
nav = (orig._effectiveType || orig).$keysNavigation;
|
|
822
|
-
setTargetReferenceKey( orig.name.id, item );
|
|
823
|
-
}
|
|
824
|
-
art = sub;
|
|
825
|
-
if (spec.envFn && !spec.allowAutoexposed && (!artItemsCount || item === last) &&
|
|
826
|
-
art && art.$inferred === 'autoexposed' && !user.$inferred) {
|
|
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
|
+
// need to do that here, because we also need to disallow Service.AutoExposed:elem
|
|
716
|
+
if (isMainRef !== 'all' && artItemsCount === 0 &&
|
|
717
|
+
art.$inferred === 'autoexposed' && !user.$inferred) {
|
|
827
718
|
// Depending on the processing sequence, the following could be a
|
|
828
719
|
// simple 'ref-undefined-art'/'ref-undefined-def' - TODO: which we
|
|
829
720
|
// could "change" to this message at the end of compile():
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
721
|
+
error( 'ref-unexpected-autoexposed', [ item.location, user ], { art },
|
|
722
|
+
// eslint-disable-next-line max-len
|
|
723
|
+
'An auto-exposed entity can\'t be referred to - expose entity $(ART) explicitly' );
|
|
724
|
+
// return null;
|
|
833
725
|
}
|
|
834
726
|
}
|
|
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
|
+
}
|
|
835
732
|
return art;
|
|
733
|
+
}
|
|
836
734
|
|
|
837
|
-
|
|
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 ) {
|
|
838
763
|
const node = nav && nav[id];
|
|
839
764
|
nav = null;
|
|
840
765
|
if (node) {
|
|
@@ -854,40 +779,373 @@ function fns( model ) {
|
|
|
854
779
|
// eslint-disable-next-line max-len
|
|
855
780
|
'You can\'t follow associations other than to elements referred to in a managed association\'s key' );
|
|
856
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
|
+
// Helper functions for resolve[Unchecked]Path, getPath{Root,Item}: -----------
|
|
811
|
+
|
|
812
|
+
function acceptLexical( art, path, semantics, user ) {
|
|
813
|
+
if (semantics.isMainRef || !art)
|
|
814
|
+
return !!art;
|
|
815
|
+
// Non-global lexical are table aliases, mixins and $self, $projection, $parameters,
|
|
816
|
+
// Do not accept a lonely table alias and `$projection`
|
|
817
|
+
// TODO: test table alias and mixin named `$projection`
|
|
818
|
+
if (path.length === 1 && !user.expand && !user.inline) { // accept lonely…
|
|
819
|
+
// allow mixins, and `up_` in anonymous target aspect:
|
|
820
|
+
if (art.kind !== '$self' || path[0].id !== '$self')
|
|
821
|
+
return art.kind === 'mixin' || art.kind === '$navElement';
|
|
822
|
+
return true;
|
|
823
|
+
}
|
|
824
|
+
// return !art.$internal && art;
|
|
825
|
+
return art.$inferred !== '$internal';
|
|
826
|
+
}
|
|
857
827
|
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
828
|
+
function acceptPathRoot( art, ref, semantics, user ) {
|
|
829
|
+
const { path } = ref;
|
|
830
|
+
const [ head ] = path;
|
|
831
|
+
if (Array.isArray( art ))
|
|
832
|
+
return getAmbiguousRefLink( art, head, user );
|
|
833
|
+
switch (art.kind) {
|
|
834
|
+
case 'using': {
|
|
835
|
+
const def = model.definitions[art.name.absolute];
|
|
836
|
+
if (!def)
|
|
837
|
+
return def;
|
|
838
|
+
if (def.$duplicates)
|
|
839
|
+
return false;
|
|
840
|
+
return setArtifactLink( head, def ); // we do not want to see the using
|
|
864
841
|
}
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
// and 'The sub query for alias $(ALIAS) has no element $(MEMBER)'
|
|
868
|
-
// TODO: probably not extra messageId, but text variant
|
|
869
|
-
// TODO: views elements are proxies to query-0 elements, not the same
|
|
870
|
-
// TODO: better message text
|
|
871
|
-
signalNotFound( 'query-undefined-element', [ item.location, user ],
|
|
872
|
-
[ env ], { id: item.id } );
|
|
842
|
+
case 'mixin': {
|
|
843
|
+
return setLink( head, '_navigation', art );
|
|
873
844
|
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
845
|
+
case '$navElement': {
|
|
846
|
+
if (head.id === user.$extended)
|
|
847
|
+
path.$prefix = user.$extended;
|
|
848
|
+
setLink( head, '_navigation', art );
|
|
849
|
+
return setArtifactLink( head, art._origin );
|
|
877
850
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
851
|
+
case '$self': // TODO: remove $projection from CC
|
|
852
|
+
case '$tableAlias': {
|
|
853
|
+
setLink( head, '_navigation', art );
|
|
854
|
+
setArtifactLink( head, art._origin ); // query source or leading query in FROM
|
|
855
|
+
if (!art._origin)
|
|
856
|
+
return art._origin;
|
|
857
|
+
// if just table alias (with expand), mark `user` with `$noOrigin` to indicate
|
|
858
|
+
// that the corresponding entity should not be put as $origin into the CSN.
|
|
859
|
+
// TODO: remove again, should be easy enough in to-csn without.
|
|
860
|
+
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
|
+
}
|
|
868
|
+
return art;
|
|
869
|
+
}
|
|
870
|
+
case '$parameters': { // TODO: remove from CC
|
|
871
|
+
// TODO: if ref.scope='param' is handled, test that here, too ?
|
|
872
|
+
const { id } = path[1];
|
|
873
|
+
message( 'ref-obsolete-parameters', [ head.location, user ],
|
|
874
|
+
{ code: `$parameters.${ id }`, newcode: `:${ id }` },
|
|
875
|
+
'Obsolete $(CODE) - replace by $(NEWCODE)' );
|
|
876
|
+
// TODO: replace it in to-csn correspondingly, probably v5 or later in v4 ?
|
|
877
|
+
return art;
|
|
886
878
|
}
|
|
887
|
-
|
|
879
|
+
default:
|
|
880
|
+
return art;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function getAmbiguousRefLink( arr, head, user ) {
|
|
885
|
+
if (arr[0].kind !== '$navElement' || arr.some( e => e._parent.$duplicates ))
|
|
886
|
+
return false;
|
|
887
|
+
// only complain about ambiguous source elements if we do not have
|
|
888
|
+
// duplicate table aliases, only mention non-ambiguous source elems
|
|
889
|
+
const uniqueNames = arr.filter( e => !e.$duplicates);
|
|
890
|
+
if (uniqueNames.length) {
|
|
891
|
+
const names = uniqueNames.filter( e => e._parent.$inferred !== '$internal' )
|
|
892
|
+
.map( e => `${ e.name.alias }.${ e.name.element }` );
|
|
893
|
+
let variant = names.length === uniqueNames.length ? 'std' : 'few';
|
|
894
|
+
if (names.length === 0)
|
|
895
|
+
variant = 'none';
|
|
896
|
+
error( 'ref-ambiguous', [ head.location, user ], { '#': variant, id: head.id, names });
|
|
888
897
|
}
|
|
898
|
+
return false;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Functions for the secondary reference semantics ----------------------------
|
|
902
|
+
|
|
903
|
+
function typeOfSemantics( user, [ head ] ) {
|
|
904
|
+
// `type of` is only allowed for (sub) elements of main artifacts
|
|
905
|
+
let struct = user;
|
|
906
|
+
while (struct.kind === 'element')
|
|
907
|
+
struct = struct._parent;
|
|
908
|
+
if (struct === user._main && struct.kind !== 'annotation')
|
|
909
|
+
return { dynamic: typeOfParentDict };
|
|
910
|
+
error( 'type-unexpected-typeof', [ head.location, user ],
|
|
911
|
+
{ keyword: 'type of', '#': struct.kind } );
|
|
912
|
+
return false;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function paramSemantics() {
|
|
916
|
+
return { dynamic: artifactParams, notFound: undefinedParam };
|
|
917
|
+
}
|
|
918
|
+
function paramUnsupported( user, _path, location ) {
|
|
919
|
+
error( 'ref-unexpected-scope', [ location, user ],
|
|
920
|
+
// why an extra text for calculated elements? or separate for all?
|
|
921
|
+
{ '#': (user.$syntax === 'calc' ? 'calc' : 'std') } );
|
|
922
|
+
return false;
|
|
889
923
|
}
|
|
890
924
|
|
|
925
|
+
// Functions for semantics.lexical: -------------------------------------------
|
|
926
|
+
|
|
927
|
+
function userBlock( user ) {
|
|
928
|
+
return definedViaCdl( user ) && user._block;
|
|
929
|
+
}
|
|
930
|
+
function justDollarSelf( user ) {
|
|
931
|
+
const query = userQuery( user );
|
|
932
|
+
if (!query)
|
|
933
|
+
return user._main || user;
|
|
934
|
+
// query.$tableAliases contains both aliases and $self/$projection
|
|
935
|
+
const aliases = query.$tableAliases;
|
|
936
|
+
const r = Object.create(null);
|
|
937
|
+
if (aliases.$self.kind === '$self')
|
|
938
|
+
r.$self = aliases.$self;
|
|
939
|
+
// TODO: disallow $projection for ON conditions all together
|
|
940
|
+
if (aliases.$projection?.kind === '$self')
|
|
941
|
+
r.$projection = aliases.$projection;
|
|
942
|
+
const { $parameters } = user._main.$tableAliases;
|
|
943
|
+
if ($parameters) // no need to test `kind`, just compiler-set “aliases”
|
|
944
|
+
r.$parameters = $parameters;
|
|
945
|
+
return { $tableAliases: r };
|
|
946
|
+
}
|
|
947
|
+
function tableAliasesAndSelf( user ) {
|
|
948
|
+
return userQuery( user ) || user._main || user;
|
|
949
|
+
}
|
|
950
|
+
function tableAliasesIfNotExtendAndSelf( user ) {
|
|
951
|
+
if (!user.$extended)
|
|
952
|
+
return tableAliasesAndSelf( user );
|
|
953
|
+
if (typeof user.$extended !== 'string') {
|
|
954
|
+
const aliases = userQuery( user ).$tableAliases;
|
|
955
|
+
user.$extended = Object.keys( aliases )[0];
|
|
956
|
+
}
|
|
957
|
+
return justDollarSelf( user );
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// Functions called via semantics.dynamic: ------------------------------------
|
|
961
|
+
|
|
962
|
+
function modelDefinitions() {
|
|
963
|
+
return model.definitions;
|
|
964
|
+
}
|
|
965
|
+
function modelBuiltinsOrDefinitions( user ) {
|
|
966
|
+
return definedViaCdl( user ) ? model.$builtins : model.definitions;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
function artifactParams( user ) {
|
|
970
|
+
const lexical = (user._main || user).$tableAliases;
|
|
971
|
+
// TODO: already report error here if no parameters?
|
|
972
|
+
return lexical?.$parameters?.elements || Object.create( null );
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
function typeOfParentDict( user ) {
|
|
976
|
+
// CDL produces the following XSN representation for `type of elem`:
|
|
977
|
+
// { path: [{ id: 'type of'}, { id: 'elem'}], scope: 'typeOf' }
|
|
978
|
+
return { 'type of': user._parent };
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
function targetElements( user, pathItemArtifact ) {
|
|
982
|
+
// const assoc = user._parent;
|
|
983
|
+
// const target = resolvePath( assoc.target, 'target', assoc );
|
|
984
|
+
return environment( pathItemArtifact || user._parent, null, null, null, true );
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
function combinedSourcesOrParentElements( user ) {
|
|
988
|
+
const query = userQuery( user );
|
|
989
|
+
if (!query)
|
|
990
|
+
return environment( user._main ? user._parent : user );
|
|
991
|
+
return query._combined; // TODO: do we need query._parent._combined ?
|
|
992
|
+
}
|
|
993
|
+
function parentElements( user ) {
|
|
994
|
+
return environment( user._main && user.kind !== 'select' ? user._parent : user );
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
function queryElements( user ) {
|
|
998
|
+
return environment( user );
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
function nestedElements( user ) {
|
|
1002
|
+
Functions.navigationEnv( user._pathHead ); // set _origin
|
|
1003
|
+
return environment( user._pathHead._origin );
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
function targetAspectOnly( prev ) {
|
|
1007
|
+
let env = Functions.navigationEnv( prev, null, null, 'targetAspectOnly' );
|
|
1008
|
+
while (env?.target && !env.targetAspect)
|
|
1009
|
+
env = env._origin || env.type?._artifact;
|
|
1010
|
+
if (env === 0)
|
|
1011
|
+
return 0;
|
|
1012
|
+
const target = env?.targetAspect;
|
|
1013
|
+
if (target) {
|
|
1014
|
+
if (target.elements)
|
|
1015
|
+
return target.elements;
|
|
1016
|
+
env = resolvePath( env.targetAspect, 'targetAspect', env );
|
|
1017
|
+
}
|
|
1018
|
+
return env?.elements || Object.create(null);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
function artifactsEnv( art ) {
|
|
1022
|
+
return art._subArtifacts || Object.create(null);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
// Return effective search environment provided by artifact `art`, i.e. the
|
|
1026
|
+
// `artifacts` or `elements` dictionary. For the latter, follow the `type`
|
|
1027
|
+
// chain and resolve the association `target`. View elements are calculated
|
|
1028
|
+
// on demand.
|
|
1029
|
+
// TODO: what about location/user when called from getPath ?
|
|
1030
|
+
// TODO: think of always acting as if falsyIfNone would be true
|
|
1031
|
+
// (if not possible, move to second param position)
|
|
1032
|
+
function environment( art, location, user, assocSpec, falsyIfNone ) {
|
|
1033
|
+
const env = Functions.navigationEnv( art, location, user, assocSpec );
|
|
1034
|
+
if (env === 0)
|
|
1035
|
+
return 0;
|
|
1036
|
+
return env?.elements || !falsyIfNone && Object.create(null);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// Functions called via semantics.notFound: -----------------------------------
|
|
1040
|
+
|
|
1041
|
+
function undefinedDefinition( user, item, valid, _dict, prev ) {
|
|
1042
|
+
// in a CSN source or for `using`, only one env was tested (valid.length 1) :
|
|
1043
|
+
const art = (!prev) ? item.id : searchName( prev, item.id, 'absolute' );
|
|
1044
|
+
signalNotFound( (valid.length > 1 ? 'ref-undefined-art' : 'ref-undefined-def'),
|
|
1045
|
+
[ item.location, user ], valid, { art } );
|
|
1046
|
+
// TODO: improve text, use text variant for: "or builtin" or "definitions" or none
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
function undefinedForAnnotate( user, item, valid, _dict, prev ) {
|
|
1050
|
+
// in a CSN source, only one env was tested (valid.length 1):
|
|
1051
|
+
const art = (!prev) ? item.id : searchName( prev, item.id, 'absolute' );
|
|
1052
|
+
signalNotFound( (valid.length > 1 ? 'anno-undefined-art' : 'anno-undefined-def'),
|
|
1053
|
+
// TODO: ext-undefined-xyz
|
|
1054
|
+
[ item.location, user ], valid, { art } );
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
function undefinedParam( user, head, valid ) {
|
|
1058
|
+
// TODO: text variant if there are no parameters, or in artifactParameters()?
|
|
1059
|
+
signalNotFound( 'ref-undefined-param', [ head.location, user ], valid,
|
|
1060
|
+
{ art: user._main || user, id: head.id } );
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
function undefinedTargetElement( user, head, valid, _dict, pathItemArtifact ) {
|
|
1064
|
+
// is only called if there is a target, targetElements() returns falsy otherwise
|
|
1065
|
+
const { target } = pathItemArtifact?._effectiveType || user._parent;
|
|
1066
|
+
// TODO: better with $refs in filter conditions
|
|
1067
|
+
signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
|
|
1068
|
+
{ '#': 'target', art: target, id: head.id } );
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
function undefinedVariable( user, head, valid ) {
|
|
1072
|
+
// TODO: avoid message if we have already complained about `(exists …)`?
|
|
1073
|
+
const { id } = head;
|
|
1074
|
+
const isVar = id.charAt( 0 ) === '$' && id !== '$self';
|
|
1075
|
+
signalNotFound( (isVar ? 'ref-undefined-var' : 'ref-expecting-const'),
|
|
1076
|
+
[ head.location, user ],
|
|
1077
|
+
valid, { '#': 'std', id } );
|
|
1078
|
+
// TODO: use s/th better than 'ref-expecting-const'?
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
function undefinedSourceElement( user, head, valid, dynamicDict ) {
|
|
1082
|
+
// TODO: we might mention both the "direct" and the "effective" type and
|
|
1083
|
+
// always just mentioned one identifier as not found
|
|
1084
|
+
const { id } = head;
|
|
1085
|
+
if (id.charAt( 0 ) === '$') {
|
|
1086
|
+
const tableAlias = dynamicDict[id]?._parent;
|
|
1087
|
+
// TODO: probably better to pass param `semantics` and calculate dynamic dict explicitly
|
|
1088
|
+
const alias = tableAlias?.kind === '$tableAlias' ? tableAlias.name?.alias : null;
|
|
1089
|
+
// TODO: mention $self without query
|
|
1090
|
+
signalNotFound( 'ref-undefined-var', [ head.location, user ], valid,
|
|
1091
|
+
{ '#': (alias ? 'alias' : 'std'), alias, id } );
|
|
1092
|
+
}
|
|
1093
|
+
else {
|
|
1094
|
+
const isVirtual = (user.name?.id === id && user.virtual?.val);
|
|
1095
|
+
const code = 'virtual null as ‹name›';
|
|
1096
|
+
signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
|
|
1097
|
+
{ art: head.id, '#': isVirtual ? 'virtual' : 'std', code } );
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
function undefinedParentElement( user, head, valid, dynamicDict ) {
|
|
1102
|
+
// TODO: we might mention both the "direct" and the "effective" type and
|
|
1103
|
+
// always just mentioned one identifier as not found
|
|
1104
|
+
const { id } = head;
|
|
1105
|
+
if (id.charAt( 0 ) === '$') {
|
|
1106
|
+
const queryOrMain = dynamicDict[id]?._parent;
|
|
1107
|
+
const withSelf = queryOrMain && (!queryOrMain._main || queryOrMain?.kind === 'select');
|
|
1108
|
+
signalNotFound( 'ref-undefined-var', [ head.location, user ], valid,
|
|
1109
|
+
{ '#': (withSelf ? 'self' : 'std'), alias: '$self', id } );
|
|
1110
|
+
}
|
|
1111
|
+
else {
|
|
1112
|
+
// TODO: extra msg like ref-rejected-on if elem found in source elements?
|
|
1113
|
+
// also whether users wronly tried to refer to aliases/mixins?
|
|
1114
|
+
const msgVar = userQuery( user ) ? 'query' : null;
|
|
1115
|
+
// TODO: better with ON in expand if that is supported
|
|
1116
|
+
signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
|
|
1117
|
+
{ '#': msgVar, art: head.id } );
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
function undefinedOrderByElement( user, head, valid, dynamicDict, _art, path ) {
|
|
1122
|
+
const { id } = head;
|
|
1123
|
+
const src = id.charAt( 0 ) !== '$' && user._combined?.[id];
|
|
1124
|
+
if (src && !Array.isArray(src)) {
|
|
1125
|
+
path.$prefix = src.name.alias; // pushing it to path directly could be problematic
|
|
1126
|
+
// configurable error:
|
|
1127
|
+
signalNotFound( 'ref-deprecated-orderby', [ head.location, user ], valid,
|
|
1128
|
+
{ id: head.id, newcode: `${ src.name.alias }.${ head.id }` } );
|
|
1129
|
+
return src;
|
|
1130
|
+
}
|
|
1131
|
+
undefinedParentElement( user, head, valid, dynamicDict );
|
|
1132
|
+
return null;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
function undefinedNestedElement( user, head, valid ) {
|
|
1136
|
+
// environment( user._pathHead ); // set _origin
|
|
1137
|
+
const art = user._pathHead._origin;
|
|
1138
|
+
// if (!art) console.log('UNE:',user,user._pathHead)
|
|
1139
|
+
if (!art)
|
|
1140
|
+
return; // no consequential error
|
|
1141
|
+
// TODO: better message with $ref
|
|
1142
|
+
signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
|
|
1143
|
+
{ art: searchName( art, head.id, 'element' ) } );
|
|
1144
|
+
// TODO: remove use of searchName() ?
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
// Low-level functions --------------------------------------------------------
|
|
1148
|
+
|
|
891
1149
|
/**
|
|
892
1150
|
* Make a "not found" error and optionally attach valid names.
|
|
893
1151
|
*
|
|
@@ -903,8 +1161,10 @@ function fns( model ) {
|
|
|
903
1161
|
/** @type {object} */
|
|
904
1162
|
const err = message( msgId, location, textParams );
|
|
905
1163
|
if (valid) {
|
|
1164
|
+
const user = Array.isArray( location ) && location[1];
|
|
1165
|
+
err.validNames = (user && definedViaCdl( user )); // viaCdl -> '.'?
|
|
906
1166
|
valid.reverse();
|
|
907
|
-
attachAndEmitValidNames(err, ...valid);
|
|
1167
|
+
attachAndEmitValidNames( err, ...valid );
|
|
908
1168
|
}
|
|
909
1169
|
}
|
|
910
1170
|
|
|
@@ -924,7 +1184,7 @@ function fns( model ) {
|
|
|
924
1184
|
prev[`${ art.name.id }.${ curr }`] = true;
|
|
925
1185
|
return prev;
|
|
926
1186
|
}, Object.create(null));
|
|
927
|
-
attachAndEmitValidNames(err, valid);
|
|
1187
|
+
attachAndEmitValidNames( err, valid );
|
|
928
1188
|
}
|
|
929
1189
|
|
|
930
1190
|
/**
|
|
@@ -935,29 +1195,46 @@ function fns( model ) {
|
|
|
935
1195
|
* @param {...object} validDicts One ore more artifact dictionaries such as in `_block`.
|
|
936
1196
|
*/
|
|
937
1197
|
function attachAndEmitValidNames( msg, ...validDicts ) {
|
|
1198
|
+
const viaCdl = msg.validNames; // TODO: move to argument list
|
|
938
1199
|
if (!options.testMode && !options.attachValidNames)
|
|
939
1200
|
return;
|
|
940
1201
|
|
|
941
1202
|
const valid = Object.assign( Object.create( null ), ...validDicts );
|
|
942
1203
|
msg.validNames = Object.create( null );
|
|
943
1204
|
for (const name of Object.keys( valid )) {
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1205
|
+
const art = valid[name];
|
|
1206
|
+
// ignore internal types such as cds.Association, ignore names with dot for
|
|
1207
|
+
// CDL references to main artifacts:
|
|
1208
|
+
if (!art.internal && !art.deprecated && art.$inferred !== '$internal' &&
|
|
1209
|
+
(viaCdl ? art._main || !name.includes('.') : art.kind !== 'namespace'))
|
|
1210
|
+
msg.validNames[name] = art;
|
|
948
1211
|
}
|
|
949
1212
|
|
|
950
1213
|
if (options.testMode && !options.$recompile) {
|
|
951
1214
|
// no semantic location => either first of [loc, semantic loc] pair or just location.
|
|
952
|
-
const loc = msg
|
|
1215
|
+
const loc = msg.$location[0] || msg.$location;
|
|
953
1216
|
const names = Object.keys(msg.validNames);
|
|
954
|
-
|
|
1217
|
+
names.sort();
|
|
1218
|
+
if (names.length > 22) {
|
|
1219
|
+
names.length = 20;
|
|
1220
|
+
names[20] = '…';
|
|
1221
|
+
}
|
|
1222
|
+
info( null, [ loc, null ],
|
|
955
1223
|
{ '#': !names.length ? 'zero' : 'std' },
|
|
956
|
-
{ std: `Valid: ${ names.
|
|
1224
|
+
{ std: `Valid: ${ names.join(', ') }`, zero: 'No valid names' });
|
|
957
1225
|
}
|
|
958
1226
|
}
|
|
959
1227
|
}
|
|
960
1228
|
|
|
1229
|
+
function removeDollarNames( dict ) {
|
|
1230
|
+
const r = Object.create( null );
|
|
1231
|
+
for (const name in dict) {
|
|
1232
|
+
if (name.charAt( 0 ) !== '$')
|
|
1233
|
+
r[name] = dict[name];
|
|
1234
|
+
}
|
|
1235
|
+
return r;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
961
1238
|
module.exports = {
|
|
962
1239
|
fns,
|
|
963
1240
|
};
|