]> git.lizzy.rs Git - rust.git/blob - util/update_lints.py
update_lints: add a check mode for travis runs
[rust.git] / util / update_lints.py
1 #!/usr/bin/env python
2 # Generate a Markdown table of all lints, and put it in README.md.
3 # With -n option, only print the new table to stdout.
4 # With -c option, print a warning and set exit status to 1 if a file would be changed.
5
6 import os
7 import re
8 import sys
9
10 declare_lint_re = re.compile(r'''
11     declare_lint! \s* [{(] \s*
12     pub \s+ (?P<name>[A-Z_]+) \s*,\s*
13     (?P<level>Forbid|Deny|Warn|Allow) \s*,\s*
14     " (?P<desc>(?:[^"\\]+|\\.)*) " \s* [})]
15 ''', re.X | re.S)
16
17 nl_escape_re = re.compile(r'\\\n\s*')
18
19
20 def collect(lints, fn):
21     """Collect all lints from a file.
22
23     Adds entries to the lints list as `(module, name, level, desc)`.
24     """
25     with open(fn) as fp:
26         code = fp.read()
27     for match in declare_lint_re.finditer(code):
28         # remove \-newline escapes from description string
29         desc = nl_escape_re.sub('', match.group('desc'))
30         lints.append((os.path.splitext(os.path.basename(fn))[0],
31                       match.group('name').lower(),
32                       match.group('level').lower(),
33                       desc.replace('\\"', '"')))
34
35
36 def gen_table(lints):
37     """Write lint table in Markdown format."""
38     # first and third column widths
39     w_name = max(len(l[1]) for l in lints)
40     w_desc = max(len(l[3]) for l in lints)
41     # header and underline
42     yield '%-*s | default | meaning\n' % (w_name, 'name')
43     yield '%s-|-%s-|-%s\n' % ('-' * w_name, '-' * 7, '-' * w_desc)
44     # one table row per lint
45     for (_, name, default, meaning) in sorted(lints, key=lambda l: l[1]):
46         yield '%-*s | %-7s | %s\n' % (w_name, name, default, meaning)
47
48
49 def gen_group(lints):
50     """Write lint group (list of all lints in the form module::NAME)."""
51     for (module, name, _, _) in sorted(lints):
52         yield '        %s::%s,\n' % (module, name.upper())
53
54
55 def replace_region(fn, region_start, region_end, callback,
56                    replace_start=True, write_back=True):
57     """Replace a region in a file delimited by two lines matching regexes.
58
59     A callback is called to write the new region.  If `replace_start` is true,
60     the start delimiter line is replaced as well.  The end delimiter line is
61     never replaced.
62     """
63     # read current content
64     with open(fn) as fp:
65         lines = list(fp)
66
67     # replace old region with new region
68     new_lines = []
69     in_old_region = False
70     for line in lines:
71         if in_old_region:
72             if re.search(region_end, line):
73                 in_old_region = False
74                 new_lines.extend(callback())
75                 new_lines.append(line)
76         elif re.search(region_start, line):
77             if not replace_start:
78                 new_lines.append(line)
79             # old region starts here
80             in_old_region = True
81         else:
82             new_lines.append(line)
83
84     # write back to file
85     if write_back:
86         with open(fn, 'w') as fp:
87             fp.writelines(new_lines)
88
89     # if something changed, return true
90     return lines != new_lines
91
92
93 def main(print_only=False, check=False):
94     lints = []
95
96     # check directory
97     if not os.path.isfile('src/lib.rs'):
98         print('Error: call this script from clippy checkout directory!')
99         return
100
101     # collect all lints from source files
102     for root, dirs, files in os.walk('src'):
103         for fn in files:
104             if fn.endswith('.rs'):
105                 collect(lints, os.path.join(root, fn))
106
107     if print_only:
108         sys.stdout.writelines(gen_table(lints))
109         return
110
111     # replace table in README.md
112     changed = replace_region('README.md', r'^name +\|', '^$',
113                              lambda: gen_table(lints),
114                              write_back=not check)
115
116     # same for "clippy" lint collection
117     changed |= replace_region('src/lib.rs', r'reg.register_lint_group\("clippy"', r'\]\);',
118                               lambda: gen_group(lints), replace_start=False,
119                               write_back=not check)
120
121     if check and changed:
122         print('Please run util/update_lints.py to regenerate lints lists.')
123         return 1
124
125
126 if __name__ == '__main__':
127     sys.exit(main(print_only='-n' in sys.argv, check='-c' in sys.argv))