mirror of
https://github.com/VibedByKaKi/t3-code-android-nightly.git
synced 2026-10-10 12:21:16 +02:00
125 lines
4.5 KiB
TypeScript
125 lines
4.5 KiB
TypeScript
import { defineRule } from "@oxlint/plugins";
|
|
import * as Option from "effect/Option";
|
|
|
|
import { getPropertyName, isIdentifier, unwrapExpression } from "../utils.ts";
|
|
|
|
const RUNTIME_PROPERTIES = new Set(["platform", "arch"]);
|
|
const NODE_OS_MODULES = new Set(["node:os", "os"]);
|
|
|
|
const isGlobalProcessObject = (node: unknown): boolean => {
|
|
const expression = unwrapExpression(node);
|
|
if (isIdentifier(expression, "process")) return true;
|
|
if (Option.isNone(expression) || expression.value.type !== "MemberExpression") return false;
|
|
|
|
const object = unwrapExpression(expression.value.object);
|
|
const property = getPropertyName(expression.value.property);
|
|
return (
|
|
isIdentifier(object, "globalThis") && Option.isSome(property) && property.value === "process"
|
|
);
|
|
};
|
|
|
|
const message = (property: string) =>
|
|
`Use HostProcess.${property === "arch" ? "Architecture" : "Platform"} instead of process.${property}; inject the runtime reference in Effect code and provide it explicitly in tests.`;
|
|
|
|
const getLiteralStringValue = (node: unknown): Option.Option<string> => {
|
|
if (typeof node !== "object" || node === null) return Option.none();
|
|
if (!("type" in node) || node.type !== "Literal") return Option.none();
|
|
if (!("value" in node) || typeof node.value !== "string") return Option.none();
|
|
return Option.some(node.value);
|
|
};
|
|
|
|
export default defineRule({
|
|
meta: {
|
|
type: "problem",
|
|
docs: {
|
|
description:
|
|
"Disallow direct host runtime platform/architecture reads; the shared host process references are exempted in the lint config.",
|
|
},
|
|
},
|
|
createOnce(context) {
|
|
const nodeOsNamespaces = new Set<string>();
|
|
const nodeOsRuntimeImports = new Map<string, string>();
|
|
|
|
const resetBindings = () => {
|
|
nodeOsNamespaces.clear();
|
|
nodeOsRuntimeImports.clear();
|
|
};
|
|
|
|
const trackImportDeclaration = (node: unknown) => {
|
|
if (typeof node !== "object" || node === null) return;
|
|
if (!("source" in node)) return;
|
|
|
|
const source = getLiteralStringValue(node.source);
|
|
if (Option.isNone(source) || !NODE_OS_MODULES.has(source.value)) return;
|
|
if (!("specifiers" in node) || !Array.isArray(node.specifiers)) return;
|
|
|
|
for (const specifier of node.specifiers) {
|
|
if (typeof specifier !== "object" || specifier === null) continue;
|
|
if (!("local" in specifier)) continue;
|
|
|
|
const local = unwrapExpression(specifier.local);
|
|
if (Option.isNone(local) || local.value.type !== "Identifier") continue;
|
|
const localName = local.value.name;
|
|
|
|
if (
|
|
specifier.type === "ImportNamespaceSpecifier" ||
|
|
specifier.type === "ImportDefaultSpecifier"
|
|
) {
|
|
nodeOsNamespaces.add(localName);
|
|
continue;
|
|
}
|
|
|
|
if (specifier.type !== "ImportSpecifier" || !("imported" in specifier)) continue;
|
|
|
|
const imported = getPropertyName(specifier.imported);
|
|
if (Option.isSome(imported) && RUNTIME_PROPERTIES.has(imported.value)) {
|
|
nodeOsRuntimeImports.set(localName, imported.value);
|
|
}
|
|
}
|
|
};
|
|
|
|
const getNodeOsRuntimeCall = (callee: unknown): Option.Option<string> => {
|
|
const expression = unwrapExpression(callee);
|
|
if (Option.isNone(expression)) return Option.none();
|
|
|
|
if (expression.value.type === "Identifier") {
|
|
const property = nodeOsRuntimeImports.get(expression.value.name);
|
|
return property === undefined ? Option.none() : Option.some(property);
|
|
}
|
|
|
|
if (expression.value.type !== "MemberExpression") return Option.none();
|
|
|
|
const object = unwrapExpression(expression.value.object);
|
|
if (Option.isNone(object) || object.value.type !== "Identifier") return Option.none();
|
|
if (!nodeOsNamespaces.has(object.value.name)) return Option.none();
|
|
|
|
return Option.filter(getPropertyName(expression.value.property), (property) =>
|
|
RUNTIME_PROPERTIES.has(property),
|
|
);
|
|
};
|
|
|
|
return {
|
|
before: resetBindings,
|
|
ImportDeclaration: trackImportDeclaration,
|
|
MemberExpression(node) {
|
|
const property = getPropertyName(node.property);
|
|
if (Option.isNone(property) || !RUNTIME_PROPERTIES.has(property.value)) return;
|
|
if (!isGlobalProcessObject(node.object)) return;
|
|
|
|
context.report({
|
|
node,
|
|
message: message(property.value),
|
|
});
|
|
},
|
|
CallExpression(node) {
|
|
const property = getNodeOsRuntimeCall(node.callee);
|
|
if (Option.isNone(property)) return;
|
|
|
|
context.report({
|
|
node,
|
|
message: message(property.value),
|
|
});
|
|
},
|
|
};
|
|
},
|
|
});
|