Skip to content

Commit 79db258

Browse files
committed
Add support for evaluating JEXL-based filter expressions.
This allows us to filter entries based on which Firefox version they apply to. Uses mozjexl extended with the version compare method from addons-moz-compare.
1 parent 5b0a768 commit 79db258

File tree

5 files changed

+150
-2
lines changed

5 files changed

+150
-2
lines changed

package-lock.json

Lines changed: 15 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
"vite": "^7.0.0"
2222
},
2323
"dependencies": {
24-
"lit": "^3.3.0"
24+
"addons-moz-compare": "^1.3.0",
25+
"lit": "^3.3.0",
26+
"mozjexl": "^1.1.6"
2527
}
2628
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
declare module "addons-moz-compare" {
6+
export function mozCompare(a: string, b: string): boolean;
7+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
import mozjexl from "mozjexl";
6+
import { mozCompare } from "addons-moz-compare";
7+
8+
const jexl = new mozjexl.Jexl();
9+
jexl.addTransforms({
10+
versionCompare: (value: unknown, ...args: unknown[]) => {
11+
// Ensure we have two string arguments for version comparison
12+
if (args.length < 1) {
13+
throw new Error("versionCompare requires two arguments");
14+
}
15+
const a = String(value);
16+
const b = String(args[0]);
17+
return mozCompare(a, b);
18+
},
19+
});
20+
21+
/**
22+
* Evaluates a JEXL-based filter expression.
23+
* @param expression The filter expression to evaluate.
24+
* @param context The context to evaluate the expression in.
25+
* @returns The result of the evaluation.
26+
*/
27+
export function evaluateFilterExpression(expression: string, context: Record<string, unknown>) {
28+
return jexl.eval(expression, context);
29+
}
30+
31+
/**
32+
* Evaluates a given JEXL filter expression against a Gecko / Firefox version
33+
* number.
34+
* @param version The version number to check.
35+
* @param filterExpression The filter expression to evaluate.
36+
* @returns True if the version number matches the filter expression, false
37+
* otherwise.
38+
*/
39+
export async function versionNumberMatchesFilterExpression(
40+
version: string,
41+
filterExpression: string | undefined,
42+
): Promise<boolean> {
43+
// An empty filter expression always matches.
44+
if (!filterExpression?.length) {
45+
return true;
46+
}
47+
48+
const context = {
49+
env: {
50+
version,
51+
},
52+
};
53+
const result = await evaluateFilterExpression(filterExpression, context);
54+
console.debug(
55+
"Evaluating filter expression",
56+
filterExpression,
57+
"for version",
58+
version,
59+
"result",
60+
result,
61+
);
62+
63+
return result === true;
64+
}

src/filter-expression/mozjexl.d.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
// Provides basic type definitions for mozjexl.
6+
// Only the properties that are used in this project are defined.
7+
8+
declare module "mozjexl" {
9+
/**
10+
* A transform function that can be used in JEXL expressions. Transform
11+
* functions receive the value to be transformed as the first argument,
12+
* followed by any additional arguments passed to the transform.
13+
*/
14+
export type TransformFunction = (
15+
value: unknown,
16+
...args: unknown[]
17+
) => unknown | Promise<unknown>;
18+
19+
/**
20+
* A map of transform names to their corresponding transform functions.
21+
*/
22+
export interface Transforms {
23+
[key: string]: TransformFunction;
24+
}
25+
26+
/**
27+
* The context object passed to JEXL expressions, containing variables
28+
* accessible during evaluation.
29+
*/
30+
export interface JexlContext {
31+
[key: string]: unknown;
32+
}
33+
34+
/**
35+
* The main JEXL class for parsing and evaluating expressions.
36+
*/
37+
export class Jexl {
38+
constructor();
39+
/**
40+
* Adds multiple transform functions at once.
41+
* @param map A map of transform names to transform functions
42+
*/
43+
addTransforms(map: Transforms): void;
44+
45+
/**
46+
* Evaluates a JEXL expression within an optional context.
47+
* @param expression The JEXL expression to be evaluated
48+
* @param context A mapping of variables to values, which will be made
49+
* accessible to the JEXL expression
50+
* @returns A Promise that resolves with the result of the evaluation
51+
*/
52+
eval(expression: string, context?: JexlContext): Promise<unknown>;
53+
}
54+
55+
// The default export is an instance of Jexl
56+
const mozjexl: Jexl & {
57+
Jexl: typeof Jexl;
58+
};
59+
60+
export default mozjexl;
61+
}

0 commit comments

Comments
 (0)