p4-vscode_designer_extension/src/parser/tk_ast/analyzer/base.py
2025-12-26 19:28:22 +03:00

223 lines
8.3 KiB
Python

import ast
import textwrap
import tokenize
import io
from typing import Dict, List, Any, Optional
from .imports import handle_import, handle_import_from
from .context import enter_class, exit_class, enter_function, exit_function
from .calls import handle_method_call
from .widget_creation import is_widget_creation, extract_widget_info, analyze_widget_creation_commands
from .extractors import extract_call_parameters, extract_parent_container
from .values import extract_value, get_variable_name, analyze_lambda_complexity, extract_lambda_body, get_operator_symbol
from .placements import update_widget_placement
from .events import analyze_bind_event, analyze_config_command, analyze_callback, is_interactive_widget
from .connections import create_widget_handler_connections
class TkinterAnalyzer(ast.NodeVisitor):
def __init__(self, source_code: str = ""):
self.source_code = source_code
self.line_offsets = [0]
current = 0
if source_code:
for line in source_code.splitlines(keepends=True):
current += len(line)
self.line_offsets.append(current)
self.widgets: List[Dict[str, Any]] = []
self.window_config = {'title': 'App', 'width': 800, 'height': 600}
self.imports: Dict[str, str] = {}
self.variables: Dict[str, Any] = {}
self.current_class: Optional[str] = None
self.current_method: Optional[str] = None
self.event_handlers: List[Dict[str, Any]] = []
self.command_callbacks: List[Dict[str, Any]] = []
self.bind_events: List[Dict[str, Any]] = []
self.methods: Dict[str, str] = {}
def visit_Import(self, node: ast.Import):
handle_import(self, node)
self.generic_visit(node)
def visit_ImportFrom(self, node: ast.ImportFrom):
handle_import_from(self, node)
self.generic_visit(node)
def visit_ClassDef(self, node: ast.ClassDef):
prev = self.current_class
enter_class(self, node)
is_tk_class = False
for base in node.bases:
if isinstance(base, ast.Attribute) and base.attr == 'Tk':
is_tk_class = True
elif isinstance(base, ast.Name) and base.id == 'Tk':
is_tk_class = True
if node.name == 'Application':
self.window_config['className'] = node.name
elif is_tk_class and self.window_config.get('className') != 'Application':
self.window_config['className'] = node.name
elif not self.window_config.get('className'):
self.window_config['className'] = node.name
self.generic_visit(node)
exit_class(self, prev)
def visit_FunctionDef(self, node: ast.FunctionDef):
prev = self.current_method
enter_function(self, node)
if self.current_class and node.name not in ['__init__', 'create_widgets', 'run'] and self.source_code and node.body:
try:
start_line = node.lineno
end_line = getattr(node, 'end_lineno', None)
if end_line is None:
end_line = node.body[-1].lineno
lines = self.source_code.splitlines(keepends=True)
func_lines = lines[start_line-1 : end_line]
func_text = "".join(func_lines)
body_start_idx = -1
try:
tokens = list(tokenize.tokenize(io.BytesIO(func_text.encode('utf-8')).readline))
nesting = 0
colon_token = None
for tok in tokens:
if tok.type == tokenize.OP:
if tok.string in '([{':
nesting += 1
elif tok.string in ')]}':
nesting -= 1
elif tok.string == ':' and nesting == 0:
colon_token = tok
break
if colon_token:
colon_line_idx = colon_token.end[0] - 1
body_lines = func_lines[colon_line_idx + 1:]
colon_line = func_lines[colon_line_idx]
after_colon = colon_line[colon_token.end[1]:]
if after_colon.strip():
body_lines.insert(0, after_colon)
body_text = "".join(body_lines)
dedented_body = textwrap.dedent(body_text)
sig_lines = func_lines[:colon_line_idx]
sig_lines.append(colon_line[:colon_token.end[1]])
sig_raw = "".join(sig_lines).strip()
if sig_raw.endswith(':'):
sig_raw = sig_raw[:-1].strip()
self.methods[node.name] = {
'body': dedented_body.strip(),
'signature': sig_raw
}
except tokenize.TokenError:
pass
except Exception as e:
pass
self.generic_visit(node)
exit_function(self, prev)
def visit_Assign(self, node: ast.Assign):
if is_widget_creation(self, node):
info = extract_widget_info(self, node)
if info:
self.widgets.append(info)
analyze_widget_creation_commands(self, node)
for target in node.targets:
if isinstance(target, ast.Name):
self.variables[target.id] = node.value
elif isinstance(target, ast.Attribute):
if isinstance(target.value, ast.Name) and target.value.id == 'self':
self.variables[target.attr] = node.value
self.generic_visit(node)
def visit_Call(self, node: ast.Call):
if isinstance(node.func, ast.Attribute):
handle_method_call(self, node)
self.generic_visit(node)
def extract_call_parameters(self, call_node: ast.Call) -> Dict[str, Any]:
return extract_call_parameters(self, call_node)
def extract_value(self, node: ast.AST) -> Any:
return extract_value(node)
def extract_parent_container(self, call_node: ast.Call) -> str:
return extract_parent_container(self, call_node)
def get_variable_name(self, node: ast.AST) -> str:
return get_variable_name(node)
def update_widget_placement(self, target_var: str, call_node: ast.Call, method: str):
return update_widget_placement(self, target_var, call_node, method)
def analyze_bind_event(self, target_var: str, call_node: ast.Call):
return analyze_bind_event(self, target_var, call_node)
def analyze_config_command(self, target_var: str, call_node: ast.Call):
return analyze_config_command(self, target_var, call_node)
def analyze_callback(self, callback_node: ast.AST) -> Dict[str, Any]:
return analyze_callback(self, callback_node)
def analyze_lambda_complexity(self, lambda_node: ast.Lambda) -> str:
return analyze_lambda_complexity(lambda_node)
def extract_lambda_body(self, body_node: ast.AST) -> str:
return extract_lambda_body(body_node)
def get_operator_symbol(self, op_node: ast.AST) -> str:
return get_operator_symbol(op_node)
def analyze_widget_creation_commands(self, node: ast.Assign):
return analyze_widget_creation_commands(self, node)