]> git.lizzy.rs Git - rust.git/blob - util/lintlib.py
098fcf256a6e5db4a3eb70b5a6a87cda19059c6d
[rust.git] / util / lintlib.py
1 # Copyright 2014-2018 The Rust Project Developers. See the COPYRIGHT
2 # file at the top-level directory of this distribution and at
3 # http://rust-lang.org/COPYRIGHT.
4 #
5 # Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 # http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 # <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 # option. This file may not be copied, modified, or distributed
9 # except according to those terms.
10
11 # Common utils for the several housekeeping scripts.
12
13 import os
14 import re
15 import collections
16
17 import logging as log
18 log.basicConfig(level=log.INFO, format='%(levelname)s: %(message)s')
19
20 Lint = collections.namedtuple('Lint', 'name level doc sourcefile group')
21 Config = collections.namedtuple('Config', 'name ty doc default')
22
23 lintname_re = re.compile(r'''pub\s+([A-Z_][A-Z_0-9]*)''')
24 group_re = re.compile(r'''\s*([a-z_][a-z_0-9]+)''')
25 conf_re = re.compile(r'''define_Conf! {\n([^}]*)\n}''', re.MULTILINE)
26 confvar_re = re.compile(
27     r'''/// Lint: (\w+). (.*).*\n\s*\([^,]+,\s+"([^"]+)",\s+([^=\)]+)=>\s+(.*)\),''', re.MULTILINE)
28
29 lint_levels = {
30     "correctness": 'Deny',
31     "style": 'Warn',
32     "complexity": 'Warn',
33     "perf": 'Warn',
34     "restriction": 'Allow',
35     "pedantic": 'Allow',
36     "nursery": 'Allow',
37     "cargo": 'Allow',
38 }
39
40
41 def parse_lints(lints, filepath):
42     last_comment = []
43     comment = True
44     clippy = False
45     deprecated = False
46     name = ""
47
48     with open(filepath) as fp:
49         for line in fp:
50             if comment:
51                 if line.startswith("/// "):
52                     last_comment.append(line[4:])
53                 elif line.startswith("///"):
54                     last_comment.append(line[3:])
55                 elif line.startswith("declare_lint!"):
56                     import sys
57                     print("don't use `declare_lint!` in Clippy, use `declare_clippy_lint!` instead")
58                     sys.exit(42)
59                 elif line.startswith("declare_clippy_lint!"):
60                     comment = False
61                     deprecated = False
62                     clippy = True
63                     name = ""
64                 elif line.startswith("declare_deprecated_lint!"):
65                     comment = False
66                     deprecated = True
67                     clippy = False
68                 else:
69                     last_comment = []
70             if not comment:
71                 m = lintname_re.search(line)
72
73                 if m:
74                     name = m.group(1).lower()
75                     line = next(fp)
76
77                     if deprecated:
78                         level = "Deprecated"
79                         group = "deprecated"
80                     else:
81                         while True:
82                             g = group_re.search(line)
83                             if g:
84                                 group = g.group(1).lower()
85                                 level = lint_levels.get(group, None)
86                                 break
87                             line = next(fp)
88
89                     if level is None:
90                         continue
91
92                     log.info("found %s with level %s in %s",
93                              name, level, filepath)
94                     lints.append(Lint(name, level, last_comment, filepath, group))
95                     last_comment = []
96                     comment = True
97
98                     if "}" in line:
99                         log.warn("Warning: missing Lint-Name in %s", filepath)
100                         comment = True
101
102
103 def parse_configs(path):
104     configs = {}
105     with open(os.path.join(path, 'utils/conf.rs')) as fp:
106         contents = fp.read()
107
108     match = re.search(conf_re, contents)
109     confvars = re.findall(confvar_re, match.group(1))
110
111     for (lint, doc, name, default, ty) in confvars:
112         configs[lint.lower()] = Config(name.replace("_", "-"), ty, doc, default)
113
114     return configs
115
116
117 def parse_all(path="clippy_lints/src"):
118     lints = []
119     for root, dirs, files in os.walk(path):
120         for fn in files:
121             if fn.endswith('.rs'):
122                 parse_lints(lints, os.path.join(root, fn))
123
124     log.info("got %s lints", len(lints))
125
126     configs = parse_configs(path)
127     log.info("got %d configs", len(configs))
128
129     return lints, configs