summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog2
-rw-r--r--checkers/strings.py56
-rw-r--r--test/functional/string_formatting.py10
-rw-r--r--test/functional/string_formatting.txt4
4 files changed, 51 insertions, 21 deletions
diff --git a/ChangeLog b/ChangeLog
index c7c3a0c..7f2dc52 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -30,6 +30,8 @@ ChangeLog for Pylint
* Analyze only strings by the string format checker. Closes issue #287.
+ * Properly handle nested format string fields. Closes issue #294.
+
2014-07-26 -- 1.3.0
* Allow hanging continued indentation for implicitly concatenated
diff --git a/checkers/strings.py b/checkers/strings.py
index 9d4f13a..714a1a2 100644
--- a/checkers/strings.py
+++ b/checkers/strings.py
@@ -133,17 +133,13 @@ else:
# the output
return keyname, _field_iterator_convertor(fielditerator)
-def parse_format_method_string(format_string):
- """
- Parses a PEP 3101 format string, returning a tuple of
- (keys, num_args, manual_pos_arg),
- where keys is the set of mapping keys in the format string, num_args
- is the number of arguments required by the format string and
- manual_pos_arg is the number of arguments passed with the position.
+
+def collect_string_fields(format_string):
+ """ Given a format string, return an iterator
+ of all the valid format fields. It handles nested fields
+ as well.
"""
- keys = []
- num_args = 0
- manual_pos_arg = 0
+
formatter = string.Formatter()
parseiterator = formatter.parse(format_string)
try:
@@ -152,21 +148,39 @@ def parse_format_method_string(format_string):
# not a replacement format
continue
name = result[1]
- if name and str(name).isdigit():
- manual_pos_arg += 1
- elif name:
- keyname, fielditerator = split_format_field_names(name)
- if not isinstance(keyname, str):
- # In Python 2 it will return long which will lead
- # to different output between 2 and 3
- keyname = int(keyname)
- keys.append((keyname, list(fielditerator)))
- else:
- num_args += 1
+ nested = result[2]
+ yield name
+ if nested:
+ for field in collect_string_fields(nested):
+ yield field
except ValueError:
# probably the format string is invalid
# should we check the argument of the ValueError?
raise utils.IncompleteFormatString(format_string)
+
+def parse_format_method_string(format_string):
+ """
+ Parses a PEP 3101 format string, returning a tuple of
+ (keys, num_args, manual_pos_arg),
+ where keys is the set of mapping keys in the format string, num_args
+ is the number of arguments required by the format string and
+ manual_pos_arg is the number of arguments passed with the position.
+ """
+ keys = []
+ num_args = 0
+ manual_pos_arg = 0
+ for name in collect_string_fields(format_string):
+ if name and str(name).isdigit():
+ manual_pos_arg += 1
+ elif name:
+ keyname, fielditerator = split_format_field_names(name)
+ if not isinstance(keyname, str):
+ # In Python 2 it will return long which will lead
+ # to different output between 2 and 3
+ keyname = int(keyname)
+ keys.append((keyname, list(fielditerator)))
+ else:
+ num_args += 1
return keys, num_args, manual_pos_arg
def get_args(callfunc):
diff --git a/test/functional/string_formatting.py b/test/functional/string_formatting.py
index 1714cf4..91b91ba 100644
--- a/test/functional/string_formatting.py
+++ b/test/functional/string_formatting.py
@@ -104,3 +104,13 @@ def good_issue287():
ret['comment'] = 'MySQL grant {0} is set to be revoked'
ret['comment'] = ret['comment'].format(name)
return ret, name
+
+def nested_issue294():
+ """ Test nested format fields. """
+ '{0:>{1}}'.format(42, 24)
+ '{0:{a[1]}} {a}'.format(1, a=[1, 2])
+ '{:>{}}'.format(42, 24)
+ '{0:>{1}}'.format(42) # [too-few-format-args]
+ '{0:>{1}}'.format(42, 24, 54) # [too-many-format-args]
+ '{0:{a[1]}}'.format(1) # [missing-format-argument-key]
+ '{0:{a.x}}'.format(1, a=2) # [missing-format-attribute]
diff --git a/test/functional/string_formatting.txt b/test/functional/string_formatting.txt
index 6ec8944..ffc00fb 100644
--- a/test/functional/string_formatting.txt
+++ b/test/functional/string_formatting.txt
@@ -29,3 +29,7 @@ too-many-format-args:85:pprint_bad:Too many arguments for format string
too-few-format-args:86:pprint_bad:Not enough arguments for format string
too-few-format-args:87:pprint_bad:Not enough arguments for format string
too-many-format-args:88:pprint_bad:Too many arguments for format string
+too-few-format-args:113:nested_issue294:Not enough arguments for format string
+too-many-format-args:114:nested_issue294:Too many arguments for format string
+missing-format-argument-key:115:nested_issue294:Missing keyword argument 'a' for format string
+missing-format-attribute:116:nested_issue294:Missing format attribute 'x' in format specifier 'a.x'