@sap/cds-compiler 4.1.2 → 4.2.4
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 +107 -1
- package/bin/cdsc.js +6 -3
- package/doc/CHANGELOG_BETA.md +5 -0
- package/doc/CHANGELOG_DEPRECATED.md +15 -0
- package/lib/api/main.js +2 -2
- package/lib/api/options.js +2 -2
- package/lib/api/validate.js +24 -24
- package/lib/base/message-registry.js +41 -6
- package/lib/base/messages.js +7 -0
- package/lib/base/model.js +38 -8
- package/lib/checks/elements.js +11 -10
- package/lib/checks/manyNavigations.js +33 -0
- package/lib/checks/onConditions.js +5 -2
- package/lib/checks/queryNoDbArtifacts.js +2 -3
- package/lib/checks/selectItems.js +4 -55
- package/lib/checks/utils.js +3 -2
- package/lib/checks/validator.js +3 -1
- package/lib/compiler/.eslintrc.json +2 -1
- package/lib/compiler/assert-consistency.js +27 -24
- package/lib/compiler/base.js +6 -2
- package/lib/compiler/builtins.js +34 -34
- package/lib/compiler/checks.js +179 -208
- package/lib/compiler/classes.js +2 -2
- package/lib/compiler/cycle-detector.js +6 -6
- package/lib/compiler/define.js +66 -45
- package/lib/compiler/extend.js +81 -72
- package/lib/compiler/finalize-parse-cdl.js +26 -26
- package/lib/compiler/generate.js +61 -45
- package/lib/compiler/index.js +47 -49
- package/lib/compiler/kick-start.js +8 -7
- package/lib/compiler/moduleLayers.js +1 -1
- package/lib/compiler/populate.js +42 -35
- package/lib/compiler/propagator.js +6 -6
- package/lib/compiler/resolve.js +170 -126
- package/lib/compiler/shared.js +122 -45
- package/lib/compiler/tweak-assocs.js +93 -40
- package/lib/compiler/utils.js +15 -12
- package/lib/edm/.eslintrc.json +40 -1
- package/lib/edm/annotations/genericTranslation.js +721 -707
- package/lib/edm/annotations/preprocessAnnotations.js +88 -77
- package/lib/edm/csn2edm.js +389 -378
- package/lib/edm/edm.js +678 -772
- package/lib/edm/edmAnnoPreprocessor.js +132 -146
- package/lib/edm/edmInboundChecks.js +29 -27
- package/lib/edm/edmPreprocessor.js +686 -646
- package/lib/edm/edmUtils.js +277 -296
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +1253 -1276
- package/lib/json/from-csn.js +34 -4
- package/lib/json/to-csn.js +4 -4
- package/lib/language/language.g4 +2 -5
- package/lib/main.d.ts +61 -1
- package/lib/model/csnUtils.js +31 -2
- package/lib/model/revealInternalProperties.js +1 -1
- package/lib/modelCompare/compare.js +37 -2
- package/lib/modelCompare/utils/filter.js +1 -1
- package/lib/optionProcessor.js +15 -3
- package/lib/render/toCdl.js +30 -4
- package/lib/render/toSql.js +5 -9
- package/lib/render/utils/common.js +8 -6
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/constraints.js +47 -17
- package/lib/transform/db/expansion.js +133 -50
- package/lib/transform/db/flattening.js +75 -7
- package/lib/transform/forOdata.js +4 -1
- package/lib/transform/forRelationalDB.js +80 -62
- package/lib/transform/localized.js +91 -54
- package/lib/transform/transformUtils.js +9 -10
- package/lib/utils/file.js +7 -7
- package/lib/utils/moduleResolve.js +210 -121
- package/lib/utils/objectUtils.js +1 -1
- package/package.json +5 -5
package/lib/compiler/checks.js
CHANGED
|
@@ -15,13 +15,14 @@ const {
|
|
|
15
15
|
forEachGeneric,
|
|
16
16
|
forEachDefinition,
|
|
17
17
|
forEachMember,
|
|
18
|
+
forEachMemberRecursively,
|
|
18
19
|
isBetaEnabled,
|
|
19
20
|
} = require('../base/model');
|
|
20
21
|
const { CompilerAssertion } = require('../base/error');
|
|
21
22
|
const { pathName } = require('./utils');
|
|
22
|
-
const { forEachMemberRecursively } = require('../model/csnUtils');
|
|
23
23
|
const { typeParameters } = require('./builtins');
|
|
24
|
-
|
|
24
|
+
|
|
25
|
+
const $location = Symbol.for( 'cds.$location' );
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* Run compiler checks on the given XSN model.
|
|
@@ -38,13 +39,14 @@ function check( model ) {
|
|
|
38
39
|
|
|
39
40
|
forEachDefinition( model, checkDefinition );
|
|
40
41
|
forEachGeneric( model, 'vocabularies', checkAnnotationDefinition );
|
|
42
|
+
|
|
41
43
|
return;
|
|
42
44
|
|
|
43
45
|
function checkDefinition( def ) {
|
|
44
46
|
checkGenericConstruct( def );
|
|
45
47
|
if (def.includes && def.elements)
|
|
46
48
|
checkElementIncludeOverride( def );
|
|
47
|
-
forEachMember( def, member => checkMember(member) );
|
|
49
|
+
forEachMember( def, member => checkMember( member ) );
|
|
48
50
|
if (def.$queries)
|
|
49
51
|
def.$queries.forEach( checkQuery );
|
|
50
52
|
}
|
|
@@ -55,7 +57,7 @@ function check( model ) {
|
|
|
55
57
|
forEachMemberRecursively( art, (member) => {
|
|
56
58
|
if (member.localized?.val)
|
|
57
59
|
warning( 'def-unexpected-localized-anno', [ member.localized.location, member ] );
|
|
58
|
-
});
|
|
60
|
+
} );
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
function checkGenericConstruct( art ) {
|
|
@@ -92,8 +94,8 @@ function check( model ) {
|
|
|
92
94
|
const isKey = parentProps.key?.val || elem.key?.val;
|
|
93
95
|
const isVirtual = parentProps.virtual?.val || elem.virtual?.val;
|
|
94
96
|
if (isKey && isVirtual) {
|
|
95
|
-
error('def-unexpected-key', [ isKey.location, elem ],
|
|
96
|
-
|
|
97
|
+
error( 'def-unexpected-key', [ isKey.location, elem ],
|
|
98
|
+
{ '#': 'virtual', art: elem.name.element, prop: 'key' } );
|
|
97
99
|
}
|
|
98
100
|
}
|
|
99
101
|
|
|
@@ -119,9 +121,11 @@ function check( model ) {
|
|
|
119
121
|
// TODO: Move a corrected version of this check to definer (but do not rely on it!):
|
|
120
122
|
// The code below misses to consider CSN input!
|
|
121
123
|
// Maybe remove the check? But consider runtimes that rely on '.' as element separator.
|
|
122
|
-
if (construct.
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
if (construct.kind === 'element' || construct.kind === 'action' || construct.kind === 'param') {
|
|
125
|
+
if (construct.name.id?.includes( '.' )) {
|
|
126
|
+
error( null, [ construct.name.location, construct ], {},
|
|
127
|
+
'The character \'.\' is not allowed in identifiers' );
|
|
128
|
+
}
|
|
125
129
|
}
|
|
126
130
|
}
|
|
127
131
|
|
|
@@ -140,7 +144,7 @@ function check( model ) {
|
|
|
140
144
|
if (art.items)
|
|
141
145
|
checkTypeArguments( art.items, art );
|
|
142
146
|
|
|
143
|
-
const actualParams = typeParameters.list.filter(param => art[param] !== undefined);
|
|
147
|
+
const actualParams = typeParameters.list.filter( param => art[param] !== undefined );
|
|
144
148
|
if (actualParams.length === 0)
|
|
145
149
|
return;
|
|
146
150
|
|
|
@@ -156,17 +160,17 @@ function check( model ) {
|
|
|
156
160
|
return; // e.g. illegal definition references, cycles, ...
|
|
157
161
|
}
|
|
158
162
|
else if (!art.type && !effectiveType.type && !effectiveType?.builtin) {
|
|
159
|
-
error('type-missing-type', [ art.location, user ],
|
|
160
|
-
|
|
161
|
-
|
|
163
|
+
error( 'type-missing-type', [ art.location, user ],
|
|
164
|
+
{ otherprop: 'type', prop: actualParams[0] },
|
|
165
|
+
'Missing $(OTHERPROP) property next to $(PROP)' );
|
|
162
166
|
return;
|
|
163
167
|
}
|
|
164
168
|
|
|
165
169
|
const expectedParams = effectiveType.parameters &&
|
|
166
|
-
effectiveType.parameters.map(p => p.name || p) || [];
|
|
170
|
+
effectiveType.parameters.map( p => p.name || p ) || [];
|
|
167
171
|
|
|
168
172
|
for (const param of actualParams) {
|
|
169
|
-
if (!expectedParams.includes(param)) {
|
|
173
|
+
if (!expectedParams.includes( param )) {
|
|
170
174
|
// Whether the type ref itself is a builtin or a custom type with a builtin as base.
|
|
171
175
|
let variant;
|
|
172
176
|
if ((art.type?._artifact || art._effectiveType).builtin)
|
|
@@ -176,18 +180,18 @@ function check( model ) {
|
|
|
176
180
|
else // effectiveType is not a builtin -> array or structured
|
|
177
181
|
variant = 'non-scalar';
|
|
178
182
|
|
|
179
|
-
error('type-unexpected-argument', [ art[param].location, user ], {
|
|
183
|
+
error( 'type-unexpected-argument', [ art[param].location, user ], {
|
|
180
184
|
'#': variant, prop: param, art: art.type || art._effectiveType, type: effectiveType,
|
|
181
|
-
});
|
|
185
|
+
} );
|
|
182
186
|
break; // Avoid spam: Only emit the first error.
|
|
183
187
|
}
|
|
184
|
-
else if (!typeParameters.expectedLiteralsFor[param].includes(art[param].literal)) {
|
|
185
|
-
error('type-unexpected-argument', [ art[param].location, user ], {
|
|
188
|
+
else if (!typeParameters.expectedLiteralsFor[param].includes( art[param].literal )) {
|
|
189
|
+
error( 'type-unexpected-argument', [ art[param].location, user ], {
|
|
186
190
|
'#': 'incorrect-type',
|
|
187
191
|
prop: param,
|
|
188
192
|
code: art[param].literal,
|
|
189
193
|
names: typeParameters.expectedLiteralsFor[param],
|
|
190
|
-
});
|
|
194
|
+
} );
|
|
191
195
|
break; // Avoid spam: Only emit the first error.
|
|
192
196
|
}
|
|
193
197
|
}
|
|
@@ -195,8 +199,8 @@ function check( model ) {
|
|
|
195
199
|
|
|
196
200
|
function requireExplicitTypeInSqlCast( art, user ) {
|
|
197
201
|
if (!art.type) {
|
|
198
|
-
error('expr-missing-type', [ art.location, user ], { },
|
|
199
|
-
|
|
202
|
+
error( 'expr-missing-type', [ art.location, user ], { },
|
|
203
|
+
'Missing type in SQL cast function' );
|
|
200
204
|
}
|
|
201
205
|
}
|
|
202
206
|
|
|
@@ -205,9 +209,9 @@ function check( model ) {
|
|
|
205
209
|
const type = elem._effectiveType;
|
|
206
210
|
// See discussion issue #6520: should we allow all scalar types?
|
|
207
211
|
if (!type || !type.builtin || type.category !== 'string') {
|
|
208
|
-
info('ref-expecting-localized-string', [ elem.type?.location, elem ],
|
|
209
|
-
|
|
210
|
-
|
|
212
|
+
info( 'ref-expecting-localized-string', [ elem.type?.location, elem ],
|
|
213
|
+
{ keyword: 'localized' },
|
|
214
|
+
'Expecting a string type in combination with keyword $(KEYWORD)' );
|
|
211
215
|
}
|
|
212
216
|
}
|
|
213
217
|
|
|
@@ -217,36 +221,35 @@ function check( model ) {
|
|
|
217
221
|
// original element is localized but not key, as that would have
|
|
218
222
|
// already resulted in a warning by localized.js
|
|
219
223
|
if (elem._origin?.localized?.val && !elem._origin.key?.val) {
|
|
220
|
-
warning('def-ignoring-localized', [ elem.key.location, elem ], { keyword: 'localized' },
|
|
221
|
-
|
|
224
|
+
warning( 'def-ignoring-localized', [ elem.key.location, elem ], { keyword: 'localized' },
|
|
225
|
+
'Keyword $(KEYWORD) is ignored for primary keys' );
|
|
222
226
|
}
|
|
223
227
|
}
|
|
224
228
|
}
|
|
225
229
|
|
|
226
230
|
function checkQuery( query ) {
|
|
227
|
-
checkNoUnmanagedAssocsInGroupByOrderBy( query );
|
|
228
231
|
// TODO: check too simple (just one source), as most of those in this file
|
|
229
232
|
// Check expressions in the various places where they may occur
|
|
230
233
|
if (query.from)
|
|
231
|
-
visitSubExpression(query.from, query, checkGenericExpression);
|
|
234
|
+
visitSubExpression( query.from, query, checkGenericExpression );
|
|
232
235
|
|
|
233
236
|
if (query.where)
|
|
234
|
-
visitExpression(query.where, query, checkGenericExpression);
|
|
237
|
+
visitExpression( query.where, query, checkGenericExpression );
|
|
235
238
|
|
|
236
239
|
if (query.groupBy) {
|
|
237
240
|
for (const groupByEntry of query.groupBy)
|
|
238
|
-
visitExpression(groupByEntry, query, checkGenericExpression);
|
|
241
|
+
visitExpression( groupByEntry, query, checkGenericExpression );
|
|
239
242
|
}
|
|
240
243
|
if (query.having)
|
|
241
|
-
visitExpression(query.having, query, checkGenericExpression);
|
|
244
|
+
visitExpression( query.having, query, checkGenericExpression );
|
|
242
245
|
|
|
243
246
|
if (query.orderBy) {
|
|
244
247
|
for (const orderByEntry of query.orderBy)
|
|
245
|
-
visitExpression(orderByEntry, query, checkGenericExpression);
|
|
248
|
+
visitExpression( orderByEntry, query, checkGenericExpression );
|
|
246
249
|
}
|
|
247
250
|
if (query.mixin) {
|
|
248
251
|
for (const mixinName in query.mixin)
|
|
249
|
-
checkAssociation(query.mixin[mixinName]);
|
|
252
|
+
checkAssociation( query.mixin[mixinName] );
|
|
250
253
|
}
|
|
251
254
|
}
|
|
252
255
|
|
|
@@ -265,8 +268,8 @@ function check( model ) {
|
|
|
265
268
|
// Special handling to print a more detailed error message.
|
|
266
269
|
// Other cases like `null` as enum value are handled in `checkEnumValueType()`
|
|
267
270
|
if (type === 'enum') {
|
|
268
|
-
warning('ref-unexpected-enum', [ loc, enumNode ], {},
|
|
269
|
-
|
|
271
|
+
warning( 'ref-unexpected-enum', [ loc, enumNode ], {},
|
|
272
|
+
'References to other values are not allowed as enum values' );
|
|
270
273
|
}
|
|
271
274
|
}
|
|
272
275
|
|
|
@@ -297,17 +300,17 @@ function check( model ) {
|
|
|
297
300
|
else if (type.items)
|
|
298
301
|
typeClass = 'items';
|
|
299
302
|
|
|
300
|
-
error('type-invalid-enum', [ enumNode.type.location, enumNode ], { '#': typeClass }, {
|
|
303
|
+
error( 'type-invalid-enum', [ enumNode.type.location, enumNode ], { '#': typeClass }, {
|
|
301
304
|
std: 'Only builtin types are allowed as enums',
|
|
302
305
|
binary: 'Binary types are not allowed as enums',
|
|
303
306
|
relation: 'Relational types are not allowed as enums',
|
|
304
307
|
struct: 'Structured types are not allowed as enums',
|
|
305
308
|
items: 'Arrayed types are not allowed as enums',
|
|
306
|
-
});
|
|
309
|
+
} );
|
|
307
310
|
return;
|
|
308
311
|
}
|
|
309
312
|
|
|
310
|
-
checkEnumValue(enumNode);
|
|
313
|
+
checkEnumValue( enumNode );
|
|
311
314
|
}
|
|
312
315
|
|
|
313
316
|
/**
|
|
@@ -327,16 +330,16 @@ function check( model ) {
|
|
|
327
330
|
|
|
328
331
|
if (!isString) {
|
|
329
332
|
// Non-string enums MUST have a value as the value is only deducted for string types.
|
|
330
|
-
const emptyValue = Object.keys(enumNode.enum)
|
|
331
|
-
.find(name => !enumNode.enum[name].value);
|
|
333
|
+
const emptyValue = Object.keys( enumNode.enum )
|
|
334
|
+
.find( name => !enumNode.enum[name].value );
|
|
332
335
|
if (emptyValue) {
|
|
333
336
|
const failedEnum = enumNode.enum[emptyValue];
|
|
334
|
-
warning('type-missing-value', [ failedEnum.location, failedEnum ], {
|
|
337
|
+
warning( 'type-missing-value', [ failedEnum.location, failedEnum ], {
|
|
335
338
|
'#': isNumeric ? 'numeric' : 'std', name: emptyValue,
|
|
336
339
|
}, {
|
|
337
340
|
std: 'Missing value for non-string enum element $(NAME)',
|
|
338
341
|
numeric: 'Missing value for numeric enum element $(NAME)',
|
|
339
|
-
});
|
|
342
|
+
} );
|
|
340
343
|
}
|
|
341
344
|
}
|
|
342
345
|
|
|
@@ -353,17 +356,17 @@ function check( model ) {
|
|
|
353
356
|
(element.value.literal !== expectedType) &&
|
|
354
357
|
(element.value.literal !== 'enum');
|
|
355
358
|
|
|
356
|
-
for (const key of Object.keys(enumNode.enum)) {
|
|
359
|
+
for (const key of Object.keys( enumNode.enum )) {
|
|
357
360
|
const element = enumNode.enum[key];
|
|
358
|
-
if (hasWrongType(element)) {
|
|
361
|
+
if (hasWrongType( element )) {
|
|
359
362
|
const actualType = element.value.literal;
|
|
360
|
-
warning('type-unexpected-value', [ element.value.location, element ], {
|
|
361
|
-
'#': expectedType, name: key, prop: actualType,
|
|
363
|
+
warning( 'type-unexpected-value', [ element.value.location, element ], {
|
|
364
|
+
'#': expectedType, name: key, prop: actualType || 'unknown',
|
|
362
365
|
}, {
|
|
363
366
|
std: 'Incorrect value type $(PROP) for enum element $(NAME)', // Not used
|
|
364
367
|
number: 'Expected numeric value for enum element $(NAME) but was $(PROP)',
|
|
365
368
|
string: 'Expected string value for enum element $(NAME) but was $(PROP)',
|
|
366
|
-
});
|
|
369
|
+
} );
|
|
367
370
|
}
|
|
368
371
|
}
|
|
369
372
|
}
|
|
@@ -392,18 +395,18 @@ function check( model ) {
|
|
|
392
395
|
* @param {XSN.Artifact} element
|
|
393
396
|
*/
|
|
394
397
|
function checkLocalizedSubElement( element ) {
|
|
395
|
-
if (element._parent
|
|
398
|
+
if (element._parent?.kind !== 'element')
|
|
396
399
|
return;
|
|
397
400
|
|
|
398
401
|
const isLocalizedSubElement = element.localized?.val;
|
|
399
402
|
if (isLocalizedSubElement || element.type?._artifact?.localized?.val) {
|
|
400
403
|
const loc = isLocalizedSubElement ? element.localized.location : element.type.location;
|
|
401
|
-
warning('localized-sub-element', [ loc, element ],
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
404
|
+
warning( 'localized-sub-element', [ loc, element ],
|
|
405
|
+
{ type: element.type, '#': isLocalizedSubElement ? 'std' : 'type' },
|
|
406
|
+
{
|
|
407
|
+
std: 'Keyword "localized" is ignored for sub elements',
|
|
408
|
+
type: 'Keyword "localized" in type $(TYPE) is ignored for sub elements',
|
|
409
|
+
} );
|
|
407
410
|
}
|
|
408
411
|
}
|
|
409
412
|
|
|
@@ -429,51 +432,18 @@ function check( model ) {
|
|
|
429
432
|
}
|
|
430
433
|
}
|
|
431
434
|
|
|
432
|
-
// Min cardinality must be a non-negative number
|
|
433
|
-
// Note: Already checked by parser (syntax error if -1 is used) and
|
|
434
|
-
// from-csn.json (expected non-negative number)
|
|
435
|
-
for (const prop of [ 'sourceMin', 'targetMin' ]) {
|
|
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 } );
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
435
|
// If provided, min cardinality must not exceed max cardinality (note that
|
|
444
436
|
// '*' is considered to be >= any number)
|
|
445
437
|
const pair = [
|
|
446
438
|
[ 'sourceMin', 'sourceMax', 'sourceVal' ],
|
|
447
439
|
[ 'targetMin', 'targetMax', 'targetVal' ],
|
|
448
440
|
];
|
|
449
|
-
pair.forEach(([ lhs, rhs, variant ]) => {
|
|
441
|
+
pair.forEach( ([ lhs, rhs, variant ]) => {
|
|
450
442
|
if (art.cardinality[lhs] && art.cardinality[rhs] &&
|
|
451
443
|
art.cardinality[rhs].literal === 'number' &&
|
|
452
444
|
art.cardinality[lhs].val > art.cardinality[rhs].val)
|
|
453
445
|
error( 'type-invalid-cardinality', [ art.cardinality.location, art ], { '#': variant } );
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
// TODO: make this part of the name resolution in the compiler
|
|
458
|
-
// Check that queries in 'art' do not contain unmanaged associations in GROUP BY or ORDER BY
|
|
459
|
-
function checkNoUnmanagedAssocsInGroupByOrderBy( query ) {
|
|
460
|
-
const art = query._main; // TODO - remove, use query for semantic location
|
|
461
|
-
for (const groupByEntry of query.groupBy || []) {
|
|
462
|
-
if (groupByEntry._artifact && groupByEntry._artifact._effectiveType &&
|
|
463
|
-
groupByEntry._artifact._effectiveType.on) {
|
|
464
|
-
// Unmanaged association - complain
|
|
465
|
-
error(null, [ groupByEntry.location, art ], {},
|
|
466
|
-
'Unmanaged associations are not allowed in GROUP BY');
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
for (const orderByEntry of query.orderBy || []) {
|
|
470
|
-
if (orderByEntry._artifact && orderByEntry._artifact._effectiveType &&
|
|
471
|
-
orderByEntry._artifact._effectiveType.on) {
|
|
472
|
-
// Unmanaged association - complain
|
|
473
|
-
error(null, [ orderByEntry.location, art ], {},
|
|
474
|
-
'Unmanaged associations are not allowed in ORDER BY');
|
|
475
|
-
}
|
|
476
|
-
}
|
|
446
|
+
} );
|
|
477
447
|
}
|
|
478
448
|
|
|
479
449
|
function checkAssociation( elem ) {
|
|
@@ -486,8 +456,8 @@ function check( model ) {
|
|
|
486
456
|
for (const k in elem.foreignKeys) {
|
|
487
457
|
++fkCount;
|
|
488
458
|
const key = elem.foreignKeys[k].targetElement;
|
|
489
|
-
if (key && isVirtualElement(key._artifact))
|
|
490
|
-
error('ref-unexpected-virtual', [ key.location, elem ], { '#': 'fkey' });
|
|
459
|
+
if (key && isVirtualElement( key._artifact ))
|
|
460
|
+
error( 'ref-unexpected-virtual', [ key.location, elem ], { '#': 'fkey' } );
|
|
491
461
|
else if (key._artifact?.$syntax === 'calc' && !key._artifact.value.stored?.val)
|
|
492
462
|
error( 'ref-unexpected-calculated', [ key.location, elem ], { '#': 'fkey' } );
|
|
493
463
|
}
|
|
@@ -508,7 +478,7 @@ function check( model ) {
|
|
|
508
478
|
} );
|
|
509
479
|
}
|
|
510
480
|
else {
|
|
511
|
-
const fkName = Object.keys(elem.foreignKeys)[0];
|
|
481
|
+
const fkName = Object.keys( elem.foreignKeys )[0];
|
|
512
482
|
if (elem.foreignKeys[fkName].targetElement._artifact?._effectiveType?.elements) {
|
|
513
483
|
error( 'type-unexpected-default', [ elem.default.location, elem ], {
|
|
514
484
|
'#': 'structured', keyword: 'default', name: fkName,
|
|
@@ -517,7 +487,7 @@ function check( model ) {
|
|
|
517
487
|
}
|
|
518
488
|
}
|
|
519
489
|
|
|
520
|
-
checkOnCondition(elem);
|
|
490
|
+
checkOnCondition( elem );
|
|
521
491
|
}
|
|
522
492
|
|
|
523
493
|
function getBinaryOp( cond ) {
|
|
@@ -543,18 +513,18 @@ function check( model ) {
|
|
|
543
513
|
const finalType = artifact.type._artifact._effectiveType || artifact.type._artifact;
|
|
544
514
|
|
|
545
515
|
if (artifact.items && !finalType.items) {
|
|
546
|
-
warning('type-items-mismatch', [ artifact.type.location, artifact ],
|
|
547
|
-
|
|
548
|
-
|
|
516
|
+
warning( 'type-items-mismatch', [ artifact.type.location, artifact ],
|
|
517
|
+
{ type: artifact.type, prop: 'items' },
|
|
518
|
+
'Used type $(TYPE) is not arrayed and conflicts with $(PROP) property' );
|
|
549
519
|
}
|
|
550
520
|
else if (artifact.elements && !finalType.elements) {
|
|
551
|
-
warning('type-elements-mismatch', [ artifact.type.location, artifact ],
|
|
552
|
-
|
|
553
|
-
|
|
521
|
+
warning( 'type-elements-mismatch', [ artifact.type.location, artifact ],
|
|
522
|
+
{ type: artifact.type, prop: 'elements' },
|
|
523
|
+
'Used type $(TYPE) is not structured and conflicts with $(PROP) property' );
|
|
554
524
|
}
|
|
555
525
|
}
|
|
556
526
|
if (artifact.items)
|
|
557
|
-
checkTypeStructure(artifact.items);
|
|
527
|
+
checkTypeStructure( artifact.items );
|
|
558
528
|
}
|
|
559
529
|
|
|
560
530
|
/**
|
|
@@ -571,7 +541,7 @@ function check( model ) {
|
|
|
571
541
|
if (element.$inferred !== 'include') {
|
|
572
542
|
for (const include of def.includes) {
|
|
573
543
|
if (include._artifact?.elements?.[name] !== undefined)
|
|
574
|
-
checkElementOverride( element, include._artifact.elements[name]);
|
|
544
|
+
checkElementOverride( element, include._artifact.elements[name] );
|
|
575
545
|
}
|
|
576
546
|
}
|
|
577
547
|
}
|
|
@@ -586,12 +556,12 @@ function check( model ) {
|
|
|
586
556
|
const name = elem.name.id;
|
|
587
557
|
// Position at type/struct, not name
|
|
588
558
|
const loc = elem.type?.location || elem.elements?.[$location] || elem.location;
|
|
589
|
-
error('ref-invalid-override', [ loc, elem ],
|
|
590
|
-
|
|
559
|
+
error( 'ref-invalid-override', [ loc, elem ],
|
|
560
|
+
{ '#': prop, art: original._main, name } );
|
|
591
561
|
return false;
|
|
592
562
|
}
|
|
593
563
|
else if (original.elements &&
|
|
594
|
-
!checkSubStructureOverride(elem, elem.elements, original.elements)) {
|
|
564
|
+
!checkSubStructureOverride( elem, elem.elements, original.elements )) {
|
|
595
565
|
return false;
|
|
596
566
|
}
|
|
597
567
|
return true;
|
|
@@ -606,10 +576,10 @@ function check( model ) {
|
|
|
606
576
|
const orig = originals[element];
|
|
607
577
|
if (elem === undefined) {
|
|
608
578
|
const loc = [ elements[$location], user ];
|
|
609
|
-
error('ref-invalid-override', loc, { '#': 'missing', id: user.name.id, name: element });
|
|
579
|
+
error( 'ref-invalid-override', loc, { '#': 'missing', id: user.name.id, name: element } );
|
|
610
580
|
return false; // only report once
|
|
611
581
|
}
|
|
612
|
-
else if (!checkElementOverride(elem, orig)) {
|
|
582
|
+
else if (!checkElementOverride( elem, orig )) {
|
|
613
583
|
return false;
|
|
614
584
|
}
|
|
615
585
|
}
|
|
@@ -626,8 +596,8 @@ function check( model ) {
|
|
|
626
596
|
* @returns {void}
|
|
627
597
|
*/
|
|
628
598
|
function checkGenericExpression( xpr, user ) {
|
|
629
|
-
checkExpressionNotVirtual(xpr, user);
|
|
630
|
-
checkExpressionAssociationUsage(xpr, user, false);
|
|
599
|
+
checkExpressionNotVirtual( xpr, user );
|
|
600
|
+
checkExpressionAssociationUsage( xpr, user, false );
|
|
631
601
|
if (xpr.op?.val === 'cast') {
|
|
632
602
|
requireExplicitTypeInSqlCast( xpr, user );
|
|
633
603
|
checkTypeArguments( xpr, user );
|
|
@@ -635,25 +605,24 @@ function check( model ) {
|
|
|
635
605
|
}
|
|
636
606
|
|
|
637
607
|
function checkExpressionNotVirtual( xpr, user ) {
|
|
638
|
-
if (xpr._artifact && isVirtualElement(xpr._artifact))
|
|
639
|
-
error('ref-unexpected-virtual', [ xpr.location, user ], { '#': 'expr' });
|
|
608
|
+
if (xpr._artifact && isVirtualElement( xpr._artifact ))
|
|
609
|
+
error( 'ref-unexpected-virtual', [ xpr.location, user ], { '#': 'expr' } );
|
|
640
610
|
}
|
|
641
611
|
|
|
642
612
|
function checkOnCondition( elem ) {
|
|
613
|
+
if (elem.$inferred === 'localized')
|
|
614
|
+
return; // ignore
|
|
615
|
+
|
|
643
616
|
// TODO: Move to checkAssociation
|
|
644
617
|
if (elem.on && !elem.on.$inferred) {
|
|
645
|
-
visitExpression(elem.on, elem, (xpr, user) => {
|
|
646
|
-
checkExpressionNotVirtual(xpr, user);
|
|
647
|
-
checkExpressionAssociationUsage(xpr, user, true);
|
|
618
|
+
visitExpression( elem.on, elem, (xpr, user) => {
|
|
619
|
+
checkExpressionNotVirtual( xpr, user );
|
|
620
|
+
checkExpressionAssociationUsage( xpr, user, true );
|
|
648
621
|
// Essential check. Dependency handling for `on` conditions must change if
|
|
649
622
|
// this is allowed. See test3/Associations/Dependencies/.
|
|
650
623
|
if (xpr._artifact?.$syntax === 'calc' && !xpr._artifact.value.stored?.val)
|
|
651
624
|
error( 'ref-unexpected-calculated', [ xpr.location, user ], { '#': 'on' } );
|
|
652
|
-
});
|
|
653
|
-
if (isDollarSelfOrProjectionOperand(elem.on)) {
|
|
654
|
-
// Bare $self usages are not allowed and don't work in A2J.
|
|
655
|
-
error('expr-missing-comparison', [ elem.on.location, elem ], { id: '$self', op: '=' } );
|
|
656
|
-
}
|
|
625
|
+
} );
|
|
657
626
|
}
|
|
658
627
|
}
|
|
659
628
|
|
|
@@ -666,37 +635,41 @@ function check( model ) {
|
|
|
666
635
|
requireExplicitTypeInSqlCast( elem.value, elem );
|
|
667
636
|
checkTypeArguments( elem.value, elem );
|
|
668
637
|
}
|
|
669
|
-
visitSubExpression(elem.value, elem, (xpr) => {
|
|
638
|
+
visitSubExpression( elem.value, elem, (xpr) => {
|
|
670
639
|
checkGenericExpression( xpr, elem );
|
|
671
|
-
});
|
|
640
|
+
} );
|
|
672
641
|
}
|
|
673
642
|
|
|
674
643
|
function checkCalculatedElementValue( elem ) {
|
|
675
|
-
visitExpression(elem.value, elem, (xpr, user) => {
|
|
676
|
-
|
|
644
|
+
visitExpression( elem.value, elem, (xpr, user) => {
|
|
645
|
+
// We only need to check artifact references. To avoid false positives and conflicts
|
|
646
|
+
// with $self comparison-checks, ignore bare $self.
|
|
647
|
+
const isArtRef = xpr._artifact && !(xpr.path?.length === 1 &&
|
|
648
|
+
xpr.path[0]._navigation?.kind === '$self');
|
|
649
|
+
if (isArtRef) {
|
|
677
650
|
const sourceLoc = xpr.path?.[xpr.path.length - 1].location || xpr.location;
|
|
678
|
-
checkExpressionNotVirtual(xpr, user);
|
|
651
|
+
checkExpressionNotVirtual( xpr, user );
|
|
679
652
|
// For inferred (e.g. included) calc elements, this error is already emitted at the origin.
|
|
680
653
|
// And users can't change structured to non-structured elements.
|
|
681
654
|
if (!elem.$inferred && xpr._artifact._effectiveType?.elements) {
|
|
682
|
-
error('ref-unexpected-structured', [ sourceLoc, elem ], { '#': 'expr' } );
|
|
655
|
+
error( 'ref-unexpected-structured', [ sourceLoc, elem ], { '#': 'expr' } );
|
|
683
656
|
}
|
|
684
657
|
else if (xpr._artifact.target !== undefined) {
|
|
685
|
-
const variant = isComposition(model, xpr._artifact) ? 'expr-comp' : 'expr';
|
|
686
|
-
error('ref-unexpected-assoc', [ sourceLoc, elem ], { '#': variant });
|
|
658
|
+
const variant = isComposition( model, xpr._artifact ) ? 'expr-comp' : 'expr';
|
|
659
|
+
error( 'ref-unexpected-assoc', [ sourceLoc, elem ], { '#': variant } );
|
|
687
660
|
}
|
|
688
661
|
else if (xpr._artifact.localized?.val && elem.value.stored?.val) {
|
|
689
|
-
error('ref-unexpected-localized', [ sourceLoc, elem ], { '#': 'calc' });
|
|
662
|
+
error( 'ref-unexpected-localized', [ sourceLoc, elem ], { '#': 'calc' } );
|
|
690
663
|
}
|
|
691
664
|
}
|
|
692
|
-
});
|
|
665
|
+
} );
|
|
693
666
|
// Calculated elements must not refer to keys, because that may lead to another
|
|
694
667
|
// key in an SQL view, which is missing in OData (for on-read).
|
|
695
668
|
// Following associations does not lead to this issue.
|
|
696
|
-
if (elem.value.path && isKeyElement(elem.value._artifact) &&
|
|
697
|
-
!followsAnAssociation(elem.value.path)) {
|
|
698
|
-
error('ref-unexpected-key', [ elem.value.location, elem ], {},
|
|
699
|
-
|
|
669
|
+
if (elem.value.path && isKeyElement( elem.value._artifact ) &&
|
|
670
|
+
!followsAnAssociation( elem.value.path )) {
|
|
671
|
+
error( 'ref-unexpected-key', [ elem.value.location, elem ], {},
|
|
672
|
+
'Calculated elements can\'t refer directly to key elements' );
|
|
700
673
|
}
|
|
701
674
|
}
|
|
702
675
|
|
|
@@ -758,21 +731,15 @@ function check( model ) {
|
|
|
758
731
|
// Only check associations and $self if this is not a backlink-like
|
|
759
732
|
// expression (a comparison of $self with an assoc).
|
|
760
733
|
// We don't check token-stream-like 'xpr's.
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
const op = getBinaryOp(xpr);
|
|
734
|
+
const args = Array.isArray( xpr.args ) ? xpr.args : Object.values( xpr.args );
|
|
735
|
+
const isNotSelfComparison = args.length > 0 && xpr.op?.val !== 'xpr' &&
|
|
736
|
+
!isBinaryDollarSelfComparisonWithAssoc( xpr );
|
|
765
737
|
|
|
766
738
|
if (isNotSelfComparison) {
|
|
739
|
+
const op = getBinaryOp( xpr );
|
|
767
740
|
for (const arg of args) {
|
|
768
|
-
if (op?.val !== '=' && isDollarSelfOrProjectionOperand(arg))
|
|
769
|
-
|
|
770
|
-
const loc = (op?.location.endLine ? op : arg).location;
|
|
771
|
-
error('expr-invalid-operator', [ loc, user ], { op: '=', id: '$self' });
|
|
772
|
-
}
|
|
773
|
-
else {
|
|
774
|
-
checkExpressionIsNotAssocOrSelf(arg, user, allowAssocTail);
|
|
775
|
-
}
|
|
741
|
+
if (arg && !(op?.val !== '=' && isDollarSelfOrProjectionOperand( arg )))
|
|
742
|
+
checkExpressionIsNotAssocOrSelf( arg, user, allowAssocTail );
|
|
776
743
|
}
|
|
777
744
|
}
|
|
778
745
|
}
|
|
@@ -782,18 +749,13 @@ function check( model ) {
|
|
|
782
749
|
// Only if path is not approved exists path (that is non-query position)
|
|
783
750
|
if (arg.path && arg.$expected !== undefined) { // not 'approved-exists'
|
|
784
751
|
if (arg.$expected === 'exists') {
|
|
785
|
-
const variant = isComposition(model, arg._artifact) ? 'expr-comp' : 'expr';
|
|
786
|
-
error('ref-unexpected-assoc', [ arg.location, user ], { '#': variant });
|
|
752
|
+
const variant = isComposition( model, arg._artifact ) ? 'expr-comp' : 'expr';
|
|
753
|
+
error( 'ref-unexpected-assoc', [ arg.location, user ], { '#': variant } );
|
|
787
754
|
}
|
|
788
755
|
}
|
|
789
|
-
else if (!allowAssocTail && isAssociationOperand(arg)) {
|
|
790
|
-
const variant = isComposition(model, arg._artifact) ? 'expr-comp' : 'expr';
|
|
791
|
-
error('ref-unexpected-assoc', [ arg.location, user ], { '#': variant } );
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
if (isDollarSelfOrProjectionOperand(arg)) {
|
|
795
|
-
error(null, [ arg.location, user ], { id: arg.path[0].id },
|
|
796
|
-
'$(ID) can only be used as a value in a comparison to an association');
|
|
756
|
+
else if (!allowAssocTail && isAssociationOperand( arg )) {
|
|
757
|
+
const variant = isComposition( model, arg._artifact ) ? 'expr-comp' : 'expr';
|
|
758
|
+
error( 'ref-unexpected-assoc', [ arg.location, user ], { '#': variant } );
|
|
797
759
|
}
|
|
798
760
|
}
|
|
799
761
|
|
|
@@ -827,13 +789,17 @@ function check( model ) {
|
|
|
827
789
|
// One argument must be "$self" and the other an assoc
|
|
828
790
|
if (xpr.op.val === '=' && xpr.args.length === 2) {
|
|
829
791
|
// Tree-ish expression from the compiler (not augmented)
|
|
830
|
-
|
|
831
|
-
|
|
792
|
+
// eslint-disable-next-line max-len
|
|
793
|
+
return (isAssociationOperand( xpr.args[0] ) && isDollarSelfOrProjectionOperand( xpr.args[1] ) ||
|
|
794
|
+
// eslint-disable-next-line max-len
|
|
795
|
+
isAssociationOperand( xpr.args[1] ) && isDollarSelfOrProjectionOperand( xpr.args[0] ));
|
|
832
796
|
}
|
|
833
797
|
else if (xpr.args.length === 3 && xpr.args[1].val === '=') {
|
|
834
798
|
// Tree-ish expression from the compiler (not augmented)
|
|
835
|
-
|
|
836
|
-
|
|
799
|
+
// eslint-disable-next-line max-len
|
|
800
|
+
return (isAssociationOperand( xpr.args[0] ) && isDollarSelfOrProjectionOperand( xpr.args[2] ) ||
|
|
801
|
+
// eslint-disable-next-line max-len
|
|
802
|
+
isAssociationOperand( xpr.args[2] ) && isDollarSelfOrProjectionOperand( xpr.args[0] ));
|
|
837
803
|
}
|
|
838
804
|
|
|
839
805
|
// Nothing else qualifies
|
|
@@ -858,7 +824,7 @@ function check( model ) {
|
|
|
858
824
|
let fromArtifact = null;
|
|
859
825
|
let pathStepsFound = 0;
|
|
860
826
|
for (let i = anno.name.path.length; i > 0; i--) {
|
|
861
|
-
const absoluteName = anno.name.path.slice(0, i).map(path => path.id).join('.');
|
|
827
|
+
const absoluteName = anno.name.path.slice( 0, i ).map( path => path.id ).join( '.' );
|
|
862
828
|
if (model.vocabularies[absoluteName]) {
|
|
863
829
|
fromArtifact = model.vocabularies[absoluteName];
|
|
864
830
|
pathStepsFound = i;
|
|
@@ -871,8 +837,8 @@ function check( model ) {
|
|
|
871
837
|
return;
|
|
872
838
|
}
|
|
873
839
|
|
|
874
|
-
const { artifact, endOfPath } = resolvePathFrom(anno.name.path.slice(pathStepsFound),
|
|
875
|
-
|
|
840
|
+
const { artifact, endOfPath } = resolvePathFrom( anno.name.path.slice( pathStepsFound ),
|
|
841
|
+
fromArtifact );
|
|
876
842
|
|
|
877
843
|
// Check what we actually want to check
|
|
878
844
|
checkAnnotationAssignment( anno, artifact, endOfPath, art );
|
|
@@ -892,33 +858,33 @@ function check( model ) {
|
|
|
892
858
|
|
|
893
859
|
// Element must exist in annotation
|
|
894
860
|
if (!elementDecl) {
|
|
895
|
-
warning(null, [ anno.location || anno.name.location, art ],
|
|
896
|
-
|
|
897
|
-
|
|
861
|
+
warning( null, [ anno.location || anno.name.location, art ],
|
|
862
|
+
{ name: pathName( anno.name.path ), anno: annoDecl.name.absolute },
|
|
863
|
+
'Element $(NAME) not found for annotation $(ANNO)' );
|
|
898
864
|
return;
|
|
899
865
|
}
|
|
900
866
|
|
|
901
867
|
// Sanity checks
|
|
902
868
|
if (!elementDecl._effectiveType)
|
|
903
|
-
throw new CompilerAssertion(`Expecting annotation declaration to have _finalType: ${ JSON.stringify(annoDecl) }`);
|
|
869
|
+
throw new CompilerAssertion(`Expecting annotation declaration to have _finalType: ${ JSON.stringify( annoDecl ) }`);
|
|
904
870
|
|
|
905
871
|
|
|
906
872
|
// Must have literal or path unless it is a boolean
|
|
907
|
-
if (!anno.literal && !anno.path && getFinalTypeNameOf(elementDecl) !== 'cds.Boolean') {
|
|
873
|
+
if (!anno.literal && !anno.path && getFinalTypeNameOf( elementDecl ) !== 'cds.Boolean') {
|
|
908
874
|
if (elementDecl.type?._artifact.name.absolute) {
|
|
909
|
-
warning('anno-expecting-value', [ anno.location || anno.name.location, art ],
|
|
910
|
-
|
|
875
|
+
warning( 'anno-expecting-value', [ anno.location || anno.name.location, art ],
|
|
876
|
+
{ '#': 'type', type: elementDecl.type._artifact } );
|
|
911
877
|
}
|
|
912
878
|
else {
|
|
913
|
-
warning('anno-expecting-value', [ anno.location || anno.name.location, art ],
|
|
914
|
-
|
|
879
|
+
warning( 'anno-expecting-value', [ anno.location || anno.name.location, art ],
|
|
880
|
+
{ '#': 'std', anno: anno.name.absolute } );
|
|
915
881
|
}
|
|
916
882
|
|
|
917
883
|
return;
|
|
918
884
|
}
|
|
919
885
|
|
|
920
886
|
// Value must be assignable to type
|
|
921
|
-
checkValueAssignableTo(anno, anno, elementDecl, art);
|
|
887
|
+
checkValueAssignableTo( anno, anno, elementDecl, art );
|
|
922
888
|
}
|
|
923
889
|
|
|
924
890
|
// Check that annotation assignment 'value' (having 'path or 'literal' and
|
|
@@ -937,12 +903,12 @@ function check( model ) {
|
|
|
937
903
|
if (elementDecl._effectiveType.items) {
|
|
938
904
|
// Make sure we have an array value
|
|
939
905
|
if (value.literal !== 'array') {
|
|
940
|
-
warning(null, loc, { anno }, 'An array value is required for annotation $(ANNO)');
|
|
906
|
+
warning( null, loc, { anno }, 'An array value is required for annotation $(ANNO)' );
|
|
941
907
|
return;
|
|
942
908
|
}
|
|
943
909
|
// Check each element
|
|
944
910
|
for (const valueItem of value.val)
|
|
945
|
-
checkValueAssignableTo(value, valueItem, elementDecl._effectiveType.items, art);
|
|
911
|
+
checkValueAssignableTo( value, valueItem, elementDecl._effectiveType.items, art );
|
|
946
912
|
|
|
947
913
|
return;
|
|
948
914
|
}
|
|
@@ -950,7 +916,7 @@ function check( model ) {
|
|
|
950
916
|
// Struct expected (can only happen within arrays)?
|
|
951
917
|
if (elementDecl._effectiveType.elements) {
|
|
952
918
|
if (value.literal !== 'struct') {
|
|
953
|
-
warning(null, loc, { anno }, 'A struct value is required here for annotation $(ANNO)');
|
|
919
|
+
warning( null, loc, { anno }, 'A struct value is required here for annotation $(ANNO)' );
|
|
954
920
|
return;
|
|
955
921
|
}
|
|
956
922
|
// FIXME: Should check each element
|
|
@@ -959,44 +925,44 @@ function check( model ) {
|
|
|
959
925
|
|
|
960
926
|
// Handle each (primitive) expected element type separately
|
|
961
927
|
// TODO: Don't rely on name; use actual type
|
|
962
|
-
const type = getFinalTypeNameOf(elementDecl);
|
|
963
|
-
if (builtins.isStringTypeName(type)) {
|
|
928
|
+
const type = getFinalTypeNameOf( elementDecl );
|
|
929
|
+
if (builtins.isStringTypeName( type )) {
|
|
964
930
|
if (value.literal !== 'string' && value.literal !== 'enum' &&
|
|
965
931
|
!elementDecl._effectiveType.enum) {
|
|
966
|
-
warning(null, loc, { type, anno },
|
|
967
|
-
|
|
932
|
+
warning( null, loc, { type, anno },
|
|
933
|
+
'A string value is required for type $(TYPE) for annotation $(ANNO)' );
|
|
968
934
|
}
|
|
969
935
|
}
|
|
970
|
-
else if (builtins.isBinaryTypeName(type)) {
|
|
936
|
+
else if (builtins.isBinaryTypeName( type )) {
|
|
971
937
|
if (value.literal !== 'string' && value.literal !== 'x') {
|
|
972
|
-
warning(null, loc, { type, anno },
|
|
973
|
-
|
|
938
|
+
warning( null, loc, { type, anno },
|
|
939
|
+
'A hexadecimal string value is required for type $(TYPE) for annotation $(ANNO)' );
|
|
974
940
|
}
|
|
975
941
|
}
|
|
976
|
-
else if (builtins.isNumericTypeName(type)) {
|
|
942
|
+
else if (builtins.isNumericTypeName( type )) {
|
|
977
943
|
if (value.literal !== 'number' && value.literal !== 'enum' &&
|
|
978
944
|
!elementDecl._effectiveType.enum) {
|
|
979
|
-
warning(null, loc, { type, anno },
|
|
980
|
-
|
|
945
|
+
warning( null, loc, { type, anno },
|
|
946
|
+
'A numerical value is required for type $(TYPE) for annotation $(ANNO)' );
|
|
981
947
|
}
|
|
982
948
|
}
|
|
983
|
-
else if (builtins.isDateOrTimeTypeName(type)) {
|
|
949
|
+
else if (builtins.isDateOrTimeTypeName( type )) {
|
|
984
950
|
if (value.literal !== 'date' && value.literal !== 'time' &&
|
|
985
951
|
value.literal !== 'timestamp' && value.literal !== 'string') {
|
|
986
|
-
warning(null, loc, { type, anno },
|
|
987
|
-
|
|
988
|
-
|
|
952
|
+
warning( null, loc, { type, anno },
|
|
953
|
+
// eslint-disable-next-line max-len
|
|
954
|
+
'A date/time value or a string is required for type $(TYPE) for annotation $(ANNO)' );
|
|
989
955
|
}
|
|
990
956
|
}
|
|
991
|
-
else if (builtins.isBooleanTypeName(type)) {
|
|
957
|
+
else if (builtins.isBooleanTypeName( type )) {
|
|
992
958
|
if (value.literal && value.literal !== 'boolean') {
|
|
993
|
-
warning(null, loc, { type, anno },
|
|
994
|
-
|
|
959
|
+
warning( null, loc, { type, anno },
|
|
960
|
+
'A boolean value is required for type $(TYPE) for annotation $(ANNO)' );
|
|
995
961
|
}
|
|
996
962
|
}
|
|
997
|
-
else if (builtins.isRelationTypeName(type) || builtins.isGeoTypeName(type)) {
|
|
998
|
-
warning(null, loc, { type, anno },
|
|
999
|
-
|
|
963
|
+
else if (builtins.isRelationTypeName( type ) || builtins.isGeoTypeName( type )) {
|
|
964
|
+
warning( null, loc, { type, anno },
|
|
965
|
+
'Type $(TYPE) can\'t be assigned a value for annotation $(ANNO)' );
|
|
1000
966
|
}
|
|
1001
967
|
else if (!elementDecl._effectiveType.enum) {
|
|
1002
968
|
throw new CompilerAssertion(`Unknown primitive type name: ${ type }`);
|
|
@@ -1009,22 +975,22 @@ function check( model ) {
|
|
|
1009
975
|
// Enum symbol provided and expected
|
|
1010
976
|
if (!expectedEnum[value.sym.id]) {
|
|
1011
977
|
// ... but no such constant
|
|
1012
|
-
warning(null, loc, { id: `#${ value.sym.id }`, anno }, 'Enum symbol $(ID) not found in enum for annotation $(ANNO)');
|
|
978
|
+
warning( null, loc, { id: `#${ value.sym.id }`, anno }, 'Enum symbol $(ID) not found in enum for annotation $(ANNO)' );
|
|
1013
979
|
}
|
|
1014
980
|
}
|
|
1015
981
|
else {
|
|
1016
982
|
// Enum symbol provided but not expected
|
|
1017
|
-
warning(null, loc, { id: `#${ value.sym.id }`, type, anno },
|
|
1018
|
-
|
|
983
|
+
warning( null, loc, { id: `#${ value.sym.id }`, type, anno },
|
|
984
|
+
'Can\'t use enum symbol $(ID) for non-enum type $(TYPE) for annotation $(ANNO)' );
|
|
1019
985
|
}
|
|
1020
986
|
}
|
|
1021
987
|
else if (expectedEnum) {
|
|
1022
988
|
// Enum symbol not provided but expected
|
|
1023
|
-
const hasValidValue = Object.keys(expectedEnum)
|
|
1024
|
-
.some(symbol => getEnumValue(expectedEnum[symbol]) === value.val);
|
|
989
|
+
const hasValidValue = Object.keys( expectedEnum )
|
|
990
|
+
.some( symbol => getEnumValue( expectedEnum[symbol] ) === value.val );
|
|
1025
991
|
if (!hasValidValue) {
|
|
1026
992
|
// ... and none of the valid enum symbols matches the value
|
|
1027
|
-
warning(null, loc, { anno }, 'An enum value is required for annotation $(ANNO)');
|
|
993
|
+
warning( null, loc, { anno }, 'An enum value is required for annotation $(ANNO)' );
|
|
1028
994
|
}
|
|
1029
995
|
}
|
|
1030
996
|
}
|
|
@@ -1059,7 +1025,7 @@ function check( model ) {
|
|
|
1059
1025
|
// Continue search with next path step
|
|
1060
1026
|
const nextStepEnv = (from._effectiveType || from).artifacts ||
|
|
1061
1027
|
from._effectiveType?.elements || [];
|
|
1062
|
-
return resolvePathFrom(path.slice(1), nextStepEnv[path[0].id], result);
|
|
1028
|
+
return resolvePathFrom( path.slice(1), nextStepEnv[path[0].id], result );
|
|
1063
1029
|
}
|
|
1064
1030
|
|
|
1065
1031
|
// TODO: remove
|
|
@@ -1094,7 +1060,7 @@ function checkSapCommonTextsAspects( model ) {
|
|
|
1094
1060
|
elemref: 'locale',
|
|
1095
1061
|
type: 'cds.String',
|
|
1096
1062
|
othertype: 'sap.common.Locale',
|
|
1097
|
-
});
|
|
1063
|
+
} );
|
|
1098
1064
|
}
|
|
1099
1065
|
}
|
|
1100
1066
|
}
|
|
@@ -1129,6 +1095,9 @@ function checkSapCommonLocale( model ) {
|
|
|
1129
1095
|
* @param {(xpr: any, user: any, parentExpr: any) => void} callback
|
|
1130
1096
|
*/
|
|
1131
1097
|
function visitExpression( xpr, user, callback ) {
|
|
1098
|
+
if (!xpr)
|
|
1099
|
+
return; // e.g. parse error
|
|
1100
|
+
|
|
1132
1101
|
callback( xpr, user, null );
|
|
1133
1102
|
visitSubExpression( xpr, user, callback );
|
|
1134
1103
|
}
|
|
@@ -1142,12 +1111,14 @@ function visitExpression( xpr, user, callback ) {
|
|
|
1142
1111
|
*/
|
|
1143
1112
|
function visitSubExpression( xpr, user, callback ) {
|
|
1144
1113
|
if (xpr.args) {
|
|
1145
|
-
const args = Array.isArray(xpr.args) ? xpr.args : Object.values(xpr.args);
|
|
1114
|
+
const args = Array.isArray( xpr.args ) ? xpr.args : Object.values( xpr.args );
|
|
1146
1115
|
// Check for illegal argument usage within the expression
|
|
1147
1116
|
for (const arg of args) {
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1117
|
+
if (arg) { // null for parse errors
|
|
1118
|
+
callback( arg, user, xpr.args );
|
|
1119
|
+
// Recursively traverse the argument expression
|
|
1120
|
+
visitSubExpression( arg, user, callback );
|
|
1121
|
+
}
|
|
1151
1122
|
}
|
|
1152
1123
|
}
|
|
1153
1124
|
|
|
@@ -1155,7 +1126,7 @@ function visitSubExpression( xpr, user, callback ) {
|
|
|
1155
1126
|
for (const arg of xpr.path) {
|
|
1156
1127
|
if (arg.where) {
|
|
1157
1128
|
callback( arg.where, user, arg );
|
|
1158
|
-
visitSubExpression(arg.where, user, callback);
|
|
1129
|
+
visitSubExpression( arg.where, user, callback );
|
|
1159
1130
|
}
|
|
1160
1131
|
}
|
|
1161
1132
|
}
|