#!/usr/bin/env python # Generate a Markdown table of all lints, and put it in README.md. # With -n option, only print the new table to stdout. # With -c option, print a warning and set exit status to 1 if a file would be # changed. import os import re import sys from subprocess import call declare_deprecated_lint_re = re.compile(r''' declare_deprecated_lint! \s* [{(] \s* pub \s+ (?P[A-Z_][A-Z_0-9]*) \s*,\s* " (?P(?:[^"\\]+|\\.)*) " \s* [})] ''', re.VERBOSE | re.DOTALL) declare_clippy_lint_re = re.compile(r''' declare_clippy_lint! \s* [{(] \s* pub \s+ (?P[A-Z_][A-Z_0-9]*) \s*,\s* (?P[a-z_]+) \s*,\s* " (?P(?:[^"\\]+|\\.)*) " \s* [})] ''', re.VERBOSE | re.DOTALL) nl_escape_re = re.compile(r'\\\n\s*') docs_link = 'https://rust-lang-nursery.github.io/rust-clippy/master/index.html' def collect(deprecated_lints, clippy_lints, fn): """Collect all lints from a file. Adds entries to the lints list as `(module, name, level, desc)`. """ with open(fn) as fp: code = fp.read() for match in declare_deprecated_lint_re.finditer(code): # remove \-newline escapes from description string desc = nl_escape_re.sub('', match.group('desc')) deprecated_lints.append((os.path.splitext(os.path.basename(fn))[0], match.group('name').lower(), desc.replace('\\"', '"'))) for match in declare_clippy_lint_re.finditer(code): # remove \-newline escapes from description string desc = nl_escape_re.sub('', match.group('desc')) cat = match.group('cat') if cat in ('internal', 'internal_warn'): continue module_name = os.path.splitext(os.path.basename(fn))[0] if module_name == 'mod': module_name = os.path.basename(os.path.dirname(fn)) clippy_lints[cat].append((module_name, match.group('name').lower(), "allow", desc.replace('\\"', '"'))) def gen_group(lints): """Write lint group (list of all lints in the form module::NAME).""" for (module, name, _, _) in sorted(lints): yield ' %s::%s,\n' % (module, name.upper()) def gen_mods(lints): """Declare modules""" for module in sorted(set(lint[0] for lint in lints)): yield 'pub mod %s;\n' % module def gen_deprecated(lints): """Declare deprecated lints""" for lint in lints: yield ' store.register_removed(\n' yield ' "%s",\n' % lint[1] yield ' "%s",\n' % lint[2] yield ' );\n' def replace_region(fn, region_start, region_end, callback, replace_start=True, write_back=True): """Replace a region in a file delimited by two lines matching regexes. A callback is called to write the new region. If `replace_start` is true, the start delimiter line is replaced as well. The end delimiter line is never replaced. """ # read current content with open(fn) as fp: lines = list(fp) found = False # replace old region with new region new_lines = [] in_old_region = False for line in lines: if in_old_region: if re.search(region_end, line): in_old_region = False new_lines.extend(callback()) new_lines.append(line) elif re.search(region_start, line): if not replace_start: new_lines.append(line) # old region starts here in_old_region = True found = True else: new_lines.append(line) if not found: print("regex " + region_start + " not found") # write back to file if write_back: with open(fn, 'w') as fp: fp.writelines(new_lines) # if something changed, return true return lines != new_lines def main(print_only=False, check=False): deprecated_lints = [] clippy_lints = { "correctness": [], "style": [], "complexity": [], "perf": [], "restriction": [], "pedantic": [], "cargo": [], "nursery": [], } # check directory if not os.path.isfile('clippy_lints/src/lib.rs'): print('Error: call this script from clippy checkout directory!') return # collect all lints from source files for root, dirs, files in os.walk('clippy_lints/src'): for fn in files: if fn.endswith('.rs'): collect(deprecated_lints, clippy_lints, os.path.join(root, fn)) # determine version with open('Cargo.toml') as fp: for line in fp: if line.startswith('version ='): clippy_version = line.split()[2].strip('"') break else: print('Error: version not found in Cargo.toml!') return all_lints = [] clippy_lint_groups = [ "correctness", "style", "complexity", "perf", ] clippy_lint_list = [] for x in clippy_lint_groups: clippy_lint_list += clippy_lints[x] for _, value in clippy_lints.iteritems(): all_lints += value if print_only: call(["./util/dev", "update_lints", "--print-only"]) return # update the lint counter in README.md changed = replace_region( 'README.md', r'^\[There are \d+ lints included in this crate!\]\(https://rust-lang-nursery.github.io/rust-clippy/master/index.html\)$', "", lambda: ['[There are %d lints included in this crate!](https://rust-lang-nursery.github.io/rust-clippy/master/index.html)\n' % (len(all_lints))], write_back=not check) # update the links in the CHANGELOG changed |= replace_region( 'CHANGELOG.md', "", "", lambda: ["[`{0}`]: {1}#{0}\n".format(l[1], docs_link) for l in sorted(all_lints + deprecated_lints, key=lambda l: l[1])], replace_start=False, write_back=not check) # update version of clippy_lints in Cargo.toml changed |= replace_region( 'Cargo.toml', r'# begin automatic update', '# end automatic update', lambda: ['clippy_lints = { version = "%s", path = "clippy_lints" }\n' % clippy_version], replace_start=False, write_back=not check) # update version of clippy_lints in Cargo.toml changed |= replace_region( 'clippy_lints/Cargo.toml', r'# begin automatic update', '# end automatic update', lambda: ['version = "%s"\n' % clippy_version], replace_start=False, write_back=not check) # update the `pub mod` list changed |= replace_region( 'clippy_lints/src/lib.rs', r'begin lints modules', r'end lints modules', lambda: gen_mods(all_lints), replace_start=False, write_back=not check) # same for "clippy::*" lint collections changed |= replace_region( 'clippy_lints/src/lib.rs', r'reg.register_lint_group\("clippy::all"', r'\]\);', lambda: gen_group(clippy_lint_list), replace_start=False, write_back=not check) for key, value in clippy_lints.iteritems(): # same for "clippy::*" lint collections changed |= replace_region( 'clippy_lints/src/lib.rs', r'reg.register_lint_group\("clippy::' + key + r'"', r'\]\);', lambda: gen_group(value), replace_start=False, write_back=not check) # same for "deprecated" lint collection changed |= replace_region( 'clippy_lints/src/lib.rs', r'let mut store', r'end deprecated lints', lambda: gen_deprecated(deprecated_lints), replace_start=False, write_back=not check) if check and changed: print('Please run util/update_lints.py to regenerate lints lists.') return 1 if __name__ == '__main__': sys.exit(main(print_only='-n' in sys.argv, check='-c' in sys.argv))