@sap/cds-compiler 3.0.0 → 3.1.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 +104 -9
- package/bin/.eslintrc.json +2 -1
- package/bin/cdsc.js +28 -16
- package/doc/API.md +11 -0
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +24 -2
- package/doc/CHANGELOG_DEPRECATED.md +21 -1
- package/lib/api/main.js +92 -40
- package/lib/api/options.js +2 -3
- package/lib/base/keywords.js +64 -1
- package/lib/base/message-registry.js +33 -5
- package/lib/base/messages.js +54 -65
- package/lib/base/model.js +2 -0
- package/lib/base/optionProcessorHelper.js +53 -21
- package/lib/checks/actionsFunctions.js +8 -7
- package/lib/checks/selectItems.js +96 -14
- package/lib/checks/types.js +5 -8
- package/lib/checks/validator.js +1 -2
- package/lib/compiler/assert-consistency.js +65 -13
- package/lib/compiler/base.js +6 -4
- package/lib/compiler/builtins.js +93 -4
- package/lib/compiler/checks.js +1 -1
- package/lib/compiler/define.js +28 -23
- package/lib/compiler/extend.js +20 -11
- package/lib/compiler/finalize-parse-cdl.js +5 -9
- package/lib/compiler/index.js +2 -0
- package/lib/compiler/populate.js +37 -32
- package/lib/compiler/propagator.js +11 -6
- package/lib/compiler/resolve.js +15 -19
- package/lib/compiler/shared.js +54 -18
- package/lib/compiler/tweak-assocs.js +5 -11
- package/lib/compiler/utils.js +15 -6
- package/lib/edm/annotations/genericTranslation.js +12 -2
- package/lib/edm/annotations/preprocessAnnotations.js +18 -15
- package/lib/edm/csn2edm.js +18 -17
- package/lib/edm/edm.js +22 -13
- package/lib/edm/edmAnnoPreprocessor.js +349 -0
- package/lib/edm/edmInboundChecks.js +85 -0
- package/lib/edm/edmPreprocessor.js +336 -665
- package/lib/edm/edmUtils.js +86 -45
- package/lib/gen/Dictionary.json +29 -9
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -2
- package/lib/gen/languageLexer.js +3 -0
- package/lib/gen/languageParser.js +4332 -4496
- package/lib/inspect/.eslintrc.json +4 -0
- package/lib/inspect/index.js +14 -0
- package/lib/inspect/inspectModelStatistics.js +81 -0
- package/lib/inspect/inspectPropagation.js +189 -0
- package/lib/inspect/inspectUtils.js +44 -0
- package/lib/json/from-csn.js +19 -20
- package/lib/json/to-csn.js +11 -8
- package/lib/language/genericAntlrParser.js +150 -92
- package/lib/language/language.g4 +47 -74
- package/lib/main.d.ts +1 -0
- package/lib/model/api.js +1 -1
- package/lib/model/csnRefs.js +56 -29
- package/lib/model/csnUtils.js +29 -14
- package/lib/model/revealInternalProperties.js +6 -4
- package/lib/modelCompare/compare.js +3 -0
- package/lib/optionProcessor.js +81 -38
- package/lib/render/toCdl.js +57 -32
- package/lib/render/toHdbcds.js +1 -1
- package/lib/render/toSql.js +31 -11
- package/lib/render/utils/common.js +3 -4
- package/lib/transform/db/associations.js +43 -35
- package/lib/transform/db/cdsPersistence.js +0 -1
- package/lib/transform/db/flattening.js +3 -4
- package/lib/transform/db/transformExists.js +7 -5
- package/lib/transform/draft/db.js +1 -1
- package/lib/transform/forHanaNew.js +11 -2
- package/lib/transform/forOdataNew.js +4 -4
- package/lib/transform/localized.js +15 -11
- package/lib/transform/odata/typesExposure.js +14 -5
- package/lib/utils/file.js +28 -18
- package/lib/utils/moduleResolve.js +0 -1
- package/package.json +3 -4
- package/share/messages/syntax-expected-integer.md +9 -8
- package/lib/checks/unknownMagic.js +0 -41
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { isBuiltinType } = require('../model/csnUtils');
|
|
4
|
+
const { isBetaEnabled } = require('../base/model');
|
|
4
5
|
|
|
5
6
|
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
6
7
|
|
|
@@ -52,13 +53,13 @@ function checkActionOrFunction(art, artName, prop, path) {
|
|
|
52
53
|
function checkActionOrFunctionParameter(param, currPath, actKind) {
|
|
53
54
|
const paramType = param.type ? this.csnUtils.getFinalTypeDef(param.type) : param;
|
|
54
55
|
|
|
55
|
-
if (param.default || paramType.default) {
|
|
56
|
-
this.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
if (!isBetaEnabled(this.options, 'optionalActionFunctionParameters') && (param.default || paramType.default)) {
|
|
57
|
+
this.message('param-default', currPath, { '#': actKind },
|
|
58
|
+
{
|
|
59
|
+
std: 'Artifact parameters can\'t have a default value', // Not used
|
|
60
|
+
action: 'Action parameters can\'t have a default value',
|
|
61
|
+
function: 'Function parameters can\'t have a default value',
|
|
62
|
+
});
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
if (paramType.type && this.csnUtils.isAssocOrComposition(param.type)) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { forEachGeneric } = require('../model/csnUtils');
|
|
3
|
+
const { forEachGeneric, applyTransformationsOnNonDictionary } = require('../model/csnUtils');
|
|
4
4
|
|
|
5
5
|
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
6
6
|
|
|
@@ -17,21 +17,103 @@ function validateSelectItems(query) {
|
|
|
17
17
|
const { SELECT } = query;
|
|
18
18
|
if (!SELECT)
|
|
19
19
|
return;
|
|
20
|
+
/**
|
|
21
|
+
* Check for a $self.<assoc> in columns etc. - since the $self.<assoc> references the "outside" view
|
|
22
|
+
* of the association, this is not allowed.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} queryPart Part of the query that is being checked
|
|
25
|
+
* @returns {Function} Function as callback for applyTransformations
|
|
26
|
+
*/
|
|
27
|
+
function checkRefForInvalid$Self(queryPart) {
|
|
28
|
+
const signalError = (error, parent, type) => {
|
|
29
|
+
if (queryPart === 'columns') {
|
|
30
|
+
error(null, parent.$path,
|
|
31
|
+
{ name: parent.ref[0], type },
|
|
32
|
+
'Select items starting with $(NAME) must not contain path steps of type $(TYPE)');
|
|
33
|
+
}
|
|
34
|
+
else if (queryPart === 'orderBy') {
|
|
35
|
+
error(null, parent.$path,
|
|
36
|
+
{ id: queryPart, type },
|
|
37
|
+
'Items of the $(ID)-clause must not contain path steps of type $(TYPE)');
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
error(null, parent.$path,
|
|
41
|
+
{ id: queryPart, name: parent.ref[0], type },
|
|
42
|
+
'Items of the $(ID)-clause starting with $(NAME) must not contain path steps of type $(TYPE)');
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
return function checkForInvalid$SelfInRef(parent) {
|
|
46
|
+
if (parent.ref && (parent.$scope === '$self' || parent.$scope === '$query')) {
|
|
47
|
+
const { _links } = parent;
|
|
48
|
+
for (let j = parent.$scope === '$self' ? 1 : 0; j < _links.length - 1; j++) {
|
|
49
|
+
if (_links[j].art.target) {
|
|
50
|
+
if (_links[j].art.on) {
|
|
51
|
+
// It's an unmanaged association - traversal is always forbidden
|
|
52
|
+
signalError(this.error, parent, _links[j].art.type);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// It's a managed association - access of the foreign keys is allowed
|
|
56
|
+
const nextRef = parent.ref[j + 1].id || parent.ref[j + 1];
|
|
57
|
+
if (!_links[j].art.keys.some(r => r.ref[0] === nextRef))
|
|
58
|
+
signalError(this.error, parent, _links[j].art.type);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const last = _links[_links.length - 1];
|
|
20
64
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
this.error(null, selectItem.$path,
|
|
26
|
-
{ name: selectItem.ref[0], type: pathStepWithTarget.art.type },
|
|
27
|
-
'Select items starting with $(NAME) must not contain path steps of type $(TYPE)');
|
|
65
|
+
if (last.art.target && last.art.on) {
|
|
66
|
+
// It's an unmanaged association - traversal is always forbidden
|
|
67
|
+
signalError(this.error, parent, last.art.type);
|
|
68
|
+
} // managed is okay, can be handled via tuple expansion
|
|
28
69
|
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Check the given assoc filter for usage of $self - in an assoc-filter, you must only
|
|
75
|
+
* address things on the target side of the association, not from global scope.
|
|
76
|
+
*
|
|
77
|
+
* @param {object} parent
|
|
78
|
+
* @param {string} prop
|
|
79
|
+
* @param {Array} where
|
|
80
|
+
*/
|
|
81
|
+
function checkFilterForInvalid$Self(parent, prop, where) {
|
|
82
|
+
where.forEach((whereStep) => {
|
|
83
|
+
if (whereStep.ref && ( whereStep.ref[0] === '$projection' || whereStep.ref[0] === '$self')) {
|
|
84
|
+
this.error('expr-where-unexpected-self', whereStep.$path,
|
|
85
|
+
{ name: whereStep.ref[0] },
|
|
86
|
+
'Path steps inside of filters must not start with $(NAME)');
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const aTCB = (parent, prop) => {
|
|
92
|
+
applyTransformationsOnNonDictionary(parent, prop, {
|
|
93
|
+
ref: checkRefForInvalid$Self(prop).bind(this),
|
|
94
|
+
where: checkFilterForInvalid$Self.bind(this),
|
|
95
|
+
}, { skipStandard: { on: true }, drillRef: true });
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const transformers = {
|
|
99
|
+
columns: aTCB,
|
|
100
|
+
groupBy: aTCB,
|
|
101
|
+
orderBy: aTCB,
|
|
102
|
+
having: aTCB,
|
|
103
|
+
where: aTCB,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
if (this.options.transformation === 'hdbcds') {
|
|
107
|
+
transformers.xpr = (parent) => {
|
|
108
|
+
if (parent.func) {
|
|
109
|
+
this.error(null, parent.$path,
|
|
110
|
+
'Window functions are not supported by SAP HANA CDS');
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
applyTransformationsOnNonDictionary(query, 'SELECT', transformers );
|
|
116
|
+
|
|
35
117
|
// .call() with 'this' to ensure we have access to the options
|
|
36
118
|
rejectManagedAssociationsAndStructuresForHdbcdsNames.call(this, SELECT, SELECT.$path);
|
|
37
119
|
}
|
package/lib/checks/types.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { getUtils,
|
|
3
|
+
const { getUtils, hasAnnotationValue } = require('../model/csnUtils');
|
|
4
4
|
|
|
5
5
|
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
6
6
|
|
|
@@ -55,7 +55,7 @@ function checkElementTypeDefinitionHasType(member, memberName, prop, path) {
|
|
|
55
55
|
|
|
56
56
|
// should only happen with csn input, not in cdl
|
|
57
57
|
if (!hasArtifactTypeInformation(member)) {
|
|
58
|
-
|
|
58
|
+
errorAboutMissingType(this.error, path, memberName, true);
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -96,7 +96,7 @@ function checkTypeDefinitionHasType(artifact, artifactName, prop, path) {
|
|
|
96
96
|
|
|
97
97
|
// should only happen with csn input, not in cdl
|
|
98
98
|
if (!hasArtifactTypeInformation(artifact)) {
|
|
99
|
-
|
|
99
|
+
errorAboutMissingType(this.error, path, artifactName);
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
102
|
|
|
@@ -157,9 +157,8 @@ 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
|
|
161
160
|
*/
|
|
162
|
-
function
|
|
161
|
+
function errorAboutMissingType(error, path, name, isElement = false) {
|
|
163
162
|
error('check-proper-type', path, { art: name, '#': isElement ? 'elm' : 'std' }, {
|
|
164
163
|
std: 'Dubious type $(ART) without type information',
|
|
165
164
|
elm: 'Dubious element $(ART) without type information',
|
|
@@ -174,12 +173,10 @@ function warnAboutMissingType(error, path, name, isElement = false) {
|
|
|
174
173
|
*
|
|
175
174
|
* @param {CSN.Artifact} artifact the artifact to check
|
|
176
175
|
* @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?
|
|
178
176
|
*/
|
|
179
177
|
function hasArtifactTypeInformation(artifact) {
|
|
180
178
|
// When is what property set?
|
|
181
|
-
return
|
|
182
|
-
artifact.elements || // => `type A {}`
|
|
179
|
+
return artifact.elements || // => `type A {}`
|
|
183
180
|
artifact.items || // => `type A : array of Integer`
|
|
184
181
|
artifact.enum || // => `type A : Integer enum {}`, `type` also set
|
|
185
182
|
artifact.target || // => `type A : Association to B;`
|
package/lib/checks/validator.js
CHANGED
|
@@ -32,7 +32,6 @@ const { validateAssociationsInItems } = require('./arrayOfs');
|
|
|
32
32
|
const checkQueryForNoDBArtifacts = require('./queryNoDbArtifacts');
|
|
33
33
|
const checkExplicitlyNullableKeys = require('./nullableKeys');
|
|
34
34
|
const nonexpandableStructuredInExpression = require('./nonexpandableStructured');
|
|
35
|
-
const unknownMagic = require('./unknownMagic');
|
|
36
35
|
const managedWithoutKeys = require('./managedWithoutKeys');
|
|
37
36
|
const {
|
|
38
37
|
checkSqlAnnotationOnArtifact,
|
|
@@ -62,7 +61,7 @@ const forHanaArtifactValidators
|
|
|
62
61
|
checkSqlAnnotationOnArtifact,
|
|
63
62
|
];
|
|
64
63
|
|
|
65
|
-
const forHanaCsnValidators = [ nonexpandableStructuredInExpression
|
|
64
|
+
const forHanaCsnValidators = [ nonexpandableStructuredInExpression ];
|
|
66
65
|
/**
|
|
67
66
|
* @type {Array<(query: CSN.Query, path: CSN.Path) => void>}
|
|
68
67
|
*/
|
|
@@ -97,7 +97,6 @@ function assertConsistency( model, stage ) {
|
|
|
97
97
|
'$lateExtensions',
|
|
98
98
|
'_entities', '$entity',
|
|
99
99
|
'$blocks',
|
|
100
|
-
'$newfeatures',
|
|
101
100
|
'$messageFunctions',
|
|
102
101
|
'$functions',
|
|
103
102
|
'$volatileFunctions',
|
|
@@ -143,7 +142,6 @@ function assertConsistency( model, stage ) {
|
|
|
143
142
|
},
|
|
144
143
|
fileDep: { test: TODO }, // in usings
|
|
145
144
|
$frontend: { parser: true, test: isString, enum: [ 'cdl', 'json', 'xml' ] },
|
|
146
|
-
$newfeatures: { test: TODO }, // if new features have been used which break the old backends
|
|
147
145
|
messages: {
|
|
148
146
|
enumerable: () => true, // does not matter (non-enum std), enum in CSN/XML parser
|
|
149
147
|
test: isArray( TODO ),
|
|
@@ -175,7 +173,6 @@ function assertConsistency( model, stage ) {
|
|
|
175
173
|
return innerDict( val, parent, lang, spec );
|
|
176
174
|
} ),
|
|
177
175
|
},
|
|
178
|
-
_assocSources: { kind: true, test: TODO }, // just null: isArray( inDefinitions ) during resolve
|
|
179
176
|
$magicVariables: {
|
|
180
177
|
// $magicVariables contains "builtin" artifacts that differ from
|
|
181
178
|
// "normal artifacts" and therefore have a custom schema
|
|
@@ -256,6 +253,7 @@ function assertConsistency( model, stage ) {
|
|
|
256
253
|
'where', 'groupBy', 'having', 'orderBy', '$orderBy', 'limit',
|
|
257
254
|
'_projections', '_block', '_parent', '_main', '_effectiveType', '$expand',
|
|
258
255
|
'$tableAliases', 'kind', '_$next', '_combined', '$inlines', '_status',
|
|
256
|
+
'_extension', // for unapplied extensions
|
|
259
257
|
],
|
|
260
258
|
},
|
|
261
259
|
none: { optional: () => true }, // parse error
|
|
@@ -375,13 +373,21 @@ function assertConsistency( model, stage ) {
|
|
|
375
373
|
},
|
|
376
374
|
// locations of parentheses pairs around expression:
|
|
377
375
|
$parens: { parser: true, test: TODO },
|
|
376
|
+
$prefix: { test: isString }, // compiler-corrected path prefix
|
|
378
377
|
$syntax: {
|
|
379
378
|
parser: true,
|
|
380
379
|
kind: [ 'entity', 'view', 'type', 'aspect' ],
|
|
381
380
|
test: isString, // CSN parser should check for 'entity', 'view', 'projection'
|
|
382
381
|
},
|
|
383
382
|
value: {
|
|
384
|
-
optional: [
|
|
383
|
+
optional: [
|
|
384
|
+
'location', '$inferred', 'sort', 'nulls',
|
|
385
|
+
'param', 'scope', // for dynamic parameter '?'
|
|
386
|
+
// through cast() with enum through CSN->XSN
|
|
387
|
+
// TODO: re-check #9225, this should be directly in the query element,
|
|
388
|
+
// not inside value, no `enum` inside `cast`!
|
|
389
|
+
'elements', 'items', 'enum', '$expand', 'target',
|
|
390
|
+
],
|
|
385
391
|
|
|
386
392
|
kind: true,
|
|
387
393
|
test: expression, // properties below are "sub specifications"
|
|
@@ -439,7 +445,10 @@ function assertConsistency( model, stage ) {
|
|
|
439
445
|
struct: { inherits: 'val', test: isDictionary( definition ) }, // def because double @
|
|
440
446
|
args: {
|
|
441
447
|
inherits: 'value',
|
|
442
|
-
optional: [
|
|
448
|
+
optional: [
|
|
449
|
+
'name', '$duplicate', '$expected', 'args', 'suffix',
|
|
450
|
+
'param', 'scope', // for dynamic parameter '?'
|
|
451
|
+
],
|
|
443
452
|
test: args,
|
|
444
453
|
},
|
|
445
454
|
on: { kind: true, inherits: 'value', test: expression },
|
|
@@ -456,7 +465,7 @@ function assertConsistency( model, stage ) {
|
|
|
456
465
|
optional: [ 'name', '_block', '$priority', '$inferred', '$duplicates', '$errorReported' ],
|
|
457
466
|
// TODO: name requires if not in parser?
|
|
458
467
|
},
|
|
459
|
-
$priority: { test:
|
|
468
|
+
$priority: { test: isOneOf([ undefined, false, 'extend', 'annotate' ]) },
|
|
460
469
|
$annotations: { parser: true, kind: true, test: TODO }, // deprecated, still there for cds-lsp
|
|
461
470
|
name: {
|
|
462
471
|
isRequired: stageParser && (() => false), // not required in parser
|
|
@@ -475,7 +484,7 @@ function assertConsistency( model, stage ) {
|
|
|
475
484
|
variant: { test: TODO }, // TODO: not set in CDL parser
|
|
476
485
|
element: { test: TODO }, // TODO: { test: isString },
|
|
477
486
|
action: { test: isString },
|
|
478
|
-
param: { test:
|
|
487
|
+
param: { test: TODO },
|
|
479
488
|
alias: { test: isString },
|
|
480
489
|
expectedKind: { kind: [ 'extend' ], inherits: 'kind' },
|
|
481
490
|
virtual: { kind: true, test: locationVal() },
|
|
@@ -528,7 +537,6 @@ function assertConsistency( model, stage ) {
|
|
|
528
537
|
_service: { kind: true, test: TODO },
|
|
529
538
|
_main: { kind: true, test: TODO },
|
|
530
539
|
_artifact: { test: TODO },
|
|
531
|
-
_base: { test: TODO, kind: true },
|
|
532
540
|
_navigation: { test: TODO },
|
|
533
541
|
_effectiveType: { kind: true, test: TODO },
|
|
534
542
|
_joinParent: { test: TODO },
|
|
@@ -552,6 +560,7 @@ function assertConsistency( model, stage ) {
|
|
|
552
560
|
'where', 'columns', 'mixin', 'quantifier', 'offset',
|
|
553
561
|
'orderBy', '$orderBy', 'groupBy', 'excludingDict', 'having',
|
|
554
562
|
'limit', '_status',
|
|
563
|
+
'_extension', // for unapplied extensions
|
|
555
564
|
],
|
|
556
565
|
},
|
|
557
566
|
_leadingQuery: { kind: true, test: TODO },
|
|
@@ -585,21 +594,57 @@ function assertConsistency( model, stage ) {
|
|
|
585
594
|
// (it can contain the artifact itself with no/failed autoexposure):
|
|
586
595
|
_descendants: { kind: [ 'entity' ], test: isDictionary( isArray( TODO ) ) },
|
|
587
596
|
|
|
588
|
-
$errorReported: { parser: true, test:
|
|
597
|
+
$errorReported: { parser: true, kind: true, test: isString }, // to avoid duplicate messages
|
|
589
598
|
$duplicates: { parser: true, kind: true, test: TODO }, // array of arts or true
|
|
590
599
|
$extension: { kind: true, test: TODO }, // TODO: introduce $applied instead or $status
|
|
591
|
-
$inferred: {
|
|
600
|
+
$inferred: {
|
|
601
|
+
parser: true,
|
|
602
|
+
kind: true,
|
|
603
|
+
test: isOneOf([
|
|
604
|
+
// Uppercase values are used in logic, lowercase value are "just for us", i.e.
|
|
605
|
+
// debugging or to add non-enumerable properties such as $generated in Universal CSN.
|
|
606
|
+
// However, that is no longer true. For example, `autoexposed` is used in populate.js
|
|
607
|
+
// as well.
|
|
608
|
+
'IMPLICIT',
|
|
609
|
+
'REDIRECTED',
|
|
610
|
+
|
|
611
|
+
'$autoElement', // for magicVars: $user is automatically changed to $user.id
|
|
612
|
+
'$generated', // compiler generated annotations, e.g. @Core.Computed
|
|
613
|
+
'*', // inferred from query wildcard
|
|
614
|
+
'as', // query alias name
|
|
615
|
+
'aspect-composition',
|
|
616
|
+
'autoexposed', // for auto-exposed entities (they can't be referred to)
|
|
617
|
+
'cast', // type from cast() function
|
|
618
|
+
'composition-entity',
|
|
619
|
+
'copy', // only used in rewriteCondition(): On-condition is copied
|
|
620
|
+
'duplicate-autoexposed', // just like `autoexposed`, but with `duplicate` error.
|
|
621
|
+
'expand-element', // expanded elements
|
|
622
|
+
'expand-param', // expanded params (difference to expand-element only for debugging)
|
|
623
|
+
'include', // through includes, e.g. `entity E : F {}`
|
|
624
|
+
'keys',
|
|
625
|
+
'localized', // e.g. compiler-generated elements for localized: `text` assoc, etc.
|
|
626
|
+
'localized-entity', // `.texts` entity
|
|
627
|
+
'nav', // only used for MASKED, TODO(v4): Remove
|
|
628
|
+
'none', // only used in ensureColumnName(): Used in object representing empty alias
|
|
629
|
+
'query', // inferred query properties, e.g. `key`
|
|
630
|
+
'rewrite', // on-conditions or FKeys are rewritten
|
|
631
|
+
]),
|
|
632
|
+
},
|
|
592
633
|
|
|
593
634
|
// Helper property for the XSN-to-CSN transformation, see function setExpandStatus():
|
|
594
635
|
// client, universal: render expanded elements? gensrc: produce annotate statements?
|
|
595
|
-
|
|
636
|
+
// TODO: rename it to $elementsExpand ?
|
|
637
|
+
$expand: {
|
|
638
|
+
kind: true,
|
|
639
|
+
// See description of `setExpandStatus()` of in `lib/compiler/utils.js`.
|
|
640
|
+
test: isOneOf([ 'origin', 'annotate', 'target' ]),
|
|
641
|
+
},
|
|
596
642
|
|
|
597
643
|
$autoexpose: { kind: [ 'entity' ], test: isBoolean, also: [ null, 'Composition' ] },
|
|
598
|
-
$a2j: { kind: true, enumerable: true, test: TODO },
|
|
599
644
|
$extra: { parser: true, test: TODO }, // for unexpected properties in CSN
|
|
600
645
|
$withLocalized: { test: isBoolean },
|
|
601
646
|
$sources: { parser: true, test: isArray( isString ) },
|
|
602
|
-
$expected: { parser: true, test:
|
|
647
|
+
$expected: { parser: true, test: isOneOf([ 'approved-exists', 'exists' ]) },
|
|
603
648
|
$messageFunctions: { test: TODO },
|
|
604
649
|
$functions: { test: TODO },
|
|
605
650
|
$volatileFunctions: { test: TODO },
|
|
@@ -859,6 +904,13 @@ function assertConsistency( model, stage ) {
|
|
|
859
904
|
isString(node, parent, prop, spec);
|
|
860
905
|
}
|
|
861
906
|
|
|
907
|
+
function isOneOf(values) {
|
|
908
|
+
return function isOneOfInner( node, parent, prop ) {
|
|
909
|
+
if (!values.includes(node))
|
|
910
|
+
throw new Error( `Unexpected value '${ node }', expected ${ JSON.stringify(values) }${ at( [ node, parent ], prop ) }` );
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
|
|
862
914
|
function isString( node, parent, prop, spec ) {
|
|
863
915
|
if (typeof node !== 'string')
|
|
864
916
|
throw new Error( `Expected string${ at( [ node, parent ], prop ) }` );
|
package/lib/compiler/base.js
CHANGED
|
@@ -16,16 +16,18 @@ const kindProperties = {
|
|
|
16
16
|
namespace: { artifacts: true }, // on-the-fly context
|
|
17
17
|
context: { artifacts: true, normalized: 'namespace' },
|
|
18
18
|
service: { artifacts: true, normalized: 'namespace' },
|
|
19
|
-
entity: {
|
|
19
|
+
entity: {
|
|
20
|
+
elements: true, actions: true, params: () => false, include: true,
|
|
21
|
+
},
|
|
20
22
|
select: { normalized: 'select', elements: true },
|
|
21
23
|
$join: { normalized: 'select' },
|
|
22
24
|
$tableAlias: { normalized: 'alias' }, // table alias in select
|
|
23
25
|
$self: { normalized: 'alias' }, // table alias in select
|
|
24
26
|
$navElement: { normalized: 'element' },
|
|
25
27
|
$inline: { normalized: 'element' }, // column with inline property
|
|
26
|
-
event: { elements: true },
|
|
27
|
-
type: { elements: propExists, enum: propExists },
|
|
28
|
-
aspect: { elements: propExists },
|
|
28
|
+
event: { elements: true, include: true },
|
|
29
|
+
type: { elements: propExists, enum: propExists, include: true },
|
|
30
|
+
aspect: { elements: propExists, actions: true, include: true },
|
|
29
31
|
annotation: { elements: propExists, enum: propExists },
|
|
30
32
|
enum: { normalized: 'element' },
|
|
31
33
|
element: { elements: propExists, enum: propExists, dict: 'elements' },
|
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
|
|
|
@@ -154,12 +154,14 @@ function compileArg( src ) {
|
|
|
154
154
|
expr: src.expr || [],
|
|
155
155
|
separator: src.separator || [],
|
|
156
156
|
};
|
|
157
|
-
for (const generic of [ 'intro', 'expr', 'separator' ]) {
|
|
157
|
+
for (const generic of [ 'intro', 'expr', 'separator' ]) {
|
|
158
|
+
// intro before expr: if both intro and expr, tag as 'expr'
|
|
158
159
|
for (const token of src[generic] || [])
|
|
159
160
|
tgt[token] = generic;
|
|
160
161
|
}
|
|
161
|
-
|
|
162
|
-
|
|
162
|
+
// As GenericIntro is always together with GenericExpr, only mention those
|
|
163
|
+
// which are not already proposed for GenericExpr:
|
|
164
|
+
tgt.introMsg = src.introMsg || tgt.intro.filter( token => tgt[token] === 'intro' );
|
|
163
165
|
return tgt;
|
|
164
166
|
}
|
|
165
167
|
|
|
@@ -193,6 +195,92 @@ const magicVariables = {
|
|
|
193
195
|
|
|
194
196
|
// see lib/render/renderUtil.js for DB-specific magic vars, specified in CAP Cds via function
|
|
195
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Patterns for literal token tests and creation. The value is a map from the
|
|
200
|
+
* `prefix` argument of function `quotedliteral` to the following properties:
|
|
201
|
+
* - `test_msg`: error message which is issued if `test_fn` fails.
|
|
202
|
+
* - `test_fn`: function called with argument `value`, fails falsy return value
|
|
203
|
+
* - `unexpected_msg`: error message which is issued if `unexpected_char` matches
|
|
204
|
+
* - `unexpected_char`: regular expression matching an illegal character in `value`,
|
|
205
|
+
* the error location is only correct for a literal <prefix>'<value>'
|
|
206
|
+
* - `literal`: the value which is used instead of `prefix` in the AST
|
|
207
|
+
* TODO: we might do a range check (consider leap seconds, i.e. max value 60),
|
|
208
|
+
* but always allow Feb 29 (no leap year computation)
|
|
209
|
+
* Notes:
|
|
210
|
+
* - Dates/Times as defined in ISO 8601, see <https://en.wikipedia.org/wiki/ISO_8601>
|
|
211
|
+
*/
|
|
212
|
+
const quotedLiteralPatterns = {
|
|
213
|
+
x: {
|
|
214
|
+
test_variant: 'uneven-hex',
|
|
215
|
+
test_fn: (str => Number.isInteger(str.length / 2)),
|
|
216
|
+
unexpected_variant: 'invalid-hex',
|
|
217
|
+
unexpected_char: /[^0-9a-f]/i,
|
|
218
|
+
json_type: 'string',
|
|
219
|
+
},
|
|
220
|
+
time: {
|
|
221
|
+
test_variant: 'time',
|
|
222
|
+
test_fn: (x) => {
|
|
223
|
+
// Leading `T` allowed in ISO 8601.
|
|
224
|
+
const match = x.match( /^T?(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/ );
|
|
225
|
+
return match !== null && checkTime( match[1], match[2], match[3] );
|
|
226
|
+
},
|
|
227
|
+
json_type: 'string',
|
|
228
|
+
},
|
|
229
|
+
date: {
|
|
230
|
+
test_variant: 'date',
|
|
231
|
+
test_fn: (x) => {
|
|
232
|
+
const match = x.match( /^(-?\d{4})-(\d{1,2})-(\d{1,2})$/ );
|
|
233
|
+
return match !== null && checkDate( match[1], match[2], match[3] );
|
|
234
|
+
},
|
|
235
|
+
json_type: 'string',
|
|
236
|
+
},
|
|
237
|
+
timestamp: {
|
|
238
|
+
test_variant: 'timestamp',
|
|
239
|
+
test_fn: (x) => {
|
|
240
|
+
// eslint-disable-next-line max-len
|
|
241
|
+
const match = x.match( /^(-?\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(\.\d{1,7})?)?$/ );
|
|
242
|
+
return match !== null && checkDate( match[1], match[2], match[3] ) &&
|
|
243
|
+
checkTime( match[4], match[5], match[6] );
|
|
244
|
+
},
|
|
245
|
+
json_type: 'string',
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Check that the given date is within boundaries.
|
|
251
|
+
* We can't use Date.parse() since that also allows non-standard values (2022-02-31 for example).
|
|
252
|
+
* Checks according to ISO 8601.
|
|
253
|
+
*
|
|
254
|
+
* @returns {boolean} True if the date is valid.
|
|
255
|
+
*/
|
|
256
|
+
function checkDate(year, month, day) {
|
|
257
|
+
// Negative years are allowed
|
|
258
|
+
year = Math.abs(Number.parseInt(year, 10));
|
|
259
|
+
month = Number.parseInt(month, 10);
|
|
260
|
+
day = Number.parseInt(day, 10);
|
|
261
|
+
// If any is NaN, the condition will be false.
|
|
262
|
+
// Year 0 does not exist, but ISO 8601 allows it and defines it as 1 BC.
|
|
263
|
+
return !Number.isNaN(year) && month > 0 && month < 13 && day > 0 && day < 32;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Check that the given time is within boundaries.
|
|
268
|
+
* Checks according to ISO 8601.
|
|
269
|
+
*
|
|
270
|
+
* @returns {boolean} True if the date is valid.
|
|
271
|
+
*/
|
|
272
|
+
function checkTime(hour, minutes, seconds) {
|
|
273
|
+
hour = Number.parseInt(hour, 10);
|
|
274
|
+
minutes = Number.parseInt(minutes, 10);
|
|
275
|
+
seconds = seconds ? Number.parseInt(seconds, 10) : 0;
|
|
276
|
+
if (hour === 24) // allow 24:00:00 (ISO 8601 version earlier than 2019)
|
|
277
|
+
return minutes === 0 && seconds === 0;
|
|
278
|
+
// If any is NaN, the condition will be false.
|
|
279
|
+
return hour >= 0 && hour < 24 &&
|
|
280
|
+
minutes >= 0 && minutes < 60 &&
|
|
281
|
+
seconds >= 0 && seconds < 61; // we allow 60 for lead seconds
|
|
282
|
+
}
|
|
283
|
+
|
|
196
284
|
/** All types belong to one category. */
|
|
197
285
|
const typeCategories = {
|
|
198
286
|
string: [],
|
|
@@ -403,6 +491,7 @@ module.exports = {
|
|
|
403
491
|
typeParameters,
|
|
404
492
|
functionsWithoutParens,
|
|
405
493
|
specialFunctions,
|
|
494
|
+
quotedLiteralPatterns,
|
|
406
495
|
initBuiltins,
|
|
407
496
|
isInReservedNamespace,
|
|
408
497
|
isBuiltinType,
|
package/lib/compiler/checks.js
CHANGED
|
@@ -414,7 +414,7 @@ function check( model ) { // = XSN
|
|
|
414
414
|
}
|
|
415
415
|
}
|
|
416
416
|
|
|
417
|
-
// TODO: make this part of the
|
|
417
|
+
// TODO: make this part of the name resolution in the compiler
|
|
418
418
|
// Check that queries in 'art' do not contain unmanaged associations in GROUP BY or ORDER BY
|
|
419
419
|
function checkNoUnmanagedAssocsInGroupByOrderBy( query ) {
|
|
420
420
|
const art = query._main; // TODO - remove, use query for semantic location
|