Skip to content

Commit 8145096

Browse files
committed
Added two new Python 3 porting checks, exception-escape and comprehension-escape
These two are emitted whenever pylint detects that a variable defined in the said blocks is used outside of the given block. On Python 3 these values are deleted.
1 parent 492e26e commit 8145096

File tree

3 files changed

+67
-1
lines changed

3 files changed

+67
-1
lines changed

ChangeLog

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ What's New in Pylint 2.0?
1010

1111
Close #1793
1212

13+
* Added two new Python 3 porting checks, `exception-escape` and `comprehension-escape`
14+
15+
These two are emitted whenever pylint detects that a variable defined in the
16+
said blocks is used outside of the given block. On Python 3 these values are deleted.
17+
1318
* Added a new `deprecated-sys-function`, emitted when accessing removed sys members.
1419

1520
* Added `xreadlines-attribute`, emitted when the `xreadlines()` attribute is accessed.

pylint/checkers/python3.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,16 @@ class Python3Checker(checkers.BaseChecker):
412412
'deprecated-sys-function',
413413
'Used when accessing a field on sys module that has been '
414414
'removed in Python 3.',),
415+
'W1661': ('Using an exception object that was bound by an except handler',
416+
'exception-escape',
417+
'Emitted when using an exception, that was bound in an except handler, outside '
418+
'of the except handler. On Python 3 these exceptions will be deleted once they get out '
419+
'of the except handler.'),
420+
'W1662': ('Using a variable that was bound inside a comprehension',
421+
'comprehension-escape',
422+
'Emitted when using a variable, that was bound in a comprehension handler, outside '
423+
'of the comprehension itself. On Python 3 these variables will be deleted outside of the '
424+
'comprehension.'),
415425
}
416426

417427
_bad_builtins = frozenset([
@@ -615,12 +625,28 @@ def visit_arguments(self, node):
615625

616626
def visit_name(self, node):
617627
"""Detect when a "bad" built-in is referenced."""
618-
found_node = node.lookup(node.name)[0]
628+
found_node, located_statements = node.lookup(node.name)
619629
if _is_builtin(found_node):
620630
if node.name in self._bad_builtins:
621631
message = node.name.lower() + '-builtin'
622632
self.add_message(message, node=node)
623633

634+
if len(located_statements) == 1:
635+
assign_statement = located_statements[0].statement()
636+
if isinstance(assign_statement, astroid.ExceptHandler):
637+
current = node
638+
while current and not isinstance(current.parent, astroid.ExceptHandler):
639+
current = current.parent
640+
641+
if current and isinstance(current.parent, astroid.ExceptHandler):
642+
return
643+
self.add_message('exception-escape', node=node)
644+
645+
if isinstance(assign_statement, (astroid.Expr, astroid.Assign)):
646+
if (isinstance(assign_statement.value, astroid.ListComp)
647+
and not assign_statement.parent_of(node)):
648+
self.add_message('comprehension-escape', node=node)
649+
624650
@utils.check_messages('print-statement')
625651
def visit_print(self, node):
626652
self.add_message('print-statement', node=node, always_warn=True)

pylint/test/unittest_checker_python3.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,41 @@ def test_bad_operator_attribute(self):
711711
with self.assertAddsMessages(message):
712712
self.checker.visit_attribute(node)
713713

714+
def test_comprehension_escape(self):
715+
list_comp, set_comp, dict_comp = astroid.extract_node('''
716+
[i for i in range(10)]
717+
i #@
718+
{c for c in range(10)}
719+
c #@
720+
{j:j for j in range(10)}
721+
j #@
722+
''')
723+
message = testutils.Message('comprehension-escape', node=list_comp)
724+
with self.assertAddsMessages(message):
725+
self.checker.visit_name(list_comp)
726+
727+
for node in (set_comp, dict_comp):
728+
with self.assertNoMessages():
729+
self.checker.visit_name(node)
730+
731+
def test_exception_escape(self):
732+
bad, good = astroid.extract_node('''
733+
try: 1/0
734+
except ValueError as exc:
735+
pass
736+
exc #@
737+
try:
738+
2/0
739+
except (ValueError, TypeError) as exc:
740+
exc = 2
741+
exc #@
742+
''')
743+
message = testutils.Message('exception-escape', node=bad)
744+
with self.assertAddsMessages(message):
745+
self.checker.visit_name(bad)
746+
with self.assertNoMessages():
747+
self.checker.visit_name(good)
748+
714749
def test_bad_sys_attribute(self):
715750
node = astroid.extract_node('''
716751
import sys

0 commit comments

Comments
 (0)