1717from logilab import astng
1818from pylint import checkers
1919from pylint import interfaces
20+ from pylint .checkers import utils
21+
22+
23+ MSGS = {
24+ 'W6501' : ('Specify string format arguments as logging function parameters' ,
25+ 'Used when a logging statement has a call form of '
26+ '"logging.<logging method>(format_string % (format_args...))". '
27+ 'Such calls should leave string interpolation to the logging '
28+ 'method itself and be written '
29+ '"logging.<logging method>(format_string, format_args...)" '
30+ 'so that the program may avoid incurring the cost of the '
31+ 'interpolation in those cases in which no message will be '
32+ 'logged. For more, see '
33+ 'http://www.python.org/dev/peps/pep-0282/.' ),
34+ 'E6500' : ('Unsupported logging format character %r (%#02x) at index %d' ,
35+ 'Used when an unsupported format character is used in a logging\
36+ statement format string.' ),
37+ 'E6501' : ('Logging format string ends in middle of conversion specifier' ,
38+ 'Used when a logging statement format string terminates before\
39+ the end of a conversion specifier.' ),
40+ 'E6505' : ('Too many arguments for logging format string' ,
41+ 'Used when a logging format string is given too few arguments.' ),
42+ 'E6506' : ('Not enough arguments for logging format string' ,
43+ 'Used when a logging format string is given too many arguments' ),
44+ }
2045
21- EAGER_STRING_INTERPOLATION = 'W6501'
2246
2347CHECKED_CONVENIENCE_FUNCTIONS = set ([
2448 'critical' , 'debug' , 'error' , 'exception' , 'fatal' , 'info' , 'warn' ,
@@ -29,21 +53,8 @@ class LoggingChecker(checkers.BaseChecker):
2953 """Checks use of the logging module."""
3054
3155 __implements__ = interfaces .IASTNGChecker
32-
3356 name = 'logging'
34-
35- msgs = {EAGER_STRING_INTERPOLATION :
36- ('Specify string format arguments as logging function parameters' ,
37- 'Used when a logging statement has a call form of '
38- '"logging.<logging method>(format_string % (format_args...))". '
39- 'Such calls should leave string interpolation to the logging '
40- 'method itself and be written '
41- '"logging.<logging method>(format_string, format_args...)" '
42- 'so that the program may avoid incurring the cost of the '
43- 'interpolation in those cases in which no message will be '
44- 'logged. For more, see '
45- 'http://www.python.org/dev/peps/pep-0282/.' )
46- }
57+ msgs = MSGS
4758
4859 def visit_module (self , unused_node ):
4960 """Clears any state left in this checker from last module checked."""
@@ -67,30 +78,87 @@ def visit_callfunc(self, node):
6778 or not isinstance (node .func .expr , astng .Name )
6879 or node .func .expr .name != self ._logging_name ):
6980 return
70- self ._CheckConvenienceMethods (node )
71- self ._CheckLogMethod (node )
81+ self ._check_convenience_methods (node )
82+ self ._check_log_methods (node )
7283
73- def _CheckConvenienceMethods (self , node ):
84+ def _check_convenience_methods (self , node ):
7485 """Checks calls to logging convenience methods (like logging.warn)."""
7586 if node .func .attrname not in CHECKED_CONVENIENCE_FUNCTIONS :
7687 return
77- if not node .args :
78- # Either no args, or star args, or double-star args. Beyond the
79- # scope of this checker in any case .
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.
8091 return
8192 if isinstance (node .args [0 ], astng .BinOp ) and node .args [0 ].op == '%' :
82- self .add_message (EAGER_STRING_INTERPOLATION , node = node )
93+ self .add_message ('W6501' , node = node )
94+ elif isinstance (node .args [0 ], astng .Const ):
95+ self ._check_format_string (node , 0 )
8396
84- def _CheckLogMethod (self , node ):
97+ def _check_log_methods (self , node ):
8598 """Checks calls to logging.log(level, format, *format_args)."""
8699 if node .func .attrname != 'log' :
87100 return
88- if len (node .args ) < 2 :
89- # Either a malformed call or something with crazy star args or
90- # double-star args magic. Beyond the scope of this checker.
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.
91104 return
92105 if isinstance (node .args [1 ], astng .BinOp ) and node .args [1 ].op == '%' :
93- self .add_message (EAGER_STRING_INTERPOLATION , node = node )
106+ self .add_message ('W6501' , node = node )
107+ elif isinstance (node .args [1 ], astng .Const ):
108+ self ._check_format_string (node , 1 )
109+
110+ def _check_format_string (self , node , format_arg ):
111+ """Checks that format string tokens match the supplied arguments.
112+
113+ Args:
114+ node: AST node to be checked.
115+ format_arg: Index of the format string in the node arguments.
116+ """
117+ num_args = self ._count_supplied_tokens (node .args [format_arg + 1 :])
118+ if not num_args :
119+ # If no args were supplied, then all format strings are valid -
120+ # don't check any further.
121+ return
122+ format_string = node .args [format_arg ].value
123+ if not isinstance (format_string , basestring ):
124+ # If the log format is constant non-string (e.g. logging.debug(5)),
125+ # ensure there are no arguments.
126+ required_num_args = 0
127+ else :
128+ try :
129+ keyword_args , required_num_args = \
130+ utils .parse_format_string (format_string )
131+ if keyword_args :
132+ # Keyword checking on logging strings is complicated by
133+ # special keywords - out of scope.
134+ return
135+ except utils .UnsupportedFormatCharacter , e :
136+ c = format_string [e .index ]
137+ self .add_message ('E6500' , node = node , args = (c , ord (c ), e .index ))
138+ return
139+ except utils .IncompleteFormatString :
140+ self .add_message ('E6501' , node = node )
141+ return
142+ if num_args > required_num_args :
143+ self .add_message ('E6505' , node = node )
144+ elif num_args < required_num_args :
145+ self .add_message ('E6506' , node = node )
146+
147+ def _count_supplied_tokens (self , args ):
148+ """Counts the number of tokens in an args list.
149+
150+ The Python log functions allow for special keyword arguments: func,
151+ exc_info and extra. To handle these cases correctly, we only count
152+ arguments that aren't keywords.
153+
154+ Args:
155+ args: List of AST nodes that are arguments for a log format string.
156+
157+ Returns:
158+ Number of AST nodes that aren't keywords.
159+ """
160+ return sum (1 for arg in args if not isinstance (arg , astng .Keyword ))
161+
94162
95163def register (linter ):
96164 """Required method to auto-register this checker."""
0 commit comments