From bb71842fce1b90ecd9a7cf9e65e739d0e11c5333 Mon Sep 17 00:00:00 2001 From: zensgit <77236085+zensgit@users.noreply.github.com> Date: Fri, 19 Sep 2025 17:13:08 +0800 Subject: [PATCH] chore(flutter): analyzer cleanup tools (unused imports parser + material import fixer) --- jive-flutter/fix_missing_material_imports.py | 67 +++++++++ jive-flutter/fix_unused_imports.py | 145 ++++++++++++------- 2 files changed, 163 insertions(+), 49 deletions(-) create mode 100644 jive-flutter/fix_missing_material_imports.py diff --git a/jive-flutter/fix_missing_material_imports.py b/jive-flutter/fix_missing_material_imports.py new file mode 100644 index 00000000..7ffc7f79 --- /dev/null +++ b/jive-flutter/fix_missing_material_imports.py @@ -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 + +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") + with open(path, 'w', encoding='utf-8') as f: + f.writelines(lines) + print(f"Added material import to {path}") + return True + except Exception as e: + print(f"Error processing {path}: {e}") + return False + +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) + try: + with open(path, 'r', encoding='utf-8') as f: + text = f.read() + if file_needs_material(text): + if add_material_import(path): + changed += 1 + except Exception as e: + print(f"Skip {path}: {e}") + continue + print(f"Material import added to {changed} files") + return 0 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/jive-flutter/fix_unused_imports.py b/jive-flutter/fix_unused_imports.py index ecbb0aca..f1aa0b7e 100644 --- a/jive-flutter/fix_unused_imports.py +++ b/jive-flutter/fix_unused_imports.py @@ -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[^:]+):(?P\d+):(?P\d+)\s+•\s+Unused import: '(?P[^']+)'\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[^']+)'\s+•\s+(?P[^:]+):(?P\d+):(?P\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) + 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() \ No newline at end of file + sys.exit(run())