Skip to content

Commit

Permalink
Adding implicit-str-concat-in-sequence check (#1655)
Browse files Browse the repository at this point in the history
``implicit-str-concat-in-sequence`` detects string concatenation inside lists, sets & tuples.
It would warn on code such as `('a', 'b' 'c')`.
  • Loading branch information
Lucas-C authored and PCManticore committed Oct 10, 2018
1 parent 04cf3f8 commit fcc0151
Show file tree
Hide file tree
Showing 22 changed files with 186 additions and 52 deletions.
2 changes: 2 additions & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -243,3 +243,5 @@ contributors:
* Scott Worley: contributor

* Michael Hudson-Doyle

* Lucas Cimon: contributor
2 changes: 2 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ Release date: TBA
Close #1422
Close #2019

* Add a new check 'implicit-str-concat-in-sequence' to spot string concatenation inside lists, sets & tuples.

What's New in Pylint 2.1.1?
===========================

Expand Down
9 changes: 9 additions & 0 deletions doc/whatsnew/2.2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ New checkers
* ``logging-format-style`` is a new option for the logging checker for usage of
str.format() style format strings in calls to loggers.

* ``implicit-str-concat-in-sequence`` detects string concatenation inside lists, sets & tuples.

Example of code that would generate such warning:

.. code-block:: python
woops = ('a', 'b' 'c')
Other Changes
=============

Expand Down
18 changes: 9 additions & 9 deletions pylint/checkers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -448,12 +448,12 @@ class BasicErrorChecker(_BasicChecker):
"E0104": (
"Return outside function",
"return-outside-function",
'Used when a "return" statement is found outside a function or ' "method.",
'Used when a "return" statement is found outside a function or method.',
),
"E0105": (
"Yield outside function",
"yield-outside-function",
'Used when a "yield" statement is found outside a function or ' "method.",
'Used when a "yield" statement is found outside a function or method.',
),
"E0106": (
"Return with argument inside generator",
Expand All @@ -472,7 +472,7 @@ class BasicErrorChecker(_BasicChecker):
"E0108": (
"Duplicate argument name %s in function definition",
"duplicate-argument-name",
"Duplicate argument names in function definitions are syntax" " errors.",
"Duplicate argument names in function definitions are syntax errors.",
),
"E0110": (
"Abstract class %r with abstract methods instantiated",
Expand All @@ -496,12 +496,12 @@ class BasicErrorChecker(_BasicChecker):
"E0113": (
"Starred assignment target must be in a list or tuple",
"invalid-star-assignment-target",
"Emitted when a star expression is used as a starred " "assignment target.",
"Emitted when a star expression is used as a starred assignment target.",
),
"E0114": (
"Can use starred expression only in assignment target",
"star-needs-assignment-target",
"Emitted when a star expression is not used in an " "assignment target.",
"Emitted when a star expression is not used in an assignment target.",
),
"E0115": (
"Name %r is nonlocal and global",
Expand Down Expand Up @@ -896,7 +896,7 @@ class BasicChecker(_BasicChecker):
"W0104": (
"Statement seems to have no effect",
"pointless-statement",
"Used when a statement doesn't have (or at least seems to) " "any effect.",
"Used when a statement doesn't have (or at least seems to) any effect.",
),
"W0105": (
"String statement has no effect",
Expand All @@ -923,7 +923,7 @@ class BasicChecker(_BasicChecker):
"W0109": (
"Duplicate key %r in dictionary",
"duplicate-key",
"Used when a dictionary expression binds the same key multiple " "times.",
"Used when a dictionary expression binds the same key multiple times.",
),
"W0122": (
"Use of exec",
Expand Down Expand Up @@ -1533,7 +1533,7 @@ class NameChecker(_BasicChecker):
"C0102": (
'Black listed name "%s"',
"blacklisted-name",
"Used when the name is listed in the black list (unauthorized " "names).",
"Used when the name is listed in the black list (unauthorized names).",
),
"C0103": (
'%s name "%s" doesn\'t conform to %s',
Expand Down Expand Up @@ -1975,7 +1975,7 @@ class PassChecker(_BasicChecker):
"W0107": (
"Unnecessary pass statement",
"unnecessary-pass",
'Used when a "pass" statement that can be avoided is ' "encountered.",
'Used when a "pass" statement that can be avoided is encountered.',
)
}

Expand Down
18 changes: 9 additions & 9 deletions pylint/checkers/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,12 +459,12 @@ def _has_same_layout_slots(slots, assigned_value):
"E0203": (
"Access to member %r before its definition line %s",
"access-member-before-definition",
"Used when an instance member is accessed before it's actually " "assigned.",
"Used when an instance member is accessed before it's actually assigned.",
),
"W0201": (
"Attribute %r defined outside __init__",
"attribute-defined-outside-init",
"Used when an instance attribute is defined outside the __init__ " "method.",
"Used when an instance attribute is defined outside the __init__ method.",
),
"W0212": (
"Access to a protected member %s of a client class", # E0214
Expand Down Expand Up @@ -550,7 +550,7 @@ def _has_same_layout_slots(slots, assigned_value):
"W0232": (
"Class has no __init__ method",
"no-init",
"Used when a class has no __init__ method, neither its parent " "classes.",
"Used when a class has no __init__ method, neither its parent classes.",
),
"W0233": (
"__init__ method from a non direct base class %r is called",
Expand All @@ -566,14 +566,14 @@ def _has_same_layout_slots(slots, assigned_value):
"from the MRO.",
),
"E0236": (
"Invalid object %r in __slots__, must contain " "only non empty strings",
"Invalid object %r in __slots__, must contain only non empty strings",
"invalid-slots-object",
"Used when an invalid (non-string) object occurs in __slots__.",
),
"E0237": (
"Assigning to attribute %r not defined in class slots",
"assigning-non-slot",
"Used when assigning to an attribute not defined " "in the class slots.",
"Used when assigning to an attribute not defined in the class slots.",
),
"E0238": (
"Invalid __slots__ object",
Expand All @@ -584,7 +584,7 @@ def _has_same_layout_slots(slots, assigned_value):
"E0239": (
"Inheriting %r, which is not a class.",
"inherit-non-class",
"Used when a class inherits from something which is not a " "class.",
"Used when a class inherits from something which is not a class.",
),
"E0240": (
"Inconsistent method resolution order for class %r",
Expand All @@ -599,17 +599,17 @@ def _has_same_layout_slots(slots, assigned_value):
"R0202": (
"Consider using a decorator instead of calling classmethod",
"no-classmethod-decorator",
"Used when a class method is defined without using the decorator " "syntax.",
"Used when a class method is defined without using the decorator syntax.",
),
"R0203": (
"Consider using a decorator instead of calling staticmethod",
"no-staticmethod-decorator",
"Used when a static method is defined without using the decorator " "syntax.",
"Used when a static method is defined without using the decorator syntax.",
),
"C0205": (
"Class __slots__ should be a non-string iterable",
"single-string-used-for-slots",
"Used when a class __slots__ is a simple string, rather " "than an iterable.",
"Used when a class __slots__ is a simple string, rather than an iterable.",
),
"R0205": (
"Class %r inherits from object, can be safely removed from bases in python3",
Expand Down
2 changes: 1 addition & 1 deletion pylint/checkers/design_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
"R0916": (
"Too many boolean expressions in if statement (%s/%s)",
"too-many-boolean-expressions",
"Used when an if statement contains too many boolean " "expressions.",
"Used when an if statement contains too many boolean expressions.",
),
}
SPECIAL_OBJ = re.compile("^_{2}[a-z]+_{2}$")
Expand Down
6 changes: 3 additions & 3 deletions pylint/checkers/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def _is_raising(body: typing.List) -> bool:
"string is raised (i.e. a `TypeError` will be raised).",
),
"E0703": (
"Exception context set to something which is not an " "exception, nor None",
"Exception context set to something which is not an exception, nor None",
"bad-exception-context",
'Used when using the syntax "raise ... from ...", '
"where the exception context is not an exception, "
Expand All @@ -113,7 +113,7 @@ def _is_raising(body: typing.List) -> bool:
"E0711": (
"NotImplemented raised - should raise NotImplementedError",
"notimplemented-raised",
"Used when NotImplemented is raised instead of " "NotImplementedError",
"Used when NotImplemented is raised instead of NotImplementedError",
),
"E0712": (
"Catching an exception which doesn't inherit from Exception: %s",
Expand All @@ -124,7 +124,7 @@ def _is_raising(body: typing.List) -> bool:
"W0702": (
"No exception type(s) specified",
"bare-except",
"Used when an except clause doesn't specify exceptions type to " "catch.",
"Used when an except clause doesn't specify exceptions type to catch.",
),
"W0703": (
"Catching too general exception %s",
Expand Down
2 changes: 1 addition & 1 deletion pylint/checkers/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@
"C0303": (
"Trailing whitespace",
"trailing-whitespace",
"Used when there is whitespace between the end of a line and the " "newline.",
"Used when there is whitespace between the end of a line and the newline.",
),
"C0304": (
"Final newline missing",
Expand Down
8 changes: 4 additions & 4 deletions pylint/checkers/imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ def _make_graph(filename, dep_info, sect, gtype):
"R0401": (
"Cyclic import (%s)",
"cyclic-import",
"Used when a cyclic import between two or more modules is " "detected.",
"Used when a cyclic import between two or more modules is detected.",
),
"W0401": (
"Wildcard import %s",
Expand All @@ -226,7 +226,7 @@ def _make_graph(filename, dep_info, sect, gtype):
"W0403": (
"Relative import %r, should be %r",
"relative-import",
"Used when an import relative to the package directory is " "detected.",
"Used when an import relative to the package directory is detected.",
{"maxversion": (3, 0)},
),
"W0404": (
Expand All @@ -248,7 +248,7 @@ def _make_graph(filename, dep_info, sect, gtype):
"C0410": (
"Multiple imports on one line (%s)",
"multiple-imports",
"Used when import statement importing multiple modules is " "detected.",
"Used when import statement importing multiple modules is detected.",
),
"C0411": (
"%s should be placed before %s",
Expand All @@ -262,7 +262,7 @@ def _make_graph(filename, dep_info, sect, gtype):
"Used when imports are not grouped by packages",
),
"C0413": (
'Import "%s" should be placed at the top of the ' "module",
'Import "%s" should be placed at the top of the module',
"wrong-import-position",
"Used when code and imports are mixed",
),
Expand Down
2 changes: 1 addition & 1 deletion pylint/checkers/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class EncodingChecker(BaseChecker):
"Used when a warning note as FIXME or XXX is detected.",
),
"W0512": (
'Cannot decode using encoding "%s",' " unexpected byte at position %d",
'Cannot decode using encoding "%s", unexpected byte at position %d',
"invalid-encoded-data",
"Used when a source line cannot be decoded using the specified "
"source file encoding.",
Expand Down
2 changes: 1 addition & 1 deletion pylint/checkers/newstyle.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"E1004": (
"Missing argument to super()",
"missing-super-argument",
"Used when the super builtin didn't receive an " "argument.",
"Used when the super builtin didn't receive an argument.",
{"maxversion": (3, 0)},
),
}
Expand Down
10 changes: 5 additions & 5 deletions pylint/checkers/python3.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,15 +179,15 @@ class Python3Checker(checkers.BaseChecker):
"(Python 3 doesn't allow it)",
),
"E1603": (
"Implicit unpacking of exceptions is not supported " "in Python 3",
"Implicit unpacking of exceptions is not supported in Python 3",
"unpacking-in-except",
"Python3 will not allow implicit unpacking of "
"exceptions in except clauses. "
"See http://www.python.org/dev/peps/pep-3110/",
{"old_names": [("W0712", "unpacking-in-except")]},
),
"E1604": (
"Use raise ErrorClass(args) instead of " "raise ErrorClass, args.",
"Use raise ErrorClass(args) instead of raise ErrorClass, args.",
"old-raise-syntax",
"Used when the alternate raise syntax "
"'raise foo, bar' is used "
Expand Down Expand Up @@ -378,7 +378,7 @@ class Python3Checker(checkers.BaseChecker):
"W1628": (
"__hex__ method defined",
"hex-method",
"Used when a __hex__ method is defined " "(method is not used by Python 3)",
"Used when a __hex__ method is defined (method is not used by Python 3)",
),
"W1629": (
"__nonzero__ method defined",
Expand All @@ -389,7 +389,7 @@ class Python3Checker(checkers.BaseChecker):
"W1630": (
"__cmp__ method defined",
"cmp-method",
"Used when a __cmp__ method is defined " "(method is not used by Python 3)",
"Used when a __cmp__ method is defined (method is not used by Python 3)",
),
# 'W1631': replaced by W1636
"W1632": (
Expand All @@ -413,7 +413,7 @@ class Python3Checker(checkers.BaseChecker):
"W1635": (
"unichr built-in referenced",
"unichr-builtin",
"Used when the unichr built-in is referenced " "(Use chr in Python 3)",
"Used when the unichr built-in is referenced (Use chr in Python 3)",
),
"W1636": (
"map built-in referenced when not iterating",
Expand Down
8 changes: 4 additions & 4 deletions pylint/checkers/refactoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class RefactoringChecker(checkers.BaseTokenChecker):
"R1703": (
"The if statement can be replaced with %s",
"simplifiable-if-statement",
"Used when an if statement can be replaced with " "'bool(test)'. ",
"Used when an if statement can be replaced with 'bool(test)'. ",
{"old_names": [("R0102", "simplifiable-if-statement")]},
),
"R1704": (
Expand Down Expand Up @@ -203,7 +203,7 @@ class RefactoringChecker(checkers.BaseTokenChecker):
"R1719": (
"The if expression can be replaced with %s",
"simplifiable-if-expression",
"Used when an if expression can be replaced with " "'bool(test)'. ",
"Used when an if expression can be replaced with 'bool(test)'. ",
),
}
options = (
Expand All @@ -213,7 +213,7 @@ class RefactoringChecker(checkers.BaseTokenChecker):
"default": 5,
"type": "int",
"metavar": "<int>",
"help": "Maximum number of nested blocks for function / " "method body",
"help": "Maximum number of nested blocks for function / method body",
},
),
(
Expand Down Expand Up @@ -1155,7 +1155,7 @@ class NotChecker(checkers.BaseChecker):
"C0113": (
'Consider changing "%s" to "%s"',
"unneeded-not",
"Used when a boolean expression contains an unneeded " "negation.",
"Used when a boolean expression contains an unneeded negation.",
)
}
name = "basic"
Expand Down
4 changes: 2 additions & 2 deletions pylint/checkers/stdlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ class StdlibChecker(BaseChecker):
{"maxversion": (3, 5)},
),
"W1503": (
"Redundant use of %s with constant " "value %r",
"Redundant use of %s with constant value %r",
"redundant-unittest-assert",
"The first argument of assertTrue and assertFalse is "
"a condition. If a constant is passed as parameter, that "
Expand All @@ -119,7 +119,7 @@ class StdlibChecker(BaseChecker):
"By default, the first parameter is the group param, not the target param. ",
),
"W1507": (
"Using copy.copy(os.environ). Use os.environ.copy() " "instead. ",
"Using copy.copy(os.environ). Use os.environ.copy() instead. ",
"shallow-copy-environ",
"os.environ is not a dict object but proxy object, so "
"shallow copy has still effects on original object. "
Expand Down
Loading

0 comments on commit fcc0151

Please sign in to comment.