diff options
| -rw-r--r-- | ChangeLog | 6 | ||||
| -rw-r--r-- | checkers/classes.py | 32 | ||||
| -rw-r--r-- | checkers/strings.py | 15 | ||||
| -rw-r--r-- | test/functional/string_formatting_py27.py | 23 | ||||
| -rw-r--r-- | test/functional/string_formatting_py27.rc | 3 | ||||
| -rw-r--r-- | test/functional/string_formatting_py27.txt | 15 | ||||
| -rw-r--r-- | test/input/func_defining-attr-methods_order.py | 34 |
7 files changed, 121 insertions, 7 deletions
@@ -32,6 +32,12 @@ ChangeLog for Pylint * Properly handle nested format string fields. Closes issue #294. + * Don't emit 'attribute-defined-outside-init' if the attribute + was set by a function call in a defining method. Closes issue #192. + + * Properly handle unicode format strings for Python 2. + Closes issue #296. + 2014-07-26 -- 1.3.0 * Allow hanging continued indentation for implicitly concatenated diff --git a/checkers/classes.py b/checkers/classes.py index 61769f2..18ca959 100644 --- a/checkers/classes.py +++ b/checkers/classes.py @@ -35,6 +35,32 @@ else: NEXT_METHOD = 'next' ITER_METHODS = ('__iter__', '__getitem__') +def _called_in_methods(func, klass, methods): + """ Check if the func was called in any of the given methods, + belonging to the *klass*. Returns True if so, False otherwise. + """ + if not isinstance(func, astroid.Function): + return False + for method in methods: + try: + infered = klass.getattr(method) + except astroid.NotFoundError: + continue + for infer_method in infered: + for callfunc in infer_method.nodes_of_class(astroid.CallFunc): + try: + bound = next(callfunc.func.infer()) + except (astroid.InferenceError, StopIteration): + continue + if not isinstance(bound, astroid.BoundMethod): + continue + func_obj = bound._proxied + if isinstance(func_obj, astroid.UnboundMethod): + func_obj = func_obj._proxied + if func_obj.name == func.name: + return True + return False + def class_is_abstract(node): """return true if the given class node should be considered as an abstract class @@ -301,6 +327,12 @@ a metaclass class method.'} except astroid.NotFoundError: for node in nodes: if node.frame().name not in defining_methods: + # If the attribute was set by a callfunc in any + # of the defining methods, then don't emit + # the warning. + if _called_in_methods(node.frame(), cnode, + defining_methods): + continue self.add_message('attribute-defined-outside-init', args=attr, node=node) diff --git a/checkers/strings.py b/checkers/strings.py index 714a1a2..5efbc90 100644 --- a/checkers/strings.py +++ b/checkers/strings.py @@ -21,6 +21,7 @@ import sys import tokenize import string +import numbers import astroid @@ -122,7 +123,7 @@ if _PY3K: else: def _field_iterator_convertor(iterator): for is_attr, key in iterator: - if not isinstance(key, str): + if isinstance(key, numbers.Number): yield is_attr, int(key) else: yield is_attr, key @@ -174,7 +175,7 @@ def parse_format_method_string(format_string): manual_pos_arg += 1 elif name: keyname, fielditerator = split_format_field_names(name) - if not isinstance(keyname, str): + if isinstance(keyname, numbers.Number): # In Python 2 it will return long which will lead # to different output between 2 and 3 keyname = int(keyname) @@ -358,9 +359,9 @@ class StringMethodsChecker(BaseChecker): return manual_fields = set(field[0] for field in fields - if isinstance(field[0], int)) + if isinstance(field[0], numbers.Number)) named_fields = set(field[0] for field in fields - if isinstance(field[0], str)) + if isinstance(field[0], basestring)) if num_args and manual_pos: self.add_message('format-combined-specification', node=node) @@ -384,7 +385,9 @@ class StringMethodsChecker(BaseChecker): # num_args can be 0 if manual_pos is not. num_args = num_args or manual_pos if positional or num_args: - if named or any(True for field in named_fields if field == ''): + empty = any(True for field in named_fields + if field == '') + if named or empty: # Verify the required number of positional arguments # only if the .format got at least one keyword argument. # This means that the format strings accepts both @@ -421,7 +424,7 @@ class StringMethodsChecker(BaseChecker): # to 0. It will not be present in `named`, so use the value # 0 for it. key = 0 - if isinstance(key, int): + if isinstance(key, numbers.Number): try: argname = utils.get_argument_from_call(node, key) except utils.NoSuchArgumentError: diff --git a/test/functional/string_formatting_py27.py b/test/functional/string_formatting_py27.py new file mode 100644 index 0000000..d6aeae7 --- /dev/null +++ b/test/functional/string_formatting_py27.py @@ -0,0 +1,23 @@ +"""test for Python 2 string formatting error
+"""
+from __future__ import unicode_literals
+# pylint: disable=line-too-long
+__revision__ = 1
+
+def pprint_bad():
+ """Test string format """
+ "{{}}".format(1) # [too-many-format-args]
+ "{} {".format() # [bad-format-string]
+ "{} }".format() # [bad-format-string]
+ "{0} {}".format(1, 2) # [format-combined-specification]
+ # +1: [missing-format-argument-key, unused-format-string-argument]
+ "{a} {b}".format(a=1, c=2)
+ "{} {a}".format(1, 2) # [missing-format-argument-key]
+ "{} {}".format(1) # [too-few-format-args]
+ "{} {}".format(1, 2, 3) # [too-many-format-args]
+ # +1: [missing-format-argument-key,missing-format-argument-key,missing-format-argument-key]
+ "{a} {b} {c}".format()
+ "{} {}".format(a=1, b=2) # [too-few-format-args]
+ # +1: [missing-format-argument-key, missing-format-argument-key]
+ "{a} {b}".format(1, 2)
+
diff --git a/test/functional/string_formatting_py27.rc b/test/functional/string_formatting_py27.rc new file mode 100644 index 0000000..80170b7 --- /dev/null +++ b/test/functional/string_formatting_py27.rc @@ -0,0 +1,3 @@ +[testoptions] +min_pyver=2.7
+max_pyver=3.0
\ No newline at end of file diff --git a/test/functional/string_formatting_py27.txt b/test/functional/string_formatting_py27.txt new file mode 100644 index 0000000..47f21de --- /dev/null +++ b/test/functional/string_formatting_py27.txt @@ -0,0 +1,15 @@ +too-many-format-args:9:pprint_bad:Too many arguments for format string +bad-format-string:10:pprint_bad:Invalid format string +bad-format-string:11:pprint_bad:Invalid format string +format-combined-specification:12:pprint_bad:Format string contains both automatic field numbering and manual field specification +missing-format-argument-key:14:pprint_bad:Missing keyword argument u'b' for format string +unused-format-string-argument:14:pprint_bad:Unused format argument 'c' +missing-format-argument-key:15:pprint_bad:Missing keyword argument u'a' for format string +too-few-format-args:16:pprint_bad:Not enough arguments for format string +too-many-format-args:17:pprint_bad:Too many arguments for format string +missing-format-argument-key:19:pprint_bad:Missing keyword argument u'a' for format string +missing-format-argument-key:19:pprint_bad:Missing keyword argument u'b' for format string +missing-format-argument-key:19:pprint_bad:Missing keyword argument u'c' for format string +too-few-format-args:20:pprint_bad:Not enough arguments for format string +missing-format-argument-key:22:pprint_bad:Missing keyword argument u'a' for format string +missing-format-argument-key:22:pprint_bad:Missing keyword argument u'b' for format string diff --git a/test/input/func_defining-attr-methods_order.py b/test/input/func_defining-attr-methods_order.py index 888b192..28918f2 100644 --- a/test/input/func_defining-attr-methods_order.py +++ b/test/input/func_defining-attr-methods_order.py @@ -1,4 +1,4 @@ -# pylint: disable=C0103 +# pylint: disable=C0103, too-few-public-methods ''' Test that y is defined properly, z is not. Default defining methods are __init__, @@ -39,3 +39,35 @@ class B(A): def test(self): """ test """ self.z = 44 + +class C(object): + ''' class C ''' + + def __init__(self): + self._init() + + def _init(self): + ''' called by __init__ ''' + self.z = 44 + +class D(object): + ''' class D ''' + + def setUp(self): + ''' defining method ''' + self.set_z() + + def set_z(self): + ''' called by the parent. ''' + self.z = 42 + +class E(object): + ''' Reassign the function. ''' + + def __init__(self): + i = self._init + i() + + def _init(self): + ''' called by __init__ ''' + self.z = 44 |
