@sap/cds-compiler 4.0.2 → 4.2.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 +200 -5
- package/bin/cdsc.js +18 -15
- package/doc/CHANGELOG_BETA.md +16 -0
- package/doc/CHANGELOG_DEPRECATED.md +15 -0
- package/lib/api/main.js +33 -13
- package/lib/api/options.js +2 -2
- package/lib/api/validate.js +25 -25
- package/lib/base/location.js +6 -7
- package/lib/base/message-registry.js +123 -42
- package/lib/base/messages.js +18 -10
- package/lib/base/model.js +43 -10
- package/lib/checks/defaultValues.js +6 -6
- package/lib/checks/elements.js +11 -10
- package/lib/checks/foreignKeys.js +0 -5
- package/lib/checks/manyNavigations.js +33 -0
- package/lib/checks/onConditions.js +22 -14
- package/lib/checks/queryNoDbArtifacts.js +132 -73
- package/lib/checks/selectItems.js +4 -55
- package/lib/checks/sql-snippets.js +15 -4
- package/lib/checks/types.js +3 -3
- package/lib/checks/utils.js +4 -3
- package/lib/checks/validator.js +3 -1
- package/lib/compiler/.eslintrc.json +2 -1
- package/lib/compiler/assert-consistency.js +71 -40
- package/lib/compiler/base.js +7 -2
- package/lib/compiler/builtins.js +40 -41
- package/lib/compiler/checks.js +415 -367
- package/lib/compiler/classes.js +62 -0
- package/lib/compiler/cycle-detector.js +9 -9
- package/lib/compiler/define.js +124 -90
- package/lib/compiler/extend.js +115 -88
- package/lib/compiler/finalize-parse-cdl.js +26 -25
- package/lib/compiler/generate.js +57 -49
- package/lib/compiler/index.js +56 -56
- package/lib/compiler/kick-start.js +10 -7
- package/lib/compiler/moduleLayers.js +1 -1
- package/lib/compiler/populate.js +180 -144
- package/lib/compiler/propagator.js +10 -9
- package/lib/compiler/resolve.js +321 -246
- package/lib/compiler/shared.js +812 -433
- package/lib/compiler/tweak-assocs.js +114 -50
- package/lib/compiler/utils.js +241 -46
- 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 +679 -770
- package/lib/edm/edmAnnoPreprocessor.js +132 -146
- package/lib/edm/edmInboundChecks.js +29 -27
- package/lib/edm/edmPreprocessor.js +689 -648
- package/lib/edm/edmUtils.js +279 -300
- 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 +2857 -2856
- package/lib/json/from-csn.js +77 -51
- package/lib/json/to-csn.js +15 -15
- package/lib/language/antlrParser.js +2 -1
- package/lib/language/genericAntlrParser.js +52 -43
- package/lib/language/language.g4 +61 -64
- package/lib/language/multiLineStringParser.js +2 -0
- package/lib/main.d.ts +65 -0
- package/lib/model/csnRefs.js +37 -19
- package/lib/model/csnUtils.js +51 -18
- package/lib/model/revealInternalProperties.js +30 -22
- package/lib/modelCompare/compare.js +149 -41
- package/lib/modelCompare/utils/filter.js +55 -25
- package/lib/optionProcessor.js +21 -9
- package/lib/render/manageConstraints.js +20 -17
- package/lib/render/toCdl.js +63 -23
- package/lib/render/toHdbcds.js +2 -2
- package/lib/render/toRename.js +4 -9
- package/lib/render/toSql.js +82 -35
- package/lib/render/utils/common.js +11 -9
- package/lib/render/utils/unique.js +52 -0
- package/lib/transform/db/applyTransformations.js +62 -21
- package/lib/transform/db/assertUnique.js +7 -8
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/cdsPersistence.js +9 -9
- package/lib/transform/db/constraints.js +47 -17
- package/lib/transform/db/expansion.js +138 -68
- package/lib/transform/db/flattening.js +98 -30
- 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} +10 -7
- package/lib/transform/forRelationalDB.js +148 -136
- package/lib/transform/localized.js +92 -54
- package/lib/transform/odata/toFinalBaseType.js +3 -3
- package/lib/transform/{transformUtilsNew.js → transformUtils.js} +13 -111
- package/lib/transform/translateAssocsToJoins.js +14 -28
- 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/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,37 @@
|
|
|
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
|
+
forEachMemberRecursively,
|
|
19
|
+
isBetaEnabled,
|
|
22
20
|
} = require('../base/model');
|
|
23
21
|
const { CompilerAssertion } = require('../base/error');
|
|
24
22
|
const { pathName } = require('./utils');
|
|
25
|
-
const {
|
|
26
|
-
const $location = Symbol.for('cds.$location');
|
|
23
|
+
const { typeParameters } = require('./builtins');
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
const $location = Symbol.for( 'cds.$location' );
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Run compiler checks on the given XSN model.
|
|
29
|
+
*
|
|
30
|
+
* @param {XSN.Model} model
|
|
31
|
+
*/
|
|
32
|
+
function check( model ) {
|
|
29
33
|
const {
|
|
30
|
-
error, warning,
|
|
34
|
+
error, warning, info,
|
|
31
35
|
} = model.$messageFunctions;
|
|
32
36
|
|
|
33
37
|
checkSapCommonLocale( model );
|
|
@@ -35,34 +39,37 @@ function check( model ) { // = XSN
|
|
|
35
39
|
|
|
36
40
|
forEachDefinition( model, checkDefinition );
|
|
37
41
|
forEachGeneric( model, 'vocabularies', checkAnnotationDefinition );
|
|
42
|
+
|
|
38
43
|
return;
|
|
39
44
|
|
|
40
45
|
function checkDefinition( def ) {
|
|
41
46
|
checkGenericConstruct( def );
|
|
42
47
|
if (def.includes && def.elements)
|
|
43
48
|
checkElementIncludeOverride( def );
|
|
44
|
-
forEachMember( def, member => checkMember(member) );
|
|
49
|
+
forEachMember( def, member => checkMember( member ) );
|
|
45
50
|
if (def.$queries)
|
|
46
51
|
def.$queries.forEach( checkQuery );
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
function checkAnnotationDefinition( art ) {
|
|
55
|
+
// TODO: Should we check elements similar to definition-elements as well?
|
|
50
56
|
checkEnumType( art );
|
|
51
57
|
forEachMemberRecursively( art, (member) => {
|
|
52
58
|
if (member.localized?.val)
|
|
53
59
|
warning( 'def-unexpected-localized-anno', [ member.localized.location, member ] );
|
|
54
|
-
});
|
|
55
|
-
// TODO: Should we check elements similar to definition-elements as well?
|
|
60
|
+
} );
|
|
56
61
|
}
|
|
57
62
|
|
|
58
63
|
function checkGenericConstruct( art ) {
|
|
59
64
|
checkName( art );
|
|
65
|
+
checkTypeArguments( art );
|
|
60
66
|
if (model.vocabularies) {
|
|
61
67
|
Object.keys( art )
|
|
62
|
-
.filter( a => a.startsWith('@') )
|
|
68
|
+
.filter( a => a.startsWith( '@' ) )
|
|
63
69
|
.forEach( a => checkAnnotationAssignment1( art, art[a] ) );
|
|
64
70
|
}
|
|
65
|
-
checkTypeStructure(art);
|
|
71
|
+
checkTypeStructure( art );
|
|
72
|
+
checkAssociation( art ); // type def could be assoc
|
|
66
73
|
if (art.kind === 'enum')
|
|
67
74
|
checkEnum( art );
|
|
68
75
|
checkEnumType( art );
|
|
@@ -75,106 +82,177 @@ function check( model ) { // = XSN
|
|
|
75
82
|
if (member.virtual?.val === true)
|
|
76
83
|
parentProps.virtual = member.virtual;
|
|
77
84
|
|
|
78
|
-
checkGenericConstruct(member);
|
|
85
|
+
checkGenericConstruct( member );
|
|
79
86
|
|
|
80
87
|
if (member.kind === 'element')
|
|
81
88
|
checkElement( member, parentProps );
|
|
82
89
|
|
|
83
|
-
forEachMember( member, m => checkMember(m, parentProps) );
|
|
90
|
+
forEachMember( member, m => checkMember( m, parentProps ) );
|
|
84
91
|
}
|
|
85
92
|
|
|
86
93
|
function checkVirtualKey( elem, parentProps ) {
|
|
87
|
-
const isKey = parentProps.key ||
|
|
88
|
-
const isVirtual = parentProps.virtual?.val ||
|
|
94
|
+
const isKey = parentProps.key?.val || elem.key?.val;
|
|
95
|
+
const isVirtual = parentProps.virtual?.val || elem.virtual?.val;
|
|
89
96
|
if (isKey && isVirtual) {
|
|
90
|
-
error('def-unexpected-key', [ isKey.location, elem ],
|
|
91
|
-
|
|
97
|
+
error( 'def-unexpected-key', [ isKey.location, elem ],
|
|
98
|
+
{ '#': 'virtual', art: elem.name.element, prop: 'key' } );
|
|
92
99
|
}
|
|
93
100
|
}
|
|
94
101
|
|
|
95
102
|
function checkElement( elem, parentProps ) {
|
|
96
|
-
checkLocalizedSubElement(elem);
|
|
97
|
-
checkVirtualKey(elem, parentProps);
|
|
98
|
-
checkForUnmanagedAssociationsAsKey( elem, parentProps );
|
|
103
|
+
checkLocalizedSubElement( elem );
|
|
104
|
+
checkVirtualKey( elem, parentProps );
|
|
99
105
|
checkLocalizedElement( elem );
|
|
100
|
-
checkAssociation( elem );
|
|
101
106
|
|
|
102
107
|
if (elem.value) {
|
|
103
108
|
if (elem._main.query)
|
|
104
|
-
checkSelectItemValue(elem);
|
|
109
|
+
checkSelectItemValue( elem );
|
|
105
110
|
else if (elem.$syntax === 'calc')
|
|
106
111
|
checkCalculatedElementValue( elem );
|
|
107
112
|
}
|
|
108
113
|
|
|
109
|
-
checkCardinality(elem); // TODO: also for assoc types
|
|
114
|
+
checkCardinality( elem ); // TODO: also for assoc types
|
|
110
115
|
}
|
|
111
116
|
|
|
112
117
|
|
|
113
118
|
function checkName( construct ) { // TODO: move to define.js
|
|
114
119
|
if (model.options.$skipNameCheck)
|
|
115
120
|
return;
|
|
116
|
-
// TODO: Move a corrected version of this check to definer (but do not rely
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
// TODO: Move a corrected version of this check to definer (but do not rely on it!):
|
|
122
|
+
// The code below misses to consider CSN input!
|
|
123
|
+
// Maybe remove the check? But consider runtimes that rely on '.' as element separator.
|
|
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
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Check the type arguments on `art`, e.g. cds.Decimal can't have a `length`, structures
|
|
135
|
+
* can't have `precision`, etc.
|
|
136
|
+
*
|
|
137
|
+
* @param {XSN.Artifact} art
|
|
138
|
+
* @param {XSN.Artifact} user
|
|
139
|
+
*/
|
|
140
|
+
function checkTypeArguments( art, user = art ) {
|
|
141
|
+
if (art.builtin || art.kind === 'context' || art.kind === 'service')
|
|
142
|
+
return;
|
|
143
|
+
|
|
144
|
+
if (art.items)
|
|
145
|
+
checkTypeArguments( art.items, art );
|
|
146
|
+
|
|
147
|
+
const actualParams = typeParameters.list.filter( param => art[param] !== undefined );
|
|
148
|
+
if (actualParams.length === 0)
|
|
149
|
+
return;
|
|
150
|
+
|
|
151
|
+
const typeArt = art.type?._artifact || art;
|
|
152
|
+
|
|
153
|
+
// Note: `_effectiveType` points to `art` itself, if it is an enum type,
|
|
154
|
+
// descend to the origin in this case.
|
|
155
|
+
let effectiveType = typeArt._effectiveType;
|
|
156
|
+
while (effectiveType?.enum)
|
|
157
|
+
effectiveType = (effectiveType._origin || effectiveType.type?._artifact)?._effectiveType;
|
|
158
|
+
|
|
159
|
+
if (!effectiveType) {
|
|
160
|
+
return; // e.g. illegal definition references, cycles, ...
|
|
161
|
+
}
|
|
162
|
+
else if (!art.type && !effectiveType.type && !effectiveType?.builtin) {
|
|
163
|
+
error( 'type-missing-type', [ art.location, user ],
|
|
164
|
+
{ otherprop: 'type', prop: actualParams[0] },
|
|
165
|
+
'Missing $(OTHERPROP) property next to $(PROP)' );
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const expectedParams = effectiveType.parameters &&
|
|
170
|
+
effectiveType.parameters.map( p => p.name || p ) || [];
|
|
171
|
+
|
|
172
|
+
for (const param of actualParams) {
|
|
173
|
+
if (!expectedParams.includes( param )) {
|
|
174
|
+
// Whether the type ref itself is a builtin or a custom type with a builtin as base.
|
|
175
|
+
let variant;
|
|
176
|
+
if ((art.type?._artifact || art._effectiveType).builtin)
|
|
177
|
+
variant = 'builtin';
|
|
178
|
+
else if (effectiveType.builtin)
|
|
179
|
+
variant = 'type';
|
|
180
|
+
else // effectiveType is not a builtin -> array or structured
|
|
181
|
+
variant = 'non-scalar';
|
|
182
|
+
|
|
183
|
+
error( 'type-unexpected-argument', [ art[param].location, user ], {
|
|
184
|
+
'#': variant, prop: param, art: art.type || art._effectiveType, type: effectiveType,
|
|
185
|
+
} );
|
|
186
|
+
break; // Avoid spam: Only emit the first error.
|
|
187
|
+
}
|
|
188
|
+
else if (!typeParameters.expectedLiteralsFor[param].includes( art[param].literal )) {
|
|
189
|
+
error( 'type-unexpected-argument', [ art[param].location, user ], {
|
|
190
|
+
'#': 'incorrect-type',
|
|
191
|
+
prop: param,
|
|
192
|
+
code: art[param].literal,
|
|
193
|
+
names: typeParameters.expectedLiteralsFor[param],
|
|
194
|
+
} );
|
|
195
|
+
break; // Avoid spam: Only emit the first error.
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function requireExplicitTypeInSqlCast( art, user ) {
|
|
201
|
+
if (!art.type) {
|
|
202
|
+
error( 'expr-missing-type', [ art.location, user ], { },
|
|
203
|
+
'Missing type in SQL cast function' );
|
|
122
204
|
}
|
|
123
205
|
}
|
|
124
206
|
|
|
125
207
|
function checkLocalizedElement( elem ) {
|
|
126
|
-
|
|
127
|
-
if (elem.localized && elem.localized.val) {
|
|
208
|
+
if (elem.localized?.val) {
|
|
128
209
|
const type = elem._effectiveType;
|
|
129
210
|
// See discussion issue #6520: should we allow all scalar types?
|
|
130
211
|
if (!type || !type.builtin || type.category !== 'string') {
|
|
131
|
-
info('ref-expecting-localized-string', [ elem.type?.location, elem ],
|
|
132
|
-
|
|
133
|
-
|
|
212
|
+
info( 'ref-expecting-localized-string', [ elem.type?.location, elem ],
|
|
213
|
+
{ keyword: 'localized' },
|
|
214
|
+
'Expecting a string type in combination with keyword $(KEYWORD)' );
|
|
134
215
|
}
|
|
135
216
|
}
|
|
136
|
-
|
|
217
|
+
|
|
137
218
|
// TODO: This check should be moved to localized.js
|
|
138
|
-
|
|
219
|
+
// "key" keyword at localized element in SELECT list.
|
|
220
|
+
if (elem.key?.val && elem._main?.query) {
|
|
139
221
|
// original element is localized but not key, as that would have
|
|
140
|
-
// already resulted in a warning
|
|
141
|
-
if (elem._origin
|
|
142
|
-
(
|
|
143
|
-
|
|
144
|
-
'Keyword $(KEYWORD) is ignored for primary keys');
|
|
222
|
+
// already resulted in a warning by localized.js
|
|
223
|
+
if (elem._origin?.localized?.val && !elem._origin.key?.val) {
|
|
224
|
+
warning( 'def-ignoring-localized', [ elem.key.location, elem ], { keyword: 'localized' },
|
|
225
|
+
'Keyword $(KEYWORD) is ignored for primary keys' );
|
|
145
226
|
}
|
|
146
227
|
}
|
|
147
228
|
}
|
|
148
229
|
|
|
149
230
|
function checkQuery( query ) {
|
|
150
|
-
checkNoUnmanagedAssocsInGroupByOrderBy( query );
|
|
151
231
|
// TODO: check too simple (just one source), as most of those in this file
|
|
152
232
|
// Check expressions in the various places where they may occur
|
|
153
233
|
if (query.from)
|
|
154
|
-
visitSubExpression(query.from, query, checkGenericExpression);
|
|
234
|
+
visitSubExpression( query.from, query, checkGenericExpression );
|
|
155
235
|
|
|
156
236
|
if (query.where)
|
|
157
|
-
visitExpression(query.where, query, checkGenericExpression);
|
|
237
|
+
visitExpression( query.where, query, checkGenericExpression );
|
|
158
238
|
|
|
159
239
|
if (query.groupBy) {
|
|
160
240
|
for (const groupByEntry of query.groupBy)
|
|
161
|
-
visitExpression(groupByEntry, query, checkGenericExpression);
|
|
241
|
+
visitExpression( groupByEntry, query, checkGenericExpression );
|
|
162
242
|
}
|
|
163
243
|
if (query.having)
|
|
164
|
-
visitExpression(query.having, query, checkGenericExpression);
|
|
244
|
+
visitExpression( query.having, query, checkGenericExpression );
|
|
165
245
|
|
|
166
246
|
if (query.orderBy) {
|
|
167
247
|
for (const orderByEntry of query.orderBy)
|
|
168
|
-
visitExpression(orderByEntry, query, checkGenericExpression);
|
|
248
|
+
visitExpression( orderByEntry, query, checkGenericExpression );
|
|
169
249
|
}
|
|
170
250
|
if (query.mixin) {
|
|
171
251
|
for (const mixinName in query.mixin)
|
|
172
|
-
checkAssociation(query.mixin[mixinName]);
|
|
252
|
+
checkAssociation( query.mixin[mixinName] );
|
|
173
253
|
}
|
|
174
254
|
}
|
|
175
255
|
|
|
176
|
-
// Individual checks -------------------------------------------------------
|
|
177
|
-
|
|
178
256
|
/**
|
|
179
257
|
* The enumNode is a single enum element and not the whole type.
|
|
180
258
|
*
|
|
@@ -190,8 +268,8 @@ function check( model ) { // = XSN
|
|
|
190
268
|
// Special handling to print a more detailed error message.
|
|
191
269
|
// Other cases like `null` as enum value are handled in `checkEnumValueType()`
|
|
192
270
|
if (type === 'enum') {
|
|
193
|
-
warning('
|
|
194
|
-
|
|
271
|
+
warning( 'ref-unexpected-enum', [ loc, enumNode ], {},
|
|
272
|
+
'References to other values are not allowed as enum values' );
|
|
195
273
|
}
|
|
196
274
|
}
|
|
197
275
|
|
|
@@ -201,8 +279,7 @@ function check( model ) { // = XSN
|
|
|
201
279
|
enumNode = enumNode.enum ? enumNode : enumNode.items;
|
|
202
280
|
if (!enumNode || !enumNode.enum)
|
|
203
281
|
return;
|
|
204
|
-
const type = enumNode
|
|
205
|
-
enumNode.type._artifact._effectiveType;
|
|
282
|
+
const type = enumNode?.type?._artifact?._effectiveType;
|
|
206
283
|
|
|
207
284
|
// We can't distinguish (in CSN) between these two cases:
|
|
208
285
|
// type Base : String enum { b;a = 'abc'; };
|
|
@@ -212,64 +289,57 @@ function check( model ) { // = XSN
|
|
|
212
289
|
if (!type || type.enum)
|
|
213
290
|
return;
|
|
214
291
|
|
|
215
|
-
const name = type.name.absolute;
|
|
216
|
-
|
|
217
292
|
// All builtin types are allowed except binary and relational types.
|
|
218
293
|
// 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';
|
|
294
|
+
if (!type.builtin || type.internal || type.category === 'binary') {
|
|
295
|
+
let typeClass = 'std';
|
|
296
|
+
if (type.category === 'binary' || type.category === 'relation')
|
|
297
|
+
typeClass = type.category;
|
|
228
298
|
else if (type.elements)
|
|
229
|
-
|
|
299
|
+
typeClass = 'struct';
|
|
230
300
|
else if (type.items)
|
|
231
|
-
|
|
301
|
+
typeClass = 'items';
|
|
232
302
|
|
|
233
|
-
error('
|
|
303
|
+
error( 'type-invalid-enum', [ enumNode.type.location, enumNode ], { '#': typeClass }, {
|
|
234
304
|
std: 'Only builtin types are allowed as enums',
|
|
235
305
|
binary: 'Binary types are not allowed as enums',
|
|
236
306
|
relation: 'Relational types are not allowed as enums',
|
|
237
307
|
struct: 'Structured types are not allowed as enums',
|
|
238
308
|
items: 'Arrayed types are not allowed as enums',
|
|
239
|
-
});
|
|
309
|
+
} );
|
|
240
310
|
return;
|
|
241
311
|
}
|
|
242
312
|
|
|
243
|
-
checkEnumValue(enumNode);
|
|
313
|
+
checkEnumValue( enumNode );
|
|
244
314
|
}
|
|
245
315
|
|
|
246
316
|
/**
|
|
247
|
-
* Check the given enum's elements and their values. For example
|
|
317
|
+
* Check the given enum's elements and their values. For example,
|
|
248
318
|
* whether the value types are valid for the used enum type.
|
|
249
319
|
* `enumNode` can be also be `type.items` if the type is an arrayed enum.
|
|
250
320
|
*
|
|
251
321
|
* @param {XSN.Definition} enumNode
|
|
252
322
|
*/
|
|
253
323
|
function checkEnumValue( enumNode ) {
|
|
254
|
-
const type = enumNode.type
|
|
255
|
-
|
|
256
|
-
if (!enumNode.enum || !type || !type.builtin)
|
|
324
|
+
const type = enumNode.type?._artifact?._effectiveType;
|
|
325
|
+
if (!type || !enumNode.enum || !type.builtin)
|
|
257
326
|
return;
|
|
258
327
|
|
|
259
|
-
const isNumeric =
|
|
260
|
-
const isString =
|
|
328
|
+
const isNumeric = type.category === 'decimal' || type.category === 'integer';
|
|
329
|
+
const isString = type.category === 'string';
|
|
261
330
|
|
|
262
331
|
if (!isString) {
|
|
263
332
|
// Non-string enums MUST have a value as the value is only deducted for string types.
|
|
264
|
-
const emptyValue = Object.keys(enumNode.enum
|
|
333
|
+
const emptyValue = Object.keys( enumNode.enum )
|
|
334
|
+
.find( name => !enumNode.enum[name].value );
|
|
265
335
|
if (emptyValue) {
|
|
266
336
|
const failedEnum = enumNode.enum[emptyValue];
|
|
267
|
-
warning('
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
337
|
+
warning( 'type-missing-value', [ failedEnum.location, failedEnum ], {
|
|
338
|
+
'#': isNumeric ? 'numeric' : 'std', name: emptyValue,
|
|
339
|
+
}, {
|
|
340
|
+
std: 'Missing value for non-string enum element $(NAME)',
|
|
341
|
+
numeric: 'Missing value for numeric enum element $(NAME)',
|
|
342
|
+
} );
|
|
273
343
|
}
|
|
274
344
|
}
|
|
275
345
|
|
|
@@ -286,29 +356,31 @@ function check( model ) { // = XSN
|
|
|
286
356
|
(element.value.literal !== expectedType) &&
|
|
287
357
|
(element.value.literal !== 'enum');
|
|
288
358
|
|
|
289
|
-
for (const key of Object.keys(enumNode.enum)) {
|
|
359
|
+
for (const key of Object.keys( enumNode.enum )) {
|
|
290
360
|
const element = enumNode.enum[key];
|
|
291
|
-
if (
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
361
|
+
if (hasWrongType( element )) {
|
|
362
|
+
const actualType = element.value.literal;
|
|
363
|
+
warning( 'type-unexpected-value', [ element.value.location, element ], {
|
|
364
|
+
'#': expectedType, name: key, prop: actualType || 'unknown',
|
|
365
|
+
}, {
|
|
366
|
+
std: 'Incorrect value type $(PROP) for enum element $(NAME)', // Not used
|
|
367
|
+
number: 'Expected numeric value for enum element $(NAME) but was $(PROP)',
|
|
368
|
+
string: 'Expected string value for enum element $(NAME) but was $(PROP)',
|
|
369
|
+
} );
|
|
370
|
+
}
|
|
301
371
|
}
|
|
302
372
|
}
|
|
303
373
|
|
|
304
|
-
// TODO: check inside compiler as it is a compiler restriction - improve
|
|
305
374
|
/**
|
|
375
|
+
* TODO: check inside compiler as it is a compiler restriction - improve
|
|
376
|
+
* TODO: Recursive check; use "parentProps" as other member checks
|
|
377
|
+
*
|
|
306
378
|
* Non-recursive check if sub-elements have a "localized" keyword since this is
|
|
307
379
|
* not yet supported.
|
|
308
380
|
*
|
|
309
381
|
* This check is not recursive to avoid a runtime overhead. Because of this it fails
|
|
310
382
|
* to detect scenarios with indirections, e.g.
|
|
311
|
-
*
|
|
383
|
+
* ```cds
|
|
312
384
|
* type L : localized String;
|
|
313
385
|
* type L1 : L;
|
|
314
386
|
* type L2 : L1;
|
|
@@ -318,153 +390,104 @@ function check( model ) { // = XSN
|
|
|
318
390
|
* subElement : L2;
|
|
319
391
|
* }
|
|
320
392
|
* }
|
|
393
|
+
* ```
|
|
321
394
|
*
|
|
322
395
|
* @param {XSN.Artifact} element
|
|
323
396
|
*/
|
|
324
397
|
function checkLocalizedSubElement( element ) {
|
|
325
|
-
if (element._parent
|
|
398
|
+
if (element._parent?.kind !== 'element')
|
|
326
399
|
return;
|
|
327
400
|
|
|
328
|
-
const isLocalizedSubElement = element.localized
|
|
329
|
-
if (isLocalizedSubElement ||
|
|
401
|
+
const isLocalizedSubElement = element.localized?.val;
|
|
402
|
+
if (isLocalizedSubElement || element.type?._artifact?.localized?.val) {
|
|
330
403
|
const loc = isLocalizedSubElement ? element.localized.location : element.type.location;
|
|
331
|
-
warning('localized-sub-element', [ loc, element ],
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
return;
|
|
339
|
-
|
|
340
|
-
// TODO: Recursive check
|
|
341
|
-
function isTypeLocalized( type ) {
|
|
342
|
-
return (type && type.localized && type.localized.val);
|
|
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
|
+
} );
|
|
343
410
|
}
|
|
344
411
|
}
|
|
345
412
|
|
|
346
413
|
/**
|
|
347
|
-
* Check that
|
|
348
|
-
* contains unmanaged associations
|
|
414
|
+
* Check that min and max cardinalities of 'art' have legal values
|
|
349
415
|
*
|
|
350
|
-
* TODO:
|
|
351
|
-
* long as the whole key is "closed", i.e., no ref in ON refers to element
|
|
352
|
-
* outside.
|
|
416
|
+
* TODO: move to define.js or parsers
|
|
353
417
|
*
|
|
354
|
-
* @param {
|
|
418
|
+
* @param {XSN.Artifact} art
|
|
355
419
|
*/
|
|
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)
|
|
420
|
+
function checkCardinality( art ) {
|
|
421
|
+
if (!art.cardinality)
|
|
377
422
|
return;
|
|
378
423
|
|
|
379
424
|
// Max cardinalities must be a positive number or '*'
|
|
380
425
|
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
|
-
});
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Min cardinality must be a non-negative number
|
|
398
|
-
// Note: Already checked by parser (syntax error if -1 is used) and
|
|
399
|
-
// from-csn.json (expected non-negative number)
|
|
400
|
-
for (const prop of [ 'sourceMin', 'targetMin' ]) {
|
|
401
|
-
if (elem.cardinality[prop]) {
|
|
402
|
-
const { literal, val, location } = elem.cardinality[prop];
|
|
403
|
-
if (!(literal === 'number' && val >= 0)) {
|
|
404
|
-
error('invalid-cardinality', [ location, elem ], { '#': prop, code: val }, {
|
|
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
|
-
});
|
|
426
|
+
if (art.cardinality[prop]) {
|
|
427
|
+
const { literal, val, location } = art.cardinality[prop];
|
|
428
|
+
if (!(literal === 'number' && val > 0 || literal === 'string' && val === '*')) {
|
|
429
|
+
error( 'type-invalid-cardinality', [ location, art ],
|
|
430
|
+
{ '#': prop, prop: val, otherprop: '*' } );
|
|
412
431
|
}
|
|
413
432
|
}
|
|
414
433
|
}
|
|
415
434
|
|
|
416
435
|
// If provided, min cardinality must not exceed max cardinality (note that
|
|
417
436
|
// '*' is considered to be >= any number)
|
|
418
|
-
const pair = [
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
}
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// TODO: make this part of the name resolution in the compiler
|
|
433
|
-
// Check that queries in 'art' do not contain unmanaged associations in GROUP BY or ORDER BY
|
|
434
|
-
function checkNoUnmanagedAssocsInGroupByOrderBy( query ) {
|
|
435
|
-
const art = query._main; // TODO - remove, use query for semantic location
|
|
436
|
-
for (const groupByEntry of query.groupBy || []) {
|
|
437
|
-
if (groupByEntry._artifact && groupByEntry._artifact._effectiveType &&
|
|
438
|
-
groupByEntry._artifact._effectiveType.on) {
|
|
439
|
-
// Unmanaged association - complain
|
|
440
|
-
error(null, [ groupByEntry.location, art ], {},
|
|
441
|
-
'Unmanaged associations are not allowed in GROUP BY');
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
for (const orderByEntry of query.orderBy || []) {
|
|
445
|
-
if (orderByEntry._artifact && orderByEntry._artifact._effectiveType &&
|
|
446
|
-
orderByEntry._artifact._effectiveType.on) {
|
|
447
|
-
// Unmanaged association - complain
|
|
448
|
-
error(null, [ orderByEntry.location, art ], {},
|
|
449
|
-
'Unmanaged associations are not allowed in ORDER BY');
|
|
450
|
-
}
|
|
451
|
-
}
|
|
437
|
+
const pair = [
|
|
438
|
+
[ 'sourceMin', 'sourceMax', 'sourceVal' ],
|
|
439
|
+
[ 'targetMin', 'targetMax', 'targetVal' ],
|
|
440
|
+
];
|
|
441
|
+
pair.forEach( ([ lhs, rhs, variant ]) => {
|
|
442
|
+
if (art.cardinality[lhs] && art.cardinality[rhs] &&
|
|
443
|
+
art.cardinality[rhs].literal === 'number' &&
|
|
444
|
+
art.cardinality[lhs].val > art.cardinality[rhs].val)
|
|
445
|
+
error( 'type-invalid-cardinality', [ art.cardinality.location, art ], { '#': variant } );
|
|
446
|
+
} );
|
|
452
447
|
}
|
|
453
448
|
|
|
454
449
|
function checkAssociation( elem ) {
|
|
450
|
+
if (!elem.target && !elem.targetAspect)
|
|
451
|
+
return;
|
|
455
452
|
// TODO: yes, a check similar to this could make it into the compiler)
|
|
456
453
|
// when virtual element is part of association
|
|
454
|
+
let fkCount = 0;
|
|
457
455
|
if (elem.foreignKeys) {
|
|
458
456
|
for (const k in elem.foreignKeys) {
|
|
457
|
+
++fkCount;
|
|
459
458
|
const key = elem.foreignKeys[k].targetElement;
|
|
460
|
-
if (key && isVirtualElement(key._artifact))
|
|
461
|
-
error('ref-unexpected-virtual', [ key.location, elem ], { '#': 'fkey' });
|
|
459
|
+
if (key && isVirtualElement( key._artifact ))
|
|
460
|
+
error( 'ref-unexpected-virtual', [ key.location, elem ], { '#': 'fkey' } );
|
|
462
461
|
else if (key._artifact?.$syntax === 'calc' && !key._artifact.value.stored?.val)
|
|
463
462
|
error( 'ref-unexpected-calculated', [ key.location, elem ], { '#': 'fkey' } );
|
|
464
463
|
}
|
|
465
464
|
}
|
|
465
|
+
if (elem.default) {
|
|
466
|
+
if (!isBetaEnabled( model.options, 'associationDefault' )) {
|
|
467
|
+
error( 'type-unsupported-default', [ elem.default.location, elem ], {
|
|
468
|
+
'#': isComposition( model, elem ) ? 'comp' : 'std',
|
|
469
|
+
}, {
|
|
470
|
+
std: 'Unsupported default value on an association',
|
|
471
|
+
comp: 'Unsupported default value on a composition',
|
|
472
|
+
} );
|
|
473
|
+
}
|
|
474
|
+
else if (elem.targetAspect || elem.on || fkCount !== 1) {
|
|
475
|
+
const variant = (elem.targetAspect && 'targetAspect') || (elem.on && 'onCond') || 'multi';
|
|
476
|
+
error( 'type-unexpected-default', [ elem.default.location, elem ], {
|
|
477
|
+
'#': variant, keyword: 'default', count: fkCount,
|
|
478
|
+
} );
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
const fkName = Object.keys( elem.foreignKeys )[0];
|
|
482
|
+
if (elem.foreignKeys[fkName].targetElement._artifact?._effectiveType?.elements) {
|
|
483
|
+
error( 'type-unexpected-default', [ elem.default.location, elem ], {
|
|
484
|
+
'#': 'structured', keyword: 'default', name: fkName,
|
|
485
|
+
} );
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
466
489
|
|
|
467
|
-
checkOnCondition(elem);
|
|
490
|
+
checkOnCondition( elem );
|
|
468
491
|
}
|
|
469
492
|
|
|
470
493
|
function getBinaryOp( cond ) {
|
|
@@ -473,8 +496,9 @@ function check( model ) { // = XSN
|
|
|
473
496
|
args[1] || op;
|
|
474
497
|
}
|
|
475
498
|
|
|
476
|
-
// A function like this could be part of the compiler
|
|
477
499
|
/**
|
|
500
|
+
* TODO: A function like this could be part of the compiler
|
|
501
|
+
*
|
|
478
502
|
* Check that the given type has no conflicts between its `type` property
|
|
479
503
|
* and its `elements` or `items` property. For example if `type` is not
|
|
480
504
|
* structured but the artifact has an `elements` property then the user
|
|
@@ -485,22 +509,22 @@ function check( model ) { // = XSN
|
|
|
485
509
|
function checkTypeStructure( artifact ) {
|
|
486
510
|
// Just a basic check. We do not check that the inner structure of `items`
|
|
487
511
|
// is the same as the type but only that all are arrayed or structured.
|
|
488
|
-
if (artifact.type
|
|
512
|
+
if (artifact.type?._artifact) {
|
|
489
513
|
const finalType = artifact.type._artifact._effectiveType || artifact.type._artifact;
|
|
490
514
|
|
|
491
515
|
if (artifact.items && !finalType.items) {
|
|
492
|
-
warning('type-items-mismatch', [ artifact.type.location, artifact ],
|
|
493
|
-
|
|
494
|
-
|
|
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' );
|
|
495
519
|
}
|
|
496
520
|
else if (artifact.elements && !finalType.elements) {
|
|
497
|
-
warning('type-elements-mismatch', [ artifact.type.location, artifact ],
|
|
498
|
-
|
|
499
|
-
|
|
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' );
|
|
500
524
|
}
|
|
501
525
|
}
|
|
502
526
|
if (artifact.items)
|
|
503
|
-
checkTypeStructure(artifact.items);
|
|
527
|
+
checkTypeStructure( artifact.items );
|
|
504
528
|
}
|
|
505
529
|
|
|
506
530
|
/**
|
|
@@ -517,7 +541,7 @@ function check( model ) { // = XSN
|
|
|
517
541
|
if (element.$inferred !== 'include') {
|
|
518
542
|
for (const include of def.includes) {
|
|
519
543
|
if (include._artifact?.elements?.[name] !== undefined)
|
|
520
|
-
checkElementOverride( element, include._artifact.elements[name]);
|
|
544
|
+
checkElementOverride( element, include._artifact.elements[name] );
|
|
521
545
|
}
|
|
522
546
|
}
|
|
523
547
|
}
|
|
@@ -532,12 +556,12 @@ function check( model ) { // = XSN
|
|
|
532
556
|
const name = elem.name.id;
|
|
533
557
|
// Position at type/struct, not name
|
|
534
558
|
const loc = elem.type?.location || elem.elements?.[$location] || elem.location;
|
|
535
|
-
error('ref-invalid-override', [ loc, elem ],
|
|
536
|
-
|
|
559
|
+
error( 'ref-invalid-override', [ loc, elem ],
|
|
560
|
+
{ '#': prop, art: original._main, name } );
|
|
537
561
|
return false;
|
|
538
562
|
}
|
|
539
563
|
else if (original.elements &&
|
|
540
|
-
!checkSubStructureOverride(elem, elem.elements, original.elements)) {
|
|
564
|
+
!checkSubStructureOverride( elem, elem.elements, original.elements )) {
|
|
541
565
|
return false;
|
|
542
566
|
}
|
|
543
567
|
return true;
|
|
@@ -552,10 +576,10 @@ function check( model ) { // = XSN
|
|
|
552
576
|
const orig = originals[element];
|
|
553
577
|
if (elem === undefined) {
|
|
554
578
|
const loc = [ elements[$location], user ];
|
|
555
|
-
error('ref-invalid-override', loc, { '#': 'missing', id: user.name.id, name: element });
|
|
556
|
-
return false; // only report
|
|
579
|
+
error( 'ref-invalid-override', loc, { '#': 'missing', id: user.name.id, name: element } );
|
|
580
|
+
return false; // only report once
|
|
557
581
|
}
|
|
558
|
-
else if (!checkElementOverride(elem, orig)) {
|
|
582
|
+
else if (!checkElementOverride( elem, orig )) {
|
|
559
583
|
return false;
|
|
560
584
|
}
|
|
561
585
|
}
|
|
@@ -564,8 +588,6 @@ function check( model ) { // = XSN
|
|
|
564
588
|
}
|
|
565
589
|
|
|
566
590
|
|
|
567
|
-
// Former checkExpressions.js ----------------------------------------------
|
|
568
|
-
|
|
569
591
|
/**
|
|
570
592
|
* Check a generic expression (or condition) for semantic validity.
|
|
571
593
|
*
|
|
@@ -574,62 +596,80 @@ function check( model ) { // = XSN
|
|
|
574
596
|
* @returns {void}
|
|
575
597
|
*/
|
|
576
598
|
function checkGenericExpression( xpr, user ) {
|
|
577
|
-
checkExpressionNotVirtual(xpr, user);
|
|
578
|
-
checkExpressionAssociationUsage(xpr, user, false);
|
|
599
|
+
checkExpressionNotVirtual( xpr, user );
|
|
600
|
+
checkExpressionAssociationUsage( xpr, user, false );
|
|
601
|
+
if (xpr.op?.val === 'cast') {
|
|
602
|
+
requireExplicitTypeInSqlCast( xpr, user );
|
|
603
|
+
checkTypeArguments( xpr, user );
|
|
604
|
+
}
|
|
579
605
|
}
|
|
580
606
|
|
|
581
607
|
function checkExpressionNotVirtual( xpr, user ) {
|
|
582
|
-
if (xpr._artifact && isVirtualElement(xpr._artifact))
|
|
583
|
-
error('ref-unexpected-virtual', [ xpr.location, user ], { '#': 'expr' });
|
|
608
|
+
if (xpr._artifact && isVirtualElement( xpr._artifact ))
|
|
609
|
+
error( 'ref-unexpected-virtual', [ xpr.location, user ], { '#': 'expr' } );
|
|
584
610
|
}
|
|
585
611
|
|
|
586
612
|
function checkOnCondition( elem ) {
|
|
613
|
+
if (elem.$inferred === 'localized')
|
|
614
|
+
return; // ignore
|
|
615
|
+
|
|
587
616
|
// TODO: Move to checkAssociation
|
|
588
617
|
if (elem.on && !elem.on.$inferred) {
|
|
589
|
-
visitExpression(elem.on, elem, (xpr, user) => {
|
|
590
|
-
checkExpressionNotVirtual(xpr, user);
|
|
591
|
-
checkExpressionAssociationUsage(xpr, user, true);
|
|
618
|
+
visitExpression( elem.on, elem, (xpr, user) => {
|
|
619
|
+
checkExpressionNotVirtual( xpr, user );
|
|
620
|
+
checkExpressionAssociationUsage( xpr, user, true );
|
|
621
|
+
// Essential check. Dependency handling for `on` conditions must change if
|
|
622
|
+
// this is allowed. See test3/Associations/Dependencies/.
|
|
592
623
|
if (xpr._artifact?.$syntax === 'calc' && !xpr._artifact.value.stored?.val)
|
|
593
624
|
error( 'ref-unexpected-calculated', [ xpr.location, user ], { '#': 'on' } );
|
|
594
|
-
});
|
|
595
|
-
if (isDollarSelfOrProjectionOperand(elem.on)) {
|
|
596
|
-
// Bare $self usages are not allowed and don't work in A2J.
|
|
597
|
-
error('expr-missing-comparison', [ elem.on.location, elem ], { id: '$self', op: '=' } );
|
|
598
|
-
}
|
|
625
|
+
} );
|
|
599
626
|
}
|
|
600
627
|
}
|
|
601
628
|
|
|
602
629
|
function checkSelectItemValue( elem ) {
|
|
603
|
-
checkExpressionAssociationUsage(elem.value, elem, false);
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
630
|
+
checkExpressionAssociationUsage( elem.value, elem, false );
|
|
631
|
+
// If a direct SQL-style cast() has no type, but type props, the compiler does not copy the type
|
|
632
|
+
// props/does not use the cast(). To avoid duplicate messages, only run this check if there is
|
|
633
|
+
// no explicit type, as otherwise we will check the cast() twice (once here, once via element).
|
|
634
|
+
if (elem.value?.op?.val === 'cast' && !elem.type) {
|
|
635
|
+
requireExplicitTypeInSqlCast( elem.value, elem );
|
|
636
|
+
checkTypeArguments( elem.value, elem );
|
|
637
|
+
}
|
|
638
|
+
visitSubExpression( elem.value, elem, (xpr) => {
|
|
639
|
+
checkGenericExpression( xpr, elem );
|
|
640
|
+
} );
|
|
609
641
|
}
|
|
610
642
|
|
|
611
643
|
function checkCalculatedElementValue( elem ) {
|
|
612
|
-
visitExpression(elem.value, elem, (xpr, user) => {
|
|
613
|
-
|
|
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) {
|
|
614
650
|
const sourceLoc = xpr.path?.[xpr.path.length - 1].location || xpr.location;
|
|
615
|
-
checkExpressionNotVirtual(xpr, user);
|
|
651
|
+
checkExpressionNotVirtual( xpr, user );
|
|
616
652
|
// For inferred (e.g. included) calc elements, this error is already emitted at the origin.
|
|
617
653
|
// And users can't change structured to non-structured elements.
|
|
618
|
-
if (!elem.$inferred &&
|
|
619
|
-
error('ref-unexpected-structured', [ sourceLoc, elem ], { '#': 'expr' } );
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
error('ref-unexpected-
|
|
654
|
+
if (!elem.$inferred && xpr._artifact._effectiveType?.elements) {
|
|
655
|
+
error( 'ref-unexpected-structured', [ sourceLoc, elem ], { '#': 'expr' } );
|
|
656
|
+
}
|
|
657
|
+
else if (xpr._artifact.target !== undefined) {
|
|
658
|
+
const variant = isComposition( model, xpr._artifact ) ? 'expr-comp' : 'expr';
|
|
659
|
+
error( 'ref-unexpected-assoc', [ sourceLoc, elem ], { '#': variant } );
|
|
660
|
+
}
|
|
661
|
+
else if (xpr._artifact.localized?.val && elem.value.stored?.val) {
|
|
662
|
+
error( 'ref-unexpected-localized', [ sourceLoc, elem ], { '#': 'calc' } );
|
|
663
|
+
}
|
|
624
664
|
}
|
|
625
|
-
});
|
|
665
|
+
} );
|
|
626
666
|
// Calculated elements must not refer to keys, because that may lead to another
|
|
627
667
|
// key in an SQL view, which is missing in OData (for on-read).
|
|
628
668
|
// Following associations does not lead to this issue.
|
|
629
|
-
if (elem.value.path && isKeyElement(elem.value._artifact) &&
|
|
630
|
-
!followsAnAssociation(elem.value.path)) {
|
|
631
|
-
error('ref-unexpected-key', [ elem.value.location, elem ], {},
|
|
632
|
-
|
|
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' );
|
|
633
673
|
}
|
|
634
674
|
}
|
|
635
675
|
|
|
@@ -676,13 +716,6 @@ function check( model ) { // = XSN
|
|
|
676
716
|
return false;
|
|
677
717
|
}
|
|
678
718
|
|
|
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
719
|
/**
|
|
687
720
|
* Check a tree-like expression for semantic validity
|
|
688
721
|
*
|
|
@@ -698,21 +731,15 @@ function check( model ) { // = XSN
|
|
|
698
731
|
// Only check associations and $self if this is not a backlink-like
|
|
699
732
|
// expression (a comparison of $self with an assoc).
|
|
700
733
|
// We don't check token-stream-like 'xpr's.
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
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 );
|
|
705
737
|
|
|
706
738
|
if (isNotSelfComparison) {
|
|
739
|
+
const op = getBinaryOp( xpr );
|
|
707
740
|
for (const arg of args) {
|
|
708
|
-
if (op?.val !== '=' && isDollarSelfOrProjectionOperand(arg))
|
|
709
|
-
|
|
710
|
-
const loc = (op?.location.endLine ? op : arg).location;
|
|
711
|
-
error('expr-invalid-operator', [ loc, user ], { op: '=', id: '$self' });
|
|
712
|
-
}
|
|
713
|
-
else {
|
|
714
|
-
checkExpressionIsNotAssocOrSelf(arg, user, allowAssocTail);
|
|
715
|
-
}
|
|
741
|
+
if (arg && !(op?.val !== '=' && isDollarSelfOrProjectionOperand( arg )))
|
|
742
|
+
checkExpressionIsNotAssocOrSelf( arg, user, allowAssocTail );
|
|
716
743
|
}
|
|
717
744
|
}
|
|
718
745
|
}
|
|
@@ -721,28 +748,23 @@ function check( model ) { // = XSN
|
|
|
721
748
|
// Arg must not be an association and not $self
|
|
722
749
|
// Only if path is not approved exists path (that is non-query position)
|
|
723
750
|
if (arg.path && arg.$expected !== undefined) { // not 'approved-exists'
|
|
724
|
-
if (arg.$expected === 'exists')
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
error('ref-unexpected-assoc', [ arg.location, user ], { '#': 'expr' } );
|
|
751
|
+
if (arg.$expected === 'exists') {
|
|
752
|
+
const variant = isComposition( model, arg._artifact ) ? 'expr-comp' : 'expr';
|
|
753
|
+
error( 'ref-unexpected-assoc', [ arg.location, user ], { '#': variant } );
|
|
754
|
+
}
|
|
729
755
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
error(
|
|
733
|
-
'$(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 } );
|
|
734
759
|
}
|
|
735
760
|
}
|
|
736
761
|
|
|
737
|
-
|
|
762
|
+
/**
|
|
763
|
+
* Return true if 'arg' is an expression argument of type association or composition.
|
|
764
|
+
*/
|
|
738
765
|
function isAssociationOperand( arg ) {
|
|
739
|
-
if (!arg.path) {
|
|
740
|
-
// Not a path, hence not an association (literal, expression, function, whatever ...)
|
|
741
|
-
return false;
|
|
742
|
-
}
|
|
743
766
|
// If it has a target, it is an association or composition
|
|
744
|
-
return
|
|
745
|
-
(arg._artifact && arg._artifact._effectiveType && arg._artifact._effectiveType.target);
|
|
767
|
+
return !!arg._artifact?._effectiveType?.target;
|
|
746
768
|
}
|
|
747
769
|
|
|
748
770
|
/**
|
|
@@ -764,17 +786,20 @@ function check( model ) { // = XSN
|
|
|
764
786
|
if (!xpr.op || !xpr.args)
|
|
765
787
|
return false;
|
|
766
788
|
|
|
767
|
-
|
|
768
789
|
// One argument must be "$self" and the other an assoc
|
|
769
790
|
if (xpr.op.val === '=' && xpr.args.length === 2) {
|
|
770
791
|
// Tree-ish expression from the compiler (not augmented)
|
|
771
|
-
|
|
772
|
-
|
|
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] ));
|
|
773
796
|
}
|
|
774
797
|
else if (xpr.args.length === 3 && xpr.args[1].val === '=') {
|
|
775
798
|
// Tree-ish expression from the compiler (not augmented)
|
|
776
|
-
|
|
777
|
-
|
|
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] ));
|
|
778
803
|
}
|
|
779
804
|
|
|
780
805
|
// Nothing else qualifies
|
|
@@ -787,19 +812,19 @@ function check( model ) { // = XSN
|
|
|
787
812
|
// Check the annotation assignments (if any) of 'annotatable', possibly using annotation
|
|
788
813
|
// definitions from 'model'. Report errors on 'options.messages.
|
|
789
814
|
//
|
|
790
|
-
// TODO: rework completely
|
|
815
|
+
// TODO: rework completely!
|
|
791
816
|
|
|
792
817
|
// Has been slightly adapted for model.vocabularies but comments need to be
|
|
793
818
|
// adapted, etc.
|
|
794
819
|
function checkAnnotationAssignment1( art, anno ) {
|
|
795
820
|
// Sanity checks (ignore broken assignments)
|
|
796
|
-
if (!anno.name
|
|
821
|
+
if (!anno.name?.path?.length)
|
|
797
822
|
return;
|
|
798
823
|
// Annotation artifact for longest path step of annotation path
|
|
799
824
|
let fromArtifact = null;
|
|
800
825
|
let pathStepsFound = 0;
|
|
801
826
|
for (let i = anno.name.path.length; i > 0; i--) {
|
|
802
|
-
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( '.' );
|
|
803
828
|
if (model.vocabularies[absoluteName]) {
|
|
804
829
|
fromArtifact = model.vocabularies[absoluteName];
|
|
805
830
|
pathStepsFound = i;
|
|
@@ -812,8 +837,8 @@ function check( model ) { // = XSN
|
|
|
812
837
|
return;
|
|
813
838
|
}
|
|
814
839
|
|
|
815
|
-
const { artifact, endOfPath } = resolvePathFrom(anno.name.path.slice(pathStepsFound),
|
|
816
|
-
|
|
840
|
+
const { artifact, endOfPath } = resolvePathFrom( anno.name.path.slice( pathStepsFound ),
|
|
841
|
+
fromArtifact );
|
|
817
842
|
|
|
818
843
|
// Check what we actually want to check
|
|
819
844
|
checkAnnotationAssignment( anno, artifact, endOfPath, art );
|
|
@@ -827,40 +852,39 @@ function check( model ) { // = XSN
|
|
|
827
852
|
if (!annoDecl || annoDecl.artifacts && !elementDecl)
|
|
828
853
|
return;
|
|
829
854
|
|
|
830
|
-
|
|
831
855
|
// Must be an annotation if found
|
|
832
856
|
if (annoDecl.kind !== 'annotation') // i.e namespace
|
|
833
857
|
return;
|
|
834
858
|
|
|
835
859
|
// Element must exist in annotation
|
|
836
860
|
if (!elementDecl) {
|
|
837
|
-
warning(null, [ anno.location || anno.name.location, art ],
|
|
838
|
-
|
|
839
|
-
|
|
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)' );
|
|
840
864
|
return;
|
|
841
865
|
}
|
|
842
866
|
|
|
843
867
|
// Sanity checks
|
|
844
868
|
if (!elementDecl._effectiveType)
|
|
845
|
-
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 ) }`);
|
|
846
870
|
|
|
847
871
|
|
|
848
872
|
// Must have literal or path unless it is a boolean
|
|
849
|
-
if (!anno.literal && !anno.path && getFinalTypeNameOf(elementDecl) !== 'cds.Boolean') {
|
|
850
|
-
if (elementDecl.type
|
|
851
|
-
warning('anno-expecting-value', [ anno.location || anno.name.location, art ],
|
|
852
|
-
|
|
873
|
+
if (!anno.literal && !anno.path && getFinalTypeNameOf( elementDecl ) !== 'cds.Boolean') {
|
|
874
|
+
if (elementDecl.type?._artifact.name.absolute) {
|
|
875
|
+
warning( 'anno-expecting-value', [ anno.location || anno.name.location, art ],
|
|
876
|
+
{ '#': 'type', type: elementDecl.type._artifact } );
|
|
853
877
|
}
|
|
854
878
|
else {
|
|
855
|
-
warning('anno-expecting-value', [ anno.location || anno.name.location, art ],
|
|
856
|
-
|
|
879
|
+
warning( 'anno-expecting-value', [ anno.location || anno.name.location, art ],
|
|
880
|
+
{ '#': 'std', anno: anno.name.absolute } );
|
|
857
881
|
}
|
|
858
882
|
|
|
859
883
|
return;
|
|
860
884
|
}
|
|
861
885
|
|
|
862
886
|
// Value must be assignable to type
|
|
863
|
-
checkValueAssignableTo(anno, anno, elementDecl, art);
|
|
887
|
+
checkValueAssignableTo( anno, anno, elementDecl, art );
|
|
864
888
|
}
|
|
865
889
|
|
|
866
890
|
// Check that annotation assignment 'value' (having 'path or 'literal' and
|
|
@@ -868,7 +892,7 @@ function check( model ) { // = XSN
|
|
|
868
892
|
// if not
|
|
869
893
|
function checkValueAssignableTo( annoDef, value, elementDecl, art ) {
|
|
870
894
|
// FIXME: We currently do not have any element declaration that could match
|
|
871
|
-
//
|
|
895
|
+
// a 'path' value, so we simply leave those alone
|
|
872
896
|
if (value.path)
|
|
873
897
|
return;
|
|
874
898
|
|
|
@@ -879,12 +903,12 @@ function check( model ) { // = XSN
|
|
|
879
903
|
if (elementDecl._effectiveType.items) {
|
|
880
904
|
// Make sure we have an array value
|
|
881
905
|
if (value.literal !== 'array') {
|
|
882
|
-
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)' );
|
|
883
907
|
return;
|
|
884
908
|
}
|
|
885
909
|
// Check each element
|
|
886
910
|
for (const valueItem of value.val)
|
|
887
|
-
checkValueAssignableTo(value, valueItem, elementDecl._effectiveType.items, art);
|
|
911
|
+
checkValueAssignableTo( value, valueItem, elementDecl._effectiveType.items, art );
|
|
888
912
|
|
|
889
913
|
return;
|
|
890
914
|
}
|
|
@@ -892,7 +916,7 @@ function check( model ) { // = XSN
|
|
|
892
916
|
// Struct expected (can only happen within arrays)?
|
|
893
917
|
if (elementDecl._effectiveType.elements) {
|
|
894
918
|
if (value.literal !== 'struct') {
|
|
895
|
-
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)' );
|
|
896
920
|
return;
|
|
897
921
|
}
|
|
898
922
|
// FIXME: Should check each element
|
|
@@ -900,44 +924,45 @@ function check( model ) { // = XSN
|
|
|
900
924
|
}
|
|
901
925
|
|
|
902
926
|
// Handle each (primitive) expected element type separately
|
|
903
|
-
|
|
904
|
-
|
|
927
|
+
// TODO: Don't rely on name; use actual type
|
|
928
|
+
const type = getFinalTypeNameOf( elementDecl );
|
|
929
|
+
if (builtins.isStringTypeName( type )) {
|
|
905
930
|
if (value.literal !== 'string' && value.literal !== 'enum' &&
|
|
906
931
|
!elementDecl._effectiveType.enum) {
|
|
907
|
-
warning(null, loc, { type, anno },
|
|
908
|
-
|
|
932
|
+
warning( null, loc, { type, anno },
|
|
933
|
+
'A string value is required for type $(TYPE) for annotation $(ANNO)' );
|
|
909
934
|
}
|
|
910
935
|
}
|
|
911
|
-
else if (builtins.isBinaryTypeName(type)) {
|
|
936
|
+
else if (builtins.isBinaryTypeName( type )) {
|
|
912
937
|
if (value.literal !== 'string' && value.literal !== 'x') {
|
|
913
|
-
warning(null, loc, { type, anno },
|
|
914
|
-
|
|
938
|
+
warning( null, loc, { type, anno },
|
|
939
|
+
'A hexadecimal string value is required for type $(TYPE) for annotation $(ANNO)' );
|
|
915
940
|
}
|
|
916
941
|
}
|
|
917
|
-
else if (builtins.isNumericTypeName(type)) {
|
|
942
|
+
else if (builtins.isNumericTypeName( type )) {
|
|
918
943
|
if (value.literal !== 'number' && value.literal !== 'enum' &&
|
|
919
944
|
!elementDecl._effectiveType.enum) {
|
|
920
|
-
warning(null, loc, { type, anno },
|
|
921
|
-
|
|
945
|
+
warning( null, loc, { type, anno },
|
|
946
|
+
'A numerical value is required for type $(TYPE) for annotation $(ANNO)' );
|
|
922
947
|
}
|
|
923
948
|
}
|
|
924
|
-
else if (builtins.isDateOrTimeTypeName(type)) {
|
|
949
|
+
else if (builtins.isDateOrTimeTypeName( type )) {
|
|
925
950
|
if (value.literal !== 'date' && value.literal !== 'time' &&
|
|
926
951
|
value.literal !== 'timestamp' && value.literal !== 'string') {
|
|
927
|
-
warning(null, loc, { type, anno },
|
|
928
|
-
|
|
929
|
-
|
|
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)' );
|
|
930
955
|
}
|
|
931
956
|
}
|
|
932
|
-
else if (builtins.isBooleanTypeName(type)) {
|
|
957
|
+
else if (builtins.isBooleanTypeName( type )) {
|
|
933
958
|
if (value.literal && value.literal !== 'boolean') {
|
|
934
|
-
warning(null, loc, { type, anno },
|
|
935
|
-
|
|
959
|
+
warning( null, loc, { type, anno },
|
|
960
|
+
'A boolean value is required for type $(TYPE) for annotation $(ANNO)' );
|
|
936
961
|
}
|
|
937
962
|
}
|
|
938
|
-
else if (builtins.isRelationTypeName(type) || builtins.isGeoTypeName(type)) {
|
|
939
|
-
warning(null, loc, { type, anno },
|
|
940
|
-
|
|
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)' );
|
|
941
966
|
}
|
|
942
967
|
else if (!elementDecl._effectiveType.enum) {
|
|
943
968
|
throw new CompilerAssertion(`Unknown primitive type name: ${ type }`);
|
|
@@ -950,22 +975,22 @@ function check( model ) { // = XSN
|
|
|
950
975
|
// Enum symbol provided and expected
|
|
951
976
|
if (!expectedEnum[value.sym.id]) {
|
|
952
977
|
// ... but no such constant
|
|
953
|
-
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)' );
|
|
954
979
|
}
|
|
955
980
|
}
|
|
956
981
|
else {
|
|
957
982
|
// Enum symbol provided but not expected
|
|
958
|
-
warning(null, loc, { id: `#${ value.sym.id }`, type, anno },
|
|
959
|
-
|
|
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)' );
|
|
960
985
|
}
|
|
961
986
|
}
|
|
962
987
|
else if (expectedEnum) {
|
|
963
988
|
// Enum symbol not provided but expected
|
|
964
|
-
const hasValidValue = Object.keys(expectedEnum)
|
|
965
|
-
.some(symbol => getEnumValue(expectedEnum[symbol]) === value.val);
|
|
989
|
+
const hasValidValue = Object.keys( expectedEnum )
|
|
990
|
+
.some( symbol => getEnumValue( expectedEnum[symbol] ) === value.val );
|
|
966
991
|
if (!hasValidValue) {
|
|
967
992
|
// ... and none of the valid enum symbols matches the value
|
|
968
|
-
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)' );
|
|
969
994
|
}
|
|
970
995
|
}
|
|
971
996
|
}
|
|
@@ -978,8 +1003,7 @@ function check( model ) { // = XSN
|
|
|
978
1003
|
return null;
|
|
979
1004
|
}
|
|
980
1005
|
|
|
981
|
-
// TODO: remove
|
|
982
|
-
|
|
1006
|
+
// TODO: remove
|
|
983
1007
|
// Return the artifact (and possibly, its element) found by following 'path'
|
|
984
1008
|
// starting at 'from'. The return value is an object { artifact, endOfPath }
|
|
985
1009
|
// with 'artifact' being the last artifact encountered on 'path' (or
|
|
@@ -1001,9 +1025,10 @@ function check( model ) { // = XSN
|
|
|
1001
1025
|
// Continue search with next path step
|
|
1002
1026
|
const nextStepEnv = (from._effectiveType || from).artifacts ||
|
|
1003
1027
|
from._effectiveType?.elements || [];
|
|
1004
|
-
return resolvePathFrom(path.slice(1), nextStepEnv[path[0].id], result);
|
|
1028
|
+
return resolvePathFrom( path.slice(1), nextStepEnv[path[0].id], result );
|
|
1005
1029
|
}
|
|
1006
1030
|
|
|
1031
|
+
// TODO: remove
|
|
1007
1032
|
// Return the absolute name of the final type of 'node'. May return 'undefined'
|
|
1008
1033
|
// for anonymous types. DO NOT USE THIS function, it has several assumptions
|
|
1009
1034
|
// which are not necessarily true.
|
|
@@ -1016,7 +1041,7 @@ function check( model ) { // = XSN
|
|
|
1016
1041
|
}
|
|
1017
1042
|
|
|
1018
1043
|
/**
|
|
1019
|
-
* Ensure that the `locale` element of sap.common.TextsAspects
|
|
1044
|
+
* Ensure that the `locale` element of `sap.common.TextsAspects`
|
|
1020
1045
|
* is a string type. This is required by CAP runtimes to work properly.
|
|
1021
1046
|
*
|
|
1022
1047
|
* @param {XSN.Model} model
|
|
@@ -1026,17 +1051,16 @@ function checkSapCommonTextsAspects( model ) {
|
|
|
1026
1051
|
const locale = model.definitions[name]?.elements?.locale;
|
|
1027
1052
|
if (locale) {
|
|
1028
1053
|
// `locale` could also be `sap.common.Locale`, which must also be a string.
|
|
1029
|
-
|
|
1030
|
-
if (type?.name?.absolute !== 'cds.String') {
|
|
1054
|
+
if (locale._effectiveType !== model.definitions['cds.String']) {
|
|
1031
1055
|
const hasCommonLocale = !!model.definitions['sap.common.Locale'];
|
|
1032
1056
|
const { error } = model.$messageFunctions;
|
|
1033
|
-
error('def-invalid-element-type', [ locale.type.location, locale ], {
|
|
1057
|
+
error( 'def-invalid-element-type', [ locale.type.location, locale ], {
|
|
1034
1058
|
'#': hasCommonLocale ? 'texts-aspect-locale' : 'std',
|
|
1035
1059
|
art: name,
|
|
1036
1060
|
elemref: 'locale',
|
|
1037
1061
|
type: 'cds.String',
|
|
1038
1062
|
othertype: 'sap.common.Locale',
|
|
1039
|
-
});
|
|
1063
|
+
} );
|
|
1040
1064
|
}
|
|
1041
1065
|
}
|
|
1042
1066
|
}
|
|
@@ -1050,12 +1074,11 @@ function checkSapCommonTextsAspects( model ) {
|
|
|
1050
1074
|
function checkSapCommonLocale( model ) {
|
|
1051
1075
|
const localeArt = model.definitions['sap.common.Locale'];
|
|
1052
1076
|
if (localeArt) {
|
|
1053
|
-
|
|
1054
|
-
if (type?.name?.absolute !== 'cds.String') {
|
|
1077
|
+
if (localeArt._effectiveType !== model.definitions['cds.String']) {
|
|
1055
1078
|
const { message } = model.$messageFunctions;
|
|
1056
|
-
message('type-expected-builtin', [ localeArt.name.location, localeArt ],
|
|
1057
|
-
|
|
1058
|
-
|
|
1079
|
+
message( 'type-expected-builtin', [ localeArt.name.location, localeArt ],
|
|
1080
|
+
{ name: 'sap.common.Locale' },
|
|
1081
|
+
'Expected $(NAME) to be a string type' );
|
|
1059
1082
|
}
|
|
1060
1083
|
}
|
|
1061
1084
|
}
|
|
@@ -1065,13 +1088,16 @@ function checkSapCommonLocale( model ) {
|
|
|
1065
1088
|
* Visits each expression.
|
|
1066
1089
|
*
|
|
1067
1090
|
* TODO: Properly visit expressions; will be improved step by step;
|
|
1068
|
-
* Currently only replaces old foreachPath().
|
|
1091
|
+
* Currently only replaces old foreachPath(), which had very poor performance.
|
|
1069
1092
|
*
|
|
1070
1093
|
* @param {any} xpr
|
|
1071
1094
|
* @param {XSN.Artifact} user
|
|
1072
1095
|
* @param {(xpr: any, user: any, parentExpr: any) => void} callback
|
|
1073
1096
|
*/
|
|
1074
1097
|
function visitExpression( xpr, user, callback ) {
|
|
1098
|
+
if (!xpr)
|
|
1099
|
+
return; // e.g. parse error
|
|
1100
|
+
|
|
1075
1101
|
callback( xpr, user, null );
|
|
1076
1102
|
visitSubExpression( xpr, user, callback );
|
|
1077
1103
|
}
|
|
@@ -1085,12 +1111,14 @@ function visitExpression( xpr, user, callback ) {
|
|
|
1085
1111
|
*/
|
|
1086
1112
|
function visitSubExpression( xpr, user, callback ) {
|
|
1087
1113
|
if (xpr.args) {
|
|
1088
|
-
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 );
|
|
1089
1115
|
// Check for illegal argument usage within the expression
|
|
1090
1116
|
for (const arg of args) {
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
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
|
+
}
|
|
1094
1122
|
}
|
|
1095
1123
|
}
|
|
1096
1124
|
|
|
@@ -1098,11 +1126,31 @@ function visitSubExpression( xpr, user, callback ) {
|
|
|
1098
1126
|
for (const arg of xpr.path) {
|
|
1099
1127
|
if (arg.where) {
|
|
1100
1128
|
callback( arg.where, user, arg );
|
|
1101
|
-
visitSubExpression(arg.where, user, callback);
|
|
1129
|
+
visitSubExpression( arg.where, user, callback );
|
|
1102
1130
|
}
|
|
1103
1131
|
}
|
|
1104
1132
|
}
|
|
1105
1133
|
}
|
|
1106
1134
|
|
|
1135
|
+
/**
|
|
1136
|
+
* Whether the given element is a composition.
|
|
1137
|
+
* TODO: `type T: Composition of E; entity V { e: T default 3 };`
|
|
1138
|
+
* See also getUnderlyingBuiltinType()/compositionTextVariant() in utils.js.
|
|
1139
|
+
*
|
|
1140
|
+
* @return {boolean}
|
|
1141
|
+
*/
|
|
1142
|
+
function isComposition( model, elem ) {
|
|
1143
|
+
elem = elem?._effectiveType;
|
|
1144
|
+
if (!elem || !elem.target)
|
|
1145
|
+
return false;
|
|
1146
|
+
do {
|
|
1147
|
+
if (elem.type?._artifact === model.definitions['cds.Composition'])
|
|
1148
|
+
return true;
|
|
1149
|
+
// Because inferred elements don't have a direct `type` property,
|
|
1150
|
+
// we need to go along the origin chain.
|
|
1151
|
+
elem = elem._origin;
|
|
1152
|
+
} while (elem);
|
|
1153
|
+
return false;
|
|
1154
|
+
}
|
|
1107
1155
|
|
|
1108
1156
|
module.exports = check;
|