@sap/cds-compiler 3.1.0 → 3.3.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 +90 -3
- package/bin/cdsc.js +1 -1
- package/doc/CHANGELOG_BETA.md +18 -0
- package/lib/api/main.js +8 -13
- package/lib/base/error.js +2 -2
- package/lib/base/keywords.js +2 -24
- package/lib/base/message-registry.js +43 -14
- package/lib/base/messages.js +20 -10
- package/lib/base/model.js +1 -1
- package/lib/checks/actionsFunctions.js +1 -1
- package/lib/checks/annotationsOData.js +2 -2
- package/lib/checks/arrayOfs.js +15 -7
- package/lib/checks/cdsPersistence.js +1 -1
- package/lib/checks/checkForTypes.js +48 -0
- package/lib/checks/defaultValues.js +2 -2
- package/lib/checks/elements.js +81 -6
- package/lib/checks/foreignKeys.js +12 -13
- package/lib/checks/invalidTarget.js +10 -11
- package/lib/checks/managedInType.js +21 -15
- package/lib/checks/nullableKeys.js +1 -1
- package/lib/checks/onConditions.js +9 -9
- package/lib/checks/parameters.js +21 -0
- package/lib/checks/selectItems.js +1 -1
- package/lib/checks/types.js +2 -2
- package/lib/checks/utils.js +17 -7
- package/lib/checks/validator.js +26 -14
- package/lib/compiler/assert-consistency.js +13 -6
- package/lib/compiler/builtins.js +8 -0
- package/lib/compiler/checks.js +40 -33
- package/lib/compiler/define.js +50 -44
- package/lib/compiler/extend.js +303 -37
- package/lib/compiler/kick-start.js +2 -35
- package/lib/compiler/populate.js +83 -62
- package/lib/compiler/propagator.js +1 -1
- package/lib/compiler/resolve.js +61 -104
- package/lib/compiler/shared.js +16 -6
- package/lib/compiler/tweak-assocs.js +25 -12
- package/lib/compiler/utils.js +2 -2
- package/lib/edm/annotations/genericTranslation.js +15 -5
- package/lib/edm/csn2edm.js +10 -10
- package/lib/edm/edm.js +17 -9
- package/lib/edm/edmPreprocessor.js +82 -42
- package/lib/edm/edmUtils.js +18 -16
- package/lib/gen/Dictionary.json +14 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -2
- package/lib/gen/languageParser.js +4205 -4100
- package/lib/inspect/inspectModelStatistics.js +1 -1
- package/lib/inspect/inspectPropagation.js +23 -9
- package/lib/json/csnVersion.js +1 -1
- package/lib/json/from-csn.js +26 -19
- package/lib/json/to-csn.js +47 -5
- package/lib/language/antlrParser.js +1 -1
- package/lib/language/genericAntlrParser.js +29 -13
- package/lib/language/language.g4 +28 -8
- package/lib/main.d.ts +3 -6
- package/lib/model/.eslintrc.json +13 -0
- package/lib/model/api.js +4 -2
- package/lib/model/csnRefs.js +74 -47
- package/lib/model/csnUtils.js +236 -218
- package/lib/model/enrichCsn.js +41 -31
- package/lib/model/revealInternalProperties.js +61 -57
- package/lib/model/sortViews.js +31 -31
- package/lib/modelCompare/compare.js +6 -6
- package/lib/optionProcessor.js +5 -0
- package/lib/render/manageConstraints.js +2 -2
- package/lib/render/toCdl.js +31 -44
- package/lib/render/toHdbcds.js +7 -5
- package/lib/render/toRename.js +4 -4
- package/lib/render/toSql.js +11 -5
- package/lib/render/utils/common.js +20 -9
- package/lib/render/utils/sql.js +5 -5
- package/lib/transform/db/applyTransformations.js +32 -3
- package/lib/transform/db/expansion.js +81 -37
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/db/transformExists.js +1 -1
- package/lib/transform/forOdataNew.js +10 -7
- package/lib/transform/{forHanaNew.js → forRelationalDB.js} +7 -7
- package/lib/transform/localized.js +28 -19
- package/lib/transform/odata/toFinalBaseType.js +8 -11
- package/lib/transform/odata/typesExposure.js +1 -1
- package/lib/transform/transformUtilsNew.js +101 -39
- package/lib/transform/translateAssocsToJoins.js +5 -4
- package/lib/utils/moduleResolve.js +5 -5
- package/lib/utils/objectUtils.js +3 -3
- package/package.json +2 -2
- package/share/messages/anno-duplicate-unrelated-layer.md +6 -6
- package/share/messages/check-proper-type-of.md +4 -4
- package/share/messages/check-proper-type.md +2 -2
- package/share/messages/duplicate-autoexposed.md +4 -4
- package/share/messages/extend-repeated-intralayer.md +4 -5
- package/share/messages/extend-unrelated-layer.md +4 -4
- package/share/messages/message-explanations.json +3 -1
- package/share/messages/redirected-to-ambiguous.md +7 -6
- package/share/messages/redirected-to-complex.md +63 -0
- package/share/messages/redirected-to-unrelated.md +6 -5
- package/share/messages/rewrite-not-supported.md +4 -4
- package/share/messages/syntax-expected-integer.md +3 -3
- package/share/messages/wildcard-excluding-one.md +37 -0
package/lib/compiler/extend.js
CHANGED
|
@@ -22,7 +22,8 @@ const {
|
|
|
22
22
|
augmentPath,
|
|
23
23
|
splitIntoPath,
|
|
24
24
|
} = require('./utils');
|
|
25
|
-
const
|
|
25
|
+
const layers = require('./moduleLayers');
|
|
26
|
+
const { typeParameters } = require('./builtins');
|
|
26
27
|
|
|
27
28
|
function extend( model ) {
|
|
28
29
|
const { options } = model;
|
|
@@ -44,6 +45,9 @@ function extend( model ) {
|
|
|
44
45
|
|
|
45
46
|
Object.assign( model.$functions, {
|
|
46
47
|
lateExtensions,
|
|
48
|
+
layeredAssignments,
|
|
49
|
+
assignmentsOfHighestLayers,
|
|
50
|
+
applyTypeExtensions,
|
|
47
51
|
} );
|
|
48
52
|
|
|
49
53
|
applyExtensions();
|
|
@@ -74,6 +78,9 @@ function extend( model ) {
|
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
/**
|
|
81
|
+
* Propagate the given `prop` (e.g. annotation) early, i.e. copy it from all `.includes`
|
|
82
|
+
* if they have the property.
|
|
83
|
+
*
|
|
77
84
|
* @param {XSN.Definition} art
|
|
78
85
|
* @param {string} prop
|
|
79
86
|
*/
|
|
@@ -163,10 +170,10 @@ function extend( model ) {
|
|
|
163
170
|
if (ext.expectedKind === 'context' || ext.expectedKind === 'service') {
|
|
164
171
|
const loc = ext.name.location;
|
|
165
172
|
// TODO: warning is enough
|
|
166
|
-
error( 'extend-with-artifacts', [ loc, ext ], { name, '#': ext.expectedKind }, {
|
|
167
|
-
std: '
|
|
168
|
-
service: '
|
|
169
|
-
context: '
|
|
173
|
+
error( 'extend-with-artifacts', [ loc, ext ], { name, '#': ext.expectedKind, keyword: `EXTEND ${ ext.expectedKind }` }, {
|
|
174
|
+
std: 'Can\'t extend non-context / non-service $(NAME) with $(KEYWORD)',
|
|
175
|
+
service: 'Can\'t extend non-service $(NAME) with $(KEYWORD)',
|
|
176
|
+
context: 'Can\'t extend non-context $(NAME) with $(KEYWORD)',
|
|
170
177
|
});
|
|
171
178
|
}
|
|
172
179
|
}
|
|
@@ -182,7 +189,7 @@ function extend( model ) {
|
|
|
182
189
|
checkDefinitions( ext, art, 'columns');
|
|
183
190
|
if (ext.includes)
|
|
184
191
|
applyIncludes( ext, art ); // emits error if `includes` is set
|
|
185
|
-
if (ext.kind === 'annotate')
|
|
192
|
+
if (ext.kind === 'annotate' || ext.kind === 'extend')
|
|
186
193
|
checkAnnotate( ext, art );
|
|
187
194
|
defineAnnotations( ext, art, ext._block, ext.kind );
|
|
188
195
|
}
|
|
@@ -221,7 +228,8 @@ function extend( model ) {
|
|
|
221
228
|
function extendMembers( extensions, art, noExtend ) {
|
|
222
229
|
// TODO: do the whole extension stuff lazily if the elements are requested
|
|
223
230
|
const elemExtensions = [];
|
|
224
|
-
extensions.sort( compareLayer );
|
|
231
|
+
extensions.sort( layers.compareLayer );
|
|
232
|
+
// TODO: use same sequence as in chooseAssignment() - better: use common code with that fn
|
|
225
233
|
for (const ext of extensions) {
|
|
226
234
|
// console.log(message( 'id', [ext.location, ext], { art: ext.name._artifact },
|
|
227
235
|
// 'Info', 'EXT').toString())
|
|
@@ -243,18 +251,19 @@ function extend( model ) {
|
|
|
243
251
|
art.includes = [ ...ext.includes ];
|
|
244
252
|
applyIncludes( ext, art );
|
|
245
253
|
}
|
|
246
|
-
if (ext.kind === 'annotate')
|
|
254
|
+
if (ext.kind === 'annotate' || ext.kind === 'extend')
|
|
247
255
|
checkAnnotate( ext, art );
|
|
248
256
|
defineAnnotations( ext, art, ext._block, ext.kind );
|
|
249
257
|
// TODO: do we allow to add elements with array of {...}? If yes, adapt
|
|
250
258
|
initMembers( ext, art, ext._block ); // might set _extend, _annotate
|
|
259
|
+
storeTypeExtension( ext, art );
|
|
251
260
|
dependsOnSilent(art, ext); // art depends silently on ext (inverse to normal dep!)
|
|
252
261
|
}
|
|
253
262
|
for (const name in ext.elements) {
|
|
254
263
|
const elem = ext.elements[name];
|
|
255
264
|
if (elem.kind === 'element') { // i.e. not extend or annotate
|
|
256
265
|
elemExtensions.push( elem );
|
|
257
|
-
break;
|
|
266
|
+
break; // more than one elem in same EXTEND is fine
|
|
258
267
|
}
|
|
259
268
|
}
|
|
260
269
|
|
|
@@ -263,6 +272,8 @@ function extend( model ) {
|
|
|
263
272
|
}
|
|
264
273
|
if (elemExtensions.length > 1)
|
|
265
274
|
reportUnstableExtensions( elemExtensions );
|
|
275
|
+
if (art._extendType && art._extendType.length > 0)
|
|
276
|
+
reportTypeExtensionsInSameLayer( art._extendType );
|
|
266
277
|
|
|
267
278
|
[ 'elements', 'actions' ].forEach( (prop) => {
|
|
268
279
|
const dict = art._extend && art._extend[prop];
|
|
@@ -315,14 +326,115 @@ function extend( model ) {
|
|
|
315
326
|
}
|
|
316
327
|
}
|
|
317
328
|
|
|
329
|
+
/**
|
|
330
|
+
* Similar to chooseAssignment for annotations, this function applies type extensions in order,
|
|
331
|
+
* such that hierarchies are respected.
|
|
332
|
+
* Order already set in `extendMembers()` using `compareLayers()`.
|
|
333
|
+
*
|
|
334
|
+
* @param art
|
|
335
|
+
*/
|
|
336
|
+
function applyTypeExtensions(art) {
|
|
337
|
+
/**
|
|
338
|
+
* Contains the previous extension for each property that was applied
|
|
339
|
+
* successfully.
|
|
340
|
+
*/
|
|
341
|
+
const previousSuccess = Object.create(null);
|
|
342
|
+
const allowedBuiltinCategories = [ 'string', 'decimal', 'integer', 'binary' ];
|
|
343
|
+
const artType = art.type?._artifact;
|
|
344
|
+
|
|
345
|
+
for (const ext of art._extendType) {
|
|
346
|
+
if (art.$inferred) {
|
|
347
|
+
// Only report first extension for $inferred artifact. Reduces noise.
|
|
348
|
+
error('ref-expected-scalar-type', [ ext.name.location, ext ], { '#': 'inferred' } );
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const baseType = art._effectiveType; // may be an ENUM
|
|
353
|
+
if (!artType || (art.kind !== 'type' && art.kind !== 'element') || !baseType?.builtin) {
|
|
354
|
+
// Only report first extension for non-scalar base type. Reduces noise.
|
|
355
|
+
error('ref-expected-scalar-type', [ ext.name.location, ext ], { } );
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
else if (!allowedBuiltinCategories.includes(baseType.category)) {
|
|
359
|
+
// Only report first extension for non-scalar type. Reduces noise.
|
|
360
|
+
error('ref-expected-scalar-type', [ ext.name.location, ext ],
|
|
361
|
+
{ '#': 'unsupported', prop: baseType.category });
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
for (const prop of typeParameters.list) {
|
|
366
|
+
if (!ext[prop])
|
|
367
|
+
continue;
|
|
368
|
+
|
|
369
|
+
if (!baseType.parameters?.includes(prop)) {
|
|
370
|
+
// For `extend T with type (length:10)` where T does not expect a length.
|
|
371
|
+
error( 'type-unexpected-argument', [ ext[prop].location, ext ], {
|
|
372
|
+
'#': 'type', prop, art, type: baseType,
|
|
373
|
+
});
|
|
374
|
+
break; // one error for first property is enough
|
|
375
|
+
}
|
|
376
|
+
else if (!art[prop]) {
|
|
377
|
+
// Can only extend properties that exist directly on the artifact.
|
|
378
|
+
error('ext-invalid-type-property', [ ext[prop].location, ext ],
|
|
379
|
+
{ prop, '#': 'new-prop' });
|
|
380
|
+
break; // one error for first property is enough
|
|
381
|
+
}
|
|
382
|
+
else if (art[prop].val === ext[prop].val) {
|
|
383
|
+
// Ignore extensions with same value
|
|
384
|
+
}
|
|
385
|
+
else if (art[prop].literal === 'string' || ext[prop].literal === 'string' ) {
|
|
386
|
+
// Users can't change from/to string value for property,
|
|
387
|
+
// e.g. `variable`/`floating` for Decimal
|
|
388
|
+
error( 'ext-invalid-type-property', [ ext[prop].location, ext ],
|
|
389
|
+
{ '#': 'string', prop } );
|
|
390
|
+
}
|
|
391
|
+
else if (art[prop].val > ext[prop].val) {
|
|
392
|
+
// TODO: Future: Sub-message that points to previous extension?
|
|
393
|
+
error( 'ext-invalid-type-property', [ ext[prop].location, ext ], {
|
|
394
|
+
prop,
|
|
395
|
+
literal: ext[prop].val,
|
|
396
|
+
number: art[prop].val,
|
|
397
|
+
'#': previousSuccess[prop] ? 'ext-smaller' : 'smaller',
|
|
398
|
+
} );
|
|
399
|
+
break; // one error for first property is enough
|
|
400
|
+
}
|
|
401
|
+
else if (art[prop].val < ext[prop].val) {
|
|
402
|
+
art[prop] = ext[prop];
|
|
403
|
+
previousSuccess[prop] = ext;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// If `scale` is increased, but `precision` is not, data may be lost in a migration.
|
|
408
|
+
// e.g. `Decimal(4,2)` allows `12.34`. Increasing scale to 3 would make this value
|
|
409
|
+
// invalid and only allow `1.234`.
|
|
410
|
+
// TODO: Should we actually check that the increase is correct?
|
|
411
|
+
if (ext.scale && !ext.precision) {
|
|
412
|
+
error('ext-invalid-type-property', [ ext.scale.location, ext ], {
|
|
413
|
+
prop: 'scale',
|
|
414
|
+
otherprop: 'precision',
|
|
415
|
+
'#': 'scale',
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Report 'Warning: Unstable element order due to repeated extensions'
|
|
423
|
+
* except if all extensions are in the same file.
|
|
424
|
+
*
|
|
425
|
+
* @param {XSN.Extension[]} extensions
|
|
426
|
+
*/
|
|
318
427
|
function reportUnstableExtensions( extensions ) {
|
|
319
|
-
//
|
|
428
|
+
// No message if all extensions are in the same file:
|
|
429
|
+
const file = layers.realname( extensions[0] );
|
|
430
|
+
if (extensions.every( ( ext, i ) => !i || file === layers.realname( ext ) ))
|
|
431
|
+
return;
|
|
320
432
|
// Similar to chooseAssignment(), TODO there: also extra intralayer message
|
|
321
433
|
// as this is a modeling error
|
|
322
434
|
let lastExt = null;
|
|
323
435
|
let open = []; // the "highest" layers
|
|
324
436
|
for (const ext of extensions) {
|
|
325
|
-
const extLayer = layer( ext ) || { realname: '', _layerExtends: Object.create(null) };
|
|
437
|
+
const extLayer = layers.layer( ext ) || { realname: '', _layerExtends: Object.create(null) };
|
|
326
438
|
if (!open.length) {
|
|
327
439
|
lastExt = ext;
|
|
328
440
|
open = [ extLayer.realname ];
|
|
@@ -346,6 +458,28 @@ function extend( model ) {
|
|
|
346
458
|
}
|
|
347
459
|
}
|
|
348
460
|
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Report type extensions in same layer, similar mechanism to chooseAssignment()
|
|
464
|
+
* for annotations.
|
|
465
|
+
*
|
|
466
|
+
* @param {XSN.Extension[]} extensions
|
|
467
|
+
*/
|
|
468
|
+
function reportTypeExtensionsInSameLayer( extensions ) {
|
|
469
|
+
// Group assignments by layer
|
|
470
|
+
const extLayers = layeredAssignments( extensions );
|
|
471
|
+
// We only care about the highest layer
|
|
472
|
+
const { assignments, issue } = assignmentsOfHighestLayers( extLayers );
|
|
473
|
+
|
|
474
|
+
if (issue) {
|
|
475
|
+
const id = (issue === 'unrelated')
|
|
476
|
+
? 'ext-duplicate-extend-type-unrelated-layer'
|
|
477
|
+
: 'ext-duplicate-extend-type';
|
|
478
|
+
for (const ext of assignments)
|
|
479
|
+
message( id, [ ext.name.location, ext ], { type: ext.name._artifact } );
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
349
483
|
/**
|
|
350
484
|
* @param {XSN.Extension[]} extensions
|
|
351
485
|
* @param {string} prop
|
|
@@ -354,12 +488,14 @@ function extend( model ) {
|
|
|
354
488
|
* @param {object} validDict
|
|
355
489
|
*/
|
|
356
490
|
function extendNothing( extensions, prop, name, art, validDict ) {
|
|
491
|
+
const artName = searchName( art, name, dictKinds[prop] );
|
|
357
492
|
for (const ext of extensions) {
|
|
358
493
|
// TODO: use shared functionality with notFound in resolver.js
|
|
359
494
|
const { location } = ext.name;
|
|
495
|
+
const extName = { ...artName, kind: ext.kind };
|
|
360
496
|
const msg
|
|
361
|
-
= error( 'extend-undefined', [ location,
|
|
362
|
-
{ art:
|
|
497
|
+
= error( 'extend-undefined', [ location, extName ],
|
|
498
|
+
{ art: artName },
|
|
363
499
|
{
|
|
364
500
|
std: 'Unknown $(ART) - nothing to extend',
|
|
365
501
|
// eslint-disable-next-line max-len
|
|
@@ -444,7 +580,11 @@ function extend( model ) {
|
|
|
444
580
|
// includes ----------------------------------------------------------------
|
|
445
581
|
|
|
446
582
|
/**
|
|
583
|
+
* Returns true, if `includes` can be applied. They can't be applied if
|
|
584
|
+
* any of the artifacts referenced in `includes` are yet to be extended.
|
|
585
|
+
*
|
|
447
586
|
* @param {XSN.Definition} art
|
|
587
|
+
* @returns {boolean}
|
|
448
588
|
*/
|
|
449
589
|
function canApplyIncludes( art ) {
|
|
450
590
|
if (art.includes) {
|
|
@@ -458,6 +598,15 @@ function extend( model ) {
|
|
|
458
598
|
}
|
|
459
599
|
|
|
460
600
|
/**
|
|
601
|
+
* Apply all includes of `ext` on `ext`. Checks that `art` allows includes.
|
|
602
|
+
* If `ext === art`, then includes of the artifact itself are applied.
|
|
603
|
+
* If `ext !== art`, applies includes on the extensions, not artifact.
|
|
604
|
+
* Sets `_ancestor` links on `art`.
|
|
605
|
+
*
|
|
606
|
+
* Examples:
|
|
607
|
+
* ext === art: `entity E : F {}` => add elements of F to E
|
|
608
|
+
* ext !== art: `extend E with F` => add elements of F to extension on E
|
|
609
|
+
*
|
|
461
610
|
* @param {XSN.Extension} ext
|
|
462
611
|
* @param {XSN.Artifact} art
|
|
463
612
|
*/
|
|
@@ -471,37 +620,42 @@ function extend( model ) {
|
|
|
471
620
|
if (!art._ancestors)
|
|
472
621
|
setLink( art, '_ancestors', [] ); // recursive array of includes
|
|
473
622
|
for (const ref of ext.includes) {
|
|
474
|
-
const template = ref._artifact;
|
|
623
|
+
const template = ref._artifact;
|
|
624
|
+
// !template -> non-includable, e.g. scalar type, or cyclic
|
|
475
625
|
if (template) {
|
|
476
626
|
if (template._ancestors)
|
|
477
627
|
art._ancestors.push( ...template._ancestors );
|
|
478
628
|
art._ancestors.push( template );
|
|
479
629
|
}
|
|
480
630
|
}
|
|
481
|
-
includeMembers( ext, 'elements'
|
|
482
|
-
includeMembers( ext, 'actions'
|
|
631
|
+
includeMembers( ext, art, 'elements' );
|
|
632
|
+
includeMembers( ext, art, 'actions' );
|
|
483
633
|
}
|
|
484
634
|
|
|
485
635
|
/**
|
|
636
|
+
* Add all members (e.g. elements or actions) of `ext.includes` to `ext[prop]`
|
|
637
|
+
* if `parent` is `false` or to `parent` with appropriate _origin links otherwise.
|
|
638
|
+
* Included members are prepended to existing ones.
|
|
639
|
+
*
|
|
486
640
|
* @param {XSN.Extension} ext
|
|
641
|
+
* @param {XSN.Artifact} art
|
|
487
642
|
* @param {string} prop
|
|
488
|
-
* @param {function} forEach
|
|
489
|
-
* @param {XSN.Artifact} parent
|
|
490
643
|
*/
|
|
491
|
-
function includeMembers( ext,
|
|
644
|
+
function includeMembers( ext, art, prop ) {
|
|
492
645
|
// TODO two kind of messages:
|
|
493
646
|
// Error 'More than one include defines element "A"' (at include ref)
|
|
494
647
|
// Warning 'Overwrites definition from include "I" (at elem def)
|
|
648
|
+
const parent = ext === art && art;
|
|
495
649
|
const members = ext[prop];
|
|
496
650
|
ext[prop] = Object.create(null); // TODO: do not set actions property if there are none
|
|
497
651
|
for (const ref of ext.includes) {
|
|
498
652
|
const template = ref._artifact; // already resolved
|
|
499
653
|
if (template) { // be robust
|
|
500
|
-
|
|
654
|
+
forEachInOrder( template, prop, ( origin, name ) => {
|
|
501
655
|
if (members && name in members)
|
|
502
656
|
return; // TODO: warning for overwritten element
|
|
503
657
|
const elem = linkToOrigin( origin, name, parent, prop, weakLocation( ref.location ) );
|
|
504
|
-
if (!parent)
|
|
658
|
+
if (!parent) // not yet set for EXTEND foo WITH bar => linkToOrigin() did not add it
|
|
505
659
|
dictAdd( ext[prop], name, elem );
|
|
506
660
|
elem.$inferred = 'include';
|
|
507
661
|
if (origin.masked)
|
|
@@ -514,7 +668,7 @@ function extend( model ) {
|
|
|
514
668
|
}
|
|
515
669
|
// TODO: expand elements having direct elements (if needed)
|
|
516
670
|
if (members) {
|
|
517
|
-
|
|
671
|
+
forEachInOrder( { [prop]: members }, prop, ( elem, name ) => {
|
|
518
672
|
dictAdd( ext[prop], name, elem );
|
|
519
673
|
});
|
|
520
674
|
}
|
|
@@ -523,6 +677,9 @@ function extend( model ) {
|
|
|
523
677
|
// localized texts entities
|
|
524
678
|
|
|
525
679
|
/**
|
|
680
|
+
* Process localized data for `art`. This includes creating `.texts` entities
|
|
681
|
+
* and `locale` associations.
|
|
682
|
+
*
|
|
526
683
|
* @param {XSN.Artifact} art
|
|
527
684
|
*/
|
|
528
685
|
function processLocalizedData( art ) {
|
|
@@ -542,9 +699,13 @@ function extend( model ) {
|
|
|
542
699
|
}
|
|
543
700
|
|
|
544
701
|
/**
|
|
702
|
+
* Returns `false`, if there is no localized data or an array of elements
|
|
703
|
+
* that are required for `.texts` entities such as keys and localized elements.
|
|
704
|
+
*
|
|
545
705
|
* @param {XSN.Artifact} art
|
|
546
706
|
* @param {XSN.Artifact|undefined} textsEntity
|
|
547
707
|
* @param {boolean} fioriEnabled
|
|
708
|
+
* @returns {false|XSN.Element[]}
|
|
548
709
|
*/
|
|
549
710
|
function localizedData( art, textsEntity, fioriEnabled ) {
|
|
550
711
|
let keys = 0;
|
|
@@ -626,7 +787,7 @@ function extend( model ) {
|
|
|
626
787
|
}
|
|
627
788
|
|
|
628
789
|
/**
|
|
629
|
-
*
|
|
790
|
+
* Create the `.texts` entity for the given base artifact.
|
|
630
791
|
*
|
|
631
792
|
* @param {XSN.Artifact} base
|
|
632
793
|
* @param {string} absolute
|
|
@@ -772,16 +933,20 @@ function extend( model ) {
|
|
|
772
933
|
}
|
|
773
934
|
|
|
774
935
|
/**
|
|
936
|
+
* Returns whether `art` directly or indirectly has the property 'prop',
|
|
937
|
+
* following the 'origin' and the 'type' (not involving elements).
|
|
938
|
+
*
|
|
939
|
+
* DON'T USE FOR ANNOTATIONS (see TODO below)
|
|
940
|
+
*
|
|
941
|
+
* TODO: we should issue a warning if we get localized via TYPE OF
|
|
942
|
+
* TODO: XSN: for anno short form, use { val: true, location, <no literal prop> }
|
|
943
|
+
* ...then this function also works with annotations
|
|
944
|
+
*
|
|
775
945
|
* @param {XSN.Artifact} art
|
|
776
946
|
* @param {string} prop
|
|
947
|
+
* @returns {boolean}
|
|
777
948
|
*/
|
|
778
949
|
function hasTruthyProp( art, prop ) {
|
|
779
|
-
// Returns whether art directly or indirectly has the property 'prop',
|
|
780
|
-
// following the 'origin' and the 'type' (not involving elements).
|
|
781
|
-
//
|
|
782
|
-
// TODO: we should issue a warning if we get localized via TYPE OF
|
|
783
|
-
// TODO XSN: for anno short form, use { val: true, location, <no literal prop> }
|
|
784
|
-
// ...then this function also works with annotations
|
|
785
950
|
const processed = Object.create(null); // avoid infloops with circular refs
|
|
786
951
|
let name = art.name.absolute; // is ok, since no recursive type possible
|
|
787
952
|
while (art && !processed[name]) {
|
|
@@ -1008,6 +1173,88 @@ function extend( model ) {
|
|
|
1008
1173
|
}
|
|
1009
1174
|
}
|
|
1010
1175
|
|
|
1176
|
+
/**
|
|
1177
|
+
* Group assignments by their layers. An assignment provided with a definition
|
|
1178
|
+
* is considered to be provided in a layer named '', the lowest layer.
|
|
1179
|
+
*
|
|
1180
|
+
* TODO: make this usable for extend (elements), too =
|
|
1181
|
+
* do not use $priority, make assignments on define do not have own _block
|
|
1182
|
+
*
|
|
1183
|
+
* @param {object[]} assignments Array of assignments, e.g. extensions.
|
|
1184
|
+
* @returns {Record<string, object>} key: layer name, value: {name, layer, assignments[]}`
|
|
1185
|
+
*/
|
|
1186
|
+
function layeredAssignments( assignments ) {
|
|
1187
|
+
const layered = Object.create(null);
|
|
1188
|
+
for (const a of assignments) {
|
|
1189
|
+
const layer = a.$priority !== false && layers.layer( a );
|
|
1190
|
+
// just consider layer if Extend/Annotate, not Define
|
|
1191
|
+
const name = (layer) ? layer.realname : '';
|
|
1192
|
+
const done = layered[name];
|
|
1193
|
+
if (done)
|
|
1194
|
+
done.assignments.push( a );
|
|
1195
|
+
else
|
|
1196
|
+
layered[name] = { name, layer, assignments: [ a ] };
|
|
1197
|
+
// TODO: file - if set: unique in layer
|
|
1198
|
+
}
|
|
1199
|
+
return layered;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
/**
|
|
1203
|
+
* Return assignments of the highest layers.
|
|
1204
|
+
* Also returns whether there could be an issue:
|
|
1205
|
+
* - false: there is just one assignment
|
|
1206
|
+
* - 'unrelated': there is just one assignment per layer
|
|
1207
|
+
* - true: there is at least one layer with two or more assignments
|
|
1208
|
+
* TODO: make this usable for extend (elements), too.
|
|
1209
|
+
*
|
|
1210
|
+
* @param {Record<string, object>} layeredAnnos Structure as returned by layeredAssignments()
|
|
1211
|
+
* @returns {{assignments, issue: boolean|string}}
|
|
1212
|
+
*/
|
|
1213
|
+
function assignmentsOfHighestLayers( layeredAnnos ) {
|
|
1214
|
+
const layerNames = Object.keys( layeredAnnos );
|
|
1215
|
+
// console.log('HIB:',layerNames)
|
|
1216
|
+
if (layerNames.length <= 1) {
|
|
1217
|
+
const name = layerNames[0];
|
|
1218
|
+
const { assignments } = layeredAnnos[name] || { assignments: [] };
|
|
1219
|
+
delete layeredAnnos[name];
|
|
1220
|
+
return { assignments, issue: assignments.length > 1 };
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
// collect all layers which are lower than another layer
|
|
1224
|
+
const allExtends = Object.create(null);
|
|
1225
|
+
allExtends[''] = {}; // the "Define" layer
|
|
1226
|
+
for (const name of layerNames) {
|
|
1227
|
+
if (name) // not the "Define" layer
|
|
1228
|
+
Object.assign( allExtends, layeredAnnos[name].layer._layerExtends );
|
|
1229
|
+
}
|
|
1230
|
+
// console.log('HIE:',Object.keys(allExtends))
|
|
1231
|
+
const assignments = [];
|
|
1232
|
+
const highest = [];
|
|
1233
|
+
for (const name of layerNames) {
|
|
1234
|
+
if (!(name in allExtends)) {
|
|
1235
|
+
const layer = layeredAnnos[name];
|
|
1236
|
+
delete layeredAnnos[name];
|
|
1237
|
+
highest.push( layer );
|
|
1238
|
+
assignments.push( ...layer.assignments );
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
assignments.sort( compareAssignments );
|
|
1242
|
+
const good = highest.every( layer => layer.assignments.length === 1 );
|
|
1243
|
+
// TODO: use layer.file instead
|
|
1244
|
+
const issue = !good || highest.length > 1 && 'unrelated';
|
|
1245
|
+
// console.log('HI:',highest.map(l=>l.name),issue,issue&&assignments)
|
|
1246
|
+
return { assignments, issue };
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
function compareAssignments( a, b ) {
|
|
1250
|
+
const fileA = layers.realname( a._block );
|
|
1251
|
+
const fileB = layers.realname( b._block );
|
|
1252
|
+
if (fileA !== fileB)
|
|
1253
|
+
return (fileA > fileB) ? 1 : -1;
|
|
1254
|
+
return (a?.location?.line || 0) - (b?.location?.line || 0) ||
|
|
1255
|
+
(a?.location?.col || 0) - (b?.location?.col || 0);
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1011
1258
|
/**
|
|
1012
1259
|
* Copy the annotations `@cds.persistence.skip`/`@cds.persistence.exists` from
|
|
1013
1260
|
* source to target if present on source but not target.
|
|
@@ -1019,14 +1266,16 @@ function extend( model ) {
|
|
|
1019
1266
|
function copyPersistenceAnnotations(target, source, options) {
|
|
1020
1267
|
if (!source)
|
|
1021
1268
|
return;
|
|
1022
|
-
|
|
1023
|
-
const
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1269
|
+
|
|
1270
|
+
const copyExists = !isDeprecatedEnabled( options, 'eagerPersistenceForGeneratedEntities' );
|
|
1271
|
+
if (copyExists)
|
|
1272
|
+
copy( '@cds.persistence.exists' );
|
|
1273
|
+
copy( '@cds.persistence.skip' );
|
|
1274
|
+
|
|
1275
|
+
function copy(anno) {
|
|
1276
|
+
if ( source[anno] && !target[anno] )
|
|
1277
|
+
target[anno] = { ...source[anno], $inferred: 'parent-origin' };
|
|
1278
|
+
}
|
|
1030
1279
|
}
|
|
1031
1280
|
|
|
1032
1281
|
function augmentEqual( location, assocname, relations, prefix = '' ) {
|
|
@@ -1054,4 +1303,21 @@ function augmentEqual( location, assocname, relations, prefix = '' ) {
|
|
|
1054
1303
|
}
|
|
1055
1304
|
}
|
|
1056
1305
|
|
|
1306
|
+
/**
|
|
1307
|
+
* If the given extension is a `EXTEND <def> WITH TYPE` extension, store
|
|
1308
|
+
* it in the given artifact. resolve.js will resolve types and call
|
|
1309
|
+
* `typeExtensions()` later.
|
|
1310
|
+
*
|
|
1311
|
+
* @param {XSN.Extension} ext
|
|
1312
|
+
* @param {object} art
|
|
1313
|
+
*/
|
|
1314
|
+
function storeTypeExtension( ext, art ) {
|
|
1315
|
+
// If there are no parameters to apply, don't store the extension.
|
|
1316
|
+
if (!typeParameters.list.some( prop => ext[prop] !== undefined ))
|
|
1317
|
+
return;
|
|
1318
|
+
else if (!art._extendType)
|
|
1319
|
+
setLink( art, '_extendType', [] );
|
|
1320
|
+
art._extendType.push( ext );
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1057
1323
|
module.exports = extend;
|
|
@@ -10,7 +10,6 @@ function kickStart( model ) {
|
|
|
10
10
|
const { message } = model.$messageFunctions;
|
|
11
11
|
|
|
12
12
|
const { resolveUncheckedPath, resolvePath } = model.$functions;
|
|
13
|
-
Object.assign( model.$functions, { projectionAncestor } );
|
|
14
13
|
|
|
15
14
|
// Set _service link (sorted to set it on parent first). Could be set
|
|
16
15
|
// directly, but beware a namespace becoming a service later.
|
|
@@ -68,7 +67,7 @@ function kickStart( model ) {
|
|
|
68
67
|
// no redirection target for E if Service2.E = projection on Service1.E and
|
|
69
68
|
// Service1.E = projection on E
|
|
70
69
|
const chain = [];
|
|
71
|
-
const autoexposed =
|
|
70
|
+
const autoexposed = annotationVal( art['@cds.autoexposed'] );
|
|
72
71
|
const preferredRedirectionTarget = annotationVal( art['@cds.redirection.target'] );
|
|
73
72
|
// no need to set preferredRedirectionTarget in the while loop as we would
|
|
74
73
|
// use the projection having @cds.redirection.target anyhow instead of
|
|
@@ -80,7 +79,7 @@ function kickStart( model ) {
|
|
|
80
79
|
chain.push( art );
|
|
81
80
|
setLink( art, '_ancestors', null ); // avoid infloop with cyclic from
|
|
82
81
|
const name = resolveUncheckedPath( art._from[0], 'include', art ); // TODO: 'include'?
|
|
83
|
-
art = name &&
|
|
82
|
+
art = name && model.definitions[name];
|
|
84
83
|
if (autoexposed)
|
|
85
84
|
break; // only direct projection for auto-exposed
|
|
86
85
|
}
|
|
@@ -93,38 +92,6 @@ function kickStart( model ) {
|
|
|
93
92
|
}
|
|
94
93
|
}
|
|
95
94
|
|
|
96
|
-
// Return argument `source` if entity `source` has parameters like `params`
|
|
97
|
-
// - same parameters, although `params` can contain a new optional one (with DEFAULT)
|
|
98
|
-
// - a parameter in `params` can be optional which is not in `source.params`, but not vice versa
|
|
99
|
-
// - exactly the same types (type argument do not matter)
|
|
100
|
-
function projectionAncestor( source, params ) {
|
|
101
|
-
if (!source)
|
|
102
|
-
return source;
|
|
103
|
-
if (!params) // proj has no params => ok if source has no params
|
|
104
|
-
return !source.params && source;
|
|
105
|
-
const sourceParams = source.params || Object.create(null);
|
|
106
|
-
for (const n in sourceParams) {
|
|
107
|
-
if (!(n in params)) // source param is not projection param
|
|
108
|
-
return null; // -> can't be used as implicit redirection target
|
|
109
|
-
}
|
|
110
|
-
for (const n in params) {
|
|
111
|
-
const pp = params[n];
|
|
112
|
-
const sp = sourceParams[n];
|
|
113
|
-
if (sp) {
|
|
114
|
-
if (sp.default && !pp.default) // param DEFAULT clause not supported yet
|
|
115
|
-
return null; // param is not optional anymore
|
|
116
|
-
const pt = pp.type && resolveUncheckedPath( pp.type, 'type', pp );
|
|
117
|
-
const st = sp.type && resolveUncheckedPath( sp.type, 'type', sp );
|
|
118
|
-
if ((pt || null) !== (st || null))
|
|
119
|
-
return null; // params have different type
|
|
120
|
-
}
|
|
121
|
-
else if (!pp.default) {
|
|
122
|
-
return null;
|
|
123
|
-
} // non-optional param in projection, but not source
|
|
124
|
-
}
|
|
125
|
-
return source;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
95
|
function postProcessArtifact( art ) {
|
|
129
96
|
tagCompositionTargets( art );
|
|
130
97
|
if (art.$queries) {
|