@sap/cds-compiler 2.15.2 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -1590
- package/bin/cdsc.js +42 -46
- 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 +312 -143
- package/lib/api/options.js +15 -85
- package/lib/api/validate.js +6 -10
- package/lib/base/keywords.js +280 -110
- package/lib/base/message-registry.js +80 -24
- package/lib/base/messages.js +103 -52
- package/lib/base/model.js +44 -2
- package/lib/base/optionProcessorHelper.js +53 -21
- package/lib/checks/actionsFunctions.js +7 -5
- package/lib/checks/annotationsOData.js +1 -1
- 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 +2 -1
- package/lib/compiler/assert-consistency.js +15 -10
- package/lib/compiler/builtins.js +127 -10
- package/lib/compiler/define.js +6 -4
- package/lib/compiler/extend.js +63 -12
- package/lib/compiler/finalize-parse-cdl.js +20 -9
- package/lib/compiler/index.js +25 -11
- package/lib/compiler/moduleLayers.js +7 -0
- package/lib/compiler/populate.js +16 -14
- package/lib/compiler/propagator.js +3 -3
- package/lib/compiler/resolve.js +194 -222
- package/lib/compiler/shared.js +56 -76
- package/lib/compiler/tweak-assocs.js +9 -10
- package/lib/compiler/utils.js +7 -2
- package/lib/edm/annotations/genericTranslation.js +60 -6
- package/lib/edm/annotations/preprocessAnnotations.js +10 -11
- package/lib/edm/csn2edm.js +39 -41
- package/lib/edm/edm.js +22 -15
- package/lib/edm/edmPreprocessor.js +66 -69
- package/lib/edm/edmUtils.js +12 -62
- package/lib/gen/Dictionary.json +8 -6
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +8 -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 +20717 -22376
- package/lib/json/from-csn.js +73 -68
- package/lib/json/to-csn.js +13 -10
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/docCommentParser.js +61 -38
- package/lib/language/errorStrategy.js +52 -40
- package/lib/language/genericAntlrParser.js +333 -259
- package/lib/language/language.g4 +600 -645
- package/lib/language/multiLineStringParser.js +14 -42
- package/lib/language/textUtils.js +44 -0
- package/lib/main.d.ts +27 -42
- package/lib/main.js +104 -81
- package/lib/model/csnRefs.js +2 -1
- package/lib/model/csnUtils.js +183 -285
- package/lib/model/revealInternalProperties.js +32 -9
- package/lib/model/sortViews.js +32 -31
- package/lib/optionProcessor.js +64 -57
- 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 +334 -339
- package/lib/render/toHdbcds.js +20 -16
- package/lib/render/toRename.js +44 -22
- package/lib/render/toSql.js +60 -54
- package/lib/render/utils/common.js +15 -1
- package/lib/render/utils/sql.js +20 -19
- package/lib/sql-identifier.js +6 -0
- package/lib/transform/db/.eslintrc.json +3 -2
- package/lib/transform/db/cdsPersistence.js +5 -15
- package/lib/transform/db/constraints.js +1 -1
- package/lib/transform/db/expansion.js +7 -6
- package/lib/transform/db/flattening.js +18 -19
- package/lib/transform/db/views.js +3 -3
- 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 +19 -22
- package/lib/transform/forOdataNew.js +13 -15
- package/lib/transform/localized.js +35 -25
- package/lib/transform/odata/toFinalBaseType.js +11 -9
- package/lib/transform/odata/typesExposure.js +3 -3
- package/lib/transform/odata/utils.js +1 -38
- package/lib/transform/transformUtilsNew.js +63 -77
- package/lib/transform/translateAssocsToJoins.js +6 -2
- package/lib/transform/universalCsn/.eslintrc.json +2 -2
- package/lib/transform/universalCsn/coreComputed.js +11 -6
- package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
- package/lib/utils/file.js +31 -21
- package/lib/utils/timetrace.js +20 -21
- package/package.json +34 -4
- package/share/messages/syntax-expected-integer.md +9 -8
- 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
|
@@ -153,36 +153,67 @@ function createOptionProcessor() {
|
|
|
153
153
|
* Internal: Define a general or command option.
|
|
154
154
|
* Throws if the option is already registered in the given command context.
|
|
155
155
|
* or in the given command.
|
|
156
|
+
*
|
|
156
157
|
* @private
|
|
157
158
|
* @see option()
|
|
158
159
|
*/
|
|
159
160
|
function _addOption(cmd, optString, validValues, options) {
|
|
160
|
-
const
|
|
161
|
-
Object.assign(
|
|
161
|
+
const cliOpt = _parseOptionString(optString, validValues);
|
|
162
|
+
Object.assign(cliOpt, options);
|
|
163
|
+
_addLongOption(cmd, cliOpt.longName, cliOpt);
|
|
164
|
+
_addShortOption(cmd, cliOpt.shortName, cliOpt);
|
|
165
|
+
|
|
166
|
+
for (const alias of cliOpt.aliases || []) {
|
|
167
|
+
const aliasOpt = Object.assign({ }, cliOpt, { isAlias: true });
|
|
168
|
+
_addLongOption(cmd, alias, aliasOpt); // use same camelName, etc. for alias
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return cmd;
|
|
172
|
+
}
|
|
162
173
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Internal: Add longName to the list of options.
|
|
176
|
+
* Throws if the option is already registered in the given command context.
|
|
177
|
+
* or in the given command.
|
|
178
|
+
* `longName` may differ from `opt.longName`, e.g. for aliases.
|
|
179
|
+
*
|
|
180
|
+
* @private
|
|
181
|
+
* @see _addOption()
|
|
182
|
+
*/
|
|
183
|
+
function _addLongOption(cmd, longName, opt) {
|
|
184
|
+
if (cmd.options[longName]) {
|
|
185
|
+
throw new Error(`Duplicate assignment for long option ${longName}`);
|
|
186
|
+
} else if (optionProcessor.options[longName]) {
|
|
166
187
|
// This path is only taken if optString is for commands
|
|
167
188
|
optionProcessor.optionClashes.push({
|
|
168
|
-
option:
|
|
169
|
-
description: `Command '${cmd.longName}' has option clash with general options for: ${
|
|
189
|
+
option: longName,
|
|
190
|
+
description: `Command '${cmd.longName}' has option clash with general options for: ${longName}`
|
|
170
191
|
});
|
|
171
192
|
}
|
|
172
|
-
cmd.options[
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
193
|
+
cmd.options[longName] = opt;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Internal: Add shortName to the list of options.
|
|
197
|
+
* Throws if the option is already registered in the given command context.
|
|
198
|
+
* or in the given command.
|
|
199
|
+
* `longName` may differ from `opt.longName`, e.g. for aliases.
|
|
200
|
+
*
|
|
201
|
+
* @private
|
|
202
|
+
* @see _addOption()
|
|
203
|
+
*/
|
|
204
|
+
function _addShortOption(cmd, shortName, opt) {
|
|
205
|
+
if (!shortName)
|
|
206
|
+
return;
|
|
207
|
+
if (cmd.options[shortName]) {
|
|
208
|
+
throw new Error(`Duplicate assignment for short option ${shortName}`);
|
|
209
|
+
} else if (optionProcessor.options[shortName]) {
|
|
210
|
+
// This path is only taken if optString is for commands
|
|
211
|
+
optionProcessor.optionClashes.push({
|
|
212
|
+
option: shortName,
|
|
213
|
+
description: `Command '${cmd.longName}' has option clash with general options for: ${shortName}`
|
|
214
|
+
});
|
|
184
215
|
}
|
|
185
|
-
|
|
216
|
+
cmd.options[shortName] = opt;
|
|
186
217
|
}
|
|
187
218
|
|
|
188
219
|
// Internal: Parse one command string like "F, toFoo". Return an object like this
|
|
@@ -275,7 +306,8 @@ function createOptionProcessor() {
|
|
|
275
306
|
shortName,
|
|
276
307
|
camelName: camelifyLongOption(longName),
|
|
277
308
|
param,
|
|
278
|
-
validValues
|
|
309
|
+
validValues,
|
|
310
|
+
isAlias: false, // default
|
|
279
311
|
}
|
|
280
312
|
}
|
|
281
313
|
|
|
@@ -16,8 +16,8 @@ function checkActionOrFunction(art, artName, prop, path) {
|
|
|
16
16
|
if (!(art.kind === 'action' || art.kind === 'function') && !art.actions)
|
|
17
17
|
return;
|
|
18
18
|
|
|
19
|
-
// const isMultiSchema = this.options.
|
|
20
|
-
// (this.options.
|
|
19
|
+
// const isMultiSchema = this.options.odataFormat === 'structured' &&
|
|
20
|
+
// (this.options.odataProxies || this.options.odataXServiceRefs);
|
|
21
21
|
|
|
22
22
|
const serviceName = this.csnUtils.getServiceName(artName);
|
|
23
23
|
if (!serviceName)
|
|
@@ -85,9 +85,11 @@ function checkActionOrFunction(art, artName, prop, path) {
|
|
|
85
85
|
* @param {string} actKind 'action' or 'function'
|
|
86
86
|
*/
|
|
87
87
|
function checkReturns(returns, currPath, actKind) {
|
|
88
|
-
const finalReturnType = returns.type ? this.csnUtils.
|
|
88
|
+
const finalReturnType = returns.type ? this.csnUtils.getFinalBaseTypeWithProps(returns.type) : returns;
|
|
89
|
+
if (!finalReturnType)
|
|
90
|
+
return; // no type, e.g. `type of V:calculated`; already an error in `checkTypeOfHasProperType()`
|
|
89
91
|
|
|
90
|
-
if (this.csnUtils.isAssocOrComposition(finalReturnType)) {
|
|
92
|
+
if (this.csnUtils.isAssocOrComposition(finalReturnType.type)) {
|
|
91
93
|
this.error(null, currPath, { '#': actKind },
|
|
92
94
|
{
|
|
93
95
|
std: 'An association is not allowed as this artifact\'s return type', // Not used
|
|
@@ -98,7 +100,7 @@ function checkActionOrFunction(art, artName, prop, path) {
|
|
|
98
100
|
|
|
99
101
|
if (finalReturnType.items) // check array return type
|
|
100
102
|
checkReturns.bind(this)(finalReturnType.items, currPath.concat('items'), actKind);
|
|
101
|
-
else // check if return type is user
|
|
103
|
+
else // check if return type is user defined from the current service
|
|
102
104
|
checkUserDefinedType.bind(this)(finalReturnType, returns.type, currPath);
|
|
103
105
|
}
|
|
104
106
|
|
|
@@ -23,7 +23,7 @@ function checkCoreMediaTypeAllowence(member) {
|
|
|
23
23
|
'cds.hana.CLOB': 1,
|
|
24
24
|
'cds.hana.BINARY': 1,
|
|
25
25
|
};
|
|
26
|
-
if (member['@Core.MediaType'] && member.type && !(this.csnUtils.
|
|
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
|
}
|
|
@@ -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
|
@@ -103,8 +103,9 @@ const commonMemberValidators
|
|
|
103
103
|
validateAssociationsInItems, checkForInvalidTarget,
|
|
104
104
|
checkVirtualElement, checkElementTypeDefinitionHasType ];
|
|
105
105
|
|
|
106
|
+
// TODO: checkManagedAssoc is a forEachMemberRecursively!
|
|
106
107
|
const commonArtifactValidators = [ checkTypeDefinitionHasType, checkPrimaryKey, checkManagedAssoc ];
|
|
107
|
-
|
|
108
|
+
// TODO: Does it make sense to run the on-condition check as part of a CSN validator?
|
|
108
109
|
const commonQueryValidators = [ validateMixinOnCondition ];
|
|
109
110
|
|
|
110
111
|
/**
|
|
@@ -118,8 +118,10 @@ function assertConsistency( model, stage ) {
|
|
|
118
118
|
'$sources',
|
|
119
119
|
],
|
|
120
120
|
},
|
|
121
|
-
location: {
|
|
122
|
-
|
|
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,
|
|
123
125
|
kind: true,
|
|
124
126
|
requires: [ 'file' ], // line is optional in top-level location
|
|
125
127
|
optional: [ 'line', 'col', 'endLine', 'endCol', '$notFound' ],
|
|
@@ -185,12 +187,16 @@ function assertConsistency( model, stage ) {
|
|
|
185
187
|
// are missing the location property
|
|
186
188
|
test: isDictionary( definition ),
|
|
187
189
|
requires: [ 'kind', 'name' ],
|
|
188
|
-
optional: [
|
|
190
|
+
optional: [
|
|
191
|
+
'elements', '$autoElement', '$uncheckedElements',
|
|
192
|
+
'$requireElementAccess', '_effectiveType', '_deps',
|
|
193
|
+
],
|
|
189
194
|
schema: {
|
|
190
195
|
kind: { test: isString, enum: [ 'builtin' ] },
|
|
191
196
|
name: { test: isObject, requires: [ 'id', 'element' ] },
|
|
192
197
|
$autoElement: { test: isString },
|
|
193
198
|
$uncheckedElements: { test: isBoolean },
|
|
199
|
+
$requireElementAccess: { test: isBoolean },
|
|
194
200
|
// missing location for normal "elements"
|
|
195
201
|
elements: { test: TODO },
|
|
196
202
|
},
|
|
@@ -217,8 +223,7 @@ function assertConsistency( model, stage ) {
|
|
|
217
223
|
usings: {
|
|
218
224
|
test: isArray(),
|
|
219
225
|
requires: [ 'kind', 'location' ],
|
|
220
|
-
optional: [ 'name', 'extern', 'usings', '
|
|
221
|
-
// TODO: get rid of $annotations: []
|
|
226
|
+
optional: [ 'name', 'extern', 'usings', 'fileDep' ],
|
|
222
227
|
},
|
|
223
228
|
extern: {
|
|
224
229
|
requires: [ 'location', 'path' ],
|
|
@@ -304,7 +309,6 @@ function assertConsistency( model, stage ) {
|
|
|
304
309
|
kind: 'element',
|
|
305
310
|
test: isDictionary( definition ), // definition since redef
|
|
306
311
|
requires: [ 'location', 'name' ],
|
|
307
|
-
optional: [ '$annotations' ], // TODO: get rid of annos: []
|
|
308
312
|
},
|
|
309
313
|
orderBy: { inherits: 'value', test: isArray( expression ) },
|
|
310
314
|
sort: { test: locationVal( isString ), enum: [ 'asc', 'desc' ] },
|
|
@@ -449,11 +453,11 @@ function assertConsistency( model, stage ) {
|
|
|
449
453
|
'@': {
|
|
450
454
|
kind: true,
|
|
451
455
|
inherits: 'value',
|
|
452
|
-
optional: [ 'name', '_block', '$priority', '$
|
|
456
|
+
optional: [ 'name', '_block', '$priority', '$inferred', '$duplicates', '$errorReported' ],
|
|
453
457
|
// TODO: name requires if not in parser?
|
|
454
458
|
},
|
|
455
|
-
$priority: { test: TODO },
|
|
456
|
-
$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
|
|
457
461
|
name: {
|
|
458
462
|
isRequired: stageParser && (() => false), // not required in parser
|
|
459
463
|
kind: true,
|
|
@@ -468,7 +472,7 @@ function assertConsistency( model, stage ) {
|
|
|
468
472
|
],
|
|
469
473
|
},
|
|
470
474
|
absolute: { test: isString },
|
|
471
|
-
variant: { test: TODO }, // TODO: not set in CDL parser
|
|
475
|
+
variant: { test: TODO }, // TODO: not set in CDL parser
|
|
472
476
|
element: { test: TODO }, // TODO: { test: isString },
|
|
473
477
|
action: { test: isString },
|
|
474
478
|
param: { test: isString },
|
|
@@ -581,6 +585,7 @@ function assertConsistency( model, stage ) {
|
|
|
581
585
|
// (it can contain the artifact itself with no/failed autoexposure):
|
|
582
586
|
_descendants: { kind: [ 'entity' ], test: isDictionary( isArray( TODO ) ) },
|
|
583
587
|
|
|
588
|
+
$errorReported: { parser: true, kind: true, test: isString }, // to avoid duplicate messages
|
|
584
589
|
$duplicates: { parser: true, kind: true, test: TODO }, // array of arts or true
|
|
585
590
|
$extension: { kind: true, test: TODO }, // TODO: introduce $applied instead or $status
|
|
586
591
|
$inferred: { parser: true, kind: true, test: isString },
|
package/lib/compiler/builtins.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The builtin artifacts of CDS
|
|
2
2
|
|
|
3
3
|
// TODO: split this file
|
|
4
|
-
// - in base/: common definitions
|
|
4
|
+
// - in base/: common definitions, datetime formats
|
|
5
5
|
// - in compiler/: XSN-specific
|
|
6
6
|
// - in ?: CSN-specific
|
|
7
7
|
|
|
@@ -76,19 +76,92 @@ const functionsWithoutParens = [
|
|
|
76
76
|
'CURRENT_USER', 'SESSION_USER', 'SYSTEM_USER',
|
|
77
77
|
];
|
|
78
78
|
|
|
79
|
-
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
|
+
],
|
|
80
87
|
ROUND: [
|
|
81
88
|
null, null, { // 3rd argument: rounding mode
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
ROUND_HALF_EVEN: 'argFull',
|
|
85
|
-
ROUND_UP: 'argFull',
|
|
86
|
-
ROUND_DOWN: 'argFull',
|
|
87
|
-
ROUND_CEILING: 'argFull',
|
|
88
|
-
ROUND_FLOOR: 'argFull',
|
|
89
|
+
expr: [ 'ROUND_HALF_UP', 'ROUND_HALF_DOWN', 'ROUND_HALF_EVEN',
|
|
90
|
+
'ROUND_UP', 'ROUND_DOWN', 'ROUND_CEILING', 'ROUND_FLOOR' ],
|
|
89
91
|
},
|
|
90
92
|
],
|
|
91
|
-
|
|
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
|
+
}
|
|
92
165
|
|
|
93
166
|
/**
|
|
94
167
|
* Variables that have special meaning in CDL/CSN.
|
|
@@ -106,17 +179,58 @@ const magicVariables = {
|
|
|
106
179
|
elements: {
|
|
107
180
|
from: {}, to: {},
|
|
108
181
|
},
|
|
182
|
+
// Require that elements are accessed, i.e. no $at, only $at.<element>.
|
|
183
|
+
$requireElementAccess: true,
|
|
109
184
|
},
|
|
110
185
|
$now: {}, // Dito
|
|
111
186
|
$session: {
|
|
112
187
|
// In ABAP CDS session variables are accessed in a generic way via
|
|
113
188
|
// the pseudo variable $session.
|
|
114
189
|
$uncheckedElements: true,
|
|
190
|
+
$requireElementAccess: true,
|
|
115
191
|
},
|
|
116
192
|
};
|
|
117
193
|
|
|
118
194
|
// see lib/render/renderUtil.js for DB-specific magic vars, specified in CAP Cds via function
|
|
119
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Patterns for literal token tests and creation. The value is a map from the
|
|
198
|
+
* `prefix` argument of function `quotedliteral` to the following properties:
|
|
199
|
+
* - `test_msg`: error message which is issued if `test_fn` or `test_re` fail.
|
|
200
|
+
* - `test_fn`: function called with argument `value`, fails falsy return value
|
|
201
|
+
* - `test_re`: regular expression, fails if it does not match argument `value`
|
|
202
|
+
* - `unexpected_msg`: error message which is issued if `unexpected_char` matches
|
|
203
|
+
* - `unexpected_char`: regular expression matching an illegal character in `value`,
|
|
204
|
+
* the error location is only correct for a literal <prefix>'<value>'
|
|
205
|
+
* - `literal`: the value which is used instead of `prefix` in the AST
|
|
206
|
+
* TODO: we might do a range check (consider leap seconds, i.e. max value 60),
|
|
207
|
+
* but always allow Feb 29 (no leap year computation)
|
|
208
|
+
*/
|
|
209
|
+
const quotedLiteralPatterns = {
|
|
210
|
+
x: {
|
|
211
|
+
test_variant: 'uneven-hex',
|
|
212
|
+
test_fn: (str => Number.isInteger(str.length / 2)),
|
|
213
|
+
unexpected_variant: 'invalid-hex',
|
|
214
|
+
unexpected_char: /[^0-9a-f]/i,
|
|
215
|
+
json_type: 'string',
|
|
216
|
+
},
|
|
217
|
+
time: {
|
|
218
|
+
test_variant: 'time',
|
|
219
|
+
test_re: /^[0-9]{1,2}:[0-9]{1,2}(:[0-9]{1,2})?$/,
|
|
220
|
+
json_type: 'string',
|
|
221
|
+
},
|
|
222
|
+
date: {
|
|
223
|
+
test_variant: 'date',
|
|
224
|
+
test_re: /^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/,
|
|
225
|
+
json_type: 'string',
|
|
226
|
+
},
|
|
227
|
+
timestamp: {
|
|
228
|
+
test_variant: 'timestamp',
|
|
229
|
+
test_re: /^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}(:[0-9]{2}(\.[0-9]{1,7})?)?$/,
|
|
230
|
+
json_type: 'string',
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
|
|
120
234
|
/** All types belong to one category. */
|
|
121
235
|
const typeCategories = {
|
|
122
236
|
string: [],
|
|
@@ -286,6 +400,8 @@ function initBuiltins( model ) {
|
|
|
286
400
|
art.$autoElement = magic.$autoElement;
|
|
287
401
|
if (magic.$uncheckedElements)
|
|
288
402
|
art.$uncheckedElements = magic.$uncheckedElements;
|
|
403
|
+
if (magic.$requireElementAccess)
|
|
404
|
+
art.$requireElementAccess = magic.$requireElementAccess;
|
|
289
405
|
|
|
290
406
|
createMagicElements( art, magic.elements );
|
|
291
407
|
if (options.variableReplacements)
|
|
@@ -325,6 +441,7 @@ module.exports = {
|
|
|
325
441
|
typeParameters,
|
|
326
442
|
functionsWithoutParens,
|
|
327
443
|
specialFunctions,
|
|
444
|
+
quotedLiteralPatterns,
|
|
328
445
|
initBuiltins,
|
|
329
446
|
isInReservedNamespace,
|
|
330
447
|
isBuiltinType,
|
package/lib/compiler/define.js
CHANGED
|
@@ -390,14 +390,14 @@ function define( model ) {
|
|
|
390
390
|
setLink( vocab, '_block', block );
|
|
391
391
|
const { name } = vocab;
|
|
392
392
|
if (!name.absolute)
|
|
393
|
-
name.absolute = prefix + name.path
|
|
393
|
+
name.absolute = prefix + pathName( name.path );
|
|
394
394
|
dictAdd( model.vocabularies, name.absolute, vocab );
|
|
395
395
|
}
|
|
396
396
|
|
|
397
397
|
// Phase 2 ("init") --------------------------------------------------------
|
|
398
398
|
|
|
399
399
|
function checkRedefinition( art ) {
|
|
400
|
-
if (!art.$duplicates)
|
|
400
|
+
if (!art.$duplicates || art.$errorReported === 'syntax-duplicate-extend')
|
|
401
401
|
return;
|
|
402
402
|
if (art._main) {
|
|
403
403
|
error( 'duplicate-definition', [ art.name.location, art ], {
|
|
@@ -505,13 +505,13 @@ function define( model ) {
|
|
|
505
505
|
initParentLink( parent, definitions );
|
|
506
506
|
}
|
|
507
507
|
if (art.kind !== 'namespace' &&
|
|
508
|
-
isDeprecatedEnabled( options, '
|
|
508
|
+
isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' )) {
|
|
509
509
|
let p = parent;
|
|
510
510
|
while (p && kindProperties[p.kind].artifacts)
|
|
511
511
|
p = p._parent;
|
|
512
512
|
if (p) {
|
|
513
513
|
error( 'subartifacts-not-supported', [ art.name.location, art ],
|
|
514
|
-
{ art: p, prop: 'deprecated.
|
|
514
|
+
{ art: p, prop: 'deprecated._generatedEntityNameWithUnderscore' },
|
|
515
515
|
// eslint-disable-next-line max-len
|
|
516
516
|
'With the option $(PROP), no sub artifact can be defined for a non-context/service $(ART)' );
|
|
517
517
|
}
|
|
@@ -670,6 +670,8 @@ function define( model ) {
|
|
|
670
670
|
* @param {object} exprOrPathElement starts w/ an expr but then subelem from .path or .where.args
|
|
671
671
|
*/
|
|
672
672
|
function approveExistsInChildren(exprOrPathElement) {
|
|
673
|
+
if (!exprOrPathElement) // may be null in case of parse error
|
|
674
|
+
return;
|
|
673
675
|
if (exprOrPathElement.$expected === 'exists')
|
|
674
676
|
exprOrPathElement.$expected = 'approved-exists';
|
|
675
677
|
// Drill down
|