@sap/cds-compiler 3.1.2 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +101 -3
- package/bin/cdsc.js +4 -2
- package/doc/CHANGELOG_BETA.md +35 -0
- package/lib/api/main.js +153 -29
- package/lib/api/validate.js +8 -3
- package/lib/base/dictionaries.js +6 -6
- package/lib/base/error.js +2 -2
- package/lib/base/keywords.js +106 -24
- package/lib/base/message-registry.js +177 -79
- package/lib/base/messages.js +78 -57
- package/lib/base/model.js +2 -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 +53 -0
- package/lib/checks/defaultValues.js +4 -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 +23 -0
- package/lib/checks/queryNoDbArtifacts.js +1 -1
- package/lib/checks/selectItems.js +1 -1
- package/lib/checks/sql-snippets.js +12 -10
- package/lib/checks/types.js +2 -2
- package/lib/checks/utils.js +17 -7
- package/lib/checks/validator.js +36 -14
- package/lib/compiler/assert-consistency.js +21 -13
- package/lib/compiler/builtins.js +8 -0
- package/lib/compiler/checks.js +57 -40
- package/lib/compiler/define.js +139 -69
- package/lib/compiler/extend.js +319 -50
- package/lib/compiler/finalize-parse-cdl.js +14 -9
- package/lib/compiler/kick-start.js +2 -35
- package/lib/compiler/populate.js +111 -68
- package/lib/compiler/propagator.js +5 -3
- package/lib/compiler/resolve.js +71 -108
- package/lib/compiler/shared.js +82 -54
- package/lib/compiler/tweak-assocs.js +26 -14
- package/lib/compiler/utils.js +13 -2
- package/lib/edm/annotations/genericTranslation.js +10 -7
- package/lib/edm/csn2edm.js +11 -11
- package/lib/edm/edm.js +17 -9
- package/lib/edm/edmPreprocessor.js +53 -30
- package/lib/edm/edmUtils.js +7 -2
- 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 +4312 -4186
- package/lib/inspect/inspectModelStatistics.js +1 -1
- package/lib/inspect/inspectPropagation.js +23 -9
- package/lib/json/csnVersion.js +13 -13
- package/lib/json/from-csn.js +161 -172
- package/lib/json/to-csn.js +70 -10
- package/lib/language/.eslintrc.json +4 -0
- package/lib/language/antlrParser.js +8 -11
- package/lib/language/docCommentParser.js +1 -2
- package/lib/language/errorStrategy.js +54 -27
- package/lib/language/genericAntlrParser.js +140 -93
- package/lib/language/language.g4 +57 -33
- package/lib/language/multiLineStringParser.js +75 -63
- package/lib/main.d.ts +3 -6
- package/lib/main.js +1 -0
- package/lib/model/.eslintrc.json +13 -0
- package/lib/model/api.js +4 -2
- package/lib/model/csnRefs.js +78 -50
- package/lib/model/csnUtils.js +272 -222
- package/lib/model/enrichCsn.js +41 -31
- package/lib/model/revealInternalProperties.js +61 -57
- package/lib/model/sortViews.js +35 -31
- package/lib/modelCompare/compare.js +52 -18
- package/lib/modelCompare/filter.js +83 -0
- package/lib/optionProcessor.js +10 -1
- package/lib/render/manageConstraints.js +11 -7
- package/lib/render/toCdl.js +151 -106
- package/lib/render/toHdbcds.js +8 -6
- package/lib/render/toRename.js +4 -4
- package/lib/render/toSql.js +17 -7
- package/lib/render/utils/common.js +27 -9
- package/lib/render/utils/sql.js +5 -5
- package/lib/sql-identifier.js +7 -0
- package/lib/transform/db/applyTransformations.js +32 -3
- package/lib/transform/db/assertUnique.js +27 -38
- package/lib/transform/db/expansion.js +92 -41
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/temporal.js +3 -1
- package/lib/transform/db/transformExists.js +8 -2
- package/lib/transform/db/views.js +42 -13
- package/lib/transform/draft/db.js +2 -2
- package/lib/transform/forOdataNew.js +10 -7
- package/lib/transform/{forHanaNew.js → forRelationalDB.js} +18 -12
- package/lib/transform/localized.js +29 -20
- package/lib/transform/odata/toFinalBaseType.js +8 -11
- package/lib/transform/odata/typesExposure.js +2 -1
- package/lib/transform/parseExpr.js +245 -0
- package/lib/transform/transformUtilsNew.js +122 -51
- package/lib/transform/translateAssocsToJoins.js +17 -16
- package/lib/utils/moduleResolve.js +5 -5
- package/lib/utils/objectUtils.js +3 -3
- package/lib/utils/term.js +5 -5
- 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 → syntax-expecting-integer.md} +4 -4
- 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;
|
|
@@ -34,7 +35,8 @@ function extend( model ) {
|
|
|
34
35
|
resolvePath,
|
|
35
36
|
resolveUncheckedPath,
|
|
36
37
|
checkAnnotate,
|
|
37
|
-
|
|
38
|
+
initAnnotations,
|
|
39
|
+
copyAnnotationsForExtensions,
|
|
38
40
|
attachAndEmitValidNames,
|
|
39
41
|
checkDefinitions,
|
|
40
42
|
initArtifact,
|
|
@@ -44,6 +46,9 @@ function extend( model ) {
|
|
|
44
46
|
|
|
45
47
|
Object.assign( model.$functions, {
|
|
46
48
|
lateExtensions,
|
|
49
|
+
layeredAssignments,
|
|
50
|
+
assignmentsOfHighestLayers,
|
|
51
|
+
applyTypeExtensions,
|
|
47
52
|
} );
|
|
48
53
|
|
|
49
54
|
applyExtensions();
|
|
@@ -74,6 +79,9 @@ function extend( model ) {
|
|
|
74
79
|
}
|
|
75
80
|
|
|
76
81
|
/**
|
|
82
|
+
* Propagate the given `prop` (e.g. annotation) early, i.e. copy it from all `.includes`
|
|
83
|
+
* if they have the property.
|
|
84
|
+
*
|
|
77
85
|
* @param {XSN.Definition} art
|
|
78
86
|
* @param {string} prop
|
|
79
87
|
*/
|
|
@@ -156,6 +164,7 @@ function extend( model ) {
|
|
|
156
164
|
}
|
|
157
165
|
|
|
158
166
|
function extendContext( name, art ) {
|
|
167
|
+
// TODO: remove this function - add remains to define.js
|
|
159
168
|
// (ext.expectedKind == art.kind) already checked by parser except for context/service
|
|
160
169
|
if (!kindProperties[art.kind].artifacts) {
|
|
161
170
|
// no context or service => warn about context extensions
|
|
@@ -163,10 +172,10 @@ function extend( model ) {
|
|
|
163
172
|
if (ext.expectedKind === 'context' || ext.expectedKind === 'service') {
|
|
164
173
|
const loc = ext.name.location;
|
|
165
174
|
// TODO: warning is enough
|
|
166
|
-
error( 'extend-with-artifacts', [ loc, ext ], { name, '#': ext.expectedKind }, {
|
|
167
|
-
std: '
|
|
168
|
-
service: '
|
|
169
|
-
context: '
|
|
175
|
+
error( 'extend-with-artifacts', [ loc, ext ], { name, '#': ext.expectedKind, keyword: `EXTEND ${ ext.expectedKind }` }, {
|
|
176
|
+
std: 'Can\'t extend non-context / non-service $(NAME) with $(KEYWORD)',
|
|
177
|
+
service: 'Can\'t extend non-service $(NAME) with $(KEYWORD)',
|
|
178
|
+
context: 'Can\'t extend non-context $(NAME) with $(KEYWORD)',
|
|
170
179
|
});
|
|
171
180
|
}
|
|
172
181
|
}
|
|
@@ -182,9 +191,8 @@ function extend( model ) {
|
|
|
182
191
|
checkDefinitions( ext, art, 'columns');
|
|
183
192
|
if (ext.includes)
|
|
184
193
|
applyIncludes( ext, art ); // emits error if `includes` is set
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
defineAnnotations( ext, art, ext._block, ext.kind );
|
|
194
|
+
checkAnnotate( ext, art );
|
|
195
|
+
copyAnnotationsForExtensions( ext, art );
|
|
188
196
|
}
|
|
189
197
|
return true;
|
|
190
198
|
}
|
|
@@ -200,7 +208,8 @@ function extend( model ) {
|
|
|
200
208
|
* @param {boolean|'gen'} [noIncludes=false]
|
|
201
209
|
*/
|
|
202
210
|
function extendArtifact( extensions, art, noIncludes = false) {
|
|
203
|
-
if (!noIncludes && !(canApplyIncludes( art ) &&
|
|
211
|
+
if (!noIncludes && !(canApplyIncludes( art, art ) &&
|
|
212
|
+
extensions.every( ext => canApplyIncludes(ext, art) )))
|
|
204
213
|
return false;
|
|
205
214
|
if (!art.query) {
|
|
206
215
|
model._entities.push( art ); // add structure with includes in dep order
|
|
@@ -221,7 +230,8 @@ function extend( model ) {
|
|
|
221
230
|
function extendMembers( extensions, art, noExtend ) {
|
|
222
231
|
// TODO: do the whole extension stuff lazily if the elements are requested
|
|
223
232
|
const elemExtensions = [];
|
|
224
|
-
extensions.sort( compareLayer );
|
|
233
|
+
extensions.sort( layers.compareLayer );
|
|
234
|
+
// TODO: use same sequence as in chooseAssignment() - better: use common code with that fn
|
|
225
235
|
for (const ext of extensions) {
|
|
226
236
|
// console.log(message( 'id', [ext.location, ext], { art: ext.name._artifact },
|
|
227
237
|
// 'Info', 'EXT').toString())
|
|
@@ -243,18 +253,19 @@ function extend( model ) {
|
|
|
243
253
|
art.includes = [ ...ext.includes ];
|
|
244
254
|
applyIncludes( ext, art );
|
|
245
255
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
256
|
+
checkAnnotate( ext, art );
|
|
257
|
+
initAnnotations( ext, ext._block, ext.kind ); // TODO: do in define.js
|
|
258
|
+
copyAnnotationsForExtensions( ext, art );
|
|
249
259
|
// TODO: do we allow to add elements with array of {...}? If yes, adapt
|
|
250
260
|
initMembers( ext, art, ext._block ); // might set _extend, _annotate
|
|
261
|
+
storeTypeExtension( ext, art );
|
|
251
262
|
dependsOnSilent(art, ext); // art depends silently on ext (inverse to normal dep!)
|
|
252
263
|
}
|
|
253
264
|
for (const name in ext.elements) {
|
|
254
265
|
const elem = ext.elements[name];
|
|
255
266
|
if (elem.kind === 'element') { // i.e. not extend or annotate
|
|
256
267
|
elemExtensions.push( elem );
|
|
257
|
-
break;
|
|
268
|
+
break; // more than one elem in same EXTEND is fine
|
|
258
269
|
}
|
|
259
270
|
}
|
|
260
271
|
|
|
@@ -263,6 +274,8 @@ function extend( model ) {
|
|
|
263
274
|
}
|
|
264
275
|
if (elemExtensions.length > 1)
|
|
265
276
|
reportUnstableExtensions( elemExtensions );
|
|
277
|
+
if (art._extendType && art._extendType.length > 0)
|
|
278
|
+
reportTypeExtensionsInSameLayer( art._extendType );
|
|
266
279
|
|
|
267
280
|
[ 'elements', 'actions' ].forEach( (prop) => {
|
|
268
281
|
const dict = art._extend && art._extend[prop];
|
|
@@ -291,9 +304,6 @@ function extend( model ) {
|
|
|
291
304
|
function extendColumns( ext, art ) {
|
|
292
305
|
// TODO: consider reportUnstableExtensions
|
|
293
306
|
|
|
294
|
-
for (const col of ext.columns)
|
|
295
|
-
defineAnnotations( col, col, ext._block, ext.kind );
|
|
296
|
-
|
|
297
307
|
const { location } = ext.name;
|
|
298
308
|
const { query } = art;
|
|
299
309
|
if (!query) {
|
|
@@ -315,14 +325,115 @@ function extend( model ) {
|
|
|
315
325
|
}
|
|
316
326
|
}
|
|
317
327
|
|
|
328
|
+
/**
|
|
329
|
+
* Similar to chooseAssignment for annotations, this function applies type extensions in order,
|
|
330
|
+
* such that hierarchies are respected.
|
|
331
|
+
* Order already set in `extendMembers()` using `compareLayers()`.
|
|
332
|
+
*
|
|
333
|
+
* @param art
|
|
334
|
+
*/
|
|
335
|
+
function applyTypeExtensions(art) {
|
|
336
|
+
/**
|
|
337
|
+
* Contains the previous extension for each property that was applied
|
|
338
|
+
* successfully.
|
|
339
|
+
*/
|
|
340
|
+
const previousSuccess = Object.create(null);
|
|
341
|
+
const allowedBuiltinCategories = [ 'string', 'decimal', 'integer', 'binary' ];
|
|
342
|
+
const artType = art.type?._artifact;
|
|
343
|
+
|
|
344
|
+
for (const ext of art._extendType) {
|
|
345
|
+
if (art.$inferred) {
|
|
346
|
+
// Only report first extension for $inferred artifact. Reduces noise.
|
|
347
|
+
error('ref-expected-scalar-type', [ ext.name.location, ext ], { '#': 'inferred' } );
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const baseType = art._effectiveType; // may be an ENUM
|
|
352
|
+
if (!artType || (art.kind !== 'type' && art.kind !== 'element') || !baseType?.builtin) {
|
|
353
|
+
// Only report first extension for non-scalar base type. Reduces noise.
|
|
354
|
+
error('ref-expected-scalar-type', [ ext.name.location, ext ], { } );
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
else if (!allowedBuiltinCategories.includes(baseType.category)) {
|
|
358
|
+
// Only report first extension for non-scalar type. Reduces noise.
|
|
359
|
+
error('ref-expected-scalar-type', [ ext.name.location, ext ],
|
|
360
|
+
{ '#': 'unsupported', prop: baseType.category });
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
for (const prop of typeParameters.list) {
|
|
365
|
+
if (!ext[prop])
|
|
366
|
+
continue;
|
|
367
|
+
|
|
368
|
+
if (!baseType.parameters?.includes(prop)) {
|
|
369
|
+
// For `extend T with type (length:10)` where T does not expect a length.
|
|
370
|
+
error( 'type-unexpected-argument', [ ext[prop].location, ext ], {
|
|
371
|
+
'#': 'type', prop, art, type: baseType,
|
|
372
|
+
});
|
|
373
|
+
break; // one error for first property is enough
|
|
374
|
+
}
|
|
375
|
+
else if (!art[prop]) {
|
|
376
|
+
// Can only extend properties that exist directly on the artifact.
|
|
377
|
+
error('ext-invalid-type-property', [ ext[prop].location, ext ],
|
|
378
|
+
{ prop, '#': 'new-prop' });
|
|
379
|
+
break; // one error for first property is enough
|
|
380
|
+
}
|
|
381
|
+
else if (art[prop].val === ext[prop].val) {
|
|
382
|
+
// Ignore extensions with same value
|
|
383
|
+
}
|
|
384
|
+
else if (art[prop].literal === 'string' || ext[prop].literal === 'string' ) {
|
|
385
|
+
// Users can't change from/to string value for property,
|
|
386
|
+
// e.g. `variable`/`floating` for Decimal
|
|
387
|
+
error( 'ext-invalid-type-property', [ ext[prop].location, ext ],
|
|
388
|
+
{ '#': 'string', prop } );
|
|
389
|
+
}
|
|
390
|
+
else if (art[prop].val > ext[prop].val) {
|
|
391
|
+
// TODO: Future: Sub-message that points to previous extension?
|
|
392
|
+
error( 'ext-invalid-type-property', [ ext[prop].location, ext ], {
|
|
393
|
+
prop,
|
|
394
|
+
value: ext[prop].val,
|
|
395
|
+
number: art[prop].val,
|
|
396
|
+
'#': previousSuccess[prop] ? 'ext-smaller' : 'smaller',
|
|
397
|
+
} );
|
|
398
|
+
break; // one error for first property is enough
|
|
399
|
+
}
|
|
400
|
+
else if (art[prop].val < ext[prop].val) {
|
|
401
|
+
art[prop] = ext[prop];
|
|
402
|
+
previousSuccess[prop] = ext;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// If `scale` is increased, but `precision` is not, data may be lost in a migration.
|
|
407
|
+
// e.g. `Decimal(4,2)` allows `12.34`. Increasing scale to 3 would make this value
|
|
408
|
+
// invalid and only allow `1.234`.
|
|
409
|
+
// TODO: Should we actually check that the increase is correct?
|
|
410
|
+
if (ext.scale && !ext.precision) {
|
|
411
|
+
error('ext-invalid-type-property', [ ext.scale.location, ext ], {
|
|
412
|
+
prop: 'scale',
|
|
413
|
+
otherprop: 'precision',
|
|
414
|
+
'#': 'scale',
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Report 'Warning: Unstable element order due to repeated extensions'
|
|
422
|
+
* except if all extensions are in the same file.
|
|
423
|
+
*
|
|
424
|
+
* @param {XSN.Extension[]} extensions
|
|
425
|
+
*/
|
|
318
426
|
function reportUnstableExtensions( extensions ) {
|
|
319
|
-
//
|
|
427
|
+
// No message if all extensions are in the same file:
|
|
428
|
+
const file = layers.realname( extensions[0] );
|
|
429
|
+
if (extensions.every( ( ext, i ) => !i || file === layers.realname( ext ) ))
|
|
430
|
+
return;
|
|
320
431
|
// Similar to chooseAssignment(), TODO there: also extra intralayer message
|
|
321
432
|
// as this is a modeling error
|
|
322
433
|
let lastExt = null;
|
|
323
434
|
let open = []; // the "highest" layers
|
|
324
435
|
for (const ext of extensions) {
|
|
325
|
-
const extLayer = layer( ext ) || { realname: '', _layerExtends: Object.create(null) };
|
|
436
|
+
const extLayer = layers.layer( ext ) || { realname: '', _layerExtends: Object.create(null) };
|
|
326
437
|
if (!open.length) {
|
|
327
438
|
lastExt = ext;
|
|
328
439
|
open = [ extLayer.realname ];
|
|
@@ -346,6 +457,28 @@ function extend( model ) {
|
|
|
346
457
|
}
|
|
347
458
|
}
|
|
348
459
|
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Report type extensions in same layer, similar mechanism to chooseAssignment()
|
|
463
|
+
* for annotations.
|
|
464
|
+
*
|
|
465
|
+
* @param {XSN.Extension[]} extensions
|
|
466
|
+
*/
|
|
467
|
+
function reportTypeExtensionsInSameLayer( extensions ) {
|
|
468
|
+
// Group assignments by layer
|
|
469
|
+
const extLayers = layeredAssignments( extensions );
|
|
470
|
+
// We only care about the highest layer
|
|
471
|
+
const { assignments, issue } = assignmentsOfHighestLayers( extLayers );
|
|
472
|
+
|
|
473
|
+
if (issue) {
|
|
474
|
+
const id = (issue === 'unrelated')
|
|
475
|
+
? 'ext-duplicate-extend-type-unrelated-layer'
|
|
476
|
+
: 'ext-duplicate-extend-type';
|
|
477
|
+
for (const ext of assignments)
|
|
478
|
+
message( id, [ ext.name.location, ext ], { type: ext.name._artifact } );
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
349
482
|
/**
|
|
350
483
|
* @param {XSN.Extension[]} extensions
|
|
351
484
|
* @param {string} prop
|
|
@@ -354,12 +487,14 @@ function extend( model ) {
|
|
|
354
487
|
* @param {object} validDict
|
|
355
488
|
*/
|
|
356
489
|
function extendNothing( extensions, prop, name, art, validDict ) {
|
|
490
|
+
const artName = searchName( art, name, dictKinds[prop] );
|
|
357
491
|
for (const ext of extensions) {
|
|
358
492
|
// TODO: use shared functionality with notFound in resolver.js
|
|
359
493
|
const { location } = ext.name;
|
|
494
|
+
const extName = { ...artName, kind: ext.kind };
|
|
360
495
|
const msg
|
|
361
|
-
= error( 'extend-undefined', [ location,
|
|
362
|
-
{ art:
|
|
496
|
+
= error( 'extend-undefined', [ location, extName ],
|
|
497
|
+
{ art: artName },
|
|
363
498
|
{
|
|
364
499
|
std: 'Unknown $(ART) - nothing to extend',
|
|
365
500
|
// eslint-disable-next-line max-len
|
|
@@ -444,12 +579,20 @@ function extend( model ) {
|
|
|
444
579
|
// includes ----------------------------------------------------------------
|
|
445
580
|
|
|
446
581
|
/**
|
|
582
|
+
* Returns true, if `art.includes` can be applied on `target`.
|
|
583
|
+
* They can't be applied if any of the artifacts referenced in
|
|
584
|
+
* `art.includes` are yet to be extended.
|
|
585
|
+
* `art !== target` if `art` is an extension.
|
|
586
|
+
*
|
|
447
587
|
* @param {XSN.Definition} art
|
|
588
|
+
* @param {XSN.Artifact} target
|
|
589
|
+
* @returns {boolean}
|
|
448
590
|
*/
|
|
449
|
-
function canApplyIncludes( art ) {
|
|
591
|
+
function canApplyIncludes( art, target ) {
|
|
450
592
|
if (art.includes) {
|
|
593
|
+
const isView = !!target.query;
|
|
451
594
|
for (const ref of art.includes) {
|
|
452
|
-
const template = resolvePath( ref, 'include', art );
|
|
595
|
+
const template = resolvePath( ref, isView ? 'viewInclude' : 'include', art );
|
|
453
596
|
if (template && template.name.absolute in extensionsDict)
|
|
454
597
|
return false;
|
|
455
598
|
}
|
|
@@ -458,50 +601,64 @@ function extend( model ) {
|
|
|
458
601
|
}
|
|
459
602
|
|
|
460
603
|
/**
|
|
604
|
+
* Apply all includes of `ext` on `ext`. Checks that `art` allows includes.
|
|
605
|
+
* If `ext === art`, then includes of the artifact itself are applied.
|
|
606
|
+
* If `ext !== art`, applies includes on the extensions, not artifact.
|
|
607
|
+
* Sets `_ancestor` links on `art`.
|
|
608
|
+
*
|
|
609
|
+
* Examples:
|
|
610
|
+
* ext === art: `entity E : F {}` => add elements of F to E
|
|
611
|
+
* ext !== art: `extend E with F` => add elements of F to extension on E
|
|
612
|
+
*
|
|
461
613
|
* @param {XSN.Extension} ext
|
|
462
614
|
* @param {XSN.Artifact} art
|
|
463
615
|
*/
|
|
464
616
|
function applyIncludes( ext, art ) {
|
|
465
617
|
if (kindProperties[art.kind].include !== true) {
|
|
466
|
-
error('extend-unexpected-include', [ ext.includes[0]?.location, ext ],
|
|
467
|
-
|
|
618
|
+
error('extend-unexpected-include', [ ext.includes[0]?.location, ext ],
|
|
619
|
+
{ kind: art.kind });
|
|
468
620
|
return;
|
|
469
621
|
}
|
|
470
622
|
|
|
471
623
|
if (!art._ancestors)
|
|
472
624
|
setLink( art, '_ancestors', [] ); // recursive array of includes
|
|
473
625
|
for (const ref of ext.includes) {
|
|
474
|
-
const template = ref._artifact;
|
|
626
|
+
const template = ref._artifact;
|
|
627
|
+
// !template -> non-includable, e.g. scalar type, or cyclic
|
|
475
628
|
if (template) {
|
|
476
629
|
if (template._ancestors)
|
|
477
630
|
art._ancestors.push( ...template._ancestors );
|
|
478
631
|
art._ancestors.push( template );
|
|
479
632
|
}
|
|
480
633
|
}
|
|
481
|
-
includeMembers( ext, 'elements'
|
|
482
|
-
includeMembers( ext, 'actions'
|
|
634
|
+
includeMembers( ext, art, 'elements' );
|
|
635
|
+
includeMembers( ext, art, 'actions' );
|
|
483
636
|
}
|
|
484
637
|
|
|
485
638
|
/**
|
|
639
|
+
* Add all members (e.g. elements or actions) of `ext.includes` to `ext[prop]`
|
|
640
|
+
* if `parent` is `false` or to `parent` with appropriate _origin links otherwise.
|
|
641
|
+
* Included members are prepended to existing ones.
|
|
642
|
+
*
|
|
486
643
|
* @param {XSN.Extension} ext
|
|
644
|
+
* @param {XSN.Artifact} art
|
|
487
645
|
* @param {string} prop
|
|
488
|
-
* @param {function} forEach
|
|
489
|
-
* @param {XSN.Artifact} parent
|
|
490
646
|
*/
|
|
491
|
-
function includeMembers( ext,
|
|
647
|
+
function includeMembers( ext, art, prop ) {
|
|
492
648
|
// TODO two kind of messages:
|
|
493
649
|
// Error 'More than one include defines element "A"' (at include ref)
|
|
494
650
|
// Warning 'Overwrites definition from include "I" (at elem def)
|
|
651
|
+
const parent = ext === art && art;
|
|
495
652
|
const members = ext[prop];
|
|
496
653
|
ext[prop] = Object.create(null); // TODO: do not set actions property if there are none
|
|
497
654
|
for (const ref of ext.includes) {
|
|
498
655
|
const template = ref._artifact; // already resolved
|
|
499
656
|
if (template) { // be robust
|
|
500
|
-
|
|
657
|
+
forEachInOrder( template, prop, ( origin, name ) => {
|
|
501
658
|
if (members && name in members)
|
|
502
659
|
return; // TODO: warning for overwritten element
|
|
503
660
|
const elem = linkToOrigin( origin, name, parent, prop, weakLocation( ref.location ) );
|
|
504
|
-
if (!parent)
|
|
661
|
+
if (!parent) // not yet set for EXTEND foo WITH bar => linkToOrigin() did not add it
|
|
505
662
|
dictAdd( ext[prop], name, elem );
|
|
506
663
|
elem.$inferred = 'include';
|
|
507
664
|
if (origin.masked)
|
|
@@ -514,7 +671,7 @@ function extend( model ) {
|
|
|
514
671
|
}
|
|
515
672
|
// TODO: expand elements having direct elements (if needed)
|
|
516
673
|
if (members) {
|
|
517
|
-
|
|
674
|
+
forEachInOrder( { [prop]: members }, prop, ( elem, name ) => {
|
|
518
675
|
dictAdd( ext[prop], name, elem );
|
|
519
676
|
});
|
|
520
677
|
}
|
|
@@ -523,6 +680,9 @@ function extend( model ) {
|
|
|
523
680
|
// localized texts entities
|
|
524
681
|
|
|
525
682
|
/**
|
|
683
|
+
* Process localized data for `art`. This includes creating `.texts` entities
|
|
684
|
+
* and `locale` associations.
|
|
685
|
+
*
|
|
526
686
|
* @param {XSN.Artifact} art
|
|
527
687
|
*/
|
|
528
688
|
function processLocalizedData( art ) {
|
|
@@ -542,9 +702,13 @@ function extend( model ) {
|
|
|
542
702
|
}
|
|
543
703
|
|
|
544
704
|
/**
|
|
705
|
+
* Returns `false`, if there is no localized data or an array of elements
|
|
706
|
+
* that are required for `.texts` entities such as keys and localized elements.
|
|
707
|
+
*
|
|
545
708
|
* @param {XSN.Artifact} art
|
|
546
709
|
* @param {XSN.Artifact|undefined} textsEntity
|
|
547
710
|
* @param {boolean} fioriEnabled
|
|
711
|
+
* @returns {false|XSN.Element[]}
|
|
548
712
|
*/
|
|
549
713
|
function localizedData( art, textsEntity, fioriEnabled ) {
|
|
550
714
|
let keys = 0;
|
|
@@ -626,7 +790,7 @@ function extend( model ) {
|
|
|
626
790
|
}
|
|
627
791
|
|
|
628
792
|
/**
|
|
629
|
-
*
|
|
793
|
+
* Create the `.texts` entity for the given base artifact.
|
|
630
794
|
*
|
|
631
795
|
* @param {XSN.Artifact} base
|
|
632
796
|
* @param {string} absolute
|
|
@@ -772,16 +936,20 @@ function extend( model ) {
|
|
|
772
936
|
}
|
|
773
937
|
|
|
774
938
|
/**
|
|
939
|
+
* Returns whether `art` directly or indirectly has the property 'prop',
|
|
940
|
+
* following the 'origin' and the 'type' (not involving elements).
|
|
941
|
+
*
|
|
942
|
+
* DON'T USE FOR ANNOTATIONS (see TODO below)
|
|
943
|
+
*
|
|
944
|
+
* TODO: we should issue a warning if we get localized via TYPE OF
|
|
945
|
+
* TODO: XSN: for anno short form, use { val: true, location, <no literal prop> }
|
|
946
|
+
* ...then this function also works with annotations
|
|
947
|
+
*
|
|
775
948
|
* @param {XSN.Artifact} art
|
|
776
949
|
* @param {string} prop
|
|
950
|
+
* @returns {boolean}
|
|
777
951
|
*/
|
|
778
952
|
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
953
|
const processed = Object.create(null); // avoid infloops with circular refs
|
|
786
954
|
let name = art.name.absolute; // is ok, since no recursive type possible
|
|
787
955
|
while (art && !processed[name]) {
|
|
@@ -1008,6 +1176,88 @@ function extend( model ) {
|
|
|
1008
1176
|
}
|
|
1009
1177
|
}
|
|
1010
1178
|
|
|
1179
|
+
/**
|
|
1180
|
+
* Group assignments by their layers. An assignment provided with a definition
|
|
1181
|
+
* is considered to be provided in a layer named '', the lowest layer.
|
|
1182
|
+
*
|
|
1183
|
+
* TODO: make this usable for extend (elements), too =
|
|
1184
|
+
* do not use $priority, make assignments on define do not have own _block
|
|
1185
|
+
*
|
|
1186
|
+
* @param {object[]} assignments Array of assignments, e.g. extensions.
|
|
1187
|
+
* @returns {Record<string, object>} key: layer name, value: {name, layer, assignments[]}`
|
|
1188
|
+
*/
|
|
1189
|
+
function layeredAssignments( assignments ) {
|
|
1190
|
+
const layered = Object.create(null);
|
|
1191
|
+
for (const a of assignments) {
|
|
1192
|
+
const layer = a.$priority !== false && layers.layer( a );
|
|
1193
|
+
// just consider layer if Extend/Annotate, not Define
|
|
1194
|
+
const name = (layer) ? layer.realname : '';
|
|
1195
|
+
const done = layered[name];
|
|
1196
|
+
if (done)
|
|
1197
|
+
done.assignments.push( a );
|
|
1198
|
+
else
|
|
1199
|
+
layered[name] = { name, layer, assignments: [ a ] };
|
|
1200
|
+
// TODO: file - if set: unique in layer
|
|
1201
|
+
}
|
|
1202
|
+
return layered;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
/**
|
|
1206
|
+
* Return assignments of the highest layers.
|
|
1207
|
+
* Also returns whether there could be an issue:
|
|
1208
|
+
* - false: there is just one assignment
|
|
1209
|
+
* - 'unrelated': there is just one assignment per layer
|
|
1210
|
+
* - true: there is at least one layer with two or more assignments
|
|
1211
|
+
* TODO: make this usable for extend (elements), too.
|
|
1212
|
+
*
|
|
1213
|
+
* @param {Record<string, object>} layeredAnnos Structure as returned by layeredAssignments()
|
|
1214
|
+
* @returns {{assignments, issue: boolean|string}}
|
|
1215
|
+
*/
|
|
1216
|
+
function assignmentsOfHighestLayers( layeredAnnos ) {
|
|
1217
|
+
const layerNames = Object.keys( layeredAnnos );
|
|
1218
|
+
// console.log('HIB:',layerNames)
|
|
1219
|
+
if (layerNames.length <= 1) {
|
|
1220
|
+
const name = layerNames[0];
|
|
1221
|
+
const { assignments } = layeredAnnos[name] || { assignments: [] };
|
|
1222
|
+
delete layeredAnnos[name];
|
|
1223
|
+
return { assignments, issue: assignments.length > 1 };
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
// collect all layers which are lower than another layer
|
|
1227
|
+
const allExtends = Object.create(null);
|
|
1228
|
+
allExtends[''] = {}; // the "Define" layer
|
|
1229
|
+
for (const name of layerNames) {
|
|
1230
|
+
if (name) // not the "Define" layer
|
|
1231
|
+
Object.assign( allExtends, layeredAnnos[name].layer._layerExtends );
|
|
1232
|
+
}
|
|
1233
|
+
// console.log('HIE:',Object.keys(allExtends))
|
|
1234
|
+
const assignments = [];
|
|
1235
|
+
const highest = [];
|
|
1236
|
+
for (const name of layerNames) {
|
|
1237
|
+
if (!(name in allExtends)) {
|
|
1238
|
+
const layer = layeredAnnos[name];
|
|
1239
|
+
delete layeredAnnos[name];
|
|
1240
|
+
highest.push( layer );
|
|
1241
|
+
assignments.push( ...layer.assignments );
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
assignments.sort( compareAssignments );
|
|
1245
|
+
const good = highest.every( layer => layer.assignments.length === 1 );
|
|
1246
|
+
// TODO: use layer.file instead
|
|
1247
|
+
const issue = !good || highest.length > 1 && 'unrelated';
|
|
1248
|
+
// console.log('HI:',highest.map(l=>l.name),issue,issue&&assignments)
|
|
1249
|
+
return { assignments, issue };
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
function compareAssignments( a, b ) {
|
|
1253
|
+
const fileA = layers.realname( a._block );
|
|
1254
|
+
const fileB = layers.realname( b._block );
|
|
1255
|
+
if (fileA !== fileB)
|
|
1256
|
+
return (fileA > fileB) ? 1 : -1;
|
|
1257
|
+
return (a?.location?.line || 0) - (b?.location?.line || 0) ||
|
|
1258
|
+
(a?.location?.col || 0) - (b?.location?.col || 0);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1011
1261
|
/**
|
|
1012
1262
|
* Copy the annotations `@cds.persistence.skip`/`@cds.persistence.exists` from
|
|
1013
1263
|
* source to target if present on source but not target.
|
|
@@ -1019,14 +1269,16 @@ function extend( model ) {
|
|
|
1019
1269
|
function copyPersistenceAnnotations(target, source, options) {
|
|
1020
1270
|
if (!source)
|
|
1021
1271
|
return;
|
|
1022
|
-
|
|
1023
|
-
const
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1272
|
+
|
|
1273
|
+
const copyExists = !isDeprecatedEnabled( options, 'eagerPersistenceForGeneratedEntities' );
|
|
1274
|
+
if (copyExists)
|
|
1275
|
+
copy( '@cds.persistence.exists' );
|
|
1276
|
+
copy( '@cds.persistence.skip' );
|
|
1277
|
+
|
|
1278
|
+
function copy(anno) {
|
|
1279
|
+
if ( source[anno] && !target[anno] )
|
|
1280
|
+
target[anno] = { ...source[anno], $inferred: 'parent-origin' };
|
|
1281
|
+
}
|
|
1030
1282
|
}
|
|
1031
1283
|
|
|
1032
1284
|
function augmentEqual( location, assocname, relations, prefix = '' ) {
|
|
@@ -1054,4 +1306,21 @@ function augmentEqual( location, assocname, relations, prefix = '' ) {
|
|
|
1054
1306
|
}
|
|
1055
1307
|
}
|
|
1056
1308
|
|
|
1309
|
+
/**
|
|
1310
|
+
* If the given extension is a `EXTEND <def> WITH TYPE` extension, store
|
|
1311
|
+
* it in the given artifact. resolve.js will resolve types and call
|
|
1312
|
+
* `typeExtensions()` later.
|
|
1313
|
+
*
|
|
1314
|
+
* @param {XSN.Extension} ext
|
|
1315
|
+
* @param {object} art
|
|
1316
|
+
*/
|
|
1317
|
+
function storeTypeExtension( ext, art ) {
|
|
1318
|
+
// If there are no parameters to apply, don't store the extension.
|
|
1319
|
+
if (!typeParameters.list.some( prop => ext[prop] !== undefined ))
|
|
1320
|
+
return;
|
|
1321
|
+
else if (!art._extendType)
|
|
1322
|
+
setLink( art, '_extendType', [] );
|
|
1323
|
+
art._extendType.push( ext );
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1057
1326
|
module.exports = extend;
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
// Things which needs to done for parse.cdl after define()
|
|
2
2
|
|
|
3
|
+
// For transforming a single CDL file into CSN, parse.cdl resolves block-relative
|
|
4
|
+
// _artifact_ references (in `type`, `includes`, `target`, `targetAspect`, `from`,
|
|
5
|
+
// `extend`, `annotate`) to absolute names. This is the task of the export
|
|
6
|
+
// function of this file, which the compiler calls after the export function of
|
|
7
|
+
// './define.js'.
|
|
8
|
+
|
|
9
|
+
// There should be no need to do other things here – if you think there is,
|
|
10
|
+
// consider adding it to './define.js'. For “semantic locations” in error
|
|
11
|
+
// messages, we set also “structural” links and names inside `extensions` (by
|
|
12
|
+
// calling functions from './define.js').
|
|
13
|
+
|
|
3
14
|
'use strict';
|
|
4
15
|
|
|
5
16
|
const { dictAddArray } = require('../base/dictionaries');
|
|
@@ -18,7 +29,6 @@ function finalizeParseCdl( model ) {
|
|
|
18
29
|
const {
|
|
19
30
|
resolveUncheckedPath,
|
|
20
31
|
resolveTypeArgumentsUnchecked,
|
|
21
|
-
defineAnnotations,
|
|
22
32
|
initMembers,
|
|
23
33
|
extensionsDict,
|
|
24
34
|
} = model.$functions;
|
|
@@ -29,20 +39,13 @@ function finalizeParseCdl( model ) {
|
|
|
29
39
|
function resolveTypesAndExtensionsForParseCdl() {
|
|
30
40
|
const extensions = [];
|
|
31
41
|
|
|
32
|
-
// TODO: probably better to loop over extensions of all sources (there is just one)
|
|
33
42
|
for (const name in extensionsDict) {
|
|
34
43
|
for (const ext of extensionsDict[name]) {
|
|
35
44
|
ext.name.absolute = resolveUncheckedPath( ext.name, 'extend', ext );
|
|
36
|
-
//
|
|
37
|
-
defineAnnotations( ext, ext, ext._block, 'extend' );
|
|
38
|
-
mergeAnnotatesForSameArtifact( ext );
|
|
45
|
+
mergeAnnotatesForSameArtifact( ext ); // TODO: should not be necessary anymore
|
|
39
46
|
// Initialize members and define annotations in sub-elements.
|
|
40
47
|
initMembers( ext, ext, ext._block, true );
|
|
41
48
|
extensions.push( ext );
|
|
42
|
-
for (const col of ext.columns || []) {
|
|
43
|
-
// Note, no `priority` argument, since we don't apply the extension in the end.
|
|
44
|
-
defineAnnotations( col, col, ext._block );
|
|
45
|
-
}
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
51
|
|
|
@@ -50,6 +53,8 @@ function finalizeParseCdl( model ) {
|
|
|
50
53
|
forEachGeneric(model, 'vocabularies', art => resolveTypesForParseCdl(art, art));
|
|
51
54
|
|
|
52
55
|
if (extensions.length > 0) {
|
|
56
|
+
// TODO: do a sort based on the location in case there were two extensions
|
|
57
|
+
// on the same definition?
|
|
53
58
|
model.extensions = extensions;
|
|
54
59
|
model.extensions.forEach(ext => resolveTypesForParseCdl(ext, ext));
|
|
55
60
|
}
|