Skip to content

Commit d7b4bf8

Browse files
committed
refactor: remove the need of having adapters for parser (yagni)
1 parent a37f9d6 commit d7b4bf8

File tree

8 files changed

+175
-218
lines changed

8 files changed

+175
-218
lines changed
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import type {
2+
ImportDeclaration,
3+
JSXAttrValue,
4+
JSXOpeningElement,
5+
Module,
6+
TsType,
7+
} from "@swc/core";
8+
import { parse as swcParse } from "@swc/core";
9+
import { walk } from "astray";
10+
11+
import type { Import } from "../entities/import";
12+
import type { Item } from "../entities/item";
13+
import type { Primitive } from "../types";
14+
15+
export const parse = async (
16+
code: string,
17+
addCallback: (
18+
item: Pick<Item, "args" | "module" | "name" | "type"> & {
19+
offset: number;
20+
},
21+
) => void,
22+
) => {
23+
const imports = new Map<Import["alias"], Import>();
24+
25+
const ast = await swcParse(code, {
26+
syntax: "typescript",
27+
tsx: true,
28+
});
29+
30+
/**
31+
* Traverse method using the visitor design pattern.
32+
* SWC traverser (`import { Visitor } from "@swc/core/Visitor"`) is not used since it doesn't traverse recursively nodes
33+
* (at least, recursive `JSXOpeningElement`s are not retrieved)
34+
*/
35+
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
36+
walk<Module, void, AST>(ast, {
37+
ImportDeclaration(node) {
38+
const module = node.source.value;
39+
40+
node.specifiers.forEach((specifier) => {
41+
const specifierValue = specifier.local.value;
42+
43+
imports.set(specifierValue, {
44+
name:
45+
// @ts-expect-error `imported` field is exposed by `ImportSpecifier` node (@todo: fix the typing issue in @swc/core)
46+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
47+
(specifier.imported?.value || specifierValue) as string,
48+
alias: specifierValue,
49+
module,
50+
});
51+
});
52+
},
53+
JSXOpeningElement(node) {
54+
if (node.name.type !== "Identifier") return;
55+
56+
const name = node.name.value;
57+
const importMetadata = imports.get(name);
58+
59+
if (!importMetadata) return;
60+
61+
addCallback({
62+
name: importMetadata.name,
63+
args: {
64+
data: node.attributes.reduce<Record<string, unknown>>(
65+
(props, prop) => {
66+
if (
67+
prop.type !== "JSXAttribute" ||
68+
prop.name.type !== "Identifier"
69+
)
70+
return props;
71+
72+
props[prop.name.value] = getLiteralValue(
73+
prop.value,
74+
);
75+
76+
return props;
77+
},
78+
{},
79+
),
80+
isSpread: false,
81+
},
82+
module: importMetadata.module,
83+
offset: node.span.start,
84+
type: "component",
85+
});
86+
},
87+
TsType(node) {
88+
let typeValue = "";
89+
90+
if (
91+
node.type === "TsTypeReference" &&
92+
node.typeName.type === "Identifier"
93+
) {
94+
typeValue = node.typeName.value;
95+
}
96+
97+
if (
98+
node.type === "TsIndexedAccessType" &&
99+
node.objectType.type === "TsTypeReference" &&
100+
node.objectType.typeName.type === "Identifier"
101+
) {
102+
typeValue = node.objectType.typeName.value;
103+
}
104+
105+
const importMetadata = imports.get(typeValue);
106+
107+
if (!importMetadata) return;
108+
109+
addCallback({
110+
name: importMetadata.name,
111+
module: importMetadata.module,
112+
offset: node.span.start,
113+
type: "type",
114+
});
115+
},
116+
});
117+
};
118+
119+
type AST = {
120+
ImportDeclaration: ImportDeclaration;
121+
JSXOpeningElement: JSXOpeningElement;
122+
TsType: TsType;
123+
};
124+
125+
/**
126+
* Helper to unify the way unknown AST token are persisted
127+
* @param token AST token value
128+
* @returns Formatted AST token
129+
*/
130+
const getUnknownValue = (token: string) => `#${token}`;
131+
132+
const getLiteralValue = (node: JSXAttrValue | undefined): Primitive => {
133+
if (!node) {
134+
return true;
135+
}
136+
137+
if (node.type === "NullLiteral") {
138+
return null;
139+
}
140+
141+
if (
142+
node.type === "StringLiteral" ||
143+
node.type === "NumericLiteral" ||
144+
node.type === "BigIntLiteral" ||
145+
node.type === "BooleanLiteral" ||
146+
node.type === "JSXText"
147+
) {
148+
return node.value;
149+
}
150+
151+
if (node.type === "JSXExpressionContainer") {
152+
return getLiteralValue(node.expression as JSXAttrValue);
153+
}
154+
155+
return getUnknownValue(node.type);
156+
};
File renamed without changes.

packages/core/src/index.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,33 @@
11
import { readFileSync } from "fs";
22

3-
import { scan } from "./scanner";
3+
import { createItem } from "./entities/item";
44
import type { Item } from "./entities/item";
5-
import { parse } from "./parser";
5+
import { createLocation } from "./entities/location";
6+
import { parse } from "./features/parse";
7+
import { scan } from "./features/scan";
68

79
export const esonar = async () => {
810
const projects = scan();
911
const items: Item[] = [];
1012

1113
for (const project of projects) {
1214
for (const file of project.files) {
13-
const content = readFileSync(file, "utf-8");
15+
const code = readFileSync(file, "utf-8");
1416
const module = project.metadata.name;
1517

16-
const projectItems = await parse(content, {
17-
file,
18-
module,
18+
await parse(code, (item) => {
19+
items.push(
20+
createItem({
21+
...item,
22+
location: createLocation({
23+
code,
24+
file,
25+
module,
26+
offset: item.offset,
27+
}),
28+
}),
29+
);
1930
});
20-
21-
items.push(...projectItems);
2231
}
2332
}
2433

packages/core/src/parser/adapters/swc.ts

Lines changed: 0 additions & 143 deletions
This file was deleted.

packages/core/src/parser/helpers.ts

Lines changed: 0 additions & 6 deletions
This file was deleted.

packages/core/src/parser/index.ts

Lines changed: 0 additions & 41 deletions
This file was deleted.

0 commit comments

Comments
 (0)