vega2ol 1.1.1 → 1.1.3

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/dist/main.js CHANGED
@@ -19,8 +19,6 @@ const OPERATOR_MAPPING = {
19
19
  "*": "*",
20
20
  "/": "/",
21
21
  "%": "%",
22
- // Other
23
- "**": "pow",
24
22
  };
25
23
  /**
26
24
  * Constants mapping from Vega constants to OpenLayers constants
@@ -47,30 +45,12 @@ const FUNCTION_MAPPING = {
47
45
  ceil: "ceil",
48
46
  round: "round",
49
47
  sqrt: "sqrt",
50
- pow: "pow",
51
- log: "log",
52
- exp: "exp",
48
+ pow: "^",
53
49
  sin: "sin",
54
50
  cos: "cos",
55
- tan: "tan",
56
- // String functions
57
- tostring: "toString",
58
- upper: "toUpperCase",
59
- lower: "toLowerCase",
60
- substring: "substring",
61
- // Array functions
62
- length: "length",
63
- indexof: "indexOf",
64
- slice: "slice",
65
- // Type functions
66
- type: "type",
67
- isvalid: "isValid",
68
- isnumber: "isNumber",
69
- isstring: "isString",
70
- isboolean: "isBoolean",
71
- // Aggregation functions
72
- min: "min",
73
- max: "max",
51
+ atan: "atan",
52
+ atan2: "atan",
53
+ clamp: "clamp",
74
54
  };
75
55
  /**
76
56
  * Custom error class for Vega2OL transpilation errors
@@ -89,6 +69,20 @@ class VegaToOLVisitor {
89
69
  constructor() {
90
70
  this.scope = {};
91
71
  }
72
+ isNegativeOne(node) {
73
+ // Case 1: Literal -1
74
+ if (node.type === "Literal" && node.value === -1) {
75
+ return true;
76
+ }
77
+ // Case 2: UnaryExpression: -1
78
+ if (node.type === "UnaryExpression" &&
79
+ node.operator === "-" &&
80
+ node.argument?.type === "Literal" &&
81
+ node.argument.value === 1) {
82
+ return true;
83
+ }
84
+ return false;
85
+ }
92
86
  visit(node) {
93
87
  if (!node || !node.type) {
94
88
  throw new Vega2OLError("Invalid AST node");
@@ -128,9 +122,44 @@ class VegaToOLVisitor {
128
122
  }
129
123
  throw new Vega2OLError(`Unsupported member access`);
130
124
  }
125
+ isIndexOfCall(node) {
126
+ return (node.type === "CallExpression" &&
127
+ node.callee?.type === "Identifier" &&
128
+ node.callee.name.toLowerCase() === "indexof");
129
+ }
131
130
  // Binary operations: a + b, a > b, etc.
132
131
  visitBinaryExpression(node) {
133
132
  const { operator, left, right } = node;
133
+ // Special handling for indexOf comparisons
134
+ if (operator === "!=" || operator === "==") {
135
+ let call = null;
136
+ if (this.isIndexOfCall(left) && this.isNegativeOne(right)) {
137
+ call = left;
138
+ }
139
+ else if (this.isIndexOfCall(right) && this.isNegativeOne(left)) {
140
+ call = right;
141
+ }
142
+ if (call) {
143
+ const [arrayArg, valueArg] = call.arguments || [];
144
+ if (arrayArg && valueArg) {
145
+ const arrayOL = this.visit(arrayArg);
146
+ const valueOL = this.visit(valueArg);
147
+ const inExpr = [
148
+ "in",
149
+ valueOL,
150
+ ["literal", arrayOL],
151
+ ];
152
+ // indexof(...) != -1 → is in
153
+ if (operator === "!=") {
154
+ return inExpr;
155
+ }
156
+ // indexof(...) == -1 → is NOT in
157
+ if (operator === "==") {
158
+ return ["!", inExpr];
159
+ }
160
+ }
161
+ }
162
+ }
134
163
  const leftOL = this.visit(left);
135
164
  const rightOL = this.visit(right);
136
165
  const olOp = OPERATOR_MAPPING[operator];
@@ -177,6 +206,10 @@ class VegaToOLVisitor {
177
206
  else {
178
207
  throw new Vega2OLError(`Unsupported function call`);
179
208
  }
209
+ if (this.isIndexOfCall(node)) {
210
+ throw new Vega2OLError(`indexof is only supported in the pattern 'indexof(array, value) != -1' (or '== -1'), ` +
211
+ `it cannot be used as a standalone expression in OpenLayers`);
212
+ }
180
213
  const olFunc = FUNCTION_MAPPING[funcName.toLowerCase()];
181
214
  if (!olFunc) {
182
215
  throw new Vega2OLError(`Unsupported function: ${funcName}`);
@@ -14,6 +14,30 @@ function assertEqual(actual, expected, message) {
14
14
  return false;
15
15
  }
16
16
  }
17
+ // Assert that vega2ol throws a Vega2OLError for the given expression
18
+ async function assertThrows(expr, message, messageContains) {
19
+ try {
20
+ const result = await vega2ol(expr);
21
+ console.log(` ❌ ${message}`);
22
+ console.log(` Expected a Vega2OLError, but got result: ${JSON.stringify(result)}`);
23
+ return false;
24
+ }
25
+ catch (error) {
26
+ if (!(error instanceof Vega2OLError)) {
27
+ console.log(` ❌ ${message}`);
28
+ console.log(` Expected Vega2OLError, got: ${error.constructor.name}: ${error.message}`);
29
+ return false;
30
+ }
31
+ if (messageContains && !error.message.includes(messageContains)) {
32
+ console.log(` ❌ ${message}`);
33
+ console.log(` Error message did not contain "${messageContains}"`);
34
+ console.log(` Got message: ${error.message}`);
35
+ return false;
36
+ }
37
+ console.log(` ✅ ${message}`);
38
+ return true;
39
+ }
40
+ }
17
41
  // Run tests
18
42
  async function runTests() {
19
43
  console.log("🚀 Testing vega2ol transpiler\n");
@@ -106,7 +130,43 @@ async function runTests() {
106
130
  passed++;
107
131
  else
108
132
  failed++;
109
- if (assertEqual(await vega2ol("pow(datum.base, 2)"), ["pow", ["get", "base"], 2], "Pow function with multiple args"))
133
+ if (assertEqual(await vega2ol("ceil(datum.value)"), ["ceil", ["get", "value"]], "ceil()"))
134
+ passed++;
135
+ else
136
+ failed++;
137
+ if (assertEqual(await vega2ol("round(datum.value)"), ["round", ["get", "value"]], "round()"))
138
+ passed++;
139
+ else
140
+ failed++;
141
+ if (assertEqual(await vega2ol("abs(datum.value)"), ["abs", ["get", "value"]], "abs()"))
142
+ passed++;
143
+ else
144
+ failed++;
145
+ if (assertEqual(await vega2ol("sqrt(datum.value)"), ["sqrt", ["get", "value"]], "sqrt()"))
146
+ passed++;
147
+ else
148
+ failed++;
149
+ if (assertEqual(await vega2ol("sin(datum.value)"), ["sin", ["get", "value"]], "sin()"))
150
+ passed++;
151
+ else
152
+ failed++;
153
+ if (assertEqual(await vega2ol("cos(datum.value)"), ["cos", ["get", "value"]], "cos()"))
154
+ passed++;
155
+ else
156
+ failed++;
157
+ if (assertEqual(await vega2ol("pow(datum.base, 2)"), ["^", ["get", "base"], 2], "pow() maps to OL's '^' operator, not a 'pow' function"))
158
+ passed++;
159
+ else
160
+ failed++;
161
+ if (assertEqual(await vega2ol("atan(datum.value)"), ["atan", ["get", "value"]], "atan() single-arg form"))
162
+ passed++;
163
+ else
164
+ failed++;
165
+ if (assertEqual(await vega2ol("atan2(datum.x, datum.y)"), ["atan", ["get", "x"], ["get", "y"]], "atan2() two-arg form"))
166
+ passed++;
167
+ else
168
+ failed++;
169
+ if (assertEqual(await vega2ol("clamp(datum.value, 0, 100)"), ["clamp", ["get", "value"], 0, 100], "clamp() preserves (value, min, max) arg order"))
110
170
  passed++;
111
171
  else
112
172
  failed++;
@@ -133,7 +193,85 @@ async function runTests() {
133
193
  passed++;
134
194
  else
135
195
  failed++;
136
- // Test 13: Error handling
196
+ // Test 13: Binary expressions
197
+ console.log("\n📋 Binary Expressions");
198
+ if (assertEqual(await vega2ol("indexof(['USA', 'India'], datum.country) != -1"), ["in", ["get", "country"], ["literal", ["USA", "India"]]], "indexOf with != -1 (is in)"))
199
+ passed++;
200
+ else
201
+ failed++;
202
+ if (assertEqual(await vega2ol("-1 != indexof(['USA', 'India'], datum.country)"), ["in", ["get", "country"], ["literal", ["USA", "India"]]], "reverse indexOf with != -1 (is in)"))
203
+ passed++;
204
+ else
205
+ failed++;
206
+ if (assertEqual(await vega2ol("indexof(['hot', 'warm'], datum.temperature) != -1 ? 'red' : 'blue'"), [
207
+ "case",
208
+ ["in", ["get", "temperature"], ["literal", ["hot", "warm"]]],
209
+ "red",
210
+ "blue",
211
+ ], "indexOf in ternary (if in)"))
212
+ passed++;
213
+ else
214
+ failed++;
215
+ if (assertEqual(await vega2ol("indexof(['USA', 'India'], datum.country) == -1"), ["!", ["in", ["get", "country"], ["literal", ["USA", "India"]]]], "indexOf with == -1 (is not in)"))
216
+ passed++;
217
+ else
218
+ failed++;
219
+ if (assertEqual(await vega2ol("-1 == indexof(['USA', 'India'], datum.country)"), ["!", ["in", ["get", "country"], ["literal", ["USA", "India"]]]], "reverse indexOf with == -1 (is not in)"))
220
+ passed++;
221
+ else
222
+ failed++;
223
+ // Test 14: Unsupported / unknown function errors
224
+ console.log("\n📋 Errors: Vega Functions With No OL Equivalent");
225
+ if (await assertThrows("upper(datum.name)", "upper() throws (no OL string case operator)", "Unsupported function"))
226
+ passed++;
227
+ else
228
+ failed++;
229
+ if (await assertThrows("lower(datum.name)", "lower() throws (no OL string case operator)", "Unsupported function"))
230
+ passed++;
231
+ else
232
+ failed++;
233
+ if (await assertThrows("toString(datum.value)", "toString() throws (no OL type coercion operator)", "Unsupported function"))
234
+ passed++;
235
+ else
236
+ failed++;
237
+ if (await assertThrows("isValid(datum.value)", "isValid() throws (no OL type-checking operator)", "Unsupported function"))
238
+ passed++;
239
+ else
240
+ failed++;
241
+ if (await assertThrows("length(datum.items)", "length() throws (no OL length operator)", "Unsupported function"))
242
+ passed++;
243
+ else
244
+ failed++;
245
+ if (await assertThrows("format(datum.value, ',.2f')", "format() throws (no OL number-formatting operator)", "Unsupported function"))
246
+ passed++;
247
+ else
248
+ failed++;
249
+ if (await assertThrows("now()", "now() throws (no OL date/time operators)", "Unsupported function"))
250
+ passed++;
251
+ else
252
+ failed++;
253
+ if (await assertThrows("min(datum.a, datum.b)", "min() throws (no OL aggregate min/max operator)", "Unsupported function"))
254
+ passed++;
255
+ else
256
+ failed++;
257
+ if (await assertThrows("tan(datum.value)", "tan() throws (OL only supports sin/cos/atan)", "Unsupported function"))
258
+ passed++;
259
+ else
260
+ failed++;
261
+ if (await assertThrows("indexof(datum.items, 'x')", "bare indexof() outside the != -1 / == -1 pattern throws a specific error", "indexof is only supported in the pattern"))
262
+ passed++;
263
+ else
264
+ failed++;
265
+ console.log("\n📋 Errors: Names That Don't Exist In Vega Or OL");
266
+ if (await assertThrows("totallyMadeUpFunction(datum.value)", "Nonexistent function name throws", "Unsupported function"))
267
+ passed++;
268
+ else
269
+ failed++;
270
+ if (await assertThrows("fooBarBaz(1, 2, 3)", "Another nonexistent function name throws", "Unsupported function"))
271
+ passed++;
272
+ else
273
+ failed++;
274
+ // Test 15: Error handling
137
275
  console.log("\n📋 Error Handling");
138
276
  try {
139
277
  await vega2ol(null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vega2ol",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "Vega expression to Open-Layers expression transpiler",
5
5
  "keywords": [
6
6
  "vega",
@@ -31,7 +31,8 @@
31
31
  "scripts": {
32
32
  "build": "tsc",
33
33
  "test": "node dist/tests/test-basic.js",
34
- "dev": "tsc --watch"
34
+ "dev": "tsc --watch",
35
+ "clean": "rm -rf dist"
35
36
  },
36
37
  "dependencies": {
37
38
  "vega-expression": "^6.1.0"