@sap/cds-compiler 2.13.8 → 2.15.6
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 +128 -4
- package/bin/cdsc.js +112 -37
- package/lib/api/main.js +63 -22
- package/lib/api/options.js +2 -3
- package/lib/api/validate.js +6 -6
- package/lib/base/message-registry.js +100 -17
- package/lib/base/messages.js +85 -64
- package/lib/base/optionProcessorHelper.js +19 -0
- package/lib/checks/annotationsOData.js +11 -32
- package/lib/checks/arrayOfs.js +1 -34
- package/lib/checks/validator.js +2 -4
- package/lib/compiler/assert-consistency.js +1 -0
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +11 -0
- package/lib/compiler/checks.js +22 -70
- package/lib/compiler/define.js +59 -11
- package/lib/compiler/extend.js +20 -3
- package/lib/compiler/finalize-parse-cdl.js +26 -20
- package/lib/compiler/index.js +75 -26
- package/lib/compiler/populate.js +36 -17
- package/lib/compiler/propagator.js +4 -1
- package/lib/compiler/resolve.js +104 -16
- package/lib/compiler/shared.js +61 -27
- package/lib/compiler/tweak-assocs.js +7 -1
- package/lib/edm/annotations/genericTranslation.js +93 -21
- package/lib/edm/csn2edm.js +216 -98
- package/lib/edm/edm.js +305 -226
- package/lib/edm/edmPreprocessor.js +499 -423
- package/lib/edm/edmUtils.js +22 -22
- package/lib/gen/Dictionary.json +98 -22
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/languageParser.js +4636 -4368
- package/lib/json/csnVersion.js +10 -11
- package/lib/json/from-csn.js +3 -2
- package/lib/json/to-csn.js +0 -2
- package/lib/language/docCommentParser.js +2 -2
- package/lib/language/genericAntlrParser.js +47 -2
- package/lib/language/language.g4 +59 -27
- package/lib/main.d.ts +19 -1
- package/lib/main.js +6 -0
- package/lib/model/csnRefs.js +33 -6
- package/lib/model/csnUtils.js +193 -75
- package/lib/model/enrichCsn.js +1 -0
- package/lib/model/revealInternalProperties.js +2 -2
- package/lib/modelCompare/compare.js +6 -6
- package/lib/optionProcessor.js +62 -26
- package/lib/render/toCdl.js +844 -679
- package/lib/render/toHdbcds.js +189 -243
- package/lib/render/toSql.js +180 -198
- package/lib/render/utils/common.js +131 -15
- package/lib/transform/db/.eslintrc.json +1 -1
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/constraints.js +3 -1
- package/lib/transform/db/expansion.js +15 -10
- package/lib/transform/db/flattening.js +94 -64
- package/lib/transform/db/transformExists.js +7 -7
- package/lib/transform/db/views.js +6 -3
- package/lib/transform/forHanaNew.js +43 -26
- package/lib/transform/forOdataNew.js +43 -42
- package/lib/transform/localized.js +12 -7
- package/lib/transform/odata/toFinalBaseType.js +8 -6
- package/lib/transform/odata/typesExposure.js +145 -197
- package/lib/transform/transformUtilsNew.js +9 -12
- package/lib/transform/translateAssocsToJoins.js +5 -1
- package/lib/transform/universalCsn/coreComputed.js +5 -3
- package/lib/transform/universalCsn/universalCsnEnricher.js +27 -5
- package/lib/utils/moduleResolve.js +13 -6
- package/package.json +1 -1
- package/share/messages/message-explanations.json +2 -1
- package/share/messages/syntax-expected-integer.md +37 -0
- package/lib/transform/odata/attachPath.js +0 -96
- package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
- package/lib/transform/odata/generateForeignKeyElements.js +0 -261
- package/lib/transform/odata/referenceFlattener.js +0 -296
- package/lib/transform/odata/sortByAssociationDependency.js +0 -105
- package/lib/transform/odata/structuralPath.js +0 -72
- package/lib/transform/odata/structureFlattener.js +0 -171
|
@@ -13,42 +13,22 @@
|
|
|
13
13
|
* @param {CSN.Element} member Member to be checked
|
|
14
14
|
*/
|
|
15
15
|
function checkCoreMediaTypeAllowence(member) {
|
|
16
|
-
const allowedCoreMediaTypes =
|
|
17
|
-
'cds.String',
|
|
18
|
-
'cds.LargeString',
|
|
19
|
-
'cds.hana.VARCHAR',
|
|
20
|
-
'cds.hana.CHAR',
|
|
21
|
-
'cds.Binary',
|
|
22
|
-
'cds.LargeBinary',
|
|
23
|
-
'cds.hana.CLOB',
|
|
24
|
-
'cds.hana.BINARY',
|
|
25
|
-
|
|
26
|
-
if (member['@Core.MediaType'] && member.type && !
|
|
16
|
+
const allowedCoreMediaTypes = {
|
|
17
|
+
'cds.String': 1,
|
|
18
|
+
'cds.LargeString': 1,
|
|
19
|
+
'cds.hana.VARCHAR': 1,
|
|
20
|
+
'cds.hana.CHAR': 1,
|
|
21
|
+
'cds.Binary': 1,
|
|
22
|
+
'cds.LargeBinary': 1,
|
|
23
|
+
'cds.hana.CLOB': 1,
|
|
24
|
+
'cds.hana.BINARY': 1,
|
|
25
|
+
};
|
|
26
|
+
if (member['@Core.MediaType'] && member.type && !(this.csnUtils.getFinalBaseType(member.type) in allowedCoreMediaTypes)) {
|
|
27
27
|
this.warning(null, member.$path, { names: [ 'Edm.String', 'Edm.Binary' ] },
|
|
28
28
|
'Element annotated with “@Core.MediaType” should be of a type mapped to $(NAMES)');
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
/**
|
|
33
|
-
* Make sure only one element in a definition is annotated with `@Core.MediaType`
|
|
34
|
-
* This is only OData V2 relevant.
|
|
35
|
-
*
|
|
36
|
-
* @param {CSN.Artifact} artifact Definition to be checked
|
|
37
|
-
* @param {string} artifactName The name of the artifact
|
|
38
|
-
*/
|
|
39
|
-
function checkForMultipleCoreMediaTypes(artifact, artifactName) {
|
|
40
|
-
if (!this.csnUtils.getServiceName(artifactName))
|
|
41
|
-
return;
|
|
42
|
-
if (this.options.toOdata && this.options.toOdata.version === 'v2' && artifact.elements) {
|
|
43
|
-
const mediaTypeElementsNames = Object.keys(artifact.elements)
|
|
44
|
-
.filter(elementName => artifact.elements[elementName]['@Core.MediaType']);
|
|
45
|
-
if (mediaTypeElementsNames.length > 1) {
|
|
46
|
-
this.error(null, artifact.$path, { names: mediaTypeElementsNames },
|
|
47
|
-
`Multiple elements $(NAMES) annotated with “@Core.MediaType”, OData V2 allows only one`);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
32
|
/**
|
|
53
33
|
* Check if `@Aggregation.default` is assigned together with `@Analytics.Measure`
|
|
54
34
|
*
|
|
@@ -88,7 +68,6 @@ function checkReadOnlyAndInsertOnly(artifact, artifactName) {
|
|
|
88
68
|
|
|
89
69
|
module.exports = {
|
|
90
70
|
checkCoreMediaTypeAllowence,
|
|
91
|
-
checkForMultipleCoreMediaTypes,
|
|
92
71
|
checkAnalytics,
|
|
93
72
|
checkAtSapAnnotations,
|
|
94
73
|
checkReadOnlyAndInsertOnly,
|
package/lib/checks/arrayOfs.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { forEachMemberRecursively } = require('../model/csnUtils');
|
|
4
|
-
|
|
5
3
|
// Only to be used with validator.js - a correct `this` value needs to be provided!
|
|
6
4
|
|
|
7
5
|
/**
|
|
@@ -43,35 +41,4 @@ function validateAssociationsInItems(member) {
|
|
|
43
41
|
}
|
|
44
42
|
}
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
* Check that there are no .items containing .items.
|
|
48
|
-
*
|
|
49
|
-
* @param {CSN.Artifact} art Artifact
|
|
50
|
-
* @param {string} artName Name of the artifact
|
|
51
|
-
*/
|
|
52
|
-
function checkChainedArray(art, artName) {
|
|
53
|
-
if (!this.csnUtils.getServiceName(artName))
|
|
54
|
-
return;
|
|
55
|
-
checkIfItemsOfItems.bind(this)(art);
|
|
56
|
-
forEachMemberRecursively(art, checkIfItemsOfItems.bind(this));
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
*
|
|
60
|
-
* @param {object} construct the construct to be checked
|
|
61
|
-
*/
|
|
62
|
-
function checkIfItemsOfItems(construct) {
|
|
63
|
-
const constructType = this.csnUtils.effectiveType(construct);
|
|
64
|
-
if (constructType.items) {
|
|
65
|
-
if (constructType.items.items) {
|
|
66
|
-
this.error('chained-array-of', construct.$path, '"Array of"/"many" must not be chained with another "array of"/"many" inside a service');
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const itemsType = this.csnUtils.effectiveType(constructType.items);
|
|
71
|
-
if (itemsType.items)
|
|
72
|
-
this.error('chained-array-of', construct.$path, '"Array of"/"many" must not be chained with another "array of"/"many" inside a service');
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
module.exports = { validateAssociationsInItems, checkChainedArray };
|
|
44
|
+
module.exports = { validateAssociationsInItems };
|
package/lib/checks/validator.js
CHANGED
|
@@ -14,11 +14,10 @@ const checkUsedTypesForAnonymousAspectComposition = require('./managedInType');
|
|
|
14
14
|
const checkForEmptyOrOnlyVirtual = require('./emptyOrOnlyVirtual');
|
|
15
15
|
// forOdata
|
|
16
16
|
const { validateDefaultValues } = require('./defaultValues');
|
|
17
|
-
// const { checkChainedArray } = require('./arrayOfs');
|
|
18
17
|
const { checkActionOrFunction } = require('./actionsFunctions');
|
|
19
18
|
const {
|
|
20
|
-
checkCoreMediaTypeAllowence,
|
|
21
|
-
|
|
19
|
+
checkCoreMediaTypeAllowence, checkAnalytics,
|
|
20
|
+
checkAtSapAnnotations, checkReadOnlyAndInsertOnly,
|
|
22
21
|
} = require('./annotationsOData');
|
|
23
22
|
// both
|
|
24
23
|
const { validateOnCondition, validateMixinOnCondition } = require('./onConditions');
|
|
@@ -92,7 +91,6 @@ const forOdataArtifactValidators
|
|
|
92
91
|
// the renderer does not work because the enricher can't handle certain
|
|
93
92
|
// OData specifics.
|
|
94
93
|
// checkChainedArray,
|
|
95
|
-
checkForMultipleCoreMediaTypes,
|
|
96
94
|
checkReadOnlyAndInsertOnly,
|
|
97
95
|
];
|
|
98
96
|
|
|
@@ -72,6 +72,7 @@ const { locationString, hasErrors } = require('../base/messages');
|
|
|
72
72
|
// Properties that can appear where a type can have type arguments.
|
|
73
73
|
const typeProperties = [
|
|
74
74
|
'type', '$typeArgs', 'length', 'precision', 'scale', 'srid',
|
|
75
|
+
'_effectiveType',
|
|
75
76
|
];
|
|
76
77
|
|
|
77
78
|
function assertConsistency( model, stage ) {
|
package/lib/compiler/base.js
CHANGED
|
@@ -45,6 +45,7 @@ const kindProperties = {
|
|
|
45
45
|
noDep: 'special',
|
|
46
46
|
elements: true, /* only for parse-cdl */
|
|
47
47
|
actions: true, /* only for parse-cdl */
|
|
48
|
+
enum: true, /* only for parse-cdl */
|
|
48
49
|
},
|
|
49
50
|
annotate: {
|
|
50
51
|
isExtension: true, noDep: 'special', elements: true, enum: true, actions: true, params: true,
|
package/lib/compiler/builtins.js
CHANGED
|
@@ -46,6 +46,16 @@ const coreHana = {
|
|
|
46
46
|
ST_GEOMETRY: { parameters: [ { name: 'srid', literal: 'number', val: 0 } ], category: 'geo' },
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
+
const typeParameters = {
|
|
50
|
+
expectedLiteralsFor: {
|
|
51
|
+
length: [ 'number' ],
|
|
52
|
+
scale: [ 'number', 'string' ],
|
|
53
|
+
precision: [ 'number' ],
|
|
54
|
+
srid: [ 'number' ],
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
typeParameters.list = Object.keys( typeParameters.expectedLiteralsFor );
|
|
58
|
+
|
|
49
59
|
// const hana = {
|
|
50
60
|
// BinaryFloat: {},
|
|
51
61
|
// LocalDate: {},
|
|
@@ -312,6 +322,7 @@ function initBuiltins( model ) {
|
|
|
312
322
|
}
|
|
313
323
|
|
|
314
324
|
module.exports = {
|
|
325
|
+
typeParameters,
|
|
315
326
|
functionsWithoutParens,
|
|
316
327
|
specialFunctions,
|
|
317
328
|
initBuiltins,
|
package/lib/compiler/checks.js
CHANGED
|
@@ -24,6 +24,7 @@ function check( model ) { // = XSN
|
|
|
24
24
|
error, warning, message,
|
|
25
25
|
} = model.$messageFunctions;
|
|
26
26
|
forEachDefinition( model, checkArtifact );
|
|
27
|
+
checkSapCommonLocale( model, model.$messageFunctions );
|
|
27
28
|
return;
|
|
28
29
|
|
|
29
30
|
function checkArtifact( art ) {
|
|
@@ -447,80 +448,10 @@ function check( model ) { // = XSN
|
|
|
447
448
|
// params are limited to actual values and params
|
|
448
449
|
if (pathStep.args)
|
|
449
450
|
checkExpression(pathStep.args);
|
|
450
|
-
|
|
451
|
-
if (!path[0] || !path[0]._navigation) { // TODO: Discuss (see #4108)
|
|
452
|
-
checkPathForMissingArguments(pathStep);
|
|
453
|
-
}
|
|
454
451
|
}
|
|
455
452
|
});
|
|
456
453
|
}
|
|
457
454
|
|
|
458
|
-
/**
|
|
459
|
-
* Check whether the argument count of the given path expression matches its artifact.
|
|
460
|
-
* If there is a mismatch, an error is issued.
|
|
461
|
-
*
|
|
462
|
-
* TODO: remove this function - it also checks for parameter in type
|
|
463
|
-
* references. We could have a warning, see also configurable errors
|
|
464
|
-
* 'args-no-params', 'args-undefined-param'.
|
|
465
|
-
*
|
|
466
|
-
* @param {object} pathStep The expression to check
|
|
467
|
-
*/
|
|
468
|
-
function checkPathForMissingArguments(pathStep) {
|
|
469
|
-
// _artifact may not be set, e.g. for functions like `convert_currency( amount => 3 )`
|
|
470
|
-
// _navigation must not be set or we would (for example) check each field of an entity
|
|
471
|
-
if (!pathStep._artifact || pathStep._navigation)
|
|
472
|
-
return;
|
|
473
|
-
|
|
474
|
-
const isAssociation = !!pathStep._artifact.target;
|
|
475
|
-
if (isAssociation) {
|
|
476
|
-
const targetFinalType = pathStep._artifact.target._artifact &&
|
|
477
|
-
pathStep._artifact.target._artifact._effectiveType;
|
|
478
|
-
const finalTypeParams = targetFinalType ? targetFinalType.params : null;
|
|
479
|
-
compareActualNamedArgsWithFormalNamedArgs(pathStep.args, finalTypeParams);
|
|
480
|
-
}
|
|
481
|
-
else {
|
|
482
|
-
// Parameters can only be provided when navigating along associations, so because this path
|
|
483
|
-
// is for non-associations, checking arguments along a navigation is unnecessary and faulty.
|
|
484
|
-
compareActualNamedArgsWithFormalNamedArgs(pathStep.args, pathStep._artifact.params);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
/**
|
|
488
|
-
* Compare two argument dictionaries for correct argument count.
|
|
489
|
-
* @param {object} actualArgs
|
|
490
|
-
* @param {object} formalArgs
|
|
491
|
-
*/
|
|
492
|
-
function compareActualNamedArgsWithFormalNamedArgs(actualArgs, formalArgs) {
|
|
493
|
-
actualArgs = actualArgs || {};
|
|
494
|
-
formalArgs = formalArgs || {};
|
|
495
|
-
|
|
496
|
-
const aArgsCount = Object.keys(actualArgs).length;
|
|
497
|
-
const expectedNames = Object.keys(formalArgs);
|
|
498
|
-
|
|
499
|
-
const missingArgs = [];
|
|
500
|
-
for (const fAName in formalArgs) {
|
|
501
|
-
if (!actualArgs[fAName]) {
|
|
502
|
-
// Note: _effectiveType points to cds.String for `type T : DefaultString`.
|
|
503
|
-
// And `default` may appear at any `type` in the hierarchy.
|
|
504
|
-
let fArg = formalArgs[fAName];
|
|
505
|
-
while (fArg.type && !fArg.default)
|
|
506
|
-
fArg = fArg.type._artifact;
|
|
507
|
-
if (!fArg.default)
|
|
508
|
-
missingArgs.push(fAName);
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
if (missingArgs.length) {
|
|
513
|
-
error(null, [ pathStep.location, pathStep ],
|
|
514
|
-
{ names: missingArgs, expected: expectedNames.length, given: aArgsCount },
|
|
515
|
-
'Expected $(EXPECTED) arguments but $(GIVEN) given; missing: $(NAMES)');
|
|
516
|
-
}
|
|
517
|
-
// Note:
|
|
518
|
-
// Unknown arguments are already handled by messages
|
|
519
|
-
// args-expected-named and args-undefined-param
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
|
|
524
455
|
function checkAssociation(elem) {
|
|
525
456
|
// TODO: yes, a check similar to this could make it into the compiler)
|
|
526
457
|
// when virtual element is part of association
|
|
@@ -956,6 +887,27 @@ function check( model ) { // = XSN
|
|
|
956
887
|
}
|
|
957
888
|
}
|
|
958
889
|
|
|
890
|
+
/**
|
|
891
|
+
* Checks that sap.common.Locale is of type cds.String. This limitation may
|
|
892
|
+
* be lifted later on.
|
|
893
|
+
*
|
|
894
|
+
* @param {XSN.Model} model
|
|
895
|
+
* @param {object} messageFunctions
|
|
896
|
+
*/
|
|
897
|
+
function checkSapCommonLocale( model, messageFunctions ) {
|
|
898
|
+
const localeArt = model.definitions['sap.common.Locale'];
|
|
899
|
+
if (localeArt) {
|
|
900
|
+
const type = localeArt._effectiveType;
|
|
901
|
+
const isCdsString = type && type.name && type.name.absolute === 'cds.String';
|
|
902
|
+
if (!isCdsString) {
|
|
903
|
+
const { message } = messageFunctions;
|
|
904
|
+
message('type-expected-builtin', [ localeArt.name.location, localeArt ],
|
|
905
|
+
{ name: 'sap.common.Locale' },
|
|
906
|
+
'Expected $(NAME) to be a string type');
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
959
911
|
// For each property named 'path' in 'node' (recursively), call callback(path, node)
|
|
960
912
|
//
|
|
961
913
|
// TODO: remove - this is not a good way to traverse expressions
|
package/lib/compiler/define.js
CHANGED
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
// References are resolved in the "resolve" phase of the compiler, see
|
|
78
78
|
// './resolver.js'. We then get the properties `type.absolute` and `length`.
|
|
79
79
|
|
|
80
|
-
// Sub phase 1 (addXYZ) - only for main
|
|
80
|
+
// Sub phase 1 (addXYZ) - only for main artifacts
|
|
81
81
|
// - set _block links
|
|
82
82
|
// - store definitions (including context extensions), NO duplicate check
|
|
83
83
|
// - artifact name check
|
|
@@ -572,7 +572,7 @@ function define( model ) {
|
|
|
572
572
|
if (query.on)
|
|
573
573
|
initExprForQuery( query.on, query );
|
|
574
574
|
// TODO: MIXIN with name = ...subquery (not yet supported anyway)
|
|
575
|
-
initSelectItems( query, query.columns );
|
|
575
|
+
initSelectItems( query, query.columns, query );
|
|
576
576
|
if (query.where)
|
|
577
577
|
initExprForQuery( query.where, query );
|
|
578
578
|
if (query.having)
|
|
@@ -580,12 +580,14 @@ function define( model ) {
|
|
|
580
580
|
initMembers( query, query, query._block );
|
|
581
581
|
}
|
|
582
582
|
|
|
583
|
-
function initSelectItems( parent, columns ) {
|
|
583
|
+
function initSelectItems( parent, columns, user ) {
|
|
584
584
|
// TODO: forbid expand/inline with :param, global:true, in ref-where, outside queries (CSN), ...
|
|
585
585
|
let wildcard = null;
|
|
586
|
+
let hasItems = false;
|
|
586
587
|
for (const col of columns || parent.expand || parent.inline || []) {
|
|
587
588
|
if (!col) // parse error
|
|
588
589
|
continue;
|
|
590
|
+
hasItems = true;
|
|
589
591
|
if (!columns) {
|
|
590
592
|
if (parent.value)
|
|
591
593
|
setLink( col, '_pathHead', parent ); // also set for '*' in expand/inline
|
|
@@ -606,13 +608,27 @@ function define( model ) {
|
|
|
606
608
|
col.val = null; // do not consider it for expandWildcard()
|
|
607
609
|
}
|
|
608
610
|
}
|
|
609
|
-
|
|
611
|
+
// Either expression (value), expand or new association (target && type)
|
|
612
|
+
else if (col.value || col.expand || (col.target && col.type)) {
|
|
610
613
|
setLink( col, '_block', parent._block );
|
|
611
614
|
defineAnnotations( col, col, parent._block ); // TODO: complain with inline
|
|
612
615
|
// TODO: allow sub queries? at least in top-level expand without parallel ref
|
|
613
616
|
if (columns)
|
|
614
617
|
initExprForQuery( col.value, parent );
|
|
615
|
-
initSelectItems( col );
|
|
618
|
+
initSelectItems( col, null, user );
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
if (hasItems && !wildcard && parent.excludingDict) {
|
|
623
|
+
// TODO: Better way to get source file?
|
|
624
|
+
let block = parent;
|
|
625
|
+
while (block._block)
|
|
626
|
+
block = block._block;
|
|
627
|
+
|
|
628
|
+
if (block.$frontend === 'cdl') {
|
|
629
|
+
warning('query-ignoring-exclude', [ parent.excludingDict[$location], user ],
|
|
630
|
+
{ prop: '*' },
|
|
631
|
+
'Excluding elements without wildcard $(PROP) has no effect');
|
|
616
632
|
}
|
|
617
633
|
}
|
|
618
634
|
}
|
|
@@ -898,15 +914,43 @@ function define( model ) {
|
|
|
898
914
|
delete obj.on; // continuation semantics: not specified
|
|
899
915
|
}
|
|
900
916
|
if (targetAspect.elements) {
|
|
917
|
+
const inEntity = parent._main && parent._main.kind === 'entity';
|
|
918
|
+
// TODO: also allow indirectly (component in component in entity)?
|
|
901
919
|
setLink( targetAspect, '_outer', obj );
|
|
902
920
|
setLink( targetAspect, '_parent', parent._parent );
|
|
903
921
|
setLink( targetAspect, '_main', null ); // for name resolution
|
|
904
|
-
|
|
905
|
-
parent =
|
|
922
|
+
|
|
923
|
+
parent = targetAspect;
|
|
906
924
|
construct = parent; // avoid extension behavior
|
|
907
|
-
|
|
908
|
-
setLink(
|
|
909
|
-
initDollarSelf(
|
|
925
|
+
targetAspect.kind = 'aspect'; // TODO: probably '$aspect' to detect
|
|
926
|
+
setLink( targetAspect, '_block', block );
|
|
927
|
+
initDollarSelf( targetAspect );
|
|
928
|
+
// allow ref of up_ in anonymous aspect inside entity
|
|
929
|
+
// (TODO: complain if used and the managed composition is included into
|
|
930
|
+
// another entity - might induce auto-redirection):
|
|
931
|
+
if (inEntity && !targetAspect.elements.up_) {
|
|
932
|
+
const up = {
|
|
933
|
+
name: {
|
|
934
|
+
id: 'up_',
|
|
935
|
+
alias: 'up_',
|
|
936
|
+
element: obj.name.element,
|
|
937
|
+
absolute: obj.name.absolute,
|
|
938
|
+
},
|
|
939
|
+
kind: '$navElement',
|
|
940
|
+
location: obj.location,
|
|
941
|
+
};
|
|
942
|
+
setLink( up, '_parent', targetAspect );
|
|
943
|
+
setLink( up, '_main', targetAspect ); // used on main artifact
|
|
944
|
+
// recompilation case: both target and targetAspect → allow up_ in that case, too:
|
|
945
|
+
const name = obj.target && resolveUncheckedPath( obj.target, 'target', obj );
|
|
946
|
+
const entity = name && model.definitions[name];
|
|
947
|
+
if (entity && entity.elements)
|
|
948
|
+
setLink( up, '_origin', entity.elements.up_ );
|
|
949
|
+
// processAspectComposition/expand() sets _origin to element of
|
|
950
|
+
// generated target entity
|
|
951
|
+
targetAspect.$tableAliases.up_ = up;
|
|
952
|
+
}
|
|
953
|
+
obj = targetAspect;
|
|
910
954
|
}
|
|
911
955
|
}
|
|
912
956
|
if (obj !== parent && obj.elements && parent.enum) {
|
|
@@ -926,6 +970,7 @@ function define( model ) {
|
|
|
926
970
|
if (checkDefinitions( construct, parent, 'enum', obj.enum || false ))
|
|
927
971
|
forEachGeneric( obj, 'enum', init );
|
|
928
972
|
}
|
|
973
|
+
|
|
929
974
|
if (obj.foreignKeys) // cannot be extended or annotated - TODO: check anyway?
|
|
930
975
|
forEachInOrder( obj, 'foreignKeys', init );
|
|
931
976
|
if (checkDefinitions( construct, parent, 'actions' ))
|
|
@@ -1009,7 +1054,7 @@ function define( model ) {
|
|
|
1009
1054
|
// - artifacts (CDL-only anyway) only inside [extend] context|service
|
|
1010
1055
|
if (!dict)
|
|
1011
1056
|
return false;
|
|
1012
|
-
const names = Object.
|
|
1057
|
+
const names = Object.keys( dict );
|
|
1013
1058
|
if (!names.length) // TODO: re-check - really allow empty dict if no other?
|
|
1014
1059
|
return false;
|
|
1015
1060
|
const feature = kindProperties[parent.kind][prop];
|
|
@@ -1046,6 +1091,9 @@ function define( model ) {
|
|
|
1046
1091
|
error( 'unexpected-elements', [ location, construct ], {},
|
|
1047
1092
|
'Elements only exist in entities, types or typed constructs' );
|
|
1048
1093
|
}
|
|
1094
|
+
else if (prop === 'columns') {
|
|
1095
|
+
error( 'extend-columns', [ location, construct ], { art: construct } );
|
|
1096
|
+
}
|
|
1049
1097
|
else { // if (prop === 'enum') {
|
|
1050
1098
|
error( 'unexpected-enum', [ location, construct ], {},
|
|
1051
1099
|
'Enum symbols can only be defined for types or typed constructs' );
|
package/lib/compiler/extend.js
CHANGED
|
@@ -158,6 +158,7 @@ function extend( model ) {
|
|
|
158
158
|
checkDefinitions( ext, art, 'enum');
|
|
159
159
|
checkDefinitions( ext, art, 'actions');
|
|
160
160
|
checkDefinitions( ext, art, 'params');
|
|
161
|
+
checkDefinitions( ext, art, 'columns');
|
|
161
162
|
defineAnnotations( ext, art, ext._block, ext.kind );
|
|
162
163
|
}
|
|
163
164
|
return true;
|
|
@@ -245,7 +246,7 @@ function extend( model ) {
|
|
|
245
246
|
while (obj.items)
|
|
246
247
|
obj = obj.items;
|
|
247
248
|
const validDict = obj[prop] || prop === 'elements' && obj.enum;
|
|
248
|
-
const member = validDict[name];
|
|
249
|
+
const member = validDict && validDict[name];
|
|
249
250
|
if (!member)
|
|
250
251
|
extendNothing( dict[name], prop, name, art, validDict );
|
|
251
252
|
else if (!(member.$duplicates))
|
|
@@ -262,6 +263,10 @@ function extend( model ) {
|
|
|
262
263
|
*/
|
|
263
264
|
function extendColumns( ext, art ) {
|
|
264
265
|
// TODO: consider reportUnstableExtensions
|
|
266
|
+
|
|
267
|
+
for (const col of ext.columns)
|
|
268
|
+
defineAnnotations( col, col, ext._block, ext.kind );
|
|
269
|
+
|
|
265
270
|
const { location } = ext.name;
|
|
266
271
|
const { query } = art;
|
|
267
272
|
if (!query) {
|
|
@@ -602,13 +607,18 @@ function extend( model ) {
|
|
|
602
607
|
elements,
|
|
603
608
|
$inferred: 'localized-entity',
|
|
604
609
|
};
|
|
610
|
+
// If there is a type `sap.common.Locale`, then use it as the type for the element `locale`.
|
|
611
|
+
// If not, use the default `cds.String` with a length of 14.
|
|
612
|
+
const hasLocaleType = model.definitions['sap.common.Locale'] &&
|
|
613
|
+
model.definitions['sap.common.Locale'].kind === 'type';
|
|
605
614
|
const locale = {
|
|
606
615
|
name: { location, id: 'locale' },
|
|
607
616
|
kind: 'element',
|
|
608
|
-
type: augmentPath( location, 'cds.String' ),
|
|
609
|
-
length: { literal: 'number', val: 14, location },
|
|
617
|
+
type: augmentPath( location, hasLocaleType ? 'sap.common.Locale' : 'cds.String' ),
|
|
610
618
|
location,
|
|
611
619
|
};
|
|
620
|
+
if (!hasLocaleType)
|
|
621
|
+
locale.length = { literal: 'number', val: 14, location };
|
|
612
622
|
|
|
613
623
|
if (!fioriEnabled) {
|
|
614
624
|
locale.key = { val: true, location };
|
|
@@ -809,6 +819,13 @@ function extend( model ) {
|
|
|
809
819
|
};
|
|
810
820
|
setArtifactLink( elem.target, entity );
|
|
811
821
|
if (entity) {
|
|
822
|
+
// Support using the up_ element in the generated entity to be used
|
|
823
|
+
// inside the anonymous aspect:
|
|
824
|
+
const { up_ } = target.$tableAliases;
|
|
825
|
+
// TODO: invalidate "up_" alias (at least further navigation) if it
|
|
826
|
+
// already has an _origin (when the managed composition is included)
|
|
827
|
+
if (up_)
|
|
828
|
+
setLink( up_, '_origin', entity.elements.up_ );
|
|
812
829
|
model.$compositionTargets[entity.name.absolute] = true;
|
|
813
830
|
processAspectComposition( entity );
|
|
814
831
|
processLocalizedData( entity );
|
|
@@ -16,7 +16,7 @@ function finalizeParseCdl( model ) {
|
|
|
16
16
|
const { message, error } = model.$messageFunctions;
|
|
17
17
|
const {
|
|
18
18
|
resolveUncheckedPath,
|
|
19
|
-
|
|
19
|
+
resolveTypeArgumentsUnchecked,
|
|
20
20
|
defineAnnotations,
|
|
21
21
|
initMembers,
|
|
22
22
|
extensionsDict,
|
|
@@ -26,8 +26,7 @@ function finalizeParseCdl( model ) {
|
|
|
26
26
|
return;
|
|
27
27
|
|
|
28
28
|
function resolveTypesAndExtensionsForParseCdl() {
|
|
29
|
-
|
|
30
|
-
model.extensions = [];
|
|
29
|
+
const extensions = [];
|
|
31
30
|
|
|
32
31
|
// TODO: probably better to loop over extensions of all sources (there is just one)
|
|
33
32
|
for (const name in extensionsDict) {
|
|
@@ -38,14 +37,21 @@ function finalizeParseCdl( model ) {
|
|
|
38
37
|
mergeAnnotatesForSameArtifact( ext );
|
|
39
38
|
// Initialize members and define annotations in sub-elements.
|
|
40
39
|
initMembers( ext, ext, ext._block, true );
|
|
41
|
-
|
|
40
|
+
extensions.push( ext );
|
|
41
|
+
for (const col of ext.columns || []) {
|
|
42
|
+
// Note, no `priority` argument, since we don't apply the extension in the end.
|
|
43
|
+
defineAnnotations( col, col, ext._block );
|
|
44
|
+
}
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
forEachGeneric(model, 'definitions', art => resolveTypesForParseCdl(art, art));
|
|
46
49
|
forEachGeneric(model, 'vocabularies', art => resolveTypesForParseCdl(art, art));
|
|
47
|
-
|
|
50
|
+
|
|
51
|
+
if (extensions.length > 0) {
|
|
52
|
+
model.extensions = extensions;
|
|
48
53
|
model.extensions.forEach(ext => resolveTypesForParseCdl(ext, ext));
|
|
54
|
+
}
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
/**
|
|
@@ -144,11 +150,13 @@ function finalizeParseCdl( model ) {
|
|
|
144
150
|
|
|
145
151
|
/**
|
|
146
152
|
* Resolves `artWithType.type` in an unchecked manner. Handles `type of` cases.
|
|
153
|
+
* `artWithType` has the `type` property, i.e. it could be an `items` object.
|
|
154
|
+
* `user` is the actual artifact, e.g. entity or element.
|
|
147
155
|
*
|
|
148
156
|
* @param {object} artWithType
|
|
149
|
-
* @param {XSN.Artifact}
|
|
157
|
+
* @param {XSN.Artifact} user
|
|
150
158
|
*/
|
|
151
|
-
function resolveTypeUnchecked(artWithType,
|
|
159
|
+
function resolveTypeUnchecked(artWithType, user) {
|
|
152
160
|
if (!artWithType.type)
|
|
153
161
|
return;
|
|
154
162
|
const root = artWithType.type.path && artWithType.type.path[0];
|
|
@@ -159,19 +167,18 @@ function finalizeParseCdl( model ) {
|
|
|
159
167
|
// without special treatment.
|
|
160
168
|
if (artWithType.type.scope !== 'typeOf') {
|
|
161
169
|
// elem: Type or elem: type of Artifact:elem
|
|
162
|
-
const name = resolveUncheckedPath(artWithType.type, 'type',
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
resolveTypeArguments( artWithType, def, artifact );
|
|
170
|
+
const name = resolveUncheckedPath( artWithType.type, 'type', user );
|
|
171
|
+
const type = name && model.definitions[name] || { name: { absolute: name } };
|
|
172
|
+
resolveTypeArgumentsUnchecked( artWithType, type, user );
|
|
166
173
|
return;
|
|
167
174
|
}
|
|
168
|
-
else if (!
|
|
169
|
-
error( 'ref-undefined-typeof', [ artWithType.type.location,
|
|
175
|
+
else if (!user._main) {
|
|
176
|
+
error( 'ref-undefined-typeof', [ artWithType.type.location, user ], {},
|
|
170
177
|
'Current artifact has no element to refer to as type' );
|
|
171
178
|
return;
|
|
172
179
|
}
|
|
173
180
|
else if (root.id === '$self' || root.id === '$projection') {
|
|
174
|
-
setArtifactLink( root,
|
|
181
|
+
setArtifactLink( root, user._main );
|
|
175
182
|
}
|
|
176
183
|
else {
|
|
177
184
|
// For better error messages, check for invalid TYPE OFs similarly
|
|
@@ -179,19 +186,18 @@ function finalizeParseCdl( model ) {
|
|
|
179
186
|
let struct = artWithType;
|
|
180
187
|
while (struct.kind === 'element')
|
|
181
188
|
struct = struct._parent;
|
|
182
|
-
if (struct.kind === 'select' || struct !==
|
|
183
|
-
message( 'type-unexpected-typeof', [ artWithType.type.location,
|
|
189
|
+
if (struct.kind === 'select' || struct !== user._main) {
|
|
190
|
+
message( 'type-unexpected-typeof', [ artWithType.type.location, user ],
|
|
184
191
|
{ keyword: 'type of', '#': struct.kind } );
|
|
185
192
|
return;
|
|
186
193
|
}
|
|
187
194
|
|
|
188
|
-
const fake = { name: { absolute:
|
|
195
|
+
const fake = { name: { absolute: user.name.absolute } };
|
|
189
196
|
// to-csn just needs a fake element whose absolute name and _parent/_main links are correct
|
|
190
|
-
setLink( fake, '_parent',
|
|
191
|
-
setLink( fake, '_main',
|
|
197
|
+
setLink( fake, '_parent', user._parent );
|
|
198
|
+
setLink( fake, '_main', user._main ); // value does not matter...
|
|
192
199
|
setArtifactLink( root, fake );
|
|
193
200
|
}
|
|
194
|
-
resolveTypeArguments( artifact, {}, artifact ); // issue error for type args
|
|
195
201
|
}
|
|
196
202
|
|
|
197
203
|
function chooseAndReportDuplicateAnnotation(artifact, annoName) {
|