Skip to content

Commit 228bf38

Browse files
committed
feat: streamline Python file processing by removing unused helper functions and optimizing import extraction
1 parent da2b067 commit 228bf38

File tree

5 files changed

+97
-308
lines changed

5 files changed

+97
-308
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "code-collector",
33
"displayName": "Code Collector",
44
"description": "Gather code context for AI based on imports",
5-
"version": "0.0.61",
5+
"version": "0.0.62",
66
"publisher": "0-don",
77
"icon": "assets/icon.png",
88
"repository": {

src/commands.ts

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -252,14 +252,13 @@ export class CommandHandler {
252252

253253
const allContexts: FileContext[] = [];
254254
const processed = new Set<string>();
255-
const pythonFiles = new Set<string>();
256255
const workspaceRoot = getWorkspaceRoot();
257256

258257
this.output.log(
259258
`Processing ${filesToProcess.length} initial files for imports...`,
260259
);
261260

262-
for (const filePath of filesToProcess.filter((f) => !f.endsWith(".py"))) {
261+
for (const filePath of filesToProcess) {
263262
if (token?.isCancellationRequested) {
264263
return;
265264
}
@@ -268,23 +267,9 @@ export class CommandHandler {
268267
allContexts,
269268
processed,
270269
workspaceRoot,
271-
pythonFiles,
272270
);
273271
}
274272

275-
filesToProcess
276-
.filter((f) => f.endsWith(".py"))
277-
.forEach((f) => pythonFiles.add(f));
278-
if (token?.isCancellationRequested) {
279-
return;
280-
}
281-
282-
await this.contextCollector.processPythonFiles(
283-
pythonFiles,
284-
allContexts,
285-
processed,
286-
workspaceRoot,
287-
);
288273
if (token?.isCancellationRequested) {
289274
return;
290275
}

src/lib/core.ts

Lines changed: 0 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
// src/lib/core.ts
21
import * as fs from "fs";
32
import * as micromatch from "micromatch";
43
import * as path from "path";
54
import { parserRegistry } from "../parsers";
65
import { resolverRegistry } from "../resolvers";
7-
import { PythonResolver } from "../resolvers/python-resolver";
86
import { FileContext } from "../types";
97
import { findProjectRoot, isTextFile } from "../utils";
108
import { getIgnorePatterns } from "./config";
@@ -22,15 +20,13 @@ export class ContextCollector {
2220

2321
this.output.log(`Using ${ignorePatterns.length} ignore patterns`);
2422

25-
// Recursively discover files while respecting ignore patterns
2623
const filteredFiles = await this.discoverFiles(
2724
workspaceRoot,
2825
workspaceRoot,
2926
ignorePatterns,
3027
);
3128
this.output.log(`Discovered ${filteredFiles.length} files after filtering`);
3229

33-
// Process files and create contexts
3430
const contexts: FileContext[] = [];
3531
for (let i = 0; i < filteredFiles.length; i++) {
3632
if (progressCallback && !progressCallback(i + 1, filteredFiles.length)) {
@@ -68,7 +64,6 @@ export class ContextCollector {
6864
const relativePath = path.relative(workspaceRoot, fullPath);
6965

7066
if (entry.isDirectory()) {
71-
// For directories, check ignore patterns against both full path and directory name
7267
const directoryName = entry.name;
7368
const isIgnored =
7469
micromatch.isMatch(relativePath, ignorePatterns, { dot: true }) ||
@@ -78,7 +73,6 @@ export class ContextCollector {
7873
micromatch.isMatch(directoryName, ignorePatterns, { dot: true });
7974

8075
if (!isIgnored) {
81-
// Only recurse into directories that aren't ignored
8276
const subFiles = await this.discoverFiles(
8377
fullPath,
8478
workspaceRoot,
@@ -87,7 +81,6 @@ export class ContextCollector {
8781
files.push(...subFiles);
8882
}
8983
} else if (entry.isFile()) {
90-
// For files, check ignore patterns against both full relative path and just filename
9184
const filename = path.basename(fullPath);
9285
const isIgnored =
9386
micromatch.isMatch(relativePath, ignorePatterns, { dot: true }) ||
@@ -120,20 +113,13 @@ export class ContextCollector {
120113
contexts: FileContext[],
121114
processed: Set<string>,
122115
workspaceRoot: string,
123-
pythonFiles: Set<string>,
124116
): Promise<void> {
125117
const normalizedPath = path.resolve(filePath);
126118

127119
if (processed.has(normalizedPath) || !fs.existsSync(normalizedPath)) {
128120
return;
129121
}
130122

131-
// Collect Python files for batch processing
132-
if (filePath.endsWith(".py")) {
133-
pythonFiles.add(normalizedPath);
134-
return;
135-
}
136-
137123
processed.add(normalizedPath);
138124

139125
try {
@@ -150,7 +136,6 @@ export class ContextCollector {
150136
this.output.log(`${relativePath}: ${imports.length} imports`);
151137
}
152138

153-
// Use project-specific root for import resolution instead of workspace root
154139
const projectRoot = this.getProjectRootForFile(normalizedPath);
155140

156141
for (const importInfo of imports) {
@@ -166,7 +151,6 @@ export class ContextCollector {
166151
contexts,
167152
processed,
168153
workspaceRoot,
169-
pythonFiles,
170154
);
171155
}
172156
}
@@ -175,74 +159,4 @@ export class ContextCollector {
175159
this.output.error(`Failed to process: ${normalizedPath}`, error);
176160
}
177161
}
178-
179-
async processPythonFiles(
180-
pythonFiles: Set<string>,
181-
contexts: FileContext[],
182-
processed: Set<string>,
183-
workspaceRoot: string,
184-
): Promise<void> {
185-
if (pythonFiles.size === 0) {
186-
return;
187-
}
188-
189-
this.output.log(
190-
`Processing ${pythonFiles.size} Python files with helper...`,
191-
);
192-
193-
const resolver = resolverRegistry.getResolver("dummy.py") as PythonResolver;
194-
const ignorePatterns = getIgnorePatterns();
195-
196-
try {
197-
const allPythonFiles = await resolver.resolveAllImports(
198-
Array.from(pythonFiles),
199-
ignorePatterns,
200-
);
201-
this.output.log(
202-
`Python helper found ${allPythonFiles.length} total Python files`,
203-
);
204-
205-
for (const pythonFile of allPythonFiles) {
206-
const normalizedPath = path.resolve(pythonFile);
207-
208-
if (!processed.has(normalizedPath) && fs.existsSync(normalizedPath)) {
209-
processed.add(normalizedPath);
210-
211-
try {
212-
const content = fs.readFileSync(normalizedPath, "utf8");
213-
const relativePath = path.relative(workspaceRoot, normalizedPath);
214-
contexts.push({ path: normalizedPath, content, relativePath });
215-
} catch (error) {
216-
this.output.error(
217-
`Failed to read Python file: ${normalizedPath}`,
218-
error,
219-
);
220-
}
221-
}
222-
}
223-
} catch (error) {
224-
this.output.error(
225-
`Python helper failed, processing files individually`,
226-
error,
227-
);
228-
229-
// Fallback: add Python files without import resolution
230-
for (const pythonFile of pythonFiles) {
231-
if (!processed.has(pythonFile)) {
232-
processed.add(pythonFile);
233-
234-
try {
235-
const content = fs.readFileSync(pythonFile, "utf8");
236-
const relativePath = path.relative(workspaceRoot, pythonFile);
237-
contexts.push({ path: pythonFile, content, relativePath });
238-
} catch (error) {
239-
this.output.error(
240-
`Failed to read Python file: ${pythonFile}`,
241-
error,
242-
);
243-
}
244-
}
245-
}
246-
}
247-
}
248162
}

src/parsers/python-parser.ts

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,103 @@
1+
import { execFile } from "child_process";
2+
import * as fs from "fs";
3+
import * as os from "os";
4+
import * as path from "path";
5+
import * as util from "util";
16
import { ImportInfo, ParserConfig } from "../types";
27
import { BaseParser } from "./base-parser";
38

9+
const execFileP = util.promisify(execFile);
10+
11+
const HELPER_PYTHON_CONTENT = `import ast
12+
import json
13+
import sys
14+
import os
15+
16+
def extract_imports_from_file(file_path):
17+
"""Extract all imports from a Python file"""
18+
if not os.path.isfile(file_path) or not file_path.endswith('.py'):
19+
return []
20+
21+
imports = []
22+
23+
try:
24+
with open(file_path, 'r', encoding='utf-8') as f:
25+
tree = ast.parse(f.read())
26+
27+
for node in ast.walk(tree):
28+
if isinstance(node, ast.Import):
29+
for alias in node.names:
30+
imports.append({
31+
'module': alias.name,
32+
'type': 'import',
33+
'line': node.lineno
34+
})
35+
36+
elif isinstance(node, ast.ImportFrom):
37+
if node.module:
38+
imports.append({
39+
'module': node.module,
40+
'type': 'from',
41+
'line': node.lineno,
42+
'level': node.level
43+
})
44+
45+
except (SyntaxError, UnicodeDecodeError):
46+
pass
47+
48+
return imports
49+
50+
if __name__ == "__main__":
51+
if len(sys.argv) < 2:
52+
print(json.dumps([]))
53+
sys.exit(0)
54+
55+
try:
56+
file_path = sys.argv[1]
57+
imports = extract_imports_from_file(file_path)
58+
print(json.dumps(imports))
59+
except Exception:
60+
print(json.dumps([]))
61+
`;
62+
463
export class PythonParser extends BaseParser {
564
config: ParserConfig = {
665
extensions: [".py"],
766
name: "Python",
867
};
968

69+
private helperPath: string | null = null;
70+
1071
async parseImports(content: string, filePath: string): Promise<ImportInfo[]> {
72+
const helperPath = await this.ensureHelperExists();
73+
if (!helperPath) {
74+
return this.parseImportsFallback(content);
75+
}
76+
77+
try {
78+
const { stdout } = await execFileP("python", [helperPath, filePath]);
79+
const result = JSON.parse(stdout) || [];
80+
return result.map((imp: any) => ({
81+
module: imp.module,
82+
type: imp.type,
83+
line: imp.line,
84+
}));
85+
} catch (error) {
86+
return this.parseImportsFallback(content);
87+
}
88+
}
89+
90+
private parseImportsFallback(content: string): ImportInfo[] {
1191
const imports: ImportInfo[] = [];
1292
const lines = content.split("\n");
1393

1494
for (let i = 0; i < lines.length; i++) {
1595
const line = lines[i].trim();
1696

17-
// Skip empty lines and comments
1897
if (!line || line.startsWith("#")) {
1998
continue;
2099
}
21100

22-
// Standard import: import module
23101
const importMatch = line.match(/^import\s+([^\s#]+)/);
24102
if (importMatch) {
25103
imports.push({
@@ -30,7 +108,6 @@ export class PythonParser extends BaseParser {
30108
continue;
31109
}
32110

33-
// From import: from module import ...
34111
const fromMatch = line.match(/^from\s+([^\s#]+)\s+import/);
35112
if (fromMatch) {
36113
imports.push({
@@ -41,7 +118,6 @@ export class PythonParser extends BaseParser {
41118
continue;
42119
}
43120

44-
// Stop at first non-import statement (basic version)
45121
if (
46122
line &&
47123
!line.startsWith("from ") &&
@@ -57,4 +133,19 @@ export class PythonParser extends BaseParser {
57133

58134
return imports;
59135
}
136+
137+
private async ensureHelperExists(): Promise<string | null> {
138+
if (this.helperPath && fs.existsSync(this.helperPath)) {
139+
return this.helperPath;
140+
}
141+
142+
try {
143+
const tempDir = os.tmpdir();
144+
this.helperPath = path.join(tempDir, "code-collector-python-helper.py");
145+
fs.writeFileSync(this.helperPath, HELPER_PYTHON_CONTENT, "utf8");
146+
return this.helperPath;
147+
} catch (error) {
148+
return null;
149+
}
150+
}
60151
}

0 commit comments

Comments
 (0)