1import type { ESTree } from "@oxlint/plugins";
2import * as Option from "effect/Option";
3
4type ExpressionWrapper =
5 | ESTree.ChainExpression
6 | ESTree.ParenthesizedExpression
7 | ESTree.TSNonNullExpression
8 | ESTree.TSAsExpression
9 | ESTree.TSTypeAssertion;
10
11type AstNode = ESTree.Node;
12
13const asAstNode = (node: unknown): Option.Option<AstNode> =>
14 typeof node === "object" && node !== null && "type" in node && typeof node.type === "string"
15 ? Option.some(node as AstNode)
16 : Option.none();
17
18const isExpressionWrapper = (node: AstNode): node is ExpressionWrapper =>
19 node.type === "ChainExpression" ||
20 node.type === "ParenthesizedExpression" ||
21 node.type === "TSNonNullExpression" ||
22 node.type === "TSAsExpression" ||
23 node.type === "TSTypeAssertion";
24
25export function unwrapExpression(node: unknown): Option.Option<AstNode> {
26 let current = asAstNode(node);
27
28 while (Option.isSome(current) && isExpressionWrapper(current.value)) {
29 current = asAstNode(current.value.expression);
30 }
31
32 return current;
33}
34
35export function getPropertyName(node: unknown): Option.Option<string> {
36 return Option.flatMap(asAstNode(node), (expression) => {
37 if (expression.type === "Identifier" && typeof expression.name === "string") {
38 return Option.some(expression.name);
39 }
40 if (expression.type === "PrivateIdentifier" && typeof expression.name === "string") {
41 return Option.some(expression.name);
42 }
43 if (expression.type === "Literal" && typeof expression.value === "string") {
44 return Option.some(expression.value);
45 }
46 return Option.none();
47 });
48}
49
50export function isIdentifier(node: Option.Option<AstNode>, name?: string): boolean {
51 if (Option.isNone(node)) return false;
52 const expression = node.value;
53 return (
54 expression.type === "Identifier" &&
55 typeof expression.name === "string" &&
56 (name === undefined || expression.name === name)
57 );
58}