diff --git a/docs/components/expression-metadata.js b/docs/components/expression-metadata.js index d96033e5a2f..199ee88e2c2 100644 --- a/docs/components/expression-metadata.js +++ b/docs/components/expression-metadata.js @@ -77,6 +77,10 @@ const types = { type: 'ItemType', parameters: ['number', 'array'] }], + in: [{ + type: 'boolean', + parameters: ['value', 'array'] + }], case: [{ type: 'OutputType', parameters: [{ repeat: ['condition: boolean', 'output: OutputType'] }, 'default: OutputType'] diff --git a/src/style-spec/expression/definitions/in.js b/src/style-spec/expression/definitions/in.js new file mode 100644 index 00000000000..9393cee16ed --- /dev/null +++ b/src/style-spec/expression/definitions/in.js @@ -0,0 +1,56 @@ +// @flow + +import { array, ValueType, BooleanType } from '../types'; + +import type { Expression } from '../expression'; +import type ParsingContext from '../parsing_context'; +import type EvaluationContext from '../evaluation_context'; +import type { Type } from '../types'; +import type { Value } from '../values'; + +class In implements Expression { + type: Type; + needle: Expression; + haystack: Expression; + + constructor(needle: Expression, haystack: Expression) { + this.type = BooleanType; + this.needle = needle; + this.haystack = haystack; + } + + static parse(args: Array, context: ParsingContext) { + if (args.length !== 3) + return context.error(`Expected 2 arguments, but found ${args.length - 1} instead.`); + + const needle = context.parse(args[1], 1, ValueType); + const haystack = context.parse(args[2], 2, array(ValueType)); + + if (!needle || !haystack) return null; + + return new In(needle, haystack); + } + + evaluate(ctx: EvaluationContext) { + const needle = (this.needle.evaluate(ctx): any); + const haystack = ((this.haystack.evaluate(ctx): any): Array); + + return haystack.indexOf(needle) >= 0; + } + + eachChild(fn: (Expression) => void) { + fn(this.needle); + fn(this.haystack); + } + + possibleOutputs() { + return [true, false]; + } + + serialize() { + return ["in", this.needle.serialize(), this.haystack.serialize()]; + } +} + +export default In; + diff --git a/src/style-spec/expression/definitions/index.js b/src/style-spec/expression/definitions/index.js index f39dc07639d..1ad81288f5a 100644 --- a/src/style-spec/expression/definitions/index.js +++ b/src/style-spec/expression/definitions/index.js @@ -11,6 +11,7 @@ import Literal from './literal'; import Assertion from './assertion'; import Coercion from './coercion'; import At from './at'; +import In from './in'; import Match from './match'; import Case from './case'; import Step from './step'; @@ -47,6 +48,7 @@ const expressions: ExpressionRegistry = { 'coalesce': Coalesce, 'collator': CollatorExpression, 'format': FormatExpression, + 'in': In, 'interpolate': Interpolate, 'interpolate-hcl': Interpolate, 'interpolate-lab': Interpolate, diff --git a/src/style-spec/feature_filter/index.js b/src/style-spec/feature_filter/index.js index 4705a078e17..3649f5cc3e3 100644 --- a/src/style-spec/feature_filter/index.js +++ b/src/style-spec/feature_filter/index.js @@ -21,6 +21,8 @@ function isExpressionFilter(filter: any) { return filter.length >= 2 && filter[1] !== '$id' && filter[1] !== '$type'; case 'in': + return filter.length === 3 && Array.isArray(filter[2]); + case '!in': case '!has': case 'none': diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json index d9b0a69496d..19ad7a4a9a5 100644 --- a/src/style-spec/reference/v8.json +++ b/src/style-spec/reference/v8.json @@ -2345,6 +2345,15 @@ } } }, + "in": { + "doc": "Determines whether an item exists in an array.", + "group": "Lookup", + "sdk-support": { + "basic functionality": { + "js": "0.49.0" + } + } + }, "case": { "doc": "Selects the first output whose corresponding test condition evaluates to true.", "group": "Decision", diff --git a/test/integration/expression-tests/in/basic/test.json b/test/integration/expression-tests/in/basic/test.json new file mode 100644 index 00000000000..e3da9d024f0 --- /dev/null +++ b/test/integration/expression-tests/in/basic/test.json @@ -0,0 +1,30 @@ +{ + "expression": [ + "boolean", + ["in", ["get", "i"], ["array", ["get", "arr"]]] + ], + "inputs": [ + [{}, {"properties": {"i": null, "arr": [9, 8, 7]}}], + [{}, {"properties": {"i": 1, "arr": [9, 8, 7]}}], + [{}, {"properties": {"i": 9, "arr": [9, 8, 7]}}], + [{}, {"properties": {"i": 1, "arr": null}}] + ], + "expected": { + "compiled": { + "result": "success", + "isFeatureConstant": false, + "isZoomConstant": true, + "type": "boolean" + }, + "outputs": [ + false, + false, + true, + {"error":"Expected value to be of type array, but found null instead."} + ], + "serialized": [ + "boolean", + ["in", ["get", "i"], ["array", ["get", "arr"]]] + ] + } +}