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

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

Issue 739393004: Revert "Revert "pylint: upgrade to 1.3.1"" (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools/
Patch Set: Created 6 years, 1 month 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 | Annotate | Revision Log
« no previous file with comments | « third_party/pylint/checkers/imports.py ('k') | third_party/pylint/checkers/misc.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright (c) 2009-2010 Google, Inc. 1 # Copyright (c) 2009-2010 Google, Inc.
2 # This program is free software; you can redistribute it and/or modify it under 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 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 4 # Foundation; either version 2 of the License, or (at your option) any later
5 # version. 5 # version.
6 # 6 #
7 # This program is distributed in the hope that it will be useful, but WITHOUT 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 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. 9 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
10 # 10 #
11 # You should have received a copy of the GNU General Public License along with 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., 12 # this program; if not, write to the Free Software Foundation, Inc.,
13 # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 13 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
14 """checker for use of Python logging 14 """checker for use of Python logging
15 """ 15 """
16 16
17 from logilab import astng 17 import astroid
18 from pylint import checkers 18 from pylint import checkers
19 from pylint import interfaces 19 from pylint import interfaces
20 from pylint.checkers import utils 20 from pylint.checkers import utils
21 21 from pylint.checkers.utils import check_messages
22 22
23 MSGS = { 23 MSGS = {
24 'W1201': ('Specify string format arguments as logging function parameters', 24 'W1201': ('Specify string format arguments as logging function parameters',
25 'Used when a logging statement has a call form of ' 25 'logging-not-lazy',
26 '"logging.<logging method>(format_string % (format_args...))". ' 26 'Used when a logging statement has a call form of '
27 'Such calls should leave string interpolation to the logging ' 27 '"logging.<logging method>(format_string % (format_args...))". '
28 'method itself and be written ' 28 'Such calls should leave string interpolation to the logging '
29 '"logging.<logging method>(format_string, format_args...)" ' 29 'method itself and be written '
30 'so that the program may avoid incurring the cost of the ' 30 '"logging.<logging method>(format_string, format_args...)" '
31 'interpolation in those cases in which no message will be ' 31 'so that the program may avoid incurring the cost of the '
32 'logged. For more, see ' 32 'interpolation in those cases in which no message will be '
33 'http://www.python.org/dev/peps/pep-0282/.'), 33 'logged. For more, see '
34 'http://www.python.org/dev/peps/pep-0282/.'),
34 'E1200': ('Unsupported logging format character %r (%#02x) at index %d', 35 'E1200': ('Unsupported logging format character %r (%#02x) at index %d',
36 'logging-unsupported-format',
35 'Used when an unsupported format character is used in a logging\ 37 'Used when an unsupported format character is used in a logging\
36 statement format string.'), 38 statement format string.'),
37 'E1201': ('Logging format string ends in middle of conversion specifier', 39 'E1201': ('Logging format string ends in middle of conversion specifier',
40 'logging-format-truncated',
38 'Used when a logging statement format string terminates before\ 41 'Used when a logging statement format string terminates before\
39 the end of a conversion specifier.'), 42 the end of a conversion specifier.'),
40 'E1205': ('Too many arguments for logging format string', 43 'E1205': ('Too many arguments for logging format string',
44 'logging-too-many-args',
41 'Used when a logging format string is given too few arguments.'), 45 'Used when a logging format string is given too few arguments.'),
42 'E1206': ('Not enough arguments for logging format string', 46 'E1206': ('Not enough arguments for logging format string',
47 'logging-too-few-args',
43 'Used when a logging format string is given too many arguments'), 48 'Used when a logging format string is given too many arguments'),
44 } 49 }
45 50
46 51
47 CHECKED_CONVENIENCE_FUNCTIONS = set([ 52 CHECKED_CONVENIENCE_FUNCTIONS = set([
48 'critical', 'debug', 'error', 'exception', 'fatal', 'info', 'warn', 53 'critical', 'debug', 'error', 'exception', 'fatal', 'info', 'warn',
49 'warning']) 54 'warning'])
50 55
51 56
52 class LoggingChecker(checkers.BaseChecker): 57 class LoggingChecker(checkers.BaseChecker):
53 """Checks use of the logging module.""" 58 """Checks use of the logging module."""
54 59
55 __implements__ = interfaces.IASTNGChecker 60 __implements__ = interfaces.IAstroidChecker
56 name = 'logging' 61 name = 'logging'
57 msgs = MSGS 62 msgs = MSGS
58 63
64 options = (('logging-modules',
65 {'default' : ('logging',),
66 'type' : 'csv',
67 'metavar' : '<comma separated list>',
68 'help' : 'Logging modules to check that the string format '
69 'arguments are in logging function parameter format'}
70 ),
71 )
72
59 def visit_module(self, unused_node): 73 def visit_module(self, unused_node):
60 """Clears any state left in this checker from last module checked.""" 74 """Clears any state left in this checker from last module checked."""
61 # The code being checked can just as easily "import logging as foo", 75 # The code being checked can just as easily "import logging as foo",
62 # so it is necessary to process the imports and store in this field 76 # so it is necessary to process the imports and store in this field
63 # what name the logging module is actually given. 77 # what name the logging module is actually given.
64 self._logging_name = None 78 self._logging_names = set()
79 logging_mods = self.config.logging_modules
80
81 self._logging_modules = set(logging_mods)
82 self._from_imports = {}
83 for logging_mod in logging_mods:
84 parts = logging_mod.rsplit('.', 1)
85 if len(parts) > 1:
86 self._from_imports[parts[0]] = parts[1]
87
88 def visit_from(self, node):
89 """Checks to see if a module uses a non-Python logging module."""
90 try:
91 logging_name = self._from_imports[node.modname]
92 for module, as_name in node.names:
93 if module == logging_name:
94 self._logging_names.add(as_name or module)
95 except KeyError:
96 pass
65 97
66 def visit_import(self, node): 98 def visit_import(self, node):
67 """Checks to see if this module uses Python's built-in logging.""" 99 """Checks to see if this module uses Python's built-in logging."""
68 for module, as_name in node.names: 100 for module, as_name in node.names:
69 if module == 'logging': 101 if module in self._logging_modules:
70 if as_name: 102 self._logging_names.add(as_name or module)
71 self._logging_name = as_name
72 else:
73 self._logging_name = 'logging'
74 103
104 @check_messages(*(MSGS.keys()))
75 def visit_callfunc(self, node): 105 def visit_callfunc(self, node):
76 """Checks calls to (simple forms of) logging methods.""" 106 """Checks calls to logging methods."""
77 if (not isinstance(node.func, astng.Getattr) 107 def is_logging_name():
78 or not isinstance(node.func.expr, astng.Name) 108 return (isinstance(node.func, astroid.Getattr) and
79 or node.func.expr.name != self._logging_name): 109 isinstance(node.func.expr, astroid.Name) and
110 node.func.expr.name in self._logging_names)
111
112 def is_logger_class():
113 try:
114 for inferred in node.func.infer():
115 if isinstance(inferred, astroid.BoundMethod):
116 parent = inferred._proxied.parent
117 if (isinstance(parent, astroid.Class) and
118 (parent.qname() == 'logging.Logger' or
119 any(ancestor.qname() == 'logging.Logger'
120 for ancestor in parent.ancestors()))):
121 return True, inferred._proxied.name
122 except astroid.exceptions.InferenceError:
123 pass
124 return False, None
125
126 if is_logging_name():
127 name = node.func.attrname
128 else:
129 result, name = is_logger_class()
130 if not result:
131 return
132 self._check_log_method(node, name)
133
134 def _check_log_method(self, node, name):
135 """Checks calls to logging.log(level, format, *format_args)."""
136 if name == 'log':
137 if node.starargs or node.kwargs or len(node.args) < 2:
138 # Either a malformed call, star args, or double-star args. Beyon d
139 # the scope of this checker.
140 return
141 format_pos = 1
142 elif name in CHECKED_CONVENIENCE_FUNCTIONS:
143 if node.starargs or node.kwargs or not node.args:
144 # Either no args, star args, or double-star args. Beyond the
145 # scope of this checker.
146 return
147 format_pos = 0
148 else:
80 return 149 return
81 self._check_convenience_methods(node)
82 self._check_log_methods(node)
83 150
84 def _check_convenience_methods(self, node): 151 if isinstance(node.args[format_pos], astroid.BinOp) and node.args[format _pos].op == '%':
85 """Checks calls to logging convenience methods (like logging.warn).""" 152 self.add_message('logging-not-lazy', node=node)
86 if node.func.attrname not in CHECKED_CONVENIENCE_FUNCTIONS: 153 elif isinstance(node.args[format_pos], astroid.Const):
87 return 154 self._check_format_string(node, format_pos)
88 if node.starargs or node.kwargs or not node.args:
89 # Either no args, star args, or double-star args. Beyond the
90 # scope of this checker.
91 return
92 if isinstance(node.args[0], astng.BinOp) and node.args[0].op == '%':
93 self.add_message('W1201', node=node)
94 elif isinstance(node.args[0], astng.Const):
95 self._check_format_string(node, 0)
96
97 def _check_log_methods(self, node):
98 """Checks calls to logging.log(level, format, *format_args)."""
99 if node.func.attrname != 'log':
100 return
101 if node.starargs or node.kwargs or len(node.args) < 2:
102 # Either a malformed call, star args, or double-star args. Beyond
103 # the scope of this checker.
104 return
105 if isinstance(node.args[1], astng.BinOp) and node.args[1].op == '%':
106 self.add_message('W1201', node=node)
107 elif isinstance(node.args[1], astng.Const):
108 self._check_format_string(node, 1)
109 155
110 def _check_format_string(self, node, format_arg): 156 def _check_format_string(self, node, format_arg):
111 """Checks that format string tokens match the supplied arguments. 157 """Checks that format string tokens match the supplied arguments.
112 158
113 Args: 159 Args:
114 node: AST node to be checked. 160 node: AST node to be checked.
115 format_arg: Index of the format string in the node arguments. 161 format_arg: Index of the format string in the node arguments.
116 """ 162 """
117 num_args = self._count_supplied_tokens(node.args[format_arg + 1:]) 163 num_args = _count_supplied_tokens(node.args[format_arg + 1:])
118 if not num_args: 164 if not num_args:
119 # If no args were supplied, then all format strings are valid - 165 # If no args were supplied, then all format strings are valid -
120 # don't check any further. 166 # don't check any further.
121 return 167 return
122 format_string = node.args[format_arg].value 168 format_string = node.args[format_arg].value
123 if not isinstance(format_string, basestring): 169 if not isinstance(format_string, basestring):
124 # If the log format is constant non-string (e.g. logging.debug(5)), 170 # If the log format is constant non-string (e.g. logging.debug(5)),
125 # ensure there are no arguments. 171 # ensure there are no arguments.
126 required_num_args = 0 172 required_num_args = 0
127 else: 173 else:
128 try: 174 try:
129 keyword_args, required_num_args = \ 175 keyword_args, required_num_args = \
130 utils.parse_format_string(format_string) 176 utils.parse_format_string(format_string)
131 if keyword_args: 177 if keyword_args:
132 # Keyword checking on logging strings is complicated by 178 # Keyword checking on logging strings is complicated by
133 # special keywords - out of scope. 179 # special keywords - out of scope.
134 return 180 return
135 except utils.UnsupportedFormatCharacter, e: 181 except utils.UnsupportedFormatCharacter, ex:
136 c = format_string[e.index] 182 char = format_string[ex.index]
137 self.add_message('E1200', node=node, args=(c, ord(c), e.index)) 183 self.add_message('logging-unsupported-format', node=node,
184 args=(char, ord(char), ex.index))
138 return 185 return
139 except utils.IncompleteFormatString: 186 except utils.IncompleteFormatString:
140 self.add_message('E1201', node=node) 187 self.add_message('logging-format-truncated', node=node)
141 return 188 return
142 if num_args > required_num_args: 189 if num_args > required_num_args:
143 self.add_message('E1205', node=node) 190 self.add_message('logging-too-many-args', node=node)
144 elif num_args < required_num_args: 191 elif num_args < required_num_args:
145 self.add_message('E1206', node=node) 192 self.add_message('logging-too-few-args', node=node)
146 193
147 def _count_supplied_tokens(self, args):
148 """Counts the number of tokens in an args list.
149 194
150 The Python log functions allow for special keyword arguments: func, 195 def _count_supplied_tokens(args):
151 exc_info and extra. To handle these cases correctly, we only count 196 """Counts the number of tokens in an args list.
152 arguments that aren't keywords.
153 197
154 Args: 198 The Python log functions allow for special keyword arguments: func,
155 args: List of AST nodes that are arguments for a log format string. 199 exc_info and extra. To handle these cases correctly, we only count
200 arguments that aren't keywords.
156 201
157 Returns: 202 Args:
158 Number of AST nodes that aren't keywords. 203 args: List of AST nodes that are arguments for a log format string.
159 """ 204
160 return sum(1 for arg in args if not isinstance(arg, astng.Keyword)) 205 Returns:
206 Number of AST nodes that aren't keywords.
207 """
208 return sum(1 for arg in args if not isinstance(arg, astroid.Keyword))
161 209
162 210
163 def register(linter): 211 def register(linter):
164 """Required method to auto-register this checker.""" 212 """Required method to auto-register this checker."""
165 linter.register_checker(LoggingChecker(linter)) 213 linter.register_checker(LoggingChecker(linter))
OLDNEW
« no previous file with comments | « third_party/pylint/checkers/imports.py ('k') | third_party/pylint/checkers/misc.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698