@purgeon/analyzer-jsx 0.1.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.
- package/LICENSE +21 -0
- package/dist/index.d.mts +51 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +330 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026-PRESENT Konstantin Kireyev <https://github.com/knst0>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { AnalyzerPlugin, GraphNode, JsxPurgeIRNode, ParseContext, PluginName } from "@purgeon/core";
|
|
2
|
+
//#region src/jsx-node.d.ts
|
|
3
|
+
interface JsxUsage {
|
|
4
|
+
component: string | null;
|
|
5
|
+
resolvedFrom: string | null;
|
|
6
|
+
props: unknown[];
|
|
7
|
+
}
|
|
8
|
+
interface ClassVariantCall {
|
|
9
|
+
name: string;
|
|
10
|
+
static: boolean;
|
|
11
|
+
config: unknown;
|
|
12
|
+
rawCode: string | null;
|
|
13
|
+
}
|
|
14
|
+
interface StyledComponentUsage {
|
|
15
|
+
baseTag: string | null;
|
|
16
|
+
kind: "element" | "component";
|
|
17
|
+
}
|
|
18
|
+
interface ImportBinding {
|
|
19
|
+
source: string;
|
|
20
|
+
imported: string;
|
|
21
|
+
}
|
|
22
|
+
interface JsxGraphNode extends GraphNode {
|
|
23
|
+
jsxUsages: JsxUsage[];
|
|
24
|
+
classVariantCalls: ClassVariantCall[];
|
|
25
|
+
styledComponentUsages: StyledComponentUsage[];
|
|
26
|
+
importBindings: Record<string, ImportBinding>;
|
|
27
|
+
staticExports: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
interface JsxAnalyzerOptions {
|
|
30
|
+
trackedCalls?: string[];
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/jsx-analyzer.d.ts
|
|
34
|
+
declare class JsxAnalyzer extends AnalyzerPlugin<JsxGraphNode> {
|
|
35
|
+
readonly name: PluginName;
|
|
36
|
+
private readonly trackedCalls;
|
|
37
|
+
constructor(options?: JsxAnalyzerOptions);
|
|
38
|
+
protected isModuleSupported(id: string): boolean;
|
|
39
|
+
protected parseModule(code: string, id: string, ctx: ParseContext): unknown;
|
|
40
|
+
protected createNode(id: string): JsxGraphNode;
|
|
41
|
+
protected analyzeModule(_id: string, ast: unknown, code: string, node: JsxGraphNode): void;
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/purge-ir.d.ts
|
|
45
|
+
declare function toJsxPurgeIR(node: JsxGraphNode): JsxPurgeIRNode;
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/index.d.ts
|
|
48
|
+
declare const jsxAnalyzer: (options?: JsxAnalyzerOptions) => JsxAnalyzer;
|
|
49
|
+
//#endregion
|
|
50
|
+
export { type ClassVariantCall, JsxAnalyzer, type JsxAnalyzerOptions, type JsxGraphNode, type JsxUsage, type StyledComponentUsage, jsxAnalyzer, toJsxPurgeIR };
|
|
51
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/jsx-node.ts","../src/jsx-analyzer.ts","../src/purge-ir.ts","../src/index.ts"],"mappings":";;UAEiB;EACf;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;;UAGe;EACf;EACA;;UAGe;EACf;EACA;;UAGe,qBAAqB;EACpC,WAAW;EACX,mBAAmB;EACnB,uBAAuB;EACvB,gBAAgB,eAAe;EAC/B,eAAe;;UAGA;EACf;;;;cChBW,oBAAoB,eAAe;WACrC,MAAM;mBAEE;cAEL,UAAS;YAKX,kBAAkB;YAIlB,YAAY,cAAc,YAAY,KAAK;YAO3C,WAAW,aAAa;YAaxB,cAAc,aAAa,cAAc,cAAc,MAAM;;;;iBChDzD,aAAa,MAAM,eAAe;;;cCErC,cAAe,UAAU,uBAAkB"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { AnalyzerPlugin, UNRESOLVED_REF_KEY, pluginName } from "@purgeon/core";
|
|
2
|
+
import { walk } from "oxc-walker";
|
|
3
|
+
//#region src/ast-utils.ts
|
|
4
|
+
function getPropKeyName(key) {
|
|
5
|
+
if (key.type === "Identifier") return key.name;
|
|
6
|
+
if (key.type === "Literal") return String(key.value);
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
/** Extracts a JSX element name: <Foo />, <Foo.Bar />, <ns:Foo /> */
|
|
10
|
+
function getJSXElementName(nameNode) {
|
|
11
|
+
if (!nameNode) return null;
|
|
12
|
+
switch (nameNode.type) {
|
|
13
|
+
case "JSXIdentifier": return nameNode.name;
|
|
14
|
+
case "JSXMemberExpression": return `${getJSXElementName(nameNode.object)}.${getJSXElementName(nameNode.property)}`;
|
|
15
|
+
case "JSXNamespacedName": return `${nameNode.namespace.name}:${nameNode.name.name}`;
|
|
16
|
+
default: return nameNode.name ?? null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Returns the source text of a node based on its position in the code. */
|
|
20
|
+
function sourceOf(node, code) {
|
|
21
|
+
if (!node || typeof node.start !== "number" || typeof node.end !== "number") return null;
|
|
22
|
+
return code.slice(node.start, node.end);
|
|
23
|
+
}
|
|
24
|
+
/** Static eval of an AST node → plain JS value. Returns { ok: false } for dynamic nodes. */
|
|
25
|
+
function tryStaticEval(node) {
|
|
26
|
+
if (!node) return { ok: false };
|
|
27
|
+
switch (node.type) {
|
|
28
|
+
case "Literal": return {
|
|
29
|
+
ok: true,
|
|
30
|
+
value: node.value
|
|
31
|
+
};
|
|
32
|
+
case "TemplateLiteral":
|
|
33
|
+
if (node.expressions?.length) return { ok: false };
|
|
34
|
+
return {
|
|
35
|
+
ok: true,
|
|
36
|
+
value: node.quasis.map((q) => q.value.cooked ?? q.value.raw).join("")
|
|
37
|
+
};
|
|
38
|
+
case "ArrayExpression": {
|
|
39
|
+
const values = [];
|
|
40
|
+
for (const el of node.elements) {
|
|
41
|
+
if (el === null) {
|
|
42
|
+
values.push(void 0);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const result = tryStaticEval(el);
|
|
46
|
+
if (!result.ok) return { ok: false };
|
|
47
|
+
values.push(result.value);
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
ok: true,
|
|
51
|
+
value: values
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
case "ObjectExpression": {
|
|
55
|
+
const obj = {};
|
|
56
|
+
for (const prop of node.properties) {
|
|
57
|
+
if (prop.type !== "Property" || prop.computed) return { ok: false };
|
|
58
|
+
const keyName = getPropKeyName(prop.key);
|
|
59
|
+
if (keyName === null) return { ok: false };
|
|
60
|
+
const valueResult = tryStaticEval(prop.value);
|
|
61
|
+
if (!valueResult.ok) return { ok: false };
|
|
62
|
+
obj[keyName] = valueResult.value;
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
ok: true,
|
|
66
|
+
value: obj
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
case "UnaryExpression":
|
|
70
|
+
if (node.operator === "-") {
|
|
71
|
+
const argResult = tryStaticEval(node.argument);
|
|
72
|
+
if (!argResult.ok || typeof argResult.value !== "number") return { ok: false };
|
|
73
|
+
return {
|
|
74
|
+
ok: true,
|
|
75
|
+
value: -argResult.value
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return { ok: false };
|
|
79
|
+
case "TSAsExpression":
|
|
80
|
+
case "TSSatisfiesExpression": return tryStaticEval(node.expression);
|
|
81
|
+
default: return { ok: false };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** Partial static eval: never bails on the whole structure for one dynamic sibling.
|
|
85
|
+
* Unresolvable → undefined; bare identifiers → UnresolvedRef marker. */
|
|
86
|
+
function tryStaticEvalPartial(node) {
|
|
87
|
+
if (!node) return void 0;
|
|
88
|
+
switch (node.type) {
|
|
89
|
+
case "Literal": return node.value;
|
|
90
|
+
case "Identifier": return { [UNRESOLVED_REF_KEY]: node.name };
|
|
91
|
+
case "TemplateLiteral":
|
|
92
|
+
if (node.expressions?.length) return void 0;
|
|
93
|
+
return node.quasis.map((q) => q.value.cooked ?? q.value.raw).join("");
|
|
94
|
+
case "ArrayExpression": return node.elements.map((el) => el === null ? void 0 : tryStaticEvalPartial(el));
|
|
95
|
+
case "ObjectExpression": {
|
|
96
|
+
const obj = {};
|
|
97
|
+
for (const prop of node.properties) {
|
|
98
|
+
if (prop.type !== "Property" || prop.computed) continue;
|
|
99
|
+
const keyName = getPropKeyName(prop.key);
|
|
100
|
+
if (keyName === null) continue;
|
|
101
|
+
obj[keyName] = tryStaticEvalPartial(prop.value);
|
|
102
|
+
}
|
|
103
|
+
return obj;
|
|
104
|
+
}
|
|
105
|
+
case "UnaryExpression": {
|
|
106
|
+
if (node.operator !== "-") return void 0;
|
|
107
|
+
const argValue = tryStaticEvalPartial(node.argument);
|
|
108
|
+
return typeof argValue === "number" ? -argValue : void 0;
|
|
109
|
+
}
|
|
110
|
+
case "TSAsExpression":
|
|
111
|
+
case "TSSatisfiesExpression": return tryStaticEvalPartial(node.expression);
|
|
112
|
+
default: return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/** Extracts the callee name of a CallExpression, to check it against tracked function names. */
|
|
116
|
+
function getCalleeName(callee) {
|
|
117
|
+
if (callee.type === "Identifier") return callee.name;
|
|
118
|
+
if (callee.type === "MemberExpression" && callee.property.type === "Identifier") return callee.property.name;
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
/** `export const X = ...` values, keyed by exported name, for cross-module ref resolution. */
|
|
122
|
+
function collectStaticExports(ast) {
|
|
123
|
+
const exports = {};
|
|
124
|
+
for (const statement of ast.body ?? ast.program?.body ?? []) {
|
|
125
|
+
if (statement.type !== "ExportNamedDeclaration" || statement.declaration?.type !== "VariableDeclaration") continue;
|
|
126
|
+
for (const declarator of statement.declaration.declarations) {
|
|
127
|
+
if (declarator.id.type !== "Identifier" || !declarator.init) continue;
|
|
128
|
+
exports[declarator.id.name] = tryStaticEvalPartial(declarator.init);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return exports;
|
|
132
|
+
}
|
|
133
|
+
/** Extracts { localName -> importSource } from the module's import declarations. */
|
|
134
|
+
function collectImportBindings(ast) {
|
|
135
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
136
|
+
walk(ast, { enter(node) {
|
|
137
|
+
if (node.type !== "ImportDeclaration") return;
|
|
138
|
+
const source = node.source.value;
|
|
139
|
+
for (const spec of node.specifiers ?? []) if (spec.type === "ImportDefaultSpecifier") bindings.set(spec.local.name, {
|
|
140
|
+
source,
|
|
141
|
+
imported: "default"
|
|
142
|
+
});
|
|
143
|
+
else if (spec.type === "ImportSpecifier") bindings.set(spec.local.name, {
|
|
144
|
+
source,
|
|
145
|
+
imported: spec.imported.name
|
|
146
|
+
});
|
|
147
|
+
else if (spec.type === "ImportNamespaceSpecifier") bindings.set(spec.local.name, {
|
|
148
|
+
source,
|
|
149
|
+
imported: "*"
|
|
150
|
+
});
|
|
151
|
+
} });
|
|
152
|
+
return bindings;
|
|
153
|
+
}
|
|
154
|
+
/** Resolves styles.foo / styles["foo"] → "<specifier>::<className>". Returns null if not a CSS Modules ref. */
|
|
155
|
+
function resolveCssModuleRef(expr, importBindings) {
|
|
156
|
+
if (expr.type !== "MemberExpression") return null;
|
|
157
|
+
if (expr.object?.type !== "Identifier") return null;
|
|
158
|
+
const binding = importBindings.get(expr.object.name);
|
|
159
|
+
if (!binding) return null;
|
|
160
|
+
if (!binding.source.endsWith(".module.css")) return null;
|
|
161
|
+
if (binding.imported !== "default") return null;
|
|
162
|
+
let propName = null;
|
|
163
|
+
if (!expr.computed && expr.property?.type === "Identifier") propName = expr.property.name;
|
|
164
|
+
else if (expr.computed && expr.property?.type === "Literal" && typeof expr.property.value === "string") propName = expr.property.value;
|
|
165
|
+
if (propName === null) return null;
|
|
166
|
+
return `${binding.source}::${propName}`;
|
|
167
|
+
}
|
|
168
|
+
/** Extracts JSX props to serializable form. Optional resolveExpr hook for custom resolution (e.g. CSS Modules). */
|
|
169
|
+
function extractProps(attributes, code, resolveExpr) {
|
|
170
|
+
return attributes.map((attr) => {
|
|
171
|
+
if (attr.type === "JSXSpreadAttribute") return {
|
|
172
|
+
name: "...spread",
|
|
173
|
+
kind: "spread",
|
|
174
|
+
code: sourceOf(attr.argument, code)
|
|
175
|
+
};
|
|
176
|
+
const name = attr.name.name ?? getJSXElementName(attr.name);
|
|
177
|
+
if (attr.value == null) return {
|
|
178
|
+
name,
|
|
179
|
+
kind: "literal",
|
|
180
|
+
value: true
|
|
181
|
+
};
|
|
182
|
+
if (attr.value.type === "JSXExpressionContainer") {
|
|
183
|
+
const expr = attr.value.expression;
|
|
184
|
+
if (resolveExpr) {
|
|
185
|
+
const resolved = resolveExpr(expr);
|
|
186
|
+
if (resolved) return {
|
|
187
|
+
name,
|
|
188
|
+
...resolved
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const staticResult = tryStaticEval(expr);
|
|
192
|
+
return staticResult.ok ? {
|
|
193
|
+
name,
|
|
194
|
+
kind: "literal",
|
|
195
|
+
value: staticResult.value
|
|
196
|
+
} : {
|
|
197
|
+
name,
|
|
198
|
+
kind: "expr",
|
|
199
|
+
code: sourceOf(expr, code)
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
name,
|
|
204
|
+
kind: "literal",
|
|
205
|
+
value: attr.value.value
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region src/jsx-analyzer.ts
|
|
211
|
+
const DEFAULT_TRACKED_CALLS = [
|
|
212
|
+
"cva",
|
|
213
|
+
"cx",
|
|
214
|
+
"cn",
|
|
215
|
+
"tv",
|
|
216
|
+
"clsx"
|
|
217
|
+
];
|
|
218
|
+
var JsxAnalyzer = class extends AnalyzerPlugin {
|
|
219
|
+
name = pluginName("jsx-graph");
|
|
220
|
+
trackedCalls;
|
|
221
|
+
constructor(options = {}) {
|
|
222
|
+
super();
|
|
223
|
+
this.trackedCalls = new Set(options.trackedCalls ?? DEFAULT_TRACKED_CALLS);
|
|
224
|
+
}
|
|
225
|
+
isModuleSupported(id) {
|
|
226
|
+
return /\.[jt]sx?$/.test(id);
|
|
227
|
+
}
|
|
228
|
+
parseModule(code, id, ctx) {
|
|
229
|
+
return ctx.parse(code, {
|
|
230
|
+
lang: id.endsWith("x") ? id.endsWith(".tsx") ? "tsx" : "jsx" : "ts",
|
|
231
|
+
sourceType: "module"
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
createNode(id) {
|
|
235
|
+
return {
|
|
236
|
+
id,
|
|
237
|
+
imports: [],
|
|
238
|
+
importers: [],
|
|
239
|
+
jsxUsages: [],
|
|
240
|
+
classVariantCalls: [],
|
|
241
|
+
styledComponentUsages: [],
|
|
242
|
+
importBindings: {},
|
|
243
|
+
staticExports: {}
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
analyzeModule(_id, ast, code, node) {
|
|
247
|
+
const importBindings = collectImportBindings(ast);
|
|
248
|
+
node.importBindings = Object.fromEntries(importBindings);
|
|
249
|
+
node.staticExports = collectStaticExports(ast);
|
|
250
|
+
const resolveCssExpr = (expr) => {
|
|
251
|
+
const ref = resolveCssModuleRef(expr, importBindings);
|
|
252
|
+
return ref ? {
|
|
253
|
+
kind: "css-module-ref",
|
|
254
|
+
value: ref
|
|
255
|
+
} : null;
|
|
256
|
+
};
|
|
257
|
+
walk(ast, { enter: (n) => {
|
|
258
|
+
if (n.type === "JSXOpeningElement") {
|
|
259
|
+
const name = getJSXElementName(n.name);
|
|
260
|
+
const binding = name ? importBindings.get(name) : void 0;
|
|
261
|
+
node.jsxUsages.push({
|
|
262
|
+
component: name,
|
|
263
|
+
resolvedFrom: binding ? binding.source : null,
|
|
264
|
+
props: extractProps(n.attributes, code, resolveCssExpr)
|
|
265
|
+
});
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (n.type === "CallExpression") {
|
|
269
|
+
const calleeName = getCalleeName(n.callee);
|
|
270
|
+
if (calleeName && this.trackedCalls.has(calleeName)) {
|
|
271
|
+
const configResult = tryStaticEval(n.arguments[0]);
|
|
272
|
+
node.classVariantCalls.push({
|
|
273
|
+
name: calleeName,
|
|
274
|
+
static: configResult.ok,
|
|
275
|
+
config: configResult.ok ? configResult.value : tryStaticEvalPartial(n.arguments[0]),
|
|
276
|
+
rawCode: configResult.ok ? null : sourceOf(n, code)
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (n.type === "TaggedTemplateExpression") {
|
|
282
|
+
const tag = n.tag;
|
|
283
|
+
if (tag.type === "MemberExpression" && tag.object.type === "Identifier" && tag.object.name === "styled") {
|
|
284
|
+
const baseTag = tag.property.type === "Identifier" ? tag.property.name : null;
|
|
285
|
+
node.styledComponentUsages.push({
|
|
286
|
+
baseTag,
|
|
287
|
+
kind: "element"
|
|
288
|
+
});
|
|
289
|
+
} else if (tag.type === "CallExpression" && tag.callee.type === "Identifier" && tag.callee.name === "styled") {
|
|
290
|
+
const arg = tag.arguments[0];
|
|
291
|
+
const baseTag = arg && arg.type === "Identifier" ? arg.name : null;
|
|
292
|
+
node.styledComponentUsages.push({
|
|
293
|
+
baseTag,
|
|
294
|
+
kind: "component"
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
} });
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
//#endregion
|
|
302
|
+
//#region src/purge-ir.ts
|
|
303
|
+
function toJsxPurgeIR(node) {
|
|
304
|
+
return {
|
|
305
|
+
id: node.id,
|
|
306
|
+
imports: node.imports,
|
|
307
|
+
jsxUsages: node.jsxUsages.map((usage) => ({ props: usage.props.map((prop) => ({
|
|
308
|
+
name: prop.name,
|
|
309
|
+
kind: prop.kind,
|
|
310
|
+
value: prop.value
|
|
311
|
+
})) })),
|
|
312
|
+
classVariantCalls: node.classVariantCalls.map((call) => ({
|
|
313
|
+
name: call.name,
|
|
314
|
+
static: call.static,
|
|
315
|
+
config: call.config
|
|
316
|
+
})),
|
|
317
|
+
importBindings: Object.fromEntries(Object.entries(node.importBindings).map(([key, binding]) => [key, {
|
|
318
|
+
source: binding.source,
|
|
319
|
+
imported: binding.imported
|
|
320
|
+
}])),
|
|
321
|
+
staticExports: node.staticExports
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
//#endregion
|
|
325
|
+
//#region src/index.ts
|
|
326
|
+
const jsxAnalyzer = (options) => new JsxAnalyzer(options);
|
|
327
|
+
//#endregion
|
|
328
|
+
export { JsxAnalyzer, jsxAnalyzer, toJsxPurgeIR };
|
|
329
|
+
|
|
330
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/ast-utils.ts","../src/jsx-analyzer.ts","../src/purge-ir.ts","../src/index.ts"],"sourcesContent":["import { UNRESOLVED_REF_KEY, type UnresolvedRef } from \"@purgeon/core\";\nimport { walk } from \"oxc-walker\";\n\nfunction getPropKeyName(key: any): string | null {\n if (key.type === \"Identifier\") return key.name;\n if (key.type === \"Literal\") return String(key.value);\n return null;\n}\n\n/** Extracts a JSX element name: <Foo />, <Foo.Bar />, <ns:Foo /> */\nexport function getJSXElementName(nameNode: any): string | null {\n if (!nameNode) return null;\n switch (nameNode.type) {\n case \"JSXIdentifier\":\n return nameNode.name;\n case \"JSXMemberExpression\":\n return `${getJSXElementName(nameNode.object)}.${getJSXElementName(nameNode.property)}`;\n case \"JSXNamespacedName\":\n return `${nameNode.namespace.name}:${nameNode.name.name}`;\n default:\n return nameNode.name ?? null;\n }\n}\n\n/** Returns the source text of a node based on its position in the code. */\nexport function sourceOf(node: any, code: string): string | null {\n if (!node || typeof node.start !== \"number\" || typeof node.end !== \"number\") {\n return null;\n }\n return code.slice(node.start, node.end);\n}\n\n/** Static eval of an AST node → plain JS value. Returns { ok: false } for dynamic nodes. */\nexport function tryStaticEval(node: any): { ok: boolean; value?: any } {\n if (!node) return { ok: false };\n\n switch (node.type) {\n case \"Literal\":\n return { ok: true, value: node.value };\n\n case \"TemplateLiteral\":\n if (node.expressions?.length) return { ok: false };\n return { ok: true, value: node.quasis.map((q: any) => q.value.cooked ?? q.value.raw).join(\"\") };\n\n case \"ArrayExpression\": {\n const values: any[] = [];\n for (const el of node.elements) {\n if (el === null) {\n values.push(undefined);\n continue;\n }\n const result = tryStaticEval(el);\n if (!result.ok) return { ok: false };\n values.push(result.value);\n }\n return { ok: true, value: values };\n }\n\n case \"ObjectExpression\": {\n const obj: Record<string, any> = {};\n for (const prop of node.properties) {\n if (prop.type !== \"Property\" || prop.computed) return { ok: false };\n\n const keyName = getPropKeyName(prop.key);\n if (keyName === null) return { ok: false };\n\n const valueResult = tryStaticEval(prop.value);\n if (!valueResult.ok) return { ok: false };\n obj[keyName] = valueResult.value;\n }\n return { ok: true, value: obj };\n }\n\n case \"UnaryExpression\": {\n if (node.operator === \"-\") {\n const argResult = tryStaticEval(node.argument);\n if (!argResult.ok || typeof argResult.value !== \"number\") return { ok: false };\n return { ok: true, value: -argResult.value };\n }\n return { ok: false };\n }\n\n case \"TSAsExpression\":\n case \"TSSatisfiesExpression\":\n return tryStaticEval(node.expression);\n\n default:\n return { ok: false };\n }\n}\n\n/** Partial static eval: never bails on the whole structure for one dynamic sibling.\n * Unresolvable → undefined; bare identifiers → UnresolvedRef marker. */\nexport function tryStaticEvalPartial(node: any): any {\n if (!node) return undefined;\n\n switch (node.type) {\n case \"Literal\":\n return node.value;\n\n case \"Identifier\":\n return { [UNRESOLVED_REF_KEY]: node.name } satisfies UnresolvedRef;\n\n case \"TemplateLiteral\":\n if (node.expressions?.length) return undefined;\n return node.quasis.map((q: any) => q.value.cooked ?? q.value.raw).join(\"\");\n\n case \"ArrayExpression\":\n return node.elements.map((el: any) => (el === null ? undefined : tryStaticEvalPartial(el)));\n\n case \"ObjectExpression\": {\n const obj: Record<string, any> = {};\n for (const prop of node.properties) {\n if (prop.type !== \"Property\" || prop.computed) continue;\n\n const keyName = getPropKeyName(prop.key);\n if (keyName === null) continue;\n\n obj[keyName] = tryStaticEvalPartial(prop.value);\n }\n return obj;\n }\n\n case \"UnaryExpression\": {\n if (node.operator !== \"-\") return undefined;\n const argValue = tryStaticEvalPartial(node.argument);\n return typeof argValue === \"number\" ? -argValue : undefined;\n }\n\n case \"TSAsExpression\":\n case \"TSSatisfiesExpression\":\n return tryStaticEvalPartial(node.expression);\n\n default:\n return undefined;\n }\n}\n\n/** Extracts the callee name of a CallExpression, to check it against tracked function names. */\nexport function getCalleeName(callee: any): string | null {\n if (callee.type === \"Identifier\") return callee.name;\n if (callee.type === \"MemberExpression\" && callee.property.type === \"Identifier\") {\n return callee.property.name;\n }\n return null;\n}\n\n/** `export const X = ...` values, keyed by exported name, for cross-module ref resolution. */\nexport function collectStaticExports(ast: any): Record<string, unknown> {\n const exports: Record<string, unknown> = {};\n\n for (const statement of ast.body ?? ast.program?.body ?? []) {\n if (statement.type !== \"ExportNamedDeclaration\" || statement.declaration?.type !== \"VariableDeclaration\") continue;\n\n for (const declarator of statement.declaration.declarations) {\n if (declarator.id.type !== \"Identifier\" || !declarator.init) continue;\n exports[declarator.id.name] = tryStaticEvalPartial(declarator.init);\n }\n }\n\n return exports;\n}\n\n/** Extracts { localName -> importSource } from the module's import declarations. */\nexport function collectImportBindings(ast: any) {\n const bindings = new Map<string, { source: string; imported: string }>();\n\n walk(ast, {\n enter(node: any) {\n if (node.type !== \"ImportDeclaration\") return;\n const source = node.source.value;\n for (const spec of node.specifiers ?? []) {\n if (spec.type === \"ImportDefaultSpecifier\") {\n bindings.set(spec.local.name, { source, imported: \"default\" });\n } else if (spec.type === \"ImportSpecifier\") {\n bindings.set(spec.local.name, { source, imported: (spec.imported as any).name });\n } else if (spec.type === \"ImportNamespaceSpecifier\") {\n bindings.set(spec.local.name, { source, imported: \"*\" });\n }\n }\n },\n });\n\n return bindings;\n}\n\n/** Resolves styles.foo / styles[\"foo\"] → \"<specifier>::<className>\". Returns null if not a CSS Modules ref. */\nexport function resolveCssModuleRef(expr: any, importBindings: Map<string, { source: string; imported: string }>): string | null {\n if (expr.type !== \"MemberExpression\") return null;\n if (expr.object?.type !== \"Identifier\") return null;\n\n const binding = importBindings.get(expr.object.name);\n if (!binding) return null;\n if (!binding.source.endsWith(\".module.css\")) return null;\n if (binding.imported !== \"default\") return null;\n\n let propName: string | null = null;\n if (!expr.computed && expr.property?.type === \"Identifier\") {\n propName = expr.property.name;\n } else if (expr.computed && expr.property?.type === \"Literal\" && typeof expr.property.value === \"string\") {\n propName = expr.property.value;\n }\n if (propName === null) return null;\n\n return `${binding.source}::${propName}`;\n}\n\n/** Extracts JSX props to serializable form. Optional resolveExpr hook for custom resolution (e.g. CSS Modules). */\nexport function extractProps(attributes: any[], code: string, resolveExpr?: (expr: any) => { kind: string; value: unknown } | null) {\n return attributes.map((attr: any) => {\n if (attr.type === \"JSXSpreadAttribute\") {\n return { name: \"...spread\", kind: \"spread\", code: sourceOf(attr.argument, code) };\n }\n\n const name = attr.name.name ?? getJSXElementName(attr.name);\n\n if (attr.value == null) {\n return { name, kind: \"literal\", value: true };\n }\n\n if (attr.value.type === \"JSXExpressionContainer\") {\n const expr = attr.value.expression;\n\n if (resolveExpr) {\n const resolved = resolveExpr(expr);\n if (resolved) return { name, ...resolved };\n }\n\n const staticResult = tryStaticEval(expr);\n return staticResult.ok ? { name, kind: \"literal\", value: staticResult.value } : { name, kind: \"expr\", code: sourceOf(expr, code) };\n }\n\n return { name, kind: \"literal\", value: attr.value.value };\n });\n}\n","import { AnalyzerPlugin, pluginName, type ParseContext, type PluginName } from \"@purgeon/core\";\nimport { walk } from \"oxc-walker\";\n\nimport {\n collectImportBindings,\n collectStaticExports,\n extractProps,\n getCalleeName,\n getJSXElementName,\n resolveCssModuleRef,\n sourceOf,\n tryStaticEval,\n tryStaticEvalPartial,\n} from \"./ast-utils\";\nimport type { JsxAnalyzerOptions, JsxGraphNode } from \"./jsx-node\";\n\nconst DEFAULT_TRACKED_CALLS = [\"cva\", \"cx\", \"cn\", \"tv\", \"clsx\"];\n\nexport class JsxAnalyzer extends AnalyzerPlugin<JsxGraphNode> {\n readonly name: PluginName = pluginName(\"jsx-graph\");\n\n private readonly trackedCalls: Set<string>;\n\n constructor(options: JsxAnalyzerOptions = {}) {\n super();\n this.trackedCalls = new Set(options.trackedCalls ?? DEFAULT_TRACKED_CALLS);\n }\n\n protected isModuleSupported(id: string): boolean {\n return /\\.[jt]sx?$/.test(id);\n }\n\n protected parseModule(code: string, id: string, ctx: ParseContext): unknown {\n return ctx.parse(code, {\n lang: id.endsWith(\"x\") ? (id.endsWith(\".tsx\") ? \"tsx\" : \"jsx\") : \"ts\",\n sourceType: \"module\",\n });\n }\n\n protected createNode(id: string): JsxGraphNode {\n return {\n id,\n imports: [],\n importers: [],\n jsxUsages: [],\n classVariantCalls: [],\n styledComponentUsages: [],\n importBindings: {},\n staticExports: {},\n };\n }\n\n protected analyzeModule(_id: string, ast: unknown, code: string, node: JsxGraphNode): void {\n const importBindings = collectImportBindings(ast);\n node.importBindings = Object.fromEntries(importBindings);\n node.staticExports = collectStaticExports(ast);\n\n const resolveCssExpr = (expr: any) => {\n const ref = resolveCssModuleRef(expr, importBindings);\n return ref ? { kind: \"css-module-ref\", value: ref } : null;\n };\n\n walk(ast as Parameters<typeof walk>[0], {\n enter: (n) => {\n if (n.type === \"JSXOpeningElement\") {\n const name = getJSXElementName(n.name);\n const binding = name ? importBindings.get(name) : undefined;\n node.jsxUsages.push({\n component: name,\n resolvedFrom: binding ? binding.source : null,\n props: extractProps(n.attributes, code, resolveCssExpr),\n });\n return;\n }\n\n if (n.type === \"CallExpression\") {\n const calleeName = getCalleeName(n.callee);\n if (calleeName && this.trackedCalls.has(calleeName)) {\n const configResult = tryStaticEval(n.arguments[0]);\n node.classVariantCalls.push({\n name: calleeName,\n static: configResult.ok,\n config: configResult.ok ? configResult.value : tryStaticEvalPartial(n.arguments[0]),\n rawCode: configResult.ok ? null : sourceOf(n, code),\n });\n }\n return;\n }\n\n if (n.type === \"TaggedTemplateExpression\") {\n const tag = n.tag;\n if (tag.type === \"MemberExpression\" && tag.object.type === \"Identifier\" && tag.object.name === \"styled\") {\n const baseTag = tag.property.type === \"Identifier\" ? tag.property.name : null;\n node.styledComponentUsages.push({ baseTag, kind: \"element\" });\n } else if (tag.type === \"CallExpression\" && tag.callee.type === \"Identifier\" && tag.callee.name === \"styled\") {\n const arg = tag.arguments[0];\n const baseTag = arg && arg.type === \"Identifier\" ? arg.name : null;\n node.styledComponentUsages.push({ baseTag, kind: \"component\" });\n }\n }\n },\n });\n }\n}\n","import type { JsxPurgeIRNode, JsxPurgeIRUsage, JsxPurgeIRVariantCall, JsxPurgeIRImportBinding } from \"@purgeon/core\";\n\nimport type { JsxGraphNode, JsxUsage, ClassVariantCall } from \"./jsx-node\";\n\nexport function toJsxPurgeIR(node: JsxGraphNode): JsxPurgeIRNode {\n return {\n id: node.id,\n imports: node.imports,\n jsxUsages: node.jsxUsages.map(\n (usage: JsxUsage): JsxPurgeIRUsage => ({\n props: usage.props.map((prop) => ({\n name: (prop as { name: string }).name,\n kind: (prop as { kind: string }).kind,\n value: (prop as { value?: unknown }).value,\n })),\n }),\n ),\n classVariantCalls: node.classVariantCalls.map(\n (call: ClassVariantCall): JsxPurgeIRVariantCall => ({\n name: call.name,\n static: call.static,\n config: call.config,\n }),\n ),\n importBindings: Object.fromEntries(\n Object.entries(node.importBindings).map(([key, binding]) => [\n key,\n { source: binding.source, imported: binding.imported } satisfies JsxPurgeIRImportBinding,\n ]),\n ),\n staticExports: node.staticExports,\n } satisfies JsxPurgeIRNode;\n}\n","import { JsxAnalyzer } from \"./jsx-analyzer\";\nimport type { JsxAnalyzerOptions } from \"./jsx-node\";\n\nexport { JsxAnalyzer } from \"./jsx-analyzer\";\nexport { toJsxPurgeIR } from \"./purge-ir\";\nexport type { JsxAnalyzerOptions, JsxGraphNode, JsxUsage, ClassVariantCall, StyledComponentUsage } from \"./jsx-node\";\nexport const jsxAnalyzer = (options?: JsxAnalyzerOptions) => new JsxAnalyzer(options);\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@purgeon/analyzer-jsx",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Walks JSX AST via oxc-walker, tracks element usages and class-variant calls (cva/cn/cx/tv/clsx) for purgeon",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ast",
|
|
7
|
+
"clsx",
|
|
8
|
+
"cva",
|
|
9
|
+
"jsx",
|
|
10
|
+
"oxc",
|
|
11
|
+
"purge",
|
|
12
|
+
"rolldown",
|
|
13
|
+
"tree-shaking",
|
|
14
|
+
"vite"
|
|
15
|
+
],
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"author": "Konstantin Kireyev <https://github.com/knst0>",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/knst0/purgeon.git",
|
|
21
|
+
"directory": "packages/analyzer-jsx"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.mts",
|
|
30
|
+
"default": "./dist/index.mjs"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"oxc-walker": "^1.0.0",
|
|
38
|
+
"@purgeon/core": "0.1.0"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsdown"
|
|
42
|
+
}
|
|
43
|
+
}
|