Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions jive-flutter/fix_missing_material_imports.py
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:
Comment on lines +18 to +19
Copy link

Copilot AI Sep 19, 2025

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.

Suggested change
def file_needs_material(text: str) -> bool:
if 'package:flutter/material.dart' in text:
MATERIAL_IMPORT_RE = re.compile(
r"^\s*import\s+['\"]package:flutter/material\.dart['\"]", re.MULTILINE
)
def file_needs_material(text: str) -> bool:
if MATERIAL_IMPORT_RE.search(text):

Copilot uses AI. Check for mistakes.

return False
for pat in NEEDLES:
if re.search(pat, text):
return True
return False
Comment on lines +14 to +24

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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 NEEDLES as a list of compiled regex objects and then use the .search() method directly on them. This avoids compiling the same pattern repeatedly inside the file_needs_material function's loop.

Suggested change
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
NEEDLES = [
re.compile(r"\bIcons\."), re.compile(r"\bColors\."), re.compile(r"\bElevatedButton\b"),
re.compile(r"\bOutlinedButton\b"), re.compile(r"\bTextButton\b"),
]
def file_needs_material(text: str) -> bool:
if 'package:flutter/material.dart' in text:
return False
for pat in NEEDLES:
if pat.search(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")
Comment on lines +33 to +37
Copy link

Copilot AI Sep 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import insertion logic will place the material import after all existing imports, but it should be inserted in the correct alphabetical order among other package imports to follow Dart conventions. Consider sorting imports or finding the appropriate position among package imports.

Suggested change
# 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.

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
Comment on lines +54 to +59
Copy link

Copilot AI Sep 19, 2025

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.

except Exception as e:
print(f"Skip {path}: {e}")
continue
print(f"Material import added to {changed} files")
return 0
Comment on lines +46 to +64

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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


if __name__ == '__main__':
sys.exit(main())
145 changes: 96 additions & 49 deletions jive-flutter/fix_unused_imports.py
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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current method of checking for the import statement using string containment (in) and startswith is brittle. It might fail with slight variations in whitespace (e.g., import '...') and is less clear about what it's matching. Using a single, more robust regular expression compiled once before the loop would be more efficient and reliable.

Suggested change
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)
changed = False
new_lines = []
# Using a regex is more robust against whitespace and quote variations.
import_pattern = re.compile(r"^\s*import\s+(['\"])%s\1" % re.escape(import_name))
for line in lines:
if import_pattern.match(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()
sys.exit(run())