@sap/cds-compiler 4.3.2 → 4.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 +29 -0
- package/lib/api/main.js +14 -24
- package/lib/api/options.js +1 -0
- package/lib/api/trace.js +38 -0
- package/lib/base/location.js +46 -1
- package/lib/base/message-registry.js +68 -16
- package/lib/base/messages.js +8 -3
- package/lib/checks/.eslintrc.json +1 -0
- package/lib/checks/actionsFunctions.js +1 -1
- package/lib/checks/annotationsOData.js +2 -2
- package/lib/checks/selectItems.js +4 -1
- package/lib/compiler/assert-consistency.js +3 -2
- package/lib/compiler/base.js +1 -1
- package/lib/compiler/builtins.js +25 -1
- package/lib/compiler/checks.js +6 -5
- package/lib/compiler/define.js +12 -10
- package/lib/compiler/extend.js +22 -22
- package/lib/compiler/finalize-parse-cdl.js +1 -1
- package/lib/compiler/generate.js +70 -53
- package/lib/compiler/kick-start.js +7 -5
- package/lib/compiler/populate.js +31 -22
- package/lib/compiler/propagator.js +6 -2
- package/lib/compiler/resolve.js +52 -17
- package/lib/compiler/shared.js +74 -38
- package/lib/compiler/tweak-assocs.js +64 -23
- package/lib/compiler/utils.js +40 -23
- package/lib/edm/.eslintrc.json +2 -0
- package/lib/edm/EdmPrimitiveTypeDefinitions.js +252 -0
- package/lib/edm/annotations/edmJson.js +994 -0
- package/lib/edm/annotations/genericTranslation.js +75 -421
- package/lib/edm/annotations/vocabularyDefinitions.js +160 -0
- package/lib/edm/csn2edm.js +12 -5
- package/lib/edm/edm.js +14 -73
- package/lib/edm/edmPreprocessor.js +6 -0
- package/lib/gen/Dictionary.json +187 -16
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageLexer.interp +1 -1
- package/lib/gen/languageLexer.js +1129 -671
- package/lib/gen/languageParser.js +4285 -4283
- package/lib/json/from-csn.js +13 -18
- package/lib/json/to-csn.js +11 -6
- package/lib/language/antlrParser.js +0 -1
- package/lib/language/docCommentParser.js +1 -1
- package/lib/language/errorStrategy.js +95 -30
- package/lib/language/genericAntlrParser.js +21 -1
- package/lib/main.js +13 -3
- package/lib/model/csnRefs.js +42 -8
- package/lib/model/csnUtils.js +14 -2
- package/lib/model/enrichCsn.js +33 -5
- package/lib/model/revealInternalProperties.js +5 -0
- package/lib/modelCompare/compare.js +76 -14
- package/lib/modelCompare/utils/filter.js +19 -12
- package/lib/optionProcessor.js +2 -0
- package/lib/render/.eslintrc.json +1 -1
- package/lib/render/manageConstraints.js +1 -0
- package/lib/render/toHdbcds.js +3 -0
- package/lib/render/toRename.js +3 -1
- package/lib/render/toSql.js +46 -92
- package/lib/render/utils/common.js +76 -0
- package/lib/render/utils/delta.js +17 -3
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/db/.eslintrc.json +1 -0
- package/lib/transform/db/applyTransformations.js +30 -4
- package/lib/transform/db/associations.js +22 -10
- package/lib/transform/db/backlinks.js +6 -2
- package/lib/transform/db/expansion.js +2 -2
- package/lib/transform/db/transformExists.js +13 -39
- package/lib/transform/draft/db.js +14 -3
- package/lib/transform/draft/odata.js +5 -18
- package/lib/transform/effective/associations.js +46 -15
- package/lib/transform/effective/main.js +7 -2
- package/lib/transform/effective/misc.js +43 -24
- package/lib/transform/effective/queries.js +20 -22
- package/lib/transform/effective/types.js +6 -2
- package/lib/transform/forOdata.js +5 -2
- package/lib/transform/localized.js +1 -1
- package/lib/transform/parseExpr.js +73 -21
- package/lib/transform/translateAssocsToJoins.js +22 -15
- package/lib/utils/term.js +2 -2
- package/package.json +2 -1
package/lib/compiler/checks.js
CHANGED
|
@@ -214,6 +214,7 @@ function check( model ) {
|
|
|
214
214
|
|
|
215
215
|
// TODO: This check should be moved to localized.js
|
|
216
216
|
// "key" keyword at localized element in SELECT list.
|
|
217
|
+
// TODO: not in inferred elements, but also inside aspects
|
|
217
218
|
if (elem.key?.val && elem._main?.query) {
|
|
218
219
|
// original element is localized but not key, as that would have
|
|
219
220
|
// already resulted in a warning by localized.js
|
|
@@ -459,7 +460,7 @@ function check( model ) {
|
|
|
459
460
|
error( 'ref-unexpected-calculated', [ key.location, elem ], { '#': 'fkey' } );
|
|
460
461
|
}
|
|
461
462
|
}
|
|
462
|
-
if (elem.default) {
|
|
463
|
+
if (elem.default?.val !== undefined) {
|
|
463
464
|
if (elem.targetAspect || elem.on || fkCount !== 1) {
|
|
464
465
|
const variant = (elem.targetAspect && 'targetAspect') || (elem.on && 'onCond') || 'multi';
|
|
465
466
|
error( 'type-unexpected-default', [ elem.default.location, elem ], {
|
|
@@ -852,7 +853,7 @@ function check( model ) {
|
|
|
852
853
|
|
|
853
854
|
// Element must exist in annotation
|
|
854
855
|
if (!elementDecl) {
|
|
855
|
-
warning( null, [ anno.location || anno.name.location, art ],
|
|
856
|
+
warning( null, [ anno.location || anno.name.location, art, anno ],
|
|
856
857
|
{ name: anno.name.id, anno: annoDecl.name.id },
|
|
857
858
|
'Element $(NAME) not found for annotation $(ANNO)' );
|
|
858
859
|
return;
|
|
@@ -866,11 +867,11 @@ function check( model ) {
|
|
|
866
867
|
// Must have literal or path unless it is a boolean
|
|
867
868
|
if (!anno.literal && !anno.path && elementDecl._effectiveType?.category !== 'boolean') {
|
|
868
869
|
if (elementDecl.type?._artifact) {
|
|
869
|
-
warning( 'anno-expecting-value', [ anno.location || anno.name.location, art ],
|
|
870
|
+
warning( 'anno-expecting-value', [ anno.location || anno.name.location, art, anno ],
|
|
870
871
|
{ '#': 'type', type: elementDecl.type._artifact } );
|
|
871
872
|
}
|
|
872
873
|
else {
|
|
873
|
-
warning( 'anno-expecting-value', [ anno.location || anno.name.location, art ],
|
|
874
|
+
warning( 'anno-expecting-value', [ anno.location || anno.name.location, art, anno ],
|
|
874
875
|
{ '#': 'std', anno: anno.name.id } );
|
|
875
876
|
}
|
|
876
877
|
|
|
@@ -891,7 +892,7 @@ function check( model ) {
|
|
|
891
892
|
return;
|
|
892
893
|
|
|
893
894
|
const anno = annoDef.name.id;
|
|
894
|
-
const loc = [ value.location || value.name.location, art ];
|
|
895
|
+
const loc = [ value.location || value.name.location, art, annoDef ];
|
|
895
896
|
|
|
896
897
|
// Array expected?
|
|
897
898
|
if (elementDecl._effectiveType.items) {
|
package/lib/compiler/define.js
CHANGED
|
@@ -136,7 +136,7 @@ const {
|
|
|
136
136
|
storeExtension,
|
|
137
137
|
dependsOnSilent,
|
|
138
138
|
pathName,
|
|
139
|
-
|
|
139
|
+
targetCantBeAspect,
|
|
140
140
|
} = require('./utils');
|
|
141
141
|
const { compareLayer } = require('./moduleLayers');
|
|
142
142
|
const { initBuiltins, isInReservedNamespace } = require('./builtins');
|
|
@@ -313,7 +313,6 @@ function define( model ) {
|
|
|
313
313
|
artifacts[using] = {
|
|
314
314
|
kind: 'using', // !, not namespace - we do not know artifact yet
|
|
315
315
|
name: { id: using, location, $inferred: 'as' },
|
|
316
|
-
// TODO: use global ref (in general - all uses of splitIntoPath)
|
|
317
316
|
extern: { location, id: absolute },
|
|
318
317
|
location,
|
|
319
318
|
$inferred: 'path-prefix',
|
|
@@ -564,7 +563,7 @@ function define( model ) {
|
|
|
564
563
|
initMembers( art, art, block );
|
|
565
564
|
initDollarSelf( art ); // $self
|
|
566
565
|
if (art.params)
|
|
567
|
-
initDollarParameters( art );
|
|
566
|
+
initDollarParameters( art ); // $parameters
|
|
568
567
|
|
|
569
568
|
if (!art.query)
|
|
570
569
|
return;
|
|
@@ -1199,7 +1198,7 @@ function define( model ) {
|
|
|
1199
1198
|
|
|
1200
1199
|
// To be reworked -------------------------------------------------------------
|
|
1201
1200
|
|
|
1202
|
-
// TODO: make special for extend/annotate
|
|
1201
|
+
// TODO: is only necessary for extensions - make special for extend/annotate
|
|
1203
1202
|
function checkDefinitions( construct, parent, prop, dict = construct[prop] ) {
|
|
1204
1203
|
// TODO: do differently, see also annotateMembers() in resolver
|
|
1205
1204
|
// To have been checked by parsers:
|
|
@@ -1227,7 +1226,7 @@ function define( model ) {
|
|
|
1227
1226
|
//
|
|
1228
1227
|
else if (parent.kind === 'action' || parent.kind === 'function') {
|
|
1229
1228
|
error( 'ext-unexpected-action', [ construct.location, construct ], { '#': parent.kind }, {
|
|
1230
|
-
std: 'Actions and functions can\'t be extended, only annotated',
|
|
1229
|
+
std: 'Actions and functions can\'t be extended, only annotated', // TODO: → ext-unsupported
|
|
1231
1230
|
action: 'Actions can\'t be extended, only annotated',
|
|
1232
1231
|
function: 'Functions can\'t be extended, only annotated',
|
|
1233
1232
|
} );
|
|
@@ -1277,17 +1276,20 @@ function define( model ) {
|
|
|
1277
1276
|
|
|
1278
1277
|
/**
|
|
1279
1278
|
* Return whether the `target` is actually a `targetAspect`
|
|
1280
|
-
* TODO: really do that here and not in kick-start.js?
|
|
1281
1279
|
*/
|
|
1282
1280
|
function targetIsTargetAspect( elem ) {
|
|
1283
1281
|
const { target } = elem;
|
|
1284
|
-
if (target.elements)
|
|
1285
|
-
// TODO: error if CSN has both target.elements and targetAspect.elements
|
|
1286
|
-
// -> delete target
|
|
1282
|
+
if (target.elements) // CSN parser ensures: has no targetAspect then
|
|
1287
1283
|
return true;
|
|
1284
|
+
|
|
1285
|
+
if (elem.targetAspect) {
|
|
1286
|
+
// Ensure that a compiled CSN is parseable - not inside query, only on element
|
|
1287
|
+
return false;
|
|
1288
1288
|
}
|
|
1289
|
-
if (elem
|
|
1289
|
+
if (targetCantBeAspect( elem ) || options.parseCdl)
|
|
1290
1290
|
return false;
|
|
1291
|
+
// Compare this check with check in acceptEntity() called by resolvePath()
|
|
1292
|
+
// Remark: do not check `on` and `foreignKeys` here, we want error for those, not the aspect
|
|
1291
1293
|
const name = resolveUncheckedPath( target, 'target', elem );
|
|
1292
1294
|
const aspect = name && model.definitions[name];
|
|
1293
1295
|
return (aspect?.kind === 'aspect' || aspect?.kind === 'type') && // type is sloppy
|
package/lib/compiler/extend.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
|
-
const {
|
|
5
|
+
const { weakRefLocation } = require('../base/location');
|
|
6
6
|
const { searchName } = require('../base/messages');
|
|
7
7
|
const {
|
|
8
8
|
forEachInOrder,
|
|
@@ -686,21 +686,15 @@ function extend( model ) {
|
|
|
686
686
|
}
|
|
687
687
|
}
|
|
688
688
|
|
|
689
|
-
// const unexpected_props = {
|
|
690
|
-
// elements: 'anno-unexpected-elements',
|
|
691
|
-
// enum: 'anno-unexpected-elements', // TODO
|
|
692
|
-
// params: 'anno-unexpected-params',
|
|
693
|
-
// actions: 'anno-unexpected-actions',
|
|
694
|
-
// };
|
|
695
|
-
// const undefined_props = {
|
|
696
|
-
// elements: 'anno-undefined-element',
|
|
697
|
-
// enum: 'anno-undefined-element', // TODO
|
|
698
|
-
// params: 'anno-undefined-param',
|
|
699
|
-
// actions: 'anno-undefined-action',
|
|
700
|
-
// };
|
|
701
|
-
|
|
702
689
|
function checkRemainingMemberExtensions( parent, ext, prop, name ) {
|
|
703
690
|
// console.log('CRME:',prop,name,parent,ext)
|
|
691
|
+
|
|
692
|
+
// TODO: just use `ext-undefined-element` etc also when no elements are there
|
|
693
|
+
// at all (but use an extra text variant and the `{…}` location). Reason: we
|
|
694
|
+
// might allow to add new actions, and an `annotate` on an undefined action
|
|
695
|
+
// should not lead to another message id. We would use and extra message id
|
|
696
|
+
// if we consider this an error or such sub annotates are then ignored
|
|
697
|
+
// (i.e. not put into the "super annotate").
|
|
704
698
|
const dict = parent[prop];
|
|
705
699
|
if (!dict) {
|
|
706
700
|
// TODO: check - for each name? - better locations
|
|
@@ -714,6 +708,9 @@ function extend( model ) {
|
|
|
714
708
|
{ '#': (parent._effectiveType?.kind === 'entity') ? 'entity' : 'std' }, {
|
|
715
709
|
std: 'Elements only exist in entities, types or typed constructs',
|
|
716
710
|
entity: 'Elements of entity types can\'t be annotated',
|
|
711
|
+
// TODO: extra msg for 'entity'? → this is some other
|
|
712
|
+
// situation, somehow similar when trying to annotate elements
|
|
713
|
+
// of target entity
|
|
717
714
|
} );
|
|
718
715
|
break;
|
|
719
716
|
case 'params':
|
|
@@ -721,8 +718,8 @@ function extend( model ) {
|
|
|
721
718
|
'Parameters only exist for actions or functions' );
|
|
722
719
|
break;
|
|
723
720
|
case 'actions':
|
|
724
|
-
// TODO:
|
|
725
|
-
notFound( '
|
|
721
|
+
// TODO: use extra text variant and location of dictionary
|
|
722
|
+
notFound( 'ext-undefined-action', ext.name.location, ext,
|
|
726
723
|
{ '#': 'action', art: parent, name } );
|
|
727
724
|
break;
|
|
728
725
|
default:
|
|
@@ -737,22 +734,22 @@ function extend( model ) {
|
|
|
737
734
|
const art = inReturns || parent;
|
|
738
735
|
switch (prop) {
|
|
739
736
|
case 'elements':
|
|
740
|
-
notFound( '
|
|
737
|
+
notFound( 'ext-undefined-element', ext.name.location, ext,
|
|
741
738
|
{ '#': (inReturns ? 'returns' : 'element'), art, name },
|
|
742
739
|
parent.elements );
|
|
743
740
|
break;
|
|
744
741
|
case 'enum': // TODO: extra msg id?
|
|
745
|
-
notFound( '
|
|
742
|
+
notFound( 'ext-undefined-element', ext.name.location, ext,
|
|
746
743
|
{ '#': (inReturns ? 'enum-returns' : 'enum'), art, name },
|
|
747
744
|
parent.enum );
|
|
748
745
|
break;
|
|
749
746
|
case 'params':
|
|
750
|
-
notFound( '
|
|
747
|
+
notFound( 'ext-undefined-param', ext.name.location, ext,
|
|
751
748
|
{ '#': 'param', art: parent, name },
|
|
752
749
|
parent.params );
|
|
753
750
|
break;
|
|
754
751
|
case 'actions':
|
|
755
|
-
notFound( '
|
|
752
|
+
notFound( 'ext-undefined-action', ext.name.location, ext,
|
|
756
753
|
{ '#': 'action', art: parent, name },
|
|
757
754
|
parent.actions );
|
|
758
755
|
break;
|
|
@@ -1153,8 +1150,10 @@ function extend( model ) {
|
|
|
1153
1150
|
// Warning 'Overwrites definition from include "I" (at elem def)
|
|
1154
1151
|
const parent = ext === art && art;
|
|
1155
1152
|
const members = ext[prop];
|
|
1156
|
-
if (members)
|
|
1153
|
+
if (members) {
|
|
1157
1154
|
ext[prop] = Object.create( null );
|
|
1155
|
+
ext[prop][$location] = members[$location];
|
|
1156
|
+
}
|
|
1158
1157
|
let hasNewElement = false;
|
|
1159
1158
|
|
|
1160
1159
|
for (const ref of ext.includes) {
|
|
@@ -1162,6 +1161,7 @@ function extend( model ) {
|
|
|
1162
1161
|
if (template) { // be robust
|
|
1163
1162
|
if (template[prop] && !ext[prop])
|
|
1164
1163
|
ext[prop] = Object.create( null );
|
|
1164
|
+
const location = weakRefLocation( ref );
|
|
1165
1165
|
// eslint-disable-next-line no-loop-func
|
|
1166
1166
|
forEachInOrder( template, prop, ( origin, name ) => {
|
|
1167
1167
|
if (members && members[name]) {
|
|
@@ -1170,7 +1170,7 @@ function extend( model ) {
|
|
|
1170
1170
|
return;
|
|
1171
1171
|
}
|
|
1172
1172
|
hasNewElement = true;
|
|
1173
|
-
const elem = linkToOrigin( origin, name, parent, prop,
|
|
1173
|
+
const elem = linkToOrigin( origin, name, parent, prop, location );
|
|
1174
1174
|
setLink( elem, '_block', origin._block );
|
|
1175
1175
|
if (!parent) // not yet set for EXTEND foo WITH bar => linkToOrigin() did not add it
|
|
1176
1176
|
dictAdd( ext[prop], name, elem );
|
|
@@ -86,7 +86,7 @@ function finalizeParseCdl( model ) {
|
|
|
86
86
|
resolveUncheckedPath( include, 'include', main );
|
|
87
87
|
|
|
88
88
|
// define.js takes care that `target` is a ref and
|
|
89
|
-
// `targetAspect` is a structure.
|
|
89
|
+
// `targetAspect` is a structure (anonymous aspect) or ref to aspect.
|
|
90
90
|
if (artifact.target)
|
|
91
91
|
resolveUncheckedPath( artifact.target, 'target', main );
|
|
92
92
|
if (artifact.targetAspect)
|
package/lib/compiler/generate.js
CHANGED
|
@@ -17,7 +17,9 @@ const {
|
|
|
17
17
|
isDirectComposition,
|
|
18
18
|
copyExpr,
|
|
19
19
|
} = require('./utils');
|
|
20
|
-
const { weakLocation } = require('../base/location');
|
|
20
|
+
const { weakLocation, weakRefLocation, weakEndLocation } = require('../base/location');
|
|
21
|
+
|
|
22
|
+
const $location = Symbol.for( 'cds.$location' );
|
|
21
23
|
|
|
22
24
|
function generate( model ) {
|
|
23
25
|
const { options } = model;
|
|
@@ -220,6 +222,7 @@ function generate( model ) {
|
|
|
220
222
|
warning( 'def-ignoring-localized', [ errpos.location, elem ],
|
|
221
223
|
{ keyword: 'localized' },
|
|
222
224
|
'Keyword $(KEYWORD) is ignored for primary keys' );
|
|
225
|
+
// continuation semantics as stated: counts as key field in texts entity
|
|
223
226
|
}
|
|
224
227
|
}
|
|
225
228
|
if (textElems.length <= keys)
|
|
@@ -277,11 +280,11 @@ function generate( model ) {
|
|
|
277
280
|
* @param {boolean} fioriEnabled
|
|
278
281
|
*/
|
|
279
282
|
function createTextsEntity( base, absolute, textElems, fioriEnabled ) {
|
|
280
|
-
const
|
|
283
|
+
const location = weakLocation( base.elements[$location] || base.location );
|
|
281
284
|
const art = {
|
|
282
285
|
kind: 'entity',
|
|
283
286
|
name: { id: absolute, location },
|
|
284
|
-
location
|
|
287
|
+
location,
|
|
285
288
|
elements: Object.create( null ),
|
|
286
289
|
$inferred: 'localized-entity',
|
|
287
290
|
};
|
|
@@ -332,30 +335,8 @@ function generate( model ) {
|
|
|
332
335
|
// assertUnique array value, first entry is 'locale'
|
|
333
336
|
const assertUniqueValue = [];
|
|
334
337
|
|
|
335
|
-
for (const orig of textElems)
|
|
336
|
-
|
|
337
|
-
if (orig.key && orig.key.val) {
|
|
338
|
-
// elem.key = { val: fioriEnabled ? null : true, $inferred: 'localized', location };
|
|
339
|
-
// TODO: the previous would be better, but currently not supported in toCDL
|
|
340
|
-
if (!fioriEnabled) {
|
|
341
|
-
elem.key = { val: true, $inferred: 'localized', location };
|
|
342
|
-
// If the propagated elements remain key (that is not fiori.draft.enabled)
|
|
343
|
-
// they should be omitted from OData containment EDM
|
|
344
|
-
setAnnotation( elem, '@odata.containment.ignore', location );
|
|
345
|
-
}
|
|
346
|
-
else {
|
|
347
|
-
// add the former key paths to the unique constraint
|
|
348
|
-
assertUniqueValue.push({
|
|
349
|
-
path: [ { id: orig.name.id, location: orig.location } ],
|
|
350
|
-
location: orig.location,
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
if (hasTruthyProp( orig, 'localized' )) { // use location of LOCALIZED keyword
|
|
355
|
-
const localized = orig.localized || orig.type || orig.name;
|
|
356
|
-
elem.localized = { val: null, $inferred: 'localized', location: localized.location };
|
|
357
|
-
}
|
|
358
|
-
}
|
|
338
|
+
for (const orig of textElems)
|
|
339
|
+
addElementToTextsEntity( orig, art, fioriEnabled, assertUniqueValue );
|
|
359
340
|
|
|
360
341
|
initArtifact( art );
|
|
361
342
|
if (art.includes) {
|
|
@@ -366,7 +347,7 @@ function generate( model ) {
|
|
|
366
347
|
if (fioriEnabled) {
|
|
367
348
|
// The includes mechanism puts TextsAspect's elements before .texts' elements.
|
|
368
349
|
// Because ID_texts is not copied from TextsAspect, the order is messed
|
|
369
|
-
// up. Fix it.
|
|
350
|
+
// up. Fix it. TODO: introduce $includeAfter from Extensions.md
|
|
370
351
|
const { elements } = art;
|
|
371
352
|
art.elements = Object.create( null );
|
|
372
353
|
const names = [ 'ID_texts', 'locale', ...Object.keys( elements ) ];
|
|
@@ -385,6 +366,31 @@ function generate( model ) {
|
|
|
385
366
|
return art;
|
|
386
367
|
}
|
|
387
368
|
|
|
369
|
+
function addElementToTextsEntity( orig, art, fioriEnabled, assertUniqueValue ) {
|
|
370
|
+
const elem = linkToOrigin( orig, orig.name.id, art, 'elements' );
|
|
371
|
+
// To keep the locations of non-inferred original elements, do not set $inferred:
|
|
372
|
+
if (orig.$inferred)
|
|
373
|
+
elem.$inferred = 'localized-origin';
|
|
374
|
+
const { location } = elem;
|
|
375
|
+
if (orig.key && orig.key.val) {
|
|
376
|
+
// elem.key = { val: fioriEnabled ? null : true, $inferred: 'localized', location };
|
|
377
|
+
// TODO: the previous would be better, but currently not supported in toCDL
|
|
378
|
+
if (!fioriEnabled) {
|
|
379
|
+
elem.key = { val: true, $inferred: 'localized', location };
|
|
380
|
+
// If the propagated elements remain key (that is not fiori.draft.enabled)
|
|
381
|
+
// they should be omitted from OData containment EDM
|
|
382
|
+
setAnnotation( elem, '@odata.containment.ignore', location );
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
// add the former key paths to the unique constraint
|
|
386
|
+
assertUniqueValue.push( { path: [ { id: orig.name.id, location } ], location } );
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (hasTruthyProp( orig, 'localized' )) { // use location of LOCALIZED keyword
|
|
390
|
+
elem.localized = { val: null, $inferred: 'localized', location };
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
388
394
|
/**
|
|
389
395
|
* Enrich the `.texts` entity for the given base artifact.
|
|
390
396
|
* In contrast to createTextsEntityWithDefaultElements(), this one creates
|
|
@@ -400,14 +406,15 @@ function generate( model ) {
|
|
|
400
406
|
function enrichTextsEntityWithInclude( art, base, absolute, fioriEnabled ) {
|
|
401
407
|
const textsAspectName = 'sap.common.TextsAspect';
|
|
402
408
|
const textsAspect = model.definitions['sap.common.TextsAspect'];
|
|
403
|
-
const { location } = art
|
|
409
|
+
const { location } = art;
|
|
404
410
|
|
|
405
|
-
art.includes = [ createInclude( textsAspectName,
|
|
411
|
+
art.includes = [ createInclude( textsAspectName, location ) ];
|
|
406
412
|
|
|
407
413
|
if (fioriEnabled) {
|
|
408
414
|
// "Early" include; only for element `locale`, which has its `key` property
|
|
409
415
|
// removed (or rather: it is not copied).
|
|
410
416
|
linkToOrigin( textsAspect.elements.locale, 'locale', art, 'elements', location );
|
|
417
|
+
art.elements.locale.$inferred = 'localized';
|
|
411
418
|
}
|
|
412
419
|
|
|
413
420
|
if (addTextsLanguageAssoc && art.elements.language)
|
|
@@ -425,12 +432,13 @@ function generate( model ) {
|
|
|
425
432
|
// If there is a type `sap.common.Locale`, then use it as the type for the element `locale`.
|
|
426
433
|
// If not, use the default `cds.String` with a length of 14.
|
|
427
434
|
const hasLocaleType = model.definitions['sap.common.Locale']?.kind === 'type';
|
|
428
|
-
const { location } = art
|
|
435
|
+
const { location } = art; // is already a weak location
|
|
429
436
|
const locale = {
|
|
430
437
|
name: { location, id: 'locale' },
|
|
431
438
|
kind: 'element',
|
|
432
439
|
type: linkMainArtifact( location, hasLocaleType ? 'sap.common.Locale' : 'cds.String' ),
|
|
433
440
|
location,
|
|
441
|
+
$inferred: 'localized', // $generated in Universal CSN, no $location
|
|
434
442
|
};
|
|
435
443
|
if (!hasLocaleType)
|
|
436
444
|
locale.length = { literal: 'number', val: 14, location };
|
|
@@ -449,7 +457,7 @@ function generate( model ) {
|
|
|
449
457
|
// texts : Composition of many Books.texts on texts.ID=ID;
|
|
450
458
|
/** @type {array} */
|
|
451
459
|
const keys = textElems.filter( e => e.key && e.key.val );
|
|
452
|
-
const
|
|
460
|
+
const location = weakEndLocation( art.elements[$location] ) || weakLocation( art.location );
|
|
453
461
|
const texts = {
|
|
454
462
|
name: { location, id: 'texts' },
|
|
455
463
|
kind: 'element',
|
|
@@ -568,7 +576,7 @@ function generate( model ) {
|
|
|
568
576
|
while (origin && !origin.targetAspect && origin._origin)
|
|
569
577
|
origin = origin._origin;
|
|
570
578
|
let target = origin.targetAspect;
|
|
571
|
-
if (target
|
|
579
|
+
if (target?.path)
|
|
572
580
|
target = resolvePath( origin.targetAspect, 'targetAspect', origin );
|
|
573
581
|
if (!target || !target.elements)
|
|
574
582
|
return;
|
|
@@ -601,7 +609,7 @@ function generate( model ) {
|
|
|
601
609
|
function allowAspectComposition( target, elem, keys, entityName ) {
|
|
602
610
|
if (!target.elements || Object.values( target.elements ).some( e => e.$duplicates ))
|
|
603
611
|
return false; // no elements or with redefinitions
|
|
604
|
-
const location = elem.
|
|
612
|
+
const location = elem.targetAspect?.location || elem.location;
|
|
605
613
|
if ((elem._main._upperAspects || []).includes( target ))
|
|
606
614
|
return 0; // circular containment of the same aspect
|
|
607
615
|
|
|
@@ -648,16 +656,22 @@ function generate( model ) {
|
|
|
648
656
|
if (elem.type && !isDirectComposition( elem )) {
|
|
649
657
|
// Only issue warning for direct usages, not for projections, includes, etc.
|
|
650
658
|
// TODO: Make it configurable error; v5: error
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
'
|
|
659
|
+
// TODO: move to resolve.js where we test the targetAspect,
|
|
660
|
+
warning( 'type-expecting-composition', [ elem.type.location, elem ],
|
|
661
|
+
{ newcode: 'Composition of', code: 'Association to' },
|
|
662
|
+
'Expecting $(NEWCODE), not $(CODE) for the anonymous target aspect' );
|
|
663
|
+
// auto-correct to avoid additional error 'type-unexpected-target-aspect' if
|
|
664
|
+
// cds.Association:
|
|
665
|
+
const { path, $inferred } = elem.type;
|
|
666
|
+
if (!$inferred && path?.length === 1 && path[0].id === 'cds.Association')
|
|
667
|
+
path[0].id = 'cds.Composition';
|
|
654
668
|
}
|
|
655
669
|
|
|
656
670
|
return true;
|
|
657
671
|
}
|
|
658
672
|
|
|
659
673
|
function createTargetEntity( target, elem, keys, entityName, base ) {
|
|
660
|
-
const
|
|
674
|
+
const location = weakRefLocation( elem.targetAspect || elem.target || elem );
|
|
661
675
|
elem.on = {
|
|
662
676
|
location,
|
|
663
677
|
op: { val: '=', location },
|
|
@@ -673,7 +687,7 @@ function generate( model ) {
|
|
|
673
687
|
name: {
|
|
674
688
|
id: entityName,
|
|
675
689
|
// for code navigation (e.g. via `extend`s): point to the element's name
|
|
676
|
-
location: elem.name.location,
|
|
690
|
+
location: weakLocation( elem.name.location ),
|
|
677
691
|
},
|
|
678
692
|
location,
|
|
679
693
|
elements: Object.create( null ),
|
|
@@ -690,18 +704,17 @@ function generate( model ) {
|
|
|
690
704
|
}
|
|
691
705
|
|
|
692
706
|
// Since there is no user-written up_ element, use a weak location to the beginning of {…}.
|
|
693
|
-
const upLocation = weakLocation( location );
|
|
694
707
|
const up = { // elements.up_ = ...
|
|
695
|
-
name: { location
|
|
708
|
+
name: { location, id: 'up_' },
|
|
696
709
|
kind: 'element',
|
|
697
|
-
location
|
|
710
|
+
location,
|
|
698
711
|
$inferred: 'aspect-composition',
|
|
699
|
-
type: linkMainArtifact(
|
|
700
|
-
target: linkMainArtifact(
|
|
712
|
+
type: linkMainArtifact( location, 'cds.Association' ),
|
|
713
|
+
target: linkMainArtifact( location, base.name.id ),
|
|
701
714
|
cardinality: {
|
|
702
|
-
targetMin: { val: 1, literal: 'number', location
|
|
703
|
-
targetMax: { val: 1, literal: 'number', location
|
|
704
|
-
location
|
|
715
|
+
targetMin: { val: 1, literal: 'number', location },
|
|
716
|
+
targetMax: { val: 1, literal: 'number', location },
|
|
717
|
+
location,
|
|
705
718
|
},
|
|
706
719
|
};
|
|
707
720
|
// By default, 'up_' is a managed primary key association.
|
|
@@ -710,18 +723,21 @@ function generate( model ) {
|
|
|
710
723
|
if (isDeprecatedEnabled( options, '_unmanagedUpInComponent' )) {
|
|
711
724
|
addProxyElements( art, keys, 'aspect-composition', target.name && location,
|
|
712
725
|
'up__', '@odata.containment.ignore' );
|
|
713
|
-
up.on = augmentEqual(
|
|
726
|
+
up.on = augmentEqual( location, 'up_', Object.values( keys ), 'up__' );
|
|
714
727
|
}
|
|
715
728
|
else {
|
|
716
|
-
up.key = { location
|
|
729
|
+
up.key = { location, val: true };
|
|
717
730
|
// managed associations must be explicitly set to not null
|
|
718
731
|
// even if target cardinality is 1..1
|
|
719
|
-
up.notNull = { location
|
|
732
|
+
up.notNull = { location, val: true };
|
|
720
733
|
}
|
|
721
734
|
|
|
722
735
|
dictAdd( art.elements, 'up_', up );
|
|
723
736
|
// Only for named aspects, use a new location; otherwise use the origin's one.
|
|
724
|
-
|
|
737
|
+
|
|
738
|
+
// To keep the locations of non-inferred original elements, do not set $inferred:
|
|
739
|
+
const enforceLocation = target.name || elem.$inferred;
|
|
740
|
+
addProxyElements( art, target.elements, 'aspect-composition', enforceLocation && location );
|
|
725
741
|
|
|
726
742
|
setLink( art, '_block', model.$internal );
|
|
727
743
|
model.definitions[entityName] = art;
|
|
@@ -742,7 +758,8 @@ function generate( model ) {
|
|
|
742
758
|
const origin = elements[name];
|
|
743
759
|
const proxy = linkToOrigin( origin, pname, null, null, location, true );
|
|
744
760
|
setLink( proxy, '_block', origin._block );
|
|
745
|
-
|
|
761
|
+
if (location)
|
|
762
|
+
proxy.$inferred = inferred;
|
|
746
763
|
if (origin.masked)
|
|
747
764
|
proxy.masked = Object.assign( { $inferred: 'include' }, origin.masked );
|
|
748
765
|
if (origin.key)
|
|
@@ -784,7 +801,7 @@ function generate( model ) {
|
|
|
784
801
|
}
|
|
785
802
|
|
|
786
803
|
function linkMainArtifact( location, absolute ) {
|
|
787
|
-
const r = { location };
|
|
804
|
+
const r = { location, path: [ { id: absolute, location } ] };
|
|
788
805
|
setArtifactLink( r, model.definitions[absolute] );
|
|
789
806
|
return r;
|
|
790
807
|
}
|
|
@@ -3,7 +3,12 @@
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
5
|
const { isBetaEnabled, forEachGeneric } = require('../base/model');
|
|
6
|
-
const {
|
|
6
|
+
const {
|
|
7
|
+
setLink,
|
|
8
|
+
annotationVal,
|
|
9
|
+
annotationIsFalse,
|
|
10
|
+
isDirectComposition,
|
|
11
|
+
} = require('./utils');
|
|
7
12
|
|
|
8
13
|
function kickStart( model ) {
|
|
9
14
|
const { options } = model;
|
|
@@ -122,10 +127,7 @@ function kickStart( model ) {
|
|
|
122
127
|
}
|
|
123
128
|
|
|
124
129
|
function tagCompositionTargets( elem ) {
|
|
125
|
-
|
|
126
|
-
if (elem.target && type &&
|
|
127
|
-
(type._artifact === model.definitions['cds.Composition'] ||
|
|
128
|
-
type.path?.[0].id === 'cds.Composition')) {
|
|
130
|
+
if (elem.target && isDirectComposition( elem )) {
|
|
129
131
|
// A target aspect would have already moved to property `targetAspect` in
|
|
130
132
|
// define.js (hm... more something for kick-start.js...)
|
|
131
133
|
// TODO: for safety, just use resolveUncheckedPath()
|