@sap/cds-compiler 2.10.4 → 2.11.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 +50 -0
- package/bin/cdsc.js +42 -25
- package/bin/cdsse.js +1 -0
- package/doc/CHANGELOG_BETA.md +4 -0
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +9 -23
- package/lib/api/options.js +12 -4
- package/lib/api/validate.js +23 -2
- package/lib/backends.js +9 -8
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/message-registry.js +10 -2
- package/lib/base/messages.js +23 -9
- package/lib/base/model.js +5 -4
- package/lib/base/optionProcessorHelper.js +56 -22
- package/lib/checks/selectItems.js +4 -0
- package/lib/checks/unknownMagic.js +6 -3
- package/lib/compiler/assert-consistency.js +7 -0
- package/lib/compiler/base.js +65 -0
- package/lib/compiler/builtins.js +28 -1
- package/lib/compiler/checks.js +2 -1
- package/lib/compiler/definer.js +58 -91
- package/lib/compiler/index.js +16 -4
- package/lib/compiler/propagator.js +5 -2
- package/lib/compiler/resolver.js +93 -34
- package/lib/compiler/shared.js +29 -202
- package/lib/compiler/utils.js +173 -0
- package/lib/edm/annotations/genericTranslation.js +1 -1
- package/lib/edm/csn2edm.js +3 -2
- package/lib/edm/edmPreprocessor.js +31 -36
- package/lib/edm/edmUtils.js +3 -3
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +17 -1
- package/lib/gen/language.tokens +79 -73
- package/lib/gen/languageLexer.interp +19 -1
- package/lib/gen/languageLexer.js +779 -731
- package/lib/gen/languageLexer.tokens +71 -65
- package/lib/gen/languageParser.js +4668 -4072
- package/lib/json/from-csn.js +10 -10
- package/lib/json/to-csn.js +169 -34
- package/lib/language/antlrParser.js +11 -0
- package/lib/language/genericAntlrParser.js +72 -14
- package/lib/language/language.g4 +73 -0
- package/lib/main.d.ts +136 -17
- package/lib/main.js +3 -1
- package/lib/model/api.js +2 -2
- package/lib/model/csnRefs.js +108 -31
- package/lib/model/csnUtils.js +63 -29
- package/lib/model/enrichCsn.js +36 -9
- package/lib/model/revealInternalProperties.js +20 -4
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +29 -18
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/toCdl.js +9 -3
- package/lib/render/toHdbcds.js +16 -36
- package/lib/render/toSql.js +23 -5
- package/lib/transform/db/constraints.js +278 -119
- package/lib/transform/db/draft.js +3 -2
- package/lib/transform/db/expansion.js +6 -4
- package/lib/transform/db/flattening.js +17 -1
- package/lib/transform/db/transformExists.js +61 -2
- package/lib/transform/db/views.js +438 -0
- package/lib/transform/forHanaNew.js +56 -435
- package/lib/transform/forOdataNew.js +9 -2
- package/lib/transform/localized.js +2 -0
- package/lib/transform/transformUtilsNew.js +10 -0
- package/lib/transform/translateAssocsToJoins.js +5 -13
- package/lib/utils/file.js +5 -3
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +48 -26
- package/package.json +1 -1
package/lib/base/model.js
CHANGED
|
@@ -19,7 +19,6 @@ const queryOps = {
|
|
|
19
19
|
*/
|
|
20
20
|
const availableBetaFlags = {
|
|
21
21
|
// enabled by --beta-mode
|
|
22
|
-
foreignKeyConstraints: true,
|
|
23
22
|
toRename: true,
|
|
24
23
|
addTextsLanguageAssoc: true,
|
|
25
24
|
assocsWithParams: true,
|
|
@@ -28,7 +27,6 @@ const availableBetaFlags = {
|
|
|
28
27
|
ignoreAssocPublishingInUnion: true,
|
|
29
28
|
nestedProjections: true,
|
|
30
29
|
enableUniversalCsn: true,
|
|
31
|
-
windowFunctions: true,
|
|
32
30
|
// disabled by --beta-mode
|
|
33
31
|
nestedServices: false,
|
|
34
32
|
};
|
|
@@ -55,15 +53,18 @@ function isBetaEnabled( options, feature ) {
|
|
|
55
53
|
/**
|
|
56
54
|
* Test for deprecated feature, stored in option `deprecated`.
|
|
57
55
|
* With that, the value of `deprecated` is a dictionary of feature=>Boolean.
|
|
56
|
+
* If no `feature` is provided, checks if any deprecated option is set.
|
|
58
57
|
*
|
|
59
58
|
* Please do not move this function to the "option processor" code.
|
|
60
59
|
*
|
|
61
60
|
* @param {object} options Options
|
|
62
|
-
* @param {string} feature Feature to check for
|
|
61
|
+
* @param {string} [feature] Feature to check for
|
|
63
62
|
* @returns {boolean}
|
|
64
63
|
*/
|
|
65
|
-
function isDeprecatedEnabled( options, feature ) {
|
|
64
|
+
function isDeprecatedEnabled( options, feature = null ) {
|
|
66
65
|
const { deprecated } = options;
|
|
66
|
+
if(!feature)
|
|
67
|
+
return !!deprecated;
|
|
67
68
|
return deprecated && typeof deprecated === 'object' && deprecated[feature];
|
|
68
69
|
}
|
|
69
70
|
|
|
@@ -30,7 +30,11 @@ function createOptionProcessor() {
|
|
|
30
30
|
optionClashes: [],
|
|
31
31
|
option,
|
|
32
32
|
command,
|
|
33
|
-
positionalArgument
|
|
33
|
+
positionalArgument: (argumentDefinition) => {
|
|
34
|
+
// Default positional arguments; may be overwritten by commands.
|
|
35
|
+
_positionalArguments(argumentDefinition);
|
|
36
|
+
return optionProcessor;
|
|
37
|
+
},
|
|
34
38
|
help,
|
|
35
39
|
processCmdLine,
|
|
36
40
|
verifyOptions,
|
|
@@ -66,7 +70,12 @@ function createOptionProcessor() {
|
|
|
66
70
|
/** @type {object} */
|
|
67
71
|
const command = {
|
|
68
72
|
options: {},
|
|
73
|
+
positionalArguments: [],
|
|
69
74
|
option,
|
|
75
|
+
positionalArgument: (argumentDefinition) => {
|
|
76
|
+
_positionalArguments(argumentDefinition, command.positionalArguments);
|
|
77
|
+
return command;
|
|
78
|
+
},
|
|
70
79
|
help,
|
|
71
80
|
..._parseCommandString(cmdString)
|
|
72
81
|
};
|
|
@@ -96,28 +105,34 @@ function createOptionProcessor() {
|
|
|
96
105
|
}
|
|
97
106
|
|
|
98
107
|
/**
|
|
99
|
-
*
|
|
100
|
-
* to either require N positional arguments or a dynamic number (but at least one)
|
|
101
|
-
*
|
|
108
|
+
* Set the positional arguments to the command line processor. Instructs the processor
|
|
109
|
+
* to either require N positional arguments or a dynamic number (but at least one).
|
|
110
|
+
* Note that you can only call this function once. Only the last invocation sets
|
|
111
|
+
* the positional arguments.
|
|
112
|
+
*
|
|
113
|
+
* @param {string} argumentDefinition Positional arguments, e.g. '<input> <output>' or '<files...>'
|
|
114
|
+
* @param {object[]} argList Array, to which the parsed arguments will be added. Default is global scope.
|
|
102
115
|
*/
|
|
103
|
-
function
|
|
104
|
-
if (
|
|
116
|
+
function _positionalArguments(argumentDefinition, argList = optionProcessor.positionalArguments) {
|
|
117
|
+
if (argList.find((arg) => arg.isDynamic)) {
|
|
105
118
|
throw new Error(`Can't add positional arguments after a dynamic one`);
|
|
106
119
|
}
|
|
107
120
|
|
|
108
|
-
const registeredNames =
|
|
109
|
-
const args =
|
|
121
|
+
const registeredNames = argList.map((arg) => arg.name);
|
|
122
|
+
const args = argumentDefinition.split(' ');
|
|
110
123
|
|
|
111
124
|
for (const arg of args) {
|
|
112
|
-
|
|
125
|
+
// Remove braces, dots and camelify.
|
|
126
|
+
const argName = arg.replace('<', '').replace('>', '').replace('...', '').replace(/[ -]./g, s => s.substring(1).toUpperCase());
|
|
127
|
+
|
|
113
128
|
if (registeredNames.includes(argName)) {
|
|
114
|
-
throw new Error(`Duplicate positional argument ${arg}`);
|
|
129
|
+
throw new Error(`Duplicate positional argument: ${arg}`);
|
|
115
130
|
}
|
|
116
131
|
if (!isParam(arg) && !isDynamicPositionalArgument(arg)) {
|
|
117
132
|
throw new Error(`Unknown positional argument syntax: ${arg}`)
|
|
118
133
|
}
|
|
119
134
|
|
|
120
|
-
|
|
135
|
+
argList.push({
|
|
121
136
|
name: argName,
|
|
122
137
|
isDynamic: isDynamicPositionalArgument(arg),
|
|
123
138
|
required: true
|
|
@@ -125,7 +140,6 @@ function createOptionProcessor() {
|
|
|
125
140
|
|
|
126
141
|
registeredNames.push(argName);
|
|
127
142
|
}
|
|
128
|
-
return optionProcessor;
|
|
129
143
|
}
|
|
130
144
|
|
|
131
145
|
/**
|
|
@@ -390,21 +404,38 @@ function createOptionProcessor() {
|
|
|
390
404
|
}
|
|
391
405
|
|
|
392
406
|
// Complain about first missing positional arguments
|
|
393
|
-
const missingArg =
|
|
407
|
+
const missingArg = getCurrentPositionArguments().find((arg) => arg.required && !result.args[arg.name]);
|
|
394
408
|
if (missingArg) {
|
|
395
|
-
result.
|
|
409
|
+
const forCommand = result.command ? ` for '${ result.command }'` : '';
|
|
410
|
+
result.errors.push(`Missing positional argument${forCommand}: <${missingArg.name}${missingArg.isDynamic ? '...' : ''}>`)
|
|
396
411
|
}
|
|
397
412
|
|
|
398
413
|
return result;
|
|
399
414
|
|
|
415
|
+
/**
|
|
416
|
+
* Specific commands may have custom positional arguments.
|
|
417
|
+
* If the current one does, use it instead of the defaults.
|
|
418
|
+
*
|
|
419
|
+
* @returns {object[]} Array of positional argument configurations.
|
|
420
|
+
*/
|
|
421
|
+
function getCurrentPositionArguments() {
|
|
422
|
+
const cmd = optionProcessor.commands[result.command];
|
|
423
|
+
const args = ( cmd && cmd.positionalArguments && cmd.positionalArguments.length ) ? cmd.positionalArguments : optionProcessor.positionalArguments;
|
|
424
|
+
return args;
|
|
425
|
+
}
|
|
426
|
+
|
|
400
427
|
function processPositionalArgument(argumentValue) {
|
|
401
|
-
|
|
428
|
+
const argList = getCurrentPositionArguments();
|
|
429
|
+
if ( result.args.length === 0 && argList.length === 0 )
|
|
402
430
|
return;
|
|
403
|
-
const inBounds = result.args.length <
|
|
404
|
-
const lastIndex = inBounds ? result.args.length :
|
|
405
|
-
const nextUnsetArgument =
|
|
431
|
+
const inBounds = result.args.length < argList.length;
|
|
432
|
+
const lastIndex = inBounds ? result.args.length : argList.length - 1;
|
|
433
|
+
const nextUnsetArgument = argList[lastIndex];
|
|
406
434
|
if (!inBounds && !nextUnsetArgument.isDynamic) {
|
|
407
|
-
result.
|
|
435
|
+
if (result.command)
|
|
436
|
+
result.errors.push(`Too many arguments. '${result.command}' expects ${argList.length}`);
|
|
437
|
+
else
|
|
438
|
+
result.errors.push(`Too many arguments. Expected ${argList.length}`);
|
|
408
439
|
return;
|
|
409
440
|
}
|
|
410
441
|
result.args.length += 1;
|
|
@@ -486,7 +517,10 @@ function createOptionProcessor() {
|
|
|
486
517
|
}
|
|
487
518
|
|
|
488
519
|
if(options) {
|
|
489
|
-
[
|
|
520
|
+
[
|
|
521
|
+
'defaultBinaryLength', 'defaultStringLength',
|
|
522
|
+
/*'length', 'precision', 'scale'*/
|
|
523
|
+
].forEach(facet => {
|
|
490
524
|
if(options[facet] && isNaN(options[facet])) {
|
|
491
525
|
result.push(`Invalid value "${options[facet]}" for option "--${facet}" - not an Integer`);
|
|
492
526
|
} else {
|
|
@@ -577,12 +611,12 @@ function isLongOption(opt) {
|
|
|
577
611
|
|
|
578
612
|
// Check if 'opt' looks like a "<foobar>" parameter
|
|
579
613
|
function isParam(opt) {
|
|
580
|
-
return /^<[a-zA-Z]+>$/.test(opt);
|
|
614
|
+
return /^<[a-zA-Z-]+>$/.test(opt);
|
|
581
615
|
}
|
|
582
616
|
|
|
583
617
|
// Check if 'arg' looks like "<foobar...>"
|
|
584
618
|
function isDynamicPositionalArgument(arg) {
|
|
585
|
-
return /^<[a-zA-Z]+[.]{3}>$/.test(arg);
|
|
619
|
+
return /^<[a-zA-Z-]+[.]{3}>$/.test(arg);
|
|
586
620
|
}
|
|
587
621
|
|
|
588
622
|
module.exports = {
|
|
@@ -23,6 +23,10 @@ function validateSelectItems(query) {
|
|
|
23
23
|
'Select items starting with $(NAME) must not contain path steps of type $(TYPE)');
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
+
else if (this.options.transformation === 'hdbcds' && selectItem.xpr && selectItem.func) {
|
|
27
|
+
this.error(null, selectItem.$path,
|
|
28
|
+
'Window functions are not supported by SAP HANA CDS');
|
|
29
|
+
}
|
|
26
30
|
});
|
|
27
31
|
// .call() with 'this' to ensure we have access to the options
|
|
28
32
|
rejectManagedAssociationsAndStructuresForHdbcsNames.call(this, SELECT, SELECT.$path);
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { getVariableReplacement } = require('../model/csnUtils');
|
|
4
|
+
|
|
3
5
|
// We only care about the "wild" ones - $at is validated by the compiler
|
|
4
6
|
const magicVariables = {
|
|
5
7
|
$user: [
|
|
@@ -17,7 +19,7 @@ const magicVariables = {
|
|
|
17
19
|
*
|
|
18
20
|
* Valid ways:
|
|
19
21
|
* - We know what to do -> $user.id on HANA
|
|
20
|
-
* - The user tells us what to do -> options.
|
|
22
|
+
* - The user tells us what to do -> options.variableReplacements
|
|
21
23
|
*
|
|
22
24
|
* @param {object} parent Object with the ref as a property
|
|
23
25
|
* @param {string} name Name of the ref property on parent
|
|
@@ -28,8 +30,9 @@ function unknownMagicVariable(parent, name, ref) {
|
|
|
28
30
|
const [ head, ...rest ] = ref;
|
|
29
31
|
const tail = rest.join('.');
|
|
30
32
|
const magicVariable = magicVariables[head];
|
|
31
|
-
if (magicVariable && magicVariable.indexOf(tail) === -1
|
|
32
|
-
|
|
33
|
+
if (magicVariable && magicVariable.indexOf(tail) === -1 &&
|
|
34
|
+
getVariableReplacement(ref, this.options) === null)
|
|
35
|
+
this.error(null, parent.$location, { id: tail, elemref: parent }, 'No configuration for magic variable was provided - path $(ELEMREF), step $(ID)');
|
|
33
36
|
}
|
|
34
37
|
}
|
|
35
38
|
|
|
@@ -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
|
|
@@ -479,6 +481,7 @@ function assertConsistency( model, stage ) {
|
|
|
479
481
|
},
|
|
480
482
|
items: {
|
|
481
483
|
kind: true,
|
|
484
|
+
also: [ 0 ], // 0 for cyclic expansions
|
|
482
485
|
requires: [ 'location' ],
|
|
483
486
|
optional: [
|
|
484
487
|
'enum',
|
|
@@ -588,6 +591,8 @@ function assertConsistency( model, stage ) {
|
|
|
588
591
|
$sources: { parser: true, test: isArray( isString ) },
|
|
589
592
|
$expected: { parser: true, test: isString },
|
|
590
593
|
$messageFunctions: { test: TODO },
|
|
594
|
+
$functions: { test: TODO },
|
|
595
|
+
$volatileFunctions: { test: TODO },
|
|
591
596
|
};
|
|
592
597
|
let _noSyntaxErrors = null;
|
|
593
598
|
assertProp( model, null, stageParser ? ':parser' : ':model', null, true );
|
|
@@ -659,6 +664,8 @@ function assertConsistency( model, stage ) {
|
|
|
659
664
|
}
|
|
660
665
|
|
|
661
666
|
function standard( node, parent, prop, spec, name ) {
|
|
667
|
+
if (spec.also && spec.also.includes( node ))
|
|
668
|
+
return;
|
|
662
669
|
isObject( node, parent, prop, spec, name );
|
|
663
670
|
|
|
664
671
|
const names = Object.getOwnPropertyNames( node );
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Base Definitions for the Core Compiler
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const dictKinds = {
|
|
7
|
+
definitions: 'absolute',
|
|
8
|
+
elements: 'element',
|
|
9
|
+
enum: 'enum',
|
|
10
|
+
foreignKeys: 'key',
|
|
11
|
+
actions: 'action',
|
|
12
|
+
params: 'param',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const kindProperties = {
|
|
16
|
+
// TODO: also foreignKeys ?
|
|
17
|
+
namespace: { artifacts: true }, // on-the-fly context
|
|
18
|
+
context: { artifacts: true, normalized: 'namespace' },
|
|
19
|
+
service: { artifacts: true, normalized: 'namespace' },
|
|
20
|
+
entity: { elements: true, actions: true, params: () => false },
|
|
21
|
+
select: { normalized: 'select', elements: true },
|
|
22
|
+
$join: { normalized: 'select' },
|
|
23
|
+
$tableAlias: { normalized: 'alias' }, // table alias in select
|
|
24
|
+
$self: { normalized: 'alias' }, // table alias in select
|
|
25
|
+
$navElement: { normalized: 'element' },
|
|
26
|
+
$inline: { normalized: 'element' }, // column with inline property
|
|
27
|
+
event: { elements: true },
|
|
28
|
+
type: { elements: propExists, enum: propExists },
|
|
29
|
+
aspect: { elements: propExists },
|
|
30
|
+
annotation: { elements: propExists, enum: propExists },
|
|
31
|
+
enum: { normalized: 'element' },
|
|
32
|
+
element: { elements: propExists, enum: propExists, dict: 'elements' },
|
|
33
|
+
mixin: { normalized: 'alias' },
|
|
34
|
+
action: {
|
|
35
|
+
params: () => false, elements: () => false, enum: () => false, dict: 'actions',
|
|
36
|
+
}, // no extend params, only annotate
|
|
37
|
+
function: {
|
|
38
|
+
params: () => false, elements: () => false, enum: () => false, normalized: 'action',
|
|
39
|
+
}, // no extend params, only annotate
|
|
40
|
+
key: { normalized: 'element' },
|
|
41
|
+
param: { elements: () => false, enum: () => false, dict: 'params' },
|
|
42
|
+
source: { artifacts: true }, // TODO -> $source
|
|
43
|
+
using: {},
|
|
44
|
+
extend: {
|
|
45
|
+
isExtension: true,
|
|
46
|
+
noDep: 'special',
|
|
47
|
+
elements: true, /* only for parse-cdl */
|
|
48
|
+
actions: true, /* only for parse-cdl */
|
|
49
|
+
},
|
|
50
|
+
annotate: {
|
|
51
|
+
isExtension: true, noDep: 'special', elements: true, enum: true, actions: true, params: true,
|
|
52
|
+
},
|
|
53
|
+
builtin: {}, // = CURRENT_DATE, TODO: improve
|
|
54
|
+
$parameters: {}, // $parameters in query entities
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function propExists( prop, parent ) {
|
|
58
|
+
const obj = parent.returns || parent;
|
|
59
|
+
return (obj.items || obj.targetAspect || obj)[prop];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = {
|
|
63
|
+
dictKinds,
|
|
64
|
+
kindProperties,
|
|
65
|
+
};
|
package/lib/compiler/builtins.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const { forEachInDict } = require('../base/dictionaries');
|
|
6
6
|
const { builtinLocation } = require('../base/location');
|
|
7
|
-
const { setProp } = require('
|
|
7
|
+
const { setProp } = require('./utils');
|
|
8
8
|
|
|
9
9
|
const core = {
|
|
10
10
|
String: { parameters: [ 'length' ], category: 'string' },
|
|
@@ -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`.
|
|
@@ -269,6 +294,8 @@ module.exports = {
|
|
|
269
294
|
functionsWithoutParens,
|
|
270
295
|
specialFunctions,
|
|
271
296
|
initBuiltins,
|
|
297
|
+
isInReservedNamespace,
|
|
298
|
+
isBuiltinType,
|
|
272
299
|
isIntegerTypeName,
|
|
273
300
|
isDecimalTypeName,
|
|
274
301
|
isNumericTypeName,
|
package/lib/compiler/checks.js
CHANGED
|
@@ -632,8 +632,9 @@ function check( model ) { // = XSN
|
|
|
632
632
|
* @returns {void}
|
|
633
633
|
*/
|
|
634
634
|
function checkTokenStreamExpression(xpr, allowAssocTail) {
|
|
635
|
+
const args = Array.isArray(xpr.args) ? xpr.args : Object.values(xpr.args || {});
|
|
635
636
|
// Check for illegal argument usage within the expression
|
|
636
|
-
for (const arg of
|
|
637
|
+
for (const arg of args) {
|
|
637
638
|
if (isVirtualElement(arg))
|
|
638
639
|
error(null, arg.location, 'Virtual elements can\'t be used in an expression');
|
|
639
640
|
|