Skip to content

Commit af6ac30

Browse files
author
Vladimir Rudnyh
committed
Update pyflakes to 1.0.0
1 parent 02c638c commit af6ac30

File tree

9 files changed

+383
-35
lines changed

9 files changed

+383
-35
lines changed

contrib/pyflakes/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
2-
__version__ = '0.8.2a0'
1+
__version__ = '1.0.0'

contrib/pyflakes/api.py

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,16 @@ def checkPath(filename, reporter=None):
7373
if reporter is None:
7474
reporter = modReporter._makeDefaultReporter()
7575
try:
76-
with open(filename, 'rb') as f:
76+
# in Python 2.6, compile() will choke on \r\n line endings. In later
77+
# versions of python it's smarter, and we want binary mode to give
78+
# compile() the best opportunity to do the right thing WRT text
79+
# encodings.
80+
if sys.version_info < (2, 7):
81+
mode = 'rU'
82+
else:
83+
mode = 'rb'
84+
85+
with open(filename, mode) as f:
7786
codestr = f.read()
7887
if sys.version_info < (2, 7):
7988
codestr += '\n' # Work around for Python <= 2.6
@@ -121,17 +130,40 @@ def checkRecursive(paths, reporter):
121130
return warnings
122131

123132

133+
def _exitOnSignal(sigName, message):
134+
"""Handles a signal with sys.exit.
135+
136+
Some of these signals (SIGPIPE, for example) don't exist or are invalid on
137+
Windows. So, ignore errors that might arise.
138+
"""
139+
import signal
140+
141+
try:
142+
sigNumber = getattr(signal, sigName)
143+
except AttributeError:
144+
# the signal constants defined in the signal module are defined by
145+
# whether the C library supports them or not. So, SIGPIPE might not
146+
# even be defined.
147+
return
148+
149+
def handler(sig, f):
150+
sys.exit(message)
151+
152+
try:
153+
signal.signal(sigNumber, handler)
154+
except ValueError:
155+
# It's also possible the signal is defined, but then it's invalid. In
156+
# this case, signal.signal raises ValueError.
157+
pass
158+
159+
124160
def main(prog=None):
125161
"""Entry point for the script "pyflakes"."""
126162
import optparse
127-
import signal
128163

129164
# Handle "Keyboard Interrupt" and "Broken pipe" gracefully
130-
try:
131-
signal.signal(signal.SIGINT, lambda sig, f: sys.exit('... stopped'))
132-
signal.signal(signal.SIGPIPE, lambda sig, f: sys.exit(1))
133-
except ValueError:
134-
pass # SIGPIPE is not supported on Windows
165+
_exitOnSignal('SIGINT', '... stopped')
166+
_exitOnSignal('SIGPIPE', 1)
135167

136168
parser = optparse.OptionParser(prog=prog, version=__version__)
137169
(__, args) = parser.parse_args()

contrib/pyflakes/checker.py

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,10 @@ def addBinding(self, node, value):
448448
elif isinstance(existing, Importation) and value.redefines(existing):
449449
existing.redefined.append(node)
450450

451+
if value.name in self.scope:
452+
# then assume the rebound name is used as a global or within a loop
453+
value.used = self.scope[value.name].used
454+
451455
self.scope[value.name] = value
452456

453457
def getNodeHandler(self, node_class):
@@ -471,7 +475,7 @@ def handleNodeLoad(self, node):
471475
return
472476

473477
scopes = [scope for scope in self.scopeStack[:-1]
474-
if isinstance(scope, (FunctionScope, ModuleScope))]
478+
if isinstance(scope, (FunctionScope, ModuleScope, GeneratorScope))]
475479
if isinstance(self.scope, GeneratorScope) and scopes[-1] != self.scopeStack[-2]:
476480
scopes.append(self.scopeStack[-2])
477481

@@ -526,14 +530,30 @@ def handleNodeStore(self, node):
526530
binding = ExportBinding(name, node.parent, self.scope)
527531
else:
528532
binding = Assignment(name, node)
529-
if name in self.scope:
530-
binding.used = self.scope[name].used
531533
self.addBinding(node, binding)
532534

533535
def handleNodeDelete(self, node):
536+
537+
def on_conditional_branch():
538+
"""
539+
Return `True` if node is part of a conditional body.
540+
"""
541+
current = getattr(node, 'parent', None)
542+
while current:
543+
if isinstance(current, (ast.If, ast.While, ast.IfExp)):
544+
return True
545+
current = getattr(current, 'parent', None)
546+
return False
547+
534548
name = getNodeName(node)
535549
if not name:
536550
return
551+
552+
if on_conditional_branch():
553+
# We can not predict if this conditional branch is going to
554+
# be executed.
555+
return
556+
537557
if isinstance(self.scope, FunctionScope) and name in self.scope.globals:
538558
self.scope.globals.remove(name)
539559
else:
@@ -630,8 +650,9 @@ def ignore(self, node):
630650
pass
631651

632652
# "stmt" type nodes
633-
DELETE = PRINT = FOR = WHILE = IF = WITH = WITHITEM = RAISE = \
634-
TRYFINALLY = ASSERT = EXEC = EXPR = ASSIGN = handleChildren
653+
DELETE = PRINT = FOR = ASYNCFOR = WHILE = IF = WITH = WITHITEM = \
654+
ASYNCWITH = ASYNCWITHITEM = RAISE = TRYFINALLY = ASSERT = EXEC = \
655+
EXPR = ASSIGN = handleChildren
635656

636657
CONTINUE = BREAK = PASS = ignore
637658

@@ -654,14 +675,36 @@ def ignore(self, node):
654675
EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = ignore
655676

656677
# additional node types
657-
LISTCOMP = COMPREHENSION = KEYWORD = handleChildren
678+
COMPREHENSION = KEYWORD = handleChildren
658679

659680
def GLOBAL(self, node):
660681
"""
661682
Keep track of globals declarations.
662683
"""
663-
if isinstance(self.scope, FunctionScope):
664-
self.scope.globals.update(node.names)
684+
# In doctests, the global scope is an anonymous function at index 1.
685+
global_scope_index = 1 if self.withDoctest else 0
686+
global_scope = self.scopeStack[global_scope_index]
687+
688+
# Ignore 'global' statement in global scope.
689+
if self.scope is not global_scope:
690+
691+
# One 'global' statement can bind multiple (comma-delimited) names.
692+
for node_name in node.names:
693+
node_value = Assignment(node_name, node)
694+
695+
# Remove UndefinedName messages already reported for this name.
696+
self.messages = [
697+
m for m in self.messages if not
698+
isinstance(m, messages.UndefinedName) and not
699+
m.message_args[0] == node_name]
700+
701+
# Bind name to global scope if it doesn't exist already.
702+
global_scope.setdefault(node_name, node_value)
703+
704+
# Bind name to non-global scopes, but as already "used".
705+
node_value.used = (global_scope, node)
706+
for scope in self.scopeStack[global_scope_index + 1:]:
707+
scope[node_name] = node_value
665708

666709
NONLOCAL = GLOBAL
667710

@@ -670,6 +713,8 @@ def GENERATOREXP(self, node):
670713
self.handleChildren(node)
671714
self.popScope()
672715

716+
LISTCOMP = handleChildren if PY2 else GENERATOREXP
717+
673718
DICTCOMP = SETCOMP = GENERATOREXP
674719

675720
def NAME(self, node):
@@ -693,6 +738,10 @@ def NAME(self, node):
693738
raise RuntimeError("Got impossible expression context: %r" % (node.ctx,))
694739

695740
def RETURN(self, node):
741+
if isinstance(self.scope, ClassScope):
742+
self.report(messages.ReturnOutsideFunction, node)
743+
return
744+
696745
if (
697746
node.value and
698747
hasattr(self.scope, 'returnValue') and
@@ -705,7 +754,7 @@ def YIELD(self, node):
705754
self.scope.isGenerator = True
706755
self.handleNode(node.value, node)
707756

708-
YIELDFROM = YIELD
757+
AWAIT = YIELDFROM = YIELD
709758

710759
def FUNCTIONDEF(self, node):
711760
for deco in node.decorator_list:
@@ -715,6 +764,8 @@ def FUNCTIONDEF(self, node):
715764
if self.withDoctest:
716765
self.deferFunction(lambda: self.handleDoctests(node))
717766

767+
ASYNCFUNCTIONDEF = FUNCTIONDEF
768+
718769
def LAMBDA(self, node):
719770
args = []
720771
annotations = []

contrib/pyflakes/messages.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,6 @@ def __init__(self, filename, loc, name):
100100
self.message_args = (name,)
101101

102102

103-
class Redefined(Message):
104-
message = 'redefinition of %r from line %r'
105-
106-
def __init__(self, filename, loc, name, orig_loc):
107-
Message.__init__(self, filename, loc)
108-
self.message_args = (name, orig_loc.lineno)
109-
110-
111103
class LateFutureImport(Message):
112104
message = 'future import(s) %r after other statements'
113105

@@ -133,3 +125,10 @@ class ReturnWithArgsInsideGenerator(Message):
133125
Indicates a return statement with arguments inside a generator.
134126
"""
135127
message = '\'return\' with argument inside generator'
128+
129+
130+
class ReturnOutsideFunction(Message):
131+
"""
132+
Indicates a return statement outside of a function/method.
133+
"""
134+
message = '\'return\' outside function'

contrib/pyflakes/test/test_api.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
checkRecursive,
1616
iterSourceCode,
1717
)
18-
from pyflakes.test.harness import TestCase
18+
from pyflakes.test.harness import TestCase, skipIf
1919

2020
if sys.version_info < (3,):
2121
from cStringIO import StringIO
@@ -385,12 +385,18 @@ def test_nonKeywordAfterKeywordSyntaxError(self):
385385
sourcePath = self.makeTempFile(source)
386386
last_line = ' ^\n' if sys.version_info >= (3, 2) else ''
387387
column = '13:' if sys.version_info >= (3, 2) else ''
388+
389+
if sys.version_info >= (3, 5):
390+
message = 'positional argument follows keyword argument'
391+
else:
392+
message = 'non-keyword arg after keyword arg'
393+
388394
self.assertHasErrors(
389395
sourcePath,
390396
["""\
391-
%s:1:%s non-keyword arg after keyword arg
397+
%s:1:%s %s
392398
foo(bar=baz, bax)
393-
%s""" % (sourcePath, column, last_line)])
399+
%s""" % (sourcePath, column, message, last_line)])
394400

395401
def test_invalidEscape(self):
396402
"""
@@ -413,6 +419,7 @@ def test_invalidEscape(self):
413419
self.assertHasErrors(
414420
sourcePath, [decoding_error])
415421

422+
@skipIf(sys.platform == 'win32', 'unsupported on Windows')
416423
def test_permissionDenied(self):
417424
"""
418425
If the source file is not readable, this is reported on standard
@@ -449,6 +456,13 @@ def test_encodedFileUTF8(self):
449456
sourcePath = self.makeTempFile(source)
450457
self.assertHasErrors(sourcePath, [])
451458

459+
def test_CRLFLineEndings(self):
460+
"""
461+
Source files with Windows CR LF line endings are parsed successfully.
462+
"""
463+
sourcePath = self.makeTempFile("x = 42\r\n")
464+
self.assertHasErrors(sourcePath, [])
465+
452466
def test_misencodedFileUTF8(self):
453467
"""
454468
If a source file contains bytes which cannot be decoded, this is
@@ -570,7 +584,7 @@ def test_fileWithFlakes(self):
570584
fd.close()
571585
d = self.runPyflakes([self.tempfilepath])
572586
expected = UnusedImport(self.tempfilepath, Node(1), 'contraband')
573-
self.assertEqual(d, ("%s\n" % expected, '', 1))
587+
self.assertEqual(d, ("%s%s" % (expected, os.linesep), '', 1))
574588

575589
def test_errors(self):
576590
"""
@@ -579,7 +593,8 @@ def test_errors(self):
579593
printed to stderr.
580594
"""
581595
d = self.runPyflakes([self.tempfilepath])
582-
error_msg = '%s: No such file or directory\n' % (self.tempfilepath,)
596+
error_msg = '%s: No such file or directory%s' % (self.tempfilepath,
597+
os.linesep)
583598
self.assertEqual(d, ('', error_msg, 1))
584599

585600
def test_readFromStdin(self):
@@ -588,4 +603,4 @@ def test_readFromStdin(self):
588603
"""
589604
d = self.runPyflakes([], stdin='import contraband'.encode('ascii'))
590605
expected = UnusedImport('<stdin>', Node(1), 'contraband')
591-
self.assertEqual(d, ("%s\n" % expected, '', 1))
606+
self.assertEqual(d, ("%s%s" % (expected, os.linesep), '', 1))

contrib/pyflakes/test/test_doctests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def doctest_stuff():
6464
"""
6565
6666
foo
67-
''', m.Redefined)
67+
''', m.RedefinedWhileUnused)
6868

6969
def test_importInDoctestAndAfter(self):
7070
self.flakes('''

contrib/pyflakes/test/test_imports.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,8 @@ def test_usedInListComp(self):
473473
self.flakes('import fu; [fu for _ in range(1)]')
474474
self.flakes('import fu; [1 for _ in range(1) if fu]')
475475

476+
@skipIf(version_info >= (3,),
477+
'in Python 3 list comprehensions execute in a separate scope')
476478
def test_redefinedByListComp(self):
477479
self.flakes('import fu; [1 for fu in range(1)]',
478480
m.RedefinedInListComp)
@@ -503,11 +505,36 @@ def test_usedInWhile(self):
503505
''')
504506

505507
def test_usedInGlobal(self):
508+
"""
509+
A 'global' statement shadowing an unused import should not prevent it
510+
from being reported.
511+
"""
506512
self.flakes('''
507513
import fu
508514
def f(): global fu
509515
''', m.UnusedImport)
510516

517+
def test_usedAndGlobal(self):
518+
"""
519+
A 'global' statement shadowing a used import should not cause it to be
520+
reported as unused.
521+
"""
522+
self.flakes('''
523+
import foo
524+
def f(): global foo
525+
def g(): foo.is_used()
526+
''')
527+
528+
def test_assignedToGlobal(self):
529+
"""
530+
Binding an import to a declared global should not cause it to be
531+
reported as unused.
532+
"""
533+
self.flakes('''
534+
def f(): global foo; import foo
535+
def g(): foo.is_used()
536+
''')
537+
511538
@skipIf(version_info >= (3,), 'deprecated syntax')
512539
def test_usedInBackquote(self):
513540
self.flakes('import fu; `fu`')

0 commit comments

Comments
 (0)