@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
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
|
|
|
@@ -88,14 +87,18 @@ function resolve( model ) {
|
|
|
88
87
|
const {
|
|
89
88
|
resolvePath,
|
|
90
89
|
checkAnnotate,
|
|
91
|
-
|
|
90
|
+
initAnnotations,
|
|
91
|
+
copyAnnotationsForExtensions,
|
|
92
92
|
attachAndEmitValidNames,
|
|
93
93
|
lateExtensions,
|
|
94
|
+
applyTypeExtensions,
|
|
94
95
|
effectiveType,
|
|
95
96
|
directType,
|
|
96
97
|
resolveType,
|
|
97
98
|
resolveTypeArgumentsUnchecked,
|
|
98
99
|
populateQuery,
|
|
100
|
+
layeredAssignments,
|
|
101
|
+
assignmentsOfHighestLayers,
|
|
99
102
|
} = model.$functions;
|
|
100
103
|
const { environment } = model.$volatileFunctions;
|
|
101
104
|
Object.assign( model.$functions, {
|
|
@@ -168,15 +171,7 @@ function resolve( model ) {
|
|
|
168
171
|
for (const name in query.elements) {
|
|
169
172
|
const elem = query.elements[name];
|
|
170
173
|
// 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;
|
|
174
|
+
const key = !elem.$duplicates && !elem.expand && inheritedSourceKeyProp( elem );
|
|
180
175
|
if (key) {
|
|
181
176
|
if (!doIt)
|
|
182
177
|
return true;
|
|
@@ -186,6 +181,20 @@ function resolve( model ) {
|
|
|
186
181
|
return false;
|
|
187
182
|
}
|
|
188
183
|
|
|
184
|
+
function inheritedSourceKeyProp( { value, _pathHead } ) {
|
|
185
|
+
if (!value || !value.path)
|
|
186
|
+
return null;
|
|
187
|
+
const nav = pathNavigation( value );
|
|
188
|
+
const item = value.path[value.path.length - 1];
|
|
189
|
+
if (nav.navigation && nav.item === item)
|
|
190
|
+
return item._artifact?.key;
|
|
191
|
+
if (value.path.length !== 1 || _pathHead?.kind !== '$inline')
|
|
192
|
+
return null;
|
|
193
|
+
const hpath = _pathHead.value?.path;
|
|
194
|
+
const head = hpath?.length === 1 && hpath[0]._navigation;
|
|
195
|
+
return head?.kind === '$tableAlias' && item._artifact?.key;
|
|
196
|
+
}
|
|
197
|
+
|
|
189
198
|
function primarySourceNavigation( aliases ) {
|
|
190
199
|
for (const name in aliases)
|
|
191
200
|
return aliases[name].elements;
|
|
@@ -240,6 +249,7 @@ function resolve( model ) {
|
|
|
240
249
|
function selectTest( expr ) {
|
|
241
250
|
const art = withAssociation( expr, targetMaxNotOne );
|
|
242
251
|
if (art) {
|
|
252
|
+
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
243
253
|
info( 'query-navigate-many', [ art.location, query ], { art },
|
|
244
254
|
{
|
|
245
255
|
// eslint-disable-next-line max-len
|
|
@@ -314,9 +324,10 @@ function resolve( model ) {
|
|
|
314
324
|
// TODO: shouldn't be this part of populate.js ?
|
|
315
325
|
const items = {
|
|
316
326
|
location: weakLocation( (obj.type || obj).location ),
|
|
317
|
-
$inferred: '
|
|
327
|
+
$inferred: 'expanded',
|
|
318
328
|
};
|
|
319
329
|
setLink( items, '_outer', obj );
|
|
330
|
+
setLink( items, '_parent', obj._parent );
|
|
320
331
|
setLink( items, '_origin', type.items );
|
|
321
332
|
obj.items = items;
|
|
322
333
|
obj.$expand = 'origin';
|
|
@@ -361,7 +372,7 @@ function resolve( model ) {
|
|
|
361
372
|
}
|
|
362
373
|
}
|
|
363
374
|
if (obj.target) {
|
|
364
|
-
// console.log(obj.name,obj._origin
|
|
375
|
+
// console.log(obj.name,obj._origin?.name,obj)
|
|
365
376
|
if (obj._origin && obj._origin.$inferred === 'REDIRECTED')
|
|
366
377
|
resolveTarget( art, obj._origin );
|
|
367
378
|
// console.log(error( 'test-target', [ obj.location, obj ],
|
|
@@ -511,9 +522,9 @@ function resolve( model ) {
|
|
|
511
522
|
setArtifactLink( ext.name, art );
|
|
512
523
|
|
|
513
524
|
if (art) {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
525
|
+
checkAnnotate( ext, art );
|
|
526
|
+
initAnnotations( ext, ext._block, ext.kind ); // TODO: do in define.js
|
|
527
|
+
copyAnnotationsForExtensions( ext, art );
|
|
517
528
|
// eslint-disable-next-line no-shadow
|
|
518
529
|
forEachMember( ext, ( elem, name, prop ) => {
|
|
519
530
|
storeExtension( elem, name, prop, art, ext._block );
|
|
@@ -555,7 +566,7 @@ function resolve( model ) {
|
|
|
555
566
|
}
|
|
556
567
|
}
|
|
557
568
|
}
|
|
558
|
-
if (art
|
|
569
|
+
if (art?._annotate) {
|
|
559
570
|
if (art.kind === 'action' || art.kind === 'function') {
|
|
560
571
|
expandParameters( art );
|
|
561
572
|
if (art.returns)
|
|
@@ -579,6 +590,15 @@ function resolve( model ) {
|
|
|
579
590
|
// annotateMembers( env && env[n], dict[n], 'elements', n, parent, 'element' );
|
|
580
591
|
// }
|
|
581
592
|
}
|
|
593
|
+
|
|
594
|
+
if (art?._extendType) {
|
|
595
|
+
// Only works because annotateMembers is called in resolveRefs() _after_ `.type` was resolved.
|
|
596
|
+
// TODO: If we allow extending included elements, we may need custom $expand,
|
|
597
|
+
// similar to annotate above.
|
|
598
|
+
art._extendType.forEach(resolveRefs);
|
|
599
|
+
applyTypeExtensions(art);
|
|
600
|
+
}
|
|
601
|
+
|
|
582
602
|
return;
|
|
583
603
|
|
|
584
604
|
function notFound( msgId, location, address, args, validDict ) {
|
|
@@ -632,7 +652,7 @@ function resolve( model ) {
|
|
|
632
652
|
// not to create proxies
|
|
633
653
|
const orig = origin.params[name];
|
|
634
654
|
linkToOrigin( orig, name, art, 'params', weakLocation( orig.location ), true )
|
|
635
|
-
.$inferred = '
|
|
655
|
+
.$inferred = 'expanded';
|
|
636
656
|
}
|
|
637
657
|
}
|
|
638
658
|
if (!art.returns && origin.returns) {
|
|
@@ -642,7 +662,7 @@ function resolve( model ) {
|
|
|
642
662
|
name: Object.assign( {}, art.name, { id: '', param: '', location } ),
|
|
643
663
|
kind: 'param',
|
|
644
664
|
location,
|
|
645
|
-
$inferred: '
|
|
665
|
+
$inferred: 'expanded',
|
|
646
666
|
};
|
|
647
667
|
setLink( art.returns, '_parent', art );
|
|
648
668
|
setLink( art.returns, '_main', art._main || art );
|
|
@@ -751,90 +771,21 @@ function resolve( model ) {
|
|
|
751
771
|
: Object.assign( {}, scheduledAssignments[scheduledAssignments.length - 1], result );
|
|
752
772
|
}
|
|
753
773
|
|
|
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
774
|
function applyAssignment( previousAnno, anno, art, annoName ) {
|
|
775
|
+
const hasBase = previousAnno?.literal === 'array';
|
|
826
776
|
if (!previousAnno) {
|
|
827
|
-
|
|
777
|
+
const firstEllipsis = annotationHasEllipsis( anno );
|
|
778
|
+
if (!firstEllipsis)
|
|
828
779
|
return anno;
|
|
829
780
|
if (anno.$priority) { // already complained about with Define
|
|
830
|
-
|
|
831
|
-
|
|
781
|
+
const loc = firstEllipsis.location || anno.name.location;
|
|
782
|
+
message( 'anno-unexpected-ellipsis-layers', [ loc, art ], { code: '...' } );
|
|
832
783
|
}
|
|
833
784
|
previousAnno = { val: [] };
|
|
834
785
|
}
|
|
835
786
|
else if (previousAnno.literal !== 'array') {
|
|
836
|
-
|
|
837
|
-
|
|
787
|
+
// TODO: If we introduce sub-messages, point to the non-array base value.
|
|
788
|
+
error( 'anno-mismatched-ellipsis', [ anno.name.location, art ], { code: '...' } );
|
|
838
789
|
previousAnno = { val: [] };
|
|
839
790
|
}
|
|
840
791
|
const previousValue = previousAnno.val;
|
|
@@ -855,7 +806,8 @@ function resolve( model ) {
|
|
|
855
806
|
break;
|
|
856
807
|
}
|
|
857
808
|
}
|
|
858
|
-
if (upToSpec) {
|
|
809
|
+
if (upToSpec && hasBase) {
|
|
810
|
+
// non-matched UP TO; if there is no base to apply to, there is already an error.
|
|
859
811
|
warning( null, [ item.upTo.location, art ], { anno: annoName, code: '... up to' },
|
|
860
812
|
'The $(CODE) value does not match any item in the base annotation $(ANNO)' );
|
|
861
813
|
}
|
|
@@ -1045,7 +997,7 @@ function resolve( model ) {
|
|
|
1045
997
|
else if (obj.type && !obj.type.$inferred && art._parent && art._parent.kind === 'select') {
|
|
1046
998
|
// New association in views, i.e. parent is a query.
|
|
1047
999
|
error( 'query-expected-on-condition', [ obj.target.location, art ], {},
|
|
1048
|
-
'Expected
|
|
1000
|
+
'Expected ON-condition for published association' );
|
|
1049
1001
|
return; // avoid subsequent errors
|
|
1050
1002
|
}
|
|
1051
1003
|
else if (target && !obj.foreignKeys && target.kind === 'entity') {
|
|
@@ -1128,6 +1080,11 @@ function resolve( model ) {
|
|
|
1128
1080
|
'Only an association can be redirected' );
|
|
1129
1081
|
return;
|
|
1130
1082
|
}
|
|
1083
|
+
else if ((elem.value || elem.expand) && elem.type && !elem.type.$inferred) {
|
|
1084
|
+
error( 'ref-unexpected-assoc', [ elem.type.location, elem ], {},
|
|
1085
|
+
'Casting to an association is not supported' );
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1131
1088
|
// console.log(message( null, elem.location, elem, {target,art:assoc}, 'Info','RE')
|
|
1132
1089
|
// .toString(), elem.value)
|
|
1133
1090
|
const nav = elem._main && elem._main.query && elem.value && pathNavigation( elem.value );
|
|
@@ -1135,7 +1092,7 @@ function resolve( model ) {
|
|
|
1135
1092
|
if (!elem.on && origType.on) {
|
|
1136
1093
|
error( 'rewrite-not-supported', [ elem.target.location, elem ], {},
|
|
1137
1094
|
// TODO: Better text ?
|
|
1138
|
-
'The ON
|
|
1095
|
+
'The ON-condition is not rewritten here - provide an explicit ON-condition' );
|
|
1139
1096
|
return;
|
|
1140
1097
|
}
|
|
1141
1098
|
}
|
|
@@ -1148,6 +1105,7 @@ function resolve( model ) {
|
|
|
1148
1105
|
if (!elem.target.$inferred && !elem.on && !elem.foreignKeys) {
|
|
1149
1106
|
// Only a managed redirection gets this info message. Because otherwise
|
|
1150
1107
|
// we'd have to check whether on-condition/foreignKeys are the same.
|
|
1108
|
+
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
1151
1109
|
info( 'redirected-to-same', [ elem.target.location, elem ], { art: target },
|
|
1152
1110
|
'The redirected target is the original $(ART)' );
|
|
1153
1111
|
}
|
|
@@ -1162,12 +1120,17 @@ function resolve( model ) {
|
|
|
1162
1120
|
if (!from)
|
|
1163
1121
|
return; // parse error - TODO: or UNION?
|
|
1164
1122
|
if (!from.path) {
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1123
|
+
const isTarget = target === elem.target._artifact;
|
|
1124
|
+
const op = from.op?.val || target.query.op?.val;
|
|
1125
|
+
const variant = (!isTarget && 'std') || (op && 'targetOp') || 'target';
|
|
1126
|
+
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
1127
|
+
info( 'redirected-to-complex', [ elem.target.location, elem ],
|
|
1128
|
+
{ art: target, '#': variant, keyword: op || '' }, {
|
|
1129
|
+
std: 'Redirection involves the complex view $(ART)',
|
|
1130
|
+
target: 'The redirected target $(ART) is a complex view',
|
|
1131
|
+
// eslint-disable-next-line max-len
|
|
1132
|
+
targetOp: 'The redirected target $(ART) is a complex view with $(KEYWORD)',
|
|
1133
|
+
});
|
|
1171
1134
|
break;
|
|
1172
1135
|
}
|
|
1173
1136
|
target = from._artifact;
|
|
@@ -1301,13 +1264,13 @@ function resolve( model ) {
|
|
|
1301
1264
|
|
|
1302
1265
|
let variant;
|
|
1303
1266
|
if (type.builtin)
|
|
1304
|
-
|
|
1267
|
+
// `.type` is already a builtin: use a nicer message.
|
|
1305
1268
|
variant = 'builtin';
|
|
1306
1269
|
else if (effectiveTypeArt.builtin)
|
|
1307
|
-
|
|
1270
|
+
// base type is a builtin, i.e. a scalar
|
|
1308
1271
|
variant = 'type';
|
|
1309
1272
|
else
|
|
1310
|
-
|
|
1273
|
+
// effectiveType is not a builtin -> array or structured
|
|
1311
1274
|
variant = 'non-scalar';
|
|
1312
1275
|
|
|
1313
1276
|
error('type-unexpected-argument', [ artWithType[param].location, artWithType ], {
|
|
@@ -1432,7 +1395,7 @@ function resolve( model ) {
|
|
|
1432
1395
|
}
|
|
1433
1396
|
const exp = (expected === 'from') ? 'expr' : expected;
|
|
1434
1397
|
if (Array.isArray(dict)) {
|
|
1435
|
-
message( 'args-expected-named', [ dict[0] && dict[0].location || stepLocation, user ],
|
|
1398
|
+
message( 'args-expected-named', [ dict[0] && dict[0].location || stepLocation, user ], {},
|
|
1436
1399
|
'Named parameters must be provided for the entity' );
|
|
1437
1400
|
for (const a of dict)
|
|
1438
1401
|
resolveExpr( a, exp, user, extDict );
|
package/lib/compiler/shared.js
CHANGED
|
@@ -12,7 +12,6 @@ const {
|
|
|
12
12
|
setArtifactLink,
|
|
13
13
|
dependsOn,
|
|
14
14
|
pathName,
|
|
15
|
-
annotationHasEllipsis,
|
|
16
15
|
} = require('./utils');
|
|
17
16
|
|
|
18
17
|
function artifactsEnv( art ) {
|
|
@@ -87,6 +86,11 @@ function fns( model ) {
|
|
|
87
86
|
expectedMsgId: 'expected-struct',
|
|
88
87
|
envFn: artifactsEnv,
|
|
89
88
|
},
|
|
89
|
+
viewInclude: {
|
|
90
|
+
check: checkViewIncludesRef,
|
|
91
|
+
expectedMsgId: 'ref-expecting-bare-aspect',
|
|
92
|
+
envFn: artifactsEnv,
|
|
93
|
+
},
|
|
90
94
|
target: {
|
|
91
95
|
check: checkEntityRef,
|
|
92
96
|
expectedMsgId: 'expected-entity',
|
|
@@ -135,15 +139,22 @@ function fns( model ) {
|
|
|
135
139
|
dollar: true,
|
|
136
140
|
rootEnv: 'elements', // the final environment for the path root
|
|
137
141
|
noDep: true, // do not set dependency for circular-check
|
|
142
|
+
allowSelf: true,
|
|
138
143
|
}, // TODO: special assoc for only on user
|
|
139
144
|
'mixin-on': {
|
|
140
145
|
escape: 'param', // TODO: extra check that assocs containing param in ON is not published
|
|
141
146
|
next: '_$next', // TODO: lexical: ... how to find the (next) lexical environment
|
|
142
147
|
dollar: true,
|
|
143
148
|
noDep: true, // do not set dependency for circular-check
|
|
149
|
+
allowSelf: true,
|
|
144
150
|
}, // TODO: special assoc for only on user
|
|
145
151
|
rewrite: {
|
|
146
|
-
next: '_$next',
|
|
152
|
+
next: '_$next',
|
|
153
|
+
dollar: true,
|
|
154
|
+
escape: 'param',
|
|
155
|
+
noDep: true,
|
|
156
|
+
allowSelf: true,
|
|
157
|
+
rewrite: true,
|
|
147
158
|
}, // TODO: assertion that there is no next/escape used
|
|
148
159
|
'order-by': {
|
|
149
160
|
next: '_$next',
|
|
@@ -153,7 +164,11 @@ function fns( model ) {
|
|
|
153
164
|
deprecatedSourceRefs: true,
|
|
154
165
|
},
|
|
155
166
|
'order-by-union': {
|
|
156
|
-
next: '_$next',
|
|
167
|
+
next: '_$next',
|
|
168
|
+
dollar: true,
|
|
169
|
+
escape: 'param',
|
|
170
|
+
noDep: true,
|
|
171
|
+
noExt: true,
|
|
157
172
|
},
|
|
158
173
|
// expr TODO: better - on condition for assoc, other on
|
|
159
174
|
// expr TODO: write dependency, but care for $self
|
|
@@ -169,7 +184,7 @@ function fns( model ) {
|
|
|
169
184
|
resolveTypeArgumentsUnchecked,
|
|
170
185
|
resolvePath,
|
|
171
186
|
checkAnnotate,
|
|
172
|
-
|
|
187
|
+
copyAnnotationsForExtensions,
|
|
173
188
|
attachAndEmitValidNames,
|
|
174
189
|
} );
|
|
175
190
|
return;
|
|
@@ -183,7 +198,22 @@ function fns( model ) {
|
|
|
183
198
|
// - derived structure types: would have to follow type in extend/include;
|
|
184
199
|
// - entities with params: clarify inheritance, use of param in ON/DEFAULT;
|
|
185
200
|
// - query entities/events: difficult sequence of resolve steps
|
|
186
|
-
|
|
201
|
+
// - aspect without elements (useful for actions/annotations)
|
|
202
|
+
return !(art.elements && !art.query && !art.type && !art.params) && art.kind !== 'aspect';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Returns true, if the given artifact can be included by a query entity / view.
|
|
207
|
+
*
|
|
208
|
+
* We currently allow:
|
|
209
|
+
* - aspects without elements (the aspect may have actions):
|
|
210
|
+
* either no `elements` property or empty dictionary
|
|
211
|
+
*
|
|
212
|
+
* @param {XSN.Artifact} art
|
|
213
|
+
* @return {boolean}
|
|
214
|
+
*/
|
|
215
|
+
function checkViewIncludesRef( art ) {
|
|
216
|
+
return !(art.kind === 'aspect' && (!art.elements || Object.keys(art.elements).length === 0));
|
|
187
217
|
}
|
|
188
218
|
|
|
189
219
|
/**
|
|
@@ -331,7 +361,8 @@ function fns( model ) {
|
|
|
331
361
|
return setArtifactLink( ref, art );
|
|
332
362
|
}
|
|
333
363
|
else if (!spec.envFn && user._pathHead) {
|
|
334
|
-
|
|
364
|
+
if (art.kind === '$self')
|
|
365
|
+
rejectBareSelf( spec, path, user, extDict );
|
|
335
366
|
}
|
|
336
367
|
else if (art.kind === 'using') {
|
|
337
368
|
const def = model.definitions[art.name.absolute];
|
|
@@ -374,7 +405,10 @@ function fns( model ) {
|
|
|
374
405
|
// TODO: set art?
|
|
375
406
|
}
|
|
376
407
|
else if (art.kind === '$tableAlias' || art.kind === '$self') {
|
|
377
|
-
if (
|
|
408
|
+
if (art.kind === '$self') {
|
|
409
|
+
rejectBareSelf( spec, path, user, extDict );
|
|
410
|
+
}
|
|
411
|
+
else if (spec.noAliasOrMixin) {
|
|
378
412
|
// TODO: good enough for now - change later to not search for table aliases at all
|
|
379
413
|
signalNotFound( 'ref-rejected-on', [ head.location, user ], extDict && [ extDict ],
|
|
380
414
|
{ '#': 'alias', id: head.id } );
|
|
@@ -447,6 +481,15 @@ function fns( model ) {
|
|
|
447
481
|
return setArtifactLink( ref, art );
|
|
448
482
|
}
|
|
449
483
|
|
|
484
|
+
function rejectBareSelf( spec, path, user, extDict ) {
|
|
485
|
+
if (path.length === 1 && !spec.allowSelf && !user.expand && !user.inline) {
|
|
486
|
+
const head = path[0];
|
|
487
|
+
// TODO: extra text variant for JOIN-ON (if we have an extra `expected`)
|
|
488
|
+
signalNotFound( 'ref-unexpected-self', [ head.location, user ], extDict && [ extDict ],
|
|
489
|
+
{ id: head.id } );
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
450
493
|
// Issue errors for "smart" element-in-artifact references
|
|
451
494
|
// without a colon; and errors for misplaced colons in references.
|
|
452
495
|
// This function likely disappears again in cds-compiler v2.x.
|
|
@@ -534,16 +577,21 @@ function fns( model ) {
|
|
|
534
577
|
// if the artifact does not exist. Return a "fresh" artifact for
|
|
535
578
|
// non-existing external using references if `unchecked` is truthy.
|
|
536
579
|
function getPathRoot( path, spec, user, env, extDict, msgArt ) {
|
|
537
|
-
|
|
538
|
-
|
|
580
|
+
const head = path[0];
|
|
581
|
+
if (!head || !head.id || !env)
|
|
582
|
+
return undefined; // parse error
|
|
583
|
+
if (!spec.envFn && user._pathHead && head.id.charAt(0) !== '$') {
|
|
584
|
+
if (spec.rootEnv === 'elements') { // ON condition in expand/inline
|
|
585
|
+
let root = user._pathHead;
|
|
586
|
+
while (root.kind === '$inline')
|
|
587
|
+
root = root._parent;
|
|
588
|
+
return root;
|
|
589
|
+
}
|
|
539
590
|
VolatileFns.environment( user._pathHead ); // make sure _origin is set
|
|
540
591
|
return user._pathHead._origin;
|
|
541
592
|
// const { _origin } = user._pathHead;
|
|
542
593
|
// return (_origin && _origin.kind === '$tableAlias') ? _origin._origin : _origin;
|
|
543
594
|
}
|
|
544
|
-
const head = path[0];
|
|
545
|
-
if (!head || !head.id || !env)
|
|
546
|
-
return undefined; // parse error
|
|
547
595
|
// if (head.id === 'k') {console.log(Object.keys(user));throw Error(JSON.stringify(user.name))}
|
|
548
596
|
// if head._artifact is set or is null then it was already computed once
|
|
549
597
|
if ('_artifact' in head)
|
|
@@ -576,7 +624,7 @@ function fns( model ) {
|
|
|
576
624
|
message( 'ref-obsolete-parameters', [ head.location, user ],
|
|
577
625
|
{ code: `$parameters.${ path[1].id }`, newcode: `:${ path[1].id }` },
|
|
578
626
|
'Obsolete $(CODE) - replace by $(NEWCODE)' );
|
|
579
|
-
// TODO: replace it in to-csn correspondingly
|
|
627
|
+
// TODO: replace it in to-csn correspondingly !!!
|
|
580
628
|
return setArtifactLink( head, r );
|
|
581
629
|
}
|
|
582
630
|
}
|
|
@@ -665,8 +713,7 @@ function fns( model ) {
|
|
|
665
713
|
{ art: searchName( msgArt, head.id, 'element' ) } );
|
|
666
714
|
}
|
|
667
715
|
else {
|
|
668
|
-
signalNotFound( 'ref-undefined-var', [ head.location, user ], valid, { id: head.id }
|
|
669
|
-
'Element or variable $(ID) has not been found' );
|
|
716
|
+
signalNotFound( 'ref-undefined-var', [ head.location, user ], valid, { id: head.id } );
|
|
670
717
|
}
|
|
671
718
|
}
|
|
672
719
|
else if (env.$frontend && env.$frontend !== 'cdl' || spec.global) {
|
|
@@ -685,6 +732,9 @@ function fns( model ) {
|
|
|
685
732
|
// search environment (for the first path item) is `arg`. For messages about
|
|
686
733
|
// missing artifacts (as opposed to elements), provide the `head` (first
|
|
687
734
|
// element item in the path)
|
|
735
|
+
// TODO - think about setting _navigation for all $navElement – the
|
|
736
|
+
// "ref: ['tabAlias']: inline: […]" handling might be easier
|
|
737
|
+
// (no _pathHead consultation for key prop and renaming support)
|
|
688
738
|
function getPathItem( path, spec, user, artItemsCount, headArt ) {
|
|
689
739
|
// let art = (headArt && headArt.kind === '$tableAlias') ? headArt._origin : headArt;
|
|
690
740
|
let art = headArt;
|
|
@@ -796,13 +846,11 @@ function fns( model ) {
|
|
|
796
846
|
// TODO: views elements are proxies to query-0 elements, not the same
|
|
797
847
|
// TODO: better message text
|
|
798
848
|
signalNotFound( 'query-undefined-element', [ item.location, user ],
|
|
799
|
-
[ env ], { id: item.id }
|
|
800
|
-
'Element $(ID) has not been found in the elements of the query' );
|
|
849
|
+
[ env ], { id: item.id } );
|
|
801
850
|
}
|
|
802
851
|
else if (art.kind === '$parameters') {
|
|
803
852
|
signalNotFound( 'ref-undefined-param', [ item.location, user ],
|
|
804
|
-
[ env ], { art:
|
|
805
|
-
{ param: 'Entity $(ART) has no parameter $(MEMBER)' } );
|
|
853
|
+
[ env ], { art: art._main, id: item.id } );
|
|
806
854
|
}
|
|
807
855
|
else {
|
|
808
856
|
const variant = art.kind === 'aspect' && !art.name && 'aspect';
|
|
@@ -824,14 +872,13 @@ function fns( model ) {
|
|
|
824
872
|
* @param {any} location
|
|
825
873
|
* @param {object[]} valid
|
|
826
874
|
* @param {object} [textParams]
|
|
827
|
-
* @param {any} [texts]
|
|
828
875
|
*/
|
|
829
|
-
function signalNotFound(msgId, location, valid, textParams
|
|
876
|
+
function signalNotFound(msgId, location, valid, textParams ) {
|
|
830
877
|
if (location.$notFound)
|
|
831
878
|
return;
|
|
832
879
|
location.$notFound = true;
|
|
833
880
|
/** @type {object} */
|
|
834
|
-
const err = message( msgId, location, textParams
|
|
881
|
+
const err = message( msgId, location, textParams );
|
|
835
882
|
if (valid)
|
|
836
883
|
attachAndEmitValidNames(err, ...valid.reverse());
|
|
837
884
|
}
|
|
@@ -889,6 +936,8 @@ function fns( model ) {
|
|
|
889
936
|
// (TODO: really here?, probably split main artifacts vs returns)
|
|
890
937
|
// see also lateExtensions() where similar messages are reported
|
|
891
938
|
function checkAnnotate( construct, art ) {
|
|
939
|
+
// TODO: Handle extend statements properly: Different message for empty extend?
|
|
940
|
+
|
|
892
941
|
// Namespaces cannot be annotated in CSN but because they exist as XSN artifacts
|
|
893
942
|
// they can still be applied. Namespace annotations are extracted in to-csn.js
|
|
894
943
|
// In parseCdl mode USINGs and other unknown references are generated as
|
|
@@ -914,44 +963,23 @@ function fns( model ) {
|
|
|
914
963
|
}
|
|
915
964
|
}
|
|
916
965
|
|
|
917
|
-
//
|
|
918
|
-
//
|
|
919
|
-
function
|
|
920
|
-
if (
|
|
921
|
-
art.doc =
|
|
922
|
-
|
|
923
|
-
// set _block (for layering) and $priority, shallow-copy from extension
|
|
924
|
-
// TODO: think of removing $priority, then
|
|
925
|
-
// no _block: define, _block: annotate/extend/edmx
|
|
926
|
-
// would fit with extending defs with props like length
|
|
927
|
-
for (const annoProp in construct) {
|
|
966
|
+
// Copy annotations from `ext` to `art`, overwriting inferred ones.
|
|
967
|
+
// TODO: move to extend.js if not used anymore in define.js
|
|
968
|
+
function copyAnnotationsForExtensions( ext, art ) {
|
|
969
|
+
if (ext.doc)
|
|
970
|
+
art.doc = ext.doc; // e.g. through `extensions` array in CSN
|
|
971
|
+
for (const annoProp in ext) {
|
|
928
972
|
if (annoProp.charAt(0) === '@') {
|
|
929
|
-
|
|
930
|
-
if (
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
a.$priority = priority; // is now: undefined (auto-set) | false | 'annotate' | 'extend'
|
|
935
|
-
if (construct !== art)
|
|
936
|
-
addAnnotation( art, annoProp, a );
|
|
937
|
-
if (!priority && annotationHasEllipsis( a )) {
|
|
938
|
-
error( 'anno-unexpected-ellipsis',
|
|
939
|
-
[ a.name.location, art ], { code: '...' } );
|
|
940
|
-
}
|
|
941
|
-
}
|
|
973
|
+
const extAnno = ext[annoProp];
|
|
974
|
+
if (art[annoProp]?.$inferred)
|
|
975
|
+
art[annoProp] = extAnno; // overwrite $inferred annos
|
|
976
|
+
else
|
|
977
|
+
dictAddArray( art, annoProp, extAnno );
|
|
942
978
|
}
|
|
943
979
|
}
|
|
944
980
|
}
|
|
945
981
|
}
|
|
946
982
|
|
|
947
|
-
// Add annotation to definition - overwriting $inferred annos
|
|
948
|
-
function addAnnotation( art, annoProp, anno ) {
|
|
949
|
-
const old = art[annoProp];
|
|
950
|
-
if (old && old.$inferred)
|
|
951
|
-
delete art[annoProp];
|
|
952
|
-
dictAddArray( art, annoProp, anno );
|
|
953
|
-
}
|
|
954
|
-
|
|
955
983
|
module.exports = {
|
|
956
984
|
fns,
|
|
957
985
|
};
|