mirror of
https://github.com/theniceboy/.config.git
synced 2025-12-26 22:54:59 +08:00
740 lines
No EOL
32 KiB
Python
Executable file
740 lines
No EOL
32 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""
|
|
Dart File Structure Analyzer
|
|
Analyzes a Dart file and extracts its structure including classes, methods, fields, imports, etc.
|
|
"""
|
|
|
|
import re
|
|
import sys
|
|
import json
|
|
from pathlib import Path
|
|
from typing import List, Dict, Any, Optional
|
|
|
|
class DartStructureAnalyzer:
|
|
def __init__(self):
|
|
self.structure = {
|
|
'imports': [],
|
|
'exports': [],
|
|
'classes': [],
|
|
'functions': [],
|
|
'variables': [],
|
|
'enums': [],
|
|
'mixins': [],
|
|
'extensions': [],
|
|
'typedefs': []
|
|
}
|
|
|
|
def analyze_file(self, file_path: str) -> Dict[str, Any]:
|
|
"""Analyze a Dart file and return its structure"""
|
|
try:
|
|
with open(file_path, 'r', encoding='utf-8') as file:
|
|
content = file.read()
|
|
except Exception as e:
|
|
return {'error': f'Could not read file: {str(e)}'}
|
|
|
|
# Remove comments and strings to avoid false matches
|
|
cleaned_content = self._remove_comments_and_strings(content)
|
|
lines = content.split('\n')
|
|
|
|
self._extract_imports_exports(cleaned_content)
|
|
self._extract_classes(cleaned_content, lines)
|
|
self._extract_functions(cleaned_content, lines)
|
|
self._extract_variables(cleaned_content, lines)
|
|
self._extract_enums(cleaned_content, lines)
|
|
self._extract_mixins(cleaned_content, lines)
|
|
self._extract_extensions(cleaned_content, lines)
|
|
self._extract_typedefs(cleaned_content, lines)
|
|
|
|
return self.structure
|
|
|
|
def _remove_comments_and_strings(self, content: str) -> str:
|
|
"""Remove comments and string literals to avoid false matches"""
|
|
result = []
|
|
i = 0
|
|
while i < len(content):
|
|
# Check for single-line comment
|
|
if i < len(content) - 1 and content[i:i+2] == '//':
|
|
# Skip to end of line
|
|
while i < len(content) and content[i] != '\n':
|
|
i += 1
|
|
if i < len(content):
|
|
result.append('\n') # Keep the newline
|
|
i += 1
|
|
# Check for multi-line comment
|
|
elif i < len(content) - 1 and content[i:i+2] == '/*':
|
|
i += 2
|
|
# Skip to end of comment
|
|
while i < len(content) - 1:
|
|
if content[i:i+2] == '*/':
|
|
i += 2
|
|
break
|
|
if content[i] == '\n':
|
|
result.append('\n') # Keep newlines in comments
|
|
i += 1
|
|
# Check for string literals
|
|
elif content[i] in ['"', "'"]:
|
|
quote = content[i]
|
|
result.append(quote)
|
|
i += 1
|
|
# Skip to end of string
|
|
while i < len(content):
|
|
if content[i] == quote:
|
|
result.append(quote)
|
|
i += 1
|
|
break
|
|
elif content[i] == '\\' and i + 1 < len(content):
|
|
# Skip escaped character
|
|
i += 2
|
|
else:
|
|
i += 1
|
|
else:
|
|
result.append(content[i])
|
|
i += 1
|
|
|
|
return ''.join(result)
|
|
|
|
def _extract_imports_exports(self, content: str):
|
|
"""Extract import and export statements"""
|
|
import_pattern = r"import\s+['\"]([^'\"]+)['\"](?:\s+as\s+(\w+))?(?:\s+show\s+([^;]+))?(?:\s+hide\s+([^;]+))?;"
|
|
export_pattern = r"export\s+['\"]([^'\"]+)['\"](?:\s+show\s+([^;]+))?(?:\s+hide\s+([^;]+))?;"
|
|
|
|
for match in re.finditer(import_pattern, content):
|
|
import_info = {
|
|
'path': match.group(1),
|
|
'alias': match.group(2),
|
|
'show': match.group(3).strip() if match.group(3) else None,
|
|
'hide': match.group(4).strip() if match.group(4) else None
|
|
}
|
|
self.structure['imports'].append(import_info)
|
|
|
|
for match in re.finditer(export_pattern, content):
|
|
export_info = {
|
|
'path': match.group(1),
|
|
'show': match.group(2).strip() if match.group(2) else None,
|
|
'hide': match.group(3).strip() if match.group(3) else None
|
|
}
|
|
self.structure['exports'].append(export_info)
|
|
|
|
def _extract_classes(self, content: str, lines: List[str]):
|
|
"""Extract class definitions"""
|
|
class_pattern = r'(?:abstract\s+)?class\s+(\w+)(?:\s*<[^>]+>)?(?:\s+extends\s+(\w+(?:<[^>]+>)?))?(?:\s+with\s+([^{]+))?(?:\s+implements\s+([^{]+))?\s*{'
|
|
|
|
for match in re.finditer(class_pattern, content):
|
|
line_num = self._get_line_number(content, match.start(), lines)
|
|
class_info = {
|
|
'name': match.group(1),
|
|
'line': line_num,
|
|
'extends': match.group(2).strip() if match.group(2) else None,
|
|
'mixins': [m.strip() for m in match.group(3).split(',')] if match.group(3) else [],
|
|
'implements': [i.strip() for i in match.group(4).split(',')] if match.group(4) else [],
|
|
'methods': [],
|
|
'fields': [],
|
|
'constructors': []
|
|
}
|
|
|
|
# Extract class body - find the position right after the opening brace
|
|
brace_pos = match.end() - 1 # Position of opening brace
|
|
class_body = self._extract_class_body(content, brace_pos)
|
|
if class_body:
|
|
# The body starts after the opening brace AND any immediate whitespace
|
|
body_start_pos = brace_pos + 1
|
|
# Skip any whitespace/newlines immediately after the brace to get to actual content
|
|
while body_start_pos < len(content) and content[body_start_pos] in ['\n', ' ', '\t']:
|
|
body_start_pos += 1
|
|
self._extract_class_members_absolute(content, class_body, class_info, lines, body_start_pos)
|
|
|
|
self.structure['classes'].append(class_info)
|
|
|
|
def _extract_class_body(self, content: str, start_pos: int) -> str:
|
|
"""Extract the body of a class between braces"""
|
|
brace_count = 0
|
|
start = start_pos
|
|
|
|
for i, char in enumerate(content[start_pos:], start_pos):
|
|
if char == '{':
|
|
brace_count += 1
|
|
elif char == '}':
|
|
brace_count -= 1
|
|
if brace_count == 0:
|
|
return content[start + 1:i]
|
|
return ""
|
|
|
|
def _extract_class_members_absolute(self, full_content: str, class_body: str, class_info: Dict, lines: List[str], body_start_pos: int):
|
|
"""Extract methods, fields, and constructors from class body with absolute line numbers"""
|
|
|
|
# Split class body into lines for better parsing
|
|
class_lines = class_body.split('\n')
|
|
processed_lines = set()
|
|
|
|
# Extract constructors first
|
|
constructor_pattern = rf'{re.escape(class_info["name"])}\s*(?:\.\w+)?\s*\([^)]*\)\s*(?::\s*[^{{]+)?\s*{{'
|
|
for match in re.finditer(constructor_pattern, class_body):
|
|
absolute_pos = body_start_pos + match.start()
|
|
line_in_class = class_body[:match.start()].count('\n')
|
|
processed_lines.add(line_in_class)
|
|
constructor_info = {
|
|
'name': match.group(0).split('(')[0].strip(),
|
|
'line': self._get_line_number(full_content, absolute_pos, lines)
|
|
}
|
|
class_info['constructors'].append(constructor_info)
|
|
|
|
# Extract getters and setters
|
|
getter_pattern = r'(?:static\s+)?(?:([\w<>?]+)\s+)?get\s+(\w+)(?:\s*=>\s*[^;]+;|\s*{)'
|
|
for match in re.finditer(getter_pattern, class_body):
|
|
absolute_pos = body_start_pos + match.start()
|
|
line_in_class = class_body[:match.start()].count('\n')
|
|
if line_in_class not in processed_lines:
|
|
processed_lines.add(line_in_class)
|
|
method_info = {
|
|
'name': f"get {match.group(2)}",
|
|
'return_type': match.group(1),
|
|
'line': self._get_line_number(full_content, absolute_pos, lines)
|
|
}
|
|
class_info['methods'].append(method_info)
|
|
|
|
setter_pattern = r'(?:static\s+)?set\s+(\w+)\([^)]*\)(?:\s*=>\s*[^;]+;|\s*{)'
|
|
for match in re.finditer(setter_pattern, class_body):
|
|
absolute_pos = body_start_pos + match.start()
|
|
line_in_class = class_body[:match.start()].count('\n')
|
|
if line_in_class not in processed_lines:
|
|
processed_lines.add(line_in_class)
|
|
method_info = {
|
|
'name': f"set {match.group(1)}",
|
|
'return_type': 'void',
|
|
'line': self._get_line_number(full_content, absolute_pos, lines)
|
|
}
|
|
class_info['methods'].append(method_info)
|
|
|
|
# Extract regular methods - must be method declarations, not calls
|
|
method_pattern = r'(?:^|\n)(\s*)(?:static\s+)?(?:@\w+\s+)*(?:([\w<>?]+)\s+)?(\w+)\s*\([^)]*\)\s*(?:async\s*)?(?:\s*=>\s*[^;]+;|\s*{)'
|
|
for match in re.finditer(method_pattern, class_body):
|
|
indent = match.group(1)
|
|
method_name = match.group(3)
|
|
return_type = match.group(2)
|
|
|
|
# Skip constructors, getters, setters
|
|
if method_name == class_info['name'] or method_name in ['get', 'set']:
|
|
continue
|
|
|
|
# Skip obvious control flow and literals
|
|
if method_name in ['if', 'for', 'while', 'switch', 'try', 'catch', 'finally', 'true', 'false', 'null', 'return']:
|
|
continue
|
|
|
|
# Skip common method calls and expressions
|
|
if method_name in ['forEach', 'map', 'where', 'listen', 'then', 'add', 'remove', 'removeWhere', 'singleWhere', 'firstWhere', 'reduce', 'fold', 'any', 'every', 'contains', 'indexOf', 'lastIndexOf', 'join', 'split', 'handleException', 'sendData', 'await']:
|
|
continue
|
|
|
|
# Methods should have reasonable indentation (2-6 spaces for class methods)
|
|
if len(indent) > 8:
|
|
continue
|
|
|
|
# Check if this is preceded by 'await' or other indicators of method calls
|
|
line_start = class_body.rfind('\n', 0, match.start()) + 1
|
|
line_before_method = class_body[line_start:match.start()]
|
|
if re.search(r'\b(await|return|=|\(|\[)\s*$', line_before_method):
|
|
continue
|
|
|
|
# Additional check: method declarations should typically have return types or be void
|
|
# If no return type is specified and it's deeply indented, it's likely a method call
|
|
if not return_type and len(indent) > 4:
|
|
continue
|
|
|
|
absolute_pos = body_start_pos + match.start()
|
|
line_in_class = class_body[:match.start()].count('\n')
|
|
if line_in_class not in processed_lines:
|
|
processed_lines.add(line_in_class)
|
|
method_info = {
|
|
'name': method_name,
|
|
'return_type': return_type,
|
|
'line': self._get_line_number(full_content, absolute_pos, lines)
|
|
}
|
|
class_info['methods'].append(method_info)
|
|
|
|
# Extract fields (only at class level, not inside methods)
|
|
# Pattern 1: Explicit type - "final Type fieldName" or "Type fieldName"
|
|
# Also handles comma-separated: "Type field1, field2, field3;"
|
|
explicit_type_pattern = r'(?:^|\n)\s*(?:static\s+)?(?:final\s+|const\s+|late\s+)?([\w<>?]+)\s+([^;]+);'
|
|
# Pattern 2: Inferred type - "final fieldName = <Type>{}" or "final fieldName = Constructor()"
|
|
inferred_type_pattern = r'(?:^|\n)\s*(?:static\s+)?(final|const|late)\s+(\w+)\s*=\s*(?:<([^>]+)>\s*\{\}|<([^>]+)>\s*\[\]|(\w+(?:<[^>]+>)?)\s*\()'
|
|
|
|
# First, identify method signatures and bodies to exclude them from field detection
|
|
method_bodies = []
|
|
# Pattern to match method signatures including multi-line parameter lists
|
|
method_signature_pattern = r'(?:^|\n)\s*(?:static\s+)?(?:@\w+\s+)*(?:[\w<>?]+\s+)?(?:get\s+|set\s+)?\w+\s*\('
|
|
|
|
for method_match in re.finditer(method_signature_pattern, class_body):
|
|
# Find the complete method signature (including parameters) and body
|
|
signature_start = method_match.start()
|
|
|
|
# Find the opening parenthesis and match it with closing parenthesis
|
|
paren_pos = method_match.end() - 1 # Position of opening parenthesis
|
|
paren_count = 0
|
|
signature_end = paren_pos
|
|
|
|
for i, char in enumerate(class_body[paren_pos:], paren_pos):
|
|
if char == '(':
|
|
paren_count += 1
|
|
elif char == ')':
|
|
paren_count -= 1
|
|
if paren_count == 0:
|
|
signature_end = i + 1
|
|
break
|
|
|
|
# Now find the method body (look for opening brace after the closing parenthesis)
|
|
body_start = -1
|
|
for i, char in enumerate(class_body[signature_end:], signature_end):
|
|
if char == '{':
|
|
body_start = i
|
|
break
|
|
elif char not in [' ', '\n', '\t'] and not char.isalpha(): # Skip 'async' keyword
|
|
if char != 'a': # Not starting 'async'
|
|
break
|
|
|
|
if body_start != -1:
|
|
# Find the end of method body
|
|
brace_count = 0
|
|
for i, char in enumerate(class_body[body_start:], body_start):
|
|
if char == '{':
|
|
brace_count += 1
|
|
elif char == '}':
|
|
brace_count -= 1
|
|
if brace_count == 0:
|
|
# Include the entire method signature and body
|
|
method_bodies.append((signature_start, i + 1))
|
|
break
|
|
|
|
# Process explicit type fields first
|
|
for match in re.finditer(explicit_type_pattern, class_body):
|
|
field_names_part = match.group(2).strip()
|
|
field_type = match.group(1)
|
|
|
|
# Skip if field_type is actually a modifier or control flow keyword
|
|
if field_type in ['final', 'const', 'late', 'static', 'var', 'dynamic', 'if', 'for', 'while', 'switch', 'try', 'catch', 'return']:
|
|
continue
|
|
|
|
# Parse field names (handle comma-separated declarations)
|
|
# Split by comma and clean up each field name
|
|
field_declarations = []
|
|
if ',' in field_names_part:
|
|
# Multiple fields: "field1, field2, field3"
|
|
raw_fields = field_names_part.split(',')
|
|
for raw_field in raw_fields:
|
|
# Clean up and extract just the field name (remove default values)
|
|
clean_field = raw_field.split('=')[0].strip()
|
|
if clean_field and re.match(r'^\w+$', clean_field):
|
|
field_declarations.append(clean_field)
|
|
else:
|
|
# Single field: "fieldName" or "fieldName = defaultValue"
|
|
clean_field = field_names_part.split('=')[0].strip()
|
|
if clean_field and re.match(r'^\w+$', clean_field):
|
|
field_declarations.append(clean_field)
|
|
|
|
# Check if this field declaration is inside a method body
|
|
field_pos = match.start()
|
|
inside_method = False
|
|
for method_start, method_end in method_bodies:
|
|
if method_start <= field_pos <= method_end:
|
|
inside_method = True
|
|
break
|
|
|
|
if not inside_method:
|
|
absolute_pos = body_start_pos + match.start()
|
|
line_in_class = class_body[:match.start()].count('\n')
|
|
# Check if we've already processed this specific field declaration
|
|
# (not just the line number, since comma-separated fields share the same line)
|
|
field_key = (line_in_class, field_type, tuple(sorted(field_declarations)))
|
|
if field_key not in processed_lines:
|
|
# Additional check: must be at proper indentation for class field
|
|
line_start = class_body.rfind('\n', 0, match.start()) + 1
|
|
line_content = class_body[line_start:match.end()]
|
|
indent = len(line_content) - len(line_content.lstrip())
|
|
|
|
# Class fields should have minimal indentation (2-4 spaces typically)
|
|
if indent <= 6:
|
|
processed_lines.add(field_key)
|
|
actual_line = self._get_line_number(full_content, absolute_pos, lines)
|
|
|
|
# Add each field from the declaration
|
|
for field_name in field_declarations:
|
|
field_info = {
|
|
'name': field_name,
|
|
'type': field_type,
|
|
'line': actual_line
|
|
}
|
|
class_info['fields'].append(field_info)
|
|
|
|
# Process inferred type fields
|
|
for match in re.finditer(inferred_type_pattern, class_body):
|
|
modifier = match.group(1) # final, const, late
|
|
field_name = match.group(2)
|
|
# Extract inferred type from different patterns
|
|
if match.group(3): # <Type>{}
|
|
field_type = f"Map<{match.group(3)}>"
|
|
elif match.group(4): # <Type>[]
|
|
field_type = f"List<{match.group(4)}>"
|
|
elif match.group(5): # Constructor()
|
|
constructor = match.group(5)
|
|
field_type = constructor.split('<')[0] # Remove generic part for constructor name
|
|
else:
|
|
field_type = "dynamic"
|
|
|
|
# Check if this field declaration is inside a method body
|
|
field_pos = match.start()
|
|
inside_method = False
|
|
for method_start, method_end in method_bodies:
|
|
if method_start <= field_pos <= method_end:
|
|
inside_method = True
|
|
break
|
|
|
|
if not inside_method:
|
|
absolute_pos = body_start_pos + match.start()
|
|
line_in_class = class_body[:match.start()].count('\n')
|
|
if line_in_class not in processed_lines:
|
|
# Additional check: must be at proper indentation for class field
|
|
line_start = class_body.rfind('\n', 0, match.start()) + 1
|
|
line_content = class_body[line_start:match.end()]
|
|
indent = len(line_content) - len(line_content.lstrip())
|
|
|
|
# Class fields should have minimal indentation (2-4 spaces typically)
|
|
if indent <= 6:
|
|
processed_lines.add(line_in_class)
|
|
field_info = {
|
|
'name': field_name,
|
|
'type': field_type,
|
|
'line': self._get_line_number(full_content, absolute_pos, lines)
|
|
}
|
|
class_info['fields'].append(field_info)
|
|
|
|
def _extract_functions(self, content: str, lines: List[str]):
|
|
"""Extract top-level function definitions"""
|
|
function_pattern = r'(?:^|\n)(?:@\w+\s+)*(?:([\w<>]+)\s+)?(\w+)\s*\([^)]*\)\s*(?:async\s*)?{'
|
|
|
|
for match in re.finditer(function_pattern, content):
|
|
# Skip if it's inside a class (simple heuristic)
|
|
before_match = content[:match.start()]
|
|
if self._is_inside_class(before_match):
|
|
continue
|
|
|
|
line_num = self._get_line_number(content, match.start(), lines)
|
|
function_info = {
|
|
'name': match.group(2),
|
|
'return_type': match.group(1),
|
|
'line': line_num
|
|
}
|
|
self.structure['functions'].append(function_info)
|
|
|
|
def _extract_variables(self, content: str, lines: List[str]):
|
|
"""Extract top-level variable declarations"""
|
|
var_pattern = r'(?:^|\n)(?:const\s+|final\s+|var\s+|late\s+)?([\w<>?]+)\s+(\w+)(?:\s*=\s*[^;]+)?;'
|
|
|
|
for match in re.finditer(var_pattern, content):
|
|
before_match = content[:match.start()]
|
|
if self._is_inside_class(before_match):
|
|
continue
|
|
|
|
line_num = self._get_line_number(content, match.start(), lines)
|
|
var_info = {
|
|
'name': match.group(2),
|
|
'type': match.group(1),
|
|
'line': line_num
|
|
}
|
|
self.structure['variables'].append(var_info)
|
|
|
|
def _extract_enums(self, content: str, lines: List[str]):
|
|
"""Extract enum definitions"""
|
|
enum_pattern = r'enum\s+(\w+)\s*{'
|
|
|
|
for match in re.finditer(enum_pattern, content):
|
|
line_num = self._get_line_number(content, match.start(), lines)
|
|
enum_info = {
|
|
'name': match.group(1),
|
|
'line': line_num,
|
|
'values': []
|
|
}
|
|
|
|
# Extract enum values - handle both single-line and multi-line enums
|
|
# First try to find the enum body between braces
|
|
brace_start = match.end() - 1 # Position of opening brace
|
|
brace_end = -1
|
|
brace_count = 0
|
|
|
|
for i, char in enumerate(content[brace_start:], brace_start):
|
|
if char == '{':
|
|
brace_count += 1
|
|
elif char == '}':
|
|
brace_count -= 1
|
|
if brace_count == 0:
|
|
brace_end = i
|
|
break
|
|
|
|
if brace_end > brace_start:
|
|
enum_body = content[brace_start + 1:brace_end]
|
|
|
|
# Remove comments and clean up the enum body
|
|
cleaned_enum_body = re.sub(r'//.*$', '', enum_body, flags=re.MULTILINE)
|
|
cleaned_enum_body = re.sub(r'/\*.*?\*/', '', cleaned_enum_body, flags=re.DOTALL)
|
|
|
|
# Extract enum values - split by comma and clean up
|
|
values_text = cleaned_enum_body.strip()
|
|
if values_text:
|
|
# Split by comma and clean up each value
|
|
raw_values = re.split(r'[,\s]+', values_text)
|
|
for value in raw_values:
|
|
value = value.strip()
|
|
# Only include valid identifiers
|
|
if value and re.match(r'^\w+$', value):
|
|
if value not in ['enum', 'const', 'final', 'static']:
|
|
enum_info['values'].append(value)
|
|
|
|
self.structure['enums'].append(enum_info)
|
|
|
|
def _extract_mixins(self, content: str, lines: List[str]):
|
|
"""Extract mixin definitions"""
|
|
mixin_pattern = r'mixin\s+(\w+)(?:\s+on\s+([^{]+))?\s*{'
|
|
|
|
for match in re.finditer(mixin_pattern, content):
|
|
line_num = self._get_line_number(content, match.start(), lines)
|
|
mixin_info = {
|
|
'name': match.group(1),
|
|
'line': line_num,
|
|
'on': match.group(2).strip() if match.group(2) else None
|
|
}
|
|
self.structure['mixins'].append(mixin_info)
|
|
|
|
def _extract_extensions(self, content: str, lines: List[str]):
|
|
"""Extract extension definitions"""
|
|
extension_pattern = r'extension\s+(?:(\w+)\s+)?on\s+([\w<>]+)\s*{'
|
|
|
|
for match in re.finditer(extension_pattern, content):
|
|
line_num = self._get_line_number(content, match.start(), lines)
|
|
extension_info = {
|
|
'name': match.group(1) if match.group(1) else 'Anonymous',
|
|
'on': match.group(2),
|
|
'line': line_num
|
|
}
|
|
self.structure['extensions'].append(extension_info)
|
|
|
|
def _extract_typedefs(self, content: str, lines: List[str]):
|
|
"""Extract typedef definitions"""
|
|
typedef_pattern = r'typedef\s+(\w+)(?:\s*<[^>]+>)?\s*=\s*([^;]+);'
|
|
|
|
for match in re.finditer(typedef_pattern, content):
|
|
line_num = self._get_line_number(content, match.start(), lines)
|
|
typedef_info = {
|
|
'name': match.group(1),
|
|
'definition': match.group(2).strip(),
|
|
'line': line_num
|
|
}
|
|
self.structure['typedefs'].append(typedef_info)
|
|
|
|
def _is_inside_class(self, content_before: str) -> bool:
|
|
"""Check if we're inside a class definition"""
|
|
open_braces = content_before.count('{')
|
|
close_braces = content_before.count('}')
|
|
class_count = len(re.findall(r'\bclass\s+\w+', content_before))
|
|
return open_braces > close_braces and class_count > 0
|
|
|
|
def _get_line_number(self, content: str, position: int, lines: List[str]) -> int:
|
|
"""Get line number for a given position in content"""
|
|
if position == 0:
|
|
return 1
|
|
return content[:position].count('\n') + 1
|
|
|
|
def print_linear_structure(structure: Dict[str, Any]):
|
|
"""Print structure in linear format with indentation"""
|
|
|
|
print("Format: [line] (type) name - lines are absolute file positions, indented items are class members")
|
|
print()
|
|
|
|
# Collect all top-level items and sort by line number
|
|
all_items = []
|
|
|
|
# Add imports
|
|
for imp in structure['imports']:
|
|
all_items.append({
|
|
'line': imp.get('line', 0),
|
|
'type': 'import',
|
|
'data': imp
|
|
})
|
|
|
|
# Add exports
|
|
for exp in structure['exports']:
|
|
all_items.append({
|
|
'line': exp.get('line', 0),
|
|
'type': 'export',
|
|
'data': exp
|
|
})
|
|
|
|
# Add classes
|
|
for cls in structure['classes']:
|
|
all_items.append({
|
|
'line': cls['line'],
|
|
'type': 'class',
|
|
'data': cls
|
|
})
|
|
|
|
# Add enums
|
|
for enum in structure['enums']:
|
|
all_items.append({
|
|
'line': enum['line'],
|
|
'type': 'enum',
|
|
'data': enum
|
|
})
|
|
|
|
# Add mixins
|
|
for mixin in structure['mixins']:
|
|
all_items.append({
|
|
'line': mixin['line'],
|
|
'type': 'mixin',
|
|
'data': mixin
|
|
})
|
|
|
|
# Add extensions
|
|
for ext in structure['extensions']:
|
|
all_items.append({
|
|
'line': ext['line'],
|
|
'type': 'extension',
|
|
'data': ext
|
|
})
|
|
|
|
# Add functions
|
|
for func in structure['functions']:
|
|
all_items.append({
|
|
'line': func['line'],
|
|
'type': 'function',
|
|
'data': func
|
|
})
|
|
|
|
# Add variables
|
|
for var in structure['variables']:
|
|
all_items.append({
|
|
'line': var['line'],
|
|
'type': 'variable',
|
|
'data': var
|
|
})
|
|
|
|
# Add typedefs
|
|
for typedef in structure['typedefs']:
|
|
all_items.append({
|
|
'line': typedef['line'],
|
|
'type': 'typedef',
|
|
'data': typedef
|
|
})
|
|
|
|
# Sort all items by line number
|
|
all_items.sort(key=lambda x: x['line'])
|
|
|
|
# Print sorted items
|
|
for item in all_items:
|
|
if item['type'] == 'import':
|
|
print(f"[{item['line']}] (import) {item['data']['path']}")
|
|
elif item['type'] == 'export':
|
|
print(f"[{item['line']}] (export) {item['data']['path']}")
|
|
elif item['type'] == 'class':
|
|
cls = item['data']
|
|
print(f"[{cls['line']}] (class) {cls['name']}")
|
|
if cls['extends']:
|
|
print(f" extends {cls['extends']}")
|
|
if cls['mixins']:
|
|
print(f" mixins: {', '.join(cls['mixins'])}")
|
|
if cls['implements']:
|
|
print(f" implements: {', '.join(cls['implements'])}")
|
|
|
|
# Collect all class members and sort by line number
|
|
members = []
|
|
|
|
# Add constructors
|
|
for ctor in cls['constructors']:
|
|
members.append({
|
|
'line': ctor['line'],
|
|
'type': 'constructor',
|
|
'name': ctor['name'],
|
|
'return_type': None
|
|
})
|
|
|
|
# Add fields
|
|
for field in cls['fields']:
|
|
members.append({
|
|
'line': field['line'],
|
|
'type': 'field',
|
|
'name': field['name'],
|
|
'return_type': field['type']
|
|
})
|
|
|
|
# Add methods
|
|
for method in cls['methods']:
|
|
members.append({
|
|
'line': method['line'],
|
|
'type': 'method',
|
|
'name': method['name'],
|
|
'return_type': method['return_type']
|
|
})
|
|
|
|
# Sort all members by line number
|
|
members.sort(key=lambda x: x['line'])
|
|
|
|
# Print sorted members
|
|
for member in members:
|
|
if member['type'] == 'constructor':
|
|
print(f" [{member['line']}] (constructor) {member['name']}")
|
|
elif member['type'] == 'field':
|
|
type_info = f" : {member['return_type']}" if member['return_type'] else ""
|
|
print(f" [{member['line']}] (field) {member['name']}{type_info}")
|
|
elif member['type'] == 'method':
|
|
return_type = f"{member['return_type']} " if member['return_type'] else ""
|
|
print(f" [{member['line']}] (method) {return_type}{member['name']}")
|
|
|
|
elif item['type'] == 'enum':
|
|
enum = item['data']
|
|
print(f"[{enum['line']}] (enum) {enum['name']}")
|
|
for value in enum['values']:
|
|
print(f" (value) {value}")
|
|
|
|
elif item['type'] == 'mixin':
|
|
mixin = item['data']
|
|
on_clause = f" on {mixin['on']}" if mixin['on'] else ""
|
|
print(f"[{mixin['line']}] (mixin) {mixin['name']}{on_clause}")
|
|
|
|
elif item['type'] == 'extension':
|
|
ext = item['data']
|
|
print(f"[{ext['line']}] (extension) {ext['name']} on {ext['on']}")
|
|
|
|
elif item['type'] == 'function':
|
|
func = item['data']
|
|
return_type = f"{func['return_type']} " if func['return_type'] else ""
|
|
print(f"[{func['line']}] (function) {return_type}{func['name']}")
|
|
|
|
elif item['type'] == 'variable':
|
|
var = item['data']
|
|
type_info = f" : {var['type']}" if var['type'] else ""
|
|
print(f"[{var['line']}] (variable) {var['name']}{type_info}")
|
|
|
|
elif item['type'] == 'typedef':
|
|
typedef = item['data']
|
|
print(f"[{typedef['line']}] (typedef) {typedef['name']} = {typedef['definition']}")
|
|
|
|
def main():
|
|
if len(sys.argv) != 2:
|
|
print("Usage: python dart_structure_analyzer.py <dart_file>")
|
|
sys.exit(1)
|
|
|
|
file_path = sys.argv[1]
|
|
|
|
if not Path(file_path).exists():
|
|
print(f"Error: File '{file_path}' does not exist")
|
|
sys.exit(1)
|
|
|
|
if not file_path.endswith('.dart'):
|
|
print(f"Warning: '{file_path}' does not have a .dart extension")
|
|
|
|
analyzer = DartStructureAnalyzer()
|
|
structure = analyzer.analyze_file(file_path)
|
|
|
|
if 'error' in structure:
|
|
print(f"Error: {structure['error']}")
|
|
sys.exit(1)
|
|
|
|
print_linear_structure(structure)
|
|
|
|
if __name__ == "__main__":
|
|
main() |