@sap/cds-compiler 3.8.2 → 3.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 +53 -0
- package/bin/cdsc.js +2 -2
- package/doc/CHANGELOG_BETA.md +26 -5
- package/lib/api/.eslintrc.json +3 -2
- package/lib/api/options.js +3 -1
- package/lib/api/validate.js +1 -1
- package/lib/base/message-registry.js +27 -18
- package/lib/base/messages.js +6 -1
- package/lib/base/model.js +2 -2
- package/lib/checks/.eslintrc.json +1 -0
- package/lib/checks/actionsFunctions.js +6 -6
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/elements.js +28 -17
- package/lib/checks/foreignKeys.js +1 -1
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/onConditions.js +11 -6
- package/lib/checks/queryNoDbArtifacts.js +1 -1
- package/lib/checks/types.js +1 -1
- package/lib/checks/utils.js +1 -1
- package/lib/checks/validator.js +3 -2
- package/lib/compiler/assert-consistency.js +7 -2
- package/lib/compiler/base.js +8 -4
- package/lib/compiler/builtins.js +7 -0
- package/lib/compiler/checks.js +73 -6
- package/lib/compiler/define.js +10 -5
- package/lib/compiler/extend.js +910 -1711
- package/lib/compiler/finalize-parse-cdl.js +1 -1
- package/lib/compiler/generate.js +838 -0
- package/lib/compiler/index.js +2 -0
- package/lib/compiler/populate.js +2 -2
- package/lib/compiler/propagator.js +20 -8
- package/lib/compiler/resolve.js +3 -3
- package/lib/compiler/shared.js +3 -1
- package/lib/edm/annotations/genericTranslation.js +6 -6
- package/lib/edm/csn2edm.js +1 -1
- package/lib/edm/edm.js +25 -11
- package/lib/edm/edmPreprocessor.js +47 -23
- package/lib/edm/edmUtils.js +37 -9
- package/lib/gen/Dictionary.json +5 -7
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/language.tokens +24 -23
- package/lib/gen/languageLexer.interp +4 -1
- package/lib/gen/languageLexer.js +792 -784
- package/lib/gen/languageLexer.tokens +12 -11
- package/lib/gen/languageParser.js +3564 -3493
- package/lib/json/from-csn.js +26 -4
- package/lib/json/to-csn.js +10 -6
- package/lib/language/antlrParser.js +11 -3
- package/lib/language/genericAntlrParser.js +2 -1
- package/lib/language/language.g4 +14 -3
- package/lib/model/csnRefs.js +10 -5
- package/lib/model/csnUtils.js +41 -76
- package/lib/modelCompare/utils/.eslintrc.json +1 -1
- package/lib/optionProcessor.js +7 -4
- package/lib/render/.eslintrc.json +1 -1
- package/lib/render/toCdl.js +244 -168
- package/lib/render/toHdbcds.js +18 -10
- package/lib/render/toSql.js +24 -2
- package/lib/transform/db/.eslintrc.json +4 -3
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/expansion.js +11 -6
- package/lib/transform/db/flattening.js +22 -15
- package/lib/transform/db/rewriteCalculatedElements.js +50 -29
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/db/views.js +1 -1
- package/lib/transform/draft/db.js +1 -1
- package/lib/transform/draft/odata.js +3 -4
- package/lib/transform/forOdataNew.js +5 -6
- package/lib/transform/forRelationalDB.js +7 -7
- package/lib/transform/odata/toFinalBaseType.js +6 -6
- package/lib/transform/odata/typesExposure.js +12 -3
- package/lib/transform/odata/utils.js +3 -0
- package/lib/transform/transformUtilsNew.js +11 -26
- package/lib/transform/translateAssocsToJoins.js +9 -9
- package/lib/transform/universalCsn/.eslintrc.json +3 -2
- package/lib/transform/universalCsn/coreComputed.js +1 -1
- package/lib/transform/universalCsn/universalCsnEnricher.js +6 -4
- package/package.json +1 -1
|
@@ -0,0 +1,838 @@
|
|
|
1
|
+
// Generate: localized data and managed compositions
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
isDeprecatedEnabled,
|
|
7
|
+
forEachGeneric, forEachDefinition,
|
|
8
|
+
} = require('../base/model');
|
|
9
|
+
const { dictAdd } = require('../base/dictionaries');
|
|
10
|
+
const {
|
|
11
|
+
setLink,
|
|
12
|
+
setArtifactLink,
|
|
13
|
+
setAnnotation,
|
|
14
|
+
linkToOrigin,
|
|
15
|
+
setMemberParent,
|
|
16
|
+
augmentPath,
|
|
17
|
+
splitIntoPath,
|
|
18
|
+
isDirectComposition,
|
|
19
|
+
} = require('./utils');
|
|
20
|
+
|
|
21
|
+
function generate( model ) {
|
|
22
|
+
const { options } = model;
|
|
23
|
+
// Get simplified "resolve" functionality and the message function:
|
|
24
|
+
const {
|
|
25
|
+
error, warning, info,
|
|
26
|
+
} = model.$messageFunctions;
|
|
27
|
+
const {
|
|
28
|
+
resolvePath,
|
|
29
|
+
resolveUncheckedPath,
|
|
30
|
+
initArtifact,
|
|
31
|
+
extendArtifactBefore,
|
|
32
|
+
applyIncludes,
|
|
33
|
+
} = model.$functions;
|
|
34
|
+
|
|
35
|
+
const addTextsLanguageAssoc = checkTextsLanguageAssocOption(model, options);
|
|
36
|
+
const useTextsAspect = checkTextsAspect();
|
|
37
|
+
|
|
38
|
+
Object.keys( model.definitions ).forEach( processArtifact );
|
|
39
|
+
|
|
40
|
+
compositionChildPersistence();
|
|
41
|
+
return;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Process "composition of" artifacts.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} name
|
|
47
|
+
*/
|
|
48
|
+
function processArtifact( name ) {
|
|
49
|
+
const art = model.definitions[name];
|
|
50
|
+
if (!(art.$duplicates)) {
|
|
51
|
+
processAspectComposition( art );
|
|
52
|
+
if (art.kind === 'entity' && !art.query && art.elements)
|
|
53
|
+
// check potential entity parse error
|
|
54
|
+
processLocalizedData( art );
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Copy `@cds.persistence.skip` and `@cds.persistence.skip` from parent to child
|
|
60
|
+
* for managed compositions. This needs to be done after extensions, i.e. annotations,
|
|
61
|
+
* have been applied or `annotate E.comp` would not have an effect on `E.comp.subComp`.
|
|
62
|
+
*/
|
|
63
|
+
function compositionChildPersistence() {
|
|
64
|
+
const processed = new WeakSet();
|
|
65
|
+
forEachDefinition(model, processCompositionPersistence);
|
|
66
|
+
|
|
67
|
+
function processCompositionPersistence( def ) {
|
|
68
|
+
if (def.$inferred === 'composition-entity' && !processed.has(def)) {
|
|
69
|
+
if (def._parent)
|
|
70
|
+
processCompositionPersistence(def._parent);
|
|
71
|
+
copyPersistenceAnnotations( def, def._parent );
|
|
72
|
+
processed.add(def);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Check that special `sap.common.*` aspects for `.texts` entities are
|
|
79
|
+
* consistent with compiler expectations. Emits messages and returns
|
|
80
|
+
* false if the aspects are not valid.
|
|
81
|
+
*
|
|
82
|
+
* @return {boolean}
|
|
83
|
+
*/
|
|
84
|
+
function checkTextsAspect() {
|
|
85
|
+
const textsAspect = model.definitions['sap.common.TextsAspect'];
|
|
86
|
+
if (!textsAspect)
|
|
87
|
+
return false;
|
|
88
|
+
|
|
89
|
+
const specialElements = { locale: { key: true } };
|
|
90
|
+
|
|
91
|
+
if (textsAspect.kind !== 'aspect' || !textsAspect.elements) {
|
|
92
|
+
error('def-invalid-texts-aspect', [ textsAspect.name.location, textsAspect ],
|
|
93
|
+
{ '#': 'no-aspect', art: textsAspect });
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let hasError = false;
|
|
98
|
+
if (addTextsLanguageAssoc && textsAspect.elements.language) {
|
|
99
|
+
const lang = textsAspect.elements.language;
|
|
100
|
+
error('def-unexpected-element', [ lang.name.location, lang ],
|
|
101
|
+
{ option: 'addTextsLanguageAssoc', art: textsAspect, name: 'language' },
|
|
102
|
+
// eslint-disable-next-line max-len
|
|
103
|
+
'$(ART) is not used because option $(OPTION) conflicts with existing element $(NAME); remove either option or element' );
|
|
104
|
+
hasError = true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (const name in specialElements) {
|
|
108
|
+
const expected = specialElements[name];
|
|
109
|
+
const elem = textsAspect.elements[name];
|
|
110
|
+
if (!elem) {
|
|
111
|
+
error('def-invalid-texts-aspect', [ textsAspect.name.location, textsAspect ],
|
|
112
|
+
{ '#': 'missing', art: textsAspect, name });
|
|
113
|
+
hasError = true;
|
|
114
|
+
}
|
|
115
|
+
else if (expected.key !== undefined && !!elem.key?.val !== expected.key) {
|
|
116
|
+
const loc = elem.key?.location || elem.name?.location || textsAspect.name.location;
|
|
117
|
+
error('def-invalid-texts-aspect', [ loc, elem ],
|
|
118
|
+
{ '#': expected.key ? 'key' : 'no-key', art: elem });
|
|
119
|
+
hasError = true;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (hasError) // avoid subsequent errors, if the special elements are already wrong
|
|
124
|
+
return false;
|
|
125
|
+
|
|
126
|
+
for (const name in textsAspect.elements) {
|
|
127
|
+
const elem = textsAspect.elements[name];
|
|
128
|
+
const include = elem.$inferred === 'include';
|
|
129
|
+
if (!specialElements[name] && elem.key) {
|
|
130
|
+
const loc = include ? elem.location : elem.key.location;
|
|
131
|
+
error( 'def-unexpected-key', [ loc, elem ],
|
|
132
|
+
{ '#': !include ? 'std' : 'include', art: textsAspect } );
|
|
133
|
+
hasError = true;
|
|
134
|
+
}
|
|
135
|
+
else if (hasTruthyProp( elem, 'localized' )) {
|
|
136
|
+
// TODO: T:loc, i.e. "localized" from other type (needs resolver?)
|
|
137
|
+
// Not supported anyway, but important for recompilation (which fails correctly).
|
|
138
|
+
const loc = elem.localized?.location || elem.location;
|
|
139
|
+
error( 'def-unexpected-localized', [ loc, elem ],
|
|
140
|
+
{ '#': !include ? 'std' : 'include', art: textsAspect } );
|
|
141
|
+
hasError = true;
|
|
142
|
+
}
|
|
143
|
+
else if (elem.targetAspect) {
|
|
144
|
+
error( 'def-unexpected-composition', [ elem.targetAspect.location, elem ],
|
|
145
|
+
{ art: textsAspect },
|
|
146
|
+
'$(ART) can\'t have composition of aspects' );
|
|
147
|
+
hasError = true;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return !hasError;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// localized texts entities ---------------------------------------------------
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Process localized data for `art`. This includes creating `.texts` entities
|
|
158
|
+
* and `locale` associations.
|
|
159
|
+
*
|
|
160
|
+
* @param {XSN.Artifact} art
|
|
161
|
+
*/
|
|
162
|
+
function processLocalizedData( art ) {
|
|
163
|
+
const fioriAnno = art['@fiori.draft.enabled'];
|
|
164
|
+
const fioriEnabled = fioriAnno && (fioriAnno.val === undefined || fioriAnno.val);
|
|
165
|
+
|
|
166
|
+
const textsName = `${ art.name.absolute }.texts`;
|
|
167
|
+
const textsEntity = model.definitions[textsName];
|
|
168
|
+
const localized = localizedData( art, textsEntity, fioriEnabled );
|
|
169
|
+
if (!localized)
|
|
170
|
+
return;
|
|
171
|
+
if (textsEntity) // expanded localized data in source
|
|
172
|
+
return; // -> make it idempotent
|
|
173
|
+
createTextsEntity( art, textsName, localized, fioriEnabled );
|
|
174
|
+
addTextsAssociations( art, textsName, localized );
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Returns `false`, if there is no localized data or an array of elements
|
|
179
|
+
* that are required for `.texts` entities such as keys and localized elements.
|
|
180
|
+
*
|
|
181
|
+
* @param {XSN.Artifact} art
|
|
182
|
+
* @param {XSN.Artifact|undefined} textsEntity
|
|
183
|
+
* @param {boolean} fioriEnabled
|
|
184
|
+
* @returns {false|XSN.Element[]}
|
|
185
|
+
*/
|
|
186
|
+
function localizedData( art, textsEntity, fioriEnabled ) {
|
|
187
|
+
let keys = 0;
|
|
188
|
+
const textElems = [];
|
|
189
|
+
const conflictingElements = [];
|
|
190
|
+
// These elements are required or the localized-mechanism does not work.
|
|
191
|
+
// Other elements from sap.common.TextsAspect may be "overridden" as per
|
|
192
|
+
// usual include-mechanism.
|
|
193
|
+
const protectedElements = [ 'locale', 'texts', 'localized' ];
|
|
194
|
+
if (fioriEnabled)
|
|
195
|
+
protectedElements.push('ID_texts');
|
|
196
|
+
if (addTextsLanguageAssoc)
|
|
197
|
+
protectedElements.push('language');
|
|
198
|
+
|
|
199
|
+
for (const name in art.elements) {
|
|
200
|
+
const elem = art.elements[name];
|
|
201
|
+
if (elem.$duplicates)
|
|
202
|
+
return false; // no localized-data unfold with redefined elems
|
|
203
|
+
if (protectedElements.includes( name ))
|
|
204
|
+
conflictingElements.push( elem );
|
|
205
|
+
|
|
206
|
+
const isKey = elem.key && elem.key.val;
|
|
207
|
+
const isLocalized = hasTruthyProp( elem, 'localized' );
|
|
208
|
+
|
|
209
|
+
if (isKey) {
|
|
210
|
+
keys += 1;
|
|
211
|
+
textElems.push( elem );
|
|
212
|
+
}
|
|
213
|
+
else if (isLocalized) {
|
|
214
|
+
textElems.push( elem );
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (isKey && isLocalized) { // key with localized is wrong - ignore localized
|
|
218
|
+
const errpos = elem.localized || elem.type || elem.name;
|
|
219
|
+
warning( 'def-ignoring-localized-key', [ errpos.location, elem ], { keyword: 'localized' },
|
|
220
|
+
'Keyword $(KEYWORD) is ignored for primary keys' );
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (textElems.length <= keys)
|
|
224
|
+
return false;
|
|
225
|
+
|
|
226
|
+
if (!keys) {
|
|
227
|
+
warning( 'def-expecting-key', [ art.name.location, art ], {},
|
|
228
|
+
'No texts entity can be created when no key element exists' );
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (textsEntity) {
|
|
233
|
+
if (textsEntity.$duplicates)
|
|
234
|
+
return false;
|
|
235
|
+
if (textsEntity.kind !== 'entity' || textsEntity.query ||
|
|
236
|
+
// already have elements "texts" and "localized" (and optionally ID_texts)
|
|
237
|
+
conflictingElements.length !== 2 || art.elements.locale ||
|
|
238
|
+
(fioriEnabled && art.elements.ID_texts)) {
|
|
239
|
+
// TODO if we have too much time: check all elements of texts entity for safety
|
|
240
|
+
warning( null, [ art.name.location, art ], { art: textsEntity },
|
|
241
|
+
// eslint-disable-next-line max-len
|
|
242
|
+
'Texts entity $(ART) can\'t be created as there is another definition with that name' );
|
|
243
|
+
info( null, [ textsEntity.name.location, textsEntity ], { art },
|
|
244
|
+
'Texts entity for $(ART) can\'t be created with this definition' );
|
|
245
|
+
}
|
|
246
|
+
else if (!art._block || art._block.$frontend !== 'json') {
|
|
247
|
+
info( null, [ art.name.location, art ], {},
|
|
248
|
+
'Localized data expansions has already been done' );
|
|
249
|
+
return textElems; // make double-compilation even with after toHana
|
|
250
|
+
}
|
|
251
|
+
else if (!art._block.$withLocalized && !options.$recompile) {
|
|
252
|
+
art._block.$withLocalized = true;
|
|
253
|
+
info( 'recalculated-text-entities', [ art.name.location, null ], {},
|
|
254
|
+
'Input CSN contains expansions for localized data' );
|
|
255
|
+
return textElems; // make compilation idempotent
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
return textElems;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
for (const elem of conflictingElements) {
|
|
262
|
+
warning( null, [ elem.name.location, art ], { name: elem.name.id },
|
|
263
|
+
'No texts entity can be created when element $(NAME) exists' );
|
|
264
|
+
}
|
|
265
|
+
return !textsEntity && !conflictingElements.length && textElems;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Create the `.texts` entity for the given base artifact.
|
|
270
|
+
*
|
|
271
|
+
* @param {XSN.Artifact} base
|
|
272
|
+
* @param {string} absolute
|
|
273
|
+
* @param {XSN.Element[]} textElems
|
|
274
|
+
* @param {boolean} fioriEnabled
|
|
275
|
+
*/
|
|
276
|
+
function createTextsEntity( base, absolute, textElems, fioriEnabled ) {
|
|
277
|
+
const art = useTextsAspect
|
|
278
|
+
? createTextsEntityWithInclude( base, absolute, fioriEnabled )
|
|
279
|
+
: createTextsEntityWithDefaultElements( base, absolute, fioriEnabled );
|
|
280
|
+
// both functions are rather similar...
|
|
281
|
+
|
|
282
|
+
const { location } = base.name;
|
|
283
|
+
|
|
284
|
+
if (addTextsLanguageAssoc) {
|
|
285
|
+
const language = {
|
|
286
|
+
name: { location, id: 'language' },
|
|
287
|
+
kind: 'element',
|
|
288
|
+
location,
|
|
289
|
+
type: augmentPath( location, 'cds.Association' ),
|
|
290
|
+
target: augmentPath( location, 'sap.common.Languages' ),
|
|
291
|
+
on: {
|
|
292
|
+
op: { val: '=', location },
|
|
293
|
+
args: [
|
|
294
|
+
{ path: [ { id: 'language', location }, { id: 'code', location } ], location },
|
|
295
|
+
{ path: [ { id: 'locale', location } ], location },
|
|
296
|
+
],
|
|
297
|
+
location,
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
setLink( language, '_block', model.$internal );
|
|
301
|
+
dictAdd( art.elements, 'language', language );
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// assertUnique array value, first entry is 'locale'
|
|
305
|
+
const assertUniqueValue = [];
|
|
306
|
+
|
|
307
|
+
for (const orig of textElems) {
|
|
308
|
+
const elem = linkToOrigin( orig, orig.name.id, art, 'elements' );
|
|
309
|
+
if (orig.key && orig.key.val) {
|
|
310
|
+
// elem.key = { val: fioriEnabled ? null : true, $inferred: 'localized', location };
|
|
311
|
+
// TODO: the previous would be better, but currently not supported in toCDL
|
|
312
|
+
if (!fioriEnabled) {
|
|
313
|
+
elem.key = { val: true, $inferred: 'localized', location };
|
|
314
|
+
// If the propagated elements remain key (that is not fiori.draft.enabled)
|
|
315
|
+
// they should be omitted from OData containment EDM
|
|
316
|
+
setAnnotation( elem, '@odata.containment.ignore', location );
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
// add the former key paths to the unique constraint
|
|
320
|
+
assertUniqueValue.push({
|
|
321
|
+
path: [ { id: orig.name.id, location: orig.location } ],
|
|
322
|
+
location: orig.location,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (hasTruthyProp( orig, 'localized' )) { // use location of LOCALIZED keyword
|
|
327
|
+
const localized = orig.localized || orig.type || orig.name;
|
|
328
|
+
elem.localized = { val: null, $inferred: 'localized', location: localized.location };
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
initArtifact( art );
|
|
333
|
+
if (art.includes) {
|
|
334
|
+
// add elements `locale`, etc. which are required below.
|
|
335
|
+
applyIncludes( art, art ); // TODO: rethink - can we avoid this if only new extend?
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (fioriEnabled) {
|
|
339
|
+
// The includes mechanism puts TextsAspect's elements before .texts' elements.
|
|
340
|
+
// Because ID_texts is not copied from TextsAspect, the order is messed
|
|
341
|
+
// up. Fix it.
|
|
342
|
+
const { elements } = art;
|
|
343
|
+
art.elements = Object.create(null);
|
|
344
|
+
const names = [ 'ID_texts', 'locale', ...Object.keys(elements) ];
|
|
345
|
+
for (const name of names)
|
|
346
|
+
art.elements[name] = elements[name];
|
|
347
|
+
|
|
348
|
+
const { locale } = art.elements;
|
|
349
|
+
assertUniqueValue.unshift({
|
|
350
|
+
path: [ { id: locale.name.id, location: locale.location } ],
|
|
351
|
+
location: locale.location,
|
|
352
|
+
});
|
|
353
|
+
setAnnotation( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' );
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
copyPersistenceAnnotations( art, base );
|
|
357
|
+
return art;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Create the `.texts` entity for the given base artifact.
|
|
362
|
+
* In contrast to createTextsEntityWithDefaultElements(), this one creates
|
|
363
|
+
* an include for `sap.common.TextsAspect`.
|
|
364
|
+
*
|
|
365
|
+
* Does NOT apply the include!
|
|
366
|
+
*
|
|
367
|
+
* @param {XSN.Artifact} base
|
|
368
|
+
* @param {string} absolute
|
|
369
|
+
* @param {boolean} fioriEnabled
|
|
370
|
+
*/
|
|
371
|
+
function createTextsEntityWithInclude( base, absolute, fioriEnabled ) {
|
|
372
|
+
const textsAspectName = 'sap.common.TextsAspect';
|
|
373
|
+
const textsAspect = model.definitions['sap.common.TextsAspect'];
|
|
374
|
+
const elements = Object.create(null);
|
|
375
|
+
const { location } = base.name;
|
|
376
|
+
const art = {
|
|
377
|
+
kind: 'entity',
|
|
378
|
+
name: { path: splitIntoPath( location, absolute ), absolute, location },
|
|
379
|
+
includes: [ createInclude( textsAspectName, base.location ) ],
|
|
380
|
+
location: base.location,
|
|
381
|
+
elements,
|
|
382
|
+
$inferred: 'localized-entity',
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
if (!fioriEnabled) {
|
|
386
|
+
// To be compatible, we switch off draft without @fiori.draft.enabled
|
|
387
|
+
// TODO (next major version): remove?
|
|
388
|
+
setAnnotation( art, '@odata.draft.enabled', art.location, false );
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
// @fiori.draft.enabled artifacts need default elements ID_texts and locale.
|
|
392
|
+
// `locale` is copied from `sap.common.TextsAspect`, but without "key".
|
|
393
|
+
const textId = {
|
|
394
|
+
name: { location, id: 'ID_texts' },
|
|
395
|
+
kind: 'element',
|
|
396
|
+
key: { val: true, location },
|
|
397
|
+
type: augmentPath( location, 'cds.UUID' ),
|
|
398
|
+
location,
|
|
399
|
+
};
|
|
400
|
+
dictAdd( art.elements, 'ID_texts', textId );
|
|
401
|
+
|
|
402
|
+
// "Early" include; only for element `locale`, which has its `key` property
|
|
403
|
+
// removed (or rather: it is not copied).
|
|
404
|
+
linkToOrigin( textsAspect.elements.locale, 'locale', art, 'elements', location );
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (addTextsLanguageAssoc && art.elements.language)
|
|
408
|
+
art.elements.language = undefined; // TODO: Message? Ignore?
|
|
409
|
+
// TODO: what is this necessary? We do not create a text entity in this case
|
|
410
|
+
|
|
411
|
+
setLink( art, '_block', model.$internal );
|
|
412
|
+
model.definitions[absolute] = art;
|
|
413
|
+
extendArtifactBefore( art ); // having extensions here would be wrong
|
|
414
|
+
return art;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* @param {XSN.Artifact} base
|
|
419
|
+
* @param {string} absolute
|
|
420
|
+
* @param {boolean} fioriEnabled
|
|
421
|
+
*/
|
|
422
|
+
function createTextsEntityWithDefaultElements( base, absolute, fioriEnabled ) {
|
|
423
|
+
const elements = Object.create(null);
|
|
424
|
+
const { location } = base.name;
|
|
425
|
+
const art = {
|
|
426
|
+
kind: 'entity',
|
|
427
|
+
name: { path: splitIntoPath( location, absolute ), absolute, location },
|
|
428
|
+
location: base.location,
|
|
429
|
+
elements,
|
|
430
|
+
$inferred: 'localized-entity',
|
|
431
|
+
};
|
|
432
|
+
// If there is a type `sap.common.Locale`, then use it as the type for the element `locale`.
|
|
433
|
+
// If not, use the default `cds.String` with a length of 14.
|
|
434
|
+
const hasLocaleType = model.definitions['sap.common.Locale']?.kind === 'type';
|
|
435
|
+
const locale = {
|
|
436
|
+
name: { location, id: 'locale' },
|
|
437
|
+
kind: 'element',
|
|
438
|
+
type: augmentPath( location, hasLocaleType ? 'sap.common.Locale' : 'cds.String' ),
|
|
439
|
+
location,
|
|
440
|
+
};
|
|
441
|
+
if (!hasLocaleType)
|
|
442
|
+
locale.length = { literal: 'number', val: 14, location };
|
|
443
|
+
|
|
444
|
+
if (!fioriEnabled) {
|
|
445
|
+
locale.key = { val: true, location };
|
|
446
|
+
// To be compatible, we switch off draft without @fiori.draft.enabled
|
|
447
|
+
// TODO (next major version): remove?
|
|
448
|
+
setAnnotation( art, '@odata.draft.enabled', art.location, false );
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
const textId = {
|
|
452
|
+
name: { location, id: 'ID_texts' },
|
|
453
|
+
kind: 'element',
|
|
454
|
+
key: { val: true, location },
|
|
455
|
+
type: augmentPath( location, 'cds.UUID' ),
|
|
456
|
+
location,
|
|
457
|
+
};
|
|
458
|
+
dictAdd( art.elements, 'ID_texts', textId );
|
|
459
|
+
}
|
|
460
|
+
dictAdd( art.elements, 'locale', locale );
|
|
461
|
+
|
|
462
|
+
setLink( art, '_block', model.$internal );
|
|
463
|
+
model.definitions[absolute] = art;
|
|
464
|
+
extendArtifactBefore( art ); // having extensions here would be wrong
|
|
465
|
+
return art;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* @param {XSN.Artifact} art
|
|
470
|
+
* @param {string} textsName
|
|
471
|
+
* @param {XSN.Element[]} textElems
|
|
472
|
+
*/
|
|
473
|
+
function addTextsAssociations( art, textsName, textElems ) {
|
|
474
|
+
// texts : Composition of many Books.texts on texts.ID=ID;
|
|
475
|
+
/** @type {array} */
|
|
476
|
+
const keys = textElems.filter( e => e.key && e.key.val );
|
|
477
|
+
const { location } = art.name;
|
|
478
|
+
const texts = {
|
|
479
|
+
name: { location, id: 'texts' },
|
|
480
|
+
kind: 'element',
|
|
481
|
+
location,
|
|
482
|
+
$inferred: 'localized',
|
|
483
|
+
type: augmentPath( location, 'cds.Composition' ),
|
|
484
|
+
cardinality: { targetMax: { literal: 'string', val: '*', location }, location },
|
|
485
|
+
target: augmentPath( location, textsName ),
|
|
486
|
+
on: augmentEqual( location, 'texts', keys ),
|
|
487
|
+
};
|
|
488
|
+
setMemberParent( texts, 'texts', art, 'elements' );
|
|
489
|
+
setLink( texts, '_block', model.$internal );
|
|
490
|
+
// localized : Association to Books.texts on
|
|
491
|
+
// localized.ID=ID and localized.locale = $user.locale;
|
|
492
|
+
keys.push( [ 'localized.locale', '$user.locale' ] );
|
|
493
|
+
const localized = {
|
|
494
|
+
name: { location, id: 'localized' },
|
|
495
|
+
kind: 'element',
|
|
496
|
+
location,
|
|
497
|
+
$inferred: 'localized',
|
|
498
|
+
type: augmentPath( location, 'cds.Association' ),
|
|
499
|
+
target: augmentPath( location, textsName ),
|
|
500
|
+
on: augmentEqual( location, 'localized', keys ),
|
|
501
|
+
};
|
|
502
|
+
setMemberParent( localized, 'localized', art, 'elements' );
|
|
503
|
+
setLink( localized, '_block', model.$internal );
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Create a structure that can be used as an item in `includes`.
|
|
508
|
+
*
|
|
509
|
+
* @param {string} name
|
|
510
|
+
* @param {XSN.Location} location
|
|
511
|
+
*/
|
|
512
|
+
function createInclude( name, location ) {
|
|
513
|
+
const include = {
|
|
514
|
+
path: [ { id: name, location } ],
|
|
515
|
+
location,
|
|
516
|
+
};
|
|
517
|
+
setArtifactLink( include.path[0], model.definitions[name] );
|
|
518
|
+
setArtifactLink( include, model.definitions[name] );
|
|
519
|
+
return include;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Returns whether `art` directly or indirectly has the property 'prop',
|
|
524
|
+
* following the 'origin' and the 'type' (not involving elements).
|
|
525
|
+
*
|
|
526
|
+
* DON'T USE FOR ANNOTATIONS (see TODO below)
|
|
527
|
+
*
|
|
528
|
+
* TODO: we should issue a warning if we get localized via TYPE OF
|
|
529
|
+
* TODO: XSN: for anno short form, use { val: true, location, <no literal prop> }
|
|
530
|
+
* ...then this function also works with annotations
|
|
531
|
+
*
|
|
532
|
+
* @param {XSN.Artifact} art
|
|
533
|
+
* @param {string} prop
|
|
534
|
+
* @returns {boolean}
|
|
535
|
+
*/
|
|
536
|
+
function hasTruthyProp( art, prop ) {
|
|
537
|
+
const processed = Object.create(null); // avoid infloops with circular refs
|
|
538
|
+
let name = art.name.absolute; // is ok, since no recursive type possible
|
|
539
|
+
while (art && !processed[name]) {
|
|
540
|
+
if (art[prop])
|
|
541
|
+
return art[prop].val;
|
|
542
|
+
processed[name] = art;
|
|
543
|
+
if (art._origin) {
|
|
544
|
+
art = art._origin;
|
|
545
|
+
if (!art.name) // anonymous aspect
|
|
546
|
+
return false;
|
|
547
|
+
name = art && art.name.absolute;
|
|
548
|
+
}
|
|
549
|
+
else if (art.type && art._block && art.type.scope !== 'typeOf') {
|
|
550
|
+
// TODO: also do something special for TYPE OF inside `art`s own elements
|
|
551
|
+
name = resolveUncheckedPath( art.type, 'type', art );
|
|
552
|
+
art = name && model.definitions[name];
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// managed composition of aspects ------------------------------------------
|
|
562
|
+
|
|
563
|
+
function processAspectComposition( base ) {
|
|
564
|
+
// TODO: we need to forbid COMPOSITION of entity w/o keys and ON anyway
|
|
565
|
+
// TODO: consider entity includes
|
|
566
|
+
// TODO: nested containment
|
|
567
|
+
// TODO: better do circular checks in the aspect!
|
|
568
|
+
if (base.kind !== 'entity' || base.query)
|
|
569
|
+
return;
|
|
570
|
+
const keys = baseKeys();
|
|
571
|
+
if (keys)
|
|
572
|
+
forEachGeneric( base, 'elements', expand ); // TODO: recursively here?
|
|
573
|
+
return;
|
|
574
|
+
|
|
575
|
+
function baseKeys() {
|
|
576
|
+
const k = Object.create(null);
|
|
577
|
+
for (const name in base.elements) {
|
|
578
|
+
const elem = base.elements[name];
|
|
579
|
+
if (elem.$duplicates)
|
|
580
|
+
return false; // no composition-of-type unfold with redefined elems
|
|
581
|
+
if (elem.key && elem.key.val)
|
|
582
|
+
k[name] = elem;
|
|
583
|
+
}
|
|
584
|
+
return k;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function expand( elem ) {
|
|
588
|
+
if (elem.target)
|
|
589
|
+
return;
|
|
590
|
+
let origin = elem;
|
|
591
|
+
// included element do not have target aspect directly
|
|
592
|
+
while (origin && !origin.targetAspect && origin._origin)
|
|
593
|
+
origin = origin._origin;
|
|
594
|
+
let target = origin.targetAspect;
|
|
595
|
+
if (target && target.path)
|
|
596
|
+
target = resolvePath( origin.targetAspect, 'compositionTarget', origin );
|
|
597
|
+
if (!target || !target.elements)
|
|
598
|
+
return;
|
|
599
|
+
const entityName = `${ base.name.absolute }.${ elem.name.id }`;
|
|
600
|
+
const entity = allowAspectComposition( target, elem, keys, entityName ) &&
|
|
601
|
+
createTargetEntity( target, elem, keys, entityName, base );
|
|
602
|
+
elem.target = {
|
|
603
|
+
location: (elem.targetAspect || elem).location,
|
|
604
|
+
$inferred: 'aspect-composition',
|
|
605
|
+
};
|
|
606
|
+
setArtifactLink( elem.target, entity );
|
|
607
|
+
if (entity) {
|
|
608
|
+
// Support using the up_ element in the generated entity to be used
|
|
609
|
+
// inside the anonymous aspect:
|
|
610
|
+
const { up_ } = target.$tableAliases;
|
|
611
|
+
// TODO: invalidate "up_" alias (at least further navigation) if it
|
|
612
|
+
// already has an _origin (when the managed composition is included)
|
|
613
|
+
if (up_)
|
|
614
|
+
setLink( up_, '_origin', entity.elements.up_ );
|
|
615
|
+
model.$compositionTargets[entity.name.absolute] = true;
|
|
616
|
+
processAspectComposition( entity );
|
|
617
|
+
processLocalizedData( entity );
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* @returns {boolean|0} `true`, if allowed, `false` if forbidden, `0` if circular containment.
|
|
624
|
+
*/
|
|
625
|
+
function allowAspectComposition( target, elem, keys, entityName ) {
|
|
626
|
+
if (!target.elements || Object.values( target.elements ).some( e => e.$duplicates ))
|
|
627
|
+
return false; // no elements or with redefinitions
|
|
628
|
+
const location = elem.target && elem.target.location || elem.location;
|
|
629
|
+
if ((elem._main._upperAspects || []).includes( target ))
|
|
630
|
+
return 0; // circular containment of the same aspect
|
|
631
|
+
|
|
632
|
+
const keyNames = Object.keys( keys );
|
|
633
|
+
if (!keyNames.length) {
|
|
634
|
+
// TODO: for "inner aspect-compositions", signal already in type
|
|
635
|
+
error( null, [ location, elem ], { target },
|
|
636
|
+
'An aspect $(TARGET) can\'t be used as target in an entity without keys' );
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
// if (keys.up_) { // only to be tested if we allow to provide a prefix, which could be ''
|
|
640
|
+
// // Cannot be in an "inner aspect-compositions" as it would already be wrong before
|
|
641
|
+
// // TODO: if anonymous type, use location of "up_" element
|
|
642
|
+
// // FUTURE: add sub info with location of "up_" element
|
|
643
|
+
// message( 'id', [location, elem], { target, name: 'up_' }, 'Error',
|
|
644
|
+
// 'An aspect $(TARGET) can't be used as target in an entity with a key named $(NAME)' );
|
|
645
|
+
// return false;
|
|
646
|
+
// }
|
|
647
|
+
if (target.elements.up_) {
|
|
648
|
+
// TODO: for "inner aspect-compositions", signal already in type
|
|
649
|
+
// TODO: if anonymous type, use location of "up_" element
|
|
650
|
+
// FUTURE: if named type, add sub info with location of "up_" element
|
|
651
|
+
error( null, [ location, elem ], { target, name: 'up_' },
|
|
652
|
+
'An aspect $(TARGET) with an element named $(NAME) can\'t be used as target' );
|
|
653
|
+
return false;
|
|
654
|
+
}
|
|
655
|
+
if (model.definitions[entityName]) {
|
|
656
|
+
error( null, [ location, elem ], { art: entityName },
|
|
657
|
+
// eslint-disable-next-line max-len
|
|
658
|
+
'Target entity $(ART) can\'t be created as there is another definition with this name' );
|
|
659
|
+
return false;
|
|
660
|
+
}
|
|
661
|
+
const names = Object.keys( target.elements )
|
|
662
|
+
.filter( n => n.startsWith('up__') && keyNames.includes( n.substring(4) ) );
|
|
663
|
+
if (names.length) {
|
|
664
|
+
// FUTURE: if named type, add sub info with location of "up_" element
|
|
665
|
+
error( null, [ location, elem ], { target: entityName, names }, {
|
|
666
|
+
std: 'Key elements $(NAMES) can\'t be added to $(TARGET) as these already exist',
|
|
667
|
+
one: 'Key element $(NAMES) can\'t be added to $(TARGET) as it already exist',
|
|
668
|
+
});
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
if (elem.type && !isDirectComposition(elem)) {
|
|
673
|
+
// Only issue warning for direct usages, not for projections, includes, etc.
|
|
674
|
+
// TODO: Make it configurable error; v4: error
|
|
675
|
+
warning( 'def-expected-comp-aspect', [ elem.type.location, elem ],
|
|
676
|
+
{ prop: 'Composition of', otherprop: 'Association to' },
|
|
677
|
+
'Expected $(PROP), but found $(OTHERPROP) for composition of aspect');
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
return true;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function createTargetEntity( target, elem, keys, entityName, base ) {
|
|
684
|
+
const { location } = elem.targetAspect || elem.target || elem;
|
|
685
|
+
elem.on = {
|
|
686
|
+
location,
|
|
687
|
+
op: { val: '=', location },
|
|
688
|
+
args: [
|
|
689
|
+
augmentPath( location, elem.name.id, 'up_' ),
|
|
690
|
+
augmentPath( location, '$self' ),
|
|
691
|
+
],
|
|
692
|
+
$inferred: 'aspect-composition',
|
|
693
|
+
};
|
|
694
|
+
|
|
695
|
+
const elements = Object.create(null);
|
|
696
|
+
const art = {
|
|
697
|
+
kind: 'entity',
|
|
698
|
+
name: { path: splitIntoPath( location, entityName ), absolute: entityName, location },
|
|
699
|
+
location,
|
|
700
|
+
elements,
|
|
701
|
+
$inferred: 'composition-entity',
|
|
702
|
+
};
|
|
703
|
+
if (target.name) { // named target aspect
|
|
704
|
+
setLink( art, '_origin', target );
|
|
705
|
+
setLink( art, '_upperAspects', [ target, ...(elem._main._upperAspects || []) ] );
|
|
706
|
+
}
|
|
707
|
+
else {
|
|
708
|
+
setLink( art, '_origin', target );
|
|
709
|
+
// TODO: do we need to give the anonymous target aspect a kind and name?
|
|
710
|
+
setLink( art, '_upperAspects', elem._main._upperAspects || [] );
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const up = { // elements.up_ = ...
|
|
714
|
+
name: { location, id: 'up_' },
|
|
715
|
+
kind: 'element',
|
|
716
|
+
location,
|
|
717
|
+
$inferred: 'aspect-composition',
|
|
718
|
+
type: augmentPath( location, 'cds.Association' ),
|
|
719
|
+
target: augmentPath( location, base.name.absolute ),
|
|
720
|
+
cardinality: {
|
|
721
|
+
targetMin: { val: 1, literal: 'number', location },
|
|
722
|
+
targetMax: { val: 1, literal: 'number', location },
|
|
723
|
+
location,
|
|
724
|
+
},
|
|
725
|
+
};
|
|
726
|
+
// By default, 'up_' is a managed primary key association.
|
|
727
|
+
// If 'up_' shall be rendered unmanaged, infer the parent
|
|
728
|
+
// primary keys and add the ON condition
|
|
729
|
+
if (isDeprecatedEnabled( options, '_unmanagedUpInComponent' )) {
|
|
730
|
+
addProxyElements( art, keys, 'aspect-composition', target.name && location,
|
|
731
|
+
'up__', '@odata.containment.ignore' );
|
|
732
|
+
up.on = augmentEqual( location, 'up_', Object.values( keys ), 'up__' );
|
|
733
|
+
}
|
|
734
|
+
else {
|
|
735
|
+
up.key = { location, val: true };
|
|
736
|
+
// managed associations must be explicitly set to not null
|
|
737
|
+
// even if target cardinality is 1..1
|
|
738
|
+
up.notNull = { location, val: true };
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
dictAdd( art.elements, 'up_', up);
|
|
742
|
+
addProxyElements( art, target.elements, 'aspect-composition', target.name && location );
|
|
743
|
+
|
|
744
|
+
setLink( art, '_block', model.$internal );
|
|
745
|
+
model.definitions[entityName] = art;
|
|
746
|
+
initArtifact( art );
|
|
747
|
+
|
|
748
|
+
extendArtifactBefore( art ); // having extensions here would be wrong
|
|
749
|
+
// Copy persistence annotations from aspect.
|
|
750
|
+
copyPersistenceAnnotations( art, target ); // after chooseAnnotation()
|
|
751
|
+
return art;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function addProxyElements( proxyDict, elements, inferred, location, prefix = '', anno = '' ) {
|
|
755
|
+
// TODO: also use for includeMembers()?
|
|
756
|
+
for (const name in elements) {
|
|
757
|
+
const pname = `${ prefix }${ name }`;
|
|
758
|
+
const origin = elements[name];
|
|
759
|
+
const proxy = linkToOrigin( origin, pname, null, null, location || origin.location );
|
|
760
|
+
proxy.$inferred = inferred;
|
|
761
|
+
if (origin.masked)
|
|
762
|
+
proxy.masked = Object.assign( { $inferred: 'include' }, origin.masked );
|
|
763
|
+
if (origin.key)
|
|
764
|
+
proxy.key = Object.assign( { $inferred: 'include' }, origin.key );
|
|
765
|
+
if (anno)
|
|
766
|
+
setAnnotation( proxy, anno );
|
|
767
|
+
dictAdd( proxyDict.elements, pname, proxy );
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Copy the annotations `@cds.persistence.skip`/`@cds.persistence.exists` from
|
|
773
|
+
* source to target if present on source but not target.
|
|
774
|
+
*
|
|
775
|
+
* @param {object} target
|
|
776
|
+
* @param {object} source
|
|
777
|
+
*/
|
|
778
|
+
function copyPersistenceAnnotations( target, source ) {
|
|
779
|
+
if (!source)
|
|
780
|
+
return;
|
|
781
|
+
|
|
782
|
+
const copyExists = !isDeprecatedEnabled( options, 'eagerPersistenceForGeneratedEntities' );
|
|
783
|
+
if (copyExists)
|
|
784
|
+
copy( '@cds.persistence.exists' );
|
|
785
|
+
copy( '@cds.persistence.skip' );
|
|
786
|
+
|
|
787
|
+
function copy( anno ) {
|
|
788
|
+
if ( source[anno] && !target[anno] )
|
|
789
|
+
target[anno] = { ...source[anno], $inferred: 'parent-origin' };
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function augmentEqual( location, assocname, relations, prefix = '' ) {
|
|
795
|
+
const args = relations.map( eq );
|
|
796
|
+
return (args.length === 1)
|
|
797
|
+
? args[0]
|
|
798
|
+
: { op: { val: 'and', location }, args, location };
|
|
799
|
+
|
|
800
|
+
function eq( refs ) {
|
|
801
|
+
if (Array.isArray(refs))
|
|
802
|
+
return { op: { val: '=', location }, args: refs.map( ref ), location };
|
|
803
|
+
|
|
804
|
+
const { id } = refs.name;
|
|
805
|
+
return {
|
|
806
|
+
op: { val: '=', location },
|
|
807
|
+
args: [
|
|
808
|
+
{ path: [ { id: assocname, location }, { id, location } ], location },
|
|
809
|
+
{ path: [ { id: `${ prefix }${ id }`, location } ], location },
|
|
810
|
+
],
|
|
811
|
+
location,
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
function ref( path ) {
|
|
815
|
+
return { path: path.split('.').map( id => ({ id, location }) ), location };
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
function checkTextsLanguageAssocOption( model, options ) {
|
|
820
|
+
const languages = model.definitions['sap.common.Languages'];
|
|
821
|
+
const commonLanguagesEntity = options.addTextsLanguageAssoc && languages?.elements?.code;
|
|
822
|
+
|
|
823
|
+
if (options.addTextsLanguageAssoc && !commonLanguagesEntity) {
|
|
824
|
+
const variant = !languages ? 'std' : 'code';
|
|
825
|
+
const loc = model.definitions['sap.common.Languages']?.name?.location || null;
|
|
826
|
+
model.$messageFunctions.info('api-ignoring-language-assoc', loc, {
|
|
827
|
+
'#': variant, option: 'addTextsLanguageAssoc', art: 'sap.common.Languages', name: 'code',
|
|
828
|
+
}, {
|
|
829
|
+
std: 'Ignoring option $(OPTION) because entity $(ART) is missing',
|
|
830
|
+
code: 'Ignoring option $(OPTION) because entity $(ART) is missing element $(NAME)',
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
return !!commonLanguagesEntity;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
module.exports = generate;
|