@sap/cds-compiler 6.6.2 → 6.7.1
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 +28 -1
- package/bin/cdsc.js +2 -0
- package/bin/cdsse.js +1 -1
- package/lib/base/message-registry.js +6 -7
- package/lib/base/model.js +0 -72
- package/lib/checks/elements.js +1 -1
- package/lib/checks/featureFlags.js +2 -2
- package/lib/compiler/assert-consistency.js +3 -4
- package/lib/compiler/base.js +8 -0
- package/lib/compiler/builtins.js +8 -9
- package/lib/compiler/checks.js +27 -6
- package/lib/compiler/cycle-detector.js +4 -4
- package/lib/compiler/define.js +65 -83
- package/lib/compiler/extend.js +357 -325
- package/lib/compiler/finalize-parse-cdl.js +3 -4
- package/lib/compiler/generate.js +205 -203
- package/lib/compiler/kick-start.js +34 -49
- package/lib/compiler/populate.js +95 -28
- package/lib/compiler/propagator.js +3 -5
- package/lib/compiler/resolve.js +17 -13
- package/lib/compiler/shared.js +47 -19
- package/lib/compiler/tweak-assocs.js +2 -4
- package/lib/compiler/utils.js +84 -31
- package/lib/gen/BaseParser.js +924 -1055
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +5 -2
- package/lib/json/from-csn.js +25 -16
- package/lib/main.d.ts +13 -0
- package/lib/model/revealInternalProperties.js +18 -0
- package/lib/parsers/AstBuildingParser.js +22 -5
- package/lib/render/toHdbcds.js +2 -2
- package/lib/render/utils/sql.js +2 -2
- package/lib/render/utils/standardDatabaseFunctions.js +2 -2
- package/lib/transform/db/constraints.js +3 -4
- package/lib/transform/db/killAnnotations.js +1 -1
- package/lib/transform/db/processSqlServices.js +10 -11
- package/lib/transform/forOdata.js +7 -124
- package/lib/transform/odata/fioriTreeViews.js +173 -0
- package/lib/transform/odata/flattening.js +2 -2
- package/lib/transform/translateAssocsToJoins.js +7 -4
- package/package.json +1 -1
- package/share/messages/message-explanations.json +0 -2
- package/share/messages/type-unexpected-foreign-keys.md +0 -52
- package/share/messages/type-unexpected-on-condition.md +0 -52
package/lib/compiler/generate.js
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} = require('../base/model');
|
|
5
|
+
/* eslint-disable no-nested-ternary */
|
|
6
|
+
|
|
7
|
+
const { isDeprecatedEnabled } = require('../base/model');
|
|
9
8
|
const { dictAdd } = require('../base/dictionaries');
|
|
10
9
|
const {
|
|
11
10
|
setLink,
|
|
@@ -17,6 +16,7 @@ const {
|
|
|
17
16
|
augmentPath,
|
|
18
17
|
isDirectComposition,
|
|
19
18
|
copyExpr,
|
|
19
|
+
forEachGeneric,
|
|
20
20
|
} = require('./utils');
|
|
21
21
|
const { weakLocation, weakRefLocation, weakEndLocation } = require('../base/location');
|
|
22
22
|
|
|
@@ -28,6 +28,7 @@ function generate( model ) {
|
|
|
28
28
|
const {
|
|
29
29
|
error, warning, info,
|
|
30
30
|
} = model.$messageFunctions;
|
|
31
|
+
const Functions = model.$functions;
|
|
31
32
|
const {
|
|
32
33
|
resolvePath,
|
|
33
34
|
resolveUncheckedPath,
|
|
@@ -35,14 +36,17 @@ function generate( model ) {
|
|
|
35
36
|
extendArtifactBefore,
|
|
36
37
|
applyIncludes,
|
|
37
38
|
} = model.$functions;
|
|
38
|
-
model.$functions
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
Object.assign( model.$functions, {
|
|
40
|
+
hasTruthyProp,
|
|
41
|
+
generateForEntity,
|
|
42
|
+
populateGeneratedEntity,
|
|
43
|
+
checkGenerateConditions,
|
|
44
|
+
} );
|
|
45
|
+
|
|
46
|
+
const commonLanguages = model.definitions['sap.common.Languages'];
|
|
47
|
+
const textsAspect = model.definitions['sap.common.TextsAspect'];
|
|
48
|
+
let enableLanguageAssoc = null; // → addLanguageAssoc()
|
|
49
|
+
let enableTextsAspect = null; // → useTextsAspect()
|
|
46
50
|
return;
|
|
47
51
|
|
|
48
52
|
/**
|
|
@@ -50,33 +54,45 @@ function generate( model ) {
|
|
|
50
54
|
*
|
|
51
55
|
* @param {string} name
|
|
52
56
|
*/
|
|
53
|
-
function
|
|
54
|
-
|
|
55
|
-
if (!
|
|
57
|
+
function generateForEntity( art ) {
|
|
58
|
+
// TODO: write dependency on base entity for final generateForEntity()
|
|
59
|
+
if (!art.$duplicates && art.elements) {
|
|
56
60
|
processAspectComposition( art );
|
|
57
|
-
|
|
58
|
-
// check potential entity parse error
|
|
59
|
-
processLocalizedData( art );
|
|
61
|
+
processLocalizedData( art );
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
65
|
+
function addLanguageAssoc( report = false ) {
|
|
66
|
+
if (!options.addTextsLanguageAssoc)
|
|
67
|
+
return false;
|
|
68
|
+
if (!report && enableLanguageAssoc != null)
|
|
69
|
+
return enableLanguageAssoc;
|
|
70
|
+
|
|
71
|
+
const codeElement = Functions.effectiveType( commonLanguages )?.elements?.code;
|
|
72
|
+
// eslint-disable no-nested-ternary
|
|
73
|
+
const err = (!commonLanguages || commonLanguages.$inferred )
|
|
74
|
+
? 'missing'
|
|
75
|
+
: (commonLanguages.kind !== 'entity' || commonLanguages.query)
|
|
76
|
+
? 'kind'
|
|
77
|
+
: (!codeElement || codeElement.$inferred)
|
|
78
|
+
? 'code'
|
|
79
|
+
: null; // TODO: better config of eslint @stylistic/indent
|
|
80
|
+
if (report && err) {
|
|
81
|
+
const loc = commonLanguages?.name?.location || null;
|
|
82
|
+
warning( 'api-ignoring-language-assoc', [ loc, commonLanguages ], {
|
|
83
|
+
'#': err,
|
|
84
|
+
option: 'addTextsLanguageAssoc',
|
|
85
|
+
art: 'sap.common.Languages',
|
|
86
|
+
name: 'code',
|
|
87
|
+
}, {
|
|
88
|
+
std: 'Ignoring option $(OPTION)',
|
|
89
|
+
missing: 'Ignoring option $(OPTION) because entity $(ART) is missing',
|
|
90
|
+
kind: 'Ignoring option $(OPTION) because entity $(ART) is no entity',
|
|
91
|
+
code: 'Ignoring option $(OPTION) because entity $(ART) is missing direct element $(NAME)',
|
|
92
|
+
} );
|
|
79
93
|
}
|
|
94
|
+
enableLanguageAssoc = (err == null);
|
|
95
|
+
return enableLanguageAssoc;
|
|
80
96
|
}
|
|
81
97
|
|
|
82
98
|
/**
|
|
@@ -86,56 +102,46 @@ function generate( model ) {
|
|
|
86
102
|
*
|
|
87
103
|
* @return {boolean}
|
|
88
104
|
*/
|
|
89
|
-
function
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
105
|
+
function useTextsAspect( report = false ) {
|
|
106
|
+
if (!report && enableTextsAspect != null)
|
|
107
|
+
return enableTextsAspect;
|
|
108
|
+
|
|
109
|
+
const locale = Functions.effectiveType( textsAspect )?.elements?.locale;
|
|
110
|
+
const err = (!textsAspect || textsAspect.$inferred )
|
|
111
|
+
? false
|
|
112
|
+
: (textsAspect.kind !== 'aspect' || !textsAspect.elements)
|
|
113
|
+
? 'no-aspect'
|
|
114
|
+
: (!locale || locale.$inferred)
|
|
115
|
+
? 'missing'
|
|
116
|
+
: (locale.key?.val ? null : 'key'); // TODO: better eslint @stylistic/indent
|
|
117
|
+
if (report && err) {
|
|
118
|
+
const loc = (err === 'key' ? locale : textsAspect)?.name?.location || null;
|
|
119
|
+
error( 'def-invalid-texts-aspect', [ loc, (err === 'key' ? locale : textsAspect) ],
|
|
120
|
+
{ '#': err, art: textsAspect, name: 'locale' } );
|
|
121
|
+
}
|
|
122
|
+
enableTextsAspect = (err == null);
|
|
123
|
+
return enableTextsAspect;
|
|
124
|
+
}
|
|
95
125
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return
|
|
100
|
-
}
|
|
126
|
+
function checkGenerateConditions() {
|
|
127
|
+
addLanguageAssoc( true );
|
|
128
|
+
if (!useTextsAspect( true ))
|
|
129
|
+
return;
|
|
101
130
|
|
|
102
|
-
|
|
103
|
-
if (addTextsLanguageAssoc && textsAspect.elements.language) {
|
|
131
|
+
if (enableLanguageAssoc && textsAspect.elements.language) {
|
|
104
132
|
const lang = textsAspect.elements.language;
|
|
105
133
|
error( 'def-unexpected-element', [ lang.name.location, lang ],
|
|
106
134
|
{ option: 'addTextsLanguageAssoc', art: textsAspect, name: 'language' },
|
|
107
|
-
|
|
108
|
-
'$(ART) is not used because option $(OPTION) conflicts with existing element $(NAME); remove either option or element' );
|
|
109
|
-
hasError = true;
|
|
135
|
+
'Element $(NAME) of $(ART) is not used because option $(OPTION) is set' );
|
|
110
136
|
}
|
|
111
137
|
|
|
112
|
-
for (const name in specialElements) {
|
|
113
|
-
const expected = specialElements[name];
|
|
114
|
-
const elem = textsAspect.elements[name];
|
|
115
|
-
if (!elem) {
|
|
116
|
-
error( 'def-invalid-texts-aspect', [ textsAspect.name.location, textsAspect ],
|
|
117
|
-
{ '#': 'missing', art: textsAspect, name } );
|
|
118
|
-
hasError = true;
|
|
119
|
-
}
|
|
120
|
-
else if (expected.key !== undefined && !!elem.key?.val !== expected.key) {
|
|
121
|
-
const loc = elem.key?.location || elem.name?.location || textsAspect.name.location;
|
|
122
|
-
error( 'def-invalid-texts-aspect', [ loc, elem ],
|
|
123
|
-
{ '#': expected.key ? 'key' : 'no-key', art: elem } );
|
|
124
|
-
hasError = true;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (hasError) // avoid subsequent errors, if the special elements are already wrong
|
|
129
|
-
return false;
|
|
130
|
-
|
|
131
138
|
for (const name in textsAspect.elements) {
|
|
132
139
|
const elem = textsAspect.elements[name];
|
|
133
140
|
const include = elem.$inferred === 'include';
|
|
134
|
-
if (
|
|
141
|
+
if (elem.key && name !== 'locale') {
|
|
135
142
|
const loc = include ? elem.location : elem.key.location;
|
|
136
143
|
error( 'def-unexpected-key', [ loc, elem ],
|
|
137
144
|
{ '#': !include ? 'std' : 'include', art: textsAspect } );
|
|
138
|
-
hasError = true;
|
|
139
145
|
}
|
|
140
146
|
else if (hasTruthyProp( elem, 'localized' )) {
|
|
141
147
|
// TODO: T:loc, i.e. "localized" from other type (needs resolver?)
|
|
@@ -143,17 +149,13 @@ function generate( model ) {
|
|
|
143
149
|
const loc = elem.localized?.location || elem.location;
|
|
144
150
|
error( 'def-unexpected-localized', [ loc, elem ],
|
|
145
151
|
{ '#': !include ? 'elements' : 'include', art: textsAspect } );
|
|
146
|
-
hasError = true;
|
|
147
152
|
}
|
|
148
153
|
else if (elem.targetAspect) {
|
|
149
154
|
error( 'def-unexpected-composition', [ elem.targetAspect.location, elem ],
|
|
150
155
|
{ art: textsAspect },
|
|
151
156
|
'$(ART) can\'t have composition of aspects' );
|
|
152
|
-
hasError = true;
|
|
153
157
|
}
|
|
154
158
|
}
|
|
155
|
-
|
|
156
|
-
return !hasError;
|
|
157
159
|
}
|
|
158
160
|
|
|
159
161
|
// localized texts entities ---------------------------------------------------
|
|
@@ -165,8 +167,13 @@ function generate( model ) {
|
|
|
165
167
|
* @param {XSN.Artifact} art
|
|
166
168
|
*/
|
|
167
169
|
function processLocalizedData( art ) {
|
|
170
|
+
// do not create texts entity for a texts entity (might be induced by erroneous
|
|
171
|
+
// sap.common.TextsAspect):
|
|
172
|
+
if (art.$inferred === 'localized-entity')
|
|
173
|
+
return;
|
|
174
|
+
// TODO: test with `annotate … with @fiori.draft.enabled`
|
|
168
175
|
const fioriAnno = art['@fiori.draft.enabled'];
|
|
169
|
-
const fioriEnabled = fioriAnno && (fioriAnno.val === undefined || fioriAnno.val);
|
|
176
|
+
const fioriEnabled = fioriAnno && (fioriAnno.val === undefined || !!fioriAnno.val);
|
|
170
177
|
|
|
171
178
|
const textsName = `${ art.name.id }.texts`;
|
|
172
179
|
const textsEntity = model.definitions[textsName];
|
|
@@ -198,7 +205,7 @@ function generate( model ) {
|
|
|
198
205
|
const protectedElements = [ 'locale', 'texts', 'localized' ];
|
|
199
206
|
if (fioriEnabled)
|
|
200
207
|
protectedElements.push( 'ID_texts' );
|
|
201
|
-
if (
|
|
208
|
+
if (addLanguageAssoc())
|
|
202
209
|
protectedElements.push( 'language' );
|
|
203
210
|
|
|
204
211
|
for (const name in art.elements) {
|
|
@@ -286,19 +293,16 @@ function generate( model ) {
|
|
|
286
293
|
*/
|
|
287
294
|
function createTextsEntity( base, absolute, textElems, fioriEnabled ) {
|
|
288
295
|
const location = weakLocation( base.elements[$location] || base.location );
|
|
289
|
-
|
|
296
|
+
const art = Functions.registerGeneratedEntity( {
|
|
290
297
|
kind: 'entity',
|
|
291
298
|
name: { id: absolute, location },
|
|
292
299
|
location,
|
|
293
300
|
elements: Object.create( null ),
|
|
294
301
|
$inferred: 'localized-entity',
|
|
295
|
-
};
|
|
296
|
-
const gap = model.definitions[absolute];
|
|
297
|
-
if (gap)
|
|
298
|
-
art = Object.assign( gap, art );
|
|
299
|
-
else
|
|
300
|
-
model.definitions[absolute] = art;
|
|
302
|
+
} );
|
|
301
303
|
setLink( art, '_block', model.$internal );
|
|
304
|
+
|
|
305
|
+
// console.log('Texts:',require('../model/revealInternalProperties').ref(art))
|
|
302
306
|
extendArtifactBefore( art ); // having extensions here would be wrong
|
|
303
307
|
|
|
304
308
|
if (!fioriEnabled) {
|
|
@@ -313,15 +317,22 @@ function generate( model ) {
|
|
|
313
317
|
type: linkMainArtifact( location, 'cds.UUID' ),
|
|
314
318
|
location,
|
|
315
319
|
};
|
|
316
|
-
|
|
320
|
+
art.elements.ID_texts = textId;
|
|
317
321
|
}
|
|
322
|
+
// _service links and "consider as composition target" must be set already here
|
|
323
|
+
setLink( art, '_service', base._service );
|
|
324
|
+
model.$compositionTargets[absolute] = true;
|
|
325
|
+
setGenExtensions( art, { _base: base, _textElems: textElems, fioriEnabled } );
|
|
326
|
+
}
|
|
318
327
|
|
|
319
|
-
|
|
328
|
+
function populateTextsEntity( art, base, textElems, fioriEnabled ) {
|
|
329
|
+
const { location } = art;
|
|
330
|
+
const enrich = useTextsAspect()
|
|
320
331
|
? enrichTextsEntityWithInclude
|
|
321
332
|
: enrichTextsEntityWithDefaultElements;
|
|
322
|
-
enrich( art,
|
|
333
|
+
enrich( art, fioriEnabled );
|
|
323
334
|
|
|
324
|
-
if (
|
|
335
|
+
if (addLanguageAssoc()) {
|
|
325
336
|
const language = {
|
|
326
337
|
name: { location, id: 'language' },
|
|
327
338
|
kind: 'element',
|
|
@@ -350,12 +361,12 @@ function generate( model ) {
|
|
|
350
361
|
initMainArtifact( art );
|
|
351
362
|
// do the kick-start relevant stuff: _service, there are no _ancestors,
|
|
352
363
|
// _descendants would have been set already for a gap artifact
|
|
353
|
-
setLink( art, '_service', art._parent._service );
|
|
354
|
-
model.$compositionTargets[absolute] = true;
|
|
355
364
|
|
|
356
|
-
if (art.includes) {
|
|
365
|
+
if (art.includes) { // i.e. sap.common.TextsAspect
|
|
366
|
+
// do not insert sap.common.TextsAspect elements before `locale`:
|
|
357
367
|
// add elements `locale`, etc. which are required below.
|
|
358
|
-
|
|
368
|
+
art.includes.$original = []; // do not apply via extendArtifactAdd()
|
|
369
|
+
applyIncludes( art, art ); // apply now
|
|
359
370
|
}
|
|
360
371
|
|
|
361
372
|
if (fioriEnabled) {
|
|
@@ -376,6 +387,7 @@ function generate( model ) {
|
|
|
376
387
|
setAnnotation( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' );
|
|
377
388
|
}
|
|
378
389
|
|
|
390
|
+
// TODO: first copy persistence annos from cds.TextsAspect ?
|
|
379
391
|
copyPersistenceAnnotations( art, base );
|
|
380
392
|
return art;
|
|
381
393
|
}
|
|
@@ -413,13 +425,10 @@ function generate( model ) {
|
|
|
413
425
|
* Does NOT apply the include!
|
|
414
426
|
*
|
|
415
427
|
* @param {XSN.Artifact} art
|
|
416
|
-
* @param {XSN.Artifact} base
|
|
417
|
-
* @param {string} absolute
|
|
418
428
|
* @param {boolean} fioriEnabled
|
|
419
429
|
*/
|
|
420
|
-
function enrichTextsEntityWithInclude( art,
|
|
430
|
+
function enrichTextsEntityWithInclude( art, fioriEnabled ) {
|
|
421
431
|
const textsAspectName = 'sap.common.TextsAspect';
|
|
422
|
-
const textsAspect = model.definitions['sap.common.TextsAspect'];
|
|
423
432
|
const { location } = art;
|
|
424
433
|
|
|
425
434
|
art.includes = [ createInclude( textsAspectName, location ) ];
|
|
@@ -433,18 +442,16 @@ function generate( model ) {
|
|
|
433
442
|
art.elements.locale.$inferred = 'localized';
|
|
434
443
|
}
|
|
435
444
|
|
|
436
|
-
if (
|
|
445
|
+
if (addLanguageAssoc() && art.elements.language)
|
|
437
446
|
art.elements.language = undefined; // TODO: Message? Ignore?
|
|
438
447
|
// TODO: what is this necessary? We do not create a text entity in this case
|
|
439
448
|
}
|
|
440
449
|
|
|
441
450
|
/**
|
|
442
451
|
* @param {XSN.Artifact} art
|
|
443
|
-
* @param {XSN.Artifact} base
|
|
444
|
-
* @param {string} absolute
|
|
445
452
|
* @param {boolean} fioriEnabled
|
|
446
453
|
*/
|
|
447
|
-
function enrichTextsEntityWithDefaultElements( art,
|
|
454
|
+
function enrichTextsEntityWithDefaultElements( art, fioriEnabled ) {
|
|
448
455
|
// If there is a type `sap.common.Locale`, then use it as the type for the element `locale`.
|
|
449
456
|
// If not, use the default `cds.String` with a length of 14.
|
|
450
457
|
const hasLocaleType = model.definitions['sap.common.Locale']?.kind === 'type';
|
|
@@ -590,11 +597,15 @@ function generate( model ) {
|
|
|
590
597
|
return;
|
|
591
598
|
let origin = elem;
|
|
592
599
|
// included element do not have target aspect directly
|
|
600
|
+
// (remark: this will get far more complex with compositions of aspects
|
|
601
|
+
// in sub elements)
|
|
593
602
|
while (origin && !origin.targetAspect && origin._origin)
|
|
594
603
|
origin = origin._origin;
|
|
595
604
|
let target = origin.targetAspect;
|
|
596
|
-
if (target?.path)
|
|
605
|
+
if (target?.path) {
|
|
597
606
|
target = resolvePath( origin.targetAspect, 'targetAspect', origin );
|
|
607
|
+
Functions.effectiveType( target ); // should have been good!
|
|
608
|
+
}
|
|
598
609
|
if (!target || !target.elements)
|
|
599
610
|
return;
|
|
600
611
|
const entityName = `${ base.name.id }.${ elem.name.id }`;
|
|
@@ -614,8 +625,6 @@ function generate( model ) {
|
|
|
614
625
|
if (up_)
|
|
615
626
|
setLink( up_, '_origin', entity.elements.up_ );
|
|
616
627
|
model.$compositionTargets[entity.name.id] = true;
|
|
617
|
-
processAspectComposition( entity );
|
|
618
|
-
processLocalizedData( entity );
|
|
619
628
|
}
|
|
620
629
|
}
|
|
621
630
|
}
|
|
@@ -637,22 +646,6 @@ function generate( model ) {
|
|
|
637
646
|
'An aspect $(TARGET) can\'t be used as target in an entity without keys' );
|
|
638
647
|
return false;
|
|
639
648
|
}
|
|
640
|
-
// if (keys.up_) { // only to be tested if we allow to provide a prefix, which could be ''
|
|
641
|
-
// // Cannot be in an "inner aspect-compositions" as it would already be wrong before
|
|
642
|
-
// // TODO: if anonymous type, use location of "up_" element
|
|
643
|
-
// // FUTURE: add sub info with location of "up_" element
|
|
644
|
-
// message( 'id', [location, elem], { target, name: 'up_' }, 'Error',
|
|
645
|
-
// 'An aspect $(TARGET) can't be used as target in an entity with a key named $(NAME)' );
|
|
646
|
-
// return false;
|
|
647
|
-
// }
|
|
648
|
-
if (target.elements.up_) {
|
|
649
|
-
// TODO: for "inner aspect-compositions", signal already in type
|
|
650
|
-
// TODO: if anonymous type, use location of "up_" element
|
|
651
|
-
// FUTURE: if named type, add sub info with location of "up_" element
|
|
652
|
-
error( null, [ location, elem ], { target, name: 'up_' },
|
|
653
|
-
'An aspect $(TARGET) with an element named $(NAME) can\'t be used as target' );
|
|
654
|
-
return false;
|
|
655
|
-
}
|
|
656
649
|
const place = model.definitions[entityName];
|
|
657
650
|
if (place && place.kind !== 'namespace') {
|
|
658
651
|
error( null, [ location, elem ], { art: entityName },
|
|
@@ -660,16 +653,6 @@ function generate( model ) {
|
|
|
660
653
|
'Target entity $(ART) can\'t be created as there is another definition with this name' );
|
|
661
654
|
return false;
|
|
662
655
|
}
|
|
663
|
-
const names = Object.keys( target.elements )
|
|
664
|
-
.filter( n => n.startsWith( 'up__' ) && keyNames.includes( n.substring(4) ) );
|
|
665
|
-
if (names.length) {
|
|
666
|
-
// FUTURE: if named type, add sub info with location of "up_" element
|
|
667
|
-
error( null, [ location, elem ], { target: entityName, names }, {
|
|
668
|
-
std: 'Key elements $(NAMES) can\'t be added to $(TARGET) as these already exist',
|
|
669
|
-
one: 'Key element $(NAMES) can\'t be added to $(TARGET) as it already exist',
|
|
670
|
-
} );
|
|
671
|
-
return false;
|
|
672
|
-
}
|
|
673
656
|
|
|
674
657
|
if (elem.type && !isDirectComposition( elem )) {
|
|
675
658
|
// Only issue warning for direct usages, not for projections, includes, etc.
|
|
@@ -689,8 +672,10 @@ function generate( model ) {
|
|
|
689
672
|
}
|
|
690
673
|
|
|
691
674
|
function createTargetEntity( target, elem, keys, entityName, base ) {
|
|
675
|
+
// Remark: keys for v2 option deprecated.unmanagedUpInComponent
|
|
692
676
|
const location = weakRefLocation( elem.targetAspect || elem.target || elem );
|
|
693
|
-
|
|
677
|
+
// Since there is no user-written up_ element, use a weak location to the beginning of {…}.
|
|
678
|
+
elem.on = { // element on base entity
|
|
694
679
|
location,
|
|
695
680
|
op: { val: '=', location },
|
|
696
681
|
args: [
|
|
@@ -700,43 +685,14 @@ function generate( model ) {
|
|
|
700
685
|
$inferred: 'aspect-composition',
|
|
701
686
|
};
|
|
702
687
|
|
|
703
|
-
|
|
704
|
-
kind: 'entity',
|
|
705
|
-
name: {
|
|
706
|
-
id: entityName,
|
|
707
|
-
// for code navigation (e.g. via `extend`s): point to the element's name
|
|
708
|
-
location: weakLocation( elem.name.location ),
|
|
709
|
-
},
|
|
710
|
-
location,
|
|
711
|
-
elements: Object.create( null ),
|
|
712
|
-
$inferred: 'composition-entity',
|
|
713
|
-
};
|
|
714
|
-
const gap = model.definitions[entityName];
|
|
715
|
-
if (gap)
|
|
716
|
-
art = Object.assign( gap, art );
|
|
717
|
-
else
|
|
718
|
-
model.definitions[entityName] = art;
|
|
719
|
-
|
|
720
|
-
if (target.name) { // named target aspect
|
|
721
|
-
if (!isDeprecatedEnabled( options, 'noCompositionIncludes' )) {
|
|
722
|
-
art.includes = [ createInclude( target.name.id, location ) ];
|
|
723
|
-
propagateEarly( art, '@cds.autoexpose' );
|
|
724
|
-
propagateEarly( art, '@fiori.draft.enabled' );
|
|
725
|
-
}
|
|
726
|
-
setLink( art, '_origin', target );
|
|
727
|
-
setLink( art, '_upperAspects', [ target, ...(elem._main._upperAspects || []) ] );
|
|
728
|
-
}
|
|
729
|
-
else {
|
|
730
|
-
setLink( art, '_origin', target );
|
|
731
|
-
// TODO: do we need to give the anonymous target aspect a kind and name?
|
|
732
|
-
setLink( art, '_upperAspects', elem._main._upperAspects || [] );
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
// Since there is no user-written up_ element, use a weak location to the beginning of {…}.
|
|
736
|
-
const up = { // elements.up_ = ...
|
|
688
|
+
const up_ = { // elements.up_ = ...
|
|
737
689
|
name: { location, id: 'up_' },
|
|
738
690
|
kind: 'element',
|
|
739
691
|
location,
|
|
692
|
+
key: { location, val: true },
|
|
693
|
+
notNull: { location, val: true },
|
|
694
|
+
// managed associations must be explicitly set to not null
|
|
695
|
+
// even if target cardinality is 1..1
|
|
740
696
|
$inferred: 'aspect-composition',
|
|
741
697
|
type: linkMainArtifact( location, 'cds.Association' ),
|
|
742
698
|
target: linkMainArtifact( location, base.name.id ),
|
|
@@ -746,40 +702,87 @@ function generate( model ) {
|
|
|
746
702
|
location,
|
|
747
703
|
},
|
|
748
704
|
};
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
705
|
+
const art = Functions.registerGeneratedEntity( {
|
|
706
|
+
kind: 'entity',
|
|
707
|
+
name: {
|
|
708
|
+
id: entityName,
|
|
709
|
+
// for code navigation (e.g. via `extend`s): point to the element's name
|
|
710
|
+
location: weakLocation( elem.name.location ),
|
|
711
|
+
},
|
|
712
|
+
location,
|
|
713
|
+
elements: { __proto__: null, up_ },
|
|
714
|
+
$inferred: 'composition-entity',
|
|
715
|
+
} );
|
|
716
|
+
// console.log('Composition:',require('../model/revealInternalProperties').ref(art))
|
|
762
717
|
setLink( art, '_block', model.$internal );
|
|
763
|
-
initMainArtifact( art );
|
|
764
718
|
|
|
765
719
|
// do the kick-start relevant stuff: _service, there are no _ancestors,
|
|
766
720
|
// _descendants would have been set already for a gap artifact
|
|
767
|
-
setLink( art, '_service',
|
|
721
|
+
setLink( art, '_service', base._service );
|
|
768
722
|
model.$compositionTargets[entityName] = true;
|
|
769
723
|
|
|
770
724
|
// Apply annotations to generated artifact, prepare (not apply!) element
|
|
771
725
|
// annotations (remark: adding elements is not allowed for generated artifacts):
|
|
772
726
|
extendArtifactBefore( art );
|
|
773
|
-
|
|
774
|
-
|
|
727
|
+
setGenExtensions( art, { _composition: elem } );
|
|
728
|
+
setLink( art, '_origin', target ); // TODO: does this hurt?
|
|
729
|
+
return art;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function populateTargetEntity( art, elem ) {
|
|
733
|
+
const location = weakRefLocation( elem.targetAspect || elem.target || elem );
|
|
734
|
+
const targetAspect = art._origin;
|
|
735
|
+
|
|
736
|
+
Functions.effectiveType( targetAspect );
|
|
737
|
+
const { elements } = targetAspect;
|
|
775
738
|
|
|
776
|
-
if (
|
|
777
|
-
|
|
739
|
+
if (elements.up_) {
|
|
740
|
+
// TODO: for "inner aspect-compositions", signal already in type
|
|
741
|
+
// TODO: if anonymous type, use location of "up_" element
|
|
742
|
+
// FUTURE: if named type, add sub info with location of "up_" element
|
|
743
|
+
error( null, [ location, elem ], { target: targetAspect, name: 'up_' },
|
|
744
|
+
'An aspect $(TARGET) with an element named $(NAME) can\'t be used as target' );
|
|
745
|
+
delete elements.up_; // continuation semantics: don't use up_ from aspect
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
if (targetAspect.name) { // named target aspect
|
|
749
|
+
if (!isDeprecatedEnabled( options, 'noCompositionIncludes' )) {
|
|
750
|
+
art.includes = [ createInclude( targetAspect.name.id, location ) ];
|
|
751
|
+
art.includes.$origin = []; // included elements after up_
|
|
752
|
+
// TODO: propagate in effectiveType()
|
|
753
|
+
propagateEarly( art, '@cds.autoexpose' );
|
|
754
|
+
propagateEarly( art, '@fiori.draft.enabled' );
|
|
755
|
+
}
|
|
756
|
+
setLink( art, '_upperAspects', [ targetAspect, ...(elem._main._upperAspects || []) ] );
|
|
757
|
+
}
|
|
758
|
+
else {
|
|
759
|
+
// TODO: do we need to give the anonymous target aspect a kind and name?
|
|
760
|
+
setLink( art, '_upperAspects', elem._main._upperAspects || [] );
|
|
761
|
+
// TODO: _upperAspects can probably now be removed
|
|
762
|
+
}
|
|
763
|
+
Functions.effectiveType( elem );
|
|
764
|
+
// To keep the locations of non-inferred original elements, do not set $inferred:
|
|
765
|
+
const enforceLocation = targetAspect.name || elem.$inferred;
|
|
766
|
+
addProxyElements( art, elements, 'aspect-composition', enforceLocation && location );
|
|
767
|
+
initMainArtifact( art );
|
|
768
|
+
|
|
769
|
+
// Copy persistence annotations from aspect.
|
|
770
|
+
if (targetAspect.kind === 'aspect') // proper aspect
|
|
771
|
+
copyPersistenceAnnotations( art, targetAspect ); // after extendArtifactBefore()
|
|
772
|
+
copyPersistenceAnnotations( art, elem._parent );
|
|
778
773
|
return art;
|
|
779
774
|
}
|
|
780
775
|
|
|
776
|
+
function populateGeneratedEntity( art ) {
|
|
777
|
+
const gen = art._extensions?.$gen;
|
|
778
|
+
if (gen?._composition)
|
|
779
|
+
populateTargetEntity( art, gen._composition );
|
|
780
|
+
else if (gen?._textElems)
|
|
781
|
+
populateTextsEntity( art, gen._base, gen._textElems, gen.fioriEnabled );
|
|
782
|
+
}
|
|
783
|
+
|
|
781
784
|
function addProxyElements( proxyDict, elements, inferred, location, prefix = '', anno = '' ) {
|
|
782
|
-
// TODO: also use for includeMembers()? Both are similar. Combine
|
|
785
|
+
// TODO: also use for includeMembers()? Both are similar. Combine!
|
|
783
786
|
for (const name in elements) {
|
|
784
787
|
const pname = `${ prefix }${ name }`;
|
|
785
788
|
const origin = elements[name];
|
|
@@ -811,6 +814,8 @@ function generate( model ) {
|
|
|
811
814
|
* Copy relevant annotations from
|
|
812
815
|
* source to target if present on source but not target.
|
|
813
816
|
*
|
|
817
|
+
* Persistence annos from target/text aspect have precedence.
|
|
818
|
+
*
|
|
814
819
|
* @param {object} target
|
|
815
820
|
* @param {object} source
|
|
816
821
|
*/
|
|
@@ -837,7 +842,7 @@ function generate( model ) {
|
|
|
837
842
|
}
|
|
838
843
|
|
|
839
844
|
function linkMainArtifact( location, absolute ) {
|
|
840
|
-
const r = { location, path: [ { id: absolute, location } ] };
|
|
845
|
+
const r = { location, path: [ { id: absolute, location } ], scope: 'global' };
|
|
841
846
|
setArtifactLink( r, model.definitions[absolute] );
|
|
842
847
|
return r;
|
|
843
848
|
}
|
|
@@ -868,24 +873,6 @@ function augmentEqual( location, assocname, relations, prefix = '' ) {
|
|
|
868
873
|
}
|
|
869
874
|
}
|
|
870
875
|
|
|
871
|
-
function checkTextsLanguageAssocOption( model, options ) {
|
|
872
|
-
const languages = model.definitions['sap.common.Languages'];
|
|
873
|
-
const commonLanguagesEntity = options.addTextsLanguageAssoc && languages?.elements?.code;
|
|
874
|
-
|
|
875
|
-
if (options.addTextsLanguageAssoc && !commonLanguagesEntity) {
|
|
876
|
-
const variant = !languages ? 'std' : 'code';
|
|
877
|
-
const loc = model.definitions['sap.common.Languages']?.name?.location || null;
|
|
878
|
-
model.$messageFunctions.info( 'api-ignoring-language-assoc', loc, {
|
|
879
|
-
'#': variant, option: 'addTextsLanguageAssoc', art: 'sap.common.Languages', name: 'code',
|
|
880
|
-
}, {
|
|
881
|
-
std: 'Ignoring option $(OPTION) because entity $(ART) is missing',
|
|
882
|
-
code: 'Ignoring option $(OPTION) because entity $(ART) is missing element $(NAME)',
|
|
883
|
-
} );
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
return !!commonLanguagesEntity;
|
|
887
|
-
}
|
|
888
|
-
|
|
889
876
|
|
|
890
877
|
/**
|
|
891
878
|
* Propagate the given `prop` (e.g. annotation) early, i.e. copy it from all `.includes`
|
|
@@ -908,5 +895,20 @@ function propagateEarly( art, prop ) {
|
|
|
908
895
|
}
|
|
909
896
|
}
|
|
910
897
|
|
|
898
|
+
function setGenExtensions( art, gen ) {
|
|
899
|
+
const $gen = {};
|
|
900
|
+
for (const prop of Object.keys( gen )) {
|
|
901
|
+
Object.defineProperty( $gen, prop, {
|
|
902
|
+
configurable: true,
|
|
903
|
+
enumerable: prop.charAt( 0 ) !== '_',
|
|
904
|
+
value: gen[prop],
|
|
905
|
+
writable: true,
|
|
906
|
+
} );
|
|
907
|
+
}
|
|
908
|
+
if (art._extensions)
|
|
909
|
+
art._extensions.$gen = $gen;
|
|
910
|
+
else
|
|
911
|
+
art._extensions = { $gen };
|
|
912
|
+
}
|
|
911
913
|
|
|
912
914
|
module.exports = generate;
|