Skip to content

Commit 2643519

Browse files
committed
refactore python new plugins to support reexports
1 parent 355473e commit 2643519

File tree

8 files changed

+2150
-0
lines changed

8 files changed

+2150
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import { describe, expect, test } from "vitest";
2+
import Parser from "tree-sitter";
3+
import {
4+
PythonExportExtractor,
5+
PYTHON_CLASS_TYPE,
6+
PYTHON_FUNCTION_TYPE,
7+
PYTHON_VARIABLE_TYPE,
8+
} from "./index";
9+
import { pythonParser } from "../../../helpers/treeSitter/parsers";
10+
11+
describe("PythonExportExtractor", () => {
12+
const parser = pythonParser;
13+
14+
test("should extract simple class definitions", () => {
15+
const code = `
16+
class MyClass:
17+
pass
18+
`;
19+
const tree = parser.parse(code);
20+
const files = new Map<
21+
string,
22+
{ path: string; rootNode: Parser.SyntaxNode }
23+
>();
24+
files.set("class_test.py", {
25+
path: "class_test.py",
26+
rootNode: tree.rootNode,
27+
});
28+
29+
const resolver = new PythonExportExtractor(parser, files);
30+
const result = resolver.getSymbols("class_test.py");
31+
const classSymbols = result.symbols.filter(
32+
(s) => s.type === PYTHON_CLASS_TYPE,
33+
);
34+
35+
expect(classSymbols.length).toBeGreaterThanOrEqual(1);
36+
const myClass = classSymbols.find((s) => s.id === "MyClass");
37+
expect(myClass).toBeDefined();
38+
// Optionally, check that the syntax node's text matches expectations.
39+
expect(myClass?.identifierNode.text).toBe("MyClass");
40+
});
41+
42+
test("should extract decorated class definitions", () => {
43+
const code = `
44+
@decorator
45+
class DecoratedClass:
46+
pass
47+
`;
48+
const tree = parser.parse(code);
49+
const files = new Map<
50+
string,
51+
{ path: string; rootNode: Parser.SyntaxNode }
52+
>();
53+
files.set("decorated_class_test.py", {
54+
path: "decorated_class_test.py",
55+
rootNode: tree.rootNode,
56+
});
57+
58+
const resolver = new PythonExportExtractor(parser, files);
59+
const result = resolver.getSymbols("decorated_class_test.py");
60+
const classSymbols = result.symbols.filter(
61+
(s) => s.type === PYTHON_CLASS_TYPE,
62+
);
63+
64+
expect(classSymbols.length).toBeGreaterThanOrEqual(1);
65+
const decClass = classSymbols.find((s) => s.id === "DecoratedClass");
66+
expect(decClass).toBeDefined();
67+
});
68+
69+
test("should extract simple function definitions", () => {
70+
const code = `
71+
def my_function():
72+
pass
73+
`;
74+
const tree = parser.parse(code);
75+
const files = new Map<
76+
string,
77+
{ path: string; rootNode: Parser.SyntaxNode }
78+
>();
79+
files.set("function_test.py", {
80+
path: "function_test.py",
81+
rootNode: tree.rootNode,
82+
});
83+
84+
const resolver = new PythonExportExtractor(parser, files);
85+
const result = resolver.getSymbols("function_test.py");
86+
const functionSymbols = result.symbols.filter(
87+
(s) => s.type === PYTHON_FUNCTION_TYPE,
88+
);
89+
90+
expect(functionSymbols.length).toBeGreaterThanOrEqual(1);
91+
const func = functionSymbols.find((s) => s.id === "my_function");
92+
expect(func).toBeDefined();
93+
});
94+
95+
test("should extract decorated function definitions", () => {
96+
const code = `
97+
@decorator
98+
def decorated_function():
99+
pass
100+
`;
101+
const tree = parser.parse(code);
102+
const files = new Map<
103+
string,
104+
{ path: string; rootNode: Parser.SyntaxNode }
105+
>();
106+
files.set("decorated_function_test.py", {
107+
path: "decorated_function_test.py",
108+
rootNode: tree.rootNode,
109+
});
110+
111+
const resolver = new PythonExportExtractor(parser, files);
112+
const result = resolver.getSymbols("decorated_function_test.py");
113+
const functionSymbols = result.symbols.filter(
114+
(s) => s.type === PYTHON_FUNCTION_TYPE,
115+
);
116+
117+
expect(functionSymbols.length).toBeGreaterThanOrEqual(1);
118+
const decFunc = functionSymbols.find((s) => s.id === "decorated_function");
119+
expect(decFunc).toBeDefined();
120+
});
121+
122+
test("should extract variable assignments and not include __all__", () => {
123+
const code = `
124+
x = 10
125+
y, z = 20, 30
126+
__all__ = ["x", "y", "z"]
127+
`;
128+
const tree = parser.parse(code);
129+
const files = new Map<
130+
string,
131+
{ path: string; rootNode: Parser.SyntaxNode }
132+
>();
133+
files.set("variable_test.py", {
134+
path: "variable_test.py",
135+
rootNode: tree.rootNode,
136+
});
137+
138+
const resolver = new PythonExportExtractor(parser, files);
139+
const result = resolver.getSymbols("variable_test.py");
140+
const variableSymbols = result.symbols.filter(
141+
(s) => s.type === PYTHON_VARIABLE_TYPE,
142+
);
143+
144+
// Expect that x, y, z are extracted as variables.
145+
const varX = variableSymbols.find((s) => s.id === "x");
146+
const varY = variableSymbols.find((s) => s.id === "y");
147+
const varZ = variableSymbols.find((s) => s.id === "z");
148+
expect(varX).toBeDefined();
149+
expect(varY).toBeDefined();
150+
expect(varZ).toBeDefined();
151+
152+
// __all__ should not be among exported symbols.
153+
const exportedIds = result.symbols.map((s) => s.id);
154+
expect(exportedIds).not.toContain("__all__");
155+
});
156+
157+
test("should extract __all__ elements correctly", () => {
158+
const code = `
159+
__all__ = ["a", "b", "c"]
160+
a = 1
161+
b = 2
162+
c = 3
163+
d = 4
164+
`;
165+
const tree = parser.parse(code);
166+
const files = new Map<
167+
string,
168+
{ path: string; rootNode: Parser.SyntaxNode }
169+
>();
170+
files.set("all_test.py", { path: "all_test.py", rootNode: tree.rootNode });
171+
172+
const resolver = new PythonExportExtractor(parser, files);
173+
const result = resolver.getSymbols("all_test.py");
174+
175+
// publicSymbols should reflect the __all__ definition.
176+
expect(result.publicSymbols).toContain("a");
177+
expect(result.publicSymbols).toContain("b");
178+
expect(result.publicSymbols).toContain("c");
179+
// 'd' is not public because it's not in __all__
180+
expect(result.publicSymbols).not.toContain("d");
181+
});
182+
});

0 commit comments

Comments
 (0)