vega2ol 1.1.1 → 1.1.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/dist/main.js +53 -24
- package/dist/tests/test-basic.js +140 -2
- package/package.json +3 -2
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: "
|
|
51
|
-
log: "log",
|
|
52
|
-
exp: "exp",
|
|
48
|
+
pow: "^",
|
|
53
49
|
sin: "sin",
|
|
54
50
|
cos: "cos",
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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,40 @@ 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 = ["in", valueOL, arrayOL];
|
|
148
|
+
// indexof(...) != -1 → is in
|
|
149
|
+
if (operator === "!=") {
|
|
150
|
+
return inExpr;
|
|
151
|
+
}
|
|
152
|
+
// indexof(...) == -1 → is NOT in
|
|
153
|
+
if (operator === "==") {
|
|
154
|
+
return ["!", inExpr];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
134
159
|
const leftOL = this.visit(left);
|
|
135
160
|
const rightOL = this.visit(right);
|
|
136
161
|
const olOp = OPERATOR_MAPPING[operator];
|
|
@@ -177,6 +202,10 @@ class VegaToOLVisitor {
|
|
|
177
202
|
else {
|
|
178
203
|
throw new Vega2OLError(`Unsupported function call`);
|
|
179
204
|
}
|
|
205
|
+
if (this.isIndexOfCall(node)) {
|
|
206
|
+
throw new Vega2OLError(`indexof is only supported in the pattern 'indexof(array, value) != -1' (or '== -1'), ` +
|
|
207
|
+
`it cannot be used as a standalone expression in OpenLayers`);
|
|
208
|
+
}
|
|
180
209
|
const olFunc = FUNCTION_MAPPING[funcName.toLowerCase()];
|
|
181
210
|
if (!olFunc) {
|
|
182
211
|
throw new Vega2OLError(`Unsupported function: ${funcName}`);
|
package/dist/tests/test-basic.js
CHANGED
|
@@ -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("
|
|
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:
|
|
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"], ["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"], ["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"], ["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"], ["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"], ["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.
|
|
3
|
+
"version": "1.1.2",
|
|
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"
|