@sap/cds-compiler 4.0.0 → 4.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 +115 -5
- package/bin/cdsc.js +12 -12
- package/doc/CHANGELOG_BETA.md +11 -0
- package/lib/api/main.js +60 -12
- package/lib/api/validate.js +1 -1
- package/lib/base/location.js +6 -7
- package/lib/base/message-registry.js +84 -38
- package/lib/base/messages.js +11 -10
- package/lib/base/model.js +6 -2
- package/lib/checks/defaultValues.js +6 -6
- package/lib/checks/foreignKeys.js +0 -5
- package/lib/checks/onConditions.js +17 -12
- package/lib/checks/queryNoDbArtifacts.js +132 -72
- package/lib/checks/sql-snippets.js +15 -4
- package/lib/checks/types.js +3 -3
- package/lib/checks/utils.js +1 -1
- package/lib/compiler/assert-consistency.js +44 -16
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +7 -8
- package/lib/compiler/checks.js +274 -197
- package/lib/compiler/classes.js +62 -0
- package/lib/compiler/cycle-detector.js +3 -3
- package/lib/compiler/define.js +63 -50
- package/lib/compiler/extend.js +38 -20
- package/lib/compiler/finalize-parse-cdl.js +2 -1
- package/lib/compiler/generate.js +0 -8
- package/lib/compiler/index.js +9 -7
- package/lib/compiler/kick-start.js +2 -0
- package/lib/compiler/populate.js +139 -110
- package/lib/compiler/propagator.js +4 -3
- package/lib/compiler/resolve.js +157 -126
- package/lib/compiler/shared.js +706 -404
- package/lib/compiler/tweak-assocs.js +21 -10
- package/lib/compiler/utils.js +228 -36
- package/lib/edm/annotations/genericTranslation.js +30 -2
- package/lib/edm/edm.js +4 -1
- package/lib/edm/edmPreprocessor.js +12 -5
- package/lib/edm/edmUtils.js +2 -4
- package/lib/gen/Dictionary.json +34 -10
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +3987 -3963
- package/lib/json/from-csn.js +43 -47
- package/lib/json/to-csn.js +11 -11
- package/lib/language/antlrParser.js +2 -1
- package/lib/language/genericAntlrParser.js +52 -43
- package/lib/language/language.g4 +59 -59
- package/lib/language/multiLineStringParser.js +2 -0
- package/lib/main.d.ts +5 -0
- package/lib/model/csnRefs.js +37 -19
- package/lib/model/csnUtils.js +20 -16
- package/lib/model/revealInternalProperties.js +29 -21
- package/lib/model/sortViews.js +4 -2
- package/lib/modelCompare/compare.js +112 -39
- package/lib/modelCompare/utils/filter.js +54 -24
- package/lib/optionProcessor.js +6 -6
- package/lib/render/manageConstraints.js +20 -17
- package/lib/render/toCdl.js +34 -20
- package/lib/render/toHdbcds.js +2 -2
- package/lib/render/toRename.js +4 -9
- package/lib/render/toSql.js +77 -26
- package/lib/render/utils/common.js +3 -3
- package/lib/render/utils/unique.js +52 -0
- package/lib/transform/db/applyTransformations.js +61 -20
- package/lib/transform/db/assertUnique.js +7 -8
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/cdsPersistence.js +8 -8
- package/lib/transform/db/expansion.js +17 -21
- package/lib/transform/db/flattening.js +23 -23
- package/lib/transform/db/rewriteCalculatedElements.js +20 -14
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/db/transformExists.js +8 -7
- package/lib/transform/db/views.js +73 -33
- package/lib/transform/draft/db.js +11 -9
- package/lib/transform/draft/odata.js +1 -1
- package/lib/transform/{forOdataNew.js → forOdata.js} +56 -42
- package/lib/transform/forRelationalDB.js +69 -75
- package/lib/transform/localized.js +6 -5
- package/lib/transform/odata/toFinalBaseType.js +3 -3
- package/lib/transform/{transformUtilsNew.js → transformUtils.js} +4 -101
- package/lib/transform/translateAssocsToJoins.js +14 -28
- package/package.json +1 -1
- package/share/messages/check-proper-type-of.md +1 -1
- package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
- package/share/messages/message-explanations.json +1 -1
|
@@ -1,136 +1,196 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { hasAnnotationValue, isPersistedOnDatabase, isBuiltinType } = require('../model/csnUtils');
|
|
4
|
+
const { requireForeignKeyAccess } = require('./onConditions');
|
|
5
|
+
const { pathId } = require('../model/csnRefs');
|
|
6
|
+
|
|
7
|
+
const generalQueryProperties = [ 'from', 'columns', 'where', 'groupBy', 'orderBy', 'having', 'limit' ];
|
|
8
|
+
|
|
4
9
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
10
|
+
* Ensure that all source artifacts and association targets are persisted on the database.
|
|
11
|
+
* Otherwise, we would end up with a JOIN against a non-existent table.
|
|
7
12
|
*
|
|
8
13
|
* Check the given query for:
|
|
9
|
-
* -
|
|
14
|
+
* - Association-traversal over skipped/abstract things
|
|
10
15
|
* - Associations (indirectly) using managed associations without foreign keys
|
|
11
16
|
*
|
|
12
17
|
* Currently checked:
|
|
13
|
-
* - "columns" for something like toF.
|
|
18
|
+
* - "columns" for something like toF.field, where F is skipped. But publishing toF is fine, will be ignored later on
|
|
14
19
|
* - "from" for something like "select from E.toF" where E, F or E AND F are no-db.
|
|
15
20
|
*
|
|
16
|
-
*
|
|
17
21
|
* @param {CSN.Query} query Query to check
|
|
18
22
|
*/
|
|
19
23
|
function checkQueryForNoDBArtifacts( query ) {
|
|
20
24
|
if (isPersistedOnDatabase(this.artifact) && !hasAnnotationValue(this.artifact, '@cds.persistence.table')) {
|
|
21
|
-
const generalQueryProperties = [ 'from', 'columns', 'where', 'groupBy', 'orderBy', 'having', 'limit' ];
|
|
22
25
|
for (const prop of generalQueryProperties) {
|
|
23
26
|
const queryPart = (query.SELECT || query.SET)[prop];
|
|
24
27
|
if (Array.isArray(queryPart)) {
|
|
25
28
|
for (const part of queryPart)
|
|
26
|
-
|
|
29
|
+
checkQueryRef.call(this, part, prop === 'columns');
|
|
27
30
|
}
|
|
28
31
|
else if (typeof queryPart === 'object') {
|
|
29
|
-
|
|
32
|
+
checkQueryRef.call(this, queryPart, prop === 'columns');
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
/**
|
|
36
|
-
*
|
|
39
|
+
* @param {CSN.Element} assoc Definition to check
|
|
40
|
+
* @returns {boolean} True, if there are any foreign keys.
|
|
41
|
+
*/
|
|
42
|
+
function hasForeignKeys( assoc ) {
|
|
43
|
+
if (!assoc || !assoc.keys)
|
|
44
|
+
return false;
|
|
45
|
+
return _hasForeignKeyOrElements.call(this, assoc);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Returns true if the given definition has at least one foreign key or element leaf node.
|
|
37
50
|
*
|
|
38
|
-
* @param {CSN.
|
|
39
|
-
* @returns {
|
|
51
|
+
* @param {CSN.Artifact} def
|
|
52
|
+
* @returns {boolean} True if there are FKs/element leaves.
|
|
40
53
|
*/
|
|
41
|
-
function
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return c;
|
|
45
|
-
if (def.elements) {
|
|
46
|
-
c += Object.values(def.elements).reduce((acc, e) => {
|
|
47
|
-
acc += leafCount.call(this, e);
|
|
48
|
-
return acc;
|
|
49
|
-
}, 0);
|
|
54
|
+
function _hasForeignKeyOrElements( def ) {
|
|
55
|
+
if (!def) {
|
|
56
|
+
return false;
|
|
50
57
|
}
|
|
51
58
|
else if (def.keys) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
59
|
+
return def.keys.some(e => _hasForeignKeyOrElements.call(this, e._art));
|
|
60
|
+
}
|
|
61
|
+
else if (def.elements) {
|
|
62
|
+
return Object.values(def.elements).some( e => _hasForeignKeyOrElements.call(this, e));
|
|
56
63
|
}
|
|
57
64
|
else if (def.type) {
|
|
58
65
|
if (isBuiltinType(def.type) && !(def.target))
|
|
59
|
-
return
|
|
60
|
-
|
|
66
|
+
return true;
|
|
67
|
+
return _hasForeignKeyOrElements.call(this, this.artifactRef(def.type, null));
|
|
61
68
|
}
|
|
62
|
-
return
|
|
69
|
+
return false;
|
|
63
70
|
}
|
|
64
71
|
|
|
65
72
|
/**
|
|
66
|
-
* Check the given ref for usage of skipped/abstract assoc targets
|
|
73
|
+
* Check the given `obj.ref` for usage of skipped/abstract assoc targets
|
|
67
74
|
*
|
|
68
|
-
* @param {
|
|
75
|
+
* @param {CSN.Column} obj CSN "thing" to check
|
|
69
76
|
* @param {boolean} inColumns True if the ref is part of a from
|
|
70
77
|
*/
|
|
71
|
-
function
|
|
72
|
-
if (!
|
|
78
|
+
function checkQueryRef( obj, inColumns ) {
|
|
79
|
+
if (!obj || obj.$scope === 'alias')
|
|
73
80
|
return;
|
|
74
81
|
|
|
75
|
-
|
|
82
|
+
if (obj.expand || obj.inline)
|
|
83
|
+
_checkExpandInline.call(this, obj);
|
|
84
|
+
|
|
85
|
+
else if (obj.ref && obj._links)
|
|
86
|
+
_checkRef.call(this, obj.ref, obj._links, obj.$path, inColumns);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Run _checkRef on all expand/inline structure leaf nodes.
|
|
91
|
+
* We do so by creating artificial paths that follow expand/inline nodes to their leaves.
|
|
92
|
+
*
|
|
93
|
+
* @param {CSN.Column} obj
|
|
94
|
+
* @param {CSN.Path} previousRefs
|
|
95
|
+
* @param {object[]} previousLinks
|
|
96
|
+
*/
|
|
97
|
+
function _checkExpandInline( obj, previousRefs = [], previousLinks = [] ) {
|
|
98
|
+
if (obj.ref && obj._links) { // There could be anonymous nested "expand".
|
|
99
|
+
previousRefs = previousRefs.concat(obj.ref);
|
|
100
|
+
previousLinks = previousLinks.concat(obj._links);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!obj.expand && !obj.inline) {
|
|
104
|
+
if (obj.ref && obj._links) {
|
|
105
|
+
// `inColumns: true` for expand/inline
|
|
106
|
+
_checkRef.call(this, previousRefs, previousLinks, obj.$path, true);
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (const col of obj.expand || obj.inline)
|
|
112
|
+
_checkExpandInline.call(this, col, previousRefs, previousLinks);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Implementation of checkQueryRef() that works on ref/links arrays instead of a column.
|
|
117
|
+
*
|
|
118
|
+
* @param {CSN.Path} ref
|
|
119
|
+
* @param {object[]} _links
|
|
120
|
+
* @param {CSN.Path} $path
|
|
121
|
+
* @param {boolean} inColumns
|
|
122
|
+
*/
|
|
123
|
+
function _checkRef( ref, _links, $path, inColumns ) {
|
|
124
|
+
if (!ref || !_links )
|
|
125
|
+
return;
|
|
126
|
+
|
|
127
|
+
let nonPersistedTarget = null;
|
|
128
|
+
const isPublishedAssoc = this.csnUtils.isAssocOrComposition(_links[_links.length - 1].art);
|
|
76
129
|
|
|
77
130
|
// Don't check the last element - to allow association publishing in columns
|
|
78
|
-
for (let i = 0; i < (inColumns ?
|
|
79
|
-
const link =
|
|
131
|
+
for (let i = 0; i < (inColumns ? _links.length - 1 : _links.length); i++) {
|
|
132
|
+
const link = _links[i];
|
|
80
133
|
if (!link)
|
|
81
134
|
continue;
|
|
82
|
-
|
|
83
135
|
const { art } = link;
|
|
84
136
|
if (!art)
|
|
85
137
|
continue;
|
|
86
138
|
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
139
|
+
const isLast = i >= _links.length - 1;
|
|
140
|
+
const isUnmanagedOrNoKeys = !art.keys;
|
|
141
|
+
const targetArt = art.target ? this.artifactRef(art.target) : art;
|
|
142
|
+
const pathStep = pathId(ref[i]);
|
|
143
|
+
const name = art.target || pathStep;
|
|
144
|
+
|
|
145
|
+
// If any path-step is not persisted, then all following path steps must only access foreign keys.
|
|
146
|
+
// For example, it could be toF.toG.field, where toG is FK of toF; the FK-only-check would succeed,
|
|
147
|
+
// but we only check "field" in the next iteration, where it is seen as access on a non-skipped
|
|
148
|
+
// entity, hence the need to store if any target is skipped.
|
|
149
|
+
if (!isPersistedOnDatabase(targetArt))
|
|
150
|
+
nonPersistedTarget = { name, pathStep };
|
|
151
|
+
|
|
152
|
+
if (nonPersistedTarget) {
|
|
153
|
+
let isJoinRelevant = isPublishedAssoc || // publishing associations is always join relevant
|
|
154
|
+
isLast || // e.g. FROM targets are always join relevant.
|
|
155
|
+
isUnmanagedOrNoKeys; // unmanaged associations are always join relevant -> no FKs
|
|
156
|
+
|
|
157
|
+
if (!isJoinRelevant) {
|
|
158
|
+
// for managed, published associations with more than one $path-step, only FK
|
|
159
|
+
// access is allowed.
|
|
160
|
+
requireForeignKeyAccess({ ref, _links }, i, () => {
|
|
161
|
+
isJoinRelevant = true;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (isJoinRelevant) {
|
|
166
|
+
const cdsPersistenceSkipped = hasAnnotationValue(targetArt, '@cds.persistence.skip');
|
|
167
|
+
this.error( null, $path, {
|
|
168
|
+
'#': cdsPersistenceSkipped ? 'std' : 'abstract',
|
|
169
|
+
id: nonPersistedTarget.pathStep,
|
|
170
|
+
elemref: { ref },
|
|
171
|
+
name: nonPersistedTarget.name,
|
|
111
172
|
}, {
|
|
112
173
|
std: 'Unexpected “@cds.persistence.skip” annotation on association target $(NAME) of $(ID) in path $(ELEMREF)',
|
|
113
174
|
abstract: 'Unexpected “abstract” association target $(NAME) of $(ID) in path $(ELEMREF)',
|
|
114
175
|
} );
|
|
176
|
+
break; // only one error per path
|
|
115
177
|
}
|
|
116
178
|
}
|
|
179
|
+
|
|
117
180
|
// check managed association to have foreign keys array filled
|
|
118
|
-
if (art.keys &&
|
|
119
|
-
this.error(null,
|
|
120
|
-
obj.$path,
|
|
121
|
-
{ id: pathStep, elemref: obj },
|
|
181
|
+
if (art.keys && !hasForeignKeys.call(this, art)) {
|
|
182
|
+
this.error(null, $path, { id: pathStep, elemref: { ref } },
|
|
122
183
|
'Path step $(ID) of $(ELEMREF) has no foreign keys');
|
|
184
|
+
break; // only one error per path
|
|
123
185
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (j < art.on.length - 2 && art.on[j].ref && art.on[j + 1] === '=' && art.on[j + 2].ref) {
|
|
186
|
+
else if (art.on) {
|
|
187
|
+
for (let j = 0; j < art.on.length - 2; j++) {
|
|
188
|
+
if (art.on[j].ref && art.on[j + 1] === '=' && art.on[j + 2].ref) {
|
|
128
189
|
const [ fwdAssoc, fwdPath ] = getForwardAssociation(pathStep, art.on[j], art.on[j + 2]);
|
|
129
|
-
if (fwdAssoc
|
|
130
|
-
this.error(null,
|
|
131
|
-
{ name: pathStep, elemref: obj, id: fwdPath },
|
|
190
|
+
if (fwdAssoc?.keys && !hasForeignKeys.call(this, fwdAssoc)) {
|
|
191
|
+
this.error(null, $path, { name: pathStep, elemref: { ref }, id: fwdPath },
|
|
132
192
|
'Path step $(NAME) of $(ELEMREF) is a $self comparison with $(ID) that has no foreign keys');
|
|
133
|
-
|
|
193
|
+
break; // only one error per path
|
|
134
194
|
}
|
|
135
195
|
}
|
|
136
196
|
}
|
|
@@ -139,7 +199,7 @@ function checkRef( obj, inColumns ) {
|
|
|
139
199
|
}
|
|
140
200
|
|
|
141
201
|
/**
|
|
142
|
-
* Get the forward association from a
|
|
202
|
+
* Get the forward association from a backlink $self association.
|
|
143
203
|
*
|
|
144
204
|
* @param {string} prefix Name of the association
|
|
145
205
|
* @param {object} lhs Left hand side of the on-condition part
|
|
@@ -18,14 +18,25 @@ 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 || this.artifact.projection)
|
|
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
|
+
}
|
|
24
|
+
else if (this.csnUtils.isStructured(member)) {
|
|
24
25
|
this.message('anno-invalid-sql-struct', path, { anno: 'sql.append' }, 'Annotation $(ANNO) can\'t be used on structured elements' );
|
|
25
|
-
|
|
26
|
+
}
|
|
27
|
+
else if (this.csnUtils.isManagedAssociation(member)) {
|
|
28
|
+
this.message('anno-invalid-sql-assoc', path, { anno: 'sql.append', '#': member.type }, {
|
|
29
|
+
std: 'Annotation $(ANNO) can\'t be used here',
|
|
30
|
+
'cds.Association': 'Annotation $(ANNO) can\'t be used on association elements',
|
|
31
|
+
'cds.Composition': 'Annotation $(ANNO) can\'t be used on composition elements',
|
|
32
|
+
} );
|
|
33
|
+
}
|
|
34
|
+
else if (member.value && !member.value.stored) {
|
|
26
35
|
this.message('anno-invalid-sql-calc', path, { anno: 'sql.append' }, 'Annotation $(ANNO) can\'t be used on calculated elements on read' );
|
|
27
|
-
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
28
38
|
checkValidAnnoValue(member, '@sql.append', path, this.error, this.options);
|
|
39
|
+
}
|
|
29
40
|
}
|
|
30
41
|
}
|
|
31
42
|
|
package/lib/checks/types.js
CHANGED
|
@@ -162,9 +162,9 @@ function errorAboutMissingType( error, path, artifact, name,
|
|
|
162
162
|
let variant = isElement ? 'elm' : 'std';
|
|
163
163
|
if (artifact.value?.stored)
|
|
164
164
|
variant = 'calc';
|
|
165
|
-
error('
|
|
166
|
-
std: '
|
|
167
|
-
elm: '
|
|
165
|
+
error('def-missing-type', path, { art: name, '#': variant }, {
|
|
166
|
+
std: 'Missing type for $(ART)',
|
|
167
|
+
elm: 'Missing type for element $(ART)',
|
|
168
168
|
calc: 'A stored calculated element must have a type',
|
|
169
169
|
});
|
|
170
170
|
}
|
package/lib/checks/utils.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { isBuiltinType } = require('../model/csnUtils');
|
|
4
|
-
const { RelationalOperators } = require('../transform/
|
|
4
|
+
const { RelationalOperators } = require('../transform/transformUtils');
|
|
5
5
|
/**
|
|
6
6
|
* Prepare the ref steps so that they are loggable
|
|
7
7
|
*
|
|
@@ -67,6 +67,9 @@
|
|
|
67
67
|
'use strict';
|
|
68
68
|
|
|
69
69
|
const { locationString, hasErrors } = require('../base/messages');
|
|
70
|
+
const {
|
|
71
|
+
XsnSource, XsnName, XsnArtifact, CsnLocation,
|
|
72
|
+
} = require('./classes');
|
|
70
73
|
|
|
71
74
|
|
|
72
75
|
// Properties that can appear where a type can have type arguments.
|
|
@@ -84,7 +87,8 @@ class InternalConsistencyError extends Error {
|
|
|
84
87
|
function assertConsistency( model, stage ) {
|
|
85
88
|
const stageParser = typeof stage === 'object';
|
|
86
89
|
const options = stageParser && stage || model.options || { testMode: true };
|
|
87
|
-
if (!options.testMode || options.
|
|
90
|
+
if (!options.testMode || options.testMode === '$noAssertConsistency' ||
|
|
91
|
+
options.parseOnly && !stageParser)
|
|
88
92
|
return;
|
|
89
93
|
|
|
90
94
|
const schema = {
|
|
@@ -105,6 +109,7 @@ function assertConsistency( model, stage ) {
|
|
|
105
109
|
'$blocks',
|
|
106
110
|
'$messageFunctions',
|
|
107
111
|
'$functions',
|
|
112
|
+
'$assert',
|
|
108
113
|
'_sortedSources',
|
|
109
114
|
],
|
|
110
115
|
},
|
|
@@ -122,12 +127,14 @@ function assertConsistency( model, stage ) {
|
|
|
122
127
|
'$withLocalized',
|
|
123
128
|
'$sources',
|
|
124
129
|
],
|
|
130
|
+
instanceOf: XsnSource,
|
|
125
131
|
},
|
|
126
132
|
location: {
|
|
127
133
|
// every thing with a $location in CSN must have a XSN location even
|
|
128
134
|
// with syntax errors (currently even internal artifacts like $using):
|
|
129
135
|
isRequired: parent => noSyntaxErrors() || parent && parent.kind,
|
|
130
136
|
kind: true,
|
|
137
|
+
instanceOf: CsnLocation,
|
|
131
138
|
requires: [ 'file' ], // line is optional in top-level location
|
|
132
139
|
optional: [ 'line', 'col', 'endLine', 'endCol', '$notFound' ],
|
|
133
140
|
schema: {
|
|
@@ -138,8 +145,8 @@ function assertConsistency( model, stage ) {
|
|
|
138
145
|
$notFound: { test: isBoolean },
|
|
139
146
|
},
|
|
140
147
|
},
|
|
141
|
-
sources: { test: isDictionary( isObject ) },
|
|
142
|
-
_sortedSources: { test: isArray( isObject ) },
|
|
148
|
+
sources: { test: isDictionary( isObject ), instanceOf: XsnSource },
|
|
149
|
+
_sortedSources: { test: isArray( isObject ), instanceOf: XsnSource },
|
|
143
150
|
file: { test: isString },
|
|
144
151
|
dirname: { test: isString }, // TODO: really necessary?
|
|
145
152
|
realname: { test: isString }, // TODO: really necessary?
|
|
@@ -158,11 +165,13 @@ function assertConsistency( model, stage ) {
|
|
|
158
165
|
test: isDictionary( definition ),
|
|
159
166
|
requires: [ 'kind', 'location', 'name' ],
|
|
160
167
|
optional: thoseWithKind,
|
|
168
|
+
instanceOf: XsnArtifact,
|
|
161
169
|
},
|
|
162
170
|
vocabularies: {
|
|
163
171
|
test: isDictionary( definition ),
|
|
164
172
|
requires: [ 'kind', 'name' ],
|
|
165
173
|
optional: thoseWithKind,
|
|
174
|
+
instanceOf: XsnArtifact,
|
|
166
175
|
},
|
|
167
176
|
extensions: {
|
|
168
177
|
kind: [ 'context' ], // syntax error (as opposed to HANA CDS), but still there
|
|
@@ -174,7 +183,7 @@ function assertConsistency( model, stage ) {
|
|
|
174
183
|
i18n: {
|
|
175
184
|
test: isDictionary( ( val, parent, prop, spec, lang ) => {
|
|
176
185
|
const textValueIsString = (v, p, textProp, s, textKey) => {
|
|
177
|
-
isString(v.val, p, textKey, s);
|
|
186
|
+
isString( v.val, p, textKey, s );
|
|
178
187
|
};
|
|
179
188
|
const innerDict = isDictionary( textValueIsString );
|
|
180
189
|
return innerDict( val, parent, lang, spec );
|
|
@@ -197,7 +206,7 @@ function assertConsistency( model, stage ) {
|
|
|
197
206
|
],
|
|
198
207
|
schema: {
|
|
199
208
|
kind: { test: isString, enum: [ 'builtin' ] },
|
|
200
|
-
name: { test: isObject, requires: [ 'id', 'element' ] },
|
|
209
|
+
name: { test: isObject, instanceOf: XsnName, requires: [ 'id', 'element' ] },
|
|
201
210
|
$autoElement: { test: isString },
|
|
202
211
|
$uncheckedElements: { test: isBoolean },
|
|
203
212
|
$requireElementAccess: { test: isBoolean },
|
|
@@ -240,7 +249,7 @@ function assertConsistency( model, stage ) {
|
|
|
240
249
|
typeProps$: { kind: true, enumerable: false, test: TODO },
|
|
241
250
|
actions: { kind: true, inherits: 'definitions' },
|
|
242
251
|
enum: { kind: true, inherits: 'definitions' },
|
|
243
|
-
foreignKeys: { kind: true, inherits: 'definitions' },
|
|
252
|
+
foreignKeys: { kind: true, inherits: 'definitions', instanceOf: 'ignore' },
|
|
244
253
|
$keysNavigation: { kind: true, test: TODO },
|
|
245
254
|
params: { kind: true, inherits: 'definitions' },
|
|
246
255
|
_extendType: { kind: true, test: TODO },
|
|
@@ -306,6 +315,7 @@ function assertConsistency( model, stage ) {
|
|
|
306
315
|
columns: {
|
|
307
316
|
kind: [ 'extend', '$column' ],
|
|
308
317
|
test: isArray( column ),
|
|
318
|
+
instanceOf: XsnArtifact,
|
|
309
319
|
optional: thoseWithKind,
|
|
310
320
|
enum: [ '*' ],
|
|
311
321
|
requires: [ 'location' ],
|
|
@@ -383,6 +393,7 @@ function assertConsistency( model, stage ) {
|
|
|
383
393
|
'select', '$join', 'mixin',
|
|
384
394
|
'source', 'namespace', 'using',
|
|
385
395
|
'$tableAlias', '$navElement',
|
|
396
|
+
'builtin', // magic variables
|
|
386
397
|
],
|
|
387
398
|
},
|
|
388
399
|
// locations of parentheses pairs around expression:
|
|
@@ -501,6 +512,7 @@ function assertConsistency( model, stage ) {
|
|
|
501
512
|
name: {
|
|
502
513
|
isRequired: stageParser && (() => false), // not required in parser
|
|
503
514
|
kind: true,
|
|
515
|
+
instanceOf: 'ignore', // TODO: XsnName,
|
|
504
516
|
schema: {
|
|
505
517
|
select: { test: TODO },
|
|
506
518
|
}, // TODO: rename query prop in name
|
|
@@ -527,6 +539,7 @@ function assertConsistency( model, stage ) {
|
|
|
527
539
|
kind: [ 'action', 'function' ],
|
|
528
540
|
requires: [ 'kind', 'location' ],
|
|
529
541
|
optional: thoseWithKind,
|
|
542
|
+
instanceOf: XsnArtifact,
|
|
530
543
|
},
|
|
531
544
|
items: {
|
|
532
545
|
kind: true,
|
|
@@ -686,6 +699,7 @@ function assertConsistency( model, stage ) {
|
|
|
686
699
|
$expected: { parser: true, test: isOneOf([ 'approved-exists', 'exists' ]) },
|
|
687
700
|
$messageFunctions: { test: TODO },
|
|
688
701
|
$functions: { test: TODO },
|
|
702
|
+
$assert: { test: TODO }, // currently just for missing Error[ref-cycle]
|
|
689
703
|
};
|
|
690
704
|
let _noSyntaxErrors = null;
|
|
691
705
|
assertProp( model, null, stageParser ? ':parser' : ':model', null, true );
|
|
@@ -714,8 +728,10 @@ function assertConsistency( model, stage ) {
|
|
|
714
728
|
: {}.propertyIsEnumerable.call( parent, prop ) !== enumerable)
|
|
715
729
|
throw new InternalConsistencyError( `Unexpected enumerability ${ !enumerable }${ at( [ node, parent ], prop ) }` );
|
|
716
730
|
}
|
|
717
|
-
(
|
|
718
|
-
|
|
731
|
+
if (node !== undefined) { // ignore if undefined
|
|
732
|
+
(spec.test || standard)( node, parent, prop, spec,
|
|
733
|
+
typeof noPropertyTest === 'string' && noPropertyTest );
|
|
734
|
+
}
|
|
719
735
|
}
|
|
720
736
|
|
|
721
737
|
function definition( node, parent, prop, spec, name ) {
|
|
@@ -779,10 +795,11 @@ function assertConsistency( model, stage ) {
|
|
|
779
795
|
const opt = Array.isArray(optional)
|
|
780
796
|
? optional.includes( n ) || optional.includes( n.charAt(0) )
|
|
781
797
|
: optional( n, spec );
|
|
782
|
-
if (
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
798
|
+
if (node[n] !== undefined) {
|
|
799
|
+
if (!(opt || requires.includes( n ) || n === '$extra'))
|
|
800
|
+
throw new InternalConsistencyError( `Property '${ n }' is not expected${ at( [ node[n], node, parent ], prop, name ) }` );
|
|
801
|
+
assertProp( node[n], node, n, spec.schema && spec.schema[n] );
|
|
802
|
+
}
|
|
786
803
|
}
|
|
787
804
|
}
|
|
788
805
|
|
|
@@ -921,7 +938,9 @@ function assertConsistency( model, stage ) {
|
|
|
921
938
|
const valSchema = { val: Object.assign( {}, spec, { test: func } ) };
|
|
922
939
|
const requires = [ 'val', 'location' ];
|
|
923
940
|
const optional = [ 'literal', '$inferred', '$priority', '_pathHead' ];
|
|
924
|
-
standard( node, parent, prop, {
|
|
941
|
+
standard( node, parent, prop, {
|
|
942
|
+
schema: valSchema, requires, optional, instanceOf: spec.instanceOf,
|
|
943
|
+
}, name );
|
|
925
944
|
};
|
|
926
945
|
}
|
|
927
946
|
|
|
@@ -948,7 +967,7 @@ function assertConsistency( model, stage ) {
|
|
|
948
967
|
|
|
949
968
|
function isString( node, parent, prop, spec ) {
|
|
950
969
|
if (typeof node !== 'string')
|
|
951
|
-
throw new InternalConsistencyError( `Expected string${ at( [ node, parent ], prop ) }` );
|
|
970
|
+
throw new InternalConsistencyError( `Expected string but found ${ typeof node }${ at( [ node, parent ], prop ) }` );
|
|
952
971
|
// TODO: also check getOwnPropertyNames(node)
|
|
953
972
|
if (spec.enum && !spec.enum.includes( node ))
|
|
954
973
|
throw new InternalConsistencyError( `Unexpected value '${ node }'${ at( [ node, parent ], prop ) }` );
|
|
@@ -962,10 +981,19 @@ function assertConsistency( model, stage ) {
|
|
|
962
981
|
}
|
|
963
982
|
|
|
964
983
|
function isObject( node, parent, prop, spec, name ) {
|
|
965
|
-
if (!node || typeof node !== 'object'
|
|
966
|
-
throw new InternalConsistencyError( `Expected
|
|
984
|
+
if (!node || typeof node !== 'object' )
|
|
985
|
+
throw new InternalConsistencyError( `Expected object${ at( [ null, parent ], prop, name ) }` );
|
|
986
|
+
const found = Object.getPrototypeOf( node )?.constructor?.name || 'null';
|
|
987
|
+
if (!spec.instanceOf && Object.getPrototypeOf( node ) !== Object.prototype)
|
|
988
|
+
throw new InternalConsistencyError( `Expected standard object but found ${ found }${ at( [ null, parent ], prop, name ) }` );
|
|
989
|
+
// TODO
|
|
990
|
+
// else if (spec.instanceOf && spec.instanceOf !== 'ignore' &&
|
|
991
|
+
// Object.getPrototypeOf( node ) !== spec.instanceOf.prototype)
|
|
992
|
+
// eslint-disable-next-line max-len
|
|
993
|
+
// throw new InternalConsistencyError( `Expected object of class ${ spec.instanceOf.name } but found ${ found }${ at( [ null, parent ], prop, name ) }` );
|
|
967
994
|
}
|
|
968
995
|
|
|
996
|
+
|
|
969
997
|
function inDefinitions( art, parent, prop, spec, name ) {
|
|
970
998
|
if (Array.isArray(art)) // do not check with redefinitions
|
|
971
999
|
return;
|
package/lib/compiler/base.js
CHANGED
package/lib/compiler/builtins.js
CHANGED
|
@@ -390,7 +390,7 @@ function isRelationTypeName( typeName ) {
|
|
|
390
390
|
* @returns {boolean}
|
|
391
391
|
*/
|
|
392
392
|
function isInReservedNamespace( absolute ) {
|
|
393
|
-
return absolute.startsWith( 'cds.') &&
|
|
393
|
+
return absolute === 'cds' || absolute.startsWith( 'cds.') &&
|
|
394
394
|
!absolute.match(/^cds\.foundation(\.|$)/) &&
|
|
395
395
|
!absolute.match(/^cds\.outbox(\.|$)/) && // Requested by Node runtime
|
|
396
396
|
!absolute.match(/^cds\.xt(\.|$)/); // Requested by Mtx
|
|
@@ -485,7 +485,10 @@ function initBuiltins( model ) {
|
|
|
485
485
|
for (const name in builtins) {
|
|
486
486
|
const magic = builtins[name];
|
|
487
487
|
// TODO: rename to $builtinFunction
|
|
488
|
-
const art = {
|
|
488
|
+
const art = {
|
|
489
|
+
kind: 'builtin',
|
|
490
|
+
name: { id: name, absolute: '', element: name },
|
|
491
|
+
};
|
|
489
492
|
artifacts[name] = art;
|
|
490
493
|
|
|
491
494
|
if (magic.$autoElement)
|
|
@@ -513,12 +516,8 @@ function initBuiltins( model ) {
|
|
|
513
516
|
|
|
514
517
|
for (const n of names) {
|
|
515
518
|
const magic = {
|
|
516
|
-
kind: 'builtin',
|
|
517
|
-
name: {
|
|
518
|
-
id: n,
|
|
519
|
-
absolute: art.name.absolute,
|
|
520
|
-
element: art.name.element ? `${ art.name.element }.${ n }` : n,
|
|
521
|
-
},
|
|
519
|
+
kind: 'builtin', // TODO: '$variable'
|
|
520
|
+
name: { id: n, absolute: '', element: `${ art.name.element }.${ n }` },
|
|
522
521
|
};
|
|
523
522
|
// Propagate this property so that it is available for sub-elements.
|
|
524
523
|
if (art.$uncheckedElements)
|