@sap/cds-compiler 4.0.2 → 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 +100 -5
- package/bin/cdsc.js +12 -12
- package/doc/CHANGELOG_BETA.md +11 -0
- package/lib/api/main.js +31 -11
- 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 +1 -1
- package/lib/edm/edm.js +4 -1
- package/lib/edm/edmPreprocessor.js +5 -4
- 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/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} +6 -6
- 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
package/lib/compiler/checks.js
CHANGED
|
@@ -1,33 +1,36 @@
|
|
|
1
|
-
// Checks on XSN performed during compile()
|
|
2
|
-
//
|
|
3
|
-
// TODO: to be reworked. It is not the intention to include more checks,
|
|
4
|
-
// rather the opposite. Therefore, this file will become smaller.
|
|
1
|
+
// Checks on XSN performed during compile() that are useful for the user
|
|
2
|
+
// but not necessary for the compiler to work.
|
|
5
3
|
|
|
6
|
-
// Major issues so far:
|
|
4
|
+
// TODO: Major issues so far:
|
|
7
5
|
// * Different ad-hoc value/type checks (associations, enum, ...) -
|
|
8
6
|
// specify a proper one and use consistently
|
|
9
7
|
// * Using name comparisons instead proper object comparisons.
|
|
10
8
|
// * effectiveType issues.
|
|
11
9
|
// * Often forgot to consider CSN input
|
|
12
|
-
// * Bad message texts/locations.
|
|
13
10
|
|
|
14
11
|
'use strict';
|
|
15
12
|
|
|
16
|
-
// const { hasArtifactTypeInformation } = require('../model/csnUtils')
|
|
17
13
|
const builtins = require('../compiler/builtins');
|
|
18
14
|
const {
|
|
19
15
|
forEachGeneric,
|
|
20
16
|
forEachDefinition,
|
|
21
17
|
forEachMember,
|
|
18
|
+
isBetaEnabled,
|
|
22
19
|
} = require('../base/model');
|
|
23
20
|
const { CompilerAssertion } = require('../base/error');
|
|
24
21
|
const { pathName } = require('./utils');
|
|
25
22
|
const { forEachMemberRecursively } = require('../model/csnUtils');
|
|
23
|
+
const { typeParameters } = require('./builtins');
|
|
26
24
|
const $location = Symbol.for('cds.$location');
|
|
27
25
|
|
|
28
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Run compiler checks on the given XSN model.
|
|
28
|
+
*
|
|
29
|
+
* @param {XSN.Model} model
|
|
30
|
+
*/
|
|
31
|
+
function check( model ) {
|
|
29
32
|
const {
|
|
30
|
-
error, warning,
|
|
33
|
+
error, warning, info,
|
|
31
34
|
} = model.$messageFunctions;
|
|
32
35
|
|
|
33
36
|
checkSapCommonLocale( model );
|
|
@@ -47,22 +50,24 @@ function check( model ) { // = XSN
|
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
function checkAnnotationDefinition( art ) {
|
|
53
|
+
// TODO: Should we check elements similar to definition-elements as well?
|
|
50
54
|
checkEnumType( art );
|
|
51
55
|
forEachMemberRecursively( art, (member) => {
|
|
52
56
|
if (member.localized?.val)
|
|
53
57
|
warning( 'def-unexpected-localized-anno', [ member.localized.location, member ] );
|
|
54
58
|
});
|
|
55
|
-
// TODO: Should we check elements similar to definition-elements as well?
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
function checkGenericConstruct( art ) {
|
|
59
62
|
checkName( art );
|
|
63
|
+
checkTypeArguments( art );
|
|
60
64
|
if (model.vocabularies) {
|
|
61
65
|
Object.keys( art )
|
|
62
|
-
.filter( a => a.startsWith('@') )
|
|
66
|
+
.filter( a => a.startsWith( '@' ) )
|
|
63
67
|
.forEach( a => checkAnnotationAssignment1( art, art[a] ) );
|
|
64
68
|
}
|
|
65
|
-
checkTypeStructure(art);
|
|
69
|
+
checkTypeStructure( art );
|
|
70
|
+
checkAssociation( art ); // type def could be assoc
|
|
66
71
|
if (art.kind === 'enum')
|
|
67
72
|
checkEnum( art );
|
|
68
73
|
checkEnumType( art );
|
|
@@ -75,56 +80,128 @@ function check( model ) { // = XSN
|
|
|
75
80
|
if (member.virtual?.val === true)
|
|
76
81
|
parentProps.virtual = member.virtual;
|
|
77
82
|
|
|
78
|
-
checkGenericConstruct(member);
|
|
83
|
+
checkGenericConstruct( member );
|
|
79
84
|
|
|
80
85
|
if (member.kind === 'element')
|
|
81
86
|
checkElement( member, parentProps );
|
|
82
87
|
|
|
83
|
-
forEachMember( member, m => checkMember(m, parentProps) );
|
|
88
|
+
forEachMember( member, m => checkMember( m, parentProps ) );
|
|
84
89
|
}
|
|
85
90
|
|
|
86
91
|
function checkVirtualKey( elem, parentProps ) {
|
|
87
|
-
const isKey = parentProps.key ||
|
|
88
|
-
const isVirtual = parentProps.virtual?.val ||
|
|
92
|
+
const isKey = parentProps.key?.val || elem.key?.val;
|
|
93
|
+
const isVirtual = parentProps.virtual?.val || elem.virtual?.val;
|
|
89
94
|
if (isKey && isVirtual) {
|
|
90
95
|
error('def-unexpected-key', [ isKey.location, elem ],
|
|
91
|
-
{ '#': 'virtual',
|
|
96
|
+
{ '#': 'virtual', art: elem.name.element, prop: 'key' });
|
|
92
97
|
}
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
function checkElement( elem, parentProps ) {
|
|
96
|
-
checkLocalizedSubElement(elem);
|
|
97
|
-
checkVirtualKey(elem, parentProps);
|
|
98
|
-
checkForUnmanagedAssociationsAsKey( elem, parentProps );
|
|
101
|
+
checkLocalizedSubElement( elem );
|
|
102
|
+
checkVirtualKey( elem, parentProps );
|
|
99
103
|
checkLocalizedElement( elem );
|
|
100
|
-
checkAssociation( elem );
|
|
101
104
|
|
|
102
105
|
if (elem.value) {
|
|
103
106
|
if (elem._main.query)
|
|
104
|
-
checkSelectItemValue(elem);
|
|
107
|
+
checkSelectItemValue( elem );
|
|
105
108
|
else if (elem.$syntax === 'calc')
|
|
106
109
|
checkCalculatedElementValue( elem );
|
|
107
110
|
}
|
|
108
111
|
|
|
109
|
-
checkCardinality(elem); // TODO: also for assoc types
|
|
112
|
+
checkCardinality( elem ); // TODO: also for assoc types
|
|
110
113
|
}
|
|
111
114
|
|
|
112
115
|
|
|
113
116
|
function checkName( construct ) { // TODO: move to define.js
|
|
114
117
|
if (model.options.$skipNameCheck)
|
|
115
118
|
return;
|
|
116
|
-
// TODO: Move a corrected version of this check to definer (but do not rely
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
// TODO: Move a corrected version of this check to definer (but do not rely on it!):
|
|
120
|
+
// The code below misses to consider CSN input!
|
|
121
|
+
// Maybe remove the check? But consider runtimes that rely on '.' as element separator.
|
|
122
|
+
if (construct.name.id?.includes( '.' )) {
|
|
120
123
|
error(null, [ construct.name.location, construct ], {},
|
|
121
124
|
'The character \'.\' is not allowed in identifiers');
|
|
122
125
|
}
|
|
123
126
|
}
|
|
124
127
|
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Check the type arguments on `art`, e.g. cds.Decimal can't have a `length`, structures
|
|
131
|
+
* can't have `precision`, etc.
|
|
132
|
+
*
|
|
133
|
+
* @param {XSN.Artifact} art
|
|
134
|
+
* @param {XSN.Artifact} user
|
|
135
|
+
*/
|
|
136
|
+
function checkTypeArguments( art, user = art ) {
|
|
137
|
+
if (art.builtin || art.kind === 'context' || art.kind === 'service')
|
|
138
|
+
return;
|
|
139
|
+
|
|
140
|
+
if (art.items)
|
|
141
|
+
checkTypeArguments( art.items, art );
|
|
142
|
+
|
|
143
|
+
const actualParams = typeParameters.list.filter(param => art[param] !== undefined);
|
|
144
|
+
if (actualParams.length === 0)
|
|
145
|
+
return;
|
|
146
|
+
|
|
147
|
+
const typeArt = art.type?._artifact || art;
|
|
148
|
+
|
|
149
|
+
// Note: `_effectiveType` points to `art` itself, if it is an enum type,
|
|
150
|
+
// descend to the origin in this case.
|
|
151
|
+
let effectiveType = typeArt._effectiveType;
|
|
152
|
+
while (effectiveType?.enum)
|
|
153
|
+
effectiveType = (effectiveType._origin || effectiveType.type?._artifact)?._effectiveType;
|
|
154
|
+
|
|
155
|
+
if (!effectiveType) {
|
|
156
|
+
return; // e.g. illegal definition references, cycles, ...
|
|
157
|
+
}
|
|
158
|
+
else if (!art.type && !effectiveType.type && !effectiveType?.builtin) {
|
|
159
|
+
error('type-missing-type', [ art.location, user ],
|
|
160
|
+
{ otherprop: 'type', prop: actualParams[0] },
|
|
161
|
+
'Missing $(OTHERPROP) property next to $(PROP)');
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const expectedParams = effectiveType.parameters &&
|
|
166
|
+
effectiveType.parameters.map(p => p.name || p) || [];
|
|
167
|
+
|
|
168
|
+
for (const param of actualParams) {
|
|
169
|
+
if (!expectedParams.includes(param)) {
|
|
170
|
+
// Whether the type ref itself is a builtin or a custom type with a builtin as base.
|
|
171
|
+
let variant;
|
|
172
|
+
if ((art.type?._artifact || art._effectiveType).builtin)
|
|
173
|
+
variant = 'builtin';
|
|
174
|
+
else if (effectiveType.builtin)
|
|
175
|
+
variant = 'type';
|
|
176
|
+
else // effectiveType is not a builtin -> array or structured
|
|
177
|
+
variant = 'non-scalar';
|
|
178
|
+
|
|
179
|
+
error('type-unexpected-argument', [ art[param].location, user ], {
|
|
180
|
+
'#': variant, prop: param, art: art.type || art._effectiveType, type: effectiveType,
|
|
181
|
+
});
|
|
182
|
+
break; // Avoid spam: Only emit the first error.
|
|
183
|
+
}
|
|
184
|
+
else if (!typeParameters.expectedLiteralsFor[param].includes(art[param].literal)) {
|
|
185
|
+
error('type-unexpected-argument', [ art[param].location, user ], {
|
|
186
|
+
'#': 'incorrect-type',
|
|
187
|
+
prop: param,
|
|
188
|
+
code: art[param].literal,
|
|
189
|
+
names: typeParameters.expectedLiteralsFor[param],
|
|
190
|
+
});
|
|
191
|
+
break; // Avoid spam: Only emit the first error.
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function requireExplicitTypeInSqlCast( art, user ) {
|
|
197
|
+
if (!art.type) {
|
|
198
|
+
error('expr-missing-type', [ art.location, user ], { },
|
|
199
|
+
'Missing type in SQL cast function');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
125
203
|
function checkLocalizedElement( elem ) {
|
|
126
|
-
|
|
127
|
-
if (elem.localized && elem.localized.val) {
|
|
204
|
+
if (elem.localized?.val) {
|
|
128
205
|
const type = elem._effectiveType;
|
|
129
206
|
// See discussion issue #6520: should we allow all scalar types?
|
|
130
207
|
if (!type || !type.builtin || type.category !== 'string') {
|
|
@@ -133,14 +210,14 @@ function check( model ) { // = XSN
|
|
|
133
210
|
'Expecting a string type in combination with keyword $(KEYWORD)');
|
|
134
211
|
}
|
|
135
212
|
}
|
|
136
|
-
|
|
213
|
+
|
|
137
214
|
// TODO: This check should be moved to localized.js
|
|
138
|
-
|
|
215
|
+
// "key" keyword at localized element in SELECT list.
|
|
216
|
+
if (elem.key?.val && elem._main?.query) {
|
|
139
217
|
// original element is localized but not key, as that would have
|
|
140
|
-
// already resulted in a warning
|
|
141
|
-
if (elem._origin
|
|
142
|
-
(
|
|
143
|
-
warning('localized-key', [ elem.key.location, elem ], { keyword: 'localized' },
|
|
218
|
+
// already resulted in a warning by localized.js
|
|
219
|
+
if (elem._origin?.localized?.val && !elem._origin.key?.val) {
|
|
220
|
+
warning('def-ignoring-localized', [ elem.key.location, elem ], { keyword: 'localized' },
|
|
144
221
|
'Keyword $(KEYWORD) is ignored for primary keys');
|
|
145
222
|
}
|
|
146
223
|
}
|
|
@@ -173,8 +250,6 @@ function check( model ) { // = XSN
|
|
|
173
250
|
}
|
|
174
251
|
}
|
|
175
252
|
|
|
176
|
-
// Individual checks -------------------------------------------------------
|
|
177
|
-
|
|
178
253
|
/**
|
|
179
254
|
* The enumNode is a single enum element and not the whole type.
|
|
180
255
|
*
|
|
@@ -190,7 +265,7 @@ function check( model ) { // = XSN
|
|
|
190
265
|
// Special handling to print a more detailed error message.
|
|
191
266
|
// Other cases like `null` as enum value are handled in `checkEnumValueType()`
|
|
192
267
|
if (type === 'enum') {
|
|
193
|
-
warning('
|
|
268
|
+
warning('ref-unexpected-enum', [ loc, enumNode ], {},
|
|
194
269
|
'References to other values are not allowed as enum values');
|
|
195
270
|
}
|
|
196
271
|
}
|
|
@@ -201,8 +276,7 @@ function check( model ) { // = XSN
|
|
|
201
276
|
enumNode = enumNode.enum ? enumNode : enumNode.items;
|
|
202
277
|
if (!enumNode || !enumNode.enum)
|
|
203
278
|
return;
|
|
204
|
-
const type = enumNode
|
|
205
|
-
enumNode.type._artifact._effectiveType;
|
|
279
|
+
const type = enumNode?.type?._artifact?._effectiveType;
|
|
206
280
|
|
|
207
281
|
// We can't distinguish (in CSN) between these two cases:
|
|
208
282
|
// type Base : String enum { b;a = 'abc'; };
|
|
@@ -212,25 +286,18 @@ function check( model ) { // = XSN
|
|
|
212
286
|
if (!type || type.enum)
|
|
213
287
|
return;
|
|
214
288
|
|
|
215
|
-
const name = type.name.absolute;
|
|
216
|
-
|
|
217
289
|
// All builtin types are allowed except binary and relational types.
|
|
218
290
|
// The latter are "internal" types.
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (isBinary)
|
|
225
|
-
typeclass = 'binary';
|
|
226
|
-
else if (builtins.isRelationTypeName(name))
|
|
227
|
-
typeclass = 'relation';
|
|
291
|
+
if (!type.builtin || type.internal || type.category === 'binary') {
|
|
292
|
+
let typeClass = 'std';
|
|
293
|
+
if (type.category === 'binary' || type.category === 'relation')
|
|
294
|
+
typeClass = type.category;
|
|
228
295
|
else if (type.elements)
|
|
229
|
-
|
|
296
|
+
typeClass = 'struct';
|
|
230
297
|
else if (type.items)
|
|
231
|
-
|
|
298
|
+
typeClass = 'items';
|
|
232
299
|
|
|
233
|
-
error('
|
|
300
|
+
error('type-invalid-enum', [ enumNode.type.location, enumNode ], { '#': typeClass }, {
|
|
234
301
|
std: 'Only builtin types are allowed as enums',
|
|
235
302
|
binary: 'Binary types are not allowed as enums',
|
|
236
303
|
relation: 'Relational types are not allowed as enums',
|
|
@@ -244,32 +311,32 @@ function check( model ) { // = XSN
|
|
|
244
311
|
}
|
|
245
312
|
|
|
246
313
|
/**
|
|
247
|
-
* Check the given enum's elements and their values. For example
|
|
314
|
+
* Check the given enum's elements and their values. For example,
|
|
248
315
|
* whether the value types are valid for the used enum type.
|
|
249
316
|
* `enumNode` can be also be `type.items` if the type is an arrayed enum.
|
|
250
317
|
*
|
|
251
318
|
* @param {XSN.Definition} enumNode
|
|
252
319
|
*/
|
|
253
320
|
function checkEnumValue( enumNode ) {
|
|
254
|
-
const type = enumNode.type
|
|
255
|
-
|
|
256
|
-
if (!enumNode.enum || !type || !type.builtin)
|
|
321
|
+
const type = enumNode.type?._artifact?._effectiveType;
|
|
322
|
+
if (!type || !enumNode.enum || !type.builtin)
|
|
257
323
|
return;
|
|
258
324
|
|
|
259
|
-
const isNumeric =
|
|
260
|
-
const isString =
|
|
325
|
+
const isNumeric = type.category === 'decimal' || type.category === 'integer';
|
|
326
|
+
const isString = type.category === 'string';
|
|
261
327
|
|
|
262
328
|
if (!isString) {
|
|
263
329
|
// Non-string enums MUST have a value as the value is only deducted for string types.
|
|
264
|
-
const emptyValue = Object.keys(enumNode.enum)
|
|
330
|
+
const emptyValue = Object.keys(enumNode.enum)
|
|
331
|
+
.find(name => !enumNode.enum[name].value);
|
|
265
332
|
if (emptyValue) {
|
|
266
333
|
const failedEnum = enumNode.enum[emptyValue];
|
|
267
|
-
warning('
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
334
|
+
warning('type-missing-value', [ failedEnum.location, failedEnum ], {
|
|
335
|
+
'#': isNumeric ? 'numeric' : 'std', name: emptyValue,
|
|
336
|
+
}, {
|
|
337
|
+
std: 'Missing value for non-string enum element $(NAME)',
|
|
338
|
+
numeric: 'Missing value for numeric enum element $(NAME)',
|
|
339
|
+
});
|
|
273
340
|
}
|
|
274
341
|
}
|
|
275
342
|
|
|
@@ -288,27 +355,29 @@ function check( model ) { // = XSN
|
|
|
288
355
|
|
|
289
356
|
for (const key of Object.keys(enumNode.enum)) {
|
|
290
357
|
const element = enumNode.enum[key];
|
|
291
|
-
if (
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
358
|
+
if (hasWrongType(element)) {
|
|
359
|
+
const actualType = element.value.literal;
|
|
360
|
+
warning('type-unexpected-value', [ element.value.location, element ], {
|
|
361
|
+
'#': expectedType, name: key, prop: actualType,
|
|
362
|
+
}, {
|
|
363
|
+
std: 'Incorrect value type $(PROP) for enum element $(NAME)', // Not used
|
|
364
|
+
number: 'Expected numeric value for enum element $(NAME) but was $(PROP)',
|
|
365
|
+
string: 'Expected string value for enum element $(NAME) but was $(PROP)',
|
|
366
|
+
});
|
|
367
|
+
}
|
|
301
368
|
}
|
|
302
369
|
}
|
|
303
370
|
|
|
304
|
-
// TODO: check inside compiler as it is a compiler restriction - improve
|
|
305
371
|
/**
|
|
372
|
+
* TODO: check inside compiler as it is a compiler restriction - improve
|
|
373
|
+
* TODO: Recursive check; use "parentProps" as other member checks
|
|
374
|
+
*
|
|
306
375
|
* Non-recursive check if sub-elements have a "localized" keyword since this is
|
|
307
376
|
* not yet supported.
|
|
308
377
|
*
|
|
309
378
|
* This check is not recursive to avoid a runtime overhead. Because of this it fails
|
|
310
379
|
* to detect scenarios with indirections, e.g.
|
|
311
|
-
*
|
|
380
|
+
* ```cds
|
|
312
381
|
* type L : localized String;
|
|
313
382
|
* type L1 : L;
|
|
314
383
|
* type L2 : L1;
|
|
@@ -318,6 +387,7 @@ function check( model ) { // = XSN
|
|
|
318
387
|
* subElement : L2;
|
|
319
388
|
* }
|
|
320
389
|
* }
|
|
390
|
+
* ```
|
|
321
391
|
*
|
|
322
392
|
* @param {XSN.Artifact} element
|
|
323
393
|
*/
|
|
@@ -325,8 +395,8 @@ function check( model ) { // = XSN
|
|
|
325
395
|
if (element._parent.kind !== 'element')
|
|
326
396
|
return;
|
|
327
397
|
|
|
328
|
-
const isLocalizedSubElement = element.localized
|
|
329
|
-
if (isLocalizedSubElement ||
|
|
398
|
+
const isLocalizedSubElement = element.localized?.val;
|
|
399
|
+
if (isLocalizedSubElement || element.type?._artifact?.localized?.val) {
|
|
330
400
|
const loc = isLocalizedSubElement ? element.localized.location : element.type.location;
|
|
331
401
|
warning('localized-sub-element', [ loc, element ],
|
|
332
402
|
{ type: element.type, '#': isLocalizedSubElement ? 'std' : 'type' },
|
|
@@ -335,61 +405,26 @@ function check( model ) { // = XSN
|
|
|
335
405
|
type: 'Keyword "localized" in type $(TYPE) is ignored for sub elements',
|
|
336
406
|
} );
|
|
337
407
|
}
|
|
338
|
-
return;
|
|
339
|
-
|
|
340
|
-
// TODO: Recursive check
|
|
341
|
-
function isTypeLocalized( type ) {
|
|
342
|
-
return (type && type.localized && type.localized.val);
|
|
343
|
-
}
|
|
344
408
|
}
|
|
345
409
|
|
|
346
410
|
/**
|
|
347
|
-
* Check that
|
|
348
|
-
* contains unmanaged associations
|
|
411
|
+
* Check that min and max cardinalities of 'art' have legal values
|
|
349
412
|
*
|
|
350
|
-
* TODO:
|
|
351
|
-
* long as the whole key is "closed", i.e., no ref in ON refers to element
|
|
352
|
-
* outside.
|
|
413
|
+
* TODO: move to define.js or parsers
|
|
353
414
|
*
|
|
354
|
-
* @param {
|
|
415
|
+
* @param {XSN.Artifact} art
|
|
355
416
|
*/
|
|
356
|
-
function
|
|
357
|
-
if (!
|
|
358
|
-
return;
|
|
359
|
-
if (element.targetAspect) {
|
|
360
|
-
// TODO: bad location / message
|
|
361
|
-
message('composition-as-key', [ parentProps.key.location, element ], {},
|
|
362
|
-
// TODO: give semantics when error downgraded
|
|
363
|
-
'Managed compositions can\'t be used as primary key');
|
|
364
|
-
}
|
|
365
|
-
else if (element.on) {
|
|
366
|
-
// TODO: bad location / message
|
|
367
|
-
message('unmanaged-as-key', [ parentProps.key.location, element ], {},
|
|
368
|
-
// TODO: give semantics when error downgraded
|
|
369
|
-
'Unmanaged associations can\'t be used as primary key');
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// Check that min and max cardinalities of 'elem' in 'art' have legal values
|
|
374
|
-
// TODO: move to define.js or parsers
|
|
375
|
-
function checkCardinality( elem ) {
|
|
376
|
-
if (!elem.cardinality)
|
|
417
|
+
function checkCardinality( art ) {
|
|
418
|
+
if (!art.cardinality)
|
|
377
419
|
return;
|
|
378
420
|
|
|
379
421
|
// Max cardinalities must be a positive number or '*'
|
|
380
422
|
for (const prop of [ 'sourceMax', 'targetMax' ]) {
|
|
381
|
-
if (
|
|
382
|
-
const { literal, val, location } =
|
|
383
|
-
if (!(literal === 'number' && val > 0 ||
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
// eslint-disable-next-line max-len
|
|
387
|
-
std: 'Value $(CODE) is invalid for maximum cardinality, expecting a positive number or $(NEWCODE)',
|
|
388
|
-
// eslint-disable-next-line max-len
|
|
389
|
-
sourceMax: 'Value $(CODE) is invalid for maximum source cardinality, expecting a positive number or $(NEWCODE)',
|
|
390
|
-
// eslint-disable-next-line max-len
|
|
391
|
-
targetMax: 'Value $(CODE) is invalid for maximum target cardinality, expecting a positive number or $(NEWCODE)',
|
|
392
|
-
});
|
|
423
|
+
if (art.cardinality[prop]) {
|
|
424
|
+
const { literal, val, location } = art.cardinality[prop];
|
|
425
|
+
if (!(literal === 'number' && val > 0 || literal === 'string' && val === '*')) {
|
|
426
|
+
error( 'type-invalid-cardinality', [ location, art ],
|
|
427
|
+
{ '#': prop, prop: val, otherprop: '*' } );
|
|
393
428
|
}
|
|
394
429
|
}
|
|
395
430
|
}
|
|
@@ -398,34 +433,24 @@ function check( model ) { // = XSN
|
|
|
398
433
|
// Note: Already checked by parser (syntax error if -1 is used) and
|
|
399
434
|
// from-csn.json (expected non-negative number)
|
|
400
435
|
for (const prop of [ 'sourceMin', 'targetMin' ]) {
|
|
401
|
-
if (
|
|
402
|
-
const { literal, val, location } =
|
|
403
|
-
if (!(literal === 'number' && val >= 0))
|
|
404
|
-
error('invalid-cardinality', [ location,
|
|
405
|
-
// eslint-disable-next-line max-len
|
|
406
|
-
std: 'Value $(CODE) is invalid for minimum cardinality, expecting a non-negative number',
|
|
407
|
-
// eslint-disable-next-line max-len
|
|
408
|
-
targetMin: 'Value $(CODE) is invalid for minimum target cardinality, expecting a non-negative number',
|
|
409
|
-
// eslint-disable-next-line max-len
|
|
410
|
-
sourceMin: 'Value $(CODE) is invalid for minimum source cardinality, expecting a non-negative number',
|
|
411
|
-
});
|
|
412
|
-
}
|
|
436
|
+
if (art.cardinality[prop]) {
|
|
437
|
+
const { literal, val, location } = art.cardinality[prop];
|
|
438
|
+
if (!(literal === 'number' && val >= 0))
|
|
439
|
+
error( 'type-invalid-cardinality', [ location, art ], { '#': prop, prop: val } );
|
|
413
440
|
}
|
|
414
441
|
}
|
|
415
442
|
|
|
416
443
|
// If provided, min cardinality must not exceed max cardinality (note that
|
|
417
444
|
// '*' is considered to be >= any number)
|
|
418
|
-
const pair = [
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
});
|
|
428
|
-
}
|
|
445
|
+
const pair = [
|
|
446
|
+
[ 'sourceMin', 'sourceMax', 'sourceVal' ],
|
|
447
|
+
[ 'targetMin', 'targetMax', 'targetVal' ],
|
|
448
|
+
];
|
|
449
|
+
pair.forEach(([ lhs, rhs, variant ]) => {
|
|
450
|
+
if (art.cardinality[lhs] && art.cardinality[rhs] &&
|
|
451
|
+
art.cardinality[rhs].literal === 'number' &&
|
|
452
|
+
art.cardinality[lhs].val > art.cardinality[rhs].val)
|
|
453
|
+
error( 'type-invalid-cardinality', [ art.cardinality.location, art ], { '#': variant } );
|
|
429
454
|
});
|
|
430
455
|
}
|
|
431
456
|
|
|
@@ -452,10 +477,14 @@ function check( model ) { // = XSN
|
|
|
452
477
|
}
|
|
453
478
|
|
|
454
479
|
function checkAssociation( elem ) {
|
|
480
|
+
if (!elem.target && !elem.targetAspect)
|
|
481
|
+
return;
|
|
455
482
|
// TODO: yes, a check similar to this could make it into the compiler)
|
|
456
483
|
// when virtual element is part of association
|
|
484
|
+
let fkCount = 0;
|
|
457
485
|
if (elem.foreignKeys) {
|
|
458
486
|
for (const k in elem.foreignKeys) {
|
|
487
|
+
++fkCount;
|
|
459
488
|
const key = elem.foreignKeys[k].targetElement;
|
|
460
489
|
if (key && isVirtualElement(key._artifact))
|
|
461
490
|
error('ref-unexpected-virtual', [ key.location, elem ], { '#': 'fkey' });
|
|
@@ -463,6 +492,30 @@ function check( model ) { // = XSN
|
|
|
463
492
|
error( 'ref-unexpected-calculated', [ key.location, elem ], { '#': 'fkey' } );
|
|
464
493
|
}
|
|
465
494
|
}
|
|
495
|
+
if (elem.default) {
|
|
496
|
+
if (!isBetaEnabled( model.options, 'associationDefault' )) {
|
|
497
|
+
error( 'type-unsupported-default', [ elem.default.location, elem ], {
|
|
498
|
+
'#': isComposition( model, elem ) ? 'comp' : 'std',
|
|
499
|
+
}, {
|
|
500
|
+
std: 'Unsupported default value on an association',
|
|
501
|
+
comp: 'Unsupported default value on a composition',
|
|
502
|
+
} );
|
|
503
|
+
}
|
|
504
|
+
else if (elem.targetAspect || elem.on || fkCount !== 1) {
|
|
505
|
+
const variant = (elem.targetAspect && 'targetAspect') || (elem.on && 'onCond') || 'multi';
|
|
506
|
+
error( 'type-unexpected-default', [ elem.default.location, elem ], {
|
|
507
|
+
'#': variant, keyword: 'default', count: fkCount,
|
|
508
|
+
} );
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
const fkName = Object.keys(elem.foreignKeys)[0];
|
|
512
|
+
if (elem.foreignKeys[fkName].targetElement._artifact?._effectiveType?.elements) {
|
|
513
|
+
error( 'type-unexpected-default', [ elem.default.location, elem ], {
|
|
514
|
+
'#': 'structured', keyword: 'default', name: fkName,
|
|
515
|
+
} );
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
466
519
|
|
|
467
520
|
checkOnCondition(elem);
|
|
468
521
|
}
|
|
@@ -473,8 +526,9 @@ function check( model ) { // = XSN
|
|
|
473
526
|
args[1] || op;
|
|
474
527
|
}
|
|
475
528
|
|
|
476
|
-
// A function like this could be part of the compiler
|
|
477
529
|
/**
|
|
530
|
+
* TODO: A function like this could be part of the compiler
|
|
531
|
+
*
|
|
478
532
|
* Check that the given type has no conflicts between its `type` property
|
|
479
533
|
* and its `elements` or `items` property. For example if `type` is not
|
|
480
534
|
* structured but the artifact has an `elements` property then the user
|
|
@@ -485,7 +539,7 @@ function check( model ) { // = XSN
|
|
|
485
539
|
function checkTypeStructure( artifact ) {
|
|
486
540
|
// Just a basic check. We do not check that the inner structure of `items`
|
|
487
541
|
// is the same as the type but only that all are arrayed or structured.
|
|
488
|
-
if (artifact.type
|
|
542
|
+
if (artifact.type?._artifact) {
|
|
489
543
|
const finalType = artifact.type._artifact._effectiveType || artifact.type._artifact;
|
|
490
544
|
|
|
491
545
|
if (artifact.items && !finalType.items) {
|
|
@@ -553,7 +607,7 @@ function check( model ) { // = XSN
|
|
|
553
607
|
if (elem === undefined) {
|
|
554
608
|
const loc = [ elements[$location], user ];
|
|
555
609
|
error('ref-invalid-override', loc, { '#': 'missing', id: user.name.id, name: element });
|
|
556
|
-
return false; // only report
|
|
610
|
+
return false; // only report once
|
|
557
611
|
}
|
|
558
612
|
else if (!checkElementOverride(elem, orig)) {
|
|
559
613
|
return false;
|
|
@@ -564,8 +618,6 @@ function check( model ) { // = XSN
|
|
|
564
618
|
}
|
|
565
619
|
|
|
566
620
|
|
|
567
|
-
// Former checkExpressions.js ----------------------------------------------
|
|
568
|
-
|
|
569
621
|
/**
|
|
570
622
|
* Check a generic expression (or condition) for semantic validity.
|
|
571
623
|
*
|
|
@@ -576,6 +628,10 @@ function check( model ) { // = XSN
|
|
|
576
628
|
function checkGenericExpression( xpr, user ) {
|
|
577
629
|
checkExpressionNotVirtual(xpr, user);
|
|
578
630
|
checkExpressionAssociationUsage(xpr, user, false);
|
|
631
|
+
if (xpr.op?.val === 'cast') {
|
|
632
|
+
requireExplicitTypeInSqlCast( xpr, user );
|
|
633
|
+
checkTypeArguments( xpr, user );
|
|
634
|
+
}
|
|
579
635
|
}
|
|
580
636
|
|
|
581
637
|
function checkExpressionNotVirtual( xpr, user ) {
|
|
@@ -589,6 +645,8 @@ function check( model ) { // = XSN
|
|
|
589
645
|
visitExpression(elem.on, elem, (xpr, user) => {
|
|
590
646
|
checkExpressionNotVirtual(xpr, user);
|
|
591
647
|
checkExpressionAssociationUsage(xpr, user, true);
|
|
648
|
+
// Essential check. Dependency handling for `on` conditions must change if
|
|
649
|
+
// this is allowed. See test3/Associations/Dependencies/.
|
|
592
650
|
if (xpr._artifact?.$syntax === 'calc' && !xpr._artifact.value.stored?.val)
|
|
593
651
|
error( 'ref-unexpected-calculated', [ xpr.location, user ], { '#': 'on' } );
|
|
594
652
|
});
|
|
@@ -600,11 +658,16 @@ function check( model ) { // = XSN
|
|
|
600
658
|
}
|
|
601
659
|
|
|
602
660
|
function checkSelectItemValue( elem ) {
|
|
603
|
-
checkExpressionAssociationUsage(elem.value, elem, false);
|
|
604
|
-
|
|
661
|
+
checkExpressionAssociationUsage( elem.value, elem, false );
|
|
662
|
+
// If a direct SQL-style cast() has no type, but type props, the compiler does not copy the type
|
|
663
|
+
// props/does not use the cast(). To avoid duplicate messages, only run this check if there is
|
|
664
|
+
// no explicit type, as otherwise we will check the cast() twice (once here, once via element).
|
|
665
|
+
if (elem.value?.op?.val === 'cast' && !elem.type) {
|
|
666
|
+
requireExplicitTypeInSqlCast( elem.value, elem );
|
|
667
|
+
checkTypeArguments( elem.value, elem );
|
|
668
|
+
}
|
|
605
669
|
visitSubExpression(elem.value, elem, (xpr) => {
|
|
606
|
-
|
|
607
|
-
checkExpressionAssociationUsage(xpr, elem, false);
|
|
670
|
+
checkGenericExpression( xpr, elem );
|
|
608
671
|
});
|
|
609
672
|
}
|
|
610
673
|
|
|
@@ -615,12 +678,16 @@ function check( model ) { // = XSN
|
|
|
615
678
|
checkExpressionNotVirtual(xpr, user);
|
|
616
679
|
// For inferred (e.g. included) calc elements, this error is already emitted at the origin.
|
|
617
680
|
// And users can't change structured to non-structured elements.
|
|
618
|
-
if (!elem.$inferred &&
|
|
681
|
+
if (!elem.$inferred && xpr._artifact._effectiveType?.elements) {
|
|
619
682
|
error('ref-unexpected-structured', [ sourceLoc, elem ], { '#': 'expr' } );
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
683
|
+
}
|
|
684
|
+
else if (xpr._artifact.target !== undefined) {
|
|
685
|
+
const variant = isComposition(model, xpr._artifact) ? 'expr-comp' : 'expr';
|
|
686
|
+
error('ref-unexpected-assoc', [ sourceLoc, elem ], { '#': variant });
|
|
687
|
+
}
|
|
688
|
+
else if (xpr._artifact.localized?.val && elem.value.stored?.val) {
|
|
623
689
|
error('ref-unexpected-localized', [ sourceLoc, elem ], { '#': 'calc' });
|
|
690
|
+
}
|
|
624
691
|
}
|
|
625
692
|
});
|
|
626
693
|
// Calculated elements must not refer to keys, because that may lead to another
|
|
@@ -676,13 +743,6 @@ function check( model ) { // = XSN
|
|
|
676
743
|
return false;
|
|
677
744
|
}
|
|
678
745
|
|
|
679
|
-
function isStructuredElement( elem ) {
|
|
680
|
-
// The effective type always points to something with elements _if_ the
|
|
681
|
-
// type is structured. But `elem` should already have `elements` if its
|
|
682
|
-
// structured due to element expansion.
|
|
683
|
-
return !!(elem?._effectiveType || elem)?.elements;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
746
|
/**
|
|
687
747
|
* Check a tree-like expression for semantic validity
|
|
688
748
|
*
|
|
@@ -721,11 +781,14 @@ function check( model ) { // = XSN
|
|
|
721
781
|
// Arg must not be an association and not $self
|
|
722
782
|
// Only if path is not approved exists path (that is non-query position)
|
|
723
783
|
if (arg.path && arg.$expected !== undefined) { // not 'approved-exists'
|
|
724
|
-
if (arg.$expected === 'exists')
|
|
725
|
-
|
|
784
|
+
if (arg.$expected === 'exists') {
|
|
785
|
+
const variant = isComposition(model, arg._artifact) ? 'expr-comp' : 'expr';
|
|
786
|
+
error('ref-unexpected-assoc', [ arg.location, user ], { '#': variant });
|
|
787
|
+
}
|
|
726
788
|
}
|
|
727
789
|
else if (!allowAssocTail && isAssociationOperand(arg)) {
|
|
728
|
-
|
|
790
|
+
const variant = isComposition(model, arg._artifact) ? 'expr-comp' : 'expr';
|
|
791
|
+
error('ref-unexpected-assoc', [ arg.location, user ], { '#': variant } );
|
|
729
792
|
}
|
|
730
793
|
|
|
731
794
|
if (isDollarSelfOrProjectionOperand(arg)) {
|
|
@@ -734,15 +797,12 @@ function check( model ) { // = XSN
|
|
|
734
797
|
}
|
|
735
798
|
}
|
|
736
799
|
|
|
737
|
-
|
|
800
|
+
/**
|
|
801
|
+
* Return true if 'arg' is an expression argument of type association or composition.
|
|
802
|
+
*/
|
|
738
803
|
function isAssociationOperand( arg ) {
|
|
739
|
-
if (!arg.path) {
|
|
740
|
-
// Not a path, hence not an association (literal, expression, function, whatever ...)
|
|
741
|
-
return false;
|
|
742
|
-
}
|
|
743
804
|
// If it has a target, it is an association or composition
|
|
744
|
-
return
|
|
745
|
-
(arg._artifact && arg._artifact._effectiveType && arg._artifact._effectiveType.target);
|
|
805
|
+
return !!arg._artifact?._effectiveType?.target;
|
|
746
806
|
}
|
|
747
807
|
|
|
748
808
|
/**
|
|
@@ -764,7 +824,6 @@ function check( model ) { // = XSN
|
|
|
764
824
|
if (!xpr.op || !xpr.args)
|
|
765
825
|
return false;
|
|
766
826
|
|
|
767
|
-
|
|
768
827
|
// One argument must be "$self" and the other an assoc
|
|
769
828
|
if (xpr.op.val === '=' && xpr.args.length === 2) {
|
|
770
829
|
// Tree-ish expression from the compiler (not augmented)
|
|
@@ -787,13 +846,13 @@ function check( model ) { // = XSN
|
|
|
787
846
|
// Check the annotation assignments (if any) of 'annotatable', possibly using annotation
|
|
788
847
|
// definitions from 'model'. Report errors on 'options.messages.
|
|
789
848
|
//
|
|
790
|
-
// TODO: rework completely
|
|
849
|
+
// TODO: rework completely!
|
|
791
850
|
|
|
792
851
|
// Has been slightly adapted for model.vocabularies but comments need to be
|
|
793
852
|
// adapted, etc.
|
|
794
853
|
function checkAnnotationAssignment1( art, anno ) {
|
|
795
854
|
// Sanity checks (ignore broken assignments)
|
|
796
|
-
if (!anno.name
|
|
855
|
+
if (!anno.name?.path?.length)
|
|
797
856
|
return;
|
|
798
857
|
// Annotation artifact for longest path step of annotation path
|
|
799
858
|
let fromArtifact = null;
|
|
@@ -827,7 +886,6 @@ function check( model ) { // = XSN
|
|
|
827
886
|
if (!annoDecl || annoDecl.artifacts && !elementDecl)
|
|
828
887
|
return;
|
|
829
888
|
|
|
830
|
-
|
|
831
889
|
// Must be an annotation if found
|
|
832
890
|
if (annoDecl.kind !== 'annotation') // i.e namespace
|
|
833
891
|
return;
|
|
@@ -847,7 +905,7 @@ function check( model ) { // = XSN
|
|
|
847
905
|
|
|
848
906
|
// Must have literal or path unless it is a boolean
|
|
849
907
|
if (!anno.literal && !anno.path && getFinalTypeNameOf(elementDecl) !== 'cds.Boolean') {
|
|
850
|
-
if (elementDecl.type
|
|
908
|
+
if (elementDecl.type?._artifact.name.absolute) {
|
|
851
909
|
warning('anno-expecting-value', [ anno.location || anno.name.location, art ],
|
|
852
910
|
{ '#': 'type', type: elementDecl.type._artifact });
|
|
853
911
|
}
|
|
@@ -868,7 +926,7 @@ function check( model ) { // = XSN
|
|
|
868
926
|
// if not
|
|
869
927
|
function checkValueAssignableTo( annoDef, value, elementDecl, art ) {
|
|
870
928
|
// FIXME: We currently do not have any element declaration that could match
|
|
871
|
-
//
|
|
929
|
+
// a 'path' value, so we simply leave those alone
|
|
872
930
|
if (value.path)
|
|
873
931
|
return;
|
|
874
932
|
|
|
@@ -900,6 +958,7 @@ function check( model ) { // = XSN
|
|
|
900
958
|
}
|
|
901
959
|
|
|
902
960
|
// Handle each (primitive) expected element type separately
|
|
961
|
+
// TODO: Don't rely on name; use actual type
|
|
903
962
|
const type = getFinalTypeNameOf(elementDecl);
|
|
904
963
|
if (builtins.isStringTypeName(type)) {
|
|
905
964
|
if (value.literal !== 'string' && value.literal !== 'enum' &&
|
|
@@ -978,8 +1037,7 @@ function check( model ) { // = XSN
|
|
|
978
1037
|
return null;
|
|
979
1038
|
}
|
|
980
1039
|
|
|
981
|
-
// TODO: remove
|
|
982
|
-
|
|
1040
|
+
// TODO: remove
|
|
983
1041
|
// Return the artifact (and possibly, its element) found by following 'path'
|
|
984
1042
|
// starting at 'from'. The return value is an object { artifact, endOfPath }
|
|
985
1043
|
// with 'artifact' being the last artifact encountered on 'path' (or
|
|
@@ -1004,6 +1062,7 @@ function check( model ) { // = XSN
|
|
|
1004
1062
|
return resolvePathFrom(path.slice(1), nextStepEnv[path[0].id], result);
|
|
1005
1063
|
}
|
|
1006
1064
|
|
|
1065
|
+
// TODO: remove
|
|
1007
1066
|
// Return the absolute name of the final type of 'node'. May return 'undefined'
|
|
1008
1067
|
// for anonymous types. DO NOT USE THIS function, it has several assumptions
|
|
1009
1068
|
// which are not necessarily true.
|
|
@@ -1016,7 +1075,7 @@ function check( model ) { // = XSN
|
|
|
1016
1075
|
}
|
|
1017
1076
|
|
|
1018
1077
|
/**
|
|
1019
|
-
* Ensure that the `locale` element of sap.common.TextsAspects
|
|
1078
|
+
* Ensure that the `locale` element of `sap.common.TextsAspects`
|
|
1020
1079
|
* is a string type. This is required by CAP runtimes to work properly.
|
|
1021
1080
|
*
|
|
1022
1081
|
* @param {XSN.Model} model
|
|
@@ -1026,11 +1085,10 @@ function checkSapCommonTextsAspects( model ) {
|
|
|
1026
1085
|
const locale = model.definitions[name]?.elements?.locale;
|
|
1027
1086
|
if (locale) {
|
|
1028
1087
|
// `locale` could also be `sap.common.Locale`, which must also be a string.
|
|
1029
|
-
|
|
1030
|
-
if (type?.name?.absolute !== 'cds.String') {
|
|
1088
|
+
if (locale._effectiveType !== model.definitions['cds.String']) {
|
|
1031
1089
|
const hasCommonLocale = !!model.definitions['sap.common.Locale'];
|
|
1032
1090
|
const { error } = model.$messageFunctions;
|
|
1033
|
-
error('def-invalid-element-type', [ locale.type.location, locale ], {
|
|
1091
|
+
error( 'def-invalid-element-type', [ locale.type.location, locale ], {
|
|
1034
1092
|
'#': hasCommonLocale ? 'texts-aspect-locale' : 'std',
|
|
1035
1093
|
art: name,
|
|
1036
1094
|
elemref: 'locale',
|
|
@@ -1050,12 +1108,11 @@ function checkSapCommonTextsAspects( model ) {
|
|
|
1050
1108
|
function checkSapCommonLocale( model ) {
|
|
1051
1109
|
const localeArt = model.definitions['sap.common.Locale'];
|
|
1052
1110
|
if (localeArt) {
|
|
1053
|
-
|
|
1054
|
-
if (type?.name?.absolute !== 'cds.String') {
|
|
1111
|
+
if (localeArt._effectiveType !== model.definitions['cds.String']) {
|
|
1055
1112
|
const { message } = model.$messageFunctions;
|
|
1056
|
-
message('type-expected-builtin', [ localeArt.name.location, localeArt ],
|
|
1057
|
-
|
|
1058
|
-
|
|
1113
|
+
message( 'type-expected-builtin', [ localeArt.name.location, localeArt ],
|
|
1114
|
+
{ name: 'sap.common.Locale' },
|
|
1115
|
+
'Expected $(NAME) to be a string type' );
|
|
1059
1116
|
}
|
|
1060
1117
|
}
|
|
1061
1118
|
}
|
|
@@ -1065,7 +1122,7 @@ function checkSapCommonLocale( model ) {
|
|
|
1065
1122
|
* Visits each expression.
|
|
1066
1123
|
*
|
|
1067
1124
|
* TODO: Properly visit expressions; will be improved step by step;
|
|
1068
|
-
* Currently only replaces old foreachPath().
|
|
1125
|
+
* Currently only replaces old foreachPath(), which had very poor performance.
|
|
1069
1126
|
*
|
|
1070
1127
|
* @param {any} xpr
|
|
1071
1128
|
* @param {XSN.Artifact} user
|
|
@@ -1104,5 +1161,25 @@ function visitSubExpression( xpr, user, callback ) {
|
|
|
1104
1161
|
}
|
|
1105
1162
|
}
|
|
1106
1163
|
|
|
1164
|
+
/**
|
|
1165
|
+
* Whether the given element is a composition.
|
|
1166
|
+
* TODO: `type T: Composition of E; entity V { e: T default 3 };`
|
|
1167
|
+
* See also getUnderlyingBuiltinType()/compositionTextVariant() in utils.js.
|
|
1168
|
+
*
|
|
1169
|
+
* @return {boolean}
|
|
1170
|
+
*/
|
|
1171
|
+
function isComposition( model, elem ) {
|
|
1172
|
+
elem = elem?._effectiveType;
|
|
1173
|
+
if (!elem || !elem.target)
|
|
1174
|
+
return false;
|
|
1175
|
+
do {
|
|
1176
|
+
if (elem.type?._artifact === model.definitions['cds.Composition'])
|
|
1177
|
+
return true;
|
|
1178
|
+
// Because inferred elements don't have a direct `type` property,
|
|
1179
|
+
// we need to go along the origin chain.
|
|
1180
|
+
elem = elem._origin;
|
|
1181
|
+
} while (elem);
|
|
1182
|
+
return false;
|
|
1183
|
+
}
|
|
1107
1184
|
|
|
1108
1185
|
module.exports = check;
|