@sap/cds-compiler 3.1.2 → 3.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +80 -3
- package/bin/cdsc.js +1 -1
- package/doc/CHANGELOG_BETA.md +18 -0
- package/lib/api/main.js +8 -13
- package/lib/base/error.js +2 -2
- package/lib/base/keywords.js +2 -24
- package/lib/base/message-registry.js +43 -14
- package/lib/base/messages.js +20 -10
- package/lib/base/model.js +1 -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 +48 -0
- package/lib/checks/defaultValues.js +2 -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 +21 -0
- package/lib/checks/selectItems.js +1 -1
- package/lib/checks/types.js +2 -2
- package/lib/checks/utils.js +17 -7
- package/lib/checks/validator.js +26 -14
- package/lib/compiler/assert-consistency.js +13 -6
- package/lib/compiler/builtins.js +8 -0
- package/lib/compiler/checks.js +40 -33
- package/lib/compiler/define.js +50 -44
- package/lib/compiler/extend.js +303 -37
- package/lib/compiler/kick-start.js +2 -35
- package/lib/compiler/populate.js +83 -62
- package/lib/compiler/propagator.js +1 -1
- package/lib/compiler/resolve.js +61 -104
- package/lib/compiler/shared.js +16 -6
- package/lib/compiler/tweak-assocs.js +25 -12
- package/lib/compiler/utils.js +2 -2
- package/lib/edm/annotations/genericTranslation.js +3 -3
- package/lib/edm/csn2edm.js +10 -10
- 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 +4205 -4100
- package/lib/inspect/inspectModelStatistics.js +1 -1
- package/lib/inspect/inspectPropagation.js +23 -9
- package/lib/json/csnVersion.js +1 -1
- package/lib/json/from-csn.js +26 -19
- package/lib/json/to-csn.js +47 -5
- package/lib/language/antlrParser.js +1 -1
- package/lib/language/genericAntlrParser.js +29 -13
- package/lib/language/language.g4 +28 -8
- package/lib/main.d.ts +3 -6
- package/lib/model/.eslintrc.json +13 -0
- package/lib/model/api.js +4 -2
- package/lib/model/csnRefs.js +74 -47
- package/lib/model/csnUtils.js +236 -218
- package/lib/model/enrichCsn.js +41 -31
- package/lib/model/revealInternalProperties.js +61 -57
- package/lib/model/sortViews.js +31 -31
- package/lib/modelCompare/compare.js +6 -6
- package/lib/optionProcessor.js +5 -0
- package/lib/render/manageConstraints.js +2 -2
- package/lib/render/toCdl.js +31 -44
- package/lib/render/toHdbcds.js +7 -5
- package/lib/render/toRename.js +4 -4
- package/lib/render/toSql.js +11 -5
- package/lib/render/utils/common.js +20 -9
- package/lib/render/utils/sql.js +5 -5
- package/lib/transform/db/applyTransformations.js +32 -3
- package/lib/transform/db/expansion.js +81 -37
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/db/transformExists.js +1 -1
- package/lib/transform/forOdataNew.js +10 -7
- package/lib/transform/{forHanaNew.js → forRelationalDB.js} +7 -7
- package/lib/transform/localized.js +28 -19
- package/lib/transform/odata/toFinalBaseType.js +8 -11
- package/lib/transform/odata/typesExposure.js +1 -1
- package/lib/transform/transformUtilsNew.js +101 -39
- package/lib/transform/translateAssocsToJoins.js +5 -4
- package/lib/utils/moduleResolve.js +5 -5
- package/lib/utils/objectUtils.js +3 -3
- 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 +3 -3
- package/share/messages/wildcard-excluding-one.md +37 -0
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,
|
|
@@ -59,7 +58,6 @@ function populate( model ) {
|
|
|
59
58
|
resolvePath,
|
|
60
59
|
attachAndEmitValidNames,
|
|
61
60
|
initArtifact,
|
|
62
|
-
projectionAncestor,
|
|
63
61
|
} = model.$functions;
|
|
64
62
|
model.$volatileFunctions.environment = environment;
|
|
65
63
|
Object.assign( model.$functions, {
|
|
@@ -103,8 +101,8 @@ function populate( model ) {
|
|
|
103
101
|
function traverseElementEnvironments( art ) {
|
|
104
102
|
populateView( art );
|
|
105
103
|
environment( art );
|
|
106
|
-
if (art.elements$)
|
|
107
|
-
|
|
104
|
+
if (art.elements$ || art.enum$)
|
|
105
|
+
mergeSpecifiedElementsOrEnum(art);
|
|
108
106
|
forEachMember( art, traverseElementEnvironments );
|
|
109
107
|
}
|
|
110
108
|
|
|
@@ -188,9 +186,6 @@ function populate( model ) {
|
|
|
188
186
|
setLink( a, '_effectiveType', art );
|
|
189
187
|
}
|
|
190
188
|
else {
|
|
191
|
-
let eType = art;
|
|
192
|
-
if (eType._outer)
|
|
193
|
-
eType = effectiveType( eType._outer );
|
|
194
189
|
// collect the "latest" cardinality (calculate lazily if necessary)
|
|
195
190
|
let cardinality = art.cardinality ||
|
|
196
191
|
art._effectiveType && (() => getCardinality( art._effectiveType ));
|
|
@@ -200,8 +195,8 @@ function populate( model ) {
|
|
|
200
195
|
cardinality = a.cardinality;
|
|
201
196
|
if (a.expand && expandFromColumns( a, art, cardinality ) ||
|
|
202
197
|
art.target && redirectImplicitly( a, art ) ||
|
|
203
|
-
art.elements && expandElements( a, art
|
|
204
|
-
art.items && expandItems( a, art
|
|
198
|
+
art.elements && expandElements( a, art ) ||
|
|
199
|
+
art.items && expandItems( a, art ))
|
|
205
200
|
art = a;
|
|
206
201
|
else if (art.enum && expandEnum( a, prev ))
|
|
207
202
|
prev = a; // do not set art - effective type is base
|
|
@@ -292,31 +287,32 @@ function populate( model ) {
|
|
|
292
287
|
// Expansiosn --------------------------------------------------------------
|
|
293
288
|
|
|
294
289
|
|
|
295
|
-
function expandItems( art, origin
|
|
290
|
+
function expandItems( art, origin ) {
|
|
296
291
|
if (art.items)
|
|
297
292
|
return false;
|
|
298
|
-
if (
|
|
293
|
+
if (origin.items === 0 || art.$inferred === 'expanded' && isInRecursiveExpansion( art )) {
|
|
299
294
|
art.items = 0; // circular
|
|
300
295
|
return true;
|
|
301
296
|
}
|
|
302
297
|
const ref = art.type || art.value || art.name;
|
|
303
298
|
const location = ref && ref.location || art.location;
|
|
304
|
-
art.items = { $inferred: '
|
|
299
|
+
art.items = { $inferred: 'expanded', location };
|
|
305
300
|
setLink( art.items, '_outer', art );
|
|
301
|
+
setLink( art.items, '_parent', art._parent );
|
|
306
302
|
setLink( art.items, '_origin', origin.items );
|
|
307
303
|
if (!art.$expand)
|
|
308
304
|
art.$expand = 'origin'; // if value stays, elements won't appear in CSN
|
|
309
305
|
return true;
|
|
310
306
|
}
|
|
311
307
|
|
|
312
|
-
function expandElements( art, struct
|
|
313
|
-
if (art.elements || art.kind === '$tableAlias' ||
|
|
308
|
+
function expandElements( art, struct ) {
|
|
309
|
+
if (art.elements || art.kind === '$tableAlias' || art.kind === '$inline' ||
|
|
314
310
|
// no element expansions for "non-proper" types like
|
|
315
311
|
// entities (as parameter types) etc:
|
|
316
312
|
struct.kind !== 'type' && struct.kind !== 'element' && struct.kind !== 'param' &&
|
|
317
313
|
!struct._outer)
|
|
318
314
|
return false;
|
|
319
|
-
if (struct.elements === 0 ||
|
|
315
|
+
if (struct.elements === 0 || art.$inferred === 'expanded' && isInRecursiveExpansion( art )) {
|
|
320
316
|
art.elements = 0; // circular
|
|
321
317
|
return true;
|
|
322
318
|
}
|
|
@@ -331,14 +327,14 @@ function populate( model ) {
|
|
|
331
327
|
continue;
|
|
332
328
|
linkToOrigin( orig, name, art, 'elements', weakLocation( location ), true )
|
|
333
329
|
// or should we use orig.location? - TODO: try to find test to see message
|
|
334
|
-
.$inferred = '
|
|
330
|
+
.$inferred = 'expanded';
|
|
335
331
|
}
|
|
336
332
|
// Set elements expansion status (the if condition is always true, as no
|
|
337
333
|
// elements expansion will take place on artifact with existing other
|
|
338
334
|
// member property):
|
|
339
335
|
if (!art.$expand)
|
|
340
336
|
art.$expand = 'origin'; // if value stays, elements won't appear in CSN
|
|
341
|
-
// TODO: have some art.elements[SYM.$inferred] = '
|
|
337
|
+
// TODO: have some art.elements[SYM.$inferred] = 'expanded';
|
|
342
338
|
return true;
|
|
343
339
|
}
|
|
344
340
|
|
|
@@ -352,39 +348,56 @@ function populate( model ) {
|
|
|
352
348
|
const orig = origin.enum[name];
|
|
353
349
|
linkToOrigin( orig, name, art, 'enum', location, true )
|
|
354
350
|
// or should we use orig.location? - TODO: try to find test to see message
|
|
355
|
-
.$inferred = '
|
|
351
|
+
.$inferred = 'expanded';
|
|
356
352
|
}
|
|
357
353
|
// Set elements expansion status (the if condition is always true, as no
|
|
358
354
|
// elements expansion will take place on artifact with existing other
|
|
359
355
|
// member property):
|
|
360
356
|
if (!art.$expand)
|
|
361
357
|
art.$expand = 'origin'; // if value stays, elements won't appear in CSN
|
|
362
|
-
art.enum[$inferred] = '
|
|
358
|
+
art.enum[$inferred] = 'expanded';
|
|
363
359
|
return true;
|
|
364
360
|
}
|
|
365
361
|
|
|
366
362
|
/**
|
|
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.)
|
|
363
|
+
* Return true iff `art` is from a recursive expansion, i.e. if any of its
|
|
364
|
+
* expanded parents (including _outer) has the same non-expansion-origin.
|
|
371
365
|
*/
|
|
372
|
-
function
|
|
373
|
-
|
|
366
|
+
function isInRecursiveExpansion( art ) {
|
|
367
|
+
const current = nonExpandedArtifact( art );
|
|
368
|
+
if (current.$inCycle)
|
|
374
369
|
return true;
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
370
|
+
const cycle = [ current ];
|
|
371
|
+
while (art.$inferred === 'expanded') {
|
|
372
|
+
art = outerOrParent( art );
|
|
373
|
+
const origin = nonExpandedArtifact( art );
|
|
374
|
+
cycle.push( origin );
|
|
375
|
+
if (origin.$inCycle || origin === current) {
|
|
376
|
+
for (const a of cycle)
|
|
377
|
+
a.$inCycle = true;
|
|
383
378
|
return true;
|
|
379
|
+
}
|
|
384
380
|
}
|
|
385
381
|
return false;
|
|
386
382
|
}
|
|
387
383
|
|
|
384
|
+
function outerOrParent( art ) {
|
|
385
|
+
if (art._outer)
|
|
386
|
+
return art._outer;
|
|
387
|
+
art = art._parent;
|
|
388
|
+
// TODO: think about setting _parent of elements in `items` object holding
|
|
389
|
+
// `elements`, not the most outer `items` -> return art._outer || art._parent
|
|
390
|
+
while (art.items)
|
|
391
|
+
art = art.items;
|
|
392
|
+
return art;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function nonExpandedArtifact( art ) {
|
|
396
|
+
while (art.$inferred === 'expanded')
|
|
397
|
+
art = art._origin;
|
|
398
|
+
return art;
|
|
399
|
+
}
|
|
400
|
+
|
|
388
401
|
//--------------------------------------------------------------------------
|
|
389
402
|
// Views
|
|
390
403
|
//--------------------------------------------------------------------------
|
|
@@ -442,12 +455,12 @@ function populate( model ) {
|
|
|
442
455
|
*
|
|
443
456
|
* @param art
|
|
444
457
|
*/
|
|
445
|
-
function
|
|
458
|
+
function mergeSpecifiedElementsOrEnum( art ) {
|
|
446
459
|
// Later we use specified elements as proxies to inferred of leading query
|
|
447
460
|
// (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
|
|
461
|
+
for (const id in (art.elements || art.enum)) {
|
|
462
|
+
const ielem = art.elements ? art.elements[id] : art.enum[id]; // inferred element
|
|
463
|
+
const selem = art.elements$ ? art.elements$[id] : art.enum$[id]; // specified element
|
|
451
464
|
if (!selem) {
|
|
452
465
|
info( 'query-missing-element', [ ielem.name.location, art ], { id },
|
|
453
466
|
'Element $(ID) is missing in specified elements' );
|
|
@@ -463,8 +476,14 @@ function populate( model ) {
|
|
|
463
476
|
setLink(ielem, 'elements$', selem.elements);
|
|
464
477
|
delete selem.elements;
|
|
465
478
|
}
|
|
479
|
+
if (selem.enum) {
|
|
480
|
+
setLink(ielem, 'enum$', selem.enum);
|
|
481
|
+
delete selem.enum;
|
|
482
|
+
}
|
|
466
483
|
}
|
|
467
484
|
}
|
|
485
|
+
// TODO: We don't check enum$, yet! We first need to fix expansion for
|
|
486
|
+
// `cast(elem as EnumType)` (see #9421)
|
|
468
487
|
for (const id in art.elements$) {
|
|
469
488
|
const selem = art.elements$[id]; // specified element
|
|
470
489
|
if (!selem.$replacement) {
|
|
@@ -569,10 +588,6 @@ function populate( model ) {
|
|
|
569
588
|
const siblings = wildcardSiblings( columns, query );
|
|
570
589
|
expandWildcard( col, siblings, inlineHead, query );
|
|
571
590
|
}
|
|
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
591
|
// If neither expression (value), expand nor new association.
|
|
577
592
|
if (!col.value && !col.expand && !(col.target && col.type))
|
|
578
593
|
continue; // error should have been reported by parser
|
|
@@ -645,18 +660,28 @@ function populate( model ) {
|
|
|
645
660
|
// console.log( message( null, elem.location, elem, {art:query}, 'Info','RED').toString(),
|
|
646
661
|
// elem.value)
|
|
647
662
|
// TODO: make this resolvePath() also part of directType() ?!
|
|
648
|
-
if (!origin)
|
|
663
|
+
if (!origin || elem.expand)
|
|
649
664
|
return;
|
|
665
|
+
// TODO: or should we push elems with `expand` sibling to extra list for better messages?
|
|
650
666
|
if (elem.foreignKeys) { // REDIRECTED with explicit foreign keys
|
|
651
667
|
forEachGeneric( elem, 'foreignKeys', (key, name) => initKey( key, name, elem ) );
|
|
652
668
|
}
|
|
653
669
|
|
|
654
670
|
// now set things which are necessary for later sub phases:
|
|
655
671
|
const nav = pathNavigation( elem.value );
|
|
656
|
-
|
|
672
|
+
const item = elem.value.path[elem.value.path.length - 1];
|
|
673
|
+
if (nav.navigation && nav.item === item) {
|
|
674
|
+
// sourceElem, alias.sourceElem, mixin:
|
|
657
675
|
// redirectImplicitly( elem, origin );
|
|
658
676
|
pushLink( nav.navigation, '_projections', elem );
|
|
659
677
|
}
|
|
678
|
+
else if (elem._pathHead?.kind === '$inline' && elem.value.path.length === 1) {
|
|
679
|
+
const hpath = elem._pathHead.value?.path;
|
|
680
|
+
const head = hpath?.length === 1 && hpath[0]._navigation;
|
|
681
|
+
// Alias .{ elem }
|
|
682
|
+
if (head?.kind === '$tableAlias')
|
|
683
|
+
pushLink( head.elements[item.id], '_projections', elem );
|
|
684
|
+
}
|
|
660
685
|
}
|
|
661
686
|
|
|
662
687
|
function initKey( key, name, elem ) {
|
|
@@ -701,7 +726,7 @@ function populate( model ) {
|
|
|
701
726
|
// Object.keys(env),Object.keys(elements))
|
|
702
727
|
for (const name in env) {
|
|
703
728
|
const navElem = env[name];
|
|
704
|
-
// TODO:
|
|
729
|
+
// TODO: remove all access to masked (use 'grep')
|
|
705
730
|
if (excludingDict[name] || navElem.masked && navElem.masked.val)
|
|
706
731
|
continue;
|
|
707
732
|
const sibling = siblingElements[name];
|
|
@@ -770,13 +795,17 @@ function populate( model ) {
|
|
|
770
795
|
path && path[path.length - 1].id !== sibling.name.id) { // or renamed
|
|
771
796
|
const { id } = sibling.name;
|
|
772
797
|
if (Array.isArray(navElem)) {
|
|
798
|
+
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
773
799
|
info( 'wildcard-excluding-many', [ sibling.name.location, query ], { id },
|
|
774
|
-
|
|
800
|
+
// eslint-disable-next-line max-len
|
|
801
|
+
'This select item replaces $(ID) from two or more sources. Add $(ID) to $(KEYWORD) to silence this message' );
|
|
775
802
|
}
|
|
776
803
|
else {
|
|
804
|
+
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
777
805
|
info( 'wildcard-excluding-one', [ sibling.name.location, query ],
|
|
778
|
-
{ id, alias: navElem._parent.name.id },
|
|
779
|
-
|
|
806
|
+
{ id, alias: navElem._parent.name.id, keyword: 'excluding' },
|
|
807
|
+
// eslint-disable-next-line max-len
|
|
808
|
+
'This select item replaces $(ID) from table alias $(ALIAS). Add $(ID) to $(KEYWORD) to silence this message' );
|
|
780
809
|
}
|
|
781
810
|
}
|
|
782
811
|
}
|
|
@@ -787,9 +816,13 @@ function populate( model ) {
|
|
|
787
816
|
queryElem.value = { path, location }; // TODO: can we omit that? We have _origin
|
|
788
817
|
setArtifactLink( path[0], origin );
|
|
789
818
|
setLink( queryElem, '_origin', origin );
|
|
790
|
-
//
|
|
819
|
+
// set _projections when inline with table alias:
|
|
820
|
+
const alias = pathHead?.value?.path?.[0]?._navigation;
|
|
821
|
+
if (alias?.kind === '$tableAlias')
|
|
822
|
+
pushLink( alias.elements[name], '_projections', queryElem );
|
|
791
823
|
}
|
|
792
824
|
|
|
825
|
+
// called by expandWildcard():
|
|
793
826
|
function setElementOrigin( queryElem, navElem, name, location ) {
|
|
794
827
|
const sourceElem = navElem._origin;
|
|
795
828
|
const alias = navElem._parent;
|
|
@@ -892,7 +925,7 @@ function populate( model ) {
|
|
|
892
925
|
preferredElemScope( target, service, elem, assoc._main || assoc );
|
|
893
926
|
const exposed = minimalExposure( target, service, elemScope );
|
|
894
927
|
|
|
895
|
-
if (!exposed.length
|
|
928
|
+
if (!exposed.length) {
|
|
896
929
|
const origTarget = target;
|
|
897
930
|
if (isAutoExposed( target ))
|
|
898
931
|
target = createAutoExposed( origTarget, service, elemScope );
|
|
@@ -930,7 +963,7 @@ function populate( model ) {
|
|
|
930
963
|
for (const proj of exposed) {
|
|
931
964
|
// TODO: def-ambiguous-target (just v3, as the current is infamous and used in options),
|
|
932
965
|
message( 'redirected-implicitly-ambiguous',
|
|
933
|
-
[ weakLocation( proj.location ), proj ],
|
|
966
|
+
[ weakLocation( proj.name.location ), proj ],
|
|
934
967
|
{
|
|
935
968
|
'#': withAnno && 'justOne',
|
|
936
969
|
target,
|
|
@@ -1021,17 +1054,6 @@ function populate( model ) {
|
|
|
1021
1054
|
function scopedExposure( descendants, elemScope, target ) {
|
|
1022
1055
|
if (!elemScope) // no scoped redirections
|
|
1023
1056
|
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
1057
|
// try scope as target first, even if it has @cds.redirection.target: false
|
|
1036
1058
|
if (isDirectProjection( elemScope, target ))
|
|
1037
1059
|
return [ elemScope ];
|
|
@@ -1059,7 +1081,6 @@ function populate( model ) {
|
|
|
1059
1081
|
|
|
1060
1082
|
function isDirectProjection( proj, base ) {
|
|
1061
1083
|
return proj.kind === 'entity' && // not event
|
|
1062
|
-
projectionAncestor( base, proj.params ) && // same params
|
|
1063
1084
|
// direct proj (TODO: or should we add them to another list?)
|
|
1064
1085
|
proj.query && proj.query.op && proj.query.op.val === 'SELECT' &&
|
|
1065
1086
|
proj._from && proj._from.length === 1 &&
|
|
@@ -233,7 +233,7 @@ function propagate( model ) {
|
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
function onlyViaParent( prop, target, source ) {
|
|
236
|
-
if (target.$inferred === 'proxy' || target.$inferred === '
|
|
236
|
+
if (target.$inferred === 'proxy' || target.$inferred === 'expanded')
|
|
237
237
|
// assocs and enums do not have 'include'
|
|
238
238
|
always( prop, target, source );
|
|
239
239
|
}
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -69,7 +69,6 @@ const {
|
|
|
69
69
|
} = require('./utils');
|
|
70
70
|
|
|
71
71
|
const detectCycles = require('./cycle-detector');
|
|
72
|
-
const layers = require('./moduleLayers');
|
|
73
72
|
|
|
74
73
|
const $location = Symbol.for('cds.$location');
|
|
75
74
|
|
|
@@ -91,11 +90,14 @@ function resolve( model ) {
|
|
|
91
90
|
defineAnnotations,
|
|
92
91
|
attachAndEmitValidNames,
|
|
93
92
|
lateExtensions,
|
|
93
|
+
applyTypeExtensions,
|
|
94
94
|
effectiveType,
|
|
95
95
|
directType,
|
|
96
96
|
resolveType,
|
|
97
97
|
resolveTypeArgumentsUnchecked,
|
|
98
98
|
populateQuery,
|
|
99
|
+
layeredAssignments,
|
|
100
|
+
assignmentsOfHighestLayers,
|
|
99
101
|
} = model.$functions;
|
|
100
102
|
const { environment } = model.$volatileFunctions;
|
|
101
103
|
Object.assign( model.$functions, {
|
|
@@ -168,15 +170,7 @@ function resolve( model ) {
|
|
|
168
170
|
for (const name in query.elements) {
|
|
169
171
|
const elem = query.elements[name];
|
|
170
172
|
// no key prop for duplicate elements or additional specified elements:
|
|
171
|
-
|
|
172
|
-
continue;
|
|
173
|
-
const nav = pathNavigation( elem.value );
|
|
174
|
-
if (!nav.navigation)
|
|
175
|
-
continue; // undefined, expr, $magic, :const, $self (!), $self.elem
|
|
176
|
-
const { item } = nav;
|
|
177
|
-
if (item !== elem.value.path[elem.value.path.length - 1])
|
|
178
|
-
continue; // having selected a sub elem / navigated along assoc
|
|
179
|
-
const { key } = item._artifact;
|
|
173
|
+
const key = !elem.$duplicates && !elem.expand && inheritedSourceKeyProp( elem );
|
|
180
174
|
if (key) {
|
|
181
175
|
if (!doIt)
|
|
182
176
|
return true;
|
|
@@ -186,6 +180,20 @@ function resolve( model ) {
|
|
|
186
180
|
return false;
|
|
187
181
|
}
|
|
188
182
|
|
|
183
|
+
function inheritedSourceKeyProp( { value, _pathHead } ) {
|
|
184
|
+
if (!value || !value.path)
|
|
185
|
+
return null;
|
|
186
|
+
const nav = pathNavigation( value );
|
|
187
|
+
const item = value.path[value.path.length - 1];
|
|
188
|
+
if (nav.navigation && nav.item === item)
|
|
189
|
+
return item._artifact?.key;
|
|
190
|
+
if (value.path.length !== 1 || _pathHead?.kind !== '$inline')
|
|
191
|
+
return null;
|
|
192
|
+
const hpath = _pathHead.value?.path;
|
|
193
|
+
const head = hpath?.length === 1 && hpath[0]._navigation;
|
|
194
|
+
return head?.kind === '$tableAlias' && item._artifact?.key;
|
|
195
|
+
}
|
|
196
|
+
|
|
189
197
|
function primarySourceNavigation( aliases ) {
|
|
190
198
|
for (const name in aliases)
|
|
191
199
|
return aliases[name].elements;
|
|
@@ -240,6 +248,7 @@ function resolve( model ) {
|
|
|
240
248
|
function selectTest( expr ) {
|
|
241
249
|
const art = withAssociation( expr, targetMaxNotOne );
|
|
242
250
|
if (art) {
|
|
251
|
+
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
243
252
|
info( 'query-navigate-many', [ art.location, query ], { art },
|
|
244
253
|
{
|
|
245
254
|
// eslint-disable-next-line max-len
|
|
@@ -314,9 +323,10 @@ function resolve( model ) {
|
|
|
314
323
|
// TODO: shouldn't be this part of populate.js ?
|
|
315
324
|
const items = {
|
|
316
325
|
location: weakLocation( (obj.type || obj).location ),
|
|
317
|
-
$inferred: '
|
|
326
|
+
$inferred: 'expanded',
|
|
318
327
|
};
|
|
319
328
|
setLink( items, '_outer', obj );
|
|
329
|
+
setLink( items, '_parent', obj._parent );
|
|
320
330
|
setLink( items, '_origin', type.items );
|
|
321
331
|
obj.items = items;
|
|
322
332
|
obj.$expand = 'origin';
|
|
@@ -511,7 +521,7 @@ function resolve( model ) {
|
|
|
511
521
|
setArtifactLink( ext.name, art );
|
|
512
522
|
|
|
513
523
|
if (art) {
|
|
514
|
-
if (
|
|
524
|
+
if (ext.kind === 'annotate' || ext.kind === 'extend')
|
|
515
525
|
checkAnnotate( ext, art );
|
|
516
526
|
defineAnnotations( ext, art, ext._block, ext.kind );
|
|
517
527
|
// eslint-disable-next-line no-shadow
|
|
@@ -555,7 +565,7 @@ function resolve( model ) {
|
|
|
555
565
|
}
|
|
556
566
|
}
|
|
557
567
|
}
|
|
558
|
-
if (art
|
|
568
|
+
if (art?._annotate) {
|
|
559
569
|
if (art.kind === 'action' || art.kind === 'function') {
|
|
560
570
|
expandParameters( art );
|
|
561
571
|
if (art.returns)
|
|
@@ -579,6 +589,15 @@ function resolve( model ) {
|
|
|
579
589
|
// annotateMembers( env && env[n], dict[n], 'elements', n, parent, 'element' );
|
|
580
590
|
// }
|
|
581
591
|
}
|
|
592
|
+
|
|
593
|
+
if (art?._extendType) {
|
|
594
|
+
// Only works because annotateMembers is called in resolveRefs() _after_ `.type` was resolved.
|
|
595
|
+
// TODO: If we allow extending included elements, we may need custom $expand,
|
|
596
|
+
// similar to annotate above.
|
|
597
|
+
art._extendType.forEach(resolveRefs);
|
|
598
|
+
applyTypeExtensions(art);
|
|
599
|
+
}
|
|
600
|
+
|
|
582
601
|
return;
|
|
583
602
|
|
|
584
603
|
function notFound( msgId, location, address, args, validDict ) {
|
|
@@ -632,7 +651,7 @@ function resolve( model ) {
|
|
|
632
651
|
// not to create proxies
|
|
633
652
|
const orig = origin.params[name];
|
|
634
653
|
linkToOrigin( orig, name, art, 'params', weakLocation( orig.location ), true )
|
|
635
|
-
.$inferred = '
|
|
654
|
+
.$inferred = 'expanded';
|
|
636
655
|
}
|
|
637
656
|
}
|
|
638
657
|
if (!art.returns && origin.returns) {
|
|
@@ -642,7 +661,7 @@ function resolve( model ) {
|
|
|
642
661
|
name: Object.assign( {}, art.name, { id: '', param: '', location } ),
|
|
643
662
|
kind: 'param',
|
|
644
663
|
location,
|
|
645
|
-
$inferred: '
|
|
664
|
+
$inferred: 'expanded',
|
|
646
665
|
};
|
|
647
666
|
setLink( art.returns, '_parent', art );
|
|
648
667
|
setLink( art.returns, '_main', art._main || art );
|
|
@@ -751,90 +770,21 @@ function resolve( model ) {
|
|
|
751
770
|
: Object.assign( {}, scheduledAssignments[scheduledAssignments.length - 1], result );
|
|
752
771
|
}
|
|
753
772
|
|
|
754
|
-
// Group assignments by their layers. An assignment provided with a definition
|
|
755
|
-
// is considered to be provided in a layer named '', the lowest layer.
|
|
756
|
-
// TODO: make this usable for extend (elements), too =
|
|
757
|
-
// do not use $priority, make assignments on define do not have own _block
|
|
758
|
-
function layeredAssignments( assignment ) {
|
|
759
|
-
const layered = Object.create(null);
|
|
760
|
-
for (const a of assignment) {
|
|
761
|
-
const layer = a.$priority && layers.layer( a );
|
|
762
|
-
// just consider layer if Extend/Annotate, not Define
|
|
763
|
-
const name = (layer) ? layer.realname : '';
|
|
764
|
-
const done = layered[name];
|
|
765
|
-
if (done)
|
|
766
|
-
done.assignments.push( a );
|
|
767
|
-
else
|
|
768
|
-
layered[name] = { name, layer, assignments: [ a ] };
|
|
769
|
-
// TODO: file - if set: unique in layer
|
|
770
|
-
}
|
|
771
|
-
return layered;
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
// Return assignments of the highest layers.
|
|
775
|
-
// Also return whether there could be an issue:
|
|
776
|
-
// - false: there is just one assignment
|
|
777
|
-
// - 'unrelated': there is just one assignment per layer
|
|
778
|
-
// - true: there is at least one layer with two or more assignments
|
|
779
|
-
// TODO: make this usable for extend (elements), too
|
|
780
|
-
function assignmentsOfHighestLayers( layeredAnnos ) {
|
|
781
|
-
const layerNames = Object.keys( layeredAnnos );
|
|
782
|
-
// console.log('HIB:',layerNames)
|
|
783
|
-
if (layerNames.length <= 1) {
|
|
784
|
-
const name = layerNames[0];
|
|
785
|
-
const { assignments } = layeredAnnos[name] || { assignments: [] };
|
|
786
|
-
delete layeredAnnos[name];
|
|
787
|
-
return { assignments, issue: assignments.length > 1 };
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
// collect all layers which are lower than another layer
|
|
791
|
-
const allExtends = Object.create(null);
|
|
792
|
-
allExtends[''] = {}; // the "Define" layer
|
|
793
|
-
for (const name of layerNames) {
|
|
794
|
-
if (name) // not the "Define" layer
|
|
795
|
-
Object.assign( allExtends, layeredAnnos[name].layer._layerExtends );
|
|
796
|
-
}
|
|
797
|
-
// console.log('HIE:',Object.keys(allExtends))
|
|
798
|
-
const assignments = [];
|
|
799
|
-
const highest = [];
|
|
800
|
-
for (const name of layerNames) {
|
|
801
|
-
if (!(name in allExtends)) {
|
|
802
|
-
const layer = layeredAnnos[name];
|
|
803
|
-
delete layeredAnnos[name];
|
|
804
|
-
highest.push( layer );
|
|
805
|
-
assignments.push( ...layer.assignments );
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
assignments.sort( compareAssignments );
|
|
809
|
-
const good = highest.every( layer => layer.assignments.length === 1 );
|
|
810
|
-
// TODO: use layer.file instead
|
|
811
|
-
const issue = !good || highest.length > 1 && 'unrelated';
|
|
812
|
-
// console.log('HI:',highest.map(l=>l.name),issue,issue&&assignments)
|
|
813
|
-
return { assignments, issue };
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
function compareAssignments( a, b ) {
|
|
817
|
-
const fileA = layers.realname( a._block );
|
|
818
|
-
const fileB = layers.realname( b._block );
|
|
819
|
-
if (fileA !== fileB)
|
|
820
|
-
return (fileA > fileB) ? 1 : -1;
|
|
821
|
-
return (a?.location?.line || 0) - (b?.location?.line || 0) ||
|
|
822
|
-
(a?.location?.col || 0) - (b?.location?.col || 0);
|
|
823
|
-
}
|
|
824
|
-
|
|
825
773
|
function applyAssignment( previousAnno, anno, art, annoName ) {
|
|
774
|
+
const hasBase = previousAnno?.literal === 'array';
|
|
826
775
|
if (!previousAnno) {
|
|
827
|
-
|
|
776
|
+
const firstEllipsis = annotationHasEllipsis( anno );
|
|
777
|
+
if (!firstEllipsis)
|
|
828
778
|
return anno;
|
|
829
779
|
if (anno.$priority) { // already complained about with Define
|
|
830
|
-
|
|
831
|
-
|
|
780
|
+
const loc = firstEllipsis.location || anno.name.location;
|
|
781
|
+
message( 'anno-unexpected-ellipsis-layers', [ loc, art ], { code: '...' } );
|
|
832
782
|
}
|
|
833
783
|
previousAnno = { val: [] };
|
|
834
784
|
}
|
|
835
785
|
else if (previousAnno.literal !== 'array') {
|
|
836
|
-
|
|
837
|
-
|
|
786
|
+
// TODO: If we introduce sub-messages, point to the non-array base value.
|
|
787
|
+
error( 'anno-mismatched-ellipsis', [ anno.name.location, art ], { code: '...' } );
|
|
838
788
|
previousAnno = { val: [] };
|
|
839
789
|
}
|
|
840
790
|
const previousValue = previousAnno.val;
|
|
@@ -855,7 +805,8 @@ function resolve( model ) {
|
|
|
855
805
|
break;
|
|
856
806
|
}
|
|
857
807
|
}
|
|
858
|
-
if (upToSpec) {
|
|
808
|
+
if (upToSpec && hasBase) {
|
|
809
|
+
// non-matched UP TO; if there is no base to apply to, there is already an error.
|
|
859
810
|
warning( null, [ item.upTo.location, art ], { anno: annoName, code: '... up to' },
|
|
860
811
|
'The $(CODE) value does not match any item in the base annotation $(ANNO)' );
|
|
861
812
|
}
|
|
@@ -1045,7 +996,7 @@ function resolve( model ) {
|
|
|
1045
996
|
else if (obj.type && !obj.type.$inferred && art._parent && art._parent.kind === 'select') {
|
|
1046
997
|
// New association in views, i.e. parent is a query.
|
|
1047
998
|
error( 'query-expected-on-condition', [ obj.target.location, art ], {},
|
|
1048
|
-
'Expected
|
|
999
|
+
'Expected ON-condition for published association' );
|
|
1049
1000
|
return; // avoid subsequent errors
|
|
1050
1001
|
}
|
|
1051
1002
|
else if (target && !obj.foreignKeys && target.kind === 'entity') {
|
|
@@ -1135,7 +1086,7 @@ function resolve( model ) {
|
|
|
1135
1086
|
if (!elem.on && origType.on) {
|
|
1136
1087
|
error( 'rewrite-not-supported', [ elem.target.location, elem ], {},
|
|
1137
1088
|
// TODO: Better text ?
|
|
1138
|
-
'The ON
|
|
1089
|
+
'The ON-condition is not rewritten here - provide an explicit ON-condition' );
|
|
1139
1090
|
return;
|
|
1140
1091
|
}
|
|
1141
1092
|
}
|
|
@@ -1148,6 +1099,7 @@ function resolve( model ) {
|
|
|
1148
1099
|
if (!elem.target.$inferred && !elem.on && !elem.foreignKeys) {
|
|
1149
1100
|
// Only a managed redirection gets this info message. Because otherwise
|
|
1150
1101
|
// we'd have to check whether on-condition/foreignKeys are the same.
|
|
1102
|
+
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
1151
1103
|
info( 'redirected-to-same', [ elem.target.location, elem ], { art: target },
|
|
1152
1104
|
'The redirected target is the original $(ART)' );
|
|
1153
1105
|
}
|
|
@@ -1162,12 +1114,17 @@ function resolve( model ) {
|
|
|
1162
1114
|
if (!from)
|
|
1163
1115
|
return; // parse error - TODO: or UNION?
|
|
1164
1116
|
if (!from.path) {
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1117
|
+
const isTarget = target === elem.target._artifact;
|
|
1118
|
+
const op = from.op?.val || target.query.op?.val;
|
|
1119
|
+
const variant = (!isTarget && 'std') || (op && 'targetOp') || 'target';
|
|
1120
|
+
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
1121
|
+
info( 'redirected-to-complex', [ elem.target.location, elem ],
|
|
1122
|
+
{ art: target, '#': variant, keyword: op || '' }, {
|
|
1123
|
+
std: 'Redirection involves the complex view $(ART)',
|
|
1124
|
+
target: 'The redirected target $(ART) is a complex view',
|
|
1125
|
+
// eslint-disable-next-line max-len
|
|
1126
|
+
targetOp: 'The redirected target $(ART) is a complex view with $(KEYWORD)',
|
|
1127
|
+
});
|
|
1171
1128
|
break;
|
|
1172
1129
|
}
|
|
1173
1130
|
target = from._artifact;
|
|
@@ -1301,13 +1258,13 @@ function resolve( model ) {
|
|
|
1301
1258
|
|
|
1302
1259
|
let variant;
|
|
1303
1260
|
if (type.builtin)
|
|
1304
|
-
|
|
1261
|
+
// `.type` is already a builtin: use a nicer message.
|
|
1305
1262
|
variant = 'builtin';
|
|
1306
1263
|
else if (effectiveTypeArt.builtin)
|
|
1307
|
-
|
|
1264
|
+
// base type is a builtin, i.e. a scalar
|
|
1308
1265
|
variant = 'type';
|
|
1309
1266
|
else
|
|
1310
|
-
|
|
1267
|
+
// effectiveType is not a builtin -> array or structured
|
|
1311
1268
|
variant = 'non-scalar';
|
|
1312
1269
|
|
|
1313
1270
|
error('type-unexpected-argument', [ artWithType[param].location, artWithType ], {
|
|
@@ -1432,7 +1389,7 @@ function resolve( model ) {
|
|
|
1432
1389
|
}
|
|
1433
1390
|
const exp = (expected === 'from') ? 'expr' : expected;
|
|
1434
1391
|
if (Array.isArray(dict)) {
|
|
1435
|
-
message( 'args-expected-named', [ dict[0] && dict[0].location || stepLocation, user ],
|
|
1392
|
+
message( 'args-expected-named', [ dict[0] && dict[0].location || stepLocation, user ], {},
|
|
1436
1393
|
'Named parameters must be provided for the entity' );
|
|
1437
1394
|
for (const a of dict)
|
|
1438
1395
|
resolveExpr( a, exp, user, extDict );
|