1#!/usr/bin/env python3 2 3# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7""" 8Fix some flake8 errors by rewriting files for which flake8 emitted 9an error/warning. Usage (from the root dir): 10 11 $ python3 -m flake8 --exit-zero | python3 scripts/fix_flake8.py 12""" 13 14import sys 15import tempfile 16import shutil 17from collections import defaultdict 18from collections import namedtuple 19from pprint import pprint as pp # NOQA 20 21 22ntentry = namedtuple('ntentry', 'msg, issue, lineno, pos, descr') 23 24 25# ===================================================================== 26# utils 27# ===================================================================== 28 29 30def enter_pdb(): 31 from pdb import set_trace as st # trick GIT commit hook 32 sys.stdin = open('/dev/tty') 33 st() 34 35 36def read_lines(fname): 37 with open(fname, 'rt') as f: 38 return f.readlines() 39 40 41def read_line(fname, lineno): 42 with open(fname, 'rt') as f: 43 for i, line in enumerate(f, 1): 44 if i == lineno: 45 return line 46 raise ValueError("lineno too big") 47 48 49def write_file(fname, newlines): 50 with tempfile.NamedTemporaryFile('wt', delete=False) as f: 51 for line in newlines: 52 f.write(line) 53 shutil.move(f.name, fname) 54 55 56# ===================================================================== 57# handlers 58# ===================================================================== 59 60 61def handle_f401(fname, lineno): 62 """ This is 'module imported but not used' 63 Able to handle this case: 64 import os 65 66 ...but not this: 67 from os import listdir 68 """ 69 line = read_line(fname, lineno).strip() 70 if line.startswith('import '): 71 return True 72 else: 73 mods = line.partition(' import ')[2] # everything after import 74 return ',' not in mods 75 76 77# ===================================================================== 78# converters 79# ===================================================================== 80 81 82def remove_lines(fname, entries): 83 """Check if we should remove lines, then do it. 84 Return the numner of lines removed. 85 """ 86 to_remove = [] 87 for entry in entries: 88 msg, issue, lineno, pos, descr = entry 89 # 'module imported but not used' 90 if issue == 'F401' and handle_f401(fname, lineno): 91 to_remove.append(lineno) 92 # 'blank line(s) at end of file' 93 elif issue == 'W391': 94 lines = read_lines(fname) 95 i = len(lines) - 1 96 while lines[i] == '\n': 97 to_remove.append(i + 1) 98 i -= 1 99 # 'too many blank lines' 100 elif issue == 'E303': 101 howmany = descr.replace('(', '').replace(')', '') 102 howmany = int(howmany[-1]) 103 for x in range(lineno - howmany, lineno): 104 to_remove.append(x) 105 106 if to_remove: 107 newlines = [] 108 for i, line in enumerate(read_lines(fname), 1): 109 if i not in to_remove: 110 newlines.append(line) 111 print("removing line(s) from %s" % fname) 112 write_file(fname, newlines) 113 114 return len(to_remove) 115 116 117def add_lines(fname, entries): 118 """Check if we should remove lines, then do it. 119 Return the numner of lines removed. 120 """ 121 EXPECTED_BLANK_LINES = ( 122 'E302', # 'expected 2 blank limes, found 1' 123 'E305') # ìexpected 2 blank lines after class or fun definition' 124 to_add = {} 125 for entry in entries: 126 msg, issue, lineno, pos, descr = entry 127 if issue in EXPECTED_BLANK_LINES: 128 howmany = 2 if descr.endswith('0') else 1 129 to_add[lineno] = howmany 130 131 if to_add: 132 newlines = [] 133 for i, line in enumerate(read_lines(fname), 1): 134 if i in to_add: 135 newlines.append('\n' * to_add[i]) 136 newlines.append(line) 137 print("adding line(s) to %s" % fname) 138 write_file(fname, newlines) 139 140 return len(to_add) 141 142 143# ===================================================================== 144# main 145# ===================================================================== 146 147 148def build_table(): 149 table = defaultdict(list) 150 for line in sys.stdin: 151 line = line.strip() 152 if not line: 153 break 154 fields = line.split(':') 155 fname, lineno, pos = fields[:3] 156 issue = fields[3].split()[0] 157 descr = fields[3].strip().partition(' ')[2] 158 lineno, pos = int(lineno), int(pos) 159 table[fname].append(ntentry(line, issue, lineno, pos, descr)) 160 return table 161 162 163def main(): 164 table = build_table() 165 166 # remove lines (unused imports) 167 removed = 0 168 for fname, entries in table.items(): 169 removed += remove_lines(fname, entries) 170 if removed: 171 print("%s lines were removed from some file(s); please re-run" % 172 removed) 173 return 174 175 # add lines (missing \n between functions/classes) 176 added = 0 177 for fname, entries in table.items(): 178 added += add_lines(fname, entries) 179 if added: 180 print("%s lines were added from some file(s); please re-run" % 181 added) 182 return 183 184 185main() 186