Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(267)

Side by Side Diff: third_party/pylint/pylint/checkers/logging.py

Issue 1920403002: [content/test/gpu] Run pylint check of gpu tests in unittest instead of PRESUBMIT (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Update path to LICENSE.txt of logilab/README.chromium Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 # Copyright (c) 2009-2010 Google, Inc.
2 # This program is free software; you can redistribute it and/or modify it under
3 # the terms of the GNU General Public License as published by the Free Software
4 # Foundation; either version 2 of the License, or (at your option) any later
5 # version.
6 #
7 # This program is distributed in the hope that it will be useful, but WITHOUT
8 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
10 #
11 # You should have received a copy of the GNU General Public License along with
12 # this program; if not, write to the Free Software Foundation, Inc.,
13 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
14 """checker for use of Python logging
15 """
16
17 import astroid
18 from pylint import checkers
19 from pylint import interfaces
20 from pylint.checkers import utils
21 from pylint.checkers.utils import check_messages
22
23 import six
24
25
26 MSGS = {
27 'W1201': ('Specify string format arguments as logging function parameters',
28 'logging-not-lazy',
29 'Used when a logging statement has a call form of '
30 '"logging.<logging method>(format_string % (format_args...))". '
31 'Such calls should leave string interpolation to the logging '
32 'method itself and be written '
33 '"logging.<logging method>(format_string, format_args...)" '
34 'so that the program may avoid incurring the cost of the '
35 'interpolation in those cases in which no message will be '
36 'logged. For more, see '
37 'http://www.python.org/dev/peps/pep-0282/.'),
38 'W1202': ('Use % formatting in logging functions but pass the % '
39 'parameters as arguments',
40 'logging-format-interpolation',
41 'Used when a logging statement has a call form of '
42 '"logging.<logging method>(format_string.format(format_args...))"'
43 '. Such calls should use % formatting instead, but leave '
44 'interpolation to the logging function by passing the parameters '
45 'as arguments.'),
46 'E1200': ('Unsupported logging format character %r (%#02x) at index %d',
47 'logging-unsupported-format',
48 'Used when an unsupported format character is used in a logging\
49 statement format string.'),
50 'E1201': ('Logging format string ends in middle of conversion specifier',
51 'logging-format-truncated',
52 'Used when a logging statement format string terminates before\
53 the end of a conversion specifier.'),
54 'E1205': ('Too many arguments for logging format string',
55 'logging-too-many-args',
56 'Used when a logging format string is given too few arguments.'),
57 'E1206': ('Not enough arguments for logging format string',
58 'logging-too-few-args',
59 'Used when a logging format string is given too many arguments'),
60 }
61
62
63 CHECKED_CONVENIENCE_FUNCTIONS = set([
64 'critical', 'debug', 'error', 'exception', 'fatal', 'info', 'warn',
65 'warning'])
66
67 def is_method_call(callfunc_node, types=(), methods=()):
68 """Determines if a CallFunc node represents a method call.
69
70 Args:
71 callfunc_node: The CallFunc AST node to check.
72 types: Optional sequence of caller type names to restrict check.
73 methods: Optional sequence of method names to restrict check.
74
75 Returns:
76 True, if the node represents a method call for the given type and
77 method names, False otherwise.
78 """
79 if not isinstance(callfunc_node, astroid.CallFunc):
80 return False
81 func = utils.safe_infer(callfunc_node.func)
82 return (isinstance(func, astroid.BoundMethod)
83 and isinstance(func.bound, astroid.Instance)
84 and (func.bound.name in types if types else True)
85 and (func.name in methods if methods else True))
86
87
88
89 class LoggingChecker(checkers.BaseChecker):
90 """Checks use of the logging module."""
91
92 __implements__ = interfaces.IAstroidChecker
93 name = 'logging'
94 msgs = MSGS
95
96 options = (('logging-modules',
97 {'default': ('logging',),
98 'type': 'csv',
99 'metavar': '<comma separated list>',
100 'help': 'Logging modules to check that the string format '
101 'arguments are in logging function parameter format'}
102 ),
103 )
104
105 def visit_module(self, node): # pylint: disable=unused-argument
106 """Clears any state left in this checker from last module checked."""
107 # The code being checked can just as easily "import logging as foo",
108 # so it is necessary to process the imports and store in this field
109 # what name the logging module is actually given.
110 self._logging_names = set()
111 logging_mods = self.config.logging_modules
112
113 self._logging_modules = set(logging_mods)
114 self._from_imports = {}
115 for logging_mod in logging_mods:
116 parts = logging_mod.rsplit('.', 1)
117 if len(parts) > 1:
118 self._from_imports[parts[0]] = parts[1]
119
120 def visit_from(self, node):
121 """Checks to see if a module uses a non-Python logging module."""
122 try:
123 logging_name = self._from_imports[node.modname]
124 for module, as_name in node.names:
125 if module == logging_name:
126 self._logging_names.add(as_name or module)
127 except KeyError:
128 pass
129
130 def visit_import(self, node):
131 """Checks to see if this module uses Python's built-in logging."""
132 for module, as_name in node.names:
133 if module in self._logging_modules:
134 self._logging_names.add(as_name or module)
135
136 @check_messages(*(MSGS.keys()))
137 def visit_callfunc(self, node):
138 """Checks calls to logging methods."""
139 def is_logging_name():
140 return (isinstance(node.func, astroid.Getattr) and
141 isinstance(node.func.expr, astroid.Name) and
142 node.func.expr.name in self._logging_names)
143
144 def is_logger_class():
145 try:
146 for inferred in node.func.infer():
147 if isinstance(inferred, astroid.BoundMethod):
148 parent = inferred._proxied.parent
149 if (isinstance(parent, astroid.Class) and
150 (parent.qname() == 'logging.Logger' or
151 any(ancestor.qname() == 'logging.Logger'
152 for ancestor in parent.ancestors()))):
153 return True, inferred._proxied.name
154 except astroid.exceptions.InferenceError:
155 pass
156 return False, None
157
158 if is_logging_name():
159 name = node.func.attrname
160 else:
161 result, name = is_logger_class()
162 if not result:
163 return
164 self._check_log_method(node, name)
165
166 def _check_log_method(self, node, name):
167 """Checks calls to logging.log(level, format, *format_args)."""
168 if name == 'log':
169 if node.starargs or node.kwargs or len(node.args) < 2:
170 # Either a malformed call, star args, or double-star args. Beyon d
171 # the scope of this checker.
172 return
173 format_pos = 1
174 elif name in CHECKED_CONVENIENCE_FUNCTIONS:
175 if node.starargs or node.kwargs or not node.args:
176 # Either no args, star args, or double-star args. Beyond the
177 # scope of this checker.
178 return
179 format_pos = 0
180 else:
181 return
182
183 if isinstance(node.args[format_pos], astroid.BinOp) and node.args[format _pos].op == '%':
184 self.add_message('logging-not-lazy', node=node)
185 elif isinstance(node.args[format_pos], astroid.CallFunc):
186 self._check_call_func(node.args[format_pos])
187 elif isinstance(node.args[format_pos], astroid.Const):
188 self._check_format_string(node, format_pos)
189
190 def _check_call_func(self, callfunc_node):
191 """Checks that function call is not format_string.format().
192
193 Args:
194 callfunc_node: CallFunc AST node to be checked.
195 """
196 if is_method_call(callfunc_node, ('str', 'unicode'), ('format',)):
197 self.add_message('logging-format-interpolation', node=callfunc_node)
198
199 def _check_format_string(self, node, format_arg):
200 """Checks that format string tokens match the supplied arguments.
201
202 Args:
203 node: AST node to be checked.
204 format_arg: Index of the format string in the node arguments.
205 """
206 num_args = _count_supplied_tokens(node.args[format_arg + 1:])
207 if not num_args:
208 # If no args were supplied, then all format strings are valid -
209 # don't check any further.
210 return
211 format_string = node.args[format_arg].value
212 if not isinstance(format_string, six.string_types):
213 # If the log format is constant non-string (e.g. logging.debug(5)),
214 # ensure there are no arguments.
215 required_num_args = 0
216 else:
217 try:
218 keyword_args, required_num_args = \
219 utils.parse_format_string(format_string)
220 if keyword_args:
221 # Keyword checking on logging strings is complicated by
222 # special keywords - out of scope.
223 return
224 except utils.UnsupportedFormatCharacter as ex:
225 char = format_string[ex.index]
226 self.add_message('logging-unsupported-format', node=node,
227 args=(char, ord(char), ex.index))
228 return
229 except utils.IncompleteFormatString:
230 self.add_message('logging-format-truncated', node=node)
231 return
232 if num_args > required_num_args:
233 self.add_message('logging-too-many-args', node=node)
234 elif num_args < required_num_args:
235 self.add_message('logging-too-few-args', node=node)
236
237
238 def _count_supplied_tokens(args):
239 """Counts the number of tokens in an args list.
240
241 The Python log functions allow for special keyword arguments: func,
242 exc_info and extra. To handle these cases correctly, we only count
243 arguments that aren't keywords.
244
245 Args:
246 args: List of AST nodes that are arguments for a log format string.
247
248 Returns:
249 Number of AST nodes that aren't keywords.
250 """
251 return sum(1 for arg in args if not isinstance(arg, astroid.Keyword))
252
253
254 def register(linter):
255 """Required method to auto-register this checker."""
256 linter.register_checker(LoggingChecker(linter))
OLDNEW
« no previous file with comments | « third_party/pylint/pylint/checkers/imports.py ('k') | third_party/pylint/pylint/checkers/misc.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698