@sap/cds-compiler 3.1.2 → 3.4.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 +101 -3
- package/bin/cdsc.js +4 -2
- package/doc/CHANGELOG_BETA.md +35 -0
- package/lib/api/main.js +153 -29
- package/lib/api/validate.js +8 -3
- package/lib/base/dictionaries.js +6 -6
- package/lib/base/error.js +2 -2
- package/lib/base/keywords.js +106 -24
- package/lib/base/message-registry.js +177 -79
- package/lib/base/messages.js +78 -57
- package/lib/base/model.js +2 -1
- package/lib/checks/actionsFunctions.js +1 -1
- package/lib/checks/annotationsOData.js +2 -2
- package/lib/checks/arrayOfs.js +15 -7
- package/lib/checks/cdsPersistence.js +1 -1
- package/lib/checks/checkForTypes.js +53 -0
- package/lib/checks/defaultValues.js +4 -2
- package/lib/checks/elements.js +81 -6
- package/lib/checks/foreignKeys.js +12 -13
- package/lib/checks/invalidTarget.js +10 -11
- package/lib/checks/managedInType.js +21 -15
- package/lib/checks/nullableKeys.js +1 -1
- package/lib/checks/onConditions.js +9 -9
- package/lib/checks/parameters.js +23 -0
- package/lib/checks/queryNoDbArtifacts.js +1 -1
- package/lib/checks/selectItems.js +1 -1
- package/lib/checks/sql-snippets.js +12 -10
- package/lib/checks/types.js +2 -2
- package/lib/checks/utils.js +17 -7
- package/lib/checks/validator.js +36 -14
- package/lib/compiler/assert-consistency.js +21 -13
- package/lib/compiler/builtins.js +8 -0
- package/lib/compiler/checks.js +57 -40
- package/lib/compiler/define.js +139 -69
- package/lib/compiler/extend.js +319 -50
- package/lib/compiler/finalize-parse-cdl.js +14 -9
- package/lib/compiler/kick-start.js +2 -35
- package/lib/compiler/populate.js +111 -68
- package/lib/compiler/propagator.js +5 -3
- package/lib/compiler/resolve.js +71 -108
- package/lib/compiler/shared.js +82 -54
- package/lib/compiler/tweak-assocs.js +26 -14
- package/lib/compiler/utils.js +13 -2
- package/lib/edm/annotations/genericTranslation.js +10 -7
- package/lib/edm/csn2edm.js +11 -11
- package/lib/edm/edm.js +17 -9
- package/lib/edm/edmPreprocessor.js +53 -30
- package/lib/edm/edmUtils.js +7 -2
- package/lib/gen/Dictionary.json +14 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -2
- package/lib/gen/languageParser.js +4312 -4186
- package/lib/inspect/inspectModelStatistics.js +1 -1
- package/lib/inspect/inspectPropagation.js +23 -9
- package/lib/json/csnVersion.js +13 -13
- package/lib/json/from-csn.js +161 -172
- package/lib/json/to-csn.js +70 -10
- package/lib/language/.eslintrc.json +4 -0
- package/lib/language/antlrParser.js +8 -11
- package/lib/language/docCommentParser.js +1 -2
- package/lib/language/errorStrategy.js +54 -27
- package/lib/language/genericAntlrParser.js +140 -93
- package/lib/language/language.g4 +57 -33
- package/lib/language/multiLineStringParser.js +75 -63
- package/lib/main.d.ts +3 -6
- package/lib/main.js +1 -0
- package/lib/model/.eslintrc.json +13 -0
- package/lib/model/api.js +4 -2
- package/lib/model/csnRefs.js +78 -50
- package/lib/model/csnUtils.js +272 -222
- package/lib/model/enrichCsn.js +41 -31
- package/lib/model/revealInternalProperties.js +61 -57
- package/lib/model/sortViews.js +35 -31
- package/lib/modelCompare/compare.js +52 -18
- package/lib/modelCompare/filter.js +83 -0
- package/lib/optionProcessor.js +10 -1
- package/lib/render/manageConstraints.js +11 -7
- package/lib/render/toCdl.js +151 -106
- package/lib/render/toHdbcds.js +8 -6
- package/lib/render/toRename.js +4 -4
- package/lib/render/toSql.js +17 -7
- package/lib/render/utils/common.js +27 -9
- package/lib/render/utils/sql.js +5 -5
- package/lib/sql-identifier.js +7 -0
- package/lib/transform/db/applyTransformations.js +32 -3
- package/lib/transform/db/assertUnique.js +27 -38
- package/lib/transform/db/expansion.js +92 -41
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/temporal.js +3 -1
- package/lib/transform/db/transformExists.js +8 -2
- package/lib/transform/db/views.js +42 -13
- package/lib/transform/draft/db.js +2 -2
- package/lib/transform/forOdataNew.js +10 -7
- package/lib/transform/{forHanaNew.js → forRelationalDB.js} +18 -12
- package/lib/transform/localized.js +29 -20
- package/lib/transform/odata/toFinalBaseType.js +8 -11
- package/lib/transform/odata/typesExposure.js +2 -1
- package/lib/transform/parseExpr.js +245 -0
- package/lib/transform/transformUtilsNew.js +122 -51
- package/lib/transform/translateAssocsToJoins.js +17 -16
- package/lib/utils/moduleResolve.js +5 -5
- package/lib/utils/objectUtils.js +3 -3
- package/lib/utils/term.js +5 -5
- package/package.json +2 -2
- package/share/messages/anno-duplicate-unrelated-layer.md +6 -6
- package/share/messages/check-proper-type-of.md +4 -4
- package/share/messages/check-proper-type.md +2 -2
- package/share/messages/duplicate-autoexposed.md +4 -4
- package/share/messages/extend-repeated-intralayer.md +4 -5
- package/share/messages/extend-unrelated-layer.md +4 -4
- package/share/messages/message-explanations.json +3 -1
- package/share/messages/redirected-to-ambiguous.md +7 -6
- package/share/messages/redirected-to-complex.md +63 -0
- package/share/messages/redirected-to-unrelated.md +6 -5
- package/share/messages/rewrite-not-supported.md +4 -4
- package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +4 -4
- package/share/messages/wildcard-excluding-one.md +37 -0
|
@@ -10,7 +10,6 @@ function kickStart( model ) {
|
|
|
10
10
|
const { message } = model.$messageFunctions;
|
|
11
11
|
|
|
12
12
|
const { resolveUncheckedPath, resolvePath } = model.$functions;
|
|
13
|
-
Object.assign( model.$functions, { projectionAncestor } );
|
|
14
13
|
|
|
15
14
|
// Set _service link (sorted to set it on parent first). Could be set
|
|
16
15
|
// directly, but beware a namespace becoming a service later.
|
|
@@ -68,7 +67,7 @@ function kickStart( model ) {
|
|
|
68
67
|
// no redirection target for E if Service2.E = projection on Service1.E and
|
|
69
68
|
// Service1.E = projection on E
|
|
70
69
|
const chain = [];
|
|
71
|
-
const autoexposed =
|
|
70
|
+
const autoexposed = annotationVal( art['@cds.autoexposed'] );
|
|
72
71
|
const preferredRedirectionTarget = annotationVal( art['@cds.redirection.target'] );
|
|
73
72
|
// no need to set preferredRedirectionTarget in the while loop as we would
|
|
74
73
|
// use the projection having @cds.redirection.target anyhow instead of
|
|
@@ -80,7 +79,7 @@ function kickStart( model ) {
|
|
|
80
79
|
chain.push( art );
|
|
81
80
|
setLink( art, '_ancestors', null ); // avoid infloop with cyclic from
|
|
82
81
|
const name = resolveUncheckedPath( art._from[0], 'include', art ); // TODO: 'include'?
|
|
83
|
-
art = name &&
|
|
82
|
+
art = name && model.definitions[name];
|
|
84
83
|
if (autoexposed)
|
|
85
84
|
break; // only direct projection for auto-exposed
|
|
86
85
|
}
|
|
@@ -93,38 +92,6 @@ function kickStart( model ) {
|
|
|
93
92
|
}
|
|
94
93
|
}
|
|
95
94
|
|
|
96
|
-
// Return argument `source` if entity `source` has parameters like `params`
|
|
97
|
-
// - same parameters, although `params` can contain a new optional one (with DEFAULT)
|
|
98
|
-
// - a parameter in `params` can be optional which is not in `source.params`, but not vice versa
|
|
99
|
-
// - exactly the same types (type argument do not matter)
|
|
100
|
-
function projectionAncestor( source, params ) {
|
|
101
|
-
if (!source)
|
|
102
|
-
return source;
|
|
103
|
-
if (!params) // proj has no params => ok if source has no params
|
|
104
|
-
return !source.params && source;
|
|
105
|
-
const sourceParams = source.params || Object.create(null);
|
|
106
|
-
for (const n in sourceParams) {
|
|
107
|
-
if (!(n in params)) // source param is not projection param
|
|
108
|
-
return null; // -> can't be used as implicit redirection target
|
|
109
|
-
}
|
|
110
|
-
for (const n in params) {
|
|
111
|
-
const pp = params[n];
|
|
112
|
-
const sp = sourceParams[n];
|
|
113
|
-
if (sp) {
|
|
114
|
-
if (sp.default && !pp.default) // param DEFAULT clause not supported yet
|
|
115
|
-
return null; // param is not optional anymore
|
|
116
|
-
const pt = pp.type && resolveUncheckedPath( pp.type, 'type', pp );
|
|
117
|
-
const st = sp.type && resolveUncheckedPath( sp.type, 'type', sp );
|
|
118
|
-
if ((pt || null) !== (st || null))
|
|
119
|
-
return null; // params have different type
|
|
120
|
-
}
|
|
121
|
-
else if (!pp.default) {
|
|
122
|
-
return null;
|
|
123
|
-
} // non-optional param in projection, but not source
|
|
124
|
-
}
|
|
125
|
-
return source;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
95
|
function postProcessArtifact( art ) {
|
|
129
96
|
tagCompositionTargets( art );
|
|
130
97
|
if (art.$queries) {
|
package/lib/compiler/populate.js
CHANGED
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
|
|
20
20
|
const {
|
|
21
21
|
isDeprecatedEnabled,
|
|
22
|
-
isBetaEnabled,
|
|
23
22
|
forEachDefinition,
|
|
24
23
|
forEachMember,
|
|
25
24
|
forEachGeneric,
|
|
@@ -29,6 +28,7 @@ const {
|
|
|
29
28
|
} = require('../base/dictionaries');
|
|
30
29
|
const { dictLocation } = require('../base/location');
|
|
31
30
|
const { weakLocation } = require('../base/messages');
|
|
31
|
+
const { CompilerAssertion } = require('../base/error');
|
|
32
32
|
|
|
33
33
|
const { kindProperties } = require('./base');
|
|
34
34
|
const {
|
|
@@ -36,6 +36,8 @@ const {
|
|
|
36
36
|
setLink,
|
|
37
37
|
setArtifactLink,
|
|
38
38
|
annotationVal,
|
|
39
|
+
annotationIsFalse,
|
|
40
|
+
annotationLocation,
|
|
39
41
|
augmentPath,
|
|
40
42
|
splitIntoPath,
|
|
41
43
|
linkToOrigin,
|
|
@@ -59,7 +61,6 @@ function populate( model ) {
|
|
|
59
61
|
resolvePath,
|
|
60
62
|
attachAndEmitValidNames,
|
|
61
63
|
initArtifact,
|
|
62
|
-
projectionAncestor,
|
|
63
64
|
} = model.$functions;
|
|
64
65
|
model.$volatileFunctions.environment = environment;
|
|
65
66
|
Object.assign( model.$functions, {
|
|
@@ -103,8 +104,8 @@ function populate( model ) {
|
|
|
103
104
|
function traverseElementEnvironments( art ) {
|
|
104
105
|
populateView( art );
|
|
105
106
|
environment( art );
|
|
106
|
-
if (art.elements$)
|
|
107
|
-
|
|
107
|
+
if (art.elements$ || art.enum$)
|
|
108
|
+
mergeSpecifiedElementsOrEnum(art);
|
|
108
109
|
forEachMember( art, traverseElementEnvironments );
|
|
109
110
|
}
|
|
110
111
|
|
|
@@ -188,9 +189,6 @@ function populate( model ) {
|
|
|
188
189
|
setLink( a, '_effectiveType', art );
|
|
189
190
|
}
|
|
190
191
|
else {
|
|
191
|
-
let eType = art;
|
|
192
|
-
if (eType._outer)
|
|
193
|
-
eType = effectiveType( eType._outer );
|
|
194
192
|
// collect the "latest" cardinality (calculate lazily if necessary)
|
|
195
193
|
let cardinality = art.cardinality ||
|
|
196
194
|
art._effectiveType && (() => getCardinality( art._effectiveType ));
|
|
@@ -200,8 +198,8 @@ function populate( model ) {
|
|
|
200
198
|
cardinality = a.cardinality;
|
|
201
199
|
if (a.expand && expandFromColumns( a, art, cardinality ) ||
|
|
202
200
|
art.target && redirectImplicitly( a, art ) ||
|
|
203
|
-
art.elements && expandElements( a, art
|
|
204
|
-
art.items && expandItems( a, art
|
|
201
|
+
art.elements && expandElements( a, art ) ||
|
|
202
|
+
art.items && expandItems( a, art ))
|
|
205
203
|
art = a;
|
|
206
204
|
else if (art.enum && expandEnum( a, prev ))
|
|
207
205
|
prev = a; // do not set art - effective type is base
|
|
@@ -292,31 +290,32 @@ function populate( model ) {
|
|
|
292
290
|
// Expansiosn --------------------------------------------------------------
|
|
293
291
|
|
|
294
292
|
|
|
295
|
-
function expandItems( art, origin
|
|
293
|
+
function expandItems( art, origin ) {
|
|
296
294
|
if (art.items)
|
|
297
295
|
return false;
|
|
298
|
-
if (
|
|
296
|
+
if (origin.items === 0 || art.$inferred === 'expanded' && isInRecursiveExpansion( art )) {
|
|
299
297
|
art.items = 0; // circular
|
|
300
298
|
return true;
|
|
301
299
|
}
|
|
302
300
|
const ref = art.type || art.value || art.name;
|
|
303
301
|
const location = ref && ref.location || art.location;
|
|
304
|
-
art.items = { $inferred: '
|
|
302
|
+
art.items = { $inferred: 'expanded', location };
|
|
305
303
|
setLink( art.items, '_outer', art );
|
|
304
|
+
setLink( art.items, '_parent', art._parent );
|
|
306
305
|
setLink( art.items, '_origin', origin.items );
|
|
307
306
|
if (!art.$expand)
|
|
308
307
|
art.$expand = 'origin'; // if value stays, elements won't appear in CSN
|
|
309
308
|
return true;
|
|
310
309
|
}
|
|
311
310
|
|
|
312
|
-
function expandElements( art, struct
|
|
313
|
-
if (art.elements || art.kind === '$tableAlias' ||
|
|
311
|
+
function expandElements( art, struct ) {
|
|
312
|
+
if (art.elements || art.kind === '$tableAlias' || art.kind === '$inline' ||
|
|
314
313
|
// no element expansions for "non-proper" types like
|
|
315
314
|
// entities (as parameter types) etc:
|
|
316
315
|
struct.kind !== 'type' && struct.kind !== 'element' && struct.kind !== 'param' &&
|
|
317
316
|
!struct._outer)
|
|
318
317
|
return false;
|
|
319
|
-
if (struct.elements === 0 ||
|
|
318
|
+
if (struct.elements === 0 || art.$inferred === 'expanded' && isInRecursiveExpansion( art )) {
|
|
320
319
|
art.elements = 0; // circular
|
|
321
320
|
return true;
|
|
322
321
|
}
|
|
@@ -331,14 +330,14 @@ function populate( model ) {
|
|
|
331
330
|
continue;
|
|
332
331
|
linkToOrigin( orig, name, art, 'elements', weakLocation( location ), true )
|
|
333
332
|
// or should we use orig.location? - TODO: try to find test to see message
|
|
334
|
-
.$inferred = '
|
|
333
|
+
.$inferred = 'expanded';
|
|
335
334
|
}
|
|
336
335
|
// Set elements expansion status (the if condition is always true, as no
|
|
337
336
|
// elements expansion will take place on artifact with existing other
|
|
338
337
|
// member property):
|
|
339
338
|
if (!art.$expand)
|
|
340
339
|
art.$expand = 'origin'; // if value stays, elements won't appear in CSN
|
|
341
|
-
// TODO: have some art.elements[SYM.$inferred] = '
|
|
340
|
+
// TODO: have some art.elements[SYM.$inferred] = 'expanded';
|
|
342
341
|
return true;
|
|
343
342
|
}
|
|
344
343
|
|
|
@@ -352,39 +351,56 @@ function populate( model ) {
|
|
|
352
351
|
const orig = origin.enum[name];
|
|
353
352
|
linkToOrigin( orig, name, art, 'enum', location, true )
|
|
354
353
|
// or should we use orig.location? - TODO: try to find test to see message
|
|
355
|
-
.$inferred = '
|
|
354
|
+
.$inferred = 'expanded';
|
|
356
355
|
}
|
|
357
356
|
// Set elements expansion status (the if condition is always true, as no
|
|
358
357
|
// elements expansion will take place on artifact with existing other
|
|
359
358
|
// member property):
|
|
360
359
|
if (!art.$expand)
|
|
361
360
|
art.$expand = 'origin'; // if value stays, elements won't appear in CSN
|
|
362
|
-
art.enum[$inferred] = '
|
|
361
|
+
art.enum[$inferred] = 'expanded';
|
|
363
362
|
return true;
|
|
364
363
|
}
|
|
365
364
|
|
|
366
365
|
/**
|
|
367
|
-
* Return true iff `
|
|
368
|
-
*
|
|
369
|
-
* check the parents of main artifacts, as these are contexts, services or
|
|
370
|
-
* namespaces, and do not serve as type.)
|
|
366
|
+
* Return true iff `art` is from a recursive expansion, i.e. if any of its
|
|
367
|
+
* expanded parents (including _outer) has the same non-expansion-origin.
|
|
371
368
|
*/
|
|
372
|
-
function
|
|
373
|
-
|
|
369
|
+
function isInRecursiveExpansion( art ) {
|
|
370
|
+
const current = nonExpandedArtifact( art );
|
|
371
|
+
if (current.$inCycle)
|
|
374
372
|
return true;
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
373
|
+
const cycle = [ current ];
|
|
374
|
+
while (art.$inferred === 'expanded') {
|
|
375
|
+
art = outerOrParent( art );
|
|
376
|
+
const origin = nonExpandedArtifact( art );
|
|
377
|
+
cycle.push( origin );
|
|
378
|
+
if (origin.$inCycle || origin === current) {
|
|
379
|
+
for (const a of cycle)
|
|
380
|
+
a.$inCycle = true;
|
|
383
381
|
return true;
|
|
382
|
+
}
|
|
384
383
|
}
|
|
385
384
|
return false;
|
|
386
385
|
}
|
|
387
386
|
|
|
387
|
+
function outerOrParent( art ) {
|
|
388
|
+
if (art._outer)
|
|
389
|
+
return art._outer;
|
|
390
|
+
art = art._parent;
|
|
391
|
+
// TODO: think about setting _parent of elements in `items` object holding
|
|
392
|
+
// `elements`, not the most outer `items` -> return art._outer || art._parent
|
|
393
|
+
while (art.items)
|
|
394
|
+
art = art.items;
|
|
395
|
+
return art;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function nonExpandedArtifact( art ) {
|
|
399
|
+
while (art.$inferred === 'expanded')
|
|
400
|
+
art = art._origin;
|
|
401
|
+
return art;
|
|
402
|
+
}
|
|
403
|
+
|
|
388
404
|
//--------------------------------------------------------------------------
|
|
389
405
|
// Views
|
|
390
406
|
//--------------------------------------------------------------------------
|
|
@@ -442,12 +458,12 @@ function populate( model ) {
|
|
|
442
458
|
*
|
|
443
459
|
* @param art
|
|
444
460
|
*/
|
|
445
|
-
function
|
|
461
|
+
function mergeSpecifiedElementsOrEnum( art ) {
|
|
446
462
|
// Later we use specified elements as proxies to inferred of leading query
|
|
447
463
|
// (No, we probably do not.)
|
|
448
|
-
for (const id in art.elements) {
|
|
449
|
-
const ielem = art.elements[id]; // inferred element
|
|
450
|
-
const selem = art.elements$[id]; // specified element
|
|
464
|
+
for (const id in (art.elements || art.enum)) {
|
|
465
|
+
const ielem = art.elements ? art.elements[id] : art.enum[id]; // inferred element
|
|
466
|
+
const selem = art.elements$ ? art.elements$[id] : art.enum$[id]; // specified element
|
|
451
467
|
if (!selem) {
|
|
452
468
|
info( 'query-missing-element', [ ielem.name.location, art ], { id },
|
|
453
469
|
'Element $(ID) is missing in specified elements' );
|
|
@@ -463,8 +479,14 @@ function populate( model ) {
|
|
|
463
479
|
setLink(ielem, 'elements$', selem.elements);
|
|
464
480
|
delete selem.elements;
|
|
465
481
|
}
|
|
482
|
+
if (selem.enum) {
|
|
483
|
+
setLink(ielem, 'enum$', selem.enum);
|
|
484
|
+
delete selem.enum;
|
|
485
|
+
}
|
|
466
486
|
}
|
|
467
487
|
}
|
|
488
|
+
// TODO: We don't check enum$, yet! We first need to fix expansion for
|
|
489
|
+
// `cast(elem as EnumType)` (see #9421)
|
|
468
490
|
for (const id in art.elements$) {
|
|
469
491
|
const selem = art.elements$[id]; // specified element
|
|
470
492
|
if (!selem.$replacement) {
|
|
@@ -569,10 +591,6 @@ function populate( model ) {
|
|
|
569
591
|
const siblings = wildcardSiblings( columns, query );
|
|
570
592
|
expandWildcard( col, siblings, inlineHead, query );
|
|
571
593
|
}
|
|
572
|
-
if ((col.expand || col.inline) && !isBetaEnabled( options, 'nestedProjections' )) {
|
|
573
|
-
error( null, [ col.location, query ], { prop: (col.expand ? 'expand' : 'inline') },
|
|
574
|
-
'Unsupported nested $(PROP)' );
|
|
575
|
-
}
|
|
576
594
|
// If neither expression (value), expand nor new association.
|
|
577
595
|
if (!col.value && !col.expand && !(col.target && col.type))
|
|
578
596
|
continue; // error should have been reported by parser
|
|
@@ -608,10 +626,10 @@ function populate( model ) {
|
|
|
608
626
|
return '';
|
|
609
627
|
const path = col.value &&
|
|
610
628
|
(col.value.path || !col.value.args && col.value.func && col.value.func.path);
|
|
611
|
-
if (path) {
|
|
629
|
+
if (path && path.length) {
|
|
612
630
|
const last = !path.broken && path.length && path[path.length - 1];
|
|
613
631
|
if (last) {
|
|
614
|
-
col.name = { id: last.id, location: last.location, $inferred: 'as' };
|
|
632
|
+
col.name = { id: last.id || '', location: last.location, $inferred: 'as' };
|
|
615
633
|
return col.name.id;
|
|
616
634
|
}
|
|
617
635
|
}
|
|
@@ -645,18 +663,28 @@ function populate( model ) {
|
|
|
645
663
|
// console.log( message( null, elem.location, elem, {art:query}, 'Info','RED').toString(),
|
|
646
664
|
// elem.value)
|
|
647
665
|
// TODO: make this resolvePath() also part of directType() ?!
|
|
648
|
-
if (!origin)
|
|
666
|
+
if (!origin || elem.expand)
|
|
649
667
|
return;
|
|
668
|
+
// TODO: or should we push elems with `expand` sibling to extra list for better messages?
|
|
650
669
|
if (elem.foreignKeys) { // REDIRECTED with explicit foreign keys
|
|
651
670
|
forEachGeneric( elem, 'foreignKeys', (key, name) => initKey( key, name, elem ) );
|
|
652
671
|
}
|
|
653
672
|
|
|
654
673
|
// now set things which are necessary for later sub phases:
|
|
655
674
|
const nav = pathNavigation( elem.value );
|
|
656
|
-
|
|
675
|
+
const item = elem.value.path[elem.value.path.length - 1];
|
|
676
|
+
if (nav.navigation && nav.item === item) {
|
|
677
|
+
// sourceElem, alias.sourceElem, mixin:
|
|
657
678
|
// redirectImplicitly( elem, origin );
|
|
658
679
|
pushLink( nav.navigation, '_projections', elem );
|
|
659
680
|
}
|
|
681
|
+
else if (elem._pathHead?.kind === '$inline' && elem.value.path.length === 1) {
|
|
682
|
+
const hpath = elem._pathHead.value?.path;
|
|
683
|
+
const head = hpath?.length === 1 && hpath[0]._navigation;
|
|
684
|
+
// Alias .{ elem } - but not with Alias.{ $magic }: consider for ON-rewrite
|
|
685
|
+
if (head?.kind === '$tableAlias' && item.id.charAt(0) !== '$')
|
|
686
|
+
pushLink( head.elements[item.id], '_projections', elem );
|
|
687
|
+
}
|
|
660
688
|
}
|
|
661
689
|
|
|
662
690
|
function initKey( key, name, elem ) {
|
|
@@ -701,7 +729,7 @@ function populate( model ) {
|
|
|
701
729
|
// Object.keys(env),Object.keys(elements))
|
|
702
730
|
for (const name in env) {
|
|
703
731
|
const navElem = env[name];
|
|
704
|
-
// TODO:
|
|
732
|
+
// TODO: remove all access to masked (use 'grep')
|
|
705
733
|
if (excludingDict[name] || navElem.masked && navElem.masked.val)
|
|
706
734
|
continue;
|
|
707
735
|
const sibling = siblingElements[name];
|
|
@@ -770,13 +798,17 @@ function populate( model ) {
|
|
|
770
798
|
path && path[path.length - 1].id !== sibling.name.id) { // or renamed
|
|
771
799
|
const { id } = sibling.name;
|
|
772
800
|
if (Array.isArray(navElem)) {
|
|
801
|
+
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
773
802
|
info( 'wildcard-excluding-many', [ sibling.name.location, query ], { id },
|
|
774
|
-
|
|
803
|
+
// eslint-disable-next-line max-len
|
|
804
|
+
'This select item replaces $(ID) from two or more sources. Add $(ID) to $(KEYWORD) to silence this message' );
|
|
775
805
|
}
|
|
776
806
|
else {
|
|
807
|
+
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
777
808
|
info( 'wildcard-excluding-one', [ sibling.name.location, query ],
|
|
778
|
-
{ id, alias: navElem._parent.name.id },
|
|
779
|
-
|
|
809
|
+
{ id, alias: navElem._parent.name.id, keyword: 'excluding' },
|
|
810
|
+
// eslint-disable-next-line max-len
|
|
811
|
+
'This select item replaces $(ID) from table alias $(ALIAS). Add $(ID) to $(KEYWORD) to silence this message' );
|
|
780
812
|
}
|
|
781
813
|
}
|
|
782
814
|
}
|
|
@@ -787,9 +819,13 @@ function populate( model ) {
|
|
|
787
819
|
queryElem.value = { path, location }; // TODO: can we omit that? We have _origin
|
|
788
820
|
setArtifactLink( path[0], origin );
|
|
789
821
|
setLink( queryElem, '_origin', origin );
|
|
790
|
-
//
|
|
822
|
+
// set _projections when inline with table alias:
|
|
823
|
+
const alias = pathHead?.value?.path?.[0]?._navigation;
|
|
824
|
+
if (alias?.kind === '$tableAlias')
|
|
825
|
+
pushLink( alias.elements[name], '_projections', queryElem );
|
|
791
826
|
}
|
|
792
827
|
|
|
828
|
+
// called by expandWildcard():
|
|
793
829
|
function setElementOrigin( queryElem, navElem, name, location ) {
|
|
794
830
|
const sourceElem = navElem._origin;
|
|
795
831
|
const alias = navElem._parent;
|
|
@@ -892,7 +928,7 @@ function populate( model ) {
|
|
|
892
928
|
preferredElemScope( target, service, elem, assoc._main || assoc );
|
|
893
929
|
const exposed = minimalExposure( target, service, elemScope );
|
|
894
930
|
|
|
895
|
-
if (!exposed.length
|
|
931
|
+
if (!exposed.length) {
|
|
896
932
|
const origTarget = target;
|
|
897
933
|
if (isAutoExposed( target ))
|
|
898
934
|
target = createAutoExposed( origTarget, service, elemScope );
|
|
@@ -930,7 +966,7 @@ function populate( model ) {
|
|
|
930
966
|
for (const proj of exposed) {
|
|
931
967
|
// TODO: def-ambiguous-target (just v3, as the current is infamous and used in options),
|
|
932
968
|
message( 'redirected-implicitly-ambiguous',
|
|
933
|
-
[ weakLocation( proj.location ), proj ],
|
|
969
|
+
[ weakLocation( proj.name.location ), proj ],
|
|
934
970
|
{
|
|
935
971
|
'#': withAnno && 'justOne',
|
|
936
972
|
target,
|
|
@@ -989,10 +1025,12 @@ function populate( model ) {
|
|
|
989
1025
|
const targetScope = definitionScope( target );
|
|
990
1026
|
if (targetScope === assocScope) { // intra-scope in model
|
|
991
1027
|
const elemScope = definitionScope( elem._main || elem );
|
|
992
|
-
|
|
1028
|
+
// without the if, compile.recompile.json versus expected csn.json in
|
|
1029
|
+
// test3/Redirections/AutoExposeDeepScoped would fail
|
|
1030
|
+
if (targetScope === target || // model target is scope root
|
|
993
1031
|
assocScope === assocMain || // unscoped assoc source in model
|
|
994
1032
|
elemScope !== (elem._main || elem)) // scoped assoc source in service
|
|
995
|
-
return elemScope;
|
|
1033
|
+
return elemScope; // own scope, then global
|
|
996
1034
|
}
|
|
997
1035
|
if (targetScope === target) // unscoped target in model / other service
|
|
998
1036
|
return false; // all (there could be no scoped autoexposed)
|
|
@@ -1021,17 +1059,6 @@ function populate( model ) {
|
|
|
1021
1059
|
function scopedExposure( descendants, elemScope, target ) {
|
|
1022
1060
|
if (!elemScope) // no scoped redirections
|
|
1023
1061
|
return descendants;
|
|
1024
|
-
if (elemScope === true || elemScope === 'auto') {
|
|
1025
|
-
// cross-scope navigation, scoped model target, but there is no unique
|
|
1026
|
-
// redirection target for target model scope -> unsure redirection scope
|
|
1027
|
-
const unscoped = descendants.filter( d => d === definitionScope( d ) );
|
|
1028
|
-
if (unscoped.length) // use unscoped new targets if present
|
|
1029
|
-
return unscoped;
|
|
1030
|
-
// Need to filter out auto-exposed, otherwise the behavior is
|
|
1031
|
-
// processing-order dependent (not storing the autoexposed in
|
|
1032
|
-
// _descendents would only be an alternative w/o recompilation)
|
|
1033
|
-
return descendants.filter( d => !d.$generated && !annotationVal( d['@cds.autoexposed'] ) );
|
|
1034
|
-
}
|
|
1035
1062
|
// try scope as target first, even if it has @cds.redirection.target: false
|
|
1036
1063
|
if (isDirectProjection( elemScope, target ))
|
|
1037
1064
|
return [ elemScope ];
|
|
@@ -1059,7 +1086,6 @@ function populate( model ) {
|
|
|
1059
1086
|
|
|
1060
1087
|
function isDirectProjection( proj, base ) {
|
|
1061
1088
|
return proj.kind === 'entity' && // not event
|
|
1062
|
-
projectionAncestor( base, proj.params ) && // same params
|
|
1063
1089
|
// direct proj (TODO: or should we add them to another list?)
|
|
1064
1090
|
proj.query && proj.query.op && proj.query.op.val === 'SELECT' &&
|
|
1065
1091
|
proj._from && proj._from.length === 1 &&
|
|
@@ -1137,8 +1163,25 @@ function populate( model ) {
|
|
|
1137
1163
|
const autoexposed = model.definitions[absolute];
|
|
1138
1164
|
if (autoexposed && (autoexposed.kind !== 'namespace' || !scopedRedirections)) {
|
|
1139
1165
|
if (isDirectProjection( autoexposed, target )) {
|
|
1140
|
-
|
|
1141
|
-
|
|
1166
|
+
const anno = autoexposed['@cds.redirection.target'];
|
|
1167
|
+
if (annotationIsFalse( anno )) {
|
|
1168
|
+
// It would probably be cleaner to ignore a dubious
|
|
1169
|
+
// `@cds.redirection.target: false` earlier, but that is not easy to detect
|
|
1170
|
+
// due to the name of the autoexposed entity with scoped redirections
|
|
1171
|
+
if (!anno.$errorReported) {
|
|
1172
|
+
info( 'anno-redirecting-anyway',
|
|
1173
|
+
[ annotationLocation( anno ), autoexposed ],
|
|
1174
|
+
{ target, art: absolute, code: '@cds.redirection.target: false' },
|
|
1175
|
+
'$(TARGET) is auto-redirected to $(ART) even with $(CODE)' );
|
|
1176
|
+
anno.$errorReported = 'anno-redirecting-anyway';
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
else if (autoexposed._parent === service ||
|
|
1180
|
+
!annotationVal( autoexposed['@cds.autoexposed'] )) {
|
|
1181
|
+
// existing def not auto-exposed, or un-scoped auto-exposed: should not happen
|
|
1182
|
+
if (options.testMode)
|
|
1183
|
+
throw new CompilerAssertion( `Tried to auto-expose ${ target.name.absolute } twice`);
|
|
1184
|
+
}
|
|
1142
1185
|
return autoexposed;
|
|
1143
1186
|
}
|
|
1144
1187
|
error( 'duplicate-autoexposed', [ service.name.location, service ],
|
|
@@ -93,12 +93,14 @@ function propagate( model ) {
|
|
|
93
93
|
source = getOrigin( target );
|
|
94
94
|
}
|
|
95
95
|
if (source) { // the source has fully propagated properties
|
|
96
|
-
|
|
96
|
+
chain.push({ target, source });
|
|
97
97
|
}
|
|
98
98
|
else if (target._main) { // source is element, which has not inherited props yet
|
|
99
99
|
run( target._main ); // run on main artifact first
|
|
100
100
|
}
|
|
101
|
-
|
|
101
|
+
|
|
102
|
+
// Even with a query source, go through `includes`. `source` is propagated first, i.e. wins.
|
|
103
|
+
if (target.includes) {
|
|
102
104
|
let targets = [ target ];
|
|
103
105
|
while (targets.length) {
|
|
104
106
|
const news = [];
|
|
@@ -233,7 +235,7 @@ function propagate( model ) {
|
|
|
233
235
|
}
|
|
234
236
|
|
|
235
237
|
function onlyViaParent( prop, target, source ) {
|
|
236
|
-
if (target.$inferred === 'proxy' || target.$inferred === '
|
|
238
|
+
if (target.$inferred === 'proxy' || target.$inferred === 'expanded')
|
|
237
239
|
// assocs and enums do not have 'include'
|
|
238
240
|
always( prop, target, source );
|
|
239
241
|
}
|