Skip to content

Commit

Permalink
Add 'contains' expression
Browse files Browse the repository at this point in the history
  • Loading branch information
Anand Thakker committed Aug 28, 2017
1 parent aa451de commit 3ce92e1
Show file tree
Hide file tree
Showing 11 changed files with 229 additions and 1 deletion.
3 changes: 2 additions & 1 deletion docs/style-spec/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ Convert the argument to the given type, producing a runtime error if the convers
- `["has", key: String, obj: Object = ["properties"] ] -> Boolean`
- `["at", index: Number, arr: Array<T, N>|Array<T>] -> T`
- `["typeof", expr: Value] -> String`
- `["length", e: Vector<T>|String] -> Number`
- `["length", e: Array<T>|String] -> Number`
- `["contains", arr: Array<T, N>|Array<T>, value: T] -> Boolean`

### Feature data:
- `["properties"] -> Object` the feature's `properties` object
Expand Down
60 changes: 60 additions & 0 deletions src/style-spec/function/definitions/contains.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// @flow

const { parseExpression } = require('../expression');
const {
array,
BooleanType,
ValueType
} = require('../types');

import type { Expression, ParsingContext } from '../expression';
import type { Type, ArrayType } from '../types';

class Contains implements Expression {
key: string;
type: Type;
value: Expression;
array: Expression;

constructor(key: string, value: Expression, array: Expression) {
this.key = key;
this.type = BooleanType;
this.value = value;
this.array = array;
}

static parse(args: Array<mixed>, context: ParsingContext) {
if (args.length !== 3)
return context.error(`Expected 2 arguments, but found ${args.length - 1} instead.`);

const arrayExpr = parseExpression(args[1], context.concat(1, array(ValueType)));
if (!arrayExpr) return null;

const t: ArrayType = (arrayExpr.type: any);
const value = parseExpression(args[2], context.concat(2, t.itemType));
if (!value) return null;

const itemType = value.type.kind;
if (itemType === 'Object' || itemType === 'Array' || itemType === 'Color') {
return context.error(`"contains" does not support values of type ${itemType}.`);
}

return new Contains(context.key, value, arrayExpr);
}

compile() {
return `this.contains(${this.array.compile()}, ${this.value.compile()})`;
}

serialize() {
return [ 'contains', this.array.serialize(), this.value.serialize() ];
}

accept(visitor: Visitor<Expression>) {
visitor.visit(this);
this.array.accept(visitor);
this.value.accept(visitor);
}
}

module.exports = Contains;
2 changes: 2 additions & 0 deletions src/style-spec/function/definitions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const Var = require('./var');
const Literal = require('./literal');
const ArrayAssertion = require('./array');
const At = require('./at');
const Contains = require('./contains');
const Match = require('./match');
const Case = require('./case');
const Curve = require('./curve');
Expand All @@ -35,6 +36,7 @@ const expressions: { [string]: Class<Expression> } = {
'literal': Literal,
'array': ArrayAssertion,
'at': At,
'contains': Contains,
'case': Case,
'match': Match,
'coalesce': Coalesce,
Expand Down
7 changes: 7 additions & 0 deletions src/style-spec/function/evaluation_context.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ module.exports = () => ({
return this.as(obj, ObjectType, name).hasOwnProperty(key);
},

contains: function (array: Array<Value>, value: Value) {
const type = typeOf(value).kind;
ensure(type !== 'Object' && type !== 'Array' && type !== 'Color',
`"contains" does not support values of type ${type}`);
return array.indexOf(value) >= 0;
},

typeOf: function (x: Value): string {
assert(isValue(x), `Invalid value ${String(x)}`);
return toString(typeOf(x));
Expand Down
16 changes: 16 additions & 0 deletions test/integration/expression-tests/contains/array/test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"expectExpressionType": null,
"expression": ["contains", ["array", ["get", "arr"]], ["literal", []]],
"inputs": [],
"expected": {
"compiled": {
"result": "error",
"errors": [
{
"key": "",
"error": "\"contains\" does not support values of type Array."
}
]
}
}
}
21 changes: 21 additions & 0 deletions test/integration/expression-tests/contains/boolean/test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"expectExpressionType": null,
"expression": [
"contains",
["literal", [false, false]],
["boolean", ["get", "item"]]
],
"inputs": [
[{}, {"properties": {"item": true}}],
[{}, {"properties": {"item": false}}]
],
"expected": {
"compiled": {
"result": "success",
"isFeatureConstant": false,
"isZoomConstant": true,
"type": "Boolean"
},
"outputs": [false, true]
}
}
16 changes: 16 additions & 0 deletions test/integration/expression-tests/contains/color/test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"expectExpressionType": null,
"expression": ["contains", ["array", ["get", "arr"]], ["parse-color", "red"]],
"inputs": [],
"expected": {
"compiled": {
"result": "error",
"errors": [
{
"key": "",
"error": "\"contains\" does not support values of type Color."
}
]
}
}
}
28 changes: 28 additions & 0 deletions test/integration/expression-tests/contains/number/test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"expectExpressionType": null,
"expression": [
"contains",
["literal", [1, 2, 3, 4]],
["number", ["get", "item"]]
],
"inputs": [
[{}, {"properties": {"item": 3}}],
[{}, {"properties": {"item": 5}}],
[{}, {"properties": {"item": "3"}}]
],
"expected": {
"compiled": {
"result": "success",
"isFeatureConstant": false,
"isZoomConstant": true,
"type": "Boolean"
},
"outputs": [
true,
false,
{
"error": "Expected value to be of type Number, but found String instead."
}
]
}
}
16 changes: 16 additions & 0 deletions test/integration/expression-tests/contains/object/test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"expectExpressionType": null,
"expression": ["contains", ["array", ["get", "arr"]], ["literal", {}]],
"inputs": [],
"expected": {
"compiled": {
"result": "error",
"errors": [
{
"key": "",
"error": "\"contains\" does not support values of type Object."
}
]
}
}
}
21 changes: 21 additions & 0 deletions test/integration/expression-tests/contains/string/test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"expectExpressionType": null,
"expression": [
"contains",
["literal", ["a", "b", "c"]],
["string", ["get", "item"]]
],
"inputs": [
[{}, {"properties": {"item": "a"}}],
[{}, {"properties": {"item": "d"}}]
],
"expected": {
"compiled": {
"result": "success",
"isFeatureConstant": false,
"isZoomConstant": true,
"type": "Boolean"
},
"outputs": [true, false]
}
}
40 changes: 40 additions & 0 deletions test/integration/expression-tests/contains/value/test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"expectExpressionType": null,
"expression": ["contains", ["array", ["get", "arr"]], ["get", "item"]],
"inputs": [
[{}, {"properties": {"item": 3, "arr": [1, 2, 3, 4]}}],
[{}, {"properties": {"item": 5, "arr": [1, 2, 3, 4]}}],
[{}, {"properties": {"item": "3", "arr": [1, 2, 3, 4]}}],
[{}, {"properties": {"item": "a", "arr": ["a", "b", "c"]}}],
[{}, {"properties": {"item": "d", "arr": ["a", "b", "c"]}}],
[{}, {"properties": {"item": true, "arr": [true, true]}}],
[{}, {"properties": {"item": false, "arr": [true, true]}}],
[
{},
{
"properties": {
"item": ["nested", "array"],
"arr": [["nested", "array"]]
}
}
]
],
"expected": {
"compiled": {
"result": "success",
"isFeatureConstant": false,
"isZoomConstant": true,
"type": "Boolean"
},
"outputs": [
true,
false,
false,
true,
false,
true,
false,
{"error": "\"contains\" does not support values of type Array"}
]
}
}

0 comments on commit 3ce92e1

Please sign in to comment.