Open History to see the last commit
1import { defineRule, type ESTree } from "@oxlint/plugins";
2
3const UNSUPPORTED_GLOBAL_APIS = new Map([
4 [
5 "Intl.Segmenter",
6 "Use a portable implementation or a simpler character-counting approximation.",
7 ],
8]);
9
10const UNSUPPORTED_METHODS = new Map([
11 [
12 "toSorted",
13 "Hermes does not implement Array#toSorted. Copy the array first: [...array].sort(...).",
14 ],
15 [
16 "toReversed",
17 "Hermes does not implement Array#toReversed. Copy the array first: [...array].reverse().",
18 ],
19 [
20 "toSpliced",
21 "Hermes does not implement Array#toSpliced. Copy the array first: const copy = [...array]; copy.splice(...); use copy.",
22 ],
23]);
24
25function memberName(node: ESTree.MemberExpression): string | null {
26 const { property } = node;
27 if (!node.computed && property.type === "Identifier") return property.name;
28 if (property.type === "Literal" && typeof property.value === "string") return property.value;
29 if (property.type === "TemplateLiteral" && property.expressions.length === 0)
30 return property.quasis[0]?.value.cooked ?? null;
31 return null;
32}
33
34function globalApiPath(node: ESTree.Node): string | null {
35 if (node.type === "Identifier") return node.name;
36 if (node.type !== "MemberExpression") return null;
37 const object = globalApiPath(node.object);
38 const property = memberName(node);
39 if (object === null || property === null) return null;
40 return ["globalThis", "global", "window"].includes(object) ? property : `${object}.${property}`;
41}
42
43export default defineRule({
44 meta: {
45 type: "problem",
46 docs: {
47 description: "Disallow APIs that Hermes does not implement in mobile and shared client code.",
48 },
49 },
50 create(context) {
51 function checkApi(node: ESTree.CallExpression | ESTree.NewExpression) {
52 const path = globalApiPath(node.callee);
53 const replacement = path === null ? undefined : UNSUPPORTED_GLOBAL_APIS.get(path);
54 if (replacement !== undefined) {
55 context.report({
56 node: node.callee,
57 message: `Hermes does not implement ${path}. ${replacement}`,
58 });
59 return;
60 }
61 if (node.type !== "CallExpression" || node.callee.type !== "MemberExpression") return;
62 const name = memberName(node.callee);
63 const message = name === null ? undefined : UNSUPPORTED_METHODS.get(name);
64 if (message !== undefined) context.report({ node: node.callee.property, message });
65 }
66 return { NewExpression: checkApi, CallExpression: checkApi };
67 },
68});