-
Notifications
You must be signed in to change notification settings - Fork 0
chore(flutter): analyzer cleanup phase 1.2 (tools) #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,67 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #!/usr/bin/env python3 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import re | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import sys | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Scan Dart files and ensure `package:flutter/material.dart` is imported when | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Icons/Colors/ElevatedButton/etc. are referenced without an existing material import. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| This is a conservative fixer: it only adds the import if none of these are present: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - package:flutter/material.dart | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - package:flutter/widgets.dart (not sufficient by itself for Icons/Colors) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| NEEDLES = [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| r"\bIcons\.", r"\bColors\.", r"\bElevatedButton\b", r"\bOutlinedButton\b", r"\bTextButton\b", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def file_needs_material(text: str) -> bool: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if 'package:flutter/material.dart' in text: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for pat in NEEDLES: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if re.search(pat, text): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+14
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For improved performance and clarity, it's a good practice to pre-compile regular expressions, especially since they are used inside a loop. You can define
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def add_material_import(path: str) -> bool: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with open(path, 'r', encoding='utf-8') as f: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| text = f.read() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not file_needs_material(text): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lines = text.splitlines(True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Insert after first import line block, or at top if none | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| insert_idx = 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while insert_idx < len(lines) and lines[insert_idx].strip().startswith('import '): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| insert_idx += 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lines.insert(insert_idx, "import 'package:flutter/material.dart';\n") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+33
to
+37
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Insert after first import line block, or at top if none | |
| insert_idx = 0 | |
| while insert_idx < len(lines) and lines[insert_idx].strip().startswith('import '): | |
| insert_idx += 1 | |
| lines.insert(insert_idx, "import 'package:flutter/material.dart';\n") | |
| # Find all import lines and their indices | |
| import_lines = [] | |
| for idx, line in enumerate(lines): | |
| if line.strip().startswith('import '): | |
| import_lines.append((idx, line.strip())) | |
| # Find the range of package: imports | |
| package_imports = [(idx, l) for idx, l in import_lines if l.startswith("import 'package:")] | |
| material_import = "import 'package:flutter/material.dart';\n" | |
| if package_imports: | |
| # Find the correct alphabetical position among package imports | |
| insert_idx = package_imports[0][0] | |
| inserted = False | |
| for i, (idx, l) in enumerate(package_imports): | |
| import_path = l.split('import ')[1].strip(';').strip("'").strip('"') | |
| if material_import.strip() < l: | |
| insert_idx = idx | |
| lines.insert(insert_idx, material_import) | |
| inserted = True | |
| break | |
| if not inserted: | |
| # Insert after the last package import | |
| insert_idx = package_imports[-1][0] + 1 | |
| lines.insert(insert_idx, material_import) | |
| else: | |
| # Insert after all imports, or at the top if none | |
| insert_idx = 0 | |
| for idx, line in enumerate(lines): | |
| if line.strip().startswith('import '): | |
| insert_idx = idx + 1 | |
| lines.insert(insert_idx, material_import) |
Copilot uses AI. Check for mistakes.
Copilot
AI
Sep 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The file is read twice - once in the main loop and again in add_material_import(). This is inefficient and could be optimized by passing the already-read text to the add_material_import() function.
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main function currently reads each file to check if it needs a material import, and then add_material_import reads the same file again. This is inefficient as it results in reading each file twice. You can refactor this to read each file only once. The add_material_import function already contains the necessary logic to check and add the import, and it handles errors gracefully. Simplifying the main loop to just call add_material_import will make the code more efficient and less redundant.
def main():
root = os.path.join(os.path.dirname(__file__), 'lib')
changed = 0
for dirpath, _, filenames in os.walk(root):
for fn in filenames:
if not fn.endswith('.dart'):
continue
path = os.path.join(dirpath, fn)
# By calling add_material_import directly, we avoid reading the file twice.
# The function itself handles the check and potential modification.
if add_material_import(path):
changed += 1
print(f"Material import added to {changed} files")
return 0| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,56 +1,103 @@ | ||||||||||||||||||||||||||||||||||||||
| #!/usr/bin/env python3 | ||||||||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||||||||
| import re | ||||||||||||||||||||||||||||||||||||||
| import sys | ||||||||||||||||||||||||||||||||||||||
| import argparse | ||||||||||||||||||||||||||||||||||||||
| import subprocess | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| def fix_unused_imports(): | ||||||||||||||||||||||||||||||||||||||
| """Remove unused imports based on flutter analyze output""" | ||||||||||||||||||||||||||||||||||||||
| # Get unused imports from flutter analyze | ||||||||||||||||||||||||||||||||||||||
| result = subprocess.run(['flutter', 'analyze'], capture_output=True, text=True, cwd='.') | ||||||||||||||||||||||||||||||||||||||
| output = result.stderr | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| unused_imports = [] | ||||||||||||||||||||||||||||||||||||||
| for line in output.split('\n'): | ||||||||||||||||||||||||||||||||||||||
| if 'unused_import' in line: | ||||||||||||||||||||||||||||||||||||||
| # Parse the line to extract file and import | ||||||||||||||||||||||||||||||||||||||
| match = re.search(r"Unused import: '([^']+)' • ([^:]+):(\d+):(\d+)", line) | ||||||||||||||||||||||||||||||||||||||
| if match: | ||||||||||||||||||||||||||||||||||||||
| import_name = match.group(1) | ||||||||||||||||||||||||||||||||||||||
| file_path = match.group(2) | ||||||||||||||||||||||||||||||||||||||
| line_num = int(match.group(3)) | ||||||||||||||||||||||||||||||||||||||
| unused_imports.append((file_path, import_name, line_num)) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| fixed_count = 0 | ||||||||||||||||||||||||||||||||||||||
| for file_path, import_name, line_num in unused_imports: | ||||||||||||||||||||||||||||||||||||||
| if os.path.exists(file_path): | ||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||
| with open(file_path, 'r', encoding='utf-8') as f: | ||||||||||||||||||||||||||||||||||||||
| lines = f.readlines() | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # Find and remove the import line | ||||||||||||||||||||||||||||||||||||||
| for i, line in enumerate(lines): | ||||||||||||||||||||||||||||||||||||||
| # Check if this line contains the import | ||||||||||||||||||||||||||||||||||||||
| if f"import '{import_name}'" in line or f'import "{import_name}"' in line: | ||||||||||||||||||||||||||||||||||||||
| # Remove the line | ||||||||||||||||||||||||||||||||||||||
| lines.pop(i) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # Write back the file | ||||||||||||||||||||||||||||||||||||||
| with open(file_path, 'w', encoding='utf-8') as f: | ||||||||||||||||||||||||||||||||||||||
| f.writelines(lines) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| print(f"Removed unused import '{import_name}' from {file_path}") | ||||||||||||||||||||||||||||||||||||||
| fixed_count += 1 | ||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||||||
| print(f"Error fixing {file_path}: {e}") | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return fixed_count | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| def main(): | ||||||||||||||||||||||||||||||||||||||
| print("Fixing unused imports...") | ||||||||||||||||||||||||||||||||||||||
| fixed_count = fix_unused_imports() | ||||||||||||||||||||||||||||||||||||||
| print(f"Fixed {fixed_count} unused imports") | ||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||
| Remove unused imports based on Flutter analyzer output. | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Usage: | ||||||||||||||||||||||||||||||||||||||
| python3 fix_unused_imports.py [--from-file local-artifacts/flutter-analyze.txt] [--dry-run] | ||||||||||||||||||||||||||||||||||||||
| python3 fix_unused_imports.py # runs `flutter analyze` and parses stdout | ||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| UNUSED_IMPORT_PATTERNS = [ | ||||||||||||||||||||||||||||||||||||||
| # Format: lib/file.dart:10:1 • Unused import: 'package:foo/bar.dart' • unused_import | ||||||||||||||||||||||||||||||||||||||
| re.compile(r"^(?P<file>[^:]+):(?P<line>\d+):(?P<col>\d+)\s+•\s+Unused import: '(?P<import>[^']+)'\s+•\s+unused_import\b"), | ||||||||||||||||||||||||||||||||||||||
| # Format: Unused import: 'package:foo/bar.dart' • lib/file.dart:10:1 • unused_import | ||||||||||||||||||||||||||||||||||||||
| re.compile(r"^Unused import: '(?P<import>[^']+)'\s+•\s+(?P<file>[^:]+):(?P<line>\d+):(?P<col>\d+)\s+•\s+unused_import\b"), | ||||||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| def parse_analyzer_output(text: str): | ||||||||||||||||||||||||||||||||||||||
| results = [] # list of tuples (file, import) | ||||||||||||||||||||||||||||||||||||||
| for raw in text.splitlines(): | ||||||||||||||||||||||||||||||||||||||
| if 'unused_import' not in raw and 'Unused import:' not in raw: | ||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||
| for pat in UNUSED_IMPORT_PATTERNS: | ||||||||||||||||||||||||||||||||||||||
| m = pat.search(raw) | ||||||||||||||||||||||||||||||||||||||
| if m: | ||||||||||||||||||||||||||||||||||||||
| file_path = m.group('file').strip() | ||||||||||||||||||||||||||||||||||||||
| imp = m.group('import').strip() | ||||||||||||||||||||||||||||||||||||||
| results.append((file_path, imp)) | ||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||
| return results | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| def remove_import_line(file_path: str, import_name: str, dry_run: bool = False) -> bool: | ||||||||||||||||||||||||||||||||||||||
| if not os.path.exists(file_path): | ||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||
| with open(file_path, 'r', encoding='utf-8') as f: | ||||||||||||||||||||||||||||||||||||||
| lines = f.readlines() | ||||||||||||||||||||||||||||||||||||||
| changed = False | ||||||||||||||||||||||||||||||||||||||
| new_lines = [] | ||||||||||||||||||||||||||||||||||||||
| for line in lines: | ||||||||||||||||||||||||||||||||||||||
| if line.strip().startswith('import '): | ||||||||||||||||||||||||||||||||||||||
| # match single or double quote | ||||||||||||||||||||||||||||||||||||||
| if (f"import '{import_name}'" in line) or (f'import "{import_name}"' in line): | ||||||||||||||||||||||||||||||||||||||
| changed = True | ||||||||||||||||||||||||||||||||||||||
| continue # skip this line | ||||||||||||||||||||||||||||||||||||||
| new_lines.append(line) | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+45
to
+53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current method of checking for the import statement using string containment (
Suggested change
|
||||||||||||||||||||||||||||||||||||||
| if changed and not dry_run: | ||||||||||||||||||||||||||||||||||||||
| with open(file_path, 'w', encoding='utf-8') as f: | ||||||||||||||||||||||||||||||||||||||
| f.writelines(new_lines) | ||||||||||||||||||||||||||||||||||||||
| return changed | ||||||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||||||
| print(f"Error fixing {file_path}: {e}") | ||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| def run(): | ||||||||||||||||||||||||||||||||||||||
| parser = argparse.ArgumentParser() | ||||||||||||||||||||||||||||||||||||||
| parser.add_argument('--from-file', help='Parse analyzer output from a file instead of running flutter analyze') | ||||||||||||||||||||||||||||||||||||||
| parser.add_argument('--dry-run', action='store_true') | ||||||||||||||||||||||||||||||||||||||
| args = parser.parse_args() | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if args.from_file and os.path.exists(args.from_file): | ||||||||||||||||||||||||||||||||||||||
| with open(args.from_file, 'r', encoding='utf-8') as f: | ||||||||||||||||||||||||||||||||||||||
| output = f.read() | ||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||
| # Fallback: run flutter analyze and capture stdout | ||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||
| proc = subprocess.run(['flutter', 'analyze'], cwd='.', text=True, capture_output=True, check=False) | ||||||||||||||||||||||||||||||||||||||
| output = proc.stdout or '' | ||||||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||||||
| print(f"Failed to run flutter analyze: {e}") | ||||||||||||||||||||||||||||||||||||||
| return 1 | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| findings = parse_analyzer_output(output) | ||||||||||||||||||||||||||||||||||||||
| if not findings: | ||||||||||||||||||||||||||||||||||||||
| print('No unused imports found in analyzer output.') | ||||||||||||||||||||||||||||||||||||||
| return 0 | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # de-duplicate by (file, import) | ||||||||||||||||||||||||||||||||||||||
| seen = set() | ||||||||||||||||||||||||||||||||||||||
| fixed = 0 | ||||||||||||||||||||||||||||||||||||||
| for file_path, imp in findings: | ||||||||||||||||||||||||||||||||||||||
| key = (file_path, imp) | ||||||||||||||||||||||||||||||||||||||
| if key in seen: | ||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||
| seen.add(key) | ||||||||||||||||||||||||||||||||||||||
| if remove_import_line(file_path, imp, dry_run=args.dry_run): | ||||||||||||||||||||||||||||||||||||||
| print(f"Removed unused import '{imp}' from {file_path}") | ||||||||||||||||||||||||||||||||||||||
| fixed += 1 | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| print(f"Fixed {fixed} unused imports") | ||||||||||||||||||||||||||||||||||||||
| return 0 | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if __name__ == '__main__': | ||||||||||||||||||||||||||||||||||||||
| main() | ||||||||||||||||||||||||||||||||||||||
| sys.exit(run()) | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The simple string search for 'package:flutter/material.dart' could match commented-out imports or imports within string literals. Consider using a more precise regex pattern to match actual import statements.
Copilot uses AI. Check for mistakes.