@sap/cds-compiler 2.10.4 → 2.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +136 -0
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +10 -8
- package/bin/cdsc.js +58 -35
- package/bin/cdsse.js +1 -0
- package/bin/cdsv2m.js +3 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +16 -0
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +10 -36
- package/lib/api/options.js +17 -8
- package/lib/api/validate.js +30 -3
- package/lib/backends.js +12 -13
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/keywords.js +3 -2
- package/lib/base/message-registry.js +64 -11
- package/lib/base/messages.js +38 -18
- package/lib/base/model.js +6 -4
- package/lib/base/optionProcessorHelper.js +148 -86
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -0
- package/lib/checks/sql-snippets.js +93 -0
- package/lib/checks/unknownMagic.js +6 -3
- package/lib/checks/validator.js +8 -0
- package/lib/compiler/assert-consistency.js +14 -5
- package/lib/compiler/base.js +64 -0
- package/lib/compiler/builtins.js +62 -16
- package/lib/compiler/checks.js +34 -10
- package/lib/compiler/definer.js +91 -112
- package/lib/compiler/index.js +30 -30
- package/lib/compiler/propagator.js +8 -4
- package/lib/compiler/resolver.js +279 -63
- package/lib/compiler/shared.js +65 -230
- package/lib/compiler/utils.js +191 -0
- package/lib/edm/annotations/genericTranslation.js +35 -18
- package/lib/edm/annotations/preprocessAnnotations.js +1 -1
- package/lib/edm/csn2edm.js +4 -3
- package/lib/edm/edm.js +8 -8
- package/lib/edm/edmPreprocessor.js +61 -59
- package/lib/edm/edmUtils.js +14 -15
- package/lib/gen/Dictionary.json +82 -40
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +19 -1
- package/lib/gen/language.tokens +80 -73
- package/lib/gen/languageLexer.interp +27 -1
- package/lib/gen/languageLexer.js +925 -826
- package/lib/gen/languageLexer.tokens +72 -65
- package/lib/gen/languageParser.js +4817 -4102
- package/lib/json/from-csn.js +57 -26
- package/lib/json/to-csn.js +244 -51
- package/lib/language/antlrParser.js +12 -1
- package/lib/language/docCommentParser.js +1 -1
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +106 -30
- package/lib/language/language.g4 +200 -70
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +220 -21
- package/lib/main.js +6 -3
- package/lib/model/api.js +2 -2
- package/lib/model/csnRefs.js +218 -86
- package/lib/model/csnUtils.js +99 -178
- package/lib/model/enrichCsn.js +84 -43
- package/lib/model/revealInternalProperties.js +25 -8
- package/lib/model/sortViews.js +8 -1
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +33 -18
- package/lib/render/.eslintrc.json +1 -2
- package/lib/render/DuplicateChecker.js +2 -2
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +202 -82
- package/lib/render/toHdbcds.js +194 -135
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +91 -51
- package/lib/render/utils/common.js +24 -5
- package/lib/render/utils/sql.js +6 -4
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/applyTransformations.js +189 -0
- package/lib/transform/db/associations.js +389 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +275 -119
- package/lib/transform/db/draft.js +6 -4
- package/lib/transform/db/expansion.js +10 -9
- package/lib/transform/db/flattening.js +23 -8
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +106 -25
- package/lib/transform/db/views.js +485 -0
- package/lib/transform/forHanaNew.js +90 -1036
- package/lib/transform/forOdataNew.js +11 -3
- package/lib/transform/localized.js +5 -14
- package/lib/transform/odata/generateForeignKeyElements.js +2 -2
- package/lib/transform/transformUtilsNew.js +34 -20
- package/lib/transform/translateAssocsToJoins.js +15 -23
- package/lib/transform/universalCsnEnricher.js +217 -47
- package/lib/utils/file.js +13 -6
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +55 -27
- package/package.json +1 -1
- package/lib/transform/db/helpers.js +0 -58
|
@@ -98,6 +98,8 @@ function assertConsistency( model, stage ) {
|
|
|
98
98
|
'$blocks',
|
|
99
99
|
'$newfeatures',
|
|
100
100
|
'$messageFunctions',
|
|
101
|
+
'$functions',
|
|
102
|
+
'$volatileFunctions',
|
|
101
103
|
],
|
|
102
104
|
},
|
|
103
105
|
':parser': { // top-level from parser
|
|
@@ -247,7 +249,7 @@ function assertConsistency( model, stage ) {
|
|
|
247
249
|
'name', '$parens', 'quantifier', 'mixin', 'excludingDict', 'columns', 'elements', '_deps',
|
|
248
250
|
'where', 'groupBy', 'having', 'orderBy', '$orderBy', 'limit',
|
|
249
251
|
'_projections', '_block', '_parent', '_main', '_effectiveType', '$expand',
|
|
250
|
-
'$tableAliases', 'kind', '_$next', '_combined', '$inlines',
|
|
252
|
+
'$tableAliases', 'kind', '_$next', '_combined', '$inlines', '_status',
|
|
251
253
|
],
|
|
252
254
|
},
|
|
253
255
|
none: { optional: () => true }, // parse error
|
|
@@ -422,9 +424,12 @@ function assertConsistency( model, stage ) {
|
|
|
422
424
|
val: {
|
|
423
425
|
test: isVal, // the following for array/struct value
|
|
424
426
|
requires: [ 'location' ],
|
|
425
|
-
optional: [
|
|
427
|
+
optional: [
|
|
428
|
+
'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicate', 'upTo',
|
|
429
|
+
],
|
|
426
430
|
// TODO: restrict path to #simplePath
|
|
427
431
|
},
|
|
432
|
+
upTo: { test: TODO },
|
|
428
433
|
struct: { inherits: 'val', test: isDictionary( definition ) }, // def because double @
|
|
429
434
|
args: {
|
|
430
435
|
inherits: 'value',
|
|
@@ -479,6 +484,7 @@ function assertConsistency( model, stage ) {
|
|
|
479
484
|
},
|
|
480
485
|
items: {
|
|
481
486
|
kind: true,
|
|
487
|
+
also: [ 0 ], // 0 for cyclic expansions
|
|
482
488
|
requires: [ 'location' ],
|
|
483
489
|
optional: [
|
|
484
490
|
'enum',
|
|
@@ -539,7 +545,7 @@ function assertConsistency( model, stage ) {
|
|
|
539
545
|
// query specific
|
|
540
546
|
'where', 'columns', 'mixin', 'quantifier', 'offset',
|
|
541
547
|
'orderBy', '$orderBy', 'groupBy', 'excludingDict', 'having',
|
|
542
|
-
'limit',
|
|
548
|
+
'limit', '_status',
|
|
543
549
|
],
|
|
544
550
|
},
|
|
545
551
|
_leadingQuery: { kind: true, test: TODO },
|
|
@@ -588,6 +594,8 @@ function assertConsistency( model, stage ) {
|
|
|
588
594
|
$sources: { parser: true, test: isArray( isString ) },
|
|
589
595
|
$expected: { parser: true, test: isString },
|
|
590
596
|
$messageFunctions: { test: TODO },
|
|
597
|
+
$functions: { test: TODO },
|
|
598
|
+
$volatileFunctions: { test: TODO },
|
|
591
599
|
};
|
|
592
600
|
let _noSyntaxErrors = null;
|
|
593
601
|
assertProp( model, null, stageParser ? ':parser' : ':model', null, true );
|
|
@@ -659,6 +667,8 @@ function assertConsistency( model, stage ) {
|
|
|
659
667
|
}
|
|
660
668
|
|
|
661
669
|
function standard( node, parent, prop, spec, name ) {
|
|
670
|
+
if (spec.also && spec.also.includes( node ))
|
|
671
|
+
return;
|
|
662
672
|
isObject( node, parent, prop, spec, name );
|
|
663
673
|
|
|
664
674
|
const names = Object.getOwnPropertyNames( node );
|
|
@@ -785,8 +795,7 @@ function assertConsistency( model, stage ) {
|
|
|
785
795
|
}
|
|
786
796
|
|
|
787
797
|
function at( nodes, prop, name ) {
|
|
788
|
-
|
|
789
|
-
const n = name ? (typeof name === 'number' ? ` for index ${ name }` : ` for "${ name }"`) : '';
|
|
798
|
+
const n = name && (typeof name === 'number' ? ` for index ${ name }` : ` for "${ name }"`) || '';
|
|
790
799
|
const loc = nodes.find( o => o && typeof o === 'object' && (o.location || o.start) );
|
|
791
800
|
const f = (prop) ? `${ n } in property '${ prop }'` : n;
|
|
792
801
|
const l = locationString( loc && loc.location || loc || model.location );
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Base Definitions for the Core Compiler
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const dictKinds = {
|
|
6
|
+
definitions: 'absolute',
|
|
7
|
+
elements: 'element',
|
|
8
|
+
enum: 'enum',
|
|
9
|
+
foreignKeys: 'key',
|
|
10
|
+
actions: 'action',
|
|
11
|
+
params: 'param',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const kindProperties = {
|
|
15
|
+
// TODO: also foreignKeys ?
|
|
16
|
+
namespace: { artifacts: true }, // on-the-fly context
|
|
17
|
+
context: { artifacts: true, normalized: 'namespace' },
|
|
18
|
+
service: { artifacts: true, normalized: 'namespace' },
|
|
19
|
+
entity: { elements: true, actions: true, params: () => false },
|
|
20
|
+
select: { normalized: 'select', elements: true },
|
|
21
|
+
$join: { normalized: 'select' },
|
|
22
|
+
$tableAlias: { normalized: 'alias' }, // table alias in select
|
|
23
|
+
$self: { normalized: 'alias' }, // table alias in select
|
|
24
|
+
$navElement: { normalized: 'element' },
|
|
25
|
+
$inline: { normalized: 'element' }, // column with inline property
|
|
26
|
+
event: { elements: true },
|
|
27
|
+
type: { elements: propExists, enum: propExists },
|
|
28
|
+
aspect: { elements: propExists },
|
|
29
|
+
annotation: { elements: propExists, enum: propExists },
|
|
30
|
+
enum: { normalized: 'element' },
|
|
31
|
+
element: { elements: propExists, enum: propExists, dict: 'elements' },
|
|
32
|
+
mixin: { normalized: 'alias' },
|
|
33
|
+
action: {
|
|
34
|
+
params: () => false, elements: () => false, enum: () => false, dict: 'actions',
|
|
35
|
+
}, // no extend params, only annotate
|
|
36
|
+
function: {
|
|
37
|
+
params: () => false, elements: () => false, enum: () => false, normalized: 'action',
|
|
38
|
+
}, // no extend params, only annotate
|
|
39
|
+
key: { normalized: 'element' },
|
|
40
|
+
param: { elements: () => false, enum: () => false, dict: 'params' },
|
|
41
|
+
source: { artifacts: true }, // TODO -> $source
|
|
42
|
+
using: {},
|
|
43
|
+
extend: {
|
|
44
|
+
isExtension: true,
|
|
45
|
+
noDep: 'special',
|
|
46
|
+
elements: true, /* only for parse-cdl */
|
|
47
|
+
actions: true, /* only for parse-cdl */
|
|
48
|
+
},
|
|
49
|
+
annotate: {
|
|
50
|
+
isExtension: true, noDep: 'special', elements: true, enum: true, actions: true, params: true,
|
|
51
|
+
},
|
|
52
|
+
builtin: {}, // = CURRENT_DATE, TODO: improve
|
|
53
|
+
$parameters: {}, // $parameters in query entities
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function propExists( prop, parent ) {
|
|
57
|
+
const obj = parent.returns || parent;
|
|
58
|
+
return (obj.items || obj.targetAspect || obj)[prop];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
dictKinds,
|
|
63
|
+
kindProperties,
|
|
64
|
+
};
|
package/lib/compiler/builtins.js
CHANGED
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
|
-
const { forEachInDict } = require('../base/dictionaries');
|
|
6
5
|
const { builtinLocation } = require('../base/location');
|
|
7
|
-
const { setProp } = require('
|
|
6
|
+
const { setProp } = require('./utils');
|
|
8
7
|
|
|
9
8
|
const core = {
|
|
10
9
|
String: { parameters: [ 'length' ], category: 'string' },
|
|
@@ -82,18 +81,19 @@ const specialFunctions = {
|
|
|
82
81
|
*/
|
|
83
82
|
const magicVariables = {
|
|
84
83
|
$user: {
|
|
84
|
+
// id and locale are always available
|
|
85
85
|
elements: { id: {}, locale: {} },
|
|
86
86
|
// Allow $user.<any>
|
|
87
87
|
$uncheckedElements: true,
|
|
88
88
|
// Allow shortcut in CDL: `$user` becomes `$user.id` in CSN.
|
|
89
89
|
$autoElement: 'id',
|
|
90
|
-
},
|
|
91
|
-
$at: {
|
|
90
|
+
},
|
|
91
|
+
$at: { // CDS-specific, not part of SQL
|
|
92
92
|
elements: {
|
|
93
93
|
from: {}, to: {},
|
|
94
94
|
},
|
|
95
95
|
},
|
|
96
|
-
$now: {},
|
|
96
|
+
$now: {}, // Dito
|
|
97
97
|
$session: {
|
|
98
98
|
// In ABAP CDS session variables are accessed in a generic way via
|
|
99
99
|
// the pseudo variable $session.
|
|
@@ -167,6 +167,31 @@ function isRelationTypeName(typeName) {
|
|
|
167
167
|
return typeCategories.relation.includes(typeName);
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Checks whether the given absolute path is inside a reserved namespace.
|
|
172
|
+
*
|
|
173
|
+
* @param {string} absolute
|
|
174
|
+
* @returns {boolean}
|
|
175
|
+
*/
|
|
176
|
+
function isInReservedNamespace(absolute) {
|
|
177
|
+
return absolute.startsWith( 'cds.') &&
|
|
178
|
+
!absolute.match(/^cds\.foundation(\.|$)/) &&
|
|
179
|
+
!absolute.match(/^cds\.outbox(\.|$)/); // Requested by Node runtime
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Tell if a type is (directly) a builtin type
|
|
184
|
+
* Note that in CSN builtins are not in the definition of the model, so we can only
|
|
185
|
+
* check against their absolute names. Builtin types are "cds.<something>", i.e. they
|
|
186
|
+
* are directly in 'cds', but not for example in 'cds.foundation'.
|
|
187
|
+
*
|
|
188
|
+
* @param {string} type
|
|
189
|
+
* @returns {boolean}
|
|
190
|
+
*/
|
|
191
|
+
function isBuiltinType(type) {
|
|
192
|
+
return typeof type === 'string' && isInReservedNamespace(type);
|
|
193
|
+
}
|
|
194
|
+
|
|
170
195
|
/**
|
|
171
196
|
* Add CDS builtins like the `cds` namespace with types like `cds.Integer` to
|
|
172
197
|
* `definitions` of the XSN model as well as to `$builtins`.
|
|
@@ -174,6 +199,7 @@ function isRelationTypeName(typeName) {
|
|
|
174
199
|
* @param {XSN.Model} model XSN model without CDS builtins
|
|
175
200
|
*/
|
|
176
201
|
function initBuiltins( model ) {
|
|
202
|
+
const { options } = model;
|
|
177
203
|
setMagicVariables( magicVariables );
|
|
178
204
|
// namespace:"cds" stores the builtins ---
|
|
179
205
|
const cds = createNamespace( 'cds', 'reserved' );
|
|
@@ -241,27 +267,45 @@ function initBuiltins( model ) {
|
|
|
241
267
|
for (const name in builtins) {
|
|
242
268
|
const magic = builtins[name];
|
|
243
269
|
// TODO: rename to $builtinFunction
|
|
244
|
-
const art = { kind: 'builtin', name: {
|
|
270
|
+
const art = { kind: 'builtin', name: { element: name, id: name } };
|
|
245
271
|
artifacts[name] = art;
|
|
246
|
-
|
|
247
|
-
art.elements = forEachInDict( magic.elements, (e, n) => magicElement( e, n, art ));
|
|
272
|
+
|
|
248
273
|
if (magic.$autoElement)
|
|
249
274
|
art.$autoElement = magic.$autoElement;
|
|
250
275
|
if (magic.$uncheckedElements)
|
|
251
276
|
art.$uncheckedElements = magic.$uncheckedElements;
|
|
277
|
+
|
|
278
|
+
createMagicElements( art, magic.elements );
|
|
279
|
+
if (options.variableReplacements)
|
|
280
|
+
createMagicElements( art, options.variableReplacements[name] );
|
|
252
281
|
// setProp( art, '_effectiveType', art );
|
|
253
282
|
}
|
|
254
283
|
model.$magicVariables = { kind: '$magicVariables', artifacts };
|
|
255
284
|
}
|
|
256
285
|
|
|
257
|
-
function
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
286
|
+
function createMagicElements( art, elements ) {
|
|
287
|
+
if (!elements)
|
|
288
|
+
return;
|
|
289
|
+
|
|
290
|
+
const names = Object.keys(elements);
|
|
291
|
+
if (names.length > 0 && !art.elements)
|
|
292
|
+
art.elements = Object.create(null);
|
|
293
|
+
|
|
294
|
+
for (const n of names) {
|
|
295
|
+
const magic = {
|
|
296
|
+
kind: 'builtin',
|
|
297
|
+
name: { id: n, element: `${ art.name.element }.${ n }` },
|
|
298
|
+
};
|
|
299
|
+
// Propagate this property so that it is available for sub-elements.
|
|
300
|
+
if (art.$uncheckedElements)
|
|
301
|
+
magic.$uncheckedElements = art.$uncheckedElements;
|
|
302
|
+
setProp( magic, '_parent', art );
|
|
303
|
+
// setProp( magic, '_effectiveType', magic );
|
|
304
|
+
if (elements[n] && typeof elements[n] === 'object')
|
|
305
|
+
createMagicElements(magic, elements[n]);
|
|
306
|
+
|
|
307
|
+
art.elements[n] = magic;
|
|
308
|
+
}
|
|
265
309
|
}
|
|
266
310
|
}
|
|
267
311
|
|
|
@@ -269,6 +313,8 @@ module.exports = {
|
|
|
269
313
|
functionsWithoutParens,
|
|
270
314
|
specialFunctions,
|
|
271
315
|
initBuiltins,
|
|
316
|
+
isInReservedNamespace,
|
|
317
|
+
isBuiltinType,
|
|
272
318
|
isIntegerTypeName,
|
|
273
319
|
isDecimalTypeName,
|
|
274
320
|
isNumericTypeName,
|
package/lib/compiler/checks.js
CHANGED
|
@@ -87,6 +87,17 @@ function check( model ) { // = XSN
|
|
|
87
87
|
'Keyword “localized” may only be used in combination with string types');
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
+
// "key" keyword at localized element in SELECT list.
|
|
91
|
+
// TODO: This check should be moved to localized.js
|
|
92
|
+
if (elem.key && elem.key.val && elem._main && elem._main.query) {
|
|
93
|
+
// original element is localized but not key, as that would have
|
|
94
|
+
// already resulted in a warning
|
|
95
|
+
if (elem._origin && elem._origin.localized && elem._origin.localized.val &&
|
|
96
|
+
( !elem._origin.key || !elem._origin.key.val)) {
|
|
97
|
+
warning('localized-key', [ elem.key.location, elem ], { keyword: 'localized' },
|
|
98
|
+
'Keyword $(KEYWORD) is ignored for primary keys');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
90
101
|
}
|
|
91
102
|
|
|
92
103
|
function checkQuery( query ) {
|
|
@@ -334,11 +345,17 @@ function check( model ) { // = XSN
|
|
|
334
345
|
// Max cardinalities must be a positive number or '*'
|
|
335
346
|
for (const prop of [ 'sourceMax', 'targetMax' ]) {
|
|
336
347
|
if (elem.cardinality[prop]) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
348
|
+
const { literal, val, location } = elem.cardinality[prop];
|
|
349
|
+
if (!(literal === 'number' && val > 0 ||
|
|
350
|
+
literal === 'string' && val === '*')) {
|
|
351
|
+
error('invalid-cardinality', [ location, elem ], { '#': prop, code: val }, {
|
|
352
|
+
// eslint-disable-next-line max-len
|
|
353
|
+
std: 'Value $(CODE) is invalid for maximum cardinality, expecting a positive number or ‘*’',
|
|
354
|
+
// eslint-disable-next-line max-len
|
|
355
|
+
sourceMax: 'Value $(CODE) is invalid for maximum source cardinality, expecting a positive number or ‘*’',
|
|
356
|
+
// eslint-disable-next-line max-len
|
|
357
|
+
targetMax: 'Value $(CODE) is invalid for maximum target cardinality, expecting a positive number or ‘*’',
|
|
358
|
+
});
|
|
342
359
|
}
|
|
343
360
|
}
|
|
344
361
|
}
|
|
@@ -348,10 +365,16 @@ function check( model ) { // = XSN
|
|
|
348
365
|
// from-csn.json (expected non-negative number)
|
|
349
366
|
for (const prop of [ 'sourceMin', 'targetMin' ]) {
|
|
350
367
|
if (elem.cardinality[prop]) {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
368
|
+
const { literal, val, location } = elem.cardinality[prop];
|
|
369
|
+
if (!(literal === 'number' && val >= 0)) {
|
|
370
|
+
error('invalid-cardinality', [ location, elem ], { '#': prop, code: val }, {
|
|
371
|
+
// eslint-disable-next-line max-len
|
|
372
|
+
std: 'Value $(CODE) is invalid for minimum cardinality, expecting a non-negative number',
|
|
373
|
+
// eslint-disable-next-line max-len
|
|
374
|
+
targetMin: 'Value $(CODE) is invalid for minimum target cardinality, expecting a non-negative number',
|
|
375
|
+
// eslint-disable-next-line max-len
|
|
376
|
+
sourceMin: 'Value $(CODE) is invalid for minimum source cardinality, expecting a non-negative number',
|
|
377
|
+
});
|
|
355
378
|
}
|
|
356
379
|
}
|
|
357
380
|
}
|
|
@@ -632,8 +655,9 @@ function check( model ) { // = XSN
|
|
|
632
655
|
* @returns {void}
|
|
633
656
|
*/
|
|
634
657
|
function checkTokenStreamExpression(xpr, allowAssocTail) {
|
|
658
|
+
const args = Array.isArray(xpr.args) ? xpr.args : Object.values(xpr.args || {});
|
|
635
659
|
// Check for illegal argument usage within the expression
|
|
636
|
-
for (const arg of
|
|
660
|
+
for (const arg of args) {
|
|
637
661
|
if (isVirtualElement(arg))
|
|
638
662
|
error(null, arg.location, 'Virtual elements can\'t be used in an expression');
|
|
639
663
|
|