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

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

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