summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog6
-rw-r--r--checkers/classes.py32
-rw-r--r--checkers/strings.py15
-rw-r--r--test/functional/string_formatting_py27.py23
-rw-r--r--test/functional/string_formatting_py27.rc3
-rw-r--r--test/functional/string_formatting_py27.txt15
-rw-r--r--test/input/func_defining-attr-methods_order.py34
7 files changed, 121 insertions, 7 deletions
diff --git a/ChangeLog b/ChangeLog
index 7f2dc52..bb79521 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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