@sap/cds-compiler 4.9.6 → 5.1.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 -0
- package/bin/cds_remove_invalid_whitespace.js +2 -1
- package/bin/cdsc.js +49 -19
- package/bin/cdshi.js +3 -1
- package/doc/CHANGELOG_BETA.md +7 -0
- package/lib/api/main.js +16 -19
- package/lib/api/options.js +5 -14
- package/lib/api/trace.js +0 -1
- package/lib/base/builtins.js +1 -0
- package/lib/base/location.js +4 -1
- package/lib/base/message-registry.js +43 -29
- package/lib/base/messages.js +23 -26
- package/lib/base/meta.js +10 -0
- package/lib/base/model.js +0 -2
- package/lib/base/node-helpers.js +0 -1
- package/lib/base/optionProcessorHelper.js +11 -0
- package/lib/checks/dbFeatureFlags.js +5 -0
- package/lib/checks/enricher.js +1 -5
- package/lib/checks/structuredAnnoExpressions.js +30 -0
- package/lib/checks/validator.js +8 -0
- package/lib/compiler/assert-consistency.js +5 -1
- package/lib/compiler/base.js +1 -1
- package/lib/compiler/builtins.js +18 -2
- package/lib/compiler/checks.js +2 -5
- package/lib/compiler/define.js +8 -8
- package/lib/compiler/extend.js +108 -37
- package/lib/compiler/generate.js +1 -1
- package/lib/compiler/index.js +27 -10
- package/lib/compiler/lsp-api.js +501 -2
- package/lib/compiler/populate.js +60 -13
- package/lib/compiler/propagator.js +10 -8
- package/lib/compiler/resolve.js +117 -94
- package/lib/compiler/shared.js +114 -32
- package/lib/compiler/tweak-assocs.js +31 -21
- package/lib/compiler/utils.js +2 -1
- package/lib/compiler/xsn-model.js +4 -0
- package/lib/edm/annotations/genericTranslation.js +69 -35
- package/lib/edm/csn2edm.js +16 -4
- package/lib/edm/edm.js +10 -3
- package/lib/edm/edmAnnoPreprocessor.js +1 -2
- package/lib/edm/edmPreprocessor.js +8 -10
- package/lib/gen/Dictionary.json +66 -2
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +2 -1
- package/lib/gen/languageParser.js +4995 -4817
- package/lib/json/csnVersion.js +1 -1
- package/lib/json/from-csn.js +4 -7
- package/lib/json/to-csn.js +25 -12
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/errorStrategy.js +0 -1
- package/lib/language/genericAntlrParser.js +35 -12
- package/lib/language/multiLineStringParser.js +3 -2
- package/lib/language/textUtils.js +1 -0
- package/lib/main.d.ts +28 -9
- package/lib/main.js +9 -9
- package/lib/model/cloneCsn.js +1 -0
- package/lib/model/csnRefs.js +22 -5
- package/lib/model/csnUtils.js +0 -14
- package/lib/model/revealInternalProperties.js +1 -2
- package/lib/modelCompare/compare.js +13 -11
- package/lib/optionProcessor.js +30 -9
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +44 -14
- package/lib/render/toHdbcds.js +1 -2
- package/lib/render/toSql.js +45 -8
- package/lib/render/utils/common.js +12 -9
- package/lib/render/utils/stringEscapes.js +1 -0
- package/lib/transform/db/applyTransformations.js +13 -8
- package/lib/transform/db/associations.js +62 -54
- package/lib/transform/db/backlinks.js +20 -5
- package/lib/transform/db/expansion.js +1 -6
- package/lib/transform/db/flattening.js +86 -109
- package/lib/transform/db/killAnnotations.js +3 -0
- package/lib/transform/db/processSqlServices.js +63 -0
- package/lib/transform/db/temporal.js +3 -4
- package/lib/transform/db/views.js +0 -1
- package/lib/transform/draft/odata.js +56 -3
- package/lib/transform/effective/annotations.js +3 -2
- package/lib/transform/effective/flattening.js +135 -0
- package/lib/transform/effective/main.js +6 -4
- package/lib/transform/effective/types.js +13 -9
- package/lib/transform/forOdata.js +0 -2
- package/lib/transform/forRelationalDB.js +9 -19
- package/lib/transform/localized.js +7 -8
- package/lib/transform/odata/flattening.js +39 -31
- package/lib/transform/odata/typesExposure.js +5 -17
- package/lib/transform/transformUtils.js +1 -1
- package/lib/transform/translateAssocsToJoins.js +0 -1
- package/lib/utils/file.js +87 -8
- package/lib/utils/moduleResolve.js +59 -8
- package/lib/utils/term.js +3 -2
- package/package.json +7 -3
- package/share/messages/message-explanations.json +2 -0
- package/share/messages/type-unexpected-foreign-keys.md +52 -0
- package/share/messages/type-unexpected-on-condition.md +52 -0
package/lib/compiler/resolve.js
CHANGED
|
@@ -67,6 +67,7 @@ const {
|
|
|
67
67
|
} = require('./utils');
|
|
68
68
|
|
|
69
69
|
const detectCycles = require('./cycle-detector');
|
|
70
|
+
const { CompilerAssertion } = require('../base/error');
|
|
70
71
|
|
|
71
72
|
const $location = Symbol.for( 'cds.$location' );
|
|
72
73
|
|
|
@@ -88,8 +89,8 @@ function resolve( model ) {
|
|
|
88
89
|
} = model.$messageFunctions;
|
|
89
90
|
const {
|
|
90
91
|
resolvePath,
|
|
92
|
+
resolveDefinitionName,
|
|
91
93
|
traverseExpr,
|
|
92
|
-
createRemainingAnnotateStatements,
|
|
93
94
|
effectiveType,
|
|
94
95
|
getOrigin,
|
|
95
96
|
getInheritedProp,
|
|
@@ -130,8 +131,6 @@ function resolve( model ) {
|
|
|
130
131
|
resolveDefinitionName( model.sources[name].namespace );
|
|
131
132
|
}
|
|
132
133
|
|
|
133
|
-
// create “super” ANNOTATE statements for annotations on unknown artifacts:
|
|
134
|
-
createRemainingAnnotateStatements();
|
|
135
134
|
// report cyclic dependencies:
|
|
136
135
|
detectCycles( model.definitions, ( user, art, location, semanticLoc ) => {
|
|
137
136
|
if (location) {
|
|
@@ -157,68 +156,102 @@ function resolve( model ) {
|
|
|
157
156
|
// Phase 2+3: calculate propagated KEYs
|
|
158
157
|
//--------------------------------------------------------------------------
|
|
159
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Set `_projection` links in navigation elements and creates $navElement hierarchy.
|
|
161
|
+
*
|
|
162
|
+
* @param {XSN.Artifact} view
|
|
163
|
+
*/
|
|
160
164
|
function setNavigationProjections( view ) {
|
|
161
165
|
if (!view.$queries)
|
|
162
166
|
return;
|
|
163
167
|
for (const query of view.$queries) {
|
|
164
|
-
|
|
165
|
-
|
|
168
|
+
// traversing sub-elements not necessary, since we're in a view
|
|
169
|
+
// TODO: Handle expand.
|
|
170
|
+
forEachGeneric( query, 'elements', function navProjectionsForElement( elem ) {
|
|
171
|
+
if (!elem._origin || elem.expand || !elem.value?.path)
|
|
166
172
|
return;
|
|
167
173
|
// TODO: what about elements where _origin is set without value?
|
|
168
174
|
// TODO: or should we push elems with `expand` sibling to extra list for
|
|
169
175
|
// better messages? (Whatever that means exactly.)
|
|
170
|
-
const nav = pathNavigation( elem.value );
|
|
171
|
-
const { path } = elem.value;
|
|
172
|
-
|
|
173
|
-
if (elem._pathHead?.kind === '$inline' && path.length === 1) {
|
|
174
|
-
const item = path[0];
|
|
175
|
-
const hpath = elem._pathHead.value?.path;
|
|
176
|
-
const head = hpath?.length === 1 && hpath[0]._navigation;
|
|
177
|
-
// Alias .{ elem } - but not with Alias.{ $magic }: consider for ON-rewrite
|
|
178
|
-
if (head?.kind === '$tableAlias' && item.id.charAt(0) !== '$')
|
|
179
|
-
pushLink( head.elements[item.id], '_projections', elem );
|
|
180
|
-
}
|
|
181
|
-
else if (nav.navigation) { // not set for $self.…
|
|
182
|
-
// Path could start with table alias; get start index
|
|
183
|
-
let index = path.indexOf(nav.item);
|
|
184
|
-
if (index === -1)
|
|
185
|
-
return; // should not happen
|
|
186
176
|
|
|
187
|
-
|
|
188
|
-
if (
|
|
189
|
-
|
|
177
|
+
if (elem._pathHead) {
|
|
178
|
+
if (elem._pathHead?.kind !== '$inline')
|
|
179
|
+
// we're traversing top-level elements of the query; other _pathHead kinds can't happen
|
|
180
|
+
throw new CompilerAssertion('found unexpected "expand", but expected "inline"');
|
|
190
181
|
|
|
191
|
-
if (
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const step = path[index];
|
|
196
|
-
if (!step?.id || step.where || step.args)
|
|
197
|
-
break;
|
|
198
|
-
if (!navItem.elements?.[step.id]) {
|
|
199
|
-
const elements = navItem._origin?.elements ||
|
|
200
|
-
navItem._origin?.target?._artifact?.elements;
|
|
201
|
-
if (!elements)
|
|
202
|
-
break;
|
|
203
|
-
// Only link available path steps (navigation tree).
|
|
204
|
-
const origin = elements[step.id];
|
|
205
|
-
const member = linkToOrigin( origin, step.id, navItem, 'elements',
|
|
206
|
-
navItem.path?.location, true );
|
|
207
|
-
member.$inferred = 'expanded';
|
|
208
|
-
member.kind = '$navElement';
|
|
209
|
-
}
|
|
210
|
-
navItem = navItem.elements[step.id];
|
|
211
|
-
setLink( step, '_navigation', navItem );
|
|
212
|
-
++index;
|
|
182
|
+
if (!isPathBreakout( elem.value )) {
|
|
183
|
+
const fullPath = columnParentPath( elem );
|
|
184
|
+
if (fullPath)
|
|
185
|
+
setNavigationProjectionsForElementRef({ path: fullPath }, elem);
|
|
213
186
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
setNavigationProjectionsForElementRef( elem.value, elem );
|
|
217
190
|
}
|
|
218
191
|
} );
|
|
219
192
|
}
|
|
220
193
|
}
|
|
221
194
|
|
|
195
|
+
function setNavigationProjectionsForElementRef( ref, elem ) {
|
|
196
|
+
const { path } = ref;
|
|
197
|
+
const nav = pathNavigation( ref );
|
|
198
|
+
if (nav.navigation) { // not set for $self.…
|
|
199
|
+
// Path could start with table alias; get start index
|
|
200
|
+
let index = path.indexOf(nav.item);
|
|
201
|
+
if (index === -1)
|
|
202
|
+
return; // should not happen
|
|
203
|
+
|
|
204
|
+
let navItem = nav.navigation;
|
|
205
|
+
if (!nav.item._navigation) // first non-table-alias
|
|
206
|
+
setLink( nav.item, '_navigation', navItem );
|
|
207
|
+
|
|
208
|
+
if (path[index].where || path[index].args)
|
|
209
|
+
return;
|
|
210
|
+
++index;
|
|
211
|
+
while (navItem && index < path.length) {
|
|
212
|
+
const step = path[index];
|
|
213
|
+
if (!step?.id || step.where || step.args)
|
|
214
|
+
break;
|
|
215
|
+
if (!navItem.elements?.[step.id]) {
|
|
216
|
+
const elements = navItem._origin?.elements ||
|
|
217
|
+
navItem._origin?.target?._artifact?.elements;
|
|
218
|
+
if (!elements)
|
|
219
|
+
break;
|
|
220
|
+
// Only link available path steps (navigation tree).
|
|
221
|
+
const origin = elements[step.id];
|
|
222
|
+
const member = linkToOrigin( origin, step.id, navItem, 'elements',
|
|
223
|
+
navItem.path?.location, true );
|
|
224
|
+
member.$inferred = 'expanded';
|
|
225
|
+
member.kind = '$navElement';
|
|
226
|
+
}
|
|
227
|
+
navItem = navItem.elements[step.id];
|
|
228
|
+
setLink( step, '_navigation', navItem );
|
|
229
|
+
++index;
|
|
230
|
+
}
|
|
231
|
+
// Last path step, if found, is a simple projection
|
|
232
|
+
if (index === path.length && navItem)
|
|
233
|
+
pushLink( navItem, '_projections', elem );
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function columnParentPath( elem ) {
|
|
238
|
+
if (!elem._pathHead || !elem.value?.path || isPathBreakout( elem.value ))
|
|
239
|
+
return elem.value?.path;
|
|
240
|
+
|
|
241
|
+
const fullPath = [ ...elem.value.path ];
|
|
242
|
+
let pathHead = elem._pathHead;
|
|
243
|
+
while (pathHead) {
|
|
244
|
+
if (pathHead.kind !== '$inline' || !pathHead.value?.path ||
|
|
245
|
+
isPathBreakout( pathHead.value )) {
|
|
246
|
+
// path breakout for e.g. `$self.{ foo }`, `1 as a .{ foo }`
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
fullPath.unshift(...pathHead.value.path);
|
|
250
|
+
pathHead = pathHead._pathHead;
|
|
251
|
+
}
|
|
252
|
+
return fullPath;
|
|
253
|
+
}
|
|
254
|
+
|
|
222
255
|
function propagateKeyProps( view ) {
|
|
223
256
|
// Second argument true ensure that `key` is only propagated along simple
|
|
224
257
|
// view, i.e. ref or subquery in FROM, not UNION or JOIN.
|
|
@@ -255,9 +288,9 @@ function resolve( model ) {
|
|
|
255
288
|
function inheritedSourceKeyProp( { value, _pathHead } ) {
|
|
256
289
|
if (!value || !value.path)
|
|
257
290
|
return null;
|
|
258
|
-
const nav = pathNavigation( value );
|
|
291
|
+
const nav = !_pathHead && pathNavigation( value );
|
|
259
292
|
const item = value.path[value.path.length - 1];
|
|
260
|
-
if (nav
|
|
293
|
+
if (nav?.navigation && nav.item === item)
|
|
261
294
|
return item._artifact?.key;
|
|
262
295
|
if (value.path.length !== 1 || _pathHead?.kind !== '$inline')
|
|
263
296
|
return null;
|
|
@@ -281,13 +314,11 @@ function resolve( model ) {
|
|
|
281
314
|
const toMany = withAssociation( from, targetMaxNotOne, true );
|
|
282
315
|
if (toMany) {
|
|
283
316
|
propagateKeys = false;
|
|
284
|
-
info( 'query-from-many', [ toMany.location, query ], { art: toMany },
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
element: 'Key properties are not propagated because a to-many association $(MEMBER) of $(ART) is selected',
|
|
290
|
-
} );
|
|
317
|
+
info( 'query-from-many', [ toMany.location, query ], { art: toMany }, {
|
|
318
|
+
std: 'Key properties are not propagated because a to-many association $(ART) is selected',
|
|
319
|
+
// eslint-disable-next-line max-len
|
|
320
|
+
element: 'Key properties are not propagated because a to-many association $(MEMBER) of $(ART) is selected',
|
|
321
|
+
} );
|
|
291
322
|
}
|
|
292
323
|
// Check that all keys from the source are projected:
|
|
293
324
|
const notProjected = []; // we actually push to the array
|
|
@@ -297,7 +328,7 @@ function resolve( model ) {
|
|
|
297
328
|
if (nav.$duplicates)
|
|
298
329
|
continue;
|
|
299
330
|
const { key } = nav._origin;
|
|
300
|
-
if (key
|
|
331
|
+
if (key?.val && !nav._projections?.length)
|
|
301
332
|
notProjected.push( nav.name.id );
|
|
302
333
|
}
|
|
303
334
|
if (notProjected.length) {
|
|
@@ -311,9 +342,12 @@ function resolve( model ) {
|
|
|
311
342
|
// Check that there is no to-many assoc used in select item:
|
|
312
343
|
for (const name in query.elements) {
|
|
313
344
|
const elem = query.elements[name];
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
345
|
+
|
|
346
|
+
if (!elem.$inferred && elem.value?.path) {
|
|
347
|
+
const path = elem._pathHead ? columnParentPath( elem ) : elem.value.path;
|
|
348
|
+
if (testExpr({ path }, selectTest, () => false, elem))
|
|
349
|
+
propagateKeys = false;
|
|
350
|
+
}
|
|
317
351
|
}
|
|
318
352
|
return propagateKeys;
|
|
319
353
|
|
|
@@ -321,15 +355,13 @@ function resolve( model ) {
|
|
|
321
355
|
const art = withAssociation( expr, targetMaxNotOne );
|
|
322
356
|
if (art) {
|
|
323
357
|
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
324
|
-
info( 'query-navigate-many', [ art.location, user || query ], { art },
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
alias: 'Navigating along to-many mixin association $(MEMBER) - key properties are not propagated',
|
|
332
|
-
} );
|
|
358
|
+
info( 'query-navigate-many', [ art.location, user || query ], { art }, {
|
|
359
|
+
std: 'Navigating along to-many association $(ART) - key properties are not propagated',
|
|
360
|
+
// eslint-disable-next-line max-len
|
|
361
|
+
element: 'Navigating along to-many association $(MEMBER) of $(ART) - key properties are not propagated',
|
|
362
|
+
// eslint-disable-next-line max-len
|
|
363
|
+
alias: 'Navigating along to-many mixin association $(MEMBER) - key properties are not propagated',
|
|
364
|
+
} );
|
|
333
365
|
}
|
|
334
366
|
return art;
|
|
335
367
|
}
|
|
@@ -442,7 +474,7 @@ function resolve( model ) {
|
|
|
442
474
|
obj.items = items;
|
|
443
475
|
obj.$expand = 'origin';
|
|
444
476
|
}
|
|
445
|
-
if (obj.items) { // TODO: make this a while in
|
|
477
|
+
if (obj.items) { // TODO: make this a while in v6 (also items proxy)
|
|
446
478
|
obj = obj.items || obj; // the object which has type properties
|
|
447
479
|
effectiveType( obj );
|
|
448
480
|
}
|
|
@@ -510,14 +542,12 @@ function resolve( model ) {
|
|
|
510
542
|
else if (obj.targetAspect && obj.targetAspect.elements) { // silent dependencies
|
|
511
543
|
forEachGeneric( obj.targetAspect, 'elements', elem => dependsOnSilent( art, elem ) );
|
|
512
544
|
}
|
|
545
|
+
|
|
513
546
|
if (obj.foreignKeys) { // silent dependencies
|
|
514
547
|
// Avoid strange ref-cyclic if managed composition is key (check comes later)
|
|
515
|
-
//
|
|
516
|
-
if (obj.$inferred !== 'aspect-composition')
|
|
517
|
-
forEachGeneric( obj, 'foreignKeys', (elem)
|
|
518
|
-
dependsOnSilent( art, elem );
|
|
519
|
-
} );
|
|
520
|
-
}
|
|
548
|
+
// Done by addImplicitForeignKeys() for implicit keys.
|
|
549
|
+
if (!art.foreignKeys?.[$inferred] && obj.$inferred !== 'aspect-composition')
|
|
550
|
+
forEachGeneric( obj, 'foreignKeys', elem => dependsOnSilent( art, elem ) );
|
|
521
551
|
addForeignKeyNavigations( art );
|
|
522
552
|
}
|
|
523
553
|
|
|
@@ -1266,7 +1296,6 @@ function resolve( model ) {
|
|
|
1266
1296
|
{ art: target, '#': variant, keyword: op || '' }, {
|
|
1267
1297
|
std: 'Redirection involves the complex view $(ART)',
|
|
1268
1298
|
target: 'The redirected target $(ART) is a complex view',
|
|
1269
|
-
// eslint-disable-next-line max-len
|
|
1270
1299
|
targetOp: 'The redirected target $(ART) is a complex view with $(KEYWORD)',
|
|
1271
1300
|
} );
|
|
1272
1301
|
break;
|
|
@@ -1360,19 +1389,6 @@ function resolve( model ) {
|
|
|
1360
1389
|
// General resolver functions
|
|
1361
1390
|
//--------------------------------------------------------------------------
|
|
1362
1391
|
|
|
1363
|
-
// Resolve the n-1 path steps before the definition name for LSP.
|
|
1364
|
-
function resolveDefinitionName( art ) {
|
|
1365
|
-
const path = art?.name?.path;
|
|
1366
|
-
if (!art || art._main || !path || path.length <= 1)
|
|
1367
|
-
return;
|
|
1368
|
-
|
|
1369
|
-
let name = art.name.id;
|
|
1370
|
-
for (let i = path.length - 1; i > 0; --i) {
|
|
1371
|
-
name = name.substring(0, name.length - path[i].id.length - 1);
|
|
1372
|
-
setArtifactLink( path[i - 1], model.definitions[name] || false );
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
1392
|
// Resolve the type and its arguments if applicable.
|
|
1377
1393
|
function resolveTypeExpr( art, user ) {
|
|
1378
1394
|
const typeArt = resolvePath( art.type, 'type', user );
|
|
@@ -1565,8 +1581,6 @@ function resolve( model ) {
|
|
|
1565
1581
|
function pathNavigation( ref ) {
|
|
1566
1582
|
// currently, indirectly projectable elements are not included - we might
|
|
1567
1583
|
// keep it this way! If we want them to be included - be aware: cycles
|
|
1568
|
-
if (!ref._artifact)
|
|
1569
|
-
return {};
|
|
1570
1584
|
let item = ref.path && ref.path[0];
|
|
1571
1585
|
const root = item && item._navigation;
|
|
1572
1586
|
if (!root)
|
|
@@ -1583,4 +1597,13 @@ function pathNavigation( ref ) {
|
|
|
1583
1597
|
return { navigation: root.elements?.[item.id], item, tableAlias: root };
|
|
1584
1598
|
}
|
|
1585
1599
|
|
|
1600
|
+
function isPathBreakout( ref ) {
|
|
1601
|
+
if (!ref.path?.[0])
|
|
1602
|
+
return false;
|
|
1603
|
+
if (ref.scope === 'param')
|
|
1604
|
+
return true;
|
|
1605
|
+
const nav = (ref.path[0]._navigation || ref.path[0]._artifact);
|
|
1606
|
+
return nav && (nav.kind === '$self' || ref.path[0].id.charAt(0) === '$');
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1586
1609
|
module.exports = resolve;
|
package/lib/compiler/shared.js
CHANGED
|
@@ -19,7 +19,6 @@ const {
|
|
|
19
19
|
isAssocToPrimaryKeys,
|
|
20
20
|
artifactRefLocation,
|
|
21
21
|
} = require('./utils');
|
|
22
|
-
const { isBetaEnabled } = require('../base/model');
|
|
23
22
|
|
|
24
23
|
const $inferred = Symbol.for( 'cds.$inferred' );
|
|
25
24
|
const $location = Symbol.for( 'cds.$location' );
|
|
@@ -56,13 +55,14 @@ function fns( model ) {
|
|
|
56
55
|
lexical: userBlock,
|
|
57
56
|
dynamic: modelDefinitions,
|
|
58
57
|
notFound: undefinedForAnnotate,
|
|
58
|
+
accept: extendableArtifact,
|
|
59
59
|
},
|
|
60
60
|
extend: {
|
|
61
61
|
isMainRef: 'no-generated',
|
|
62
62
|
lexical: userBlock,
|
|
63
63
|
dynamic: modelDefinitions,
|
|
64
64
|
notFound: undefinedDefinition,
|
|
65
|
-
accept:
|
|
65
|
+
accept: extendableArtifact,
|
|
66
66
|
},
|
|
67
67
|
_extensions: {
|
|
68
68
|
isMainRef: 'all',
|
|
@@ -163,7 +163,7 @@ function fns( model ) {
|
|
|
163
163
|
param: paramSemantics,
|
|
164
164
|
},
|
|
165
165
|
'limit-offset': 'limit-rows',
|
|
166
|
-
// general element references
|
|
166
|
+
// general element / variable references --------------------------------------
|
|
167
167
|
where: {
|
|
168
168
|
lexical: tableAliasesAndSelf,
|
|
169
169
|
dollar: true,
|
|
@@ -279,7 +279,7 @@ function fns( model ) {
|
|
|
279
279
|
annotation: { // annotation assignments
|
|
280
280
|
lexical: justDollarAliases,
|
|
281
281
|
dollar: true,
|
|
282
|
-
dynamic:
|
|
282
|
+
dynamic: parentElementsOrKeys,
|
|
283
283
|
navigation: assocOnNavigation,
|
|
284
284
|
noDep: true,
|
|
285
285
|
notFound: undefinedParentElement,
|
|
@@ -298,6 +298,7 @@ function fns( model ) {
|
|
|
298
298
|
}),
|
|
299
299
|
},
|
|
300
300
|
// TODO: introduce some kind of inheritance
|
|
301
|
+
// used by xpr-rewrite.js to resolve rewritten path roots.
|
|
301
302
|
annoRewrite: { // annotation assignments
|
|
302
303
|
lexical: justDollarAliases,
|
|
303
304
|
dollar: true,
|
|
@@ -323,6 +324,7 @@ function fns( model ) {
|
|
|
323
324
|
resolveTypeArgumentsUnchecked, // TODO: move to some other file
|
|
324
325
|
resolvePathRoot,
|
|
325
326
|
resolvePath,
|
|
327
|
+
resolveDefinitionName,
|
|
326
328
|
checkExpr,
|
|
327
329
|
checkOnCondition,
|
|
328
330
|
navigationEnv,
|
|
@@ -405,6 +407,7 @@ function fns( model ) {
|
|
|
405
407
|
|
|
406
408
|
const s = referenceSemantics[expected];
|
|
407
409
|
const semantics = (typeof s === 'string') ? referenceSemantics[s] : s;
|
|
410
|
+
semantics.name = expected;
|
|
408
411
|
|
|
409
412
|
const r = getPathRoot( ref, semantics, origUser );
|
|
410
413
|
const root = r && acceptPathRoot( r, ref, semantics, origUser );
|
|
@@ -512,6 +515,25 @@ function fns( model ) {
|
|
|
512
515
|
artifact.$typeArgs = undefined;
|
|
513
516
|
}
|
|
514
517
|
|
|
518
|
+
// Resolve the n-1 path steps before the definition name for LSP.
|
|
519
|
+
function resolveDefinitionName( art ) {
|
|
520
|
+
const path = art?.name?.path;
|
|
521
|
+
if (!art || art._main || !path || path.length <= 1)
|
|
522
|
+
return;
|
|
523
|
+
|
|
524
|
+
// Don't resolve paths in an annotation as a definition!
|
|
525
|
+
const definitions = art.kind === 'annotation' ? model.vocabularies : model.definitions;
|
|
526
|
+
|
|
527
|
+
let name = art.name.id;
|
|
528
|
+
if (art.kind === 'namespace') // namespace-statements are ref-only.
|
|
529
|
+
setArtifactLink( path[path.length - 1], definitions[name] || false );
|
|
530
|
+
|
|
531
|
+
for (let i = path.length - 1; i > 0; --i) {
|
|
532
|
+
name = name.substring(0, name.length - path[i].id.length - 1);
|
|
533
|
+
setArtifactLink( path[i - 1], definitions[name] || false );
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
515
537
|
function getPathRoot( { path, scope, location }, semantics, user ) {
|
|
516
538
|
// TODO: use string value of isMainRef?
|
|
517
539
|
const head = path[0];
|
|
@@ -524,8 +546,11 @@ function fns( model ) {
|
|
|
524
546
|
ruser = ruser._outer;
|
|
525
547
|
|
|
526
548
|
// Handle expand/inline, `type of`, :param, global (internally for CDL):
|
|
527
|
-
if (user._pathHead && !semantics.isMainRef) // in expand/inline
|
|
549
|
+
if (user._pathHead && !semantics.isMainRef) { // in expand/inline
|
|
550
|
+
const { name } = semantics;
|
|
528
551
|
semantics = semantics.nestedColumn();
|
|
552
|
+
semantics.name = name;
|
|
553
|
+
}
|
|
529
554
|
if (typeof scope === 'string') { // typeOf, param, global
|
|
530
555
|
semantics = semantics?.[scope] && semantics[scope]( ruser, path, location, semantics );
|
|
531
556
|
if (!semantics) {
|
|
@@ -563,10 +588,13 @@ function fns( model ) {
|
|
|
563
588
|
if (r)
|
|
564
589
|
return setArtifactLink( head, r );
|
|
565
590
|
|
|
566
|
-
if (!semantics.dollar)
|
|
591
|
+
if (!semantics.dollar) {
|
|
567
592
|
valid.push( dynamicDict );
|
|
568
|
-
|
|
569
|
-
|
|
593
|
+
}
|
|
594
|
+
else {
|
|
595
|
+
valid.push( removeInvalidMagicVariables( model.$magicVariables.elements, semantics ),
|
|
596
|
+
removeDollarNames( dynamicDict ) );
|
|
597
|
+
}
|
|
570
598
|
// TODO: streamline function arguments (probably: user, path, semantics )
|
|
571
599
|
const undef = semantics.notFound?.( user._user || user, head, valid, dynamicDict,
|
|
572
600
|
!isMainRef && user._user && user._artifact,
|
|
@@ -629,7 +657,6 @@ function fns( model ) {
|
|
|
629
657
|
// simple 'ref-undefined-art'/'ref-undefined-def' - TODO: which we
|
|
630
658
|
// could "change" to this message at the end of compile():
|
|
631
659
|
error( 'ref-unexpected-autoexposed', [ item.location, user ], { art },
|
|
632
|
-
// eslint-disable-next-line max-len
|
|
633
660
|
'An auto-exposed entity can\'t be referred to - expose entity $(ART) explicitly' );
|
|
634
661
|
return null; // continuation semantics: like “not found”
|
|
635
662
|
}
|
|
@@ -663,9 +690,13 @@ function fns( model ) {
|
|
|
663
690
|
// Do not accept a lonely table alias and `$projection`
|
|
664
691
|
// TODO: test table alias and mixin named `$projection`
|
|
665
692
|
if (path.length !== 1 || user.expand || user.inline) {
|
|
666
|
-
|
|
667
|
-
|
|
693
|
+
if (semantics.rewriteProjectionToSelf &&
|
|
694
|
+
art.kind === '$self' && path[0].id === '$projection') {
|
|
695
|
+
// Rewrite $projection to $self
|
|
668
696
|
path[0].id = '$self';
|
|
697
|
+
warning( 'ref-expecting-$self', [ path[0].location, user ],
|
|
698
|
+
{ code: '$projection', newcode: '$self' });
|
|
699
|
+
}
|
|
669
700
|
return art.name?.$inferred !== '$internal'; // not a compiler-generated internal alias
|
|
670
701
|
}
|
|
671
702
|
|
|
@@ -725,7 +756,14 @@ function fns( model ) {
|
|
|
725
756
|
message( 'ref-obsolete-parameters', [ head.location, user ],
|
|
726
757
|
{ code: `$parameters.${ id }`, newcode: `:${ id }` },
|
|
727
758
|
'Obsolete $(CODE) - replace by $(NEWCODE)' );
|
|
728
|
-
|
|
759
|
+
return art;
|
|
760
|
+
}
|
|
761
|
+
case 'builtin': {
|
|
762
|
+
if (art.name.id === '$at') {
|
|
763
|
+
warning( 'ref-deprecated-variable', [ head.location, user ],
|
|
764
|
+
{ code: '$at', newcode: '$valid' },
|
|
765
|
+
'$(CODE) is deprecated; use $(NEWCODE) instead' );
|
|
766
|
+
}
|
|
729
767
|
return art;
|
|
730
768
|
}
|
|
731
769
|
default:
|
|
@@ -868,6 +906,13 @@ function fns( model ) {
|
|
|
868
906
|
return environment( useParent ? user._parent : user );
|
|
869
907
|
}
|
|
870
908
|
|
|
909
|
+
function parentElementsOrKeys( user ) {
|
|
910
|
+
// annotations on foreign keys only ever have access to their keys (except of course via $self)
|
|
911
|
+
if (user.kind === 'key')
|
|
912
|
+
return user._parent?.foreignKeys || Object.create( null );
|
|
913
|
+
return parentElements( user );
|
|
914
|
+
}
|
|
915
|
+
|
|
871
916
|
function queryElements( user ) {
|
|
872
917
|
return environment( user );
|
|
873
918
|
}
|
|
@@ -1113,11 +1158,11 @@ function fns( model ) {
|
|
|
1113
1158
|
// TODO: if it becomes non-configurable, we can omit this warning
|
|
1114
1159
|
let id = pathName( path );
|
|
1115
1160
|
let head = path[0]._artifact || { _parent: art };
|
|
1116
|
-
// eslint-disable-next-line no-cond-assign
|
|
1117
1161
|
while ((head = head?._parent) && head.kind === 'builtin')
|
|
1118
1162
|
id = `${ head.name.id }.${ id }`;
|
|
1119
1163
|
const msgId = (art.$uncheckedElements) ? 'ref-unknown-var' : 'ref-undefined-var';
|
|
1120
|
-
signalNotFound( msgId, [ item.location, user ],
|
|
1164
|
+
signalNotFound( msgId, [ item.location, user ],
|
|
1165
|
+
removeInvalidMagicVariables( valid, semantics ), { id }, semantics );
|
|
1121
1166
|
}
|
|
1122
1167
|
else if (art.kind === 'aspect' && !art.name) { // anonymous target aspect - TODO: still?
|
|
1123
1168
|
signalNotFound( 'ref-undefined-element', [ item.location, user ], valid,
|
|
@@ -1199,9 +1244,16 @@ function fns( model ) {
|
|
|
1199
1244
|
: acceptElemOrVar( art, user, ref );
|
|
1200
1245
|
}
|
|
1201
1246
|
|
|
1202
|
-
function acceptElemOrVar( art, user, ref ) {
|
|
1247
|
+
function acceptElemOrVar( art, user, ref, semantics ) {
|
|
1203
1248
|
const { path } = ref;
|
|
1204
1249
|
if (art.kind === 'builtin') {
|
|
1250
|
+
if (art.$onlyInExprCtx && !art.$onlyInExprCtx.includes(semantics.name)) {
|
|
1251
|
+
error( 'ref-unexpected-var', [ ref.location, user ], {
|
|
1252
|
+
'#': art.$onlyInExprCtx[0], name: pathName( path ),
|
|
1253
|
+
});
|
|
1254
|
+
return null;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1205
1257
|
if (user.expand || user.inline) {
|
|
1206
1258
|
const location = (user.expand || user.inline)[$location];
|
|
1207
1259
|
const code = (user.expand) ? '{ ‹expand› }' : '.{ ‹inline› }';
|
|
@@ -1234,16 +1286,19 @@ function fns( model ) {
|
|
|
1234
1286
|
return art;
|
|
1235
1287
|
}
|
|
1236
1288
|
|
|
1237
|
-
|
|
1289
|
+
/**
|
|
1290
|
+
* Returns true, if the artifact is a _real_ artifact that can be used for `extend`/`annotate`.
|
|
1291
|
+
*/
|
|
1292
|
+
function extendableArtifact( art, user, ref ) {
|
|
1238
1293
|
if (art.kind !== 'namespace')
|
|
1239
1294
|
return art;
|
|
1240
|
-
// For compatibility (≤v4), we accept `extend Unknown` without elements/actions/includes
|
|
1241
|
-
// In v5, only allow `extend with definitions`.
|
|
1242
|
-
if (!isBetaEnabled( model.options, 'v5preview' ) &&
|
|
1243
|
-
!(user.elements || user.actions || user.includes))
|
|
1244
|
-
return art;
|
|
1245
1295
|
const { location } = ref.path[ref.path.length - 1];
|
|
1246
|
-
|
|
1296
|
+
if (user.kind === 'extend' && !(user.elements || user.actions || user.includes))
|
|
1297
|
+
return art; // allow `extend with definitions` and empty extends
|
|
1298
|
+
|
|
1299
|
+
// for `annotate`, handle "namespaces" just like unknown artifacts: only emit a warning
|
|
1300
|
+
signalNotFound( user.kind === 'annotate' ? 'ext-undefined-def' : 'ref-undefined-def',
|
|
1301
|
+
[ location, user ], null, { art } );
|
|
1247
1302
|
return false;
|
|
1248
1303
|
}
|
|
1249
1304
|
|
|
@@ -1459,7 +1514,7 @@ function fns( model ) {
|
|
|
1459
1514
|
// console.log('NAV:',expr.path.map(r=>r.id),self)
|
|
1460
1515
|
if (self || self == null && columnRefStartsWithSelf( user )) {
|
|
1461
1516
|
checkOnlyForeignKeyNavigation( user, expr.path, 0, 'self-' );
|
|
1462
|
-
checkNoUnmanaged( expr, user, 'self-unmanaged' );
|
|
1517
|
+
checkNoUnmanaged( expr, user, true, 'self-unmanaged' );
|
|
1463
1518
|
}
|
|
1464
1519
|
// TODO: set navigation dependencies later to avoid both ref-cyclic and
|
|
1465
1520
|
// ref-invalid-navigation/ref-unexpected-assoc
|
|
@@ -1469,18 +1524,20 @@ function fns( model ) {
|
|
|
1469
1524
|
const { path } = expr;
|
|
1470
1525
|
if (!path)
|
|
1471
1526
|
return;
|
|
1472
|
-
|
|
1527
|
+
const self = path?.[0]?._navigation?.kind !== '$tableAlias';
|
|
1528
|
+
if (self)
|
|
1473
1529
|
checkOnlyForeignKeyNavigation( user, expr.path );
|
|
1474
|
-
checkNoUnmanaged( expr, user );
|
|
1530
|
+
checkNoUnmanaged( expr, user, self );
|
|
1475
1531
|
}
|
|
1476
1532
|
|
|
1477
1533
|
function checkRefInQuery( expr, exprCtx, user ) {
|
|
1478
1534
|
const { path } = expr;
|
|
1479
1535
|
if (!path)
|
|
1480
1536
|
return;
|
|
1481
|
-
|
|
1537
|
+
const self = pathStartsWithSelf( expr );
|
|
1538
|
+
if (self)
|
|
1482
1539
|
checkOnlyForeignKeyNavigation( user, expr.path, 0, 'self-' );
|
|
1483
|
-
checkNoUnmanaged( expr, user );
|
|
1540
|
+
checkNoUnmanaged( expr, user, self );
|
|
1484
1541
|
}
|
|
1485
1542
|
|
|
1486
1543
|
function checkExpandInlineRef( art, user, ref ) {
|
|
@@ -1523,10 +1580,10 @@ function fns( model ) {
|
|
|
1523
1580
|
}
|
|
1524
1581
|
const index = userTargetElementPathIndex( user, path );
|
|
1525
1582
|
checkOnlyForeignKeyNavigation( user, path, index );
|
|
1526
|
-
|
|
1583
|
+
const last = path[path.length - 1];
|
|
1584
|
+
if (!last.where && ref._artifact?.on) { // filter already complained about
|
|
1527
1585
|
const target = index > 0 && index < path.length && ref._artifact?.target;
|
|
1528
1586
|
const msg = (target?._artifact === user._main) ? 'self' : 'unmanaged';
|
|
1529
|
-
const last = path[path.length - 1];
|
|
1530
1587
|
error( 'ref-unexpected-assoc', [ last.location, user ],
|
|
1531
1588
|
{ '#': msg, code: '= $self' } );
|
|
1532
1589
|
}
|
|
@@ -1614,6 +1671,16 @@ function fns( model ) {
|
|
|
1614
1671
|
index = checkCoveredByForeignKey( assoc, path, index, user, msgPrefix );
|
|
1615
1672
|
}
|
|
1616
1673
|
assoc = path[index]?._artifact;
|
|
1674
|
+
if (assoc?.target) {
|
|
1675
|
+
// testing this above is not enough: we would not complain about $self
|
|
1676
|
+
// assoc filter at end of ref with expand/inline. We might also move the
|
|
1677
|
+
// unmanaged test above to here.
|
|
1678
|
+
if (path[index]?.where) {
|
|
1679
|
+
error( 'ref-unexpected-assoc', [ path[index].location, user ],
|
|
1680
|
+
{ '#': `${ msgPrefix }with-filter`, alias: '$self' } );
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1617
1684
|
}
|
|
1618
1685
|
}
|
|
1619
1686
|
|
|
@@ -1635,7 +1702,6 @@ function fns( model ) {
|
|
|
1635
1702
|
const txt = index >= path.length
|
|
1636
1703
|
? 'complete'
|
|
1637
1704
|
: (isAssocToPrimaryKeys( assoc ) ? 'keys' : 'std');
|
|
1638
|
-
// eslint-disable-next-line max-len
|
|
1639
1705
|
error( 'ref-invalid-navigation', [ last.location, user ], {
|
|
1640
1706
|
'#': msgPrefix + txt, art: assoc, name: last.id, alias: '$self',
|
|
1641
1707
|
}, {
|
|
@@ -1653,10 +1719,12 @@ function fns( model ) {
|
|
|
1653
1719
|
return path.length;
|
|
1654
1720
|
}
|
|
1655
1721
|
|
|
1656
|
-
function checkNoUnmanaged( ref, user, messageVariant = 'unmanaged' ) {
|
|
1722
|
+
function checkNoUnmanaged( ref, user, self, messageVariant = 'unmanaged' ) {
|
|
1657
1723
|
if (ref._artifact?.on && !ref.$expected) {
|
|
1658
1724
|
const { path } = ref;
|
|
1659
1725
|
const last = path[path.length - 1];
|
|
1726
|
+
if (self && last.where) // already complained about filter
|
|
1727
|
+
return;
|
|
1660
1728
|
error( 'ref-unexpected-assoc', [ last.location, user ],
|
|
1661
1729
|
{ '#': messageVariant, alias: '$self' } );
|
|
1662
1730
|
}
|
|
@@ -1670,7 +1738,8 @@ function fns( model ) {
|
|
|
1670
1738
|
* @param {string} msgId
|
|
1671
1739
|
* @param {any} location
|
|
1672
1740
|
* @param {object[]} valid
|
|
1673
|
-
* @param
|
|
1741
|
+
* @param {object} [textParams]
|
|
1742
|
+
* @param {object} [semantics]
|
|
1674
1743
|
*/
|
|
1675
1744
|
function signalNotFound( msgId, location, valid, textParams, semantics ) {
|
|
1676
1745
|
if (location.$notFound) // TODO: still necessary?
|
|
@@ -1745,6 +1814,19 @@ function fns( model ) {
|
|
|
1745
1814
|
}
|
|
1746
1815
|
}
|
|
1747
1816
|
|
|
1817
|
+
function removeInvalidMagicVariables( variables, semantics ) {
|
|
1818
|
+
if (Array.isArray(variables))
|
|
1819
|
+
return variables.map(variable => removeInvalidMagicVariables( variable, semantics ));
|
|
1820
|
+
|
|
1821
|
+
const valid = Object.create(null);
|
|
1822
|
+
for (const name in variables) {
|
|
1823
|
+
const variable = variables[name];
|
|
1824
|
+
if (!variable.$onlyInExprCtx || variable.$onlyInExprCtx.includes( semantics.name ))
|
|
1825
|
+
valid[name] = variable;
|
|
1826
|
+
}
|
|
1827
|
+
return valid;
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1748
1830
|
function removeDollarNames( dict ) {
|
|
1749
1831
|
const r = Object.create( null );
|
|
1750
1832
|
for (const name in dict) {
|