]> git.lizzy.rs Git - rust.git/blob - util/update_lints.py
update_lints.py: port another print to print() for python3 compatibility.
[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
5 # changed.
6
7 import os
8 import re
9 import sys
10
11 declare_deprecated_lint_re = re.compile(r'''
12     declare_deprecated_lint! \s* [{(] \s*
13     pub \s+ (?P<name>[A-Z_][A-Z_0-9]*) \s*,\s*
14     " (?P<desc>(?:[^"\\]+|\\.)*) " \s* [})]
15 ''', re.VERBOSE | re.DOTALL)
16
17 declare_clippy_lint_re = re.compile(r'''
18     declare_clippy_lint! \s* [{(] \s*
19     pub \s+ (?P<name>[A-Z_][A-Z_0-9]*) \s*,\s*
20     (?P<cat>[a-z_]+) \s*,\s*
21     " (?P<desc>(?:[^"\\]+|\\.)*) " \s* [})]
22 ''', re.VERBOSE | re.DOTALL)
23
24 nl_escape_re = re.compile(r'\\\n\s*')
25
26 docs_link = 'https://rust-lang-nursery.github.io/rust-clippy/master/index.html'
27
28
29 def collect(deprecated_lints, clippy_lints, fn):
30     """Collect all lints from a file.
31
32     Adds entries to the lints list as `(module, name, level, desc)`.
33     """
34     with open(fn) as fp:
35         code = fp.read()
36
37     for match in declare_deprecated_lint_re.finditer(code):
38         # remove \-newline escapes from description string
39         desc = nl_escape_re.sub('', match.group('desc'))
40         deprecated_lints.append((os.path.splitext(os.path.basename(fn))[0],
41                                 match.group('name').lower(),
42                                 desc.replace('\\"', '"')))
43
44     for match in declare_clippy_lint_re.finditer(code):
45         # remove \-newline escapes from description string
46         desc = nl_escape_re.sub('', match.group('desc'))
47         cat = match.group('cat')
48         clippy_lints[cat].append((os.path.splitext(os.path.basename(fn))[0],
49                                   match.group('name').lower(),
50                                   "allow",
51                                   desc.replace('\\"', '"')))
52
53
54 def gen_group(lints):
55     """Write lint group (list of all lints in the form module::NAME)."""
56     for (module, name, _, _) in sorted(lints):
57         yield '        %s::%s,\n' % (module, name.upper())
58
59
60 def gen_mods(lints):
61     """Declare modules"""
62
63     for module in sorted(set(lint[0] for lint in lints)):
64         yield 'pub mod %s;\n' % module
65
66
67 def gen_deprecated(lints):
68     """Declare deprecated lints"""
69
70     for lint in lints:
71         yield '    store.register_removed(\n'
72         yield '        "%s",\n' % lint[1]
73         yield '        "%s",\n' % lint[2]
74         yield '    );\n'
75
76
77 def replace_region(fn, region_start, region_end, callback,
78                    replace_start=True, write_back=True):
79     """Replace a region in a file delimited by two lines matching regexes.
80
81     A callback is called to write the new region.  If `replace_start` is true,
82     the start delimiter line is replaced as well.  The end delimiter line is
83     never replaced.
84     """
85     # read current content
86     with open(fn) as fp:
87         lines = list(fp)
88
89     found = False
90
91     # replace old region with new region
92     new_lines = []
93     in_old_region = False
94     for line in lines:
95         if in_old_region:
96             if re.search(region_end, line):
97                 in_old_region = False
98                 new_lines.extend(callback())
99                 new_lines.append(line)
100         elif re.search(region_start, line):
101             if not replace_start:
102                 new_lines.append(line)
103             # old region starts here
104             in_old_region = True
105             found = True
106         else:
107             new_lines.append(line)
108
109     if not found:
110         print("regex " + region_start + " not found")
111
112     # write back to file
113     if write_back:
114         with open(fn, 'w') as fp:
115             fp.writelines(new_lines)
116
117     # if something changed, return true
118     return lines != new_lines
119
120
121 def main(print_only=False, check=False):
122     deprecated_lints = []
123     clippy_lints = {
124         "correctness": [],
125         "style": [],
126         "complexity": [],
127         "perf": [],
128         "restriction": [],
129         "pedantic": [],
130         "cargo": [],
131         "nursery": [],
132     }
133
134     # check directory
135     if not os.path.isfile('clippy_lints/src/lib.rs'):
136         print('Error: call this script from clippy checkout directory!')
137         return
138
139     # collect all lints from source files
140     for fn in os.listdir('clippy_lints/src'):
141         if fn.endswith('.rs'):
142             collect(deprecated_lints, clippy_lints,
143                     os.path.join('clippy_lints', 'src', fn))
144
145     # determine version
146     with open('Cargo.toml') as fp:
147         for line in fp:
148             if line.startswith('version ='):
149                 clippy_version = line.split()[2].strip('"')
150                 break
151         else:
152             print('Error: version not found in Cargo.toml!')
153             return
154
155     all_lints = []
156     clippy_lint_groups = [
157         "correctness",
158         "style",
159         "complexity",
160         "perf",
161     ]
162     clippy_lint_list = []
163     for x in clippy_lint_groups:
164         clippy_lint_list += clippy_lints[x]
165     for _, value in clippy_lints.iteritems():
166         all_lints += value
167
168     if print_only:
169         print_clippy_lint_groups = [
170             "correctness",
171             "style",
172             "complexity",
173             "perf",
174             "pedantic",
175             "nursery",
176             "restriction"
177         ]
178         for group in print_clippy_lint_groups:
179             sys.stdout.write('\n## ' + group + '\n')
180             for (_, name, _, descr) in sorted(clippy_lints[group]):
181                 sys.stdout.write('* [' + name + '](https://rust-lang-nursery.github.io/rust-clippy/master/index.html#' + name + ') (' + descr + ')\n')
182         return
183
184     # update the lint counter in README.md
185     changed = replace_region(
186         'README.md',
187         r'^\[There are \d+ lints included in this crate!\]\(https://rust-lang-nursery.github.io/rust-clippy/master/index.html\)$', "",
188         lambda: ['[There are %d lints included in this crate!](https://rust-lang-nursery.github.io/rust-clippy/master/index.html)\n' %
189                  (len(all_lints))],
190         write_back=not check)
191
192     # update the links in the CHANGELOG
193     changed |= replace_region(
194         'CHANGELOG.md',
195         "<!-- begin autogenerated links to lint list -->",
196         "<!-- end autogenerated links to lint list -->",
197         lambda: ["[`{0}`]: {1}#{0}\n".format(l[1], docs_link) for l in
198                  sorted(all_lints + deprecated_lints,
199                         key=lambda l: l[1])],
200         replace_start=False, write_back=not check)
201
202     # update version of clippy_lints in Cargo.toml
203     changed |= replace_region(
204         'Cargo.toml', r'# begin automatic update', '# end automatic update',
205         lambda: ['clippy_lints = { version = "%s", path = "clippy_lints" }\n' %
206                  clippy_version],
207         replace_start=False, write_back=not check)
208
209     # update version of clippy_lints in Cargo.toml
210     changed |= replace_region(
211         'clippy_lints/Cargo.toml', r'# begin automatic update', '# end automatic update',
212         lambda: ['version = "%s"\n' % clippy_version],
213         replace_start=False, write_back=not check)
214
215     # update the `pub mod` list
216     changed |= replace_region(
217         'clippy_lints/src/lib.rs', r'begin lints modules', r'end lints modules',
218         lambda: gen_mods(all_lints),
219         replace_start=False, write_back=not check)
220
221     # same for "clippy_*" lint collections
222     changed |= replace_region(
223         'clippy_lints/src/lib.rs', r'reg.register_lint_group\("clippy"', r'\]\);',
224         lambda: gen_group(clippy_lint_list),
225         replace_start=False, write_back=not check)
226
227     for key, value in clippy_lints.iteritems():
228         # same for "clippy_*" lint collections
229         changed |= replace_region(
230             'clippy_lints/src/lib.rs', r'reg.register_lint_group\("clippy_' + key + r'"', r'\]\);',
231             lambda: gen_group(value),
232             replace_start=False, write_back=not check)
233
234     # same for "deprecated" lint collection
235     changed |= replace_region(
236         'clippy_lints/src/lib.rs', r'let mut store', r'end deprecated lints',
237         lambda: gen_deprecated(deprecated_lints),
238         replace_start=False,
239         write_back=not check)
240
241     if check and changed:
242         print('Please run util/update_lints.py to regenerate lints lists.')
243         return 1
244
245
246 if __name__ == '__main__':
247     sys.exit(main(print_only='-n' in sys.argv, check='-c' in sys.argv))