Skip to content

Commit 0479ee2

Browse files
committed
2 parents e4a9f61 + 5e917d1 commit 0479ee2

21 files changed

Lines changed: 380 additions & 121 deletions

.travis.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
language: python
2+
3+
python:
4+
- "2.7"
5+
6+
# Twisted is required for trial.
7+
install:
8+
- pip install --upgrade twisted==13.2.0 pyflakes==0.8.1 .
9+
10+
script:
11+
- trial twistedchecker
12+
- python ./check_pyflakes.py ./twistedchecker setup.py
13+
14+
# Dump trial log on failure.
15+
after_failure: "cat _trial_temp/test.log"

README.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ TwistedChecker's dependencies are pylint (== 0.26.0) and a recent version of PEP
1414
Tests
1515
-----
1616

17+
.. image:: https://travis-ci.org/twisted/twistedchecker.svg?branch=master
18+
:target: https://travis-ci.org/twisted/twistedchecker
19+
1720
To test twistedchecker, run the following in the source directory::
1821

1922
trial twistedchecker

check_pyflakes.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#!/usr/bin/env python
2+
"""
3+
API for the command-line I{pyflakes} tool.
4+
5+
This is a copy of pyflakes/api.py file which excludes some files
6+
for twistedchecker.
7+
"""
8+
from __future__ import with_statement
9+
10+
import sys
11+
import os
12+
import re
13+
import _ast
14+
from optparse import OptionParser
15+
16+
from pyflakes import checker, __version__
17+
from pyflakes import reporter as modReporter
18+
19+
__all__ = ['check', 'checkPath', 'checkRecursive', 'iterSourceCode', 'main']
20+
21+
22+
RE_EXCLUDE = '.*functionaltests.*'
23+
24+
25+
def check(codeString, filename, reporter=None):
26+
"""
27+
Check the Python source given by C{codeString} for flakes.
28+
29+
@param codeString: The Python source to check.
30+
@type codeString: C{str}
31+
32+
@param filename: The name of the file the source came from, used to report
33+
errors.
34+
@type filename: C{str}
35+
36+
@param reporter: A L{Reporter} instance, where errors and warnings will be
37+
reported.
38+
39+
@return: The number of warnings emitted.
40+
@rtype: C{int}
41+
"""
42+
if reporter is None:
43+
reporter = modReporter._makeDefaultReporter()
44+
# First, compile into an AST and handle syntax errors.
45+
try:
46+
tree = compile(codeString, filename, "exec", _ast.PyCF_ONLY_AST)
47+
except SyntaxError:
48+
value = sys.exc_info()[1]
49+
msg = value.args[0]
50+
51+
(lineno, offset, text) = value.lineno, value.offset, value.text
52+
53+
# If there's an encoding problem with the file, the text is None.
54+
if text is None:
55+
# Avoid using msg, since for the only known case, it contains a
56+
# bogus message that claims the encoding the file declared was
57+
# unknown.
58+
reporter.unexpectedError(filename, 'problem decoding source')
59+
else:
60+
reporter.syntaxError(filename, msg, lineno, offset, text)
61+
return 1
62+
except Exception:
63+
reporter.unexpectedError(filename, 'problem decoding source')
64+
return 1
65+
# Okay, it's syntactically valid. Now check it.
66+
w = checker.Checker(tree, filename)
67+
w.messages.sort(key=lambda m: m.lineno)
68+
for warning in w.messages:
69+
reporter.flake(warning)
70+
return len(w.messages)
71+
72+
73+
def checkPath(filename, reporter=None):
74+
"""
75+
Check the given path, printing out any warnings detected.
76+
77+
@param reporter: A L{Reporter} instance, where errors and warnings will be
78+
reported.
79+
80+
@return: the number of warnings printed
81+
"""
82+
if reporter is None:
83+
reporter = modReporter._makeDefaultReporter()
84+
try:
85+
with open(filename, 'rb') as f:
86+
codestr = f.read()
87+
if sys.version_info < (2, 7):
88+
codestr += '\n' # Work around for Python <= 2.6
89+
except UnicodeError:
90+
reporter.unexpectedError(filename, 'problem decoding source')
91+
return 1
92+
except IOError:
93+
msg = sys.exc_info()[1]
94+
reporter.unexpectedError(filename, msg.args[1])
95+
return 1
96+
return check(codestr, filename, reporter)
97+
98+
99+
def iterSourceCode(paths):
100+
"""
101+
Iterate over all Python source files in C{paths}.
102+
103+
@param paths: A list of paths. Directories will be recursed into and
104+
any .py files found will be yielded. Any non-directories will be
105+
yielded as-is.
106+
"""
107+
for path in paths:
108+
if os.path.isdir(path):
109+
for dirpath, dirnames, filenames in os.walk(path):
110+
for filename in filenames:
111+
if filename.endswith('.py'):
112+
yield os.path.join(dirpath, filename)
113+
else:
114+
yield path
115+
116+
117+
def checkRecursive(paths, reporter):
118+
"""
119+
Recursively check all source files in C{paths}.
120+
121+
@param paths: A list of paths to Python source files and directories
122+
containing Python source files.
123+
@param reporter: A L{Reporter} where all of the warnings and errors
124+
will be reported to.
125+
@return: The number of warnings found.
126+
"""
127+
warnings = 0
128+
for sourcePath in iterSourceCode(paths):
129+
if re.search(RE_EXCLUDE, sourcePath):
130+
continue
131+
warnings += checkPath(sourcePath, reporter)
132+
return warnings
133+
134+
135+
def main(prog=None):
136+
parser = OptionParser(prog=prog, version=__version__)
137+
(__, args) = parser.parse_args()
138+
reporter = modReporter._makeDefaultReporter()
139+
if args:
140+
warnings = checkRecursive(args, reporter)
141+
else:
142+
warnings = check(sys.stdin.read(), '<stdin>', reporter)
143+
raise SystemExit(warnings > 0)
144+
145+
146+
if __name__ == "__main__":
147+
main()

setup.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@
1212
version='0.2.0',
1313
author='Twisted Matrix Laboratories',
1414
author_email='twisted-python@twistedmatrix.com',
15-
url='https://launchpad.net/twistedchecker',
15+
url='https://github.com/twisted/twistedchecker',
1616
packages=find_packages(),
1717
package_data={
1818
"twistedchecker": ["configuration/pylintrc"]
19-
},
19+
},
2020
scripts=[
2121
'bin/twistedchecker'
22-
],
22+
],
2323
license='MIT',
2424
classifiers=[
2525
"Development Status :: 3 - Alpha",
@@ -31,13 +31,14 @@
3131
"Programming Language :: Python :: 2.6",
3232
"Programming Language :: Python :: 2.7",
3333
"Topic :: Software Development :: Quality Assurance"
34-
],
34+
],
3535
keywords=[
3636
"twisted", "checker", "compliance", "pep8"
37-
],
37+
],
3838
install_requires=[
3939
"pylint == 0.26.0",
40-
"pep8"
41-
],
40+
"logilab-common == 0.62.0",
41+
"pep8 == 1.5.7"
42+
],
4243
long_description=file('README.rst').read()
43-
)
44+
)

twistedchecker/checkers/comment.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
1-
import sys
2-
import re
3-
4-
from logilab import astng
5-
from logilab.common.ureports import Table
6-
from logilab.astng import are_exclusive
7-
81
from pylint.interfaces import IASTNGChecker
9-
from pylint.reporters import diff_string
10-
from pylint.checkers import BaseChecker, EmptyReport
2+
from pylint.checkers import BaseChecker
113
from pylint.checkers.format import STRING_RGX, COMMENT_RGX
124

5+
6+
137
class CommentChecker(BaseChecker):
148
"""
159
A checker for checking comment issues.

twistedchecker/checkers/docstring.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
1-
import sys
21
import re
32

4-
from logilab import astng
5-
from logilab.common.ureports import Table
6-
from logilab.astng import are_exclusive
73
from logilab.astng import node_classes
84
from logilab.astng import scoped_nodes
95

106
from pylint.interfaces import IASTNGChecker
11-
from pylint.reporters import diff_string
127
from pylint.checkers.base import DocStringChecker as PylintDocStringChecker
138
from pylint.checkers.base import NO_REQUIRED_DOC_RGX
149

10+
11+
1512
class DocstringChecker(PylintDocStringChecker):
1613
"""
1714
A checker for checking docstrings.

twistedchecker/checkers/formattingoperation.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,8 @@
1-
import sys
2-
import re
3-
4-
from logilab import astng
5-
from logilab.common.ureports import Table
6-
from logilab.astng import are_exclusive
7-
from logilab.astng import node_classes
8-
91
from pylint.interfaces import IASTNGChecker
10-
from pylint.reporters import diff_string
112
from pylint.checkers.string_format import StringFormatChecker
123

134

5+
146
class FormattingOperationChecker(StringFormatChecker):
157
"""
168
When string formatting operations are used like formatString % values,

twistedchecker/checkers/header.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
1-
import sys
21
import re
32

4-
from logilab import astng
5-
from logilab.common.ureports import Table
6-
from logilab.astng import are_exclusive
7-
83
from pylint.interfaces import IASTNGChecker
9-
from pylint.reporters import diff_string
10-
from pylint.checkers import BaseChecker, EmptyReport
4+
from pylint.checkers import BaseChecker
115

126
from twistedchecker.core.util import isTestModule, moduleNeedsTests
137

8+
149
class HeaderChecker(BaseChecker):
1510
"""
1611
A checker for checking headers.

twistedchecker/checkers/modulename.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
1-
import sys
21
import re
32

4-
from logilab import astng
5-
from logilab.common.ureports import Table
6-
from logilab.astng import are_exclusive
7-
83
from pylint.interfaces import IASTNGChecker
9-
from pylint.reporters import diff_string
10-
from pylint.checkers import BaseChecker, EmptyReport
4+
from pylint.checkers import BaseChecker
5+
6+
from twistedchecker.core.util import isTestModule
117

12-
from twistedchecker.core.util import isTestModule, moduleNeedsTests
138

149
class ModuleNameChecker(BaseChecker):
1510
"""
@@ -41,7 +36,6 @@ def visit_module(self, node):
4136
@param node: node of current module
4237
"""
4338
modulename = node.name.split(".")[-1]
44-
codeOfModule = node.file_stream.read()
4539
if isTestModule(node.name) and self.moduleContainsTestCase(node):
4640
self._checkTestModuleName(modulename, node)
4741

twistedchecker/checkers/pep8format.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ def _outputMessages(self, warnings):
222222

223223

224224
def modifiedBlankLines(logical_line, blank_lines, indent_level, line_number,
225-
previous_logical, previous_indent_level):
225+
blank_before, previous_logical, previous_indent_level):
226226
"""
227227
This function is copied from a modified pep8 checker for Twisted.
228228
See https://github.com/cyli/TwistySublime/blob/master/twisted_pep8.py
@@ -253,6 +253,7 @@ def modifiedBlankLines(logical_line, blank_lines, indent_level, line_number,
253253
@param blank_lines: Supplied by PEP8.
254254
@param indent_level: Supplied by PEP8. The current indent level.
255255
@param line_number: Supplied by PEP8. The current line number.
256+
@param blank_before: Supplied by PEP8. The number of blank lines before this one.
256257
@param previous_logical: Supplied by PEP8. The previous logical line.
257258
@param previous_indent_level: Supplied by PEP8. The indent level of the previous line.
258259
"""
@@ -265,39 +266,37 @@ def isClassDefDecorator(thing):
265266
if line_number == 1:
266267
return
267268

268-
blank_lines_before_comment = 0
269-
max_blank_lines = max(blank_lines, blank_lines_before_comment)
270269
previous_is_comment = pep8.DOCSTRING_REGEX.match(previous_logical)
271270

272271
# Check blank lines after a decorator,
273272
if previous_logical.startswith('@'):
274-
if max_blank_lines:
273+
if blank_before:
275274
yield 0, "E304 blank lines found after function decorator"
276275

277276
if isClassDefDecorator(logical_line):
278277
if indent_level:
279278
# There should only be 1 line or less between docstrings and
280279
# the next function
281280
if previous_is_comment:
282-
if max_blank_lines > 1:
281+
if blank_before > 1:
283282
yield 0, (
284283
"E305 too many blank lines after docstring "
285-
"(%d)" % (max_blank_lines,))
284+
"(%d)" % (blank_before,))
286285

287286
# Between first level functions, there should be 2 blank lines.
288287
# any further indended functions can have one or zero lines
289288
else:
290-
if not (max_blank_lines == 2 or
289+
if not (blank_before == 2 or
291290
indent_level > 4 or
292291
previous_indent_level <= indent_level):
293292
yield 0, ("E301 expected 2 blank lines, "
294-
"found %d" % (max_blank_lines,))
293+
"found %d" % (blank_before,))
295294

296295
# Top level, there should be 3 blank lines between class/function
297296
# definitions (but not necessarily after variable declarations)
298-
elif previous_indent_level and max_blank_lines != 3:
297+
elif previous_indent_level and blank_before != 3:
299298
yield 0, ("E302 expected 3 blank lines, "
300-
"found %d" % (max_blank_lines,))
299+
"found %d" % (blank_before,))
301300

302-
elif max_blank_lines > 1 and indent_level:
303-
yield 0, "E303 too many blank lines (%d)" % (max_blank_lines,)
301+
elif blank_before > 1 and indent_level:
302+
yield 0, "E303 too many blank lines (%d)" % (blank_before,)

0 commit comments

Comments
 (0)