Skip to content

Commit 0dbaae2

Browse files
committed
feat: add jsx-element and type plugin
1 parent f70915e commit 0dbaae2

File tree

22 files changed

+416
-216
lines changed

22 files changed

+416
-216
lines changed

.changeset/welcome.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
---
22
"@esusage/cli": minor
33
"@esusage/core": minor
4+
"@esusage/plugin-jsx-element": minor
5+
"@esusage/plugin-type": minor
46
---
57

68
v0.1.0 release 🚀

applications/cli/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"repository": {
3333
"type": "git",
3434
"url": "git@github.com:adbayb/esusage.git",
35-
"directory": "esusage"
35+
"directory": "applications/cli"
3636
},
3737
"scripts": {
3838
"build": "quickbundle build",
@@ -42,6 +42,8 @@
4242
"quickbundle": "1.2.0"
4343
},
4444
"dependencies": {
45-
"@esusage/core": "workspace:^"
45+
"@esusage/core": "workspace:^",
46+
"@esusage/plugin-jsx-element": "workspace:^",
47+
"@esusage/plugin-type": "workspace:^"
4648
}
4749
}

applications/cli/src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { esusage } from "@esusage/core";
2+
import jsxElementPlugin from "@esusage/plugin-jsx-element";
3+
import typePlugin from "@esusage/plugin-type";
24

35
const main = async () => {
4-
const items = await esusage(process.cwd());
6+
const items = await esusage(process.cwd(), {
7+
plugins: [jsxElementPlugin, typePlugin],
8+
});
59

610
console.log(JSON.stringify(items, null, 2), items.length);
711
};

libraries/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"repository": {
2929
"type": "git",
3030
"url": "https://github.com/adbayb/esusage.git",
31-
"directory": "packages/core"
31+
"directory": "libraries/core"
3232
},
3333
"keywords": [
3434
"analyze",

libraries/core/src/esusage.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { readFileSync } from "fs";
2+
3+
import { require, resolvePackageJson } from "./helpers";
4+
import { createItem } from "./modules/item";
5+
import type { Item } from "./modules/item";
6+
import { parse } from "./modules/parser";
7+
import type { ParseOptions } from "./modules/parser";
8+
import { scan } from "./modules/scanner";
9+
import type { ScanOptions } from "./modules/scanner";
10+
import type { Package } from "./types";
11+
12+
type Options = Partial<
13+
Pick<ScanOptions, "excludeFolders" | "includeFiles"> & {
14+
/**
15+
* Only analyze components imported from the specificied module list.
16+
*/
17+
includeModules: string[];
18+
}
19+
> &
20+
Pick<ParseOptions, "plugins">;
21+
22+
export const esusage = async (path: string, options: Options) => {
23+
const projects = await scan(path);
24+
const items: Item[] = [];
25+
26+
for (const project of projects) {
27+
const module = project.metadata.name;
28+
29+
const dependencies = {
30+
...project.metadata.devDependencies,
31+
...project.metadata.optionalDependencies,
32+
...project.metadata.dependencies,
33+
};
34+
35+
const link = project.link;
36+
37+
for (const file of project.files) {
38+
const code = readFileSync(file, "utf-8");
39+
40+
await parse(code, {
41+
onAdd(item) {
42+
if (
43+
options.includeModules &&
44+
options.includeModules.length > 0 &&
45+
!options.includeModules.includes(item.module)
46+
) {
47+
return;
48+
}
49+
50+
let version: string;
51+
52+
try {
53+
version = (
54+
require(
55+
resolvePackageJson(
56+
require.resolve(item.module, {
57+
paths: [file],
58+
}),
59+
),
60+
) as Package
61+
).version;
62+
} catch {
63+
version = dependencies[item.module] ?? "";
64+
}
65+
66+
items.push(
67+
createItem({
68+
...item,
69+
location: {
70+
code,
71+
file,
72+
link,
73+
module,
74+
offset: item.offset,
75+
path,
76+
},
77+
version,
78+
}),
79+
);
80+
},
81+
plugins: options.plugins,
82+
});
83+
}
84+
}
85+
86+
return items;
87+
};

libraries/core/src/index.ts

Lines changed: 2 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,2 @@
1-
import { readFileSync } from "fs";
2-
3-
import { require, resolvePackageJson } from "./helpers";
4-
import { createItem } from "./modules/item";
5-
import type { Item } from "./modules/item";
6-
import { parse } from "./modules/parser";
7-
import { jsxElementPlugin, typePlugin } from "./modules/plugin";
8-
import { scan } from "./modules/scanner";
9-
import type { ScanOptions } from "./modules/scanner";
10-
import type { Package } from "./types";
11-
12-
type Options = Partial<
13-
Pick<ScanOptions, "excludeFolders" | "includeFiles"> & {
14-
/**
15-
* Only analyze components imported from the specificied module list.
16-
*/
17-
includeModules: string[];
18-
}
19-
>;
20-
21-
export const esusage = async (path: string, options: Options = {}) => {
22-
const projects = await scan(path);
23-
const items: Item[] = [];
24-
25-
for (const project of projects) {
26-
const module = project.metadata.name;
27-
28-
const dependencies = {
29-
...project.metadata.devDependencies,
30-
...project.metadata.optionalDependencies,
31-
...project.metadata.dependencies,
32-
};
33-
34-
const link = project.link;
35-
36-
for (const file of project.files) {
37-
const code = readFileSync(file, "utf-8");
38-
39-
await parse(code, {
40-
onAdd(item) {
41-
if (
42-
options.includeModules &&
43-
options.includeModules.length > 0 &&
44-
!options.includeModules.includes(item.module)
45-
) {
46-
return;
47-
}
48-
49-
let version: string;
50-
51-
try {
52-
version = (
53-
require(
54-
resolvePackageJson(
55-
require.resolve(item.module, {
56-
paths: [file],
57-
}),
58-
),
59-
) as Package
60-
).version;
61-
} catch {
62-
version = dependencies[item.module] ?? "";
63-
}
64-
65-
items.push(
66-
createItem({
67-
...item,
68-
location: {
69-
code,
70-
file,
71-
link,
72-
module,
73-
offset: item.offset,
74-
path,
75-
},
76-
version,
77-
}),
78-
);
79-
},
80-
plugins: [jsxElementPlugin, typePlugin],
81-
});
82-
}
83-
}
84-
85-
return items;
86-
};
1+
export { esusage } from "./esusage";
2+
export { createPlugin } from "./modules/plugin";
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
export type { ParseOptions } from "./parse";
12
export { parse } from "./parse";

libraries/core/src/modules/parser/parse.ts

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import { parse as swcParse } from "@swc/core";
2-
import type { ImportDeclaration, JSXAttrValue, Module } from "@swc/core";
2+
import type { Module } from "@swc/core";
33
import { visit } from "esvisitor";
44

5-
import type { Import, Primitive } from "../../types";
6-
import type { Nodes, Plugin, PluginItemOutput } from "../plugin";
5+
import type { Import, Nodes, Primitive } from "../../types";
6+
import type { Plugin, PluginOutput } from "../plugin";
77

8-
type Options = {
9-
onAdd: (item: PluginItemOutput) => void;
10-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
11-
plugins: Plugin<any>[];
8+
export type ParseOptions = {
9+
onAdd: (item: PluginOutput) => void;
10+
/**
11+
* A list of plugins to enable.
12+
*/
13+
plugins: Plugin[];
1214
};
1315

14-
export const parse = async (code: string, { onAdd, plugins }: Options) => {
16+
export const parse = async (code: string, { onAdd, plugins }: ParseOptions) => {
1517
const context = {
1618
imports: new Map<Import["alias"], Import>(),
1719
};
@@ -31,7 +33,7 @@ export const parse = async (code: string, { onAdd, plugins }: Options) => {
3133
if (ast === null) return;
3234

3335
const visitor: {
34-
[Key in keyof SupportedNodes]?: VisitorFunction<Key>;
36+
[Key in keyof Nodes]?: VisitorFunction<Key>;
3537
} = {
3638
ImportDeclaration(node) {
3739
const module = node.source.value;
@@ -54,19 +56,26 @@ export const parse = async (code: string, { onAdd, plugins }: Options) => {
5456
for (const plugin of plugins) {
5557
const pluginOutput = plugin(context, {
5658
getJSXAttributeValue,
57-
});
59+
}) as Record<
60+
keyof Nodes,
61+
(node: Nodes[keyof Nodes]) => PluginOutput | undefined
62+
>;
5863

59-
const nodeKeys = Object.keys(pluginOutput) as (keyof SupportedNodes)[];
64+
const nodeKeys = Object.keys(
65+
pluginOutput,
66+
) as (keyof typeof pluginOutput)[];
6067

6168
for (const nodeKey of nodeKeys) {
62-
const currentVisitorFn = visitor[nodeKey] as VisitorFunction;
69+
const currentVisitorFn = visitor[nodeKey] as
70+
| VisitorFunction
71+
| undefined;
6372

6473
visitor[nodeKey] = (node) => {
6574
if (typeof currentVisitorFn === "function") {
6675
currentVisitorFn(node);
6776
}
6877

69-
const output = pluginOutput[nodeKey]?.(node);
78+
const output = pluginOutput[nodeKey](node);
7079

7180
if (output) {
7281
onAdd(output);
@@ -75,17 +84,16 @@ export const parse = async (code: string, { onAdd, plugins }: Options) => {
7584
}
7685
}
7786

78-
visit<SupportedNodes>(ast, visitor);
79-
};
80-
81-
type SupportedNodes = Nodes & {
82-
ImportDeclaration: ImportDeclaration;
87+
visit<Nodes>(ast, visitor);
8388
};
8489

85-
type VisitorFunction<Key extends keyof SupportedNodes = keyof SupportedNodes> =
86-
(node: SupportedNodes[Key]) => void;
90+
type VisitorFunction<Key extends keyof Nodes = keyof Nodes> = (
91+
node: Nodes[Key],
92+
) => void;
8793

88-
const getJSXAttributeValue = (node: JSXAttrValue | undefined): Primitive => {
94+
const getJSXAttributeValue = (
95+
node: Nodes["JSXAttrValue"] | undefined,
96+
): Primitive => {
8997
if (!node) {
9098
return true;
9199
}
@@ -105,7 +113,7 @@ const getJSXAttributeValue = (node: JSXAttrValue | undefined): Primitive => {
105113
}
106114

107115
if (node.type === "JSXExpressionContainer") {
108-
return getJSXAttributeValue(node.expression as JSXAttrValue);
116+
return getJSXAttributeValue(node.expression as Nodes["JSXAttrValue"]);
109117
}
110118

111119
return createUnknownToken(node.type);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import type { Import, Nodes, Primitive } from "../../types";
2+
import type { Item } from "../item";
3+
4+
export const createPlugin = (factory: Plugin) => {
5+
return factory;
6+
};
7+
8+
export type Plugin = (
9+
context: {
10+
// TODO: do not expose setters (read-only context for getter purposes)
11+
imports: Map<Import["alias"], Import>;
12+
},
13+
helpers: {
14+
getJSXAttributeValue: (
15+
node: Nodes["JSXAttrValue"] | undefined,
16+
) => Primitive;
17+
},
18+
) => {
19+
[Key in keyof Nodes]?: (node: Nodes[Key]) => PluginOutput | undefined;
20+
};
21+
22+
export type PluginOutput = Partial<Pick<Item, "data" | "metadata">> &
23+
Pick<Item, "module" | "name" | "type"> & {
24+
offset: number;
25+
};
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export type { Nodes, Plugin, PluginItemOutput } from "./plugins";
2-
export { jsxElementPlugin, typePlugin } from "./plugins";
1+
export type { Plugin, PluginOutput } from "./createPlugin";
2+
export { createPlugin } from "./createPlugin";

0 commit comments

Comments
 (0)