Skip to content

Commit 1cb965f

Browse files
committed
split language plugins into multiple ones, for now exportExtractor and importExtractor
1 parent bc5ff38 commit 1cb965f

File tree

10 files changed

+3159
-1
lines changed

10 files changed

+3159
-1
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import Parser from "tree-sitter";
2+
import Python from "tree-sitter-python";
3+
import Javascript from "tree-sitter-javascript";
4+
import Typescript from "tree-sitter-typescript";
5+
6+
const pythonParser = new Parser();
7+
pythonParser.setLanguage(Python);
8+
9+
const javascriptParser = new Parser();
10+
javascriptParser.setLanguage(Javascript);
11+
12+
const typescriptParser = new Parser();
13+
typescriptParser.setLanguage(Typescript.typescript);
14+
15+
export { pythonParser, javascriptParser, typescriptParser };
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { describe, it, expect } from "vitest";
2+
import {
3+
generateExportMap,
4+
getExportExtractor,
5+
UnsupportedExtensionForExportExtractorError,
6+
} from "./index";
7+
import Parser from "tree-sitter";
8+
import Python from "tree-sitter-python";
9+
10+
// Set up the Tree-sitter parser for Python
11+
const parser = new Parser();
12+
parser.setLanguage(Python);
13+
14+
describe("generateExportMap", () => {
15+
it("should generate an export map for a valid Python file", () => {
16+
const files = [{ path: "test.py", code: "def my_function(): pass" }];
17+
const exportMap = generateExportMap(files);
18+
19+
expect(exportMap).toHaveProperty("test.py");
20+
expect(exportMap["test.py"].couldNotProcess).toBe(false);
21+
expect(exportMap["test.py"].exportStatements.length).toBe(1);
22+
expect(
23+
exportMap["test.py"].exportStatements[0].members[0].identifierNode.text,
24+
).toBe("my_function");
25+
});
26+
27+
it("should return an empty export map for a Python file with no exports", () => {
28+
const files = [{ path: "empty.py", code: "" }];
29+
const exportMap = generateExportMap(files);
30+
31+
expect(exportMap).toHaveProperty("empty.py");
32+
expect(exportMap["empty.py"].language).toBe(Python.name);
33+
expect(exportMap["empty.py"].couldNotProcess).toBe(false);
34+
expect(exportMap["empty.py"].exportStatements).toEqual([]);
35+
});
36+
37+
it("should handle multiple files", () => {
38+
const files = [
39+
{ path: "file1.py", code: "def my_function(): pass" },
40+
{ path: "file2.py", code: "class MyClass: pass" },
41+
];
42+
const exportMap = generateExportMap(files);
43+
44+
expect(Object.keys(exportMap)).toEqual(["file1.py", "file2.py"]);
45+
expect(exportMap["file1.py"].language).toBe(Python.name);
46+
expect(exportMap["file1.py"].couldNotProcess).toBe(false);
47+
expect(exportMap["file1.py"].exportStatements.length).toBe(1);
48+
expect(
49+
exportMap["file1.py"].exportStatements[0].members[0].identifierNode.text,
50+
).toBe("my_function");
51+
expect(exportMap["file2.py"].language).toBe(Python.name);
52+
expect(exportMap["file2.py"].couldNotProcess).toBe(false);
53+
expect(exportMap["file2.py"].exportStatements.length).toBe(1);
54+
expect(
55+
exportMap["file2.py"].exportStatements[0].members[0].identifierNode.text,
56+
).toBe("MyClass");
57+
});
58+
59+
it("should return an error for unsupported file extensions", () => {
60+
const files = [{ path: "script.js", code: "function foo() {}" }];
61+
const exportMap = generateExportMap(files);
62+
63+
expect(exportMap).toHaveProperty("script.js");
64+
expect(exportMap["script.js"].language).toBe("unknown");
65+
expect(exportMap["script.js"].couldNotProcess).toBe(true);
66+
expect(exportMap["script.js"].exportStatements).toEqual([]);
67+
});
68+
69+
it("should handle syntax errors gracefully", () => {
70+
// tree sitter struggle to parse long lines
71+
const code = ".".repeat(100000);
72+
const files = [
73+
{
74+
path: "broken.py",
75+
code,
76+
},
77+
];
78+
const exportMap = generateExportMap(files);
79+
80+
expect(exportMap).toHaveProperty("broken.py");
81+
expect(exportMap["broken.py"].language).toBe(Python.name);
82+
expect(exportMap["broken.py"].couldNotProcess).toBe(true);
83+
expect(exportMap["broken.py"].exportStatements).toEqual([]);
84+
});
85+
86+
it("should throw an error for completely unknown file types", () => {
87+
expect(() => getExportExtractor("unknown.xyz")).toThrow(
88+
UnsupportedExtensionForExportExtractorError,
89+
);
90+
});
91+
92+
it("should extract functions, classes, and assignments together", () => {
93+
const files = [
94+
{
95+
path: "combined.py",
96+
code: `
97+
def my_function(): pass
98+
class MyClass: pass
99+
MY_CONSTANT = 42
100+
`.trim(),
101+
},
102+
];
103+
104+
const exportMap = generateExportMap(files);
105+
106+
expect(exportMap).toHaveProperty("combined.py");
107+
expect(exportMap["combined.py"].language).toBe(Python.name);
108+
expect(exportMap["combined.py"].couldNotProcess).toBe(false);
109+
expect(exportMap["combined.py"].exportStatements.length).toBe(3);
110+
111+
const [funcExport, classExport, constExport] =
112+
exportMap["combined.py"].exportStatements;
113+
114+
expect(funcExport.members[0].identifierNode.text).toBe("my_function");
115+
expect(classExport.members[0].identifierNode.text).toBe("MyClass");
116+
expect(constExport.members[0].identifierNode.text).toBe("MY_CONSTANT");
117+
});
118+
});
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import PythonExportExtractor from "./python";
2+
import Parser from "tree-sitter";
3+
import {
4+
ExportExtractor,
5+
ExportMap,
6+
UnsupportedExtensionForExportExtractorError,
7+
} from "./types";
8+
import { pythonParser } from "../../helpers/treeSitter/parsers";
9+
10+
export function getExportExtractor(filePath: string) {
11+
const extension = filePath.split(".").pop()?.toLowerCase();
12+
13+
switch (extension) {
14+
case "py":
15+
return new PythonExportExtractor(pythonParser);
16+
default:
17+
throw new UnsupportedExtensionForExportExtractorError(
18+
extension || "No extension",
19+
);
20+
}
21+
}
22+
23+
export function generateExportMap(files: { path: string; code: string }[]) {
24+
const exportMap: ExportMap = {};
25+
26+
files.forEach(({ path, code }) => {
27+
let exportExtractor: ExportExtractor;
28+
29+
try {
30+
exportExtractor = getExportExtractor(path);
31+
} catch (e) {
32+
if (e instanceof UnsupportedExtensionForExportExtractorError) {
33+
console.log(e.message);
34+
exportMap[path] = {
35+
filePath: path,
36+
language: "unknown",
37+
couldNotProcess: true,
38+
exportStatements: [],
39+
};
40+
return;
41+
} else {
42+
throw e;
43+
}
44+
}
45+
46+
let tree: Parser.Tree;
47+
48+
try {
49+
tree = exportExtractor.parser.parse(code);
50+
} catch (e) {
51+
console.error(`Error parsing file: ${path}`);
52+
console.error(e);
53+
exportMap[path] = {
54+
filePath: path,
55+
language: exportExtractor.parser.getLanguage().name,
56+
couldNotProcess: true,
57+
exportStatements: [],
58+
};
59+
return;
60+
}
61+
62+
const exportStatements = exportExtractor.run(path, tree.rootNode);
63+
64+
exportMap[path] = {
65+
filePath: path,
66+
language: exportExtractor.parser.getLanguage().name,
67+
couldNotProcess: false,
68+
exportStatements,
69+
};
70+
});
71+
72+
return exportMap;
73+
}

0 commit comments

Comments
 (0)