@mapwhit/tilerenderer 0.48.0 → 0.50.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/build/min/package.json +1 -1
- package/build/min/src/shaders/fill_extrusion.fragment.glsl.txt +1 -9
- package/build/min/src/shaders/fill_extrusion.vertex.glsl.txt +4 -4
- package/package.json +3 -2
- package/src/data/array_types.js +169 -0
- package/src/data/bucket/symbol_attributes.js +18 -0
- package/src/data/bucket/symbol_bucket.js +108 -78
- package/src/geo/transform.js +13 -5
- package/src/render/glyph_atlas.js +3 -6
- package/src/render/image_atlas.js +3 -6
- package/src/render/image_manager.js +41 -41
- package/src/shaders/fill_extrusion.fragment.glsl +0 -7
- package/src/shaders/fill_extrusion.vertex.glsl +4 -4
- package/src/style/style_layer/symbol_style_layer_properties.js +6 -0
- package/src/style-spec/expression/definitions/assertion.js +52 -5
- package/src/style-spec/expression/definitions/coalesce.js +1 -3
- package/src/style-spec/expression/definitions/coercion.js +32 -21
- package/src/style-spec/expression/definitions/collator.js +1 -23
- package/src/style-spec/expression/definitions/{formatted.js → format.js} +3 -29
- package/src/style-spec/expression/definitions/index.js +8 -26
- package/src/style-spec/expression/definitions/let.js +1 -1
- package/src/style-spec/expression/definitions/literal.js +1 -1
- package/src/style-spec/expression/evaluation_context.js +3 -0
- package/src/style-spec/expression/index.js +18 -17
- package/src/style-spec/expression/parsing_context.js +28 -24
- package/src/style-spec/expression/types/collator.js +24 -0
- package/src/style-spec/expression/types/formatted.js +39 -0
- package/src/style-spec/expression/types.js +1 -1
- package/src/style-spec/expression/values.js +24 -1
- package/src/style-spec/feature_filter/convert.js +197 -0
- package/src/style-spec/feature_filter/index.js +4 -1
- package/src/style-spec/function/convert.js +86 -102
- package/src/style-spec/function/index.js +4 -0
- package/src/style-spec/reference/v8.json +43 -6
- package/src/symbol/collision_index.js +0 -1
- package/src/symbol/cross_tile_symbol_index.js +12 -7
- package/src/symbol/mergelines.js +2 -2
- package/src/symbol/placement.js +71 -54
- package/src/symbol/shaping.js +9 -18
- package/src/symbol/symbol_layout.js +33 -33
- package/src/symbol/transform_text.js +5 -8
- package/src/style-spec/expression/definitions/array.js +0 -82
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
1
2
|
const Scope = require('./scope');
|
|
2
3
|
|
|
3
4
|
const { checkSubtype } = require('./types');
|
|
4
5
|
const ParsingError = require('./parsing_error');
|
|
5
6
|
const Literal = require('./definitions/literal');
|
|
6
7
|
const Assertion = require('./definitions/assertion');
|
|
7
|
-
const ArrayAssertion = require('./definitions/array');
|
|
8
8
|
const Coercion = require('./definitions/coercion');
|
|
9
9
|
const EvaluationContext = require('./evaluation_context');
|
|
10
10
|
const { CollatorExpression } = require('./definitions/collator');
|
|
@@ -49,6 +49,16 @@ class ParsingContext {
|
|
|
49
49
|
expr = ['literal', expr];
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
function annotate(parsed, type, typeAnnotation) {
|
|
53
|
+
if (typeAnnotation === 'assert') {
|
|
54
|
+
return new Assertion(type, [parsed]);
|
|
55
|
+
}
|
|
56
|
+
if (typeAnnotation === 'coerce') {
|
|
57
|
+
return new Coercion(type, [parsed]);
|
|
58
|
+
}
|
|
59
|
+
return parsed;
|
|
60
|
+
}
|
|
61
|
+
|
|
52
62
|
if (Array.isArray(expr)) {
|
|
53
63
|
if (expr.length === 0) {
|
|
54
64
|
return this.error(
|
|
@@ -74,34 +84,29 @@ class ParsingContext {
|
|
|
74
84
|
const expected = this.expectedType;
|
|
75
85
|
const actual = parsed.type;
|
|
76
86
|
|
|
77
|
-
// When we expect a number, string, boolean, or array but
|
|
78
|
-
//
|
|
79
|
-
// When we expect a Color but have a String or Value, we
|
|
80
|
-
// can wrap it in "to-color" coercion.
|
|
87
|
+
// When we expect a number, string, boolean, or array but have a value, wrap it in an assertion.
|
|
88
|
+
// When we expect a color or formatted string, but have a string or value, wrap it in a coercion.
|
|
81
89
|
// Otherwise, we do static type-checking.
|
|
90
|
+
//
|
|
91
|
+
// These behaviors are overridable for:
|
|
92
|
+
// * The "coalesce" operator, which needs to omit type annotations.
|
|
93
|
+
// * String-valued properties (e.g. `text-field`), where coercion is more convenient than assertion.
|
|
94
|
+
//
|
|
82
95
|
if (
|
|
83
96
|
(expected.kind === 'string' ||
|
|
84
97
|
expected.kind === 'number' ||
|
|
85
98
|
expected.kind === 'boolean' ||
|
|
86
|
-
expected.kind === 'object'
|
|
99
|
+
expected.kind === 'object' ||
|
|
100
|
+
expected.kind === 'array') &&
|
|
87
101
|
actual.kind === 'value'
|
|
88
102
|
) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
} else if (expected.kind === 'color' && (actual.kind === 'value' || actual.kind === 'string')) {
|
|
97
|
-
if (!options.omitTypeAnnotations) {
|
|
98
|
-
parsed = new Coercion(expected, [parsed]);
|
|
99
|
-
}
|
|
100
|
-
} else if (expected.kind === 'formatted' && (actual.kind === 'value' || actual.kind === 'string')) {
|
|
101
|
-
if (!options.omitTypeAnnotations) {
|
|
102
|
-
parsed = new Coercion(expected, [parsed]);
|
|
103
|
-
}
|
|
104
|
-
} else if (this.checkSubtype(this.expectedType, parsed.type)) {
|
|
103
|
+
parsed = annotate(parsed, expected, options.typeAnnotation || 'assert');
|
|
104
|
+
} else if (
|
|
105
|
+
(expected.kind === 'color' || expected.kind === 'formatted') &&
|
|
106
|
+
(actual.kind === 'value' || actual.kind === 'string')
|
|
107
|
+
) {
|
|
108
|
+
parsed = annotate(parsed, expected, options.typeAnnotation || 'coerce');
|
|
109
|
+
} else if (this.checkSubtype(expected, actual)) {
|
|
105
110
|
return null;
|
|
106
111
|
}
|
|
107
112
|
}
|
|
@@ -188,8 +193,7 @@ function isConstant(expression) {
|
|
|
188
193
|
return false;
|
|
189
194
|
}
|
|
190
195
|
|
|
191
|
-
const isTypeAnnotation =
|
|
192
|
-
expression instanceof Coercion || expression instanceof Assertion || expression instanceof ArrayAssertion;
|
|
196
|
+
const isTypeAnnotation = expression instanceof Coercion || expression instanceof Assertion;
|
|
193
197
|
|
|
194
198
|
let childrenConstant = true;
|
|
195
199
|
expression.eachChild(child => {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
class Collator {
|
|
2
|
+
constructor(caseSensitive, diacriticSensitive, locale) {
|
|
3
|
+
if (caseSensitive) this.sensitivity = diacriticSensitive ? 'variant' : 'case';
|
|
4
|
+
else this.sensitivity = diacriticSensitive ? 'accent' : 'base';
|
|
5
|
+
|
|
6
|
+
this.locale = locale;
|
|
7
|
+
this.collator = new Intl.Collator(this.locale ? this.locale : [], {
|
|
8
|
+
sensitivity: this.sensitivity,
|
|
9
|
+
usage: 'search'
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
compare(lhs, rhs) {
|
|
14
|
+
return this.collator.compare(lhs, rhs);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
resolvedLocale() {
|
|
18
|
+
// We create a Collator without "usage: search" because we don't want
|
|
19
|
+
// the search options encoded in our result (e.g. "en-u-co-search")
|
|
20
|
+
return new Intl.Collator(this.locale ? this.locale : []).resolvedOptions().locale;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = { Collator };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
class FormattedSection {
|
|
2
|
+
constructor(text, scale, fontStack = null) {
|
|
3
|
+
this.text = text;
|
|
4
|
+
this.scale = scale;
|
|
5
|
+
this.fontStack = fontStack;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
class Formatted {
|
|
10
|
+
constructor(sections) {
|
|
11
|
+
this.sections = sections;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static fromString(unformatted) {
|
|
15
|
+
return new Formatted([new FormattedSection(unformatted, null, null)]);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
toString() {
|
|
19
|
+
return this.sections.map(section => section.text).join('');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
serialize() {
|
|
23
|
+
const serialized = ['format'];
|
|
24
|
+
for (const section of this.sections) {
|
|
25
|
+
serialized.push(section.text);
|
|
26
|
+
const options = {};
|
|
27
|
+
if (section.fontStack) {
|
|
28
|
+
options['text-font'] = ['literal', section.fontStack.split(',')];
|
|
29
|
+
}
|
|
30
|
+
if (section.scale) {
|
|
31
|
+
options['font-scale'] = section.scale;
|
|
32
|
+
}
|
|
33
|
+
serialized.push(options);
|
|
34
|
+
}
|
|
35
|
+
return serialized;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = { Formatted, FormattedSection };
|
|
@@ -53,7 +53,7 @@ function checkSubtype(expected, t) {
|
|
|
53
53
|
if (expected.kind === 'array') {
|
|
54
54
|
if (
|
|
55
55
|
t.kind === 'array' &&
|
|
56
|
-
!checkSubtype(expected.itemType, t.itemType) &&
|
|
56
|
+
((t.N === 0 && t.itemType.kind === 'value') || !checkSubtype(expected.itemType, t.itemType)) &&
|
|
57
57
|
(typeof expected.N !== 'number' || expected.N === t.N)
|
|
58
58
|
) {
|
|
59
59
|
return null;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
const assert = require('assert');
|
|
2
2
|
|
|
3
3
|
const Color = require('../util/color');
|
|
4
|
-
const { Collator } = require('./
|
|
4
|
+
const { Collator } = require('./types/collator');
|
|
5
|
+
const { Formatted } = require('./types/formatted');
|
|
5
6
|
const {
|
|
6
7
|
NullType,
|
|
7
8
|
NumberType,
|
|
@@ -11,6 +12,7 @@ const {
|
|
|
11
12
|
ObjectType,
|
|
12
13
|
ValueType,
|
|
13
14
|
CollatorType,
|
|
15
|
+
FormattedType,
|
|
14
16
|
array
|
|
15
17
|
} = require('./types');
|
|
16
18
|
|
|
@@ -58,6 +60,9 @@ function isValue(mixed) {
|
|
|
58
60
|
if (mixed instanceof Collator) {
|
|
59
61
|
return true;
|
|
60
62
|
}
|
|
63
|
+
if (mixed instanceof Formatted) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
61
66
|
if (Array.isArray(mixed)) {
|
|
62
67
|
for (const item of mixed) {
|
|
63
68
|
if (!isValue(item)) {
|
|
@@ -96,6 +101,9 @@ function typeOf(value) {
|
|
|
96
101
|
if (value instanceof Collator) {
|
|
97
102
|
return CollatorType;
|
|
98
103
|
}
|
|
104
|
+
if (value instanceof Formatted) {
|
|
105
|
+
return FormattedType;
|
|
106
|
+
}
|
|
99
107
|
if (Array.isArray(value)) {
|
|
100
108
|
const length = value.length;
|
|
101
109
|
let itemType;
|
|
@@ -117,7 +125,22 @@ function typeOf(value) {
|
|
|
117
125
|
return ObjectType;
|
|
118
126
|
}
|
|
119
127
|
|
|
128
|
+
function toString(value) {
|
|
129
|
+
const type = typeof value;
|
|
130
|
+
if (value === null) {
|
|
131
|
+
return '';
|
|
132
|
+
}
|
|
133
|
+
if (type === 'string' || type === 'number' || type === 'boolean') {
|
|
134
|
+
return String(value);
|
|
135
|
+
}
|
|
136
|
+
if (value instanceof Color || value instanceof Formatted) {
|
|
137
|
+
return value.toString();
|
|
138
|
+
}
|
|
139
|
+
return JSON.stringify(value);
|
|
140
|
+
}
|
|
141
|
+
|
|
120
142
|
module.exports = {
|
|
143
|
+
toString,
|
|
121
144
|
Color,
|
|
122
145
|
Collator,
|
|
123
146
|
validateRGBA,
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
const { isExpressionFilter } = require('./index');
|
|
2
|
+
|
|
3
|
+
module.exports = convertFilter;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Convert the given legacy filter to (the JSON representation of) an
|
|
7
|
+
* equivalent expression
|
|
8
|
+
* @private
|
|
9
|
+
*/
|
|
10
|
+
function convertFilter(filter) {
|
|
11
|
+
return _convertFilter(filter, {});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* Convert the given filter to an expression, storing the expected types for
|
|
16
|
+
* any feature properties referenced in expectedTypes.
|
|
17
|
+
*
|
|
18
|
+
* These expected types are needed in order to construct preflight type checks
|
|
19
|
+
* needed for handling 'any' filters. A preflight type check is necessary in
|
|
20
|
+
* order to mimic legacy filters' semantics around expected type mismatches.
|
|
21
|
+
* For example, consider the legacy filter:
|
|
22
|
+
*
|
|
23
|
+
* ["any", ["all", [">", "y", 0], [">", "y", 0]], [">", "x", 0]]
|
|
24
|
+
*
|
|
25
|
+
* Naively, we might convert this to the expression:
|
|
26
|
+
*
|
|
27
|
+
* ["any", ["all", [">", ["get", "y"], 0], [">", ["get", "z"], 0]], [">", ["get", "x"], 0]]
|
|
28
|
+
*
|
|
29
|
+
* But if we tried to evaluate this against, say `{x: 1, y: null, z: 0}`, the
|
|
30
|
+
* [">", ["get", "y"], 0] would cause an evaluation error, leading to the
|
|
31
|
+
* entire filter returning false. Legacy filter semantics, though, ask for
|
|
32
|
+
* [">", "y", 0] to simply return `false` when `y` is of the wrong type,
|
|
33
|
+
* allowing the subsequent terms of the outer "any" expression to be evaluated
|
|
34
|
+
* (resulting, in this case, in a `true` value, because x > 0).
|
|
35
|
+
*
|
|
36
|
+
* We account for this by inserting a preflight type-checking expression before
|
|
37
|
+
* each "any" term, allowing us to avoid evaluating the actual converted filter
|
|
38
|
+
* if any type mismatches would cause it to produce an evalaution error:
|
|
39
|
+
*
|
|
40
|
+
* ["any",
|
|
41
|
+
* ["case",
|
|
42
|
+
* ["all", ["==", ["typeof", ["get", "y"]], "number"], ["==", ["typeof", ["get", "z"], "number]],
|
|
43
|
+
* ["all", [">", ["get", "y"], 0], [">", ["get", "z"], 0]],
|
|
44
|
+
* false
|
|
45
|
+
* ],
|
|
46
|
+
* ["case",
|
|
47
|
+
* ["==", ["typeof", ["get", "x"], "number"]],
|
|
48
|
+
* [">", ["get", "x"], 0],
|
|
49
|
+
* false
|
|
50
|
+
* ]
|
|
51
|
+
* ]
|
|
52
|
+
*
|
|
53
|
+
* An alternative, possibly more direct approach would be to use type checks
|
|
54
|
+
* in the conversion of each comparison operator, so that the converted version
|
|
55
|
+
* of each individual ==, >=, etc. would mimic the legacy filter semantics. The
|
|
56
|
+
* downside of this approach is that it can lead to many more type checks than
|
|
57
|
+
* would otherwise be necessary: outside the context of an "any" expression,
|
|
58
|
+
* bailing out due to a runtime type error (expression semantics) and returning
|
|
59
|
+
* false (legacy filter semantics) are equivalent: they cause the filter to
|
|
60
|
+
* produce a `false` result.
|
|
61
|
+
*/
|
|
62
|
+
function _convertFilter(filter, expectedTypes) {
|
|
63
|
+
if (isExpressionFilter(filter)) {
|
|
64
|
+
return filter;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!filter) return true;
|
|
68
|
+
const op = filter[0];
|
|
69
|
+
if (filter.length <= 1) return op !== 'any';
|
|
70
|
+
|
|
71
|
+
let converted;
|
|
72
|
+
|
|
73
|
+
if (op === '==' || op === '!=' || op === '<' || op === '>' || op === '<=' || op === '>=') {
|
|
74
|
+
const [, property, value] = filter;
|
|
75
|
+
converted = convertComparisonOp(property, value, op, expectedTypes);
|
|
76
|
+
} else if (op === 'any') {
|
|
77
|
+
const children = filter.slice(1).map(f => {
|
|
78
|
+
const types = {};
|
|
79
|
+
const child = _convertFilter(f, types);
|
|
80
|
+
const typechecks = runtimeTypeChecks(types);
|
|
81
|
+
return typechecks === true ? child : ['case', typechecks, child, false];
|
|
82
|
+
});
|
|
83
|
+
return ['any'].concat(children);
|
|
84
|
+
} else if (op === 'all') {
|
|
85
|
+
const children = filter.slice(1).map(f => _convertFilter(f, expectedTypes));
|
|
86
|
+
return ['all'].concat(children);
|
|
87
|
+
} else if (op === 'none') {
|
|
88
|
+
return ['!', _convertFilter(['any'].concat(filter.slice(1)), {})];
|
|
89
|
+
} else if (op === 'in') {
|
|
90
|
+
converted = convertInOp(filter[1], filter.slice(2));
|
|
91
|
+
} else if (op === '!in') {
|
|
92
|
+
converted = convertInOp(filter[1], filter.slice(2), true);
|
|
93
|
+
} else if (op === 'has') {
|
|
94
|
+
converted = convertHasOp(filter[1]);
|
|
95
|
+
} else if (op === '!has') {
|
|
96
|
+
converted = ['!', convertHasOp(filter[1])];
|
|
97
|
+
} else {
|
|
98
|
+
converted = true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return converted;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Given a set of feature properties and an expected type for each one,
|
|
105
|
+
// construct an boolean expression that tests whether each property has the
|
|
106
|
+
// right type.
|
|
107
|
+
// E.g.: for {name: 'string', population: 'number'}, return
|
|
108
|
+
// [ 'all',
|
|
109
|
+
// ['==', ['typeof', ['get', 'name'], 'string']],
|
|
110
|
+
// ['==', ['typeof', ['get', 'population'], 'number]]
|
|
111
|
+
// ]
|
|
112
|
+
function runtimeTypeChecks(expectedTypes) {
|
|
113
|
+
const conditions = [];
|
|
114
|
+
for (const property in expectedTypes) {
|
|
115
|
+
const get = property === '$id' ? ['id'] : ['get', property];
|
|
116
|
+
conditions.push(['==', ['typeof', get], expectedTypes[property]]);
|
|
117
|
+
}
|
|
118
|
+
if (conditions.length === 0) return true;
|
|
119
|
+
if (conditions.length === 1) return conditions[0];
|
|
120
|
+
return ['all'].concat(conditions);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function convertComparisonOp(property, value, op, expectedTypes) {
|
|
124
|
+
let get;
|
|
125
|
+
if (property === '$type') {
|
|
126
|
+
return [op, ['geometry-type'], value];
|
|
127
|
+
}
|
|
128
|
+
if (property === '$id') {
|
|
129
|
+
get = ['id'];
|
|
130
|
+
} else {
|
|
131
|
+
get = ['get', property];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (expectedTypes && value !== null) {
|
|
135
|
+
const type = typeof value;
|
|
136
|
+
expectedTypes[property] = type;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (op === '==' && property !== '$id' && value === null) {
|
|
140
|
+
return [
|
|
141
|
+
'all',
|
|
142
|
+
['has', property], // missing property != null for legacy filters
|
|
143
|
+
['==', get, null]
|
|
144
|
+
];
|
|
145
|
+
}
|
|
146
|
+
if (op === '!=' && property !== '$id' && value === null) {
|
|
147
|
+
return [
|
|
148
|
+
'any',
|
|
149
|
+
['!', ['has', property]], // missing property != null for legacy filters
|
|
150
|
+
['!=', get, null]
|
|
151
|
+
];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return [op, get, value];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function convertInOp(property, values, negate = false) {
|
|
158
|
+
if (values.length === 0) return negate;
|
|
159
|
+
|
|
160
|
+
let get;
|
|
161
|
+
if (property === '$type') {
|
|
162
|
+
get = ['geometry-type'];
|
|
163
|
+
} else if (property === '$id') {
|
|
164
|
+
get = ['id'];
|
|
165
|
+
} else {
|
|
166
|
+
get = ['get', property];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Determine if the list of values to be searched is homogenously typed.
|
|
170
|
+
// If so (and if the type is string or number), then we can use a
|
|
171
|
+
// [match, input, [...values], true, false] construction rather than a
|
|
172
|
+
// bunch of `==` tests.
|
|
173
|
+
let uniformTypes = true;
|
|
174
|
+
const type = typeof values[0];
|
|
175
|
+
for (const value of values) {
|
|
176
|
+
if (typeof value !== type) {
|
|
177
|
+
uniformTypes = false;
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (uniformTypes && (type === 'string' || type === 'number')) {
|
|
183
|
+
return ['match', get, values, !negate, negate];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return [negate ? 'all' : 'any'].concat(values.map(v => [negate ? '!=' : '==', get, v]));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function convertHasOp(property) {
|
|
190
|
+
if (property === '$type') {
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
if (property === '$id') {
|
|
194
|
+
return ['!=', ['id'], null];
|
|
195
|
+
}
|
|
196
|
+
return ['has', property];
|
|
197
|
+
}
|
|
@@ -5,6 +5,9 @@ module.exports = createFilter;
|
|
|
5
5
|
createFilter.isExpressionFilter = isExpressionFilter;
|
|
6
6
|
|
|
7
7
|
function isExpressionFilter(filter) {
|
|
8
|
+
if (filter === true || filter === false) {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
8
11
|
if (!Array.isArray(filter) || filter.length === 0) {
|
|
9
12
|
return false;
|
|
10
13
|
}
|
|
@@ -61,7 +64,7 @@ const filterSpec = {
|
|
|
61
64
|
* @returns {Function} filter-evaluating function
|
|
62
65
|
*/
|
|
63
66
|
function createFilter(filter) {
|
|
64
|
-
if (
|
|
67
|
+
if (filter === null || filter === undefined) {
|
|
65
68
|
return () => true;
|
|
66
69
|
}
|
|
67
70
|
|