1 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 # This module is not automatically loaded by the `cros` helper. The filename
6 # would need a "cros_" prefix to make that happen. It lives here so that it
7 # is alongside the cros_lint.py file.
9 # For msg namespaces, the 9xxx should generally be reserved for our own use.
11 """Additional lint modules loaded by pylint.
13 This is loaded by pylint directly via its pylintrc file:
14 load-plugins=chromite.cros.commands.lint
16 Then pylint will import the register function and call it. So we can have
17 as many/few checkers as we want in this one module.
20 from __future__ import print_function
25 from pylint.checkers import BaseChecker
26 from pylint.interfaces import IASTNGChecker
29 class DocStringChecker(BaseChecker):
30 """PyLint AST based checker to verify PEP 257 compliance
32 See our style guide for more info:
33 http://dev.chromium.org/chromium-os/python-style-guidelines#TOC-Describing-arguments-in-docstrings
36 # TODO: See about merging with the pep257 project:
37 # https://github.com/GreenSteam/pep257
39 __implements__ = IASTNGChecker
41 name = 'doc_string_checker'
43 MSG_ARGS = 'offset:%(offset)i: {%(line)s}'
45 'C9001': ('Modules should have docstrings (even a one liner)',
46 ('Used when a module lacks a docstring entirely')),
47 'C9002': ('Classes should have docstrings (even a one liner)',
48 ('Used when a class lacks a docstring entirely')),
49 'C9003': ('Trailing whitespace in docstring'
51 ('Used whenever we find trailing whitespace')),
52 'C9004': ('Leading whitespace in docstring (excess or missing)'
54 ('Used whenever we find incorrect leading whitespace')),
55 'C9005': ('Closing triple quotes should not be cuddled',
56 ('Used when the closing quotes are not by themselves')),
57 'C9006': ('Section names should be preceded by one blank line'
59 ('Used when we detect misbehavior around sections')),
60 'C9007': ('Section names should be "Args:", "Returns:", "Yields:", '
61 'and "Raises:": %s' % MSG_ARGS,
62 ('Used when we detect misbehavior around sections')),
63 'C9008': ('Sections should be in the order: Args, Returns/Yields, Raises',
64 ('Used when the various sections are misordered')),
65 'C9009': ('First line should be a short summary',
66 ('Used when a short doc string is on multiple lines')),
67 'C9010': ('Not all args mentioned in doc string: |%(arg)s|',
68 ('Used when not all arguments are in the doc string')),
69 'C9011': ('Variable args/keywords are named *args/**kwargs, not %(arg)s',
70 ('Used when funcs use different names for varargs')),
71 'C9012': ('Incorrectly formatted Args section: %(arg)s',
72 ('Used when spacing is incorrect after colon in Args')),
73 'C9013': ('Too many blank lines in a row: %s' % MSG_ARGS,
74 ('Used when more than one blank line is found')),
78 # TODO: Should we enforce Examples?
79 VALID_SECTIONS = ('Args', 'Returns', 'Yields', 'Raises',)
81 def visit_function(self, node):
82 """Verify function docstrings"""
84 lines = node.doc.split('\n')
85 self._check_common(node, lines)
86 self._check_last_line_function(node, lines)
87 self._check_section_lines(node, lines)
88 self._check_all_args_in_doc(node, lines)
89 self._check_func_signature(node)
91 # This is what C0111 already does for us, so ignore.
94 def visit_module(self, node):
95 """Verify module docstrings"""
97 self._check_common(node)
99 # Ignore stub __init__.py files.
100 if os.path.basename(node.file) == '__init__.py':
102 self.add_message('C9001', node=node)
104 def visit_class(self, node):
105 """Verify class docstrings"""
107 self._check_common(node)
109 self.add_message('C9002', node=node, line=node.fromlineno)
111 def _check_common(self, node, lines=None):
112 """Common checks we enforce on all docstrings"""
114 lines = node.doc.split('\n')
117 self._check_first_line,
118 self._check_whitespace,
119 self._check_last_line,
124 def _check_first_line(self, node, lines):
125 """Make sure first line is a short summary by itself"""
127 self.add_message('C9009', node=node, line=node.fromlineno)
129 def _check_whitespace(self, node, lines):
130 """Verify whitespace is sane"""
131 # Make sure first line doesn't have leading whitespace.
132 if lines[0].lstrip() != lines[0]:
133 margs = {'offset': 0, 'line': lines[0]}
134 self.add_message('C9004', node=node, line=node.fromlineno, args=margs)
136 # Verify no trailing whitespace.
137 # We skip the last line since it's supposed to be pure whitespace.
139 # Also check for multiple blank lines in a row.
141 for i, l in enumerate(lines[:-1]):
142 margs = {'offset': i, 'line': l}
145 self.add_message('C9003', node=node, line=node.fromlineno, args=margs)
148 if last_blank and curr_blank:
149 self.add_message('C9013', node=node, line=node.fromlineno, args=margs)
150 last_blank = curr_blank
152 # Now specially handle the last line.
154 if l.strip() != '' and l.rstrip() != l:
155 margs = {'offset': len(lines), 'line': l}
156 self.add_message('C9003', node=node, line=node.fromlineno, args=margs)
158 def _check_last_line(self, node, lines):
159 """Make sure last line is all by itself"""
161 if lines[-1].strip() != '':
162 self.add_message('C9005', node=node, line=node.fromlineno)
164 def _check_last_line_function(self, node, lines):
165 """Make sure last line is indented"""
168 self.add_message('C9005', node=node, line=node.fromlineno)
170 def _check_section_lines(self, node, lines):
171 """Verify each section (Args/Returns/Yields/Raises) is sane"""
172 lineno_sections = [-1] * len(self.VALID_SECTIONS)
174 # Handle common misnamings.
175 'arg', 'argument', 'arguments',
176 'ret', 'rets', 'return',
177 'yield', 'yeild', 'yeilds',
178 'raise', 'throw', 'throws',
181 last = lines[0].strip()
182 for i, line in enumerate(lines[1:]):
183 margs = {'offset': i + 1, 'line': line}
186 # Catch semi-common javadoc style.
187 if l.startswith('@param') or l.startswith('@return'):
188 self.add_message('C9007', node=node, line=node.fromlineno, args=margs)
190 # See if we can detect incorrect behavior.
191 section = l.split(':', 1)[0]
192 if section in self.VALID_SECTIONS or section.lower() in invalid_sections:
193 # Make sure it has some number of leading whitespace.
194 if not line.startswith(' '):
195 self.add_message('C9004', node=node, line=node.fromlineno, args=margs)
197 # Make sure it has a single trailing colon.
198 if l != '%s:' % section:
199 self.add_message('C9007', node=node, line=node.fromlineno, args=margs)
201 # Make sure it's valid.
202 if section.lower() in invalid_sections:
203 self.add_message('C9007', node=node, line=node.fromlineno, args=margs)
205 # Gather the order of the sections.
206 lineno_sections[self.VALID_SECTIONS.index(section)] = i
208 # Verify blank line before it.
210 self.add_message('C9006', node=node, line=node.fromlineno, args=margs)
214 # Make sure the sections are in the right order.
215 valid_lineno = lambda x: x >= 0
216 lineno_sections = filter(valid_lineno, lineno_sections)
217 if lineno_sections != sorted(lineno_sections):
218 self.add_message('C9008', node=node, line=node.fromlineno)
220 def _check_all_args_in_doc(self, node, lines):
221 """All function arguments are mentioned in doc"""
222 if not hasattr(node, 'argnames'):
225 # Locate the start of the args section.
229 if l.strip() in [''] + ['%s:' % x for x in self.VALID_SECTIONS]:
231 elif l.strip() != 'Args:':
235 # If they don't have an Args section, then give it a pass.
238 # Now verify all args exist.
239 # TODO: Should we verify arg order matches doc order ?
240 # TODO: Should we check indentation of wrapped docs ?
242 for arg in node.args.args:
243 # Ignore class related args.
244 if arg.name in ('cls', 'self'):
246 # Ignore ignored args.
247 if arg.name.startswith('_'):
252 if aline.startswith('%s:' % arg.name):
253 amsg = aline[len(arg.name) + 1:]
254 if len(amsg) and len(amsg) - len(amsg.lstrip()) != 1:
256 self.add_message('C9012', node=node, line=node.fromlineno,
260 missing_args.append(arg.name)
263 margs = {'arg': '|, |'.join(missing_args)}
264 self.add_message('C9010', node=node, line=node.fromlineno, args=margs)
266 def _check_func_signature(self, node):
267 """Require *args to be named args, and **kwargs kwargs"""
268 vararg = node.args.vararg
269 if vararg and vararg != 'args' and vararg != '_args':
270 margs = {'arg': vararg}
271 self.add_message('C9011', node=node, line=node.fromlineno, args=margs)
273 kwarg = node.args.kwarg
274 if kwarg and kwarg != 'kwargs' and kwarg != '_kwargs':
275 margs = {'arg': kwarg}
276 self.add_message('C9011', node=node, line=node.fromlineno, args=margs)
279 class Py3kCompatChecker(BaseChecker):
280 """Make sure we enforce py3k compatible features"""
282 __implements__ = IASTNGChecker
284 name = 'py3k_compat_checker'
286 MSG_ARGS = 'offset:%(offset)i: {%(line)s}'
288 'W9100': ('Missing "from __future__ import print_function" line',
289 ('Used when a module misses print function import for py3k')),
293 def __init__(self, *args, **kwargs):
294 super(Py3kCompatChecker, self).__init__(*args, **kwargs)
295 self.seen_print_func = False
296 self.saw_imports = False
299 """Called when done processing module"""
300 if not self.seen_print_func:
301 # Do not warn if moduler doesn't import anything at all (like
302 # empty __init__.py files).
304 self.add_message('W9100')
306 def _check_print_function(self, node):
307 """Verify print_function is imported"""
308 if node.modname == '__future__':
309 for name, _ in node.names:
310 if name == 'print_function':
311 self.seen_print_func = True
313 def visit_from(self, node):
314 """Process 'from' statements"""
315 self.saw_imports = True
316 self._check_print_function(node)
318 def visit_import(self, _node):
319 """Process 'import' statements"""
320 self.saw_imports = True
323 def register(linter):
324 """pylint will call this func to register all our checkers"""
325 # Walk all the classes in this module and register ours.
326 this_module = sys.modules[__name__]
327 for member in dir(this_module):
328 if (not member.endswith('Checker') or
329 member in ('BaseChecker', 'IASTNGChecker')):
331 cls = getattr(this_module, member)
332 linter.register_checker(cls(linter))