summaryrefslogtreecommitdiff
path: root/scripts/internal/fix_flake8.py
blob: 44da4a32746428a8f6131c56611a6dc6acdb58a6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#!/usr/bin/env python3

# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""
Fix some flake8 errors by rewriting files for which flake8 emitted
an error/warning. Usage (from the root dir):

    $ python3 -m flake8 --exit-zero | python3 scripts/fix_flake8.py
"""

import shutil
import sys
import tempfile
from collections import defaultdict
from collections import namedtuple
from pprint import pprint as pp  # NOQA


ntentry = namedtuple('ntentry', 'msg, issue, lineno, pos, descr')


# =====================================================================
# utils
# =====================================================================


def enter_pdb():
    from pdb import set_trace as st  # trick GIT commit hook
    sys.stdin = open('/dev/tty')
    st()


def read_lines(fname):
    with open(fname, 'rt') as f:
        return f.readlines()


def read_line(fname, lineno):
    with open(fname, 'rt') as f:
        for i, line in enumerate(f, 1):
            if i == lineno:
                return line
    raise ValueError("lineno too big")


def write_file(fname, newlines):
    with tempfile.NamedTemporaryFile('wt', delete=False) as f:
        for line in newlines:
            f.write(line)
    shutil.move(f.name, fname)


# =====================================================================
# handlers
# =====================================================================


def handle_f401(fname, lineno):
    """ This is 'module imported but not used'
    Able to handle this case:
        import os

    ...but not this:
        from os import listdir
    """
    line = read_line(fname, lineno).strip()
    if line.startswith('import '):
        return True
    else:
        mods = line.partition(' import ')[2]  # everything after import
        return ',' not in mods


# =====================================================================
# converters
# =====================================================================


def remove_lines(fname, entries):
    """Check if we should remove lines, then do it.
    Return the number of lines removed.
    """
    to_remove = []
    for entry in entries:
        msg, issue, lineno, pos, descr = entry
        # 'module imported but not used'
        if issue == 'F401' and handle_f401(fname, lineno):
            to_remove.append(lineno)
        # 'blank line(s) at end of file'
        elif issue == 'W391':
            lines = read_lines(fname)
            i = len(lines) - 1
            while lines[i] == '\n':
                to_remove.append(i + 1)
                i -= 1
        # 'too many blank lines'
        elif issue == 'E303':
            howmany = descr.replace('(', '').replace(')', '')
            howmany = int(howmany[-1])
            for x in range(lineno - howmany, lineno):
                to_remove.append(x)

    if to_remove:
        newlines = []
        for i, line in enumerate(read_lines(fname), 1):
            if i not in to_remove:
                newlines.append(line)
        print("removing line(s) from %s" % fname)
        write_file(fname, newlines)

    return len(to_remove)


def add_lines(fname, entries):
    """Check if we should remove lines, then do it.
    Return the number of lines removed.
    """
    EXPECTED_BLANK_LINES = (
        'E302',  # 'expected 2 blank limes, found 1'
        'E305')  # ìexpected 2 blank lines after class or fun definition'
    to_add = {}
    for entry in entries:
        msg, issue, lineno, pos, descr = entry
        if issue in EXPECTED_BLANK_LINES:
            howmany = 2 if descr.endswith('0') else 1
            to_add[lineno] = howmany

    if to_add:
        newlines = []
        for i, line in enumerate(read_lines(fname), 1):
            if i in to_add:
                newlines.append('\n' * to_add[i])
            newlines.append(line)
        print("adding line(s) to %s" % fname)
        write_file(fname, newlines)

    return len(to_add)


# =====================================================================
# main
# =====================================================================


def build_table():
    table = defaultdict(list)
    for line in sys.stdin:
        line = line.strip()
        if not line:
            break
        fields = line.split(':')
        fname, lineno, pos = fields[:3]
        issue = fields[3].split()[0]
        descr = fields[3].strip().partition(' ')[2]
        lineno, pos = int(lineno), int(pos)
        table[fname].append(ntentry(line, issue, lineno, pos, descr))
    return table


def main():
    table = build_table()

    # remove lines (unused imports)
    removed = 0
    for fname, entries in table.items():
        removed += remove_lines(fname, entries)
    if removed:
        print("%s lines were removed from some file(s); please re-run" %
              removed)
        return

    # add lines (missing \n between functions/classes)
    added = 0
    for fname, entries in table.items():
        added += add_lines(fname, entries)
    if added:
        print("%s lines were added from some file(s); please re-run" %
              added)
        return


main()