@sap/cds-compiler 2.11.4 → 2.12.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 +58 -1
- package/bin/cds_update_identifiers.js +7 -7
- package/bin/cdsc.js +9 -10
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +12 -0
- package/lib/api/main.js +2 -0
- package/lib/api/options.js +2 -2
- package/lib/base/message-registry.js +31 -2
- package/lib/base/model.js +1 -0
- package/lib/base/optionProcessorHelper.js +97 -69
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/sql-snippets.js +93 -0
- package/lib/checks/validator.js +8 -0
- package/lib/compiler/assert-consistency.js +5 -3
- package/lib/compiler/base.js +0 -1
- package/lib/compiler/checks.js +32 -9
- package/lib/compiler/definer.js +25 -4
- package/lib/compiler/index.js +1 -1
- package/lib/compiler/propagator.js +3 -2
- package/lib/compiler/resolver.js +97 -6
- package/lib/compiler/shared.js +12 -1
- package/lib/compiler/utils.js +7 -0
- package/lib/edm/annotations/genericTranslation.js +34 -17
- package/lib/edm/annotations/preprocessAnnotations.js +1 -1
- package/lib/edm/csn2edm.js +1 -1
- package/lib/edm/edm.js +8 -8
- package/lib/edm/edmPreprocessor.js +30 -23
- package/lib/edm/edmUtils.js +11 -12
- package/lib/gen/Dictionary.json +82 -40
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/language.tokens +15 -14
- package/lib/gen/languageLexer.interp +9 -1
- package/lib/gen/languageLexer.js +830 -779
- package/lib/gen/languageLexer.tokens +7 -6
- package/lib/gen/languageParser.js +2401 -2282
- package/lib/json/from-csn.js +47 -16
- package/lib/json/to-csn.js +17 -5
- package/lib/language/antlrParser.js +3 -3
- package/lib/language/docCommentParser.js +1 -1
- package/lib/language/genericAntlrParser.js +68 -51
- package/lib/language/language.g4 +128 -74
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +5 -3
- package/lib/main.js +3 -2
- package/lib/model/csnRefs.js +116 -68
- package/lib/model/csnUtils.js +40 -48
- package/lib/model/enrichCsn.js +30 -14
- package/lib/optionProcessor.js +3 -3
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +193 -79
- package/lib/render/toHdbcds.js +179 -95
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +57 -40
- package/lib/render/utils/common.js +24 -5
- package/lib/render/utils/sql.js +6 -4
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/associations.js +389 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +6 -4
- package/lib/transform/db/draft.js +3 -2
- package/lib/transform/db/expansion.js +4 -5
- package/lib/transform/db/flattening.js +5 -6
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +36 -23
- package/lib/transform/forHanaNew.js +35 -626
- package/lib/transform/forOdataNew.js +5 -4
- package/lib/transform/localized.js +3 -14
- package/lib/transform/odata/generateForeignKeyElements.js +2 -2
- package/lib/transform/transformUtilsNew.js +13 -13
- package/lib/transform/translateAssocsToJoins.js +8 -8
- package/lib/transform/universalCsnEnricher.js +217 -47
- package/lib/utils/file.js +2 -1
- package/lib/utils/timetrace.js +8 -2
- package/package.json +1 -1
|
@@ -110,7 +110,8 @@ function checkActionOrFunction(art, artName, prop, path) {
|
|
|
110
110
|
* @param {CSN.Path} currPath The current path
|
|
111
111
|
*/
|
|
112
112
|
function checkUserDefinedType(type, typeName, currPath) {
|
|
113
|
-
|
|
113
|
+
// TODO: isBuiltinType does not resolve any type-chains.
|
|
114
|
+
if (!isBuiltinType(type.type) && type.kind && type.kind !== 'type') {
|
|
114
115
|
const serviceOfType = this.csnUtils.getServiceName(typeName);
|
|
115
116
|
if (serviceName && serviceName !== serviceOfType) {
|
|
116
117
|
// if (!(isMultiSchema && serviceOfType)) {
|
|
@@ -18,12 +18,12 @@ function validateForeignKeys(member) {
|
|
|
18
18
|
|
|
19
19
|
// Declared as arrow-function to keep scope the same (this value)
|
|
20
20
|
const handleAssociation = (mem) => {
|
|
21
|
-
for (
|
|
22
|
-
if (
|
|
23
|
-
if (!
|
|
21
|
+
for (const key of mem.keys) {
|
|
22
|
+
if (key.ref) {
|
|
23
|
+
if (!key._art)
|
|
24
24
|
continue;
|
|
25
25
|
// eslint-disable-next-line no-use-before-define
|
|
26
|
-
checkForItems(
|
|
26
|
+
checkForItems(key._art);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
};
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
function checkUsedTypesForAnonymousAspectComposition(member) {
|
|
14
14
|
// Declared as arrow-function to keep scope the same (this value)
|
|
15
15
|
const handleAssociation = (mem, fn) => {
|
|
16
|
-
for (
|
|
17
|
-
if (
|
|
18
|
-
if (!
|
|
16
|
+
for (const key of mem.keys) {
|
|
17
|
+
if (key.ref) {
|
|
18
|
+
if (!key._art)
|
|
19
19
|
continue;
|
|
20
|
-
fn(
|
|
20
|
+
fn(key._art);
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
};
|
|
@@ -124,10 +124,8 @@ function checkQueryForNoDBArtifacts(query) {
|
|
|
124
124
|
for (const prop of generalQueryProperties) {
|
|
125
125
|
const queryPart = (query.SELECT || query.SET)[prop];
|
|
126
126
|
if (Array.isArray(queryPart)) {
|
|
127
|
-
for (
|
|
128
|
-
const part = queryPart[i];
|
|
127
|
+
for (const part of queryPart)
|
|
129
128
|
checkRef(part, prop === 'columns');
|
|
130
|
-
}
|
|
131
129
|
}
|
|
132
130
|
else if (typeof queryPart === 'object') {
|
|
133
131
|
checkRef(queryPart, prop === 'columns');
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isBetaEnabled } = require('../base/model');
|
|
4
|
+
|
|
5
|
+
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Check that @sql.prepend annotation is not used on any elements and @sql.append is not used on elements in views.
|
|
9
|
+
*
|
|
10
|
+
* @param {CSN.Element} member
|
|
11
|
+
* @param {string} memberName
|
|
12
|
+
* @param {string} prop
|
|
13
|
+
* @param {CSN.Path} path
|
|
14
|
+
* @returns {void}
|
|
15
|
+
*/
|
|
16
|
+
function checkSqlAnnotationOnElement(member, memberName, prop, path) {
|
|
17
|
+
if (isBetaEnabled(this.options, 'sqlSnippets')) {
|
|
18
|
+
if (member['@sql.replace'])
|
|
19
|
+
this.error(null, path, { anno: 'sql.replace' }, `Annotation $(ANNO) is reserved and must not be used`);
|
|
20
|
+
if (member['@sql.prepend'])
|
|
21
|
+
this.error('anno-invalid-sql-element', path, { anno: 'sql.prepend' }, `Annotation $(ANNO) can't be used on elements` );
|
|
22
|
+
|
|
23
|
+
if (member['@sql.append']) {
|
|
24
|
+
if (this.artifact.query)
|
|
25
|
+
this.error('anno-invalid-sql-view-element', path, { anno: 'sql.append' }, `Annotation $(ANNO) can't be used on elements in views` );
|
|
26
|
+
else if (this.csnUtils.isStructured(member))
|
|
27
|
+
this.error('anno-invalid-sql-struct', path, { anno: 'sql.append' }, `Annotation $(ANNO) can't be used on structured elements` );
|
|
28
|
+
else
|
|
29
|
+
checkValidAnnoValue(member, '@sql.append', path, this.error, this.options);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {object} carrier element which has the annotation
|
|
36
|
+
* @param {string} annotation
|
|
37
|
+
* @param {CSN.Path} path
|
|
38
|
+
* @param {Function} error
|
|
39
|
+
* @param {CSN.Options} options
|
|
40
|
+
*/
|
|
41
|
+
function checkValidAnnoValue(carrier, annotation, path, error, options) {
|
|
42
|
+
if (carrier[annotation] !== undefined && carrier[annotation] !== null) {
|
|
43
|
+
if (typeof carrier[annotation] !== 'string')
|
|
44
|
+
error(null, path, { anno: annotation.slice(1), type: typeof carrier[annotation] }, `Annotation $(ANNO) must be a string, found $(TYPE)` );
|
|
45
|
+
else if (options.transformation === 'sql') // HDI and HDBCDS do their own checks
|
|
46
|
+
guardAgainstInjection(annotation, carrier[annotation], path, error);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Check that @sql.prepend is not used on views - only supported for entities (tables)
|
|
52
|
+
*
|
|
53
|
+
* @param {CSN.Artifact} artifact
|
|
54
|
+
* @param {string} artifactName
|
|
55
|
+
*/
|
|
56
|
+
function checkSqlAnnotationOnArtifact(artifact, artifactName) {
|
|
57
|
+
if (isBetaEnabled(this.options, 'sqlSnippets')) {
|
|
58
|
+
if (artifact['@sql.prepend']) {
|
|
59
|
+
if (artifact.query)
|
|
60
|
+
this.error('anno-invalid-sql-view', [ 'definitions', artifactName ], { name: '@sql.prepend' }, `Annotation $(NAME) can't be used on views` );
|
|
61
|
+
else
|
|
62
|
+
checkValidAnnoValue(artifact, '@sql.prepend', [ 'definitions', artifactName ], this.error, this.options);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (artifact['@sql.replace'])
|
|
66
|
+
this.error(null, [ 'definitions', artifactName ], { anno: 'sql.replace' }, `Annotation $(ANNO) is reserved and must not be used`);
|
|
67
|
+
|
|
68
|
+
checkValidAnnoValue(artifact, '@sql.append', [ 'definitions', artifactName ], this.error, this.options);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Anything that could terminate the "old" statement and start a new one basically.
|
|
73
|
+
const invalidInSnippet = [ ';', '--', '/*', '*/' ];
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check that the common characters used to terminate the current statement and start a fresh one are not used.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} annoName
|
|
79
|
+
* @param {string} annoValue
|
|
80
|
+
* @param {CSN.Path} path
|
|
81
|
+
* @param {Function} error
|
|
82
|
+
*/
|
|
83
|
+
function guardAgainstInjection(annoName, annoValue, path, error) {
|
|
84
|
+
for (const invalid of invalidInSnippet) {
|
|
85
|
+
if (annoValue.indexOf(invalid) !== -1) // These should probably not be configurable, right?
|
|
86
|
+
error(null, path, { name: annoName, prop: invalid }, 'Annotation $(NAME) must not contain $(PROP)');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = {
|
|
91
|
+
checkSqlAnnotationOnArtifact,
|
|
92
|
+
checkSqlAnnotationOnElement,
|
|
93
|
+
};
|
package/lib/checks/validator.js
CHANGED
|
@@ -35,6 +35,10 @@ const checkExplicitlyNullableKeys = require('./nullableKeys');
|
|
|
35
35
|
const nonexpandableStructuredInExpression = require('./nonexpandableStructured');
|
|
36
36
|
const unknownMagic = require('./unknownMagic');
|
|
37
37
|
const managedWithoutKeys = require('./managedWithoutKeys');
|
|
38
|
+
const {
|
|
39
|
+
checkSqlAnnotationOnArtifact,
|
|
40
|
+
checkSqlAnnotationOnElement,
|
|
41
|
+
} = require('./sql-snippets');
|
|
38
42
|
|
|
39
43
|
const forHanaMemberValidators
|
|
40
44
|
= [
|
|
@@ -45,6 +49,8 @@ const forHanaMemberValidators
|
|
|
45
49
|
checkExplicitlyNullableKeys,
|
|
46
50
|
managedWithoutKeys,
|
|
47
51
|
warnAboutDefaultOnAssociationForHanaCds,
|
|
52
|
+
// sql.prepend/append
|
|
53
|
+
checkSqlAnnotationOnElement,
|
|
48
54
|
];
|
|
49
55
|
|
|
50
56
|
const forHanaArtifactValidators
|
|
@@ -53,6 +59,8 @@ const forHanaArtifactValidators
|
|
|
53
59
|
validateCdsPersistenceAnnotation,
|
|
54
60
|
// virtual items are not persisted on the db
|
|
55
61
|
checkForEmptyOrOnlyVirtual,
|
|
62
|
+
// sql.prepend/append
|
|
63
|
+
checkSqlAnnotationOnArtifact,
|
|
56
64
|
];
|
|
57
65
|
|
|
58
66
|
const forHanaCsnValidators = [ nonexpandableStructuredInExpression, unknownMagic ];
|
|
@@ -424,9 +424,12 @@ function assertConsistency( model, stage ) {
|
|
|
424
424
|
val: {
|
|
425
425
|
test: isVal, // the following for array/struct value
|
|
426
426
|
requires: [ 'location' ],
|
|
427
|
-
optional: [
|
|
427
|
+
optional: [
|
|
428
|
+
'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicate', 'upTo',
|
|
429
|
+
],
|
|
428
430
|
// TODO: restrict path to #simplePath
|
|
429
431
|
},
|
|
432
|
+
upTo: { test: TODO },
|
|
430
433
|
struct: { inherits: 'val', test: isDictionary( definition ) }, // def because double @
|
|
431
434
|
args: {
|
|
432
435
|
inherits: 'value',
|
|
@@ -792,8 +795,7 @@ function assertConsistency( model, stage ) {
|
|
|
792
795
|
}
|
|
793
796
|
|
|
794
797
|
function at( nodes, prop, name ) {
|
|
795
|
-
|
|
796
|
-
const n = name ? (typeof name === 'number' ? ` for index ${ name }` : ` for "${ name }"`) : '';
|
|
798
|
+
const n = name && (typeof name === 'number' ? ` for index ${ name }` : ` for "${ name }"`) || '';
|
|
797
799
|
const loc = nodes.find( o => o && typeof o === 'object' && (o.location || o.start) );
|
|
798
800
|
const f = (prop) ? `${ n } in property '${ prop }'` : n;
|
|
799
801
|
const l = locationString( loc && loc.location || loc || model.location );
|
package/lib/compiler/base.js
CHANGED
package/lib/compiler/checks.js
CHANGED
|
@@ -87,6 +87,17 @@ function check( model ) { // = XSN
|
|
|
87
87
|
'Keyword “localized” may only be used in combination with string types');
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
+
// "key" keyword at localized element in SELECT list.
|
|
91
|
+
// TODO: This check should be moved to localized.js
|
|
92
|
+
if (elem.key && elem.key.val && elem._main && elem._main.query) {
|
|
93
|
+
// original element is localized but not key, as that would have
|
|
94
|
+
// already resulted in a warning
|
|
95
|
+
if (elem._origin && elem._origin.localized && elem._origin.localized.val &&
|
|
96
|
+
( !elem._origin.key || !elem._origin.key.val)) {
|
|
97
|
+
warning('localized-key', [ elem.key.location, elem ], { keyword: 'localized' },
|
|
98
|
+
'Keyword $(KEYWORD) is ignored for primary keys');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
90
101
|
}
|
|
91
102
|
|
|
92
103
|
function checkQuery( query ) {
|
|
@@ -334,11 +345,17 @@ function check( model ) { // = XSN
|
|
|
334
345
|
// Max cardinalities must be a positive number or '*'
|
|
335
346
|
for (const prop of [ 'sourceMax', 'targetMax' ]) {
|
|
336
347
|
if (elem.cardinality[prop]) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
348
|
+
const { literal, val, location } = elem.cardinality[prop];
|
|
349
|
+
if (!(literal === 'number' && val > 0 ||
|
|
350
|
+
literal === 'string' && val === '*')) {
|
|
351
|
+
error('invalid-cardinality', [ location, elem ], { '#': prop, code: val }, {
|
|
352
|
+
// eslint-disable-next-line max-len
|
|
353
|
+
std: 'Value $(CODE) is invalid for maximum cardinality, expecting a positive number or ‘*’',
|
|
354
|
+
// eslint-disable-next-line max-len
|
|
355
|
+
sourceMax: 'Value $(CODE) is invalid for maximum source cardinality, expecting a positive number or ‘*’',
|
|
356
|
+
// eslint-disable-next-line max-len
|
|
357
|
+
targetMax: 'Value $(CODE) is invalid for maximum target cardinality, expecting a positive number or ‘*’',
|
|
358
|
+
});
|
|
342
359
|
}
|
|
343
360
|
}
|
|
344
361
|
}
|
|
@@ -348,10 +365,16 @@ function check( model ) { // = XSN
|
|
|
348
365
|
// from-csn.json (expected non-negative number)
|
|
349
366
|
for (const prop of [ 'sourceMin', 'targetMin' ]) {
|
|
350
367
|
if (elem.cardinality[prop]) {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
368
|
+
const { literal, val, location } = elem.cardinality[prop];
|
|
369
|
+
if (!(literal === 'number' && val >= 0)) {
|
|
370
|
+
error('invalid-cardinality', [ location, elem ], { '#': prop, code: val }, {
|
|
371
|
+
// eslint-disable-next-line max-len
|
|
372
|
+
std: 'Value $(CODE) is invalid for minimum cardinality, expecting a non-negative number',
|
|
373
|
+
// eslint-disable-next-line max-len
|
|
374
|
+
targetMin: 'Value $(CODE) is invalid for minimum target cardinality, expecting a non-negative number',
|
|
375
|
+
// eslint-disable-next-line max-len
|
|
376
|
+
sourceMin: 'Value $(CODE) is invalid for minimum source cardinality, expecting a non-negative number',
|
|
377
|
+
});
|
|
355
378
|
}
|
|
356
379
|
}
|
|
357
380
|
}
|
package/lib/compiler/definer.js
CHANGED
|
@@ -370,7 +370,7 @@ function define( model ) {
|
|
|
370
370
|
// TODO: check name: no "."
|
|
371
371
|
if (path[0].id === 'localized' || path[0].id.startsWith( 'localized.' )) {
|
|
372
372
|
decl.$inferred = 'LOCALIZED-IGNORED';
|
|
373
|
-
warning( 'using-localized-view', [
|
|
373
|
+
warning( 'using-localized-view', [ decl.location, decl ], {},
|
|
374
374
|
'Localization views can\'t be referred to - ignored USING' );
|
|
375
375
|
// actually not ignored anymore
|
|
376
376
|
}
|
|
@@ -1152,7 +1152,8 @@ function define( model ) {
|
|
|
1152
1152
|
break; // only direct projection for auto-exposed
|
|
1153
1153
|
}
|
|
1154
1154
|
let ancestors = art && (!autoexposed && art._ancestors || []);
|
|
1155
|
-
|
|
1155
|
+
chain.reverse();
|
|
1156
|
+
for (const a of chain) {
|
|
1156
1157
|
ancestors = (ancestors ? [ ...ancestors, art ] : []);
|
|
1157
1158
|
setProp( a, '_ancestors', ancestors );
|
|
1158
1159
|
art = a;
|
|
@@ -1274,6 +1275,9 @@ function define( model ) {
|
|
|
1274
1275
|
}
|
|
1275
1276
|
}
|
|
1276
1277
|
|
|
1278
|
+
/**
|
|
1279
|
+
* @returns {boolean|0} `true`, if allowed, `false` if forbidden, `0` if circular containment.
|
|
1280
|
+
*/
|
|
1277
1281
|
function allowAspectComposition( target, elem, keys, entityName ) {
|
|
1278
1282
|
if (!target.elements || Object.values( target.elements ).some( e => e.$duplicates ))
|
|
1279
1283
|
return false; // no elements or with redefinitions
|
|
@@ -1520,8 +1524,15 @@ function define( model ) {
|
|
|
1520
1524
|
forEachMemberRecursivelyWithQuery(art, checkArtifact);
|
|
1521
1525
|
}
|
|
1522
1526
|
|
|
1523
|
-
|
|
1524
|
-
|
|
1527
|
+
/**
|
|
1528
|
+
* Function for parse.cdl.
|
|
1529
|
+
* This parse.cdl function resolves types inside the artifact ("resolveTypeUnchecked").
|
|
1530
|
+
*
|
|
1531
|
+
* @todo This function needs to be properly reworked because at the moment we simply
|
|
1532
|
+
* added checks as issues arose (e.g. for "artifact.value").
|
|
1533
|
+
*
|
|
1534
|
+
* @param {XSN.Artifact} artifact
|
|
1535
|
+
* */
|
|
1525
1536
|
function checkArtifact( artifact ) {
|
|
1526
1537
|
// columns are initialized (and made to elements) in the resolver - do init here
|
|
1527
1538
|
for (const col of artifact.columns || []) {
|
|
@@ -1532,11 +1543,21 @@ function define( model ) {
|
|
|
1532
1543
|
recursivelyResolveExpressionCastTypes(col.value, artifact);
|
|
1533
1544
|
}
|
|
1534
1545
|
|
|
1546
|
+
// Possible inside expand/inline
|
|
1547
|
+
if (artifact.value)
|
|
1548
|
+
recursivelyResolveExpressionCastTypes(artifact.value, artifact);
|
|
1549
|
+
|
|
1535
1550
|
resolveTypeUnchecked(artifact, artifact);
|
|
1536
1551
|
|
|
1537
1552
|
if (artifact.items)
|
|
1538
1553
|
resolveTypeUnchecked(artifact.items, artifact);
|
|
1539
1554
|
|
|
1555
|
+
if (Array.isArray(artifact.expand))
|
|
1556
|
+
artifact.expand.forEach(art => checkArtifact(art));
|
|
1557
|
+
|
|
1558
|
+
if (Array.isArray(artifact.inline))
|
|
1559
|
+
artifact.inline.forEach(art => checkArtifact(art));
|
|
1560
|
+
|
|
1540
1561
|
for (const include of (artifact.includes || []))
|
|
1541
1562
|
resolveUncheckedPath(include, 'include', artifact);
|
|
1542
1563
|
|
package/lib/compiler/index.js
CHANGED
|
@@ -64,7 +64,7 @@ class ArgumentError extends Error {
|
|
|
64
64
|
* @param {object} options Compile options
|
|
65
65
|
* @param {object} messageFunctions If not provided, parse errors will not lead to an exception
|
|
66
66
|
*/
|
|
67
|
-
function parseX( source, filename, options = {}, messageFunctions ) {
|
|
67
|
+
function parseX( source, filename, options = {}, messageFunctions = null ) {
|
|
68
68
|
if (!messageFunctions)
|
|
69
69
|
messageFunctions = createMessageFunctions( options, 'parse' );
|
|
70
70
|
const ext = path.extname( filename ).toLowerCase();
|
|
@@ -19,6 +19,8 @@ function propagate( model ) {
|
|
|
19
19
|
'@cds.persistence.calcview': never,
|
|
20
20
|
'@cds.persistence.udf': never,
|
|
21
21
|
'@cds.persistence.skip': notWithPersistenceTable,
|
|
22
|
+
'@sql.prepend': never,
|
|
23
|
+
'@sql.append': never,
|
|
22
24
|
'@Analytics.hidden': never,
|
|
23
25
|
'@Analytics.visible': never,
|
|
24
26
|
'@cds.autoexpose': onlyViaArtifact,
|
|
@@ -64,8 +66,7 @@ function propagate( model ) {
|
|
|
64
66
|
if (!art)
|
|
65
67
|
return;
|
|
66
68
|
if (!checkAndSetStatus( art )) {
|
|
67
|
-
|
|
68
|
-
runMembers( art );
|
|
69
|
+
runMembers( art );
|
|
69
70
|
return;
|
|
70
71
|
}
|
|
71
72
|
// console.log('RUN:', art.name, art.elements ? Object.keys(art.elements) : 0)
|
package/lib/compiler/resolver.js
CHANGED
|
@@ -54,6 +54,7 @@ const {
|
|
|
54
54
|
setLink,
|
|
55
55
|
annotationVal,
|
|
56
56
|
augmentPath,
|
|
57
|
+
pathName,
|
|
57
58
|
splitIntoPath,
|
|
58
59
|
linkToOrigin,
|
|
59
60
|
setMemberParent,
|
|
@@ -1108,7 +1109,6 @@ function resolve( model ) {
|
|
|
1108
1109
|
// or use userQuery( query ) in the following, too?
|
|
1109
1110
|
setMemberParent( col, `.${ q.$inlines.length }`, query );
|
|
1110
1111
|
initFromColumns( query, col.inline, col );
|
|
1111
|
-
continue;
|
|
1112
1112
|
}
|
|
1113
1113
|
else if (!col.$replacement) {
|
|
1114
1114
|
const id = ensureColumnName( col, query );
|
|
@@ -1602,9 +1602,17 @@ function resolve( model ) {
|
|
|
1602
1602
|
}
|
|
1603
1603
|
}
|
|
1604
1604
|
|
|
1605
|
-
|
|
1605
|
+
/**
|
|
1606
|
+
* @param {XSN.Artifact} art
|
|
1607
|
+
* @param {XSN.Extension[]} [extensions]
|
|
1608
|
+
* @param {string} [prop]
|
|
1609
|
+
* @param {string} [name]
|
|
1610
|
+
* @param {object} [parent]
|
|
1611
|
+
* @param {string} [kind]
|
|
1612
|
+
*/
|
|
1613
|
+
function annotateMembers( art, extensions, prop, name, parent, kind ) {
|
|
1606
1614
|
const showMsg = !art && parent && parent.kind !== 'annotate';
|
|
1607
|
-
if (!art && extensions.length) {
|
|
1615
|
+
if (!art && extensions && extensions.length) {
|
|
1608
1616
|
if (Array.isArray( parent ))
|
|
1609
1617
|
return;
|
|
1610
1618
|
const parentExt = extensionFor(parent);
|
|
@@ -1620,7 +1628,7 @@ function resolve( model ) {
|
|
|
1620
1628
|
}
|
|
1621
1629
|
}
|
|
1622
1630
|
|
|
1623
|
-
for (const ext of extensions) {
|
|
1631
|
+
for (const ext of extensions || []) {
|
|
1624
1632
|
if ('_artifact' in ext.name) // already applied
|
|
1625
1633
|
continue;
|
|
1626
1634
|
setProp( ext.name, '_artifact', art );
|
|
@@ -1849,7 +1857,7 @@ function resolve( model ) {
|
|
|
1849
1857
|
[ mergeSource.name.location, art ], { code: '...' } );
|
|
1850
1858
|
return;
|
|
1851
1859
|
}
|
|
1852
|
-
mergeTarget.val
|
|
1860
|
+
mergeTarget.val = mergeArrayValues( mergeSource.val, mergeTarget.val );
|
|
1853
1861
|
}
|
|
1854
1862
|
}
|
|
1855
1863
|
});
|
|
@@ -1869,7 +1877,7 @@ function resolve( model ) {
|
|
|
1869
1877
|
[ mergeSource.name.location, art ], { code: '...' } );
|
|
1870
1878
|
return mergeTarget;
|
|
1871
1879
|
}
|
|
1872
|
-
mergeTarget.val
|
|
1880
|
+
mergeTarget.val = mergeArrayValues( mergeSource.val, mergeTarget.val );
|
|
1873
1881
|
layer = layers.layer( mergeSource._block );
|
|
1874
1882
|
delete layerAnnos[(layer) ? layer.realname : ''];
|
|
1875
1883
|
pos = findEllipsis( mergeTarget );
|
|
@@ -1880,6 +1888,89 @@ function resolve( model ) {
|
|
|
1880
1888
|
return mergeTarget;
|
|
1881
1889
|
}
|
|
1882
1890
|
|
|
1891
|
+
function mergeArrayValues( previousValue, arraySpec ) {
|
|
1892
|
+
let prevPos = 0;
|
|
1893
|
+
const result = [];
|
|
1894
|
+
for (const item of arraySpec) {
|
|
1895
|
+
const ell = item && item.literal === 'token' && item.val === '...';
|
|
1896
|
+
if (!ell) {
|
|
1897
|
+
result.push( item );
|
|
1898
|
+
}
|
|
1899
|
+
else {
|
|
1900
|
+
let upToSpec = item.upTo && checkUpToSpec( item.upTo, true );
|
|
1901
|
+
while (prevPos < previousValue.length) {
|
|
1902
|
+
const prevItem = previousValue[prevPos++];
|
|
1903
|
+
result.push( prevItem );
|
|
1904
|
+
if (upToSpec && prevItem && equalUpTo( prevItem, item.upTo)) {
|
|
1905
|
+
upToSpec = false;
|
|
1906
|
+
break;
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
if (upToSpec) { // non-matched UP TO
|
|
1910
|
+
warning( null, [ item.upTo.location, art ], { anno: annoName, code: '... up to' },
|
|
1911
|
+
'The $(CODE) value does not match any item in the base annotation $(ANNO)' );
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
return result;
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
function checkUpToSpec( upToSpec, trueIfFullUpTo ) {
|
|
1919
|
+
const { literal } = upToSpec;
|
|
1920
|
+
if (trueIfFullUpTo !== true) { // inside struct of UP TO
|
|
1921
|
+
if (![ 'struct', 'array' ].includes( literal ))
|
|
1922
|
+
return true;
|
|
1923
|
+
}
|
|
1924
|
+
else if (literal === 'struct') {
|
|
1925
|
+
return Object.values( upToSpec.struct ).every( checkUpToSpec );
|
|
1926
|
+
}
|
|
1927
|
+
else if (![ 'array', 'boolean', 'null' ].includes( literal )) {
|
|
1928
|
+
return true;
|
|
1929
|
+
}
|
|
1930
|
+
error( null, [ upToSpec.location, art ],
|
|
1931
|
+
{ anno: annoName, code: '... up to', '#': literal },
|
|
1932
|
+
{
|
|
1933
|
+
std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
|
|
1934
|
+
array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
|
|
1935
|
+
// eslint-disable-next-line max-len
|
|
1936
|
+
struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
|
|
1937
|
+
boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
|
|
1938
|
+
null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
|
|
1939
|
+
} );
|
|
1940
|
+
return false;
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
function equalUpTo( previousItem, upToSpec ) {
|
|
1944
|
+
if (!previousItem)
|
|
1945
|
+
return false;
|
|
1946
|
+
if ('val' in upToSpec) {
|
|
1947
|
+
if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
|
|
1948
|
+
return true;
|
|
1949
|
+
const typeUpTo = typeof upToSpec.val;
|
|
1950
|
+
const typePrev = typeof previousItem.val;
|
|
1951
|
+
if (typeUpTo === 'number')
|
|
1952
|
+
return typePrev === 'string' && previousItem.val === upToSpec.val.toString();
|
|
1953
|
+
if (typePrev === 'number')
|
|
1954
|
+
return typeUpTo === 'string' && upToSpec.val === previousItem.val.toString();
|
|
1955
|
+
}
|
|
1956
|
+
else if (upToSpec.path) {
|
|
1957
|
+
return previousItem.path && normalizeRef( previousItem ) === normalizeRef( upToSpec );
|
|
1958
|
+
}
|
|
1959
|
+
else if (upToSpec.sym) {
|
|
1960
|
+
return previousItem.sym && previousItem.sym.id === upToSpec.sym.id;
|
|
1961
|
+
}
|
|
1962
|
+
else if (upToSpec.struct && previousItem.struct) {
|
|
1963
|
+
return Object.entries( upToSpec.struct )
|
|
1964
|
+
.every( ([ n, v ]) => equalUpTo( previousItem.struct[n], v ) );
|
|
1965
|
+
}
|
|
1966
|
+
return false;
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
function normalizeRef( node ) { // see to-csn.js
|
|
1970
|
+
const ref = pathName( node.path );
|
|
1971
|
+
return node.variant ? `${ ref }#${ node.variant.id }` : ref;
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1883
1974
|
function removeEllipsis(a, pos = findEllipsis( a )) {
|
|
1884
1975
|
let count = 0;
|
|
1885
1976
|
while (a.literal === 'array' && pos > -1) {
|
package/lib/compiler/shared.js
CHANGED
|
@@ -26,7 +26,6 @@ function artifactsEnv( art ) {
|
|
|
26
26
|
*/
|
|
27
27
|
// TODO: yes, this function will be renamed
|
|
28
28
|
function fns( model ) {
|
|
29
|
-
/** @type {CSN.Options} */
|
|
30
29
|
const { options } = model;
|
|
31
30
|
const {
|
|
32
31
|
info, warning, error, message,
|
|
@@ -173,16 +172,25 @@ function fns( model ) {
|
|
|
173
172
|
return !(art.elements && !art.query && !art.type && !art.params);
|
|
174
173
|
}
|
|
175
174
|
|
|
175
|
+
/**
|
|
176
|
+
* @returns {boolean|string}
|
|
177
|
+
*/
|
|
176
178
|
function checkTypeRef( art ) {
|
|
177
179
|
if (art.kind === 'type' || art.kind === 'element')
|
|
178
180
|
return false;
|
|
179
181
|
return ![ 'entity', 'aspect', 'event' ].includes( art.kind ) || 'sloppy';
|
|
180
182
|
}
|
|
181
183
|
|
|
184
|
+
/**
|
|
185
|
+
* @returns {boolean|string}
|
|
186
|
+
*/
|
|
182
187
|
function checkActionParamTypeRef( art ) {
|
|
183
188
|
return !(art.kind === 'entity' && art._service) && checkTypeRef( art );
|
|
184
189
|
}
|
|
185
190
|
|
|
191
|
+
/**
|
|
192
|
+
* @returns {boolean|string}
|
|
193
|
+
*/
|
|
186
194
|
function checkEventTypeRef( art ) {
|
|
187
195
|
return art.kind !== 'event' && checkActionParamTypeRef( art );
|
|
188
196
|
}
|
|
@@ -191,6 +199,9 @@ function fns( model ) {
|
|
|
191
199
|
return art.kind !== 'entity';
|
|
192
200
|
}
|
|
193
201
|
|
|
202
|
+
/**
|
|
203
|
+
* @returns {boolean|string}
|
|
204
|
+
*/
|
|
194
205
|
function checkTargetRef( art ) {
|
|
195
206
|
if (art.kind === 'entity' || art.kind === 'aspect')
|
|
196
207
|
return false;
|
package/lib/compiler/utils.js
CHANGED
|
@@ -30,6 +30,13 @@ function annotationIsFalse( anno ) { // falsy, but not null (u
|
|
|
30
30
|
return anno && (anno.val === false || anno.val === 0 || anno.val === '');
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* @param {XSN.Artifact} art
|
|
35
|
+
* @param {string} anno
|
|
36
|
+
* @param {XSN.Location} [location]
|
|
37
|
+
* @param {*} [val]
|
|
38
|
+
* @param {string} [literal]
|
|
39
|
+
*/
|
|
33
40
|
function annotateWith( art, anno, location = art.location, val = true, literal = 'boolean' ) {
|
|
34
41
|
if (art[anno]) // do not overwrite user-defined including null
|
|
35
42
|
return;
|