@aaswe/codebase-ai
Version:
AI-Assisted Software Engineering (AASWE) - Rich codebase context for IDE LLMs
628 lines (620 loc) • 24.1 kB
JavaScript
"use strict";
/**
* Python AST Analyzer
* Implements AST analysis for Python files using Python's ast module via child process
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.PythonAnalyzer = void 0;
const child_process_1 = require("child_process");
const fs_1 = require("fs");
const path_1 = require("path");
const BaseAnalyzer_1 = require("./BaseAnalyzer");
class PythonAnalyzer extends BaseAnalyzer_1.BaseAnalyzer {
constructor() {
super('python');
}
async parseAST(content, context) {
try {
// Create Python script to parse the AST using Python's ast module
const tempScript = this.createPythonParserScript();
const tempFile = (0, path_1.join)(__dirname, 'temp_parser.py');
const inputFile = (0, path_1.join)(__dirname, 'temp_input.py');
(0, fs_1.writeFileSync)(tempFile, tempScript);
(0, fs_1.writeFileSync)(inputFile, content);
try {
// Execute Python AST parser
const result = (0, child_process_1.execSync)(`python3 "${tempFile}" "${inputFile}"`, {
encoding: 'utf-8',
timeout: context.options.timeout
});
return JSON.parse(result);
}
finally {
// Clean up generated files
try {
(0, fs_1.unlinkSync)(tempFile);
(0, fs_1.unlinkSync)(inputFile);
}
catch (error) {
// Ignore cleanup errors
}
}
}
catch (error) {
throw new Error(`Failed to parse Python AST: ${error}`);
}
}
async extractNodes(ast, context) {
const nodes = [];
const extractNodesRecursive = (node, parent) => {
if (!node || typeof node !== 'object')
return;
const astNode = {
id: this.generateId(),
type: node._type || 'Unknown',
name: this.getNodeName(node),
startLine: node.lineno || 0,
endLine: node.end_lineno || node.lineno || 0,
startColumn: node.col_offset || 0,
endColumn: node.end_col_offset || node.col_offset || 0,
filePath: context.filePath,
parent,
children: [],
properties: this.extractNodeProperties(node)
};
nodes.push(astNode);
// Process child nodes
if (node.body && Array.isArray(node.body)) {
for (const child of node.body) {
extractNodesRecursive(child, astNode.id);
}
}
// Process other child properties
for (const [key, value] of Object.entries(node)) {
if (key !== 'body' && key !== '_type' && Array.isArray(value)) {
for (const child of value) {
if (child && typeof child === 'object' && child._type) {
extractNodesRecursive(child, astNode.id);
}
}
}
else if (key !== 'body' && key !== '_type' && value && typeof value === 'object' && value._type) {
extractNodesRecursive(value, astNode.id);
}
}
};
extractNodesRecursive(ast);
return nodes;
}
async extractFunctions(ast, context) {
const functions = [];
const extractFunctionsRecursive = (node) => {
if (!node || typeof node !== 'object')
return;
if (node._type === 'FunctionDef' || node._type === 'AsyncFunctionDef') {
const func = {
id: this.generateId(),
name: node.name || 'anonymous',
parameters: this.extractPythonParameters(node.args || {}),
returnType: this.extractPythonReturnType(node),
complexity: this.calculatePythonFunctionComplexity(node),
startLine: node.lineno || 0,
endLine: node.end_lineno || node.lineno || 0,
filePath: context.filePath,
isAsync: node._type === 'AsyncFunctionDef',
isExported: this.isPythonExported(node),
visibility: this.getPythonVisibility(node.name),
dependencies: this.extractPythonFunctionDependencies(node),
calls: this.extractPythonFunctionCalls(node)
};
functions.push(func);
}
// Recursively process child nodes
this.traversePythonNode(node, extractFunctionsRecursive);
};
extractFunctionsRecursive(ast);
return functions;
}
async extractClasses(ast, context) {
const classes = [];
const extractClassesRecursive = (node) => {
if (!node || typeof node !== 'object')
return;
if (node._type === 'ClassDef') {
const cls = {
id: this.generateId(),
name: node.name || 'anonymous',
methods: [],
properties: this.extractPythonClassProperties(node),
extends: this.extractPythonBaseClasses(node),
implements: [], // Python doesn't have explicit interfaces
startLine: node.lineno || 0,
endLine: node.end_lineno || node.lineno || 0,
filePath: context.filePath,
isExported: this.isPythonExported(node),
visibility: 'public', // Python doesn't have explicit visibility
isAbstract: this.isPythonAbstractClass(node)
};
classes.push(cls);
}
// Recursively process child nodes
this.traversePythonNode(node, extractClassesRecursive);
};
extractClassesRecursive(ast);
return classes;
}
async extractImports(ast, context) {
const imports = [];
const extractImportsRecursive = (node) => {
if (!node || typeof node !== 'object')
return;
if (node._type === 'Import') {
for (const alias of node.names || []) {
const importDecl = {
id: this.generateId(),
source: alias.name || '',
imports: [{
name: alias.name || '',
alias: alias.asname || undefined,
isDefault: false,
isNamespace: true
}],
filePath: context.filePath,
startLine: node.lineno || 0,
endLine: node.end_lineno || node.lineno || 0
};
imports.push(importDecl);
}
}
else if (node._type === 'ImportFrom') {
const importSpecs = [];
for (const alias of node.names || []) {
importSpecs.push({
name: alias.name || '',
alias: alias.asname || undefined,
isDefault: false,
isNamespace: alias.name === '*'
});
}
const importDecl = {
id: this.generateId(),
source: node.module || '',
imports: importSpecs,
filePath: context.filePath,
startLine: node.lineno || 0,
endLine: node.end_lineno || node.lineno || 0
};
imports.push(importDecl);
}
// Recursively process child nodes
this.traversePythonNode(node, extractImportsRecursive);
};
extractImportsRecursive(ast);
return imports;
}
async extractExports(ast, context) {
const exports = [];
// Python doesn't have explicit exports, but we can identify public functions/classes
const extractExportsRecursive = (node) => {
if (!node || typeof node !== 'object')
return;
if (node._type === 'FunctionDef' && this.isPythonExported(node)) {
exports.push({
id: this.generateId(),
name: node.name,
type: 'function',
isDefault: false,
filePath: context.filePath,
startLine: node.lineno || 0,
endLine: node.end_lineno || node.lineno || 0
});
}
else if (node._type === 'ClassDef' && this.isPythonExported(node)) {
exports.push({
id: this.generateId(),
name: node.name,
type: 'class',
isDefault: false,
filePath: context.filePath,
startLine: node.lineno || 0,
endLine: node.end_lineno || node.lineno || 0
});
}
// Check for __all__ variable
if (node._type === 'Assign') {
for (const target of node.targets || []) {
if (target._type === 'Name' && target.id === '__all__') {
// Extract names from __all__ list
if (node.value && node.value._type === 'List') {
for (const element of node.value.elts || []) {
if (element._type === 'Str' || element._type === 'Constant') {
exports.push({
id: this.generateId(),
name: element.s || element.value || '',
type: 'variable',
isDefault: false,
filePath: context.filePath,
startLine: node.lineno || 0,
endLine: node.end_lineno || node.lineno || 0
});
}
}
}
}
}
}
// Recursively process child nodes
this.traversePythonNode(node, extractExportsRecursive);
};
extractExportsRecursive(ast);
return exports;
}
async extractDependencies(ast, _context) {
const dependencies = new Set();
const extractDepsRecursive = (node) => {
if (!node || typeof node !== 'object')
return;
if (node._type === 'Import') {
for (const alias of node.names || []) {
if (alias.name) {
dependencies.add(alias.name.split('.')[0]);
}
}
}
else if (node._type === 'ImportFrom' && node.module) {
dependencies.add(node.module.split('.')[0]);
}
// Recursively process child nodes
this.traversePythonNode(node, extractDepsRecursive);
};
extractDepsRecursive(ast);
return Array.from(dependencies);
}
async calculateComplexity(ast, _context) {
let cyclomaticComplexity = 1;
let cognitiveComplexity = 0;
let linesOfCode = 0;
const calculateComplexityRecursive = (node) => {
if (!node || typeof node !== 'object')
return;
// Cyclomatic complexity
if (this.isPythonComplexityNode(node)) {
cyclomaticComplexity++;
}
// Cognitive complexity
if (this.isPythonCognitiveComplexityNode(node)) {
cognitiveComplexity++;
}
// Lines of code
if (node.end_lineno) {
linesOfCode = Math.max(linesOfCode, node.end_lineno);
}
// Recursively process child nodes
this.traversePythonNode(node, calculateComplexityRecursive);
};
calculateComplexityRecursive(ast);
const maintainabilityIndex = Math.max(0, 171 - 5.2 * Math.log(linesOfCode) - 0.23 * cyclomaticComplexity);
const technicalDebt = Math.max(0, (cyclomaticComplexity - 10) * 0.5 + (cognitiveComplexity - 15) * 0.3);
return {
cyclomaticComplexity,
cognitiveComplexity,
linesOfCode,
maintainabilityIndex,
technicalDebt
};
}
getParserConfig() {
return {
language: 'python',
sourceType: 'module',
allowImportExportEverywhere: true,
allowReturnOutsideFunction: false,
strictMode: false,
plugins: []
};
}
// Helper methods
createPythonParserScript() {
return `
import ast
import json
import sys
def ast_to_dict(node):
if isinstance(node, ast.AST):
result = {'_type': node.__class__.__name__}
for field, value in ast.iter_fields(node):
if isinstance(value, list):
result[field] = [ast_to_dict(item) for item in value]
elif isinstance(value, ast.AST):
result[field] = ast_to_dict(value)
else:
result[field] = value
# Add position information
if hasattr(node, 'lineno'):
result['lineno'] = node.lineno
if hasattr(node, 'col_offset'):
result['col_offset'] = node.col_offset
if hasattr(node, 'end_lineno'):
result['end_lineno'] = node.end_lineno
if hasattr(node, 'end_col_offset'):
result['end_col_offset'] = node.end_col_offset
return result
else:
return value
def main():
if len(sys.argv) != 2:
print("Usage: python parser.py <input_file>")
sys.exit(1)
input_file = sys.argv[1]
try:
with open(input_file, 'r', encoding='utf-8') as f:
source_code = f.read()
tree = ast.parse(source_code)
result = ast_to_dict(tree)
print(json.dumps(result, indent=2))
except Exception as e:
print(json.dumps({"error": str(e)}), file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
`;
}
getNodeName(node) {
if (node.name)
return node.name;
if (node.id)
return node.id;
return undefined;
}
extractNodeProperties(node) {
const properties = {};
if (node._type === 'FunctionDef') {
properties.async = false;
}
else if (node._type === 'AsyncFunctionDef') {
properties.async = true;
}
if (node.decorator_list && node.decorator_list.length > 0) {
properties.decorators = node.decorator_list.map((d) => d.id || d.attr || 'unknown');
}
return properties;
}
extractPythonParameters(args) {
const parameters = [];
// Regular arguments
if (args.args) {
for (const arg of args.args) {
parameters.push({
name: arg.arg || 'unknown',
type: arg.annotation ? this.extractPythonTypeAnnotation(arg.annotation) : undefined,
optional: false,
defaultValue: undefined
});
}
}
// Default values
if (args.defaults && args.args) {
const defaultStart = args.args.length - args.defaults.length;
for (let i = 0; i < args.defaults.length; i++) {
const paramIndex = defaultStart + i;
if (parameters[paramIndex]) {
parameters[paramIndex].optional = true;
parameters[paramIndex].defaultValue = this.extractPythonDefaultValue(args.defaults[i]);
}
}
}
// Keyword-only arguments
if (args.kwonlyargs) {
for (const arg of args.kwonlyargs) {
parameters.push({
name: arg.arg || 'unknown',
type: arg.annotation ? this.extractPythonTypeAnnotation(arg.annotation) : undefined,
optional: true,
defaultValue: undefined
});
}
}
return parameters;
}
extractPythonReturnType(node) {
if (node.returns) {
return this.extractPythonTypeAnnotation(node.returns);
}
return undefined;
}
extractPythonTypeAnnotation(annotation) {
if (!annotation)
return 'Any';
if (annotation._type === 'Name') {
return annotation.id || 'Any';
}
else if (annotation._type === 'Constant') {
return String(annotation.value);
}
else if (annotation._type === 'Attribute') {
return `${annotation.value?.id || 'unknown'}.${annotation.attr || 'unknown'}`;
}
return 'Any';
}
extractPythonDefaultValue(node) {
if (!node)
return 'None';
if (node._type === 'Constant') {
if (typeof node.value === 'string')
return `"${node.value}"`;
return String(node.value);
}
else if (node._type === 'Name') {
return node.id || 'None';
}
else if (node._type === 'List') {
return '[]';
}
else if (node._type === 'Dict') {
return '{}';
}
return 'None';
}
calculatePythonFunctionComplexity(node) {
let complexity = 1;
const countComplexity = (n) => {
if (!n || typeof n !== 'object')
return;
if (this.isPythonComplexityNode(n)) {
complexity++;
}
this.traversePythonNode(n, countComplexity);
};
countComplexity(node);
return complexity;
}
isPythonExported(node) {
// In Python, functions/classes not starting with _ are considered public
return node.name && !node.name.startsWith('_');
}
getPythonVisibility(name) {
if (name.startsWith('__') && name.endsWith('__'))
return 'public'; // Magic methods
if (name.startsWith('__'))
return 'private';
if (name.startsWith('_'))
return 'protected';
return 'public';
}
extractPythonFunctionDependencies(node) {
const dependencies = new Set();
const extractDeps = (n) => {
if (!n || typeof n !== 'object')
return;
if (n._type === 'Name') {
dependencies.add(n.id);
}
else if (n._type === 'Attribute' && n.value && n.value._type === 'Name') {
dependencies.add(n.value.id);
}
this.traversePythonNode(n, extractDeps);
};
if (node.body) {
for (const stmt of node.body) {
extractDeps(stmt);
}
}
return Array.from(dependencies);
}
extractPythonFunctionCalls(node) {
const calls = new Set();
const extractCalls = (n) => {
if (!n || typeof n !== 'object')
return;
if (n._type === 'Call') {
if (n.func._type === 'Name') {
calls.add(n.func.id);
}
else if (n.func._type === 'Attribute') {
calls.add(n.func.attr);
}
}
this.traversePythonNode(n, extractCalls);
};
if (node.body) {
for (const stmt of node.body) {
extractCalls(stmt);
}
}
return Array.from(calls);
}
extractPythonClassProperties(node) {
const properties = [];
if (node.body) {
for (const stmt of node.body) {
if (stmt._type === 'Assign') {
for (const target of stmt.targets || []) {
if (target._type === 'Name') {
properties.push({
name: target.id,
type: undefined,
visibility: this.getPythonVisibility(target.id),
isStatic: false,
isReadonly: false,
defaultValue: this.extractPythonDefaultValue(stmt.value)
});
}
}
}
else if (stmt._type === 'AnnAssign' && stmt.target && stmt.target._type === 'Name') {
properties.push({
name: stmt.target.id,
type: this.extractPythonTypeAnnotation(stmt.annotation),
visibility: this.getPythonVisibility(stmt.target.id),
isStatic: false,
isReadonly: false,
defaultValue: stmt.value ? this.extractPythonDefaultValue(stmt.value) : undefined
});
}
}
}
return properties;
}
extractPythonBaseClasses(node) {
if (node.bases && node.bases.length > 0) {
const base = node.bases[0];
if (base._type === 'Name') {
return base.id;
}
else if (base._type === 'Attribute') {
return `${base.value?.id || 'unknown'}.${base.attr || 'unknown'}`;
}
}
return undefined;
}
isPythonAbstractClass(node) {
// Check for ABC inheritance or @abstractmethod decorators
if (node.bases) {
for (const base of node.bases) {
if (base._type === 'Name' && (base.id === 'ABC' || base.id === 'AbstractBase')) {
return true;
}
}
}
// Check for abstract methods
if (node.body) {
for (const stmt of node.body) {
if (stmt._type === 'FunctionDef' && stmt.decorator_list) {
for (const decorator of stmt.decorator_list) {
if (decorator._type === 'Name' && decorator.id === 'abstractmethod') {
return true;
}
}
}
}
}
return false;
}
isPythonComplexityNode(node) {
return ['If', 'While', 'For', 'Try', 'With', 'AsyncWith', 'AsyncFor'].includes(node._type);
}
isPythonCognitiveComplexityNode(node) {
return this.isPythonComplexityNode(node) || ['ExceptHandler', 'ListComp', 'DictComp', 'SetComp'].includes(node._type);
}
traversePythonNode(node, callback) {
if (!node || typeof node !== 'object')
return;
// Process all child nodes
for (const [key, value] of Object.entries(node)) {
if (key === '_type')
continue;
if (Array.isArray(value)) {
for (const child of value) {
if (child && typeof child === 'object' && child._type) {
callback(child);
}
}
}
else if (value && typeof value === 'object' && value._type) {
callback(value);
}
}
}
}
exports.PythonAnalyzer = PythonAnalyzer;
//# sourceMappingURL=PythonAnalyzer.js.map