@sap/cds-compiler 4.8.0 → 4.9.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 +38 -4
- package/bin/cds_remove_invalid_whitespace.js +135 -0
- package/bin/cds_update_annotations.js +180 -0
- package/bin/cds_update_identifiers.js +3 -4
- package/bin/cdsc.js +30 -17
- package/doc/CHANGELOG_BETA.md +19 -0
- package/lib/api/main.js +59 -24
- package/lib/api/options.js +12 -1
- package/lib/api/validate.js +1 -5
- package/lib/base/builtins.js +27 -0
- package/lib/base/message-registry.js +38 -21
- package/lib/base/messages.js +51 -20
- package/lib/base/model.js +4 -5
- package/lib/checks/actionsFunctions.js +2 -2
- package/lib/checks/annotationsOData.js +3 -0
- package/lib/checks/defaultValues.js +5 -2
- package/lib/checks/queryNoDbArtifacts.js +3 -2
- package/lib/checks/validator.js +2 -34
- package/lib/compiler/assert-consistency.js +10 -3
- package/lib/compiler/checks.js +44 -18
- package/lib/compiler/define.js +38 -30
- package/lib/compiler/extend.js +33 -10
- package/lib/compiler/index.js +0 -1
- package/lib/compiler/lsp-api.js +5 -0
- package/lib/compiler/populate.js +0 -2
- package/lib/compiler/propagator.js +23 -19
- package/lib/compiler/resolve.js +48 -29
- package/lib/compiler/shared.js +60 -20
- package/lib/compiler/tweak-assocs.js +72 -116
- package/lib/compiler/xpr-rewrite.js +762 -0
- package/lib/edm/annotations/edmJson.js +24 -7
- package/lib/edm/annotations/genericTranslation.js +81 -61
- package/lib/edm/edm.js +4 -4
- package/lib/edm/edmInboundChecks.js +33 -0
- package/lib/edm/edmPreprocessor.js +9 -6
- package/lib/gen/Dictionary.json +129 -14
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +1523 -1518
- package/lib/json/from-csn.js +13 -4
- package/lib/json/to-csn.js +12 -12
- package/lib/language/genericAntlrParser.js +14 -6
- package/lib/main.d.ts +67 -14
- package/lib/main.js +1 -0
- package/lib/model/cloneCsn.js +6 -3
- package/lib/model/csnRefs.js +23 -11
- package/lib/model/csnUtils.js +13 -7
- package/lib/model/enrichCsn.js +3 -1
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/model/sortViews.js +14 -6
- package/lib/modelCompare/compare.js +33 -34
- package/lib/optionProcessor.js +27 -2
- package/lib/render/DuplicateChecker.js +6 -6
- package/lib/render/manageConstraints.js +1 -0
- package/lib/render/toCdl.js +3 -1
- package/lib/transform/db/applyTransformations.js +33 -0
- package/lib/transform/db/constraints.js +75 -28
- package/lib/transform/db/expansion.js +8 -3
- package/lib/transform/db/flattening.js +2 -2
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/temporal.js +6 -3
- package/lib/transform/db/transformExists.js +2 -2
- package/lib/transform/effective/annotations.js +194 -0
- package/lib/transform/effective/main.js +6 -8
- package/lib/transform/effective/misc.js +31 -10
- package/lib/transform/forOdata.js +23 -7
- package/lib/transform/forRelationalDB.js +3 -3
- package/lib/transform/localized.js +7 -6
- package/lib/transform/odata/flattening.js +221 -124
- package/lib/transform/odata/toFinalBaseType.js +1 -1
- package/lib/transform/odata/typesExposure.js +15 -12
- package/lib/transform/parseExpr.js +4 -4
- package/lib/transform/transformUtils.js +47 -42
- package/lib/transform/translateAssocsToJoins.js +47 -47
- package/lib/transform/universalCsn/universalCsnEnricher.js +16 -19
- package/package.json +1 -1
- package/share/messages/anno-missing-rewrite.md +45 -0
- package/share/messages/message-explanations.json +1 -0
- package/bin/.eslintrc.json +0 -17
- package/lib/api/.eslintrc.json +0 -37
- package/lib/checks/.eslintrc.json +0 -31
- package/lib/compiler/.eslintrc.json +0 -8
- package/lib/edm/.eslintrc.json +0 -46
- package/lib/inspect/.eslintrc.json +0 -4
- package/lib/json/.eslintrc.json +0 -4
- package/lib/language/.eslintrc.json +0 -4
- package/lib/model/.eslintrc.json +0 -13
- package/lib/modelCompare/utils/.eslintrc.json +0 -22
- package/lib/render/.eslintrc.json +0 -22
- package/lib/transform/.eslintrc.json +0 -13
- package/lib/transform/db/.eslintrc.json +0 -41
- package/lib/transform/draft/.eslintrc.json +0 -4
- package/lib/transform/effective/.eslintrc.json +0 -4
- package/lib/transform/universalCsn/.eslintrc.json +0 -37
- package/lib/utils/.eslintrc.json +0 -7
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
const {
|
|
6
6
|
forEachGeneric,
|
|
7
|
-
forEachMember,
|
|
8
7
|
forEachInOrder,
|
|
8
|
+
isBetaEnabled,
|
|
9
9
|
} = require('../base/model');
|
|
10
10
|
const { dictLocation, weakLocation, weakRefLocation } = require('../base/location');
|
|
11
11
|
|
|
@@ -19,6 +19,7 @@ const {
|
|
|
19
19
|
traverseQueryPost,
|
|
20
20
|
traverseQueryExtra,
|
|
21
21
|
setExpandStatus,
|
|
22
|
+
getUnderlyingBuiltinType,
|
|
22
23
|
} = require('./utils');
|
|
23
24
|
const { Location } = require('../base/location');
|
|
24
25
|
const { CompilerAssertion } = require('../base/error');
|
|
@@ -33,7 +34,6 @@ function tweakAssocs( model ) {
|
|
|
33
34
|
info, warning, error,
|
|
34
35
|
} = model.$messageFunctions;
|
|
35
36
|
const {
|
|
36
|
-
resolvePath,
|
|
37
37
|
traverseExpr,
|
|
38
38
|
checkExpr,
|
|
39
39
|
checkOnCondition,
|
|
@@ -41,6 +41,12 @@ function tweakAssocs( model ) {
|
|
|
41
41
|
getOrigin,
|
|
42
42
|
} = model.$functions;
|
|
43
43
|
|
|
44
|
+
Object.assign(model.$functions, {
|
|
45
|
+
firstProjectionForPath,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const isV5preview = isBetaEnabled( model.options, 'v5preview' );
|
|
49
|
+
|
|
44
50
|
// Phase 5: rewrite associations
|
|
45
51
|
model._entities.forEach( rewriteArtifact );
|
|
46
52
|
// Think hard whether an on condition rewrite can lead to a new cyclic
|
|
@@ -78,8 +84,6 @@ function tweakAssocs( model ) {
|
|
|
78
84
|
traverseQueryPost( art.query, false, ( query ) => {
|
|
79
85
|
forEachGeneric( query, 'elements', rewriteAssociationCheck );
|
|
80
86
|
} );
|
|
81
|
-
|
|
82
|
-
checkForAnnotationRefs( art );
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
// function rewriteView( view ) {
|
|
@@ -124,90 +128,6 @@ function tweakAssocs( model ) {
|
|
|
124
128
|
}
|
|
125
129
|
}
|
|
126
130
|
|
|
127
|
-
function checkForAnnotationRefs( art ) {
|
|
128
|
-
// TODO: no check for type inheritance yet
|
|
129
|
-
const origin = art._origin ||
|
|
130
|
-
art.includes && art.includes[art.includes.length - 1]._artifact;
|
|
131
|
-
// Make sure not to waste time if no inherited annotation has checked refs:
|
|
132
|
-
if (!origin?.$contains?.$annotation)
|
|
133
|
-
return;
|
|
134
|
-
for (const prop in origin) {
|
|
135
|
-
const anno = prop.charAt(0) === '@' && !art[prop] && origin[prop];
|
|
136
|
-
// Remark: to be on the academic safe side, we should consider the
|
|
137
|
-
// annotations which are not propagated, but they never have references as
|
|
138
|
-
// value. So “no” for the moment. We also do not perform these checks in
|
|
139
|
-
// propagator.js, as it should go away in compiler v6.
|
|
140
|
-
if (anno.kind) { // i.e. with values refs
|
|
141
|
-
art[prop] = { ...anno, $inferred: 'prop' };
|
|
142
|
-
setLink( art[prop], '_outer', art );
|
|
143
|
-
const errorRef = checkAnnotationForRefs( art[prop], art );
|
|
144
|
-
if (errorRef) {
|
|
145
|
-
const valid = errorRef.path[errorRef.path.length - 1]._artifact;
|
|
146
|
-
error( 'anno-missing-rewrite', [ weakLocation( art.location ), art ], {
|
|
147
|
-
'#': (valid ? 'unrelated' : 'std'),
|
|
148
|
-
anno: prop,
|
|
149
|
-
art: origin,
|
|
150
|
-
elemref: errorRef,
|
|
151
|
-
} );
|
|
152
|
-
}
|
|
153
|
-
art.$contains ??= {};
|
|
154
|
-
art.$contains.$annotation = true;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
forEachMember( art, checkForAnnotationRefs );
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function checkAnnotationForRefs( expr, user ) {
|
|
161
|
-
if (expr.$tokenTexts)
|
|
162
|
-
return traverseExpr( expr, 'annoRewrite', user, checkAnnotationRef );
|
|
163
|
-
if (expr.literal === 'array') {
|
|
164
|
-
for (const val of expr.val) {
|
|
165
|
-
const found = checkAnnotationForRefs( val, user );
|
|
166
|
-
if (found)
|
|
167
|
-
return found;
|
|
168
|
-
}
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
if (expr.literal !== 'struct')
|
|
172
|
-
return null;
|
|
173
|
-
const struct = Object.values( expr.struct );
|
|
174
|
-
for (const val of struct) {
|
|
175
|
-
const found = checkAnnotationForRefs( val, user );
|
|
176
|
-
if (found)
|
|
177
|
-
return found;
|
|
178
|
-
}
|
|
179
|
-
return null;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function checkAnnotationRef( ref, refCtx, user ) {
|
|
183
|
-
const origPath = ref.path;
|
|
184
|
-
if (!origPath[origPath.length - 1]._artifact) // already wrong in original
|
|
185
|
-
return null;
|
|
186
|
-
ref = { ...ref, path: [ ...origPath.map( item => ({ ...item }) ) ] };
|
|
187
|
-
if (!resolvePath( ref, refCtx, user ))
|
|
188
|
-
return ref;
|
|
189
|
-
return ref.path.some( isUnrelated ) && ref;
|
|
190
|
-
|
|
191
|
-
function isUnrelated( item, idx ) {
|
|
192
|
-
let elem = item._artifact;
|
|
193
|
-
const orig = origPath[idx]._artifact;
|
|
194
|
-
// With includes, we allow shadowing: an included element might seem to be
|
|
195
|
-
// unrelated.
|
|
196
|
-
if (elem._main?.includes && elem._main === (user._main || user))
|
|
197
|
-
return false;
|
|
198
|
-
if (!elem._effectiveType) // safety
|
|
199
|
-
return false;
|
|
200
|
-
// With redirections, the originally referred object might not be the same
|
|
201
|
-
// or even the direct _origin
|
|
202
|
-
do {
|
|
203
|
-
if (elem === orig)
|
|
204
|
-
return false;
|
|
205
|
-
elem = elem._origin;
|
|
206
|
-
} while (elem);
|
|
207
|
-
return true;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
131
|
function rewriteAssociationCheck( element ) {
|
|
212
132
|
const elem = element.items || element; // TODO v5: nested items
|
|
213
133
|
if (elem.elements)
|
|
@@ -524,14 +444,39 @@ function tweakAssocs( model ) {
|
|
|
524
444
|
$inferred: 'copy',
|
|
525
445
|
};
|
|
526
446
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
447
|
+
// Published paths with filters are always associations, never
|
|
448
|
+
// compositions, hence we need to change the type to avoid type propagation.
|
|
449
|
+
const assocType = { id: 'cds.Association', location };
|
|
450
|
+
setArtifactLink( assocType, model.definitions['cds.Association'] );
|
|
451
|
+
elem.type = {
|
|
452
|
+
path: [ assocType ],
|
|
453
|
+
scope: 'global',
|
|
530
454
|
location,
|
|
531
455
|
$inferred: '$generated',
|
|
532
456
|
};
|
|
457
|
+
setArtifactLink( elem.type, assocType._artifact );
|
|
458
|
+
|
|
459
|
+
if (!isV5preview) { // TODO(v5): Remove, only use $enclosed
|
|
460
|
+
elem.$filtered = {
|
|
461
|
+
val: true,
|
|
462
|
+
literal: 'boolean',
|
|
463
|
+
location,
|
|
464
|
+
$inferred: '$generated',
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const isComp = (getUnderlyingBuiltinType( assoc )?.name?.id === 'cds.Composition');
|
|
469
|
+
if (isComp) {
|
|
470
|
+
elem.$enclosed = {
|
|
471
|
+
val: true,
|
|
472
|
+
literal: 'boolean',
|
|
473
|
+
location,
|
|
474
|
+
$inferred: '$generated',
|
|
475
|
+
};
|
|
476
|
+
}
|
|
533
477
|
}
|
|
534
478
|
|
|
479
|
+
|
|
535
480
|
/**
|
|
536
481
|
* Transform a filter on `assocPathStep` into an ON-condition.
|
|
537
482
|
* Paths inside the filter are rewritten relative to `assoc`, so they can be redirected
|
|
@@ -617,7 +562,7 @@ function tweakAssocs( model ) {
|
|
|
617
562
|
setLink( rhs, '_artifact', rhs.path[rhs.path.length - 1]._artifact );
|
|
618
563
|
|
|
619
564
|
if (elem.$syntax !== 'calc') { // different to lhs!
|
|
620
|
-
const projectedFk = firstProjectionForPath( rhs.path, nav.tableAlias, elem );
|
|
565
|
+
const projectedFk = firstProjectionForPath( rhs.path, 0, nav.tableAlias, elem );
|
|
621
566
|
rewritePath( rhs, projectedFk.item, elem, projectedFk.elem, elem.value.location );
|
|
622
567
|
}
|
|
623
568
|
|
|
@@ -672,7 +617,8 @@ function tweakAssocs( model ) {
|
|
|
672
617
|
return; // not $self or source element
|
|
673
618
|
if (expr.scope === 'param' || root.kind === '$parameters')
|
|
674
619
|
return; // are not allowed anyway - there was an error before
|
|
675
|
-
const
|
|
620
|
+
const startIndex = (root.kind === '$self' ? 1 : 0);
|
|
621
|
+
const result = firstProjectionForPath( expr.path, startIndex, tableAlias, assoc );
|
|
676
622
|
// For `assoc[…]`, ensure that we don't rewrite to another projection on `assoc`.
|
|
677
623
|
if (result.item && assoc._origin === result.item._artifact)
|
|
678
624
|
result.elem = assoc;
|
|
@@ -820,6 +766,7 @@ function tweakAssocs( model ) {
|
|
|
820
766
|
return null;
|
|
821
767
|
}
|
|
822
768
|
item.id = name;
|
|
769
|
+
// TODO: Why not break here? Test test3/scenarios/AFC/db/view/consumption/C_ScopedRole.cds
|
|
823
770
|
}
|
|
824
771
|
}
|
|
825
772
|
let env = name && elem._effectiveType; // should have been computed
|
|
@@ -857,51 +804,60 @@ function navProjection( navigation, preferred ) {
|
|
|
857
804
|
|
|
858
805
|
|
|
859
806
|
/**
|
|
860
|
-
* For a path `a.b.c.d`, return a projection for the first path item that is projected
|
|
807
|
+
* For a path `a.b.c.d`, return a projection for the first path item that is projected,
|
|
808
|
+
* starting at `startIndex` in this path using the given navigation (table alias or
|
|
809
|
+
* navigation element).
|
|
861
810
|
* For example, if a query has multiple projections such as `a.b, a, a.b.c`, the
|
|
862
811
|
* _first_ possible projection will be used and the caller can rewrite `a.b.c.d` to `b.c.d`.
|
|
863
|
-
* This avoids
|
|
812
|
+
* This avoids `extend`s affect the ON-condition.
|
|
864
813
|
*
|
|
865
|
-
* The returned object `ret` has `ret.item`, which is the path item
|
|
866
|
-
* `ret.elem` is the element projection.
|
|
814
|
+
* The returned object `ret` has `ret.item`, which is the path item at index `ret.index`
|
|
815
|
+
* that is projected. `ret.elem` is the element projection.
|
|
867
816
|
*
|
|
868
817
|
* @param {any[]} path
|
|
869
|
-
* @param {
|
|
870
|
-
* @param {object}
|
|
818
|
+
* @param {number} startIndex
|
|
819
|
+
* @param {object} nav
|
|
820
|
+
* @param {object} elem Preferred association/element that should be used if projected.
|
|
871
821
|
* @return {{elem: object, item: object}|null}
|
|
872
822
|
*/
|
|
873
|
-
function firstProjectionForPath( path,
|
|
874
|
-
|
|
875
|
-
const root = viaSelf ? 1 : 0;
|
|
876
|
-
if (root >= path.length) // e.g. just `$self` path item
|
|
823
|
+
function firstProjectionForPath( path, startIndex, nav, elem ) {
|
|
824
|
+
if (startIndex >= path.length) // e.g. just `$self` path item
|
|
877
825
|
return { item: undefined, elem: {} };
|
|
878
826
|
|
|
827
|
+
let tableAlias = nav;
|
|
828
|
+
while (tableAlias.kind === '$navElement')
|
|
829
|
+
tableAlias = tableAlias._parent;
|
|
830
|
+
|
|
879
831
|
// We want to use the _first_ valid projection that is written by the user (if the preferred
|
|
880
|
-
// `assoc` is not directly projected). To achieve that, look into the
|
|
832
|
+
// `assoc` is not directly projected). To achieve that, look into the query's elements.
|
|
881
833
|
const selectedElements = Object.values(tableAlias._parent.elements);
|
|
882
|
-
|
|
883
|
-
let
|
|
884
|
-
|
|
834
|
+
|
|
835
|
+
let proj = null;
|
|
836
|
+
let navItem = nav;
|
|
837
|
+
for (let i = startIndex; i < path.length; ++i) {
|
|
838
|
+
const item = path[i];
|
|
885
839
|
navItem = item?.id && navItem.elements?.[item.id];
|
|
886
840
|
if (!navItem) {
|
|
887
841
|
break;
|
|
888
842
|
}
|
|
889
843
|
else if (navItem._projections) {
|
|
890
|
-
const
|
|
891
|
-
if (
|
|
844
|
+
const projElem = navProjection( navItem, elem );
|
|
845
|
+
if (projElem && projElem === elem) {
|
|
892
846
|
// in case the specified association is found, _always_ use it.
|
|
893
|
-
return { item, elem };
|
|
847
|
+
return { index: i, item, elem };
|
|
894
848
|
}
|
|
895
|
-
else if (
|
|
896
|
-
const
|
|
897
|
-
proj
|
|
849
|
+
else if (projElem) {
|
|
850
|
+
const queryIndex = selectedElements.indexOf(projElem);
|
|
851
|
+
if (!proj || queryIndex < proj.queryIndex) {
|
|
852
|
+
proj = {
|
|
853
|
+
index: i, item, elem: projElem, queryIndex,
|
|
854
|
+
};
|
|
855
|
+
}
|
|
898
856
|
}
|
|
899
857
|
}
|
|
900
858
|
}
|
|
901
859
|
|
|
902
|
-
return
|
|
903
|
-
? { item: path[root], elem: null }
|
|
904
|
-
: proj.reduce( (acc, curr) => (acc.index > curr.index ? curr : acc), proj[0] ); // first
|
|
860
|
+
return proj || { index: startIndex, item: path[startIndex], elem: null };
|
|
905
861
|
}
|
|
906
862
|
|
|
907
863
|
/**
|