@sap/cds-compiler 2.13.8 → 3.0.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 +155 -1594
- package/bin/cdsc.js +144 -66
- package/doc/CHANGELOG_ARCHIVE.md +1592 -0
- package/doc/CHANGELOG_BETA.md +3 -4
- package/doc/CHANGELOG_DEPRECATED.md +35 -1
- package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
- package/doc/Versioning.md +20 -1
- package/lib/api/.eslintrc.json +2 -2
- package/lib/api/main.js +237 -122
- package/lib/api/options.js +17 -88
- package/lib/api/validate.js +12 -16
- package/lib/base/keywords.js +216 -109
- package/lib/base/message-registry.js +152 -37
- package/lib/base/messages.js +145 -83
- package/lib/base/model.js +44 -2
- package/lib/base/optionProcessorHelper.js +19 -0
- package/lib/checks/actionsFunctions.js +7 -5
- package/lib/checks/annotationsOData.js +11 -32
- package/lib/checks/arrayOfs.js +1 -34
- package/lib/checks/cdsPersistence.js +1 -0
- package/lib/checks/elements.js +6 -6
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/nonexpandableStructured.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -1
- package/lib/checks/selectItems.js +5 -1
- package/lib/checks/types.js +4 -2
- package/lib/checks/utils.js +2 -2
- package/lib/checks/validator.js +4 -5
- package/lib/compiler/assert-consistency.js +16 -10
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +98 -9
- package/lib/compiler/checks.js +22 -70
- package/lib/compiler/define.js +61 -13
- package/lib/compiler/extend.js +79 -14
- package/lib/compiler/finalize-parse-cdl.js +46 -29
- package/lib/compiler/index.js +100 -37
- package/lib/compiler/moduleLayers.js +7 -0
- package/lib/compiler/populate.js +19 -18
- package/lib/compiler/propagator.js +7 -4
- package/lib/compiler/resolve.js +297 -234
- package/lib/compiler/shared.js +107 -102
- package/lib/compiler/tweak-assocs.js +16 -11
- package/lib/compiler/utils.js +5 -0
- package/lib/edm/annotations/genericTranslation.js +93 -21
- package/lib/edm/csn2edm.js +230 -115
- package/lib/edm/edm.js +305 -226
- package/lib/edm/edmPreprocessor.js +509 -438
- package/lib/edm/edmUtils.js +31 -45
- package/lib/gen/Dictionary.json +98 -22
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +10 -30
- package/lib/gen/language.tokens +105 -114
- package/lib/gen/languageLexer.interp +1 -34
- package/lib/gen/languageLexer.js +889 -1007
- package/lib/gen/languageLexer.tokens +95 -106
- package/lib/gen/languageParser.js +20786 -22199
- package/lib/json/csnVersion.js +10 -11
- package/lib/json/from-csn.js +59 -51
- package/lib/json/to-csn.js +10 -10
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/docCommentParser.js +62 -39
- package/lib/language/errorStrategy.js +52 -40
- package/lib/language/genericAntlrParser.js +348 -229
- package/lib/language/language.g4 +629 -653
- package/lib/language/multiLineStringParser.js +14 -42
- package/lib/language/textUtils.js +44 -0
- package/lib/main.d.ts +46 -43
- package/lib/main.js +108 -79
- package/lib/model/csnRefs.js +34 -7
- package/lib/model/csnUtils.js +337 -332
- package/lib/model/enrichCsn.js +1 -0
- package/lib/model/revealInternalProperties.js +30 -10
- package/lib/model/sortViews.js +32 -31
- package/lib/modelCompare/compare.js +6 -6
- package/lib/optionProcessor.js +73 -46
- package/lib/render/.eslintrc.json +1 -1
- package/lib/render/DuplicateChecker.js +4 -7
- package/lib/render/manageConstraints.js +70 -2
- package/lib/render/toCdl.js +1042 -882
- package/lib/render/toHdbcds.js +195 -245
- package/lib/render/toRename.js +44 -22
- package/lib/render/toSql.js +225 -241
- package/lib/render/utils/common.js +145 -15
- package/lib/render/utils/sql.js +20 -19
- package/lib/sql-identifier.js +6 -0
- package/lib/transform/db/.eslintrc.json +4 -3
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/cdsPersistence.js +5 -15
- package/lib/transform/db/constraints.js +4 -2
- package/lib/transform/db/expansion.js +22 -16
- package/lib/transform/db/flattening.js +109 -80
- package/lib/transform/db/transformExists.js +7 -7
- package/lib/transform/db/views.js +9 -6
- package/lib/transform/draft/.eslintrc.json +2 -2
- package/lib/transform/draft/db.js +6 -6
- package/lib/transform/draft/odata.js +6 -7
- package/lib/transform/forHanaNew.js +62 -48
- package/lib/transform/forOdataNew.js +49 -50
- package/lib/transform/localized.js +31 -20
- package/lib/transform/odata/toFinalBaseType.js +16 -14
- package/lib/transform/odata/typesExposure.js +146 -198
- package/lib/transform/odata/utils.js +1 -38
- package/lib/transform/transformUtilsNew.js +67 -84
- package/lib/transform/translateAssocsToJoins.js +7 -3
- package/lib/transform/universalCsn/.eslintrc.json +2 -2
- package/lib/transform/universalCsn/coreComputed.js +16 -9
- package/lib/transform/universalCsn/universalCsnEnricher.js +60 -10
- package/lib/utils/file.js +3 -3
- package/lib/utils/moduleResolve.js +13 -6
- package/lib/utils/timetrace.js +20 -21
- package/package.json +35 -4
- package/share/messages/message-explanations.json +2 -1
- package/share/messages/syntax-expected-integer.md +37 -0
- package/doc/ApiMigration.md +0 -237
- package/doc/CommandLineMigration.md +0 -58
- package/doc/ErrorMessages.md +0 -175
- package/doc/FioriAnnotations.md +0 -94
- package/doc/ODataTransformation.md +0 -273
- package/lib/backends.js +0 -529
- package/lib/fix_antlr4-8_warning.js +0 -56
- 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.getFinalBaseTypeWithProps(member.type)?.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 };
|
|
@@ -14,6 +14,7 @@ function validateCdsPersistenceAnnotation(artifact, artifactName, prop, path) {
|
|
|
14
14
|
if (artifact.kind === 'entity') {
|
|
15
15
|
// filter for 'table', 'udf', 'calcview' === true
|
|
16
16
|
const persistenceAnnos = [ '@cds.persistence.table', '@cds.persistence.udf', '@cds.persistence.calcview' ];
|
|
17
|
+
// TODO: Why not filter over persistenceAnnos, is shorter!
|
|
17
18
|
const TableUdfCv = Object.keys(artifact).filter(p => persistenceAnnos.includes(p) && artifact[p]);
|
|
18
19
|
if (TableUdfCv.length > 1)
|
|
19
20
|
this.error(null, path, `Annotations ${ TableUdfCv.join(', ') } can't be used in combination`);
|
package/lib/checks/elements.js
CHANGED
|
@@ -36,10 +36,10 @@ function checkPrimaryKey(art) {
|
|
|
36
36
|
*/
|
|
37
37
|
function checkIfPrimaryKeyIsOfGeoType(member, elemFqName, parentIsKey, parentPath) {
|
|
38
38
|
if (member.key || parentIsKey) {
|
|
39
|
-
const finalBaseType = this.csnUtils.
|
|
40
|
-
if (
|
|
39
|
+
const finalBaseType = this.csnUtils.getFinalBaseTypeWithProps(member.type);
|
|
40
|
+
if (isGeoTypeName(finalBaseType?.type)) {
|
|
41
41
|
this.error(null, parentPath || member.$path,
|
|
42
|
-
{ type: finalBaseType, name: elemFqName },
|
|
42
|
+
{ type: finalBaseType.type, name: elemFqName },
|
|
43
43
|
'Type $(TYPE) can\'t be used as primary key in element $(NAME)');
|
|
44
44
|
}
|
|
45
45
|
else if (finalBaseType && this.csnUtils.isStructured(finalBaseType)) {
|
|
@@ -63,7 +63,7 @@ function checkPrimaryKey(art) {
|
|
|
63
63
|
*/
|
|
64
64
|
function checkIfPrimaryKeyIsArray(member, elemFqName, parentIsKey, parentPath) {
|
|
65
65
|
if (member.key || parentIsKey) {
|
|
66
|
-
const finalBaseType = this.csnUtils.
|
|
66
|
+
const finalBaseType = this.csnUtils.getFinalBaseTypeWithProps(member.type);
|
|
67
67
|
if (member.items || (finalBaseType && finalBaseType.items)) {
|
|
68
68
|
this.error(null, parentPath || member.$path, { name: elemFqName },
|
|
69
69
|
'Array-like type in element $(NAME) can\'t be used as primary key');
|
|
@@ -96,10 +96,10 @@ function checkVirtualElement(member) {
|
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
98
|
* Checks whether managed associations
|
|
99
|
-
* with cardinality 'to many' have an on-condition
|
|
100
|
-
* and if managed associations have foreign keys.
|
|
99
|
+
* with cardinality 'to many' have an on-condition.
|
|
101
100
|
*
|
|
102
101
|
* @param {CSN.Artifact} art The artifact
|
|
102
|
+
* @todo this is a member validator, is it not?
|
|
103
103
|
*/
|
|
104
104
|
function checkManagedAssoc(art) {
|
|
105
105
|
forEachMemberRecursively(art, (member) => {
|
|
@@ -26,7 +26,7 @@ function invalidTarget(member) {
|
|
|
26
26
|
if (!target)
|
|
27
27
|
throw new ModelError(`Expected target ${ mem.target }`);
|
|
28
28
|
if (target.kind !== 'entity') {
|
|
29
|
-
const isAssoc = this.csnUtils.
|
|
29
|
+
const isAssoc = this.csnUtils.getFinalBaseTypeWithProps(member.type)?.type !== 'cds.Composition';
|
|
30
30
|
this.error(
|
|
31
31
|
null,
|
|
32
32
|
member.$path,
|
|
@@ -21,7 +21,7 @@ function nonexpandableStructuredInExpression(parent, name, expression) {
|
|
|
21
21
|
if (_art) {
|
|
22
22
|
_art = resolveArtifactType.call(this, _art);
|
|
23
23
|
// Paths of an expression may end on a structured element only if both operands in the expression end on a structured element
|
|
24
|
-
if (_art
|
|
24
|
+
if (_art?.elements && !validStructuredElement && $scope !== '$self') { // TODO: Use $self to navigate to struct
|
|
25
25
|
this.error(null, expression[i].$path, { elemref: { ref } },
|
|
26
26
|
'Unexpected usage of structured type $(ELEMREF)');
|
|
27
27
|
}
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
const { hasAnnotationValue, isPersistedOnDatabase, isBuiltinType } = require('../model/csnUtils');
|
|
4
4
|
/**
|
|
5
5
|
* Make sure that all source artifacts and association targets reach the database
|
|
6
|
-
* (otherwise the view can't be activated), but only if the source artifact is NOT activated against the database
|
|
6
|
+
* (otherwise the view can't be activated), but only if the source artifact is NOT activated against the database // <- what does this mean?
|
|
7
|
+
*
|
|
7
8
|
* Check the given query for:
|
|
8
9
|
* - Associations-traversal over skipped/abstract things
|
|
9
10
|
* - Associations (indirectly) using managed associations without foreign keys
|
|
@@ -5,9 +5,13 @@ const { forEachGeneric } = require('../model/csnUtils');
|
|
|
5
5
|
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Validate select items of a query.
|
|
8
|
+
* Validate select items of a query. If a column reference starts with $self or $projection, it must not contain association steps.
|
|
9
|
+
* Furthermore, for to.hdbcds, window functions are not allowed.
|
|
10
|
+
*
|
|
11
|
+
* For to.hdbcds-hdbcds, structures and managed associations are not allowed as they are not flattened - @see rejectManagedAssociationsAndStructuresForHdbcdsNames
|
|
9
12
|
*
|
|
10
13
|
* @param {CSN.Query} query query object
|
|
14
|
+
* @todo Why do we care about this with $self?
|
|
11
15
|
*/
|
|
12
16
|
function validateSelectItems(query) {
|
|
13
17
|
const { SELECT } = query;
|
package/lib/checks/types.js
CHANGED
|
@@ -129,8 +129,8 @@ function checkTypeOfHasProperType(artOrElement, name, model, error, path, derive
|
|
|
129
129
|
if (!artOrElement.type)
|
|
130
130
|
return;
|
|
131
131
|
|
|
132
|
-
const {
|
|
133
|
-
const typeOfType =
|
|
132
|
+
const { getFinalBaseTypeWithProps } = getUtils(model);
|
|
133
|
+
const typeOfType = getFinalBaseTypeWithProps(artOrElement.type);
|
|
134
134
|
|
|
135
135
|
if (typeOfType === null) {
|
|
136
136
|
if (artOrElement.type.ref) {
|
|
@@ -157,6 +157,7 @@ function checkTypeOfHasProperType(artOrElement, name, model, error, path, derive
|
|
|
157
157
|
* @param {CSN.Path} path the path to the element or the artifact
|
|
158
158
|
* @param {string} name of the element or the artifact which is dubious
|
|
159
159
|
* @param {boolean} isElement indicates whether we are dealing with an element or an artifact
|
|
160
|
+
* @todo Rename, is an error not a warning
|
|
160
161
|
*/
|
|
161
162
|
function warnAboutMissingType(error, path, name, isElement = false) {
|
|
162
163
|
error('check-proper-type', path, { art: name, '#': isElement ? 'elm' : 'std' }, {
|
|
@@ -173,6 +174,7 @@ function warnAboutMissingType(error, path, name, isElement = false) {
|
|
|
173
174
|
*
|
|
174
175
|
* @param {CSN.Artifact} artifact the artifact to check
|
|
175
176
|
* @returns {boolean} indicates whether the artifact has type information
|
|
177
|
+
* @todo What is the point of isBuiltinType here if we check for artifact.type at the end?
|
|
176
178
|
*/
|
|
177
179
|
function hasArtifactTypeInformation(artifact) {
|
|
178
180
|
// When is what property set?
|
package/lib/checks/utils.js
CHANGED
|
@@ -31,7 +31,7 @@ function otherSideIsExpandableStructure(on, startIndex) {
|
|
|
31
31
|
return false;
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* Artifact is structured or a managed association/
|
|
34
|
+
* Artifact is structured or a managed association/composition
|
|
35
35
|
*
|
|
36
36
|
* @param {CSN.Artifact} art Artifact
|
|
37
37
|
* @returns {boolean} True if expandable
|
|
@@ -49,7 +49,7 @@ function otherSideIsExpandableStructure(on, startIndex) {
|
|
|
49
49
|
*/
|
|
50
50
|
function resolveArtifactType(art) {
|
|
51
51
|
if (art && art.type && !isBuiltinType(art.type))
|
|
52
|
-
return this.
|
|
52
|
+
return this.csnUtils.getFinalBaseTypeWithProps(art.type);
|
|
53
53
|
|
|
54
54
|
return art;
|
|
55
55
|
}
|
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
|
|
|
@@ -105,8 +103,9 @@ const commonMemberValidators
|
|
|
105
103
|
validateAssociationsInItems, checkForInvalidTarget,
|
|
106
104
|
checkVirtualElement, checkElementTypeDefinitionHasType ];
|
|
107
105
|
|
|
106
|
+
// TODO: checkManagedAssoc is a forEachMemberRecursively!
|
|
108
107
|
const commonArtifactValidators = [ checkTypeDefinitionHasType, checkPrimaryKey, checkManagedAssoc ];
|
|
109
|
-
|
|
108
|
+
// TODO: Does it make sense to run the on-condition check as part of a CSN validator?
|
|
110
109
|
const commonQueryValidators = [ validateMixinOnCondition ];
|
|
111
110
|
|
|
112
111
|
/**
|
|
@@ -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 ) {
|
|
@@ -117,8 +118,10 @@ function assertConsistency( model, stage ) {
|
|
|
117
118
|
'$sources',
|
|
118
119
|
],
|
|
119
120
|
},
|
|
120
|
-
location: {
|
|
121
|
-
|
|
121
|
+
location: {
|
|
122
|
+
// every thing with a $location in CSN must have a XSN location even
|
|
123
|
+
// with syntax errors (currently even internal artifacts like $using):
|
|
124
|
+
isRequired: parent => noSyntaxErrors() || parent && parent.kind,
|
|
122
125
|
kind: true,
|
|
123
126
|
requires: [ 'file' ], // line is optional in top-level location
|
|
124
127
|
optional: [ 'line', 'col', 'endLine', 'endCol', '$notFound' ],
|
|
@@ -184,12 +187,16 @@ function assertConsistency( model, stage ) {
|
|
|
184
187
|
// are missing the location property
|
|
185
188
|
test: isDictionary( definition ),
|
|
186
189
|
requires: [ 'kind', 'name' ],
|
|
187
|
-
optional: [
|
|
190
|
+
optional: [
|
|
191
|
+
'elements', '$autoElement', '$uncheckedElements',
|
|
192
|
+
'$requireElementAccess', '_effectiveType', '_deps',
|
|
193
|
+
],
|
|
188
194
|
schema: {
|
|
189
195
|
kind: { test: isString, enum: [ 'builtin' ] },
|
|
190
196
|
name: { test: isObject, requires: [ 'id', 'element' ] },
|
|
191
197
|
$autoElement: { test: isString },
|
|
192
198
|
$uncheckedElements: { test: isBoolean },
|
|
199
|
+
$requireElementAccess: { test: isBoolean },
|
|
193
200
|
// missing location for normal "elements"
|
|
194
201
|
elements: { test: TODO },
|
|
195
202
|
},
|
|
@@ -216,8 +223,7 @@ function assertConsistency( model, stage ) {
|
|
|
216
223
|
usings: {
|
|
217
224
|
test: isArray(),
|
|
218
225
|
requires: [ 'kind', 'location' ],
|
|
219
|
-
optional: [ 'name', 'extern', 'usings', '
|
|
220
|
-
// TODO: get rid of $annotations: []
|
|
226
|
+
optional: [ 'name', 'extern', 'usings', 'fileDep' ],
|
|
221
227
|
},
|
|
222
228
|
extern: {
|
|
223
229
|
requires: [ 'location', 'path' ],
|
|
@@ -303,7 +309,6 @@ function assertConsistency( model, stage ) {
|
|
|
303
309
|
kind: 'element',
|
|
304
310
|
test: isDictionary( definition ), // definition since redef
|
|
305
311
|
requires: [ 'location', 'name' ],
|
|
306
|
-
optional: [ '$annotations' ], // TODO: get rid of annos: []
|
|
307
312
|
},
|
|
308
313
|
orderBy: { inherits: 'value', test: isArray( expression ) },
|
|
309
314
|
sort: { test: locationVal( isString ), enum: [ 'asc', 'desc' ] },
|
|
@@ -448,11 +453,11 @@ function assertConsistency( model, stage ) {
|
|
|
448
453
|
'@': {
|
|
449
454
|
kind: true,
|
|
450
455
|
inherits: 'value',
|
|
451
|
-
optional: [ 'name', '_block', '$priority', '$
|
|
456
|
+
optional: [ 'name', '_block', '$priority', '$inferred', '$duplicates', '$errorReported' ],
|
|
452
457
|
// TODO: name requires if not in parser?
|
|
453
458
|
},
|
|
454
|
-
$priority: { test: TODO },
|
|
455
|
-
$annotations: { parser: true, kind: true, test: TODO },
|
|
459
|
+
$priority: { test: TODO },
|
|
460
|
+
$annotations: { parser: true, kind: true, test: TODO }, // deprecated, still there for cds-lsp
|
|
456
461
|
name: {
|
|
457
462
|
isRequired: stageParser && (() => false), // not required in parser
|
|
458
463
|
kind: true,
|
|
@@ -467,7 +472,7 @@ function assertConsistency( model, stage ) {
|
|
|
467
472
|
],
|
|
468
473
|
},
|
|
469
474
|
absolute: { test: isString },
|
|
470
|
-
variant: { test: TODO }, // TODO: not set in CDL parser
|
|
475
|
+
variant: { test: TODO }, // TODO: not set in CDL parser
|
|
471
476
|
element: { test: TODO }, // TODO: { test: isString },
|
|
472
477
|
action: { test: isString },
|
|
473
478
|
param: { test: isString },
|
|
@@ -580,6 +585,7 @@ function assertConsistency( model, stage ) {
|
|
|
580
585
|
// (it can contain the artifact itself with no/failed autoexposure):
|
|
581
586
|
_descendants: { kind: [ 'entity' ], test: isDictionary( isArray( TODO ) ) },
|
|
582
587
|
|
|
588
|
+
$errorReported: { parser: true, test: isBoolean }, // to avoid duplicate messages
|
|
583
589
|
$duplicates: { parser: true, kind: true, test: TODO }, // array of arts or true
|
|
584
590
|
$extension: { kind: true, test: TODO }, // TODO: introduce $applied instead or $status
|
|
585
591
|
$inferred: { parser: true, kind: true, test: isString },
|
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: {},
|
|
@@ -66,19 +76,92 @@ const functionsWithoutParens = [
|
|
|
66
76
|
'CURRENT_USER', 'SESSION_USER', 'SYSTEM_USER',
|
|
67
77
|
];
|
|
68
78
|
|
|
69
|
-
const specialFunctions = {
|
|
79
|
+
const specialFunctions = compileFunctions( {
|
|
80
|
+
'': [ // the default
|
|
81
|
+
{
|
|
82
|
+
intro: [ 'ALL', 'DISTINCT' ],
|
|
83
|
+
introMsg: [], // do not list them in code completion
|
|
84
|
+
},
|
|
85
|
+
{},
|
|
86
|
+
],
|
|
70
87
|
ROUND: [
|
|
71
88
|
null, null, { // 3rd argument: rounding mode
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
ROUND_HALF_EVEN: 'argFull',
|
|
75
|
-
ROUND_UP: 'argFull',
|
|
76
|
-
ROUND_DOWN: 'argFull',
|
|
77
|
-
ROUND_CEILING: 'argFull',
|
|
78
|
-
ROUND_FLOOR: 'argFull',
|
|
89
|
+
expr: [ 'ROUND_HALF_UP', 'ROUND_HALF_DOWN', 'ROUND_HALF_EVEN',
|
|
90
|
+
'ROUND_UP', 'ROUND_DOWN', 'ROUND_CEILING', 'ROUND_FLOOR' ],
|
|
79
91
|
},
|
|
80
92
|
],
|
|
81
|
-
|
|
93
|
+
TRIM: [
|
|
94
|
+
{
|
|
95
|
+
intro: [ 'LEADING', 'TRAILING', 'BOTH' ],
|
|
96
|
+
expr: [ 'LEADING', 'TRAILING', 'BOTH' ],
|
|
97
|
+
separator: [ 'FROM' ],
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
EXTRACT: [
|
|
101
|
+
{
|
|
102
|
+
expr: [ 'YEAR', 'MONTH', 'DAY', 'HOUR', 'MINUTE', 'SECOND' ],
|
|
103
|
+
separator: [ 'FROM' ],
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
COUNT: [
|
|
107
|
+
{
|
|
108
|
+
expr: [ '*' ],
|
|
109
|
+
intro: [ 'ALL', 'DISTINCT' ],
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
MIN: 'COUNT',
|
|
113
|
+
MAX: 'COUNT',
|
|
114
|
+
SUM: 'COUNT',
|
|
115
|
+
AVG: 'COUNT',
|
|
116
|
+
STDDDEV: 'COUNT',
|
|
117
|
+
VAR: 'COUNT',
|
|
118
|
+
LOCATE_REGEXPR: [
|
|
119
|
+
{
|
|
120
|
+
intro: [ 'START', 'AFTER' ],
|
|
121
|
+
separator: [ 'FLAG', 'IN', 'FROM', 'OCCURRENCE', 'GROUP' ],
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
OCCURRENCES_REGEXPR: [
|
|
125
|
+
{
|
|
126
|
+
separator: [ 'FLAG', 'IN', 'FROM' ],
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
REPLACE_REGEXPR: [
|
|
130
|
+
{
|
|
131
|
+
separator: [ 'FLAG', 'IN', 'WITH', 'FROM', 'OCCURRENCE' ],
|
|
132
|
+
expr: [ 'ALL' ],
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
SUBSTRING_REGEXPR: [
|
|
136
|
+
{
|
|
137
|
+
separator: [ 'FLAG', 'IN', 'FROM', 'OCCURRENCE', 'GROUP' ],
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
} );
|
|
141
|
+
|
|
142
|
+
function compileFunctions( special ) {
|
|
143
|
+
const compiled = {};
|
|
144
|
+
for (const [ name, val ] of Object.entries( special ))
|
|
145
|
+
compiled[name] = (typeof val === 'string' ? special[val] : val).map( compileArg );
|
|
146
|
+
return compiled;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function compileArg( src ) {
|
|
150
|
+
if (!src)
|
|
151
|
+
return src;
|
|
152
|
+
const tgt = {
|
|
153
|
+
intro: src.intro || [],
|
|
154
|
+
expr: src.expr || [],
|
|
155
|
+
separator: src.separator || [],
|
|
156
|
+
};
|
|
157
|
+
for (const generic of [ 'intro', 'expr', 'separator' ]) { // intro before expr!
|
|
158
|
+
for (const token of src[generic] || [])
|
|
159
|
+
tgt[token] = generic;
|
|
160
|
+
}
|
|
161
|
+
if (tgt.intro) // same token could be in both 'expr' and 'intro':
|
|
162
|
+
tgt.introMsg = src.introMsg || tgt.intro.filter( token => tgt[token] === 'intro' );
|
|
163
|
+
return tgt;
|
|
164
|
+
}
|
|
82
165
|
|
|
83
166
|
/**
|
|
84
167
|
* Variables that have special meaning in CDL/CSN.
|
|
@@ -96,12 +179,15 @@ const magicVariables = {
|
|
|
96
179
|
elements: {
|
|
97
180
|
from: {}, to: {},
|
|
98
181
|
},
|
|
182
|
+
// Require that elements are accessed, i.e. no $at, only $at.<element>.
|
|
183
|
+
$requireElementAccess: true,
|
|
99
184
|
},
|
|
100
185
|
$now: {}, // Dito
|
|
101
186
|
$session: {
|
|
102
187
|
// In ABAP CDS session variables are accessed in a generic way via
|
|
103
188
|
// the pseudo variable $session.
|
|
104
189
|
$uncheckedElements: true,
|
|
190
|
+
$requireElementAccess: true,
|
|
105
191
|
},
|
|
106
192
|
};
|
|
107
193
|
|
|
@@ -276,6 +362,8 @@ function initBuiltins( model ) {
|
|
|
276
362
|
art.$autoElement = magic.$autoElement;
|
|
277
363
|
if (magic.$uncheckedElements)
|
|
278
364
|
art.$uncheckedElements = magic.$uncheckedElements;
|
|
365
|
+
if (magic.$requireElementAccess)
|
|
366
|
+
art.$requireElementAccess = magic.$requireElementAccess;
|
|
279
367
|
|
|
280
368
|
createMagicElements( art, magic.elements );
|
|
281
369
|
if (options.variableReplacements)
|
|
@@ -312,6 +400,7 @@ function initBuiltins( model ) {
|
|
|
312
400
|
}
|
|
313
401
|
|
|
314
402
|
module.exports = {
|
|
403
|
+
typeParameters,
|
|
315
404
|
functionsWithoutParens,
|
|
316
405
|
specialFunctions,
|
|
317
406
|
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
|