@sap/cds-compiler 6.4.2 → 6.5.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 +87 -1159
- package/README.md +1 -10
- package/doc/IncompatibleChanges_v5.md +436 -0
- package/doc/IncompatibleChanges_v6.md +659 -0
- package/doc/Versioning.md +3 -7
- package/lib/api/main.js +1 -0
- package/lib/api/options.js +5 -0
- package/lib/api/validate.js +3 -0
- package/lib/base/message-registry.js +25 -2
- package/lib/base/messages.js +1 -1
- package/lib/base/model.js +3 -2
- package/lib/checks/actionsFunctions.js +6 -3
- package/lib/checks/existsExpressionsOnlyForeignKeys.js +16 -10
- package/lib/checks/existsInForbiddenPlaces.js +32 -0
- package/lib/checks/existsMustEndInAssoc.js +1 -1
- package/lib/checks/existsMustNotStartWithDollarSelf.js +31 -0
- package/lib/checks/validator.js +6 -2
- package/lib/compiler/assert-consistency.js +5 -7
- package/lib/compiler/builtins.js +5 -6
- package/lib/compiler/checks.js +4 -8
- package/lib/compiler/define.js +244 -459
- package/lib/compiler/extend.js +297 -11
- package/lib/compiler/finalize-parse-cdl.js +2 -10
- package/lib/compiler/generate.js +29 -1
- package/lib/compiler/populate.js +21 -63
- package/lib/compiler/propagator.js +1 -2
- package/lib/compiler/resolve.js +2 -12
- package/lib/compiler/shared.js +145 -114
- package/lib/compiler/tweak-assocs.js +14 -10
- package/lib/compiler/utils.js +97 -0
- package/lib/compiler/xpr-rewrite.js +113 -140
- package/lib/edm/annotations/edmJson.js +9 -6
- package/lib/edm/annotations/genericTranslation.js +8 -4
- package/lib/edm/csn2edm.js +3 -4
- package/lib/edm/edmInboundChecks.js +1 -2
- package/lib/edm/edmPreprocessor.js +3 -3
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +4 -3
- package/lib/gen/Dictionary.json +16 -1
- package/lib/json/from-csn.js +4 -6
- package/lib/json/to-csn.js +3 -3
- package/lib/model/csnRefs.js +13 -4
- package/lib/model/enrichCsn.js +4 -2
- package/lib/optionProcessor.js +8 -4
- package/lib/parsers/AstBuildingParser.js +1 -1
- package/lib/render/utils/sql.js +3 -2
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/assertUnique.js +3 -3
- package/lib/transform/db/assocsToQueries/normalizeFrom.js +33 -0
- package/lib/transform/db/assocsToQueries/transformExists.js +17 -13
- package/lib/transform/db/assocsToQueries/utils.js +1 -6
- package/lib/transform/db/backlinks.js +4 -4
- package/lib/transform/db/cdsPersistence.js +4 -4
- package/lib/transform/db/constraints.js +4 -4
- package/lib/transform/db/expansion.js +5 -5
- package/lib/transform/db/flattening.js +4 -5
- package/lib/transform/db/rewriteCalculatedElements.js +3 -3
- package/lib/transform/db/temporal.js +11 -11
- package/lib/transform/draft/db.js +2 -0
- package/lib/transform/draft/odata.js +5 -7
- package/lib/transform/effective/flattening.js +1 -2
- package/lib/transform/forOdata.js +3 -3
- package/lib/transform/forRelationalDB.js +1 -1
- package/lib/transform/localized.js +13 -20
- package/lib/transform/odata/createForeignKeys.js +1 -2
- package/lib/transform/odata/flattening.js +1 -2
- package/lib/transform/odata/toFinalBaseType.js +52 -55
- package/lib/transform/transformUtils.js +3 -4
- package/package.json +3 -3
- package/doc/CHANGELOG_BETA.md +0 -464
- package/doc/CHANGELOG_DEPRECATED.md +0 -235
package/lib/compiler/resolve.js
CHANGED
|
@@ -75,7 +75,6 @@ const $location = Symbol.for( 'cds.$location' );
|
|
|
75
75
|
const $inferred = Symbol.for( 'cds.$inferred' );
|
|
76
76
|
|
|
77
77
|
// TODO: make this part of specExpected in shared.js
|
|
78
|
-
// (standard: not possible on last if !ref.$expected → ref.scope: '$exists')
|
|
79
78
|
const expWithFilter = [ 'from', 'expand', 'inline' ];
|
|
80
79
|
|
|
81
80
|
// Export function of this file. Resolve type references in augmented CSN
|
|
@@ -1557,16 +1556,6 @@ function resolve( model ) {
|
|
|
1557
1556
|
}
|
|
1558
1557
|
|
|
1559
1558
|
function resolveExprPath( expr, expected, user ) {
|
|
1560
|
-
// TODO: re-think this $expected: 'exists' thing
|
|
1561
|
-
if (expr.$expected === 'exists') {
|
|
1562
|
-
if (expected !== 'annotation') { // `exists e[…]` allowed in annotation expressions
|
|
1563
|
-
error( 'expr-unexpected-exists', [ expr.location, user ], {},
|
|
1564
|
-
'An EXISTS predicate is not expected here' );
|
|
1565
|
-
}
|
|
1566
|
-
// We complain about the EXISTS before, as EXISTS subquery is also not supported
|
|
1567
|
-
// TODO: location of EXISTS, TODO: really do this in define.js
|
|
1568
|
-
expr.$expected = 'approved-exists'; // only complain once
|
|
1569
|
-
}
|
|
1570
1559
|
const ref = resolvePath( expr, expected, user );
|
|
1571
1560
|
|
|
1572
1561
|
if (expected === 'annotation') {
|
|
@@ -1588,9 +1577,10 @@ function resolve( model ) {
|
|
|
1588
1577
|
last._navigation?.kind === '$tableAlias') // error already reported
|
|
1589
1578
|
return ref;
|
|
1590
1579
|
|
|
1591
|
-
if (expr.$
|
|
1580
|
+
if (expr.$syntax === 'after-exists') {
|
|
1592
1581
|
if (last.where?.args?.length === 0) {
|
|
1593
1582
|
// at the moment, empty filter is not allowed on last path step
|
|
1583
|
+
// TODO: allow it at some places
|
|
1594
1584
|
reportUnexpectedArgsAndFilter( last, expected, user, null, 'last-empty-filter' );
|
|
1595
1585
|
}
|
|
1596
1586
|
return ref;
|
package/lib/compiler/shared.js
CHANGED
|
@@ -49,6 +49,13 @@ function fns( model ) {
|
|
|
49
49
|
dynamic: modelDefinitions,
|
|
50
50
|
notFound: undefinedDefinition,
|
|
51
51
|
},
|
|
52
|
+
// scope:'global': for cds.Association and auto-redirected targets
|
|
53
|
+
$global: {
|
|
54
|
+
isMainRef: 'all',
|
|
55
|
+
lexical: null,
|
|
56
|
+
dynamic: modelDefinitions,
|
|
57
|
+
notFound: undefinedDefinition,
|
|
58
|
+
},
|
|
52
59
|
// only used for the main annotate/extend statements, not inner ones:
|
|
53
60
|
annotate: {
|
|
54
61
|
isMainRef: 'all',
|
|
@@ -57,6 +64,17 @@ function fns( model ) {
|
|
|
57
64
|
notFound: undefinedForAnnotate,
|
|
58
65
|
accept: extendableArtifact,
|
|
59
66
|
},
|
|
67
|
+
'annotate-sec': {
|
|
68
|
+
isMainRef: 'all',
|
|
69
|
+
lexical: userBlock,
|
|
70
|
+
dynamic: modelDefinitions,
|
|
71
|
+
notFound: undefinedDefinition,
|
|
72
|
+
messageMap: {
|
|
73
|
+
'ref-undefined-art': 'ext-undefined-art-sec',
|
|
74
|
+
'ref-undefined-def': 'ext-undefined-def-sec',
|
|
75
|
+
},
|
|
76
|
+
accept: extendableArtifact,
|
|
77
|
+
},
|
|
60
78
|
extend: {
|
|
61
79
|
isMainRef: 'no-generated',
|
|
62
80
|
lexical: userBlock,
|
|
@@ -85,7 +103,7 @@ function fns( model ) {
|
|
|
85
103
|
accept: acceptEntity,
|
|
86
104
|
noDep: true,
|
|
87
105
|
// special `scope`s for auto-redirections:
|
|
88
|
-
global: () =>
|
|
106
|
+
global: () => '$global',
|
|
89
107
|
},
|
|
90
108
|
targetAspect: {
|
|
91
109
|
isMainRef: 'no-autoexposed',
|
|
@@ -102,6 +120,7 @@ function fns( model ) {
|
|
|
102
120
|
notFound: undefinedDefinition,
|
|
103
121
|
accept: acceptQuerySource,
|
|
104
122
|
noDep: '', // dependency special for from
|
|
123
|
+
args: () => 'from-args',
|
|
105
124
|
},
|
|
106
125
|
type: {
|
|
107
126
|
isMainRef: 'no-autoexposed',
|
|
@@ -112,11 +131,11 @@ function fns( model ) {
|
|
|
112
131
|
accept: acceptTypeOrElement,
|
|
113
132
|
// special `scope`s for CDL parser - TYPE OF (TODO generated?), cds.Association:
|
|
114
133
|
typeOf: typeOfSemantics,
|
|
115
|
-
global: () =>
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
134
|
+
global: () => '$global', // TODO: do we need `navigation: staticTarget`?
|
|
135
|
+
},
|
|
136
|
+
$typeOf: {
|
|
137
|
+
dynamic: typeOfParentDict,
|
|
138
|
+
navigation: staticTarget,
|
|
120
139
|
},
|
|
121
140
|
// element references without lexical scope (except $self/$projection): -----
|
|
122
141
|
targetElement: {
|
|
@@ -125,22 +144,24 @@ function fns( model ) {
|
|
|
125
144
|
dynamic: targetElements,
|
|
126
145
|
navigation: targetNavigation,
|
|
127
146
|
notFound: undefinedTargetElement,
|
|
128
|
-
param:
|
|
147
|
+
param: () => '$scopePar',
|
|
129
148
|
},
|
|
130
149
|
filter: {
|
|
131
150
|
lexical: justDollarAliases,
|
|
132
151
|
dollar: true,
|
|
133
152
|
dynamic: targetElements,
|
|
134
153
|
notFound: undefinedTargetElement,
|
|
135
|
-
param:
|
|
154
|
+
param: () => '$scopePar',
|
|
155
|
+
nestedColumn: () => 'filter',
|
|
136
156
|
},
|
|
137
|
-
'calc-filter': {
|
|
157
|
+
'calc-filter': { // TODO: what is so special about this?
|
|
138
158
|
lexical: justDollarAliases,
|
|
139
159
|
dollar: true,
|
|
140
160
|
dynamic: targetElements,
|
|
141
161
|
navigation: calcElemNavigation,
|
|
142
162
|
notFound: undefinedTargetElement,
|
|
143
163
|
param: paramUnsupported,
|
|
164
|
+
filter: () => 'calc-filter',
|
|
144
165
|
},
|
|
145
166
|
default: {
|
|
146
167
|
lexical: null,
|
|
@@ -154,7 +175,7 @@ function fns( model ) {
|
|
|
154
175
|
dollar: true,
|
|
155
176
|
dynamic: () => Object.create( null ),
|
|
156
177
|
notFound: undefinedVariable,
|
|
157
|
-
param:
|
|
178
|
+
param: () => '$scopePar',
|
|
158
179
|
},
|
|
159
180
|
'limit-offset': 'limit-rows',
|
|
160
181
|
// general element / variable references --------------------------------------
|
|
@@ -164,7 +185,7 @@ function fns( model ) {
|
|
|
164
185
|
dynamic: combinedSourcesOrParentElements,
|
|
165
186
|
notFound: undefinedSourceElement,
|
|
166
187
|
check: checkRefInQuery,
|
|
167
|
-
param:
|
|
188
|
+
param: () => '$scopePar',
|
|
168
189
|
},
|
|
169
190
|
having: 'where',
|
|
170
191
|
groupBy: 'where',
|
|
@@ -174,22 +195,15 @@ function fns( model ) {
|
|
|
174
195
|
dynamic: combinedSourcesOrParentElements,
|
|
175
196
|
notFound: undefinedSourceElement,
|
|
176
197
|
check: checkColumnRef,
|
|
177
|
-
param:
|
|
178
|
-
nestedColumn: () =>
|
|
179
|
-
lexical: justDollarAliases,
|
|
180
|
-
dollar: true,
|
|
181
|
-
dynamic: nestedElements,
|
|
182
|
-
notFound: undefinedNestedElement,
|
|
183
|
-
check: checkColumnRef,
|
|
184
|
-
param: paramSemantics,
|
|
185
|
-
}),
|
|
198
|
+
param: () => '$scopePar',
|
|
199
|
+
nestedColumn: () => '$srcRefInNestedColumn',
|
|
186
200
|
},
|
|
187
201
|
'from-args': {
|
|
188
202
|
lexical: null,
|
|
189
203
|
dollar: true,
|
|
190
204
|
dynamic: () => Object.create( null ),
|
|
191
205
|
notFound: undefinedVariable,
|
|
192
|
-
param:
|
|
206
|
+
param: () => '$scopePar',
|
|
193
207
|
},
|
|
194
208
|
calc: {
|
|
195
209
|
lexical: justDollarAliases,
|
|
@@ -198,6 +212,7 @@ function fns( model ) {
|
|
|
198
212
|
navigation: calcElemNavigation,
|
|
199
213
|
notFound: undefinedParentElement,
|
|
200
214
|
param: paramUnsupported,
|
|
215
|
+
filter: () => 'calc-filter',
|
|
201
216
|
},
|
|
202
217
|
'join-on': {
|
|
203
218
|
lexical: tableAliasesAndSelf,
|
|
@@ -205,7 +220,7 @@ function fns( model ) {
|
|
|
205
220
|
dynamic: combinedSourcesOrParentElements,
|
|
206
221
|
rejectRoot: rejectOwnExceptVisibleAliases,
|
|
207
222
|
notFound: undefinedSourceElement,
|
|
208
|
-
param:
|
|
223
|
+
param: () => '$scopePar',
|
|
209
224
|
},
|
|
210
225
|
on: { // unmanaged assoc: outside query, redirected or new assoc in column
|
|
211
226
|
lexical: justDollarAliases,
|
|
@@ -216,15 +231,8 @@ function fns( model ) {
|
|
|
216
231
|
accept: acceptElemOrVarOrSelf,
|
|
217
232
|
check: checkAssocOn,
|
|
218
233
|
param: paramUnsupported,
|
|
219
|
-
nestedColumn: () => ({ // in expand and inline
|
|
220
|
-
lexical: justDollarAliases,
|
|
221
|
-
dollar: true,
|
|
222
|
-
dynamic: parentElements,
|
|
223
|
-
navigation: assocOnNavigation,
|
|
224
|
-
notFound: undefinedParentElement,
|
|
225
|
-
rewriteProjectionToSelf: true,
|
|
226
|
-
}),
|
|
227
234
|
rewriteProjectionToSelf: true,
|
|
235
|
+
nestedColumn: () => '$projRefInNestedColumn',
|
|
228
236
|
},
|
|
229
237
|
'mixin-on': {
|
|
230
238
|
lexical: tableAliasesAndSelf,
|
|
@@ -234,15 +242,16 @@ function fns( model ) {
|
|
|
234
242
|
notFound: undefinedSourceElement,
|
|
235
243
|
accept: acceptElemOrVarOrSelf,
|
|
236
244
|
check: checkAssocOn,
|
|
237
|
-
param:
|
|
245
|
+
param: () => '$scopePar', // TODO: check that assocs containing param in ON is not published
|
|
238
246
|
},
|
|
247
|
+
'rewrite-on': {}, // only for traversal when rewriting on condition
|
|
239
248
|
'orderBy-ref': {
|
|
240
249
|
lexical: tableAliasesAndSelf,
|
|
241
250
|
dollar: true,
|
|
242
251
|
dynamic: parentElements,
|
|
243
252
|
notFound: undefinedOrderByElement,
|
|
244
253
|
check: checkOrderByRef,
|
|
245
|
-
param:
|
|
254
|
+
param: () => '$scopePar',
|
|
246
255
|
},
|
|
247
256
|
'orderBy-expr': {
|
|
248
257
|
lexical: tableAliasesAndSelf,
|
|
@@ -250,7 +259,7 @@ function fns( model ) {
|
|
|
250
259
|
dynamic: combinedSourcesOrParentElements,
|
|
251
260
|
notFound: undefinedSourceElement,
|
|
252
261
|
check: checkRefInQuery,
|
|
253
|
-
param:
|
|
262
|
+
param: () => '$scopePar',
|
|
254
263
|
},
|
|
255
264
|
'orderBy-set-ref': {
|
|
256
265
|
lexical: tableAliasesAndSelf, // TODO: reject own tab aliases
|
|
@@ -259,7 +268,7 @@ function fns( model ) {
|
|
|
259
268
|
rejectRoot: rejectOwnAliasesAndMixins,
|
|
260
269
|
notFound: undefinedParentElement,
|
|
261
270
|
check: checkOrderByRef,
|
|
262
|
-
param:
|
|
271
|
+
param: () => '$scopePar',
|
|
263
272
|
},
|
|
264
273
|
'orderBy-set-expr': {
|
|
265
274
|
lexical: tableAliasesAndSelf, // TODO: reject own tab aliases
|
|
@@ -268,7 +277,7 @@ function fns( model ) {
|
|
|
268
277
|
rejectRoot: rejectAllOwn,
|
|
269
278
|
notFound: undefinedVariable,
|
|
270
279
|
check: checkRefInQuery,
|
|
271
|
-
param:
|
|
280
|
+
param: () => '$scopePar',
|
|
272
281
|
},
|
|
273
282
|
annotation: { // annotation assignments
|
|
274
283
|
lexical: justDollarAliases,
|
|
@@ -277,19 +286,14 @@ function fns( model ) {
|
|
|
277
286
|
navigation: assocOnNavigation,
|
|
278
287
|
noDep: true,
|
|
279
288
|
notFound: undefinedParentElement,
|
|
289
|
+
accept: acceptElemOrAnyVar,
|
|
290
|
+
variableFilter: (dict => dict),
|
|
280
291
|
messageMap: {
|
|
281
292
|
'ref-undefined-element': 'anno-undefined-element',
|
|
282
293
|
'ref-undefined-param': 'anno-undefined-param',
|
|
283
294
|
},
|
|
284
|
-
param:
|
|
285
|
-
nestedColumn: () =>
|
|
286
|
-
lexical: justDollarAliases,
|
|
287
|
-
dollar: true,
|
|
288
|
-
dynamic: parentElements,
|
|
289
|
-
navigation: assocOnNavigation,
|
|
290
|
-
notFound: undefinedParentElement,
|
|
291
|
-
rewriteProjectionToSelf: true,
|
|
292
|
-
}),
|
|
295
|
+
param: () => '$annotationScopePar',
|
|
296
|
+
nestedColumn: () => '$projRefInNestedColumn',
|
|
293
297
|
},
|
|
294
298
|
// TODO: introduce some kind of inheritance
|
|
295
299
|
// used by xpr-rewrite.js to resolve rewritten path roots.
|
|
@@ -300,17 +304,36 @@ function fns( model ) {
|
|
|
300
304
|
navigation: assocOnNavigation,
|
|
301
305
|
noDep: true,
|
|
302
306
|
notFound: null, // no error, just falsy links
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
307
|
+
accept: acceptElemOrAnyVar,
|
|
308
|
+
param: () => '$scopePar',
|
|
309
|
+
nestedColumn: () => '$projRefInNestedColumn',
|
|
310
|
+
},
|
|
311
|
+
$scopePar: {
|
|
312
|
+
dynamic: artifactParams,
|
|
313
|
+
notFound: undefinedParam,
|
|
314
|
+
},
|
|
315
|
+
$annotationScopePar: {
|
|
316
|
+
messageMap: {
|
|
317
|
+
'ref-undefined-element': 'anno-undefined-element',
|
|
318
|
+
'ref-undefined-param': 'anno-undefined-param',
|
|
319
|
+
},
|
|
320
|
+
dynamic: artifactParams,
|
|
321
|
+
notFound: undefinedParam,
|
|
322
|
+
},
|
|
323
|
+
// for `nestedColumn`, these two will be merged with base semantics:
|
|
324
|
+
$projRefInNestedColumn: { // for assoc-`on` and annotations
|
|
325
|
+
lexical: justDollarAliases,
|
|
326
|
+
dynamic: parentElements,
|
|
327
|
+
navigation: assocOnNavigation, // like std `environment`, but no dependency
|
|
328
|
+
rewriteProjectionToSelf: true,
|
|
329
|
+
},
|
|
330
|
+
$srcRefInNestedColumn: { // for column refs
|
|
331
|
+
lexical: justDollarAliases,
|
|
332
|
+
dollar: true,
|
|
333
|
+
dynamic: nestedElements,
|
|
334
|
+
navigation: environment,
|
|
335
|
+
notFound: undefinedNestedElement,
|
|
312
336
|
},
|
|
313
|
-
//
|
|
314
337
|
};
|
|
315
338
|
|
|
316
339
|
Object.assign( model.$functions, {
|
|
@@ -371,7 +394,7 @@ function fns( model ) {
|
|
|
371
394
|
}
|
|
372
395
|
if (expr.args) {
|
|
373
396
|
const args = Array.isArray( expr.args ) ? expr.args : Object.values( expr.args );
|
|
374
|
-
for (const arg of args
|
|
397
|
+
for (const arg of args) {
|
|
375
398
|
if (traverseExpr( arg, exprCtx, user, callback ) === traverseExpr.STOP)
|
|
376
399
|
return traverseExpr.STOP;
|
|
377
400
|
}
|
|
@@ -389,16 +412,13 @@ function fns( model ) {
|
|
|
389
412
|
const exit = callback( step, exprCtx, user );
|
|
390
413
|
if (exit === traverseExpr.STOP)
|
|
391
414
|
return true;
|
|
392
|
-
if (step.where && exit !== traverseExpr.SKIP
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
: 'filter' ),
|
|
398
|
-
step, callback ) === traverseExpr.STOP)
|
|
399
|
-
return true;
|
|
415
|
+
if (step.where && exit !== traverseExpr.SKIP) {
|
|
416
|
+
const ctx = referenceSemantics[exprCtx].filter?.() || 'filter';
|
|
417
|
+
if (traverseExpr( step.where, ctx, step, callback ) === traverseExpr.STOP)
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
400
420
|
if (step.args) {
|
|
401
|
-
const ctx = (
|
|
421
|
+
const ctx = referenceSemantics[exprCtx].args?.() || exprCtx;
|
|
402
422
|
const args = Array.isArray( step.args ) ? step.args : Object.values( step.args );
|
|
403
423
|
// TODO: there should be no array `args` on path item
|
|
404
424
|
for (const arg of args) {
|
|
@@ -412,7 +432,7 @@ function fns( model ) {
|
|
|
412
432
|
// Special expression traversal function for `resolveExpr`. Let's see
|
|
413
433
|
// later whether we can use this version as the general one.
|
|
414
434
|
// If we continue to have separate ones, remove the STOP stuff – it is not
|
|
415
|
-
// needed for `resolveExpr
|
|
435
|
+
// needed for `resolveExpr`; SKIP is used, though.
|
|
416
436
|
|
|
417
437
|
function traverseTypedExpr( expr, exprCtx, user, type, callback ) {
|
|
418
438
|
if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
|
|
@@ -565,16 +585,13 @@ function fns( model ) {
|
|
|
565
585
|
const exit = callback( step, exprCtx, user, null );
|
|
566
586
|
if (exit === traverseExpr.STOP)
|
|
567
587
|
return true;
|
|
568
|
-
if (step.where && exit !== traverseExpr.SKIP
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
: 'filter' ),
|
|
574
|
-
step, null, callback ) === traverseExpr.STOP)
|
|
575
|
-
return true;
|
|
588
|
+
if (step.where && exit !== traverseExpr.SKIP) {
|
|
589
|
+
const ctx = referenceSemantics[exprCtx].filter?.() || 'filter';
|
|
590
|
+
if (traverseTypedExpr( step.where, ctx, step, null, callback ) === traverseExpr.STOP)
|
|
591
|
+
return true;
|
|
592
|
+
}
|
|
576
593
|
if (step.args) {
|
|
577
|
-
const ctx = (
|
|
594
|
+
const ctx = referenceSemantics[exprCtx].args?.() || exprCtx;
|
|
578
595
|
const args = Array.isArray( step.args ) ? step.args : Object.values( step.args );
|
|
579
596
|
// TODO: there should be no array `args` on path item
|
|
580
597
|
for (const arg of args) {
|
|
@@ -637,7 +654,6 @@ function fns( model ) {
|
|
|
637
654
|
|
|
638
655
|
const s = referenceSemantics[expected];
|
|
639
656
|
const semantics = (typeof s === 'string') ? referenceSemantics[s] : s;
|
|
640
|
-
semantics.name = expected;
|
|
641
657
|
|
|
642
658
|
const r = getPathRoot( ref, semantics, origUser );
|
|
643
659
|
const root = r && acceptPathRoot( r, ref, semantics, origUser );
|
|
@@ -778,19 +794,26 @@ function fns( model ) {
|
|
|
778
794
|
ruser = ruser._outer._outer;
|
|
779
795
|
}
|
|
780
796
|
|
|
781
|
-
// Handle expand/inline
|
|
797
|
+
// Handle expand/inline before `type of`, :param, global (internally for CDL):
|
|
782
798
|
if (user._columnParent && !semantics.isMainRef) { // in expand/inline
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
|
|
799
|
+
const func = semantics.nestedColumn;
|
|
800
|
+
if (!func)
|
|
801
|
+
throw new CompilerAssertion( 'Unexpected ref context in nested column' );
|
|
802
|
+
const ctx = func();
|
|
803
|
+
semantics = (typeof ctx === 'string')
|
|
804
|
+
? ({ ...semantics, ...referenceSemantics[ctx] })
|
|
805
|
+
: ctx;
|
|
786
806
|
}
|
|
787
807
|
if (typeof scope === 'string') { // typeOf, param, global
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
808
|
+
const func = semantics[scope] || scope === 'param' && paramUnsupported;
|
|
809
|
+
// 'param' is a user scope → useful default (error msg ref-unexpected-param)
|
|
810
|
+
// 'global' and 'typeOf' are internal scopes of the compiler → dump if not provided
|
|
811
|
+
if (!func)
|
|
812
|
+
throw new CompilerAssertion( `Unexpected scope ${ scope }, no handler defined in context` );
|
|
813
|
+
const ctx = func( ruser, path, location, semantics );
|
|
814
|
+
semantics = (typeof ctx === 'string') ? referenceSemantics[ctx] : ctx;
|
|
815
|
+
if (!semantics)
|
|
792
816
|
return setArtifactLink( head, null );
|
|
793
|
-
}
|
|
794
817
|
}
|
|
795
818
|
const valid = [];
|
|
796
819
|
|
|
@@ -825,7 +848,8 @@ function fns( model ) {
|
|
|
825
848
|
valid.push( dynamicDict );
|
|
826
849
|
}
|
|
827
850
|
else {
|
|
828
|
-
|
|
851
|
+
const filterFn = semantics.variableFilter || removeRestrictedVariables;
|
|
852
|
+
valid.push( filterFn( model.$magicVariables.elements ),
|
|
829
853
|
removeDollarNames( dynamicDict ) );
|
|
830
854
|
}
|
|
831
855
|
// TODO: streamline function arguments (probably: user, path, semantics )
|
|
@@ -1020,11 +1044,17 @@ function fns( model ) {
|
|
|
1020
1044
|
return art;
|
|
1021
1045
|
}
|
|
1022
1046
|
case 'builtin': {
|
|
1047
|
+
// TODO: use properties in builtins
|
|
1023
1048
|
if (art.name.id === '$at') {
|
|
1024
1049
|
message( 'ref-deprecated-variable', [ head.location, user ],
|
|
1025
1050
|
{ code: '$at', newcode: '$valid' },
|
|
1026
1051
|
'$(CODE) is deprecated; use $(NEWCODE) instead' );
|
|
1027
1052
|
}
|
|
1053
|
+
else if (art.$restricted && semantics.accept !== acceptElemOrAnyVar) {
|
|
1054
|
+
error( 'ref-unexpected-var', [ head.location, user ],
|
|
1055
|
+
{ '#': 'annotation', name: head.id } );
|
|
1056
|
+
return null; // no further error on `unknown` for $draft.unknown
|
|
1057
|
+
}
|
|
1028
1058
|
return art;
|
|
1029
1059
|
}
|
|
1030
1060
|
default:
|
|
@@ -1059,20 +1089,12 @@ function fns( model ) {
|
|
|
1059
1089
|
while (struct.kind === 'element')
|
|
1060
1090
|
struct = struct._parent;
|
|
1061
1091
|
if (struct === user._main && struct.kind !== 'annotation')
|
|
1062
|
-
return
|
|
1092
|
+
return '$typeOf';
|
|
1063
1093
|
error( 'type-unexpected-typeof', [ head.location, user ],
|
|
1064
1094
|
{ keyword: 'type of', '#': struct.kind } );
|
|
1065
1095
|
return false;
|
|
1066
1096
|
}
|
|
1067
1097
|
|
|
1068
|
-
function paramSemantics( _user, _path, _loction, semantics ) {
|
|
1069
|
-
return {
|
|
1070
|
-
messageMap: semantics.messageMap,
|
|
1071
|
-
dynamic: artifactParams,
|
|
1072
|
-
notFound: undefinedParam,
|
|
1073
|
-
};
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
1098
|
function paramUnsupported( user, _path, location ) {
|
|
1077
1099
|
error( 'ref-unexpected-scope', [ location, user ], // TODO: ref-unexpected-param
|
|
1078
1100
|
// why an extra text for calculated elements? or separate for all?
|
|
@@ -1273,11 +1295,11 @@ function fns( model ) {
|
|
|
1273
1295
|
|
|
1274
1296
|
// Functions called via semantics.notFound: -----------------------------------
|
|
1275
1297
|
|
|
1276
|
-
function undefinedDefinition( user, item, valid, _dict, prev ) {
|
|
1298
|
+
function undefinedDefinition( user, item, valid, _dict, prev, _path, semantics ) {
|
|
1277
1299
|
// in a CSN source or for `using`, only one env was tested (valid.length 1) :
|
|
1278
1300
|
const art = (!prev) ? item.id : searchName( prev, item.id, 'absolute' );
|
|
1279
1301
|
signalNotFound( (valid.length > 1 ? 'ref-undefined-art' : 'ref-undefined-def'),
|
|
1280
|
-
[ item.location, user ], valid, { art } );
|
|
1302
|
+
[ item.location, user ], valid, { art }, semantics );
|
|
1281
1303
|
// TODO: improve text, use text variant for: "or builtin" or "definitions" or none
|
|
1282
1304
|
}
|
|
1283
1305
|
|
|
@@ -1430,8 +1452,7 @@ function fns( model ) {
|
|
|
1430
1452
|
while ((head = head?._parent) && head.kind === 'builtin')
|
|
1431
1453
|
id = `${ head.name.id }.${ id }`;
|
|
1432
1454
|
const msgId = (art.$uncheckedElements) ? 'ref-unknown-var' : 'ref-undefined-var';
|
|
1433
|
-
signalNotFound( msgId, [ item.location, user ],
|
|
1434
|
-
removeInvalidMagicVariables( valid, semantics ), { id }, semantics );
|
|
1455
|
+
signalNotFound( msgId, [ item.location, user ], valid, { id }, semantics );
|
|
1435
1456
|
}
|
|
1436
1457
|
else if (art.kind === 'aspect' && !art.name) { // anonymous target aspect - TODO: still?
|
|
1437
1458
|
signalNotFound( 'ref-undefined-element', [ item.location, user ], valid,
|
|
@@ -1513,16 +1534,17 @@ function fns( model ) {
|
|
|
1513
1534
|
: acceptElemOrVar( art, user, ref );
|
|
1514
1535
|
}
|
|
1515
1536
|
|
|
1516
|
-
function acceptElemOrVar( art, user, ref
|
|
1537
|
+
function acceptElemOrVar( art, user, ref ) {
|
|
1538
|
+
if (art.kind !== 'builtin' || !art.$restricted)
|
|
1539
|
+
return acceptElemOrAnyVar( art, user, ref );
|
|
1540
|
+
error( 'ref-unexpected-var', [ ref.location, user ],
|
|
1541
|
+
{ '#': 'annotation', name: pathName( ref.path ) } );
|
|
1542
|
+
return null;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
function acceptElemOrAnyVar( art, user, ref ) {
|
|
1517
1546
|
const { path } = ref;
|
|
1518
1547
|
if (art.kind === 'builtin') {
|
|
1519
|
-
if (art.$onlyInExprCtx && !art.$onlyInExprCtx.includes(semantics.name)) {
|
|
1520
|
-
error( 'ref-unexpected-var', [ ref.location, user ], {
|
|
1521
|
-
'#': art.$onlyInExprCtx[0], name: pathName( path ),
|
|
1522
|
-
});
|
|
1523
|
-
return null;
|
|
1524
|
-
}
|
|
1525
|
-
|
|
1526
1548
|
if (user.expand || user.inline) {
|
|
1527
1549
|
const location = (user.expand || user.inline)[$location];
|
|
1528
1550
|
const code = (user.expand) ? '{ ‹expand› }' : '.{ ‹inline› }';
|
|
@@ -1866,8 +1888,9 @@ function fns( model ) {
|
|
|
1866
1888
|
}
|
|
1867
1889
|
const index = userTargetElementPathIndex( user, path );
|
|
1868
1890
|
checkOnlyForeignKeyNavigation( user, path, index );
|
|
1891
|
+
// TODO: did we check for no filter/args, no `exists` etc?
|
|
1869
1892
|
const last = path[path.length - 1];
|
|
1870
|
-
if (!last.where && ref._artifact?.on) { // filter already complained about
|
|
1893
|
+
if (!last.where && ref._artifact?.on) { // filter already complained about - TODO: where?
|
|
1871
1894
|
const target = index > 0 && index < path.length && ref._artifact?.target;
|
|
1872
1895
|
const msg = (target?._artifact === user._main) ? 'self' : 'unmanaged';
|
|
1873
1896
|
error( 'ref-unexpected-assoc', [ last.location, user ],
|
|
@@ -2006,7 +2029,7 @@ function fns( model ) {
|
|
|
2006
2029
|
}
|
|
2007
2030
|
|
|
2008
2031
|
function checkNoUnmanaged( ref, user, self, messageVariant = 'unmanaged' ) {
|
|
2009
|
-
if (ref._artifact?.on &&
|
|
2032
|
+
if (ref._artifact?.on && ref.$syntax !== 'after-exists') {
|
|
2010
2033
|
const { path } = ref;
|
|
2011
2034
|
const last = path[path.length - 1];
|
|
2012
2035
|
if (self && last.where) // already complained about filter
|
|
@@ -2035,7 +2058,8 @@ function fns( model ) {
|
|
|
2035
2058
|
const err = message( semantics?.messageMap?.[msgId] || msgId, location, textParams );
|
|
2036
2059
|
if (valid) {
|
|
2037
2060
|
const user = Array.isArray( location ) && location[1];
|
|
2038
|
-
|
|
2061
|
+
// TODO: see TODO missing param `viaCdl` in attachAndEmitValidNames:
|
|
2062
|
+
err.validNames = (user && definedViaCdl( user ));
|
|
2039
2063
|
valid.reverse();
|
|
2040
2064
|
attachAndEmitValidNames( err, ...valid );
|
|
2041
2065
|
}
|
|
@@ -2076,6 +2100,8 @@ function fns( model ) {
|
|
|
2076
2100
|
const valid = Object.assign( Object.create( null ), ...validDicts );
|
|
2077
2101
|
msg.validNames = Object.create( null );
|
|
2078
2102
|
for (const name of Object.keys( valid )) {
|
|
2103
|
+
if (!name)
|
|
2104
|
+
continue;
|
|
2079
2105
|
const art = valid[name];
|
|
2080
2106
|
// ignore internal types such as cds.Association, ignore names with dot for
|
|
2081
2107
|
// CDL references to main artifacts:
|
|
@@ -2100,14 +2126,19 @@ function fns( model ) {
|
|
|
2100
2126
|
}
|
|
2101
2127
|
}
|
|
2102
2128
|
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2129
|
+
/**
|
|
2130
|
+
* Filter out restricted variables from a dictionary of variables.
|
|
2131
|
+
* Variables with the `$restricted` property set to true are excluded.
|
|
2132
|
+
*
|
|
2133
|
+
* @param {object} variables - Dictionary of variable objects
|
|
2134
|
+
* @returns {object} New dictionary with restricted variables removed
|
|
2135
|
+
*/
|
|
2136
|
+
function removeRestrictedVariables( variables ) {
|
|
2137
|
+
// TODO: we could also do something with `deprecated`, $requiresBetaFlag, …
|
|
2107
2138
|
const valid = Object.create(null);
|
|
2108
2139
|
for (const name in variables) {
|
|
2109
2140
|
const variable = variables[name];
|
|
2110
|
-
if (!variable.$
|
|
2141
|
+
if (!variable.$restricted)
|
|
2111
2142
|
valid[name] = variable;
|
|
2112
2143
|
}
|
|
2113
2144
|
return valid;
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
const {
|
|
6
6
|
forEachGeneric,
|
|
7
7
|
forEachInOrder,
|
|
8
|
-
isBetaEnabled,
|
|
9
8
|
} = require('../base/model');
|
|
10
9
|
const { dictLocation, weakLocation, weakRefLocation } = require('../base/location');
|
|
11
10
|
|
|
@@ -138,10 +137,10 @@ function tweakAssocs( model ) {
|
|
|
138
137
|
|
|
139
138
|
function handleQueryElements( column ) {
|
|
140
139
|
rewriteAssociationCheck( column );
|
|
141
|
-
if (
|
|
142
|
-
return;
|
|
140
|
+
if (model.options.noDollarCalc || column._columnParent)
|
|
141
|
+
return; // no $calc supported with inline
|
|
143
142
|
const { value } = column; // `value` = column expression
|
|
144
|
-
if (!value ||
|
|
143
|
+
if (!value || value.path) // not with references
|
|
145
144
|
return;
|
|
146
145
|
// TODO: what about non-simple refs (assocs, even with filter/args)?
|
|
147
146
|
|
|
@@ -457,7 +456,7 @@ function tweakAssocs( model ) {
|
|
|
457
456
|
return;
|
|
458
457
|
|
|
459
458
|
if (elem._parent?.kind === 'element') {
|
|
460
|
-
//
|
|
459
|
+
// unmanaged association as sub element not supported yet
|
|
461
460
|
// TODO: Only report once for multi-include chains, see
|
|
462
461
|
// Associations/SubElements/UnmanagedInSubElement.err.cds
|
|
463
462
|
error( 'type-unsupported-rewrite', [ elem.location, elem ], { '#': 'sub-element' } );
|
|
@@ -946,6 +945,8 @@ function tweakAssocs( model ) {
|
|
|
946
945
|
* for item.
|
|
947
946
|
*/
|
|
948
947
|
function rewriteColumnPath( ref, column ) {
|
|
948
|
+
if (ref.query)
|
|
949
|
+
return traverseExpr.STOP; // sub queries rewrite not supported
|
|
949
950
|
if (!ref._artifact)
|
|
950
951
|
return null;
|
|
951
952
|
const root = ref.path?.[0];
|
|
@@ -1010,9 +1011,12 @@ function tweakAssocs( model ) {
|
|
|
1010
1011
|
state = setArtifactLink( i, elem );
|
|
1011
1012
|
}
|
|
1012
1013
|
else {
|
|
1013
|
-
state = rewriteItem( state, i, assoc );
|
|
1014
|
-
if (
|
|
1015
|
-
|
|
1014
|
+
state = rewriteItem( state, i, assoc, !location );
|
|
1015
|
+
if (state && state !== true)
|
|
1016
|
+
continue;
|
|
1017
|
+
if (!state && !location)
|
|
1018
|
+
return true;
|
|
1019
|
+
break;
|
|
1016
1020
|
}
|
|
1017
1021
|
}
|
|
1018
1022
|
if (state !== true)
|
|
@@ -1032,7 +1036,7 @@ function tweakAssocs( model ) {
|
|
|
1032
1036
|
* @param item Path segment to rewrite.
|
|
1033
1037
|
* @param assoc Published association of query.
|
|
1034
1038
|
*/
|
|
1035
|
-
function rewriteItem( elem, item, assoc ) {
|
|
1039
|
+
function rewriteItem( elem, item, assoc, noError ) {
|
|
1036
1040
|
if (!elem._redirected)
|
|
1037
1041
|
return true;
|
|
1038
1042
|
let name = item.id;
|
|
@@ -1055,7 +1059,7 @@ function tweakAssocs( model ) {
|
|
|
1055
1059
|
if (env?.target)
|
|
1056
1060
|
env = env.target._artifact?._effectiveType;
|
|
1057
1061
|
const found = setArtifactLink( item, env?.elements?.[name] );
|
|
1058
|
-
if (found)
|
|
1062
|
+
if (found || noError)
|
|
1059
1063
|
return found;
|
|
1060
1064
|
|
|
1061
1065
|
const isExplicit = elem.target && !elem.target.$inferred;
|