@timmo001/oxlint-rules 0.3.6 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,219 @@
1
+ import { staticMemberName } from "./ast.ts";
2
+ import { jsonObject } from "./json.ts";
3
+
4
+ import type { ESTree, Settings } from "@oxlint/plugins";
5
+
6
+ type AssignedMethod = "assignedElements" | "assignedNodes";
7
+
8
+ type QueryMethod = "querySelector" | "querySelectorAll";
9
+
10
+ export type LitQueryMethod = AssignedMethod | QueryMethod;
11
+
12
+ type WrapperExpression =
13
+ | ESTree.ChainExpression
14
+ | ESTree.ParenthesizedExpression
15
+ | ESTree.TSAsExpression
16
+ | ESTree.TSNonNullExpression
17
+ | ESTree.TSSatisfiesExpression
18
+ | ESTree.TSTypeAssertion;
19
+
20
+ const renderRoots = new Set(["renderRoot", "shadowRoot"]);
21
+
22
+ /** Read `settings.lit.elementBaseClasses`, as eslint-plugin-lit does. */
23
+ export function litElementBaseClasses(
24
+ settings: Readonly<Settings>,
25
+ ): ReadonlySet<string> {
26
+ const configured = jsonObject(settings.lit)?.elementBaseClasses;
27
+
28
+ return new Set([
29
+ "LitElement",
30
+ ...(Array.isArray(configured) ? configured.map(String) : []),
31
+ ]);
32
+ }
33
+
34
+ function isWrapperExpression(
35
+ node: ESTree.Node | null,
36
+ ): node is WrapperExpression {
37
+ return (
38
+ node?.type === "ChainExpression" ||
39
+ node?.type === "ParenthesizedExpression" ||
40
+ node?.type === "TSAsExpression" ||
41
+ node?.type === "TSNonNullExpression" ||
42
+ node?.type === "TSSatisfiesExpression" ||
43
+ node?.type === "TSTypeAssertion"
44
+ );
45
+ }
46
+
47
+ function unwrapExpression(node: ESTree.Node): ESTree.Node {
48
+ let current = node;
49
+
50
+ while (isWrapperExpression(current)) current = current.expression;
51
+
52
+ return current;
53
+ }
54
+
55
+ function extendsLitBase(
56
+ node: ESTree.Node,
57
+ bases: ReadonlySet<string>,
58
+ ): boolean {
59
+ if (node.type === "Identifier") return bases.has(node.name);
60
+
61
+ return (
62
+ node.type === "CallExpression" &&
63
+ node.arguments.some((argument) => extendsLitBase(argument, bases))
64
+ );
65
+ }
66
+
67
+ function isLitClass(node: ESTree.Class, bases: ReadonlySet<string>): boolean {
68
+ const hasCustomElementDecorator = node.decorators.some(
69
+ (decorator) =>
70
+ decorator.expression.type === "CallExpression" &&
71
+ decorator.expression.callee.type === "Identifier" &&
72
+ decorator.expression.callee.name === "customElement",
73
+ );
74
+
75
+ return (
76
+ hasCustomElementDecorator ||
77
+ (node.superClass !== null && extendsLitBase(node.superClass, bases))
78
+ );
79
+ }
80
+
81
+ function classOfBody(body: ESTree.Node): ESTree.Class | null {
82
+ const owner = body.parent;
83
+
84
+ return owner?.type === "ClassDeclaration" || owner?.type === "ClassExpression"
85
+ ? owner
86
+ : null;
87
+ }
88
+
89
+ /** The class whose instance `this` refers to at `node`, if any. */
90
+ function thisClass(node: ESTree.Node): ESTree.Class | null {
91
+ let current = node;
92
+
93
+ while (current.parent) {
94
+ const parent: ESTree.Node = current.parent;
95
+
96
+ if (
97
+ parent.type === "FunctionDeclaration" ||
98
+ parent.type === "FunctionExpression"
99
+ ) {
100
+ const owner = parent.parent;
101
+
102
+ return (owner?.type === "MethodDefinition" ||
103
+ owner?.type === "TSAbstractMethodDefinition") &&
104
+ !owner.static &&
105
+ owner.value === parent
106
+ ? classOfBody(owner.parent)
107
+ : null;
108
+ }
109
+
110
+ if (
111
+ (parent.type === "PropertyDefinition" ||
112
+ parent.type === "AccessorProperty") &&
113
+ parent.value === current
114
+ ) {
115
+ return parent.static ? null : classOfBody(parent.parent);
116
+ }
117
+
118
+ if (
119
+ parent.type === "StaticBlock" ||
120
+ parent.type === "ClassBody" ||
121
+ parent.type === "Program"
122
+ ) {
123
+ return null;
124
+ }
125
+
126
+ current = parent;
127
+ }
128
+
129
+ return null;
130
+ }
131
+
132
+ function isStaticSelector(node: ESTree.Node | undefined): boolean {
133
+ return (
134
+ node?.type === "Literal" ||
135
+ (node?.type === "TemplateLiteral" && node.expressions.length === 0)
136
+ );
137
+ }
138
+
139
+ function renderRootQuery(
140
+ node: ESTree.CallExpression,
141
+ bases: ReadonlySet<string>,
142
+ ): { readonly method: QueryMethod; readonly root: string } | null {
143
+ const method = staticMemberName(node.callee);
144
+
145
+ if (
146
+ (method !== "querySelector" && method !== "querySelectorAll") ||
147
+ node.callee.type !== "MemberExpression" ||
148
+ !isStaticSelector(node.arguments[0])
149
+ ) {
150
+ return null;
151
+ }
152
+
153
+ const receiver = unwrapExpression(node.callee.object);
154
+ const root = staticMemberName(receiver);
155
+
156
+ if (
157
+ !root ||
158
+ !renderRoots.has(root) ||
159
+ receiver.type !== "MemberExpression" ||
160
+ receiver.object.type !== "ThisExpression"
161
+ ) {
162
+ return null;
163
+ }
164
+
165
+ const owner = thisClass(receiver.object);
166
+
167
+ return owner && isLitClass(owner, bases) ? { method, root } : null;
168
+ }
169
+
170
+ function isAssignedMethod(name: string | null): name is AssignedMethod {
171
+ return name === "assignedElements" || name === "assignedNodes";
172
+ }
173
+
174
+ function feedsAssignedCall(node: ESTree.CallExpression): boolean {
175
+ let current: ESTree.Node = node;
176
+
177
+ while (isWrapperExpression(current.parent)) current = current.parent;
178
+
179
+ const member = current.parent;
180
+
181
+ return (
182
+ member?.type === "MemberExpression" &&
183
+ member.object === current &&
184
+ isAssignedMethod(staticMemberName(member))
185
+ );
186
+ }
187
+
188
+ /**
189
+ * Match a static render-root query in a Lit class that a query decorator can
190
+ * replace, following eslint-plugin-lit's `prefer-query-decorators`.
191
+ */
192
+ export function litQueryDecoratorCall(
193
+ node: ESTree.CallExpression,
194
+ bases: ReadonlySet<string>,
195
+ ): { readonly method: LitQueryMethod; readonly root: string } | null {
196
+ const method = staticMemberName(node.callee);
197
+
198
+ if (!isAssignedMethod(method)) {
199
+ return feedsAssignedCall(node) ? null : renderRootQuery(node, bases);
200
+ }
201
+
202
+ if (node.callee.type !== "MemberExpression") return null;
203
+ const inner = unwrapExpression(node.callee.object);
204
+
205
+ if (inner.type !== "CallExpression") return null;
206
+ const query = renderRootQuery(inner, bases);
207
+
208
+ return query?.method === "querySelector"
209
+ ? { method, root: query.root }
210
+ : null;
211
+ }
212
+
213
+ /** Whether a call is a static render-root query in a Lit class. */
214
+ export function isLitRenderRootQuery(
215
+ node: ESTree.CallExpression,
216
+ bases: ReadonlySet<string>,
217
+ ): boolean {
218
+ return renderRootQuery(node, bases) !== null;
219
+ }
@@ -0,0 +1 @@
1
+ export { resolveVariable } from "../../vendor/anti-slop/src/shared/scope.ts";