@sap/cds-compiler 3.7.2 → 3.8.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 +63 -4
- package/bin/cdsc.js +3 -0
- package/doc/CHANGELOG_ARCHIVE.md +6 -6
- package/doc/CHANGELOG_BETA.md +15 -0
- package/doc/DeprecatedOptions_v2.md +1 -1
- package/doc/NameResolution.md +1 -1
- package/lib/api/main.js +61 -22
- package/lib/api/options.js +1 -0
- package/lib/api/validate.js +5 -0
- package/lib/base/dictionaries.js +5 -3
- package/lib/base/keywords.js +2 -0
- package/lib/base/message-registry.js +64 -22
- package/lib/base/messages.js +12 -7
- package/lib/base/model.js +3 -2
- package/lib/checks/arrayOfs.js +1 -1
- package/lib/checks/defaultValues.js +1 -1
- package/lib/checks/hasPersistedElements.js +1 -1
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/onConditions.js +9 -6
- package/lib/checks/sql-snippets.js +2 -2
- package/lib/checks/types.js +1 -2
- package/lib/compiler/assert-consistency.js +24 -5
- package/lib/compiler/base.js +49 -2
- package/lib/compiler/builtins.js +15 -6
- package/lib/compiler/checks.js +4 -4
- package/lib/compiler/define.js +59 -80
- package/lib/compiler/extend.js +701 -498
- package/lib/compiler/finalize-parse-cdl.js +4 -3
- package/lib/compiler/index.js +1 -1
- package/lib/compiler/kick-start.js +2 -2
- package/lib/compiler/populate.js +17 -9
- package/lib/compiler/propagator.js +12 -5
- package/lib/compiler/resolve.js +26 -173
- package/lib/compiler/shared.js +12 -53
- package/lib/compiler/tweak-assocs.js +1 -1
- package/lib/compiler/utils.js +2 -2
- package/lib/edm/annotations/genericTranslation.js +124 -46
- package/lib/edm/csn2edm.js +22 -1
- package/lib/edm/edmPreprocessor.js +41 -21
- package/lib/gen/Dictionary.json +4 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/languageLexer.js +1 -1
- package/lib/gen/languageParser.js +4810 -4482
- package/lib/inspect/inspectPropagation.js +20 -36
- package/lib/json/from-csn.js +55 -5
- package/lib/json/to-csn.js +71 -110
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +47 -8
- package/lib/language/language.g4 +88 -62
- package/lib/language/textUtils.js +13 -0
- package/lib/main.d.ts +43 -3
- package/lib/main.js +4 -2
- package/lib/model/csnRefs.js +14 -2
- package/lib/model/csnUtils.js +11 -74
- package/lib/model/revealInternalProperties.js +3 -0
- package/lib/optionProcessor.js +3 -0
- package/lib/render/toCdl.js +203 -104
- package/lib/render/toHdbcds.js +0 -1
- package/lib/render/toRename.js +14 -51
- package/lib/transform/braceExpression.js +6 -0
- package/lib/transform/db/rewriteCalculatedElements.js +55 -14
- package/lib/transform/forOdataNew.js +20 -15
- package/lib/transform/forRelationalDB.js +21 -14
- package/lib/transform/parseExpr.js +2 -0
- package/lib/transform/transformUtilsNew.js +36 -9
- package/lib/transform/translateAssocsToJoins.js +11 -4
- package/lib/transform/universalCsn/coreComputed.js +15 -7
- package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
- package/package.json +2 -1
package/lib/base/messages.js
CHANGED
|
@@ -11,6 +11,7 @@ const { centralMessages, centralMessageTexts, oldMessageIds } = require('./messa
|
|
|
11
11
|
const _messageIdsWithExplanation = require('../../share/messages/message-explanations.json').messages;
|
|
12
12
|
const { analyseCsnPath, traverseQuery } = require('../model/csnRefs');
|
|
13
13
|
const { CompilerAssertion } = require('./error');
|
|
14
|
+
const { getArtifactName } = require('../compiler/base');
|
|
14
15
|
|
|
15
16
|
const fs = require('fs');
|
|
16
17
|
const path = require('path');
|
|
@@ -841,6 +842,7 @@ const nameProp = {
|
|
|
841
842
|
function: 'action',
|
|
842
843
|
};
|
|
843
844
|
|
|
845
|
+
// TODO: very likely delete this function
|
|
844
846
|
function searchName( art, id, variant ) {
|
|
845
847
|
if (!variant) {
|
|
846
848
|
// used to mention the "effective" type in the message, not the
|
|
@@ -1186,7 +1188,7 @@ function deduplicateMessages( messages ) {
|
|
|
1186
1188
|
}
|
|
1187
1189
|
|
|
1188
1190
|
function shortArtName( art ) {
|
|
1189
|
-
const
|
|
1191
|
+
const name = getArtifactName( art );
|
|
1190
1192
|
if ([ 'select', 'action', 'alias', 'param' ].every( n => name[n] == null || name[n] === 1 ) &&
|
|
1191
1193
|
!name.absolute.includes(':'))
|
|
1192
1194
|
return quote.double( name.element ? `${ name.absolute }:${ name.element }` : name.absolute );
|
|
@@ -1194,13 +1196,13 @@ function shortArtName( art ) {
|
|
|
1194
1196
|
}
|
|
1195
1197
|
|
|
1196
1198
|
function artName( art, omit ) {
|
|
1197
|
-
const
|
|
1199
|
+
const name = getArtifactName( art );
|
|
1198
1200
|
const r = (name.absolute) ? [ quoted( name.absolute ) ] : [];
|
|
1199
1201
|
if (name.select && name.select > 1 || name.select != null && art.kind !== 'element') // Yes, omit select:1 for element - TODO: re-check
|
|
1200
1202
|
r.push( (art.kind === 'extend' ? 'block:' : 'query:') + name.select ); // TODO: rename to 'select:1' and consider whether there are more selects
|
|
1201
1203
|
if (name.action && omit !== 'action')
|
|
1202
1204
|
r.push( `${ memberActionName(art) }:${ quoted( name.action ) }` );
|
|
1203
|
-
if (name.alias && art.kind !== '$self')
|
|
1205
|
+
if (name.alias && art.kind !== '$self' && name.$inferred !== '$internal')
|
|
1204
1206
|
r.push( (art.kind === 'mixin' ? 'mixin:' : 'alias:') + quoted( name.alias ) );
|
|
1205
1207
|
if (name.param != null && omit !== 'param')
|
|
1206
1208
|
r.push( name.param ? `param:${ quoted( name.param ) }` : 'returns' ); // TODO: join
|
|
@@ -1231,7 +1233,7 @@ function memberActionName( art ) {
|
|
|
1231
1233
|
function homeName( art, absoluteOnly ) {
|
|
1232
1234
|
if (!art)
|
|
1233
1235
|
return art;
|
|
1234
|
-
if (art._outer) // in
|
|
1236
|
+
if (art._outer) // in items property
|
|
1235
1237
|
return homeName( art._outer, absoluteOnly );
|
|
1236
1238
|
else if (art.kind === 'source' || !art.name) // error reported in parser or on source level
|
|
1237
1239
|
return null;
|
|
@@ -1251,7 +1253,10 @@ function homeName( art, absoluteOnly ) {
|
|
|
1251
1253
|
|
|
1252
1254
|
// The "home" for extensions is handled differently because `_artifact` is not
|
|
1253
1255
|
// set for unknown extensions, and we could have nested extensions.
|
|
1256
|
+
// TODO: delete this function, just set correct name/_parent for extensions
|
|
1254
1257
|
function homeNameForExtend( art ) {
|
|
1258
|
+
if (!art.name.absolute && art._main) // new-style member name
|
|
1259
|
+
return `${ art._main.kind }:${ artName( art ) }`;
|
|
1255
1260
|
const kind = art.kind || 'extend';
|
|
1256
1261
|
// TODO: fix the following - do like in collectArtifactExtensions() or
|
|
1257
1262
|
// basically resolveUncheckedPath()
|
|
@@ -1590,11 +1595,11 @@ function queryDepthForMessage( csnPath, model, view ) {
|
|
|
1590
1595
|
if (!targetQuery)
|
|
1591
1596
|
return 0;
|
|
1592
1597
|
const rootQuery = view.query || { SELECT: view.projection };
|
|
1593
|
-
let depth =
|
|
1598
|
+
let depth = 0;
|
|
1594
1599
|
let totalDepth = 0;
|
|
1595
1600
|
let isFound = false;
|
|
1596
|
-
traverseQuery(rootQuery, null, null, (q
|
|
1597
|
-
if (
|
|
1601
|
+
traverseQuery(rootQuery, null, null, (q) => {
|
|
1602
|
+
if (q.SELECT) {
|
|
1598
1603
|
totalDepth += 1;
|
|
1599
1604
|
if (!isFound)
|
|
1600
1605
|
depth += 1;
|
package/lib/base/model.js
CHANGED
|
@@ -21,7 +21,6 @@ const queryOps = {
|
|
|
21
21
|
*/
|
|
22
22
|
const availableBetaFlags = {
|
|
23
23
|
// enabled by --beta-mode
|
|
24
|
-
calculatedElements: true,
|
|
25
24
|
annotationExpressions: true,
|
|
26
25
|
toRename: true,
|
|
27
26
|
assocsWithParams: true,
|
|
@@ -36,6 +35,7 @@ const availableBetaFlags = {
|
|
|
36
35
|
optionalActionFunctionParameters: true,
|
|
37
36
|
// disabled by --beta-mode
|
|
38
37
|
nestedServices: false,
|
|
38
|
+
v4preview: false,
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
const availableDeprecatedFlags = {
|
|
@@ -111,7 +111,7 @@ function isDeprecatedEnabled( options, feature = null ) {
|
|
|
111
111
|
* to change their code, emit an error if one of such removed flags was used.
|
|
112
112
|
*
|
|
113
113
|
* @param {CSN.Options} options
|
|
114
|
-
* @param error Error message function returned by
|
|
114
|
+
* @param error Error message function returned by makeMessageFunction().
|
|
115
115
|
*/
|
|
116
116
|
function checkRemovedDeprecatedFlags( options, { error } ) {
|
|
117
117
|
// Assume that we emitted these errors once if a message with this ID was found.
|
|
@@ -128,6 +128,7 @@ function checkRemovedDeprecatedFlags( options, { error } ) {
|
|
|
128
128
|
|
|
129
129
|
// Apply function `callback` to all artifacts in dictionary
|
|
130
130
|
// `model.definitions`. See function `forEachGeneric` for details.
|
|
131
|
+
// TODO: should we skip "namespaces" already here?
|
|
131
132
|
function forEachDefinition( model, callback ) {
|
|
132
133
|
forEachGeneric( model, 'definitions', callback );
|
|
133
134
|
}
|
package/lib/checks/arrayOfs.js
CHANGED
|
@@ -32,7 +32,7 @@ function validateAssociationsInItems( member ) {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
};
|
|
35
|
-
if (this.artifact &&
|
|
35
|
+
if (this.artifact && this.artifact.kind === 'entity' && member && member.items && member.$path[2] === 'elements') {
|
|
36
36
|
if (member.items.type) {
|
|
37
37
|
const type = member.items.type.ref
|
|
38
38
|
? this.artifactRef(member.items.type)
|
|
@@ -53,7 +53,7 @@ function rejectParamDefaultsInHanaCds( member, memberName, prop, path ) {
|
|
|
53
53
|
*/
|
|
54
54
|
function warnAboutDefaultOnAssociationForHanaCds( member, memberName, prop, path ) {
|
|
55
55
|
const art = this.csn.definitions[path[1]];
|
|
56
|
-
if (!art.query && this.options.transformation === 'hdbcds' && member.target && member.default) {
|
|
56
|
+
if (!art.query && !art.projection && this.options.transformation === 'hdbcds' && member.target && member.default) {
|
|
57
57
|
this.warning(null, path, { '#': member._type.type === 'cds.Association' ? 'std' : 'comp' },
|
|
58
58
|
{
|
|
59
59
|
std: 'Unexpected default defined on association',
|
|
@@ -17,7 +17,7 @@ function validateHasPersistedElements( artifact, artifactName, prop, path ) {
|
|
|
17
17
|
if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact)) {
|
|
18
18
|
if (!artifact.elements || !hasRealElements(artifact.elements))
|
|
19
19
|
// TODO: Maybe check if there are only calc elements and adapt the message?
|
|
20
|
-
this.error('def-missing-element', path, { '#': artifact.query ? 'view' : 'std' });
|
|
20
|
+
this.error('def-missing-element', path, { '#': ( artifact.query || artifact.projection ) ? 'view' : 'std' });
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -95,7 +95,6 @@ function validateOnCondition( member, memberName, property, path ) {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
-
|
|
99
98
|
if (_links[j].art.virtual)
|
|
100
99
|
this.error(null, csnPath, { id, elemref }, 'Virtual elements can\'t be used in ON-conditions, step $(ID) of path $(ELEMREF)');
|
|
101
100
|
|
|
@@ -116,11 +115,11 @@ function validateOnCondition( member, memberName, property, path ) {
|
|
|
116
115
|
// 2) Path ends on an association (managed or unmanaged) and the other operand is a '$self'
|
|
117
116
|
|
|
118
117
|
// If this path ends structured or on an association, perform the check:
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
118
|
+
if (
|
|
119
|
+
((type.target && type.keys || type.elements) && validStructuredElement ||
|
|
120
|
+
(type.target && validDollarSelf)) && !type.virtual
|
|
121
|
+
) {
|
|
122
|
+
// Do nothing - handled by lib/checks/nonexpandableStructured.js
|
|
124
123
|
}
|
|
125
124
|
else if (type.items && !type.virtual) {
|
|
126
125
|
this.error(null, onPath, { elemref: { ref } },
|
|
@@ -130,6 +129,10 @@ function validateOnCondition( member, memberName, property, path ) {
|
|
|
130
129
|
this.error(null, onPath, { elemref: { ref } },
|
|
131
130
|
'Virtual elements can\'t be used in ON-conditions, path $(ELEMREF)');
|
|
132
131
|
}
|
|
132
|
+
else if (type.on) {
|
|
133
|
+
// Path leaf is an unmanaged association, can't use an unmanaged assoc as operand
|
|
134
|
+
this.error('ref-unexpected-navigation', onPath, { '#': 'unmanagedleaf', id: logReady(ref[ref.length - 1]), elemref: { ref } });
|
|
135
|
+
}
|
|
133
136
|
}
|
|
134
137
|
}
|
|
135
138
|
}
|
|
@@ -18,7 +18,7 @@ function checkSqlAnnotationOnElement( member, memberName, prop, path ) {
|
|
|
18
18
|
this.message('anno-invalid-sql-element', path, { anno: 'sql.prepend' }, 'Annotation $(ANNO) can\'t be used on elements' );
|
|
19
19
|
|
|
20
20
|
if (member['@sql.append']) {
|
|
21
|
-
if (this.artifact.query)
|
|
21
|
+
if (this.artifact.query || this.artifact.projection)
|
|
22
22
|
this.message('anno-invalid-sql-view-element', path, { anno: 'sql.append' }, 'Annotation $(ANNO) can\'t be used on elements in views' );
|
|
23
23
|
else if (this.csnUtils.isStructured(member))
|
|
24
24
|
this.message('anno-invalid-sql-struct', path, { anno: 'sql.append' }, 'Annotation $(ANNO) can\'t be used on structured elements' );
|
|
@@ -57,7 +57,7 @@ function checkSqlAnnotationOnArtifact( artifact, artifactName ) {
|
|
|
57
57
|
this.message('anno-invalid-sql-kind', [ 'definitions', artifactName ], { name: '@sql.append', kind: artifact.kind }, 'Annotation $(NAME) can\'t be used on an artifact of kind $(KIND)' );
|
|
58
58
|
}
|
|
59
59
|
else if (artifact['@sql.prepend']) {
|
|
60
|
-
if (artifact.query)
|
|
60
|
+
if (artifact.query || artifact.projection)
|
|
61
61
|
this.message('anno-invalid-sql-view', [ 'definitions', artifactName ], { name: '@sql.prepend' }, 'Annotation $(NAME) can\'t be used on views' );
|
|
62
62
|
else
|
|
63
63
|
checkValidAnnoValue(artifact, '@sql.prepend', [ 'definitions', artifactName ], this.error, this.options);
|
package/lib/checks/types.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { hasAnnotationValue } = require('../model/csnUtils');
|
|
4
|
-
const { isBetaEnabled } = require('../base/model');
|
|
5
4
|
|
|
6
5
|
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
7
6
|
|
|
@@ -55,7 +54,7 @@ function checkElementTypeDefinitionHasType( member, memberName, prop, path ) {
|
|
|
55
54
|
|
|
56
55
|
// should only happen with csn input, not in cdl
|
|
57
56
|
// calculated elements may not have a .type (requires beta flag)
|
|
58
|
-
if (
|
|
57
|
+
if (!member.value &&
|
|
59
58
|
!parent.projection && !parent.query && !hasArtifactTypeInformation(member)) {
|
|
60
59
|
errorAboutMissingType(this.error, path, memberName, true);
|
|
61
60
|
return;
|
|
@@ -191,7 +191,7 @@ function assertConsistency( model, stage ) {
|
|
|
191
191
|
test: isDictionary( definition ),
|
|
192
192
|
requires: [ 'kind', 'name' ],
|
|
193
193
|
optional: [
|
|
194
|
-
'elements', '$autoElement', '$uncheckedElements', '_origin',
|
|
194
|
+
'elements', '$autoElement', '$uncheckedElements', '_origin', '_extensions',
|
|
195
195
|
'$requireElementAccess', '_effectiveType', '$effectiveSeqNo', '_deps',
|
|
196
196
|
],
|
|
197
197
|
schema: {
|
|
@@ -295,6 +295,7 @@ function assertConsistency( model, stage ) {
|
|
|
295
295
|
'kind', 'name', '_block', '_parent', '_main', 'elements',
|
|
296
296
|
'_effectiveType', '$effectiveSeqNo', '_origin', '_joinParent', '$joinArgsIndex',
|
|
297
297
|
'$duplicates', // duplicate query in FROM clause
|
|
298
|
+
'$inferred', // table alias with $inferred: '$internal'
|
|
298
299
|
],
|
|
299
300
|
},
|
|
300
301
|
none: { optional: () => true }, // parse error
|
|
@@ -339,7 +340,7 @@ function assertConsistency( model, stage ) {
|
|
|
339
340
|
optional: [
|
|
340
341
|
'path', 'elements', '_outer', '_parent', '_main', '_block', 'kind',
|
|
341
342
|
'scope', '_artifact', '$inferred', '$expand', '$inCycle', '$tableAliases', '_$next',
|
|
342
|
-
'_origin', '_effectiveType', '$effectiveSeqNo',
|
|
343
|
+
'_origin', '_effectiveType', '$effectiveSeqNo', '_extensions',
|
|
343
344
|
],
|
|
344
345
|
},
|
|
345
346
|
target: {
|
|
@@ -388,12 +389,18 @@ function assertConsistency( model, stage ) {
|
|
|
388
389
|
kind: [ 'entity', 'view', 'type', 'aspect' ],
|
|
389
390
|
test: isString, // CSN parser should check for 'entity', 'view', 'projection'
|
|
390
391
|
},
|
|
392
|
+
$tokenTexts: {
|
|
393
|
+
parser: true,
|
|
394
|
+
test: isString,
|
|
395
|
+
},
|
|
391
396
|
value: {
|
|
392
397
|
optional: [
|
|
393
398
|
'location', '$inferred', 'sort', 'nulls',
|
|
394
399
|
'param', 'scope', // for dynamic parameter '?'
|
|
395
400
|
// A2J wrongly propagates the following into a CAST of the CSN passed to compileX:
|
|
396
401
|
'elements', 'items', 'enum',
|
|
402
|
+
// CSN parser may let these properties slip through to XSN, even if input is invalid.
|
|
403
|
+
'args', 'op', 'func', 'suffix',
|
|
397
404
|
],
|
|
398
405
|
|
|
399
406
|
kind: true,
|
|
@@ -443,6 +450,8 @@ function assertConsistency( model, stage ) {
|
|
|
443
450
|
requires: [ 'location' ],
|
|
444
451
|
optional: [
|
|
445
452
|
'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicates', 'upTo',
|
|
453
|
+
// expressions as annotation values
|
|
454
|
+
'$tokenTexts', 'op', 'args', 'func',
|
|
446
455
|
],
|
|
447
456
|
// TODO: restrict path to #simplePath
|
|
448
457
|
},
|
|
@@ -470,7 +479,13 @@ function assertConsistency( model, stage ) {
|
|
|
470
479
|
'@': {
|
|
471
480
|
kind: true,
|
|
472
481
|
inherits: 'value',
|
|
473
|
-
optional: [
|
|
482
|
+
optional: [
|
|
483
|
+
'name', '_block', '$priority', '$inferred', '$duplicates', '$errorReported',
|
|
484
|
+
// annotation values
|
|
485
|
+
'$tokenTexts',
|
|
486
|
+
// CSN parser may let these properties slip through to XSN, even if input is invalid.
|
|
487
|
+
'args', 'op', 'func', 'suffix',
|
|
488
|
+
],
|
|
474
489
|
// TODO: name requires if not in parser?
|
|
475
490
|
},
|
|
476
491
|
$priority: { test: isOneOf([ undefined, false, 'extend', 'annotate' ]) },
|
|
@@ -494,7 +509,7 @@ function assertConsistency( model, stage ) {
|
|
|
494
509
|
action: { test: isString },
|
|
495
510
|
param: { test: TODO },
|
|
496
511
|
alias: { test: isString },
|
|
497
|
-
expectedKind: { kind: [ 'extend' ],
|
|
512
|
+
expectedKind: { kind: [ 'extend' ], test: locationVal( isString ) },
|
|
498
513
|
virtual: { kind: true, test: locationVal() },
|
|
499
514
|
key: { kind: true, test: locationVal(), also: [ null, undefined ] },
|
|
500
515
|
masked: { kind: true, test: locationVal() },
|
|
@@ -514,7 +529,7 @@ function assertConsistency( model, stage ) {
|
|
|
514
529
|
'elements', 'cardinality', 'target', 'on', 'foreignKeys', 'items',
|
|
515
530
|
'_outer', '_effectiveType', '$effectiveSeqNo', 'notNull', '_parent',
|
|
516
531
|
'_origin', '_block', '$inferred', '$expand', '$inCycle', '_deps',
|
|
517
|
-
'$syntax',
|
|
532
|
+
'$syntax', '_extensions',
|
|
518
533
|
'_status', '_redirected',
|
|
519
534
|
...typeProperties,
|
|
520
535
|
],
|
|
@@ -574,12 +589,14 @@ function assertConsistency( model, stage ) {
|
|
|
574
589
|
_leadingQuery: { kind: true, test: TODO },
|
|
575
590
|
$replacement: { kind: true, test: TODO }, // for smart * in queries
|
|
576
591
|
_origin: { kind: true, test: TODO },
|
|
592
|
+
_calcOrigin: { kind: true, test: TODO },
|
|
577
593
|
_pathHead: { kind: [ 'element', undefined ], test: TODO }, // column or * (wildcard)
|
|
578
594
|
_from: { kind: true, test: TODO }, // TODO: not necessary anymore ?
|
|
579
595
|
// array of $tableAlias (or includes) for explicit and implicit redirection:
|
|
580
596
|
_redirected: { kind: true, test: TODO },
|
|
581
597
|
// ...array of table aliases for targets from orig to new
|
|
582
598
|
_$next: { kind: true, test: TODO }, // next lexical search environment for values
|
|
599
|
+
_extensions: { kind: true, test: TODO }, // for collecting extend/annotate on artifact
|
|
583
600
|
_extend: { kind: true, test: TODO }, // for collecting extend/annotate on artifact
|
|
584
601
|
_annotate: { kind: true, test: TODO }, // for collecting extend/annotate on artifact
|
|
585
602
|
_extension: { kind: true, test: TODO }, // on artifact to its "super extend/annotate" statement
|
|
@@ -609,6 +626,7 @@ function assertConsistency( model, stage ) {
|
|
|
609
626
|
parser: true,
|
|
610
627
|
kind: true,
|
|
611
628
|
test: isOneOf([
|
|
629
|
+
'', // constructed “super annotate” statement
|
|
612
630
|
// Uppercase values are used in logic, lowercase value are "just for us", i.e.
|
|
613
631
|
// debugging or to add properties such as $generated in Universal CSN.
|
|
614
632
|
// However, that is no longer true. For example, `autoexposed` is used in populate.js
|
|
@@ -620,6 +638,7 @@ function assertConsistency( model, stage ) {
|
|
|
620
638
|
|
|
621
639
|
'$autoElement', // for magicVars: $user is automatically changed to $user.id
|
|
622
640
|
'$generated', // compiler generated annotations, e.g. @Core.Computed
|
|
641
|
+
'$internal', // compiler internal; must not reach CSN output
|
|
623
642
|
'*', // inferred from query wildcard
|
|
624
643
|
'as', // query alias name
|
|
625
644
|
'aspect-composition',
|
package/lib/compiler/base.js
CHANGED
|
@@ -29,14 +29,18 @@ const kindProperties = {
|
|
|
29
29
|
type: { elements: propExists, enum: propExists, include: true },
|
|
30
30
|
aspect: { elements: propExists, actions: true, include: true },
|
|
31
31
|
annotation: { elements: propExists, enum: propExists },
|
|
32
|
-
enum: { normalized: 'element' },
|
|
32
|
+
enum: { normalized: 'element', dict: 'enum' },
|
|
33
33
|
element: { elements: propExists, enum: propExists, dict: 'elements' },
|
|
34
34
|
mixin: { normalized: 'alias' },
|
|
35
35
|
action: {
|
|
36
36
|
params: () => false, elements: () => false, enum: () => false, dict: 'actions',
|
|
37
37
|
}, // no extend params, only annotate
|
|
38
38
|
function: {
|
|
39
|
-
params: () => false,
|
|
39
|
+
params: () => false,
|
|
40
|
+
elements: () => false,
|
|
41
|
+
enum: () => false,
|
|
42
|
+
normalized: 'action',
|
|
43
|
+
dict: 'actions',
|
|
40
44
|
}, // no extend params, only annotate
|
|
41
45
|
key: { normalized: 'element' },
|
|
42
46
|
param: { elements: () => false, enum: () => false, dict: 'params' },
|
|
@@ -60,7 +64,50 @@ function propExists( prop, parent ) {
|
|
|
60
64
|
return (obj.items || obj.targetAspect || obj)[prop];
|
|
61
65
|
}
|
|
62
66
|
|
|
67
|
+
// Return the "old style" name structure with `absolute`, `action`, `param`,
|
|
68
|
+
// `element`. Later we also need to add `select` and `alias`.
|
|
69
|
+
// (Currently not needed, as only used for extend and annotate statements.)
|
|
70
|
+
function getArtifactName( art ) {
|
|
71
|
+
if (!art.name || art.name.absolute)
|
|
72
|
+
return art.name;
|
|
73
|
+
// extend and annotate statement already have "sparse" names → calculate old one
|
|
74
|
+
const link = art.name._artifact;
|
|
75
|
+
const namePath = [];
|
|
76
|
+
while (art._main && !art.name.absolute) { // until we hit an old-style name or the main artifact
|
|
77
|
+
namePath.push( art );
|
|
78
|
+
art = art._parent;
|
|
79
|
+
}
|
|
80
|
+
namePath.reverse();
|
|
81
|
+
const name = { ...art.name };
|
|
82
|
+
for (const np of namePath) {
|
|
83
|
+
const prop = getMemberNameProp( np, np.kind );
|
|
84
|
+
name[prop] = (name[prop]) ? `${ name[prop] }.${ np.name.id }` : np.name.id;
|
|
85
|
+
name.id = np.name.id;
|
|
86
|
+
name.location = np.name.location;
|
|
87
|
+
}
|
|
88
|
+
if (link !== undefined)
|
|
89
|
+
Object.defineProperty( name, '_artifact', { value: link, configurable: true, writable: true } );
|
|
90
|
+
return name;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// TODO: probably store this prop in name
|
|
94
|
+
function getMemberNameProp( elem, kind ) {
|
|
95
|
+
if (kind !== 'annotate' && kind !== 'extend')
|
|
96
|
+
return kindProperties[kind]?.normalized || kind;
|
|
97
|
+
let obj = elem._parent;
|
|
98
|
+
if (obj.params || obj.returns)
|
|
99
|
+
return 'param';
|
|
100
|
+
if (obj.actions)
|
|
101
|
+
return 'action';
|
|
102
|
+
while (obj.items)
|
|
103
|
+
obj = obj.items;
|
|
104
|
+
if (obj.elements || obj.enum)
|
|
105
|
+
return 'element';
|
|
106
|
+
return 'id';
|
|
107
|
+
}
|
|
108
|
+
|
|
63
109
|
module.exports = {
|
|
64
110
|
dictKinds,
|
|
65
111
|
kindProperties,
|
|
112
|
+
getArtifactName,
|
|
66
113
|
};
|
package/lib/compiler/builtins.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
const { builtinLocation } = require('../base/location');
|
|
11
11
|
const { setLink: setProp } = require('./utils');
|
|
12
12
|
|
|
13
|
+
// TODO: make type parameters a dict
|
|
13
14
|
const core = {
|
|
14
15
|
String: { parameters: [ 'length' ], category: 'string' },
|
|
15
16
|
LargeString: { category: 'string' },
|
|
@@ -199,7 +200,16 @@ const magicVariables = {
|
|
|
199
200
|
},
|
|
200
201
|
};
|
|
201
202
|
|
|
202
|
-
// see lib/render/renderUtil.js for DB-specific magic vars, specified in CAP
|
|
203
|
+
// see lib/render/renderUtil.js for DB-specific magic vars, specified in CAP CDS via function
|
|
204
|
+
|
|
205
|
+
const dateRegEx = /^(-?\d{4})-(\d{1,2})-(\d{1,2})$/;
|
|
206
|
+
// YYYY - MM - dd
|
|
207
|
+
const timeRegEx = /^T?(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?(?:Z|[+-]\d{2}(?::\d{2})?)?$/;
|
|
208
|
+
// T HH : mm : ss TZD
|
|
209
|
+
// eslint-disable-next-line max-len
|
|
210
|
+
const timestampRegEx = /^(-?\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(\.\d{1,7})?)?(?:Z|[+-]\d{2}(?::\d{2})?)?$/;
|
|
211
|
+
// YYYY - MM - dd T HH : mm : ss . fraction TZD
|
|
212
|
+
const numberRegEx = /^[ \t]*[-+]?(\d+(\.\d*)?|\.\d+)(e[-+]\d+)?[ \t]*$/i;
|
|
203
213
|
|
|
204
214
|
/**
|
|
205
215
|
* Patterns for literal token tests and creation. The value is a map from the
|
|
@@ -227,7 +237,7 @@ const quotedLiteralPatterns = {
|
|
|
227
237
|
test_variant: 'time',
|
|
228
238
|
test_fn: (x) => {
|
|
229
239
|
// Leading `T` allowed in ISO 8601.
|
|
230
|
-
const match = x.match(
|
|
240
|
+
const match = x.match( timeRegEx );
|
|
231
241
|
return match !== null && checkTime( match[1], match[2], match[3] );
|
|
232
242
|
},
|
|
233
243
|
json_type: 'string',
|
|
@@ -235,7 +245,7 @@ const quotedLiteralPatterns = {
|
|
|
235
245
|
date: {
|
|
236
246
|
test_variant: 'date',
|
|
237
247
|
test_fn: (x) => {
|
|
238
|
-
const match = x.match(
|
|
248
|
+
const match = x.match( dateRegEx );
|
|
239
249
|
return match !== null && checkDate( match[1], match[2], match[3] );
|
|
240
250
|
},
|
|
241
251
|
json_type: 'string',
|
|
@@ -243,8 +253,7 @@ const quotedLiteralPatterns = {
|
|
|
243
253
|
timestamp: {
|
|
244
254
|
test_variant: 'timestamp',
|
|
245
255
|
test_fn: (x) => {
|
|
246
|
-
|
|
247
|
-
const match = x.match( /^(-?\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(\.\d{1,7})?)?$/ );
|
|
256
|
+
const match = x.match( timestampRegEx );
|
|
248
257
|
return match !== null && checkDate( match[1], match[2], match[3] ) &&
|
|
249
258
|
checkTime( match[4], match[5], match[6] );
|
|
250
259
|
},
|
|
@@ -259,7 +268,7 @@ const quotedLiteralPatterns = {
|
|
|
259
268
|
},
|
|
260
269
|
number: {
|
|
261
270
|
test_variant: 'number',
|
|
262
|
-
test_fn: (x =>
|
|
271
|
+
test_fn: (x => numberRegEx.test( x )),
|
|
263
272
|
json_type: 'number',
|
|
264
273
|
secondary_json_type: 'string',
|
|
265
274
|
},
|
package/lib/compiler/checks.js
CHANGED
|
@@ -777,12 +777,12 @@ function check( model ) { // = XSN
|
|
|
777
777
|
// Must have literal or path unless it is a boolean
|
|
778
778
|
if (!anno.literal && !anno.path && getFinalTypeNameOf(elementDecl) !== 'cds.Boolean') {
|
|
779
779
|
if (elementDecl.type && elementDecl.type._artifact.name.absolute) {
|
|
780
|
-
warning(
|
|
781
|
-
'
|
|
780
|
+
warning('anno-expecting-value', anno.location || anno.name.location,
|
|
781
|
+
{ '#': 'type', type: elementDecl.type._artifact });
|
|
782
782
|
}
|
|
783
783
|
else {
|
|
784
|
-
warning(
|
|
785
|
-
'
|
|
784
|
+
warning('anno-expecting-value', anno.location || anno.name.location,
|
|
785
|
+
{ '#': 'std', anno: anno.name.absolute });
|
|
786
786
|
}
|
|
787
787
|
|
|
788
788
|
return;
|