@sap/cds-compiler 3.4.4 → 3.5.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 +58 -0
- package/README.md +1 -0
- package/bin/cds_update_identifiers.js +5 -5
- package/bin/cdsc.js +12 -12
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +9 -1
- package/doc/CHANGELOG_DEPRECATED.md +2 -0
- package/lib/api/main.js +58 -59
- package/lib/api/options.js +4 -2
- package/lib/api/validate.js +2 -2
- package/lib/base/cleanSymbols.js +2 -3
- package/lib/base/dictionaries.js +6 -6
- package/lib/base/error.js +2 -2
- package/lib/base/keywords.js +6 -6
- package/lib/base/location.js +11 -12
- package/lib/base/message-registry.js +124 -28
- package/lib/base/messages.js +247 -179
- package/lib/base/model.js +14 -11
- package/lib/base/node-helpers.js +9 -10
- package/lib/base/optionProcessorHelper.js +138 -129
- package/lib/checks/actionsFunctions.js +5 -5
- package/lib/checks/annotationsOData.js +4 -4
- package/lib/checks/arrayOfs.js +1 -1
- package/lib/checks/cdsPersistence.js +1 -1
- package/lib/checks/checkForTypes.js +3 -3
- package/lib/checks/defaultValues.js +3 -3
- package/lib/checks/elements.js +7 -7
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/foreignKeys.js +1 -1
- package/lib/checks/invalidTarget.js +4 -4
- package/lib/checks/managedInType.js +1 -1
- package/lib/checks/managedWithoutKeys.js +1 -1
- package/lib/checks/nonexpandableStructured.js +5 -3
- package/lib/checks/nullableKeys.js +1 -1
- package/lib/checks/onConditions.js +5 -6
- package/lib/checks/parameters.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -2
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +4 -4
- package/lib/checks/types.js +7 -7
- package/lib/checks/utils.js +4 -4
- package/lib/checks/validator.js +16 -13
- package/lib/compiler/.eslintrc.json +1 -1
- package/lib/compiler/assert-consistency.js +0 -1
- package/lib/compiler/builtins.js +1 -1
- package/lib/compiler/checks.js +73 -15
- package/lib/compiler/define.js +3 -7
- package/lib/compiler/extend.js +212 -32
- package/lib/compiler/finalize-parse-cdl.js +7 -2
- package/lib/compiler/index.js +17 -14
- package/lib/compiler/populate.js +2 -5
- package/lib/compiler/propagator.js +2 -0
- package/lib/compiler/shared.js +23 -12
- package/lib/compiler/tweak-assocs.js +5 -6
- package/lib/compiler/utils.js +6 -0
- package/lib/edm/annotations/genericTranslation.js +553 -319
- package/lib/edm/annotations/preprocessAnnotations.js +39 -35
- package/lib/edm/csn2edm.js +88 -75
- package/lib/edm/edm.js +17 -3
- package/lib/edm/edmAnnoPreprocessor.js +5 -5
- package/lib/edm/edmPreprocessor.js +106 -76
- package/lib/edm/edmUtils.js +41 -2
- package/lib/gen/Dictionary.json +34 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +66 -63
- package/lib/gen/language.tokens +81 -81
- package/lib/gen/languageLexer.interp +4 -10
- package/lib/gen/languageLexer.js +854 -869
- package/lib/gen/languageLexer.tokens +79 -81
- package/lib/gen/languageParser.js +14360 -14146
- package/lib/inspect/inspectModelStatistics.js +2 -2
- package/lib/inspect/inspectPropagation.js +6 -6
- package/lib/inspect/inspectUtils.js +2 -2
- package/lib/json/from-csn.js +82 -40
- package/lib/json/to-csn.js +82 -157
- package/lib/language/.eslintrc.json +1 -4
- package/lib/language/genericAntlrParser.js +59 -38
- package/lib/language/language.g4 +1508 -1490
- package/lib/language/multiLineStringParser.js +1 -1
- package/lib/main.js +3 -3
- package/lib/model/csnUtils.js +130 -122
- package/lib/model/revealInternalProperties.js +1 -1
- package/lib/model/sortViews.js +4 -6
- package/lib/modelCompare/utils/filter.js +4 -3
- package/lib/optionProcessor.js +5 -0
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +12 -12
- package/lib/render/toCdl.js +225 -159
- package/lib/render/toHdbcds.js +63 -63
- package/lib/render/toRename.js +5 -5
- package/lib/render/toSql.js +55 -65
- package/lib/render/utils/common.js +20 -37
- package/lib/render/utils/delta.js +3 -3
- package/lib/render/utils/sql.js +22 -6
- package/lib/render/utils/stringEscapes.js +3 -3
- package/lib/transform/db/applyTransformations.js +3 -3
- package/lib/transform/db/assertUnique.js +13 -12
- package/lib/transform/db/associations.js +5 -5
- package/lib/transform/db/cdsPersistence.js +10 -8
- package/lib/transform/db/constraints.js +14 -14
- package/lib/transform/db/expansion.js +20 -22
- package/lib/transform/db/flattening.js +24 -42
- package/lib/transform/db/groupByOrderBy.js +3 -3
- package/lib/transform/db/temporal.js +6 -6
- package/lib/transform/db/transformExists.js +23 -23
- package/lib/transform/db/views.js +16 -16
- package/lib/transform/draft/db.js +10 -10
- package/lib/transform/draft/odata.js +2 -2
- package/lib/transform/forOdataNew.js +12 -40
- package/lib/transform/forRelationalDB.js +17 -7
- package/lib/transform/localized.js +2 -2
- package/lib/transform/odata/toFinalBaseType.js +41 -27
- package/lib/transform/odata/typesExposure.js +106 -62
- package/lib/transform/parseExpr.js +209 -106
- package/lib/transform/transformUtilsNew.js +2 -2
- package/lib/transform/translateAssocsToJoins.js +24 -19
- package/lib/transform/universalCsn/coreComputed.js +10 -10
- package/lib/transform/universalCsn/universalCsnEnricher.js +26 -26
- package/lib/transform/universalCsn/utils.js +3 -3
- package/lib/utils/file.js +5 -5
- package/lib/utils/moduleResolve.js +13 -13
- package/lib/utils/objectUtils.js +6 -6
- package/lib/utils/term.js +5 -2
- package/lib/utils/timetrace.js +51 -24
- package/package.json +5 -7
- package/share/messages/check-proper-type-of.md +1 -1
- package/share/messages/message-explanations.json +1 -1
- package/share/messages/redirected-to-complex.md +4 -4
- package/share/messages/{syntax-expecting-integer.md → syntax-expecting-unsigned-int.md} +7 -4
|
@@ -10,7 +10,7 @@ const { term } = require('../utils/term');
|
|
|
10
10
|
* @param {CSN.Options} options
|
|
11
11
|
* @returns {string}
|
|
12
12
|
*/
|
|
13
|
-
function inspectModelStatistics(xsn, options) {
|
|
13
|
+
function inspectModelStatistics( xsn, options ) {
|
|
14
14
|
let result = '';
|
|
15
15
|
|
|
16
16
|
// Default color mode is 'auto'
|
|
@@ -45,7 +45,7 @@ ${ color.underline('vocabularies') }: ${ Object.keys(xsn.vocabularies || {}).len
|
|
|
45
45
|
return result;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
function countDefinitionKinds(xsn) {
|
|
48
|
+
function countDefinitionKinds( xsn ) {
|
|
49
49
|
const result = {
|
|
50
50
|
definitions: 0,
|
|
51
51
|
entity: 0,
|
|
@@ -11,7 +11,7 @@ const { term } = require('../utils/term');
|
|
|
11
11
|
* @param {string} artifactName
|
|
12
12
|
* @returns {string|null}
|
|
13
13
|
*/
|
|
14
|
-
function inspectPropagation(xsn, options, artifactName) {
|
|
14
|
+
function inspectPropagation( xsn, options, artifactName ) {
|
|
15
15
|
const { error } = createMessageFunctions(options, 'inspect', xsn);
|
|
16
16
|
const result = [];
|
|
17
17
|
|
|
@@ -56,7 +56,7 @@ function inspectPropagation(xsn, options, artifactName) {
|
|
|
56
56
|
@param {string} indent
|
|
57
57
|
* @returns {string[]}
|
|
58
58
|
*/
|
|
59
|
-
function _indent(lines, indent = ' ') {
|
|
59
|
+
function _indent( lines, indent = ' ' ) {
|
|
60
60
|
return lines.map(str => `${ indent }${ str }`);
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -65,7 +65,7 @@ function _indent(lines, indent = ' ') {
|
|
|
65
65
|
* @returns {string[]}
|
|
66
66
|
* @private
|
|
67
67
|
*/
|
|
68
|
-
function _inspectAnnotations(artifactXsn) {
|
|
68
|
+
function _inspectAnnotations( artifactXsn ) {
|
|
69
69
|
const result = [];
|
|
70
70
|
const annos = Object.keys(artifactXsn).filter(str => str.startsWith('@')).sort();
|
|
71
71
|
|
|
@@ -114,7 +114,7 @@ function _inspectAnnotations(artifactXsn) {
|
|
|
114
114
|
* @returns {string[]}
|
|
115
115
|
* @private
|
|
116
116
|
*/
|
|
117
|
-
function _inspectElements(artifactXsn) {
|
|
117
|
+
function _inspectElements( artifactXsn ) {
|
|
118
118
|
if (!artifactXsn.elements)
|
|
119
119
|
return [ 'does not have elements' ];
|
|
120
120
|
|
|
@@ -173,7 +173,7 @@ function _inspectElements(artifactXsn) {
|
|
|
173
173
|
return result;
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
function _origin(elementXsn) {
|
|
176
|
+
function _origin( elementXsn ) {
|
|
177
177
|
while (elementXsn._origin)
|
|
178
178
|
elementXsn = elementXsn._origin;
|
|
179
179
|
return elementXsn;
|
|
@@ -186,7 +186,7 @@ function _origin(elementXsn) {
|
|
|
186
186
|
* @param parent
|
|
187
187
|
* @returns {boolean}
|
|
188
188
|
*/
|
|
189
|
-
function isContainedInParentLocation(art, parent) {
|
|
189
|
+
function isContainedInParentLocation( art, parent ) {
|
|
190
190
|
const artLoc = art.location;
|
|
191
191
|
const parentLoc = parent.location;
|
|
192
192
|
if (artLoc.file !== parentLoc.file)
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @param str
|
|
6
6
|
* @returns {*[]|*}
|
|
7
7
|
*/
|
|
8
|
-
function stringRefToPath(str) {
|
|
8
|
+
function stringRefToPath( str ) {
|
|
9
9
|
// e.g. `ns.service.E:sub.elem.structured`
|
|
10
10
|
const path = str.split(':');
|
|
11
11
|
if (path.length === 1)
|
|
@@ -20,7 +20,7 @@ function stringRefToPath(str) {
|
|
|
20
20
|
* @param {string} path
|
|
21
21
|
* @private
|
|
22
22
|
*/
|
|
23
|
-
function findArtifact(xsn, path) {
|
|
23
|
+
function findArtifact( xsn, path ) {
|
|
24
24
|
const segments = [ ...path ];
|
|
25
25
|
const topLevelName = segments[0];
|
|
26
26
|
let art = (xsn.definitions && xsn.definitions[topLevelName]) ||
|
package/lib/json/from-csn.js
CHANGED
|
@@ -73,6 +73,8 @@
|
|
|
73
73
|
* @property {string} [vZeroIgnore] Marks the property as a CSN 0.1.0 property. The
|
|
74
74
|
* property is ignored and a warning may be issues about
|
|
75
75
|
* it.
|
|
76
|
+
* @property {string} [xorException] A property name that is allowed besides another property
|
|
77
|
+
* of an xorGroup (as an exception to the rule).
|
|
76
78
|
*/
|
|
77
79
|
|
|
78
80
|
/**
|
|
@@ -585,7 +587,7 @@ const schema = compileSchema( {
|
|
|
585
587
|
inKind: () => true, // allowed in all definitions (including columns and extensions)
|
|
586
588
|
},
|
|
587
589
|
abstract: { // v1: with 'abstract', an entity becomes an aspect
|
|
588
|
-
type:
|
|
590
|
+
type: abstract,
|
|
589
591
|
inKind: [ 'entity', 'aspect' ], // 'aspect' because 'entity' is replaced by 'aspect' early
|
|
590
592
|
},
|
|
591
593
|
key: {
|
|
@@ -633,7 +635,7 @@ const schema = compileSchema( {
|
|
|
633
635
|
inKind: [ 'entity' ],
|
|
634
636
|
},
|
|
635
637
|
$syntax: {
|
|
636
|
-
type:
|
|
638
|
+
type: dollarSyntax,
|
|
637
639
|
ignore: true,
|
|
638
640
|
inKind: [ 'entity', 'type', 'aspect' ],
|
|
639
641
|
},
|
|
@@ -719,7 +721,7 @@ let arrayLevelCount = 0;
|
|
|
719
721
|
* @param {object} [proto]
|
|
720
722
|
* @returns {Object.<string, SchemaSpec>}
|
|
721
723
|
*/
|
|
722
|
-
function compileSchema( specs, proto = null) {
|
|
724
|
+
function compileSchema( specs, proto = null ) {
|
|
723
725
|
// no prototype to protect against evil-CSN properties 'toString' etc.
|
|
724
726
|
const r = Object.assign( Object.create( proto ), specs );
|
|
725
727
|
for (const p of Object.keys( specs )) {
|
|
@@ -978,7 +980,8 @@ function keys( array, spec, xsn ) {
|
|
|
978
980
|
return;
|
|
979
981
|
const r = Object.create(null);
|
|
980
982
|
r[$location] = location();
|
|
981
|
-
|
|
983
|
+
if (array.length)
|
|
984
|
+
++virtualLine; // possibly empty array
|
|
982
985
|
for (const def of array) {
|
|
983
986
|
const id = def.as || implicitName( def.ref );
|
|
984
987
|
const name = (typeof id === 'string') ? id : '';
|
|
@@ -1033,6 +1036,39 @@ function explicitName( id, spec, xsn ) {
|
|
|
1033
1036
|
xsn.name = { id, location: location() };
|
|
1034
1037
|
}
|
|
1035
1038
|
|
|
1039
|
+
function abstract( val, spec, xsn, csn ) {
|
|
1040
|
+
const strange = csn.kind !== 'entity';
|
|
1041
|
+
if (strange || !csnVersionZero) {
|
|
1042
|
+
warning( 'syntax-deprecated-abstract', location(true),
|
|
1043
|
+
{ '#': strange ? 'strange-kind' : 'std', prop: 'abstract', kind: 'entity' } );
|
|
1044
|
+
}
|
|
1045
|
+
boolOrNull( val, spec );
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
function dollarSyntax( val, spec, xsn, csn ) {
|
|
1049
|
+
if (csn.kind === 'type' && val === 'aspect') {
|
|
1050
|
+
warning( 'syntax-deprecated-dollar-syntax', location(true),
|
|
1051
|
+
{ '#': 'aspect', prop: '$syntax', kind: 'aspect' } );
|
|
1052
|
+
return ignore( val );
|
|
1053
|
+
}
|
|
1054
|
+
else if (xsn.kind === 'entity') {
|
|
1055
|
+
if (val === 'projection') {
|
|
1056
|
+
warning( 'syntax-deprecated-dollar-syntax', location(true),
|
|
1057
|
+
{
|
|
1058
|
+
'#': 'projection',
|
|
1059
|
+
prop: '$syntax',
|
|
1060
|
+
siblingprop: 'projection',
|
|
1061
|
+
otherprop: 'query',
|
|
1062
|
+
} );
|
|
1063
|
+
return string( val, spec );
|
|
1064
|
+
}
|
|
1065
|
+
if (val === 'entity' || val === 'view')
|
|
1066
|
+
return string( val, spec );
|
|
1067
|
+
}
|
|
1068
|
+
warning( 'syntax-deprecated-dollar-syntax', location(true), { prop: '$syntax' } );
|
|
1069
|
+
return ignore( val );
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1036
1072
|
function validKind( val, spec, xsn ) {
|
|
1037
1073
|
if (val === xsn.kind) // has been set in definition - the same = ok!
|
|
1038
1074
|
return undefined; // already set in definition
|
|
@@ -1040,17 +1076,7 @@ function validKind( val, spec, xsn ) {
|
|
|
1040
1076
|
warning( 'syntax-deprecated-value', location(true),
|
|
1041
1077
|
{ '#': 'replace', prop: spec.msgProp, value: 'entity' } );
|
|
1042
1078
|
}
|
|
1043
|
-
|
|
1044
|
-
// change message id in a later change
|
|
1045
|
-
else if ((val === 'entity' || val === 'type') && xsn.kind === 'aspect') {
|
|
1046
|
-
info( 'syntax-aspect', location(true), { kind: 'aspect', '#': val },
|
|
1047
|
-
{
|
|
1048
|
-
std: 'Use the dedicated kind $(KIND) for aspect definitions',
|
|
1049
|
-
// eslint-disable-next-line max-len
|
|
1050
|
-
entity: 'Abstract entity definitions are deprecated; use aspect definitions (having kind $(KIND)) instead',
|
|
1051
|
-
} );
|
|
1052
|
-
}
|
|
1053
|
-
else {
|
|
1079
|
+
else if (val !== 'entity' && val !== 'type' || xsn.kind !== 'aspect') {
|
|
1054
1080
|
error( 'syntax-invalid-kind', location(true), { prop: spec.msgProp },
|
|
1055
1081
|
'Invalid value for property $(PROP)' );
|
|
1056
1082
|
}
|
|
@@ -1145,14 +1171,14 @@ function scalenum( val, spec ) {
|
|
|
1145
1171
|
}
|
|
1146
1172
|
|
|
1147
1173
|
function natnum( val, spec ) {
|
|
1148
|
-
if (typeof val === 'number' && val >= 0)
|
|
1174
|
+
if (typeof val === 'number' && val >= 0 && Number.isSafeInteger( val ))
|
|
1149
1175
|
// XSN TODO: do not require literal
|
|
1150
1176
|
return { val, literal: 'number', location: location() };
|
|
1151
1177
|
const loc = location(true);
|
|
1152
1178
|
if (spec.msgId)
|
|
1153
1179
|
error( spec.msgId, loc, { prop: spec.msgProp } );
|
|
1154
1180
|
else
|
|
1155
|
-
error( 'syntax-expecting-
|
|
1181
|
+
error( 'syntax-expecting-unsigned-int', loc, { '#': 'csn', prop: spec.msgProp } );
|
|
1156
1182
|
return ignore( val );
|
|
1157
1183
|
}
|
|
1158
1184
|
|
|
@@ -1322,14 +1348,16 @@ function xpr( exprs, spec, xsn, csn ) {
|
|
|
1322
1348
|
xsn.suffix = exprArgs( exprs, spec );
|
|
1323
1349
|
}
|
|
1324
1350
|
else {
|
|
1351
|
+
// setting $parens here would not always be correct; thus, keep distinction
|
|
1352
|
+
// between 'xpr' and 'ixpr' (”internal” `xpr` = without implicit parens)
|
|
1325
1353
|
xsn.op = { val: 'xpr', location: location() };
|
|
1326
|
-
xsn.args = exprArgs( exprs, spec
|
|
1354
|
+
xsn.args = exprArgs( exprs, spec );
|
|
1327
1355
|
}
|
|
1328
1356
|
}
|
|
1329
1357
|
|
|
1330
1358
|
function list( exprs, spec, xsn ) {
|
|
1331
|
-
xsn.op = { val: '
|
|
1332
|
-
xsn.args = arrayOf( exprOrString )( exprs, spec
|
|
1359
|
+
xsn.op = { val: 'list', location: location() };
|
|
1360
|
+
xsn.args = arrayOf( exprOrString )( exprs, spec );
|
|
1333
1361
|
}
|
|
1334
1362
|
|
|
1335
1363
|
function xprInValue( exprs, spec, xsn, csn ) {
|
|
@@ -1369,20 +1397,30 @@ function args( exprs, spec ) {
|
|
|
1369
1397
|
}
|
|
1370
1398
|
|
|
1371
1399
|
function expr( e, spec ) {
|
|
1372
|
-
if (Array.isArray( e )
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1400
|
+
if (Array.isArray( e )) {
|
|
1401
|
+
if (e.length > 1) { // struct-xpr
|
|
1402
|
+
const loc = location();
|
|
1403
|
+
return {
|
|
1404
|
+
op: { val: 'ixpr', location: loc },
|
|
1405
|
+
args: exprArgs( e, spec ),
|
|
1406
|
+
location: loc,
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
else if (e.length === 1) { // CSN v.0.1.0 way for parentheses
|
|
1410
|
+
const loc = location();
|
|
1411
|
+
if (e[0] && !e[0].op) // do not complain with 'op' (for which we complain)
|
|
1412
|
+
replaceZeroValue( spec, 'zero-parens' );
|
|
1413
|
+
++virtualLine;
|
|
1414
|
+
const r = expr( e[0], spec );
|
|
1415
|
+
if (!r)
|
|
1416
|
+
return r;
|
|
1417
|
+
if (r.$parens)
|
|
1418
|
+
r.$parens.push( loc );
|
|
1419
|
+
else
|
|
1420
|
+
r.$parens = [ loc ];
|
|
1421
|
+
++virtualLine;
|
|
1379
1422
|
return r;
|
|
1380
|
-
|
|
1381
|
-
r.$parens.push( loc );
|
|
1382
|
-
else
|
|
1383
|
-
r.$parens = [ loc ];
|
|
1384
|
-
++virtualLine;
|
|
1385
|
-
return r;
|
|
1423
|
+
}
|
|
1386
1424
|
}
|
|
1387
1425
|
else if (e === null || [ 'string', 'number', 'boolean' ].includes( typeof e )) {
|
|
1388
1426
|
// && spec.optional.includes( 'val' )) ?
|
|
@@ -1392,16 +1430,19 @@ function expr( e, spec ) {
|
|
|
1392
1430
|
return object( e, spec );
|
|
1393
1431
|
}
|
|
1394
1432
|
|
|
1395
|
-
function exprOrString(
|
|
1396
|
-
return (typeof
|
|
1433
|
+
function exprOrString( val, spec ) {
|
|
1434
|
+
return (typeof val === 'string' && !csnVersionZero)
|
|
1435
|
+
? { val, literal: 'token', location: location() }
|
|
1436
|
+
: expr( val, spec );
|
|
1397
1437
|
}
|
|
1398
1438
|
|
|
1399
1439
|
// mark path argument of 'exits' predicate with $expected:'exists'
|
|
1400
|
-
function exprArgs( cond, spec
|
|
1401
|
-
const rxsn = arrayOf( exprOrString )( cond, spec
|
|
1402
|
-
|
|
1440
|
+
function exprArgs( cond, spec ) {
|
|
1441
|
+
const rxsn = arrayOf( exprOrString )( cond, spec );
|
|
1442
|
+
// TODO: do that in definer.js, neither here nor in CDL parser
|
|
1443
|
+
if (Array.isArray( rxsn )) {
|
|
1403
1444
|
for (let i = 0; i < rxsn.length - 1; i++) {
|
|
1404
|
-
if (rxsn[i] === 'exists' && rxsn[i + 1].path)
|
|
1445
|
+
if (rxsn[i]?.val === 'exists' && rxsn[i].literal === 'token' && rxsn[i + 1].path)
|
|
1405
1446
|
rxsn[++i].$expected = 'exists';
|
|
1406
1447
|
}
|
|
1407
1448
|
}
|
|
@@ -1448,7 +1489,8 @@ function excluding( array, spec, xsn ) {
|
|
|
1448
1489
|
return;
|
|
1449
1490
|
const r = Object.create(null);
|
|
1450
1491
|
r[$location] = location();
|
|
1451
|
-
|
|
1492
|
+
if (array.length)
|
|
1493
|
+
++virtualLine; // possibly empty array
|
|
1452
1494
|
for (const ex of array) {
|
|
1453
1495
|
const id = string( ex, spec ) || '';
|
|
1454
1496
|
dictAdd( r, id, { name: { id, location: location() }, location: location() },
|