Skip to content
Source
Sign inSign up

no-mobile-pull-request-glyph-literals.ts

65 lines2.2 KB

1import { defineRule, type ESTree } from "@oxlint/plugins";
2​
3const PULL_REQUEST_GLYPH_NAMES = new Set([
4 "arrow.triangle.pull",
5 "arrow.triangle.merge",
6 "git.pull-request.closed",
7 "git.pull-request.draft",
8 "square.3.layers.3d",
9]);
10​
11// Material names reach AppSymbol only through `{ ios, android }` objects, and
12// "merge" is too common a string to ban outside that slot.
13const ANDROID_PULL_REQUEST_GLYPH_NAMES = new Set(["merge"]);
14​
15const MESSAGE =
16 "Pick a glyph by meaning from PullRequestGlyph in apps/mobile/src/features/pull-requests/pullRequestGlyphs.ts so every surface draws the same pull request the same way.";
17​
18function propertyKey(node: { key: ESTree.PropertyKey }) {
19 if (node.key.type === "Identifier") return node.key.name;
20 if (node.key.type === "Literal") return node.key.value;
21 return null;
23​
24function staticString(node: ESTree.Node) {
25 if (node.type === "Literal") return typeof node.value === "string" ? node.value : null;
26 if (node.type === "TemplateLiteral" && node.expressions.length === 0) {
27 return node.quasis[0]?.value.cooked ?? null;
28 }
29 return null;
31​
32export default defineRule({
33 meta: {
34 type: "problem",
35 docs: {
36 description:
37 "Disallow pull request symbol names as string literals in mobile code; the import restriction that guards web cannot see string names.",
38 },
39 },
40 create(context) {
41 return {
42 Literal(node) {
43 if (typeof node.value !== "string" || !PULL_REQUEST_GLYPH_NAMES.has(node.value)) return;
44 context.report({ node, message: MESSAGE });
45 },
46 Property(node) {
47 if (
48 propertyKey(node) !== "android" ||
49 node.parent.type !== "ObjectExpression" ||
50 !node.parent.properties.some(
51 (property) => property.type === "Property" && propertyKey(property) === "ios",
52 )
53 )
54 return;
55 const value = staticString(node.value);
56 if (value === null || !ANDROID_PULL_REQUEST_GLYPH_NAMES.has(value)) return;
57 context.report({ node: node.value, message: MESSAGE });
58 },
59 TemplateElement(node) {
60 if (!PULL_REQUEST_GLYPH_NAMES.has(node.value.cooked ?? "")) return;
61 context.report({ node, message: MESSAGE });
62 },
63 };
64 },
65});