##// END OF EJS Templates
check-code: catch Python 'is' comparing number or string literals...
check-code: catch Python 'is' comparing number or string literals The Python 'is' operator compares object identity, so it should definitely not be applied to string or number literals, which Python implementations are free to represent with a temporary object. This should catch the following kinds of bogus expressions (examples): x is 'foo' x is not 'foo' x is "bar" x is not "bar" x is 42 x is not 42 x is -36 x is not -36 As originally proposed by Martin Geisler, amended with catching negative numbers.

File last commit:

r13026:53391819 default
r13026:53391819 default
Show More
check-code.py
301 lines | 10.7 KiB | text/x-python | PythonLexer
Matt Mackall
Introduce check-code.py...
r10281 #!/usr/bin/env python
#
# check-code - a style and portability checker for Mercurial
#
Matt Mackall
check-code: fix copyright date
r10290 # Copyright 2010 Matt Mackall <mpm@selenic.com>
Matt Mackall
Introduce check-code.py...
r10281 #
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
Alecs King
check-code: add exit status...
r11816 import re, glob, os, sys
Matt Mackall
check-code: add a warnings level...
r10895 import optparse
Matt Mackall
Introduce check-code.py...
r10281
def repquote(m):
Benoit Boissinot
check-code: improve quote detection regexp, add tests
r10722 t = re.sub(r"\w", "x", m.group('text'))
Matt Mackall
check-code: two more rules...
r10451 t = re.sub(r"[^\sx]", "o", t)
Benoit Boissinot
check-code: improve quote detection regexp, add tests
r10722 return m.group('quote') + t + m.group('quote')
Matt Mackall
Introduce check-code.py...
r10281
Benoit Boissinot
check-code: more tests and more robust python filtering
r10727 def reppython(m):
comment = m.group('comment')
if comment:
return "#" * len(comment)
return repquote(m)
Matt Mackall
Introduce check-code.py...
r10281
def repcomment(m):
return m.group(1) + "#" * len(m.group(2))
def repccomment(m):
t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
return m.group(1) + t + "*/"
def repcallspaces(m):
t = re.sub(r"\n\s+", "\n", m.group(2))
return m.group(1) + t
def repinclude(m):
return m.group(1) + "<foo>"
def rephere(m):
t = re.sub(r"\S", "x", m.group(2))
return m.group(1) + t
testpats = [
Martin Geisler
check-code.py: make help strings consistent
r10374 (r'(pushd|popd)', "don't use 'pushd' or 'popd', use 'cd'"),
(r'\W\$?\(\([^\)]*\)\)', "don't use (()) or $(()), use 'expr'"),
Matt Mackall
Introduce check-code.py...
r10281 (r'^function', "don't use 'function', use old style"),
Martin Geisler
check-code.py: make help strings consistent
r10374 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
Mads Kiilerich
check-code.py: escape backslash
r10373 (r'echo.*\\n', "don't use 'echo \\n', use printf"),
Martin Geisler
check-code: catch "echo -n" in tests
r11884 (r'echo -n', "don't use 'echo -n', use printf"),
Martin Geisler
check-code.py: make help strings consistent
r10374 (r'^diff.*-\w*N', "don't use 'diff -N'"),
Matt Mackall
Introduce check-code.py...
r10281 (r'(^| )wc[^|]*$', "filter wc output"),
Martin Geisler
check-code.py: make help strings consistent
r10374 (r'head -c', "don't use 'head -c', use 'dd'"),
(r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
(r'printf.*\\\d\d\d', "don't use 'printf \NNN', use Python"),
(r'printf.*\\x', "don't use printf \\x, use Python"),
Matt Mackall
Introduce check-code.py...
r10281 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
(r'rm -rf \*', "don't use naked rm -rf, target a directory"),
(r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
"use egrep for extended grep syntax"),
(r'/bin/', "don't use explicit paths for tools"),
(r'\$PWD', "don't use $PWD, use `pwd`"),
(r'[^\n]\Z', "no trailing newline"),
Mads Kiilerich
test-merge-default and check-code.py: No "export x=x" in sh
r10658 (r'export.*=', "don't export and assign at once"),
Mads Kiilerich
check-code.py: Check for bare ^...
r10802 ('^([^"\']|("[^"]*")|(\'[^\']*\'))*\\^', "^ must be quoted"),
Yuya Nishihara
check-code: add check for 'source'
r11210 (r'^source\b', "don't use 'source', use '.'"),
Dan Villiom Podlaski Christiansen
tests: compatibility fix....
r12367 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
Martin Geisler
check-code: catch 'ls filename --option' case as per 6bdae8ea0b48
r13012 (r'ls\s+[^-]+\s+-', "options to 'ls' must come before filenames"),
Matt Mackall
Introduce check-code.py...
r10281 ]
testfilters = [
(r"( *)(#([^\n]*\S)?)", repcomment),
(r"<<(\S+)((.|\n)*?\n\1)", rephere),
]
Matt Mackall
check-code: add some basic support for unified tests
r12364 uprefix = r"^ \$ "
Adrian Buehlmann
check-code: add 'no tab indent' check for unified tests...
r12743 uprefixc = r"^ > "
Matt Mackall
check-code: add some basic support for unified tests
r12364 utestpats = [
Matt Mackall
check-code: warning and fixes for whitespace in unified tests
r12785 (r'^(\S| $ ).*(\S\s+|^\s+)\n', "trailing whitespace on non-output"),
Matt Mackall
tests: drop a bunch of sed calls from unified tests
r12366 (uprefix + r'.*\|\s*sed', "use regex test output patterns instead of sed"),
Matt Mackall
check-code: add some basic support for unified tests
r12364 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
(uprefix + r'.*\$\?', "explicit exit code checks unnecessary"),
(uprefix + r'.*\|\| echo.*(fail|error)',
"explicit exit code checks unnecessary"),
(uprefix + r'set -e', "don't use set -e"),
Adrian Buehlmann
check-code: add 'no tab indent' check for unified tests...
r12743 (uprefixc + r'( *)\t', "don't use tabs to indent"),
Matt Mackall
check-code: add some basic support for unified tests
r12364 ]
for p, m in testpats:
if p.startswith('^'):
p = uprefix + p[1:]
else:
p = uprefix + p
utestpats.append((p, m))
utestfilters = [
(r"( *)(#([^\n]*\S)?)", repcomment),
]
Matt Mackall
Introduce check-code.py...
r10281 pypats = [
Renato Cunha
check-code: check for tuple parameter unpacking (missing in py3k)
r11568 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
"tuple parameter unpacking not available in Python 3+"),
(r'lambda\s*\(.*,.*\)',
"tuple parameter unpacking not available in Python 3+"),
Renato Cunha
check-code: added a check for calls to the builtin cmp function
r11764 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
Renato Cunha
check-code: added check for reduce usage
r11569 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
Martin Geisler
check-code: catch dict.has_key
r11602 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
Matt Mackall
Introduce check-code.py...
r10281 (r'^\s*\t', "don't use tabs"),
Matt Mackall
check-code: import some pylint checks
r10412 (r'\S;\s*\n', "semicolon"),
Matt Mackall
Introduce check-code.py...
r10281 (r'\w,\w', "missing whitespace after ,"),
(r'\w[+/*\-<>]\w', "missing whitespace in expression"),
(r'^\s+\w+=\w+[^,)]$', "missing whitespace in assignment"),
(r'.{85}', "line too long"),
Matt Mackall
check-code: add warning on lines over 80 characters
r11672 (r'.{81}', "warning: line over 80 characters"),
Matt Mackall
Introduce check-code.py...
r10281 (r'[^\n]\Z', "no trailing newline"),
Martin Geisler
check-code: find trailing whitespace
r12770 (r'(\S\s+|^\s+)\n', "trailing whitespace"),
Matt Mackall
Introduce check-code.py...
r10281 # (r'^\s+[^_ ][^_. ]+_[^_]+\s*=', "don't use underbars in identifiers"),
# (r'\w*[a-z][A-Z]\w*\s*=', "don't use camelcase in identifiers"),
Matt Mackall
check-code: check thyself
r10286 (r'^\s*(if|while|def|class|except|try)\s[^[]*:\s*[^\]#\s]+',
"linebreak after :"),
Matt Mackall
Introduce check-code.py...
r10281 (r'class\s[^(]:', "old-style class, use class foo(object)"),
Matt Mackall
check-code: del isn't a function
r10291 (r'^\s+del\(', "del isn't a function"),
Martin Geisler
check-code: using and/or/not as a function is bad style
r12964 (r'\band\(', "and isn't a function"),
(r'\bor\(', "or isn't a function"),
(r'\bnot\(', "not isn't a function"),
Matt Mackall
Introduce check-code.py...
r10281 (r'^\s+except\(', "except isn't a function"),
Matt Mackall
check-code: import some pylint checks
r10412 (r',]', "unneeded trailing ',' in list"),
Matt Mackall
Introduce check-code.py...
r10281 # (r'class\s[A-Z][^\(]*\((?!Exception)',
# "don't capitalize non-exception classes"),
# (r'in range\(', "use xrange"),
# (r'^\s*print\s+', "avoid using print in core and extensions"),
(r'[\x80-\xff]', "non-ASCII character literal"),
(r'("\')\.format\(', "str.format() not available in Python 2.4"),
(r'^\s*with\s+', "with not available in Python 2.4"),
Martin Geisler
check-code: reformat long lines
r11345 (r'(?<!def)\s+(any|all|format)\(',
"any/all/format not available in Python 2.4"),
Martin Geisler
check-code: add test for callable
r11522 (r'(?<!def)\s+(callable)\(',
"callable not available in Python 3, use hasattr(f, '__call__')"),
Matt Mackall
Introduce check-code.py...
r10281 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
(r'([\(\[]\s\S)|(\S\s[\)\]])', "gratuitous whitespace in () or []"),
# (r'\s\s=', "gratuitous whitespace before ="),
Martin Geisler
check-code: reformat long lines
r11345 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
"missing whitespace around operator"),
(r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\s',
"missing whitespace around operator"),
(r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
"missing whitespace around operator"),
(r'[^+=*!<>&| -](\s=|=\s)[^= ]',
"wrong whitespace around ="),
Matt Mackall
check-code: two more rules...
r10451 (r'raise Exception', "don't raise generic exceptions"),
Martin Geisler
check-code: warn about untranslated ui.warn calls
r11599 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
Matt Mackall
check-code: add a warnings level...
r10895 "warning: unwrapped ui message"),
Adrian Buehlmann
check-code: catch Python 'is' comparing number or string literals...
r13026 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
Matt Mackall
Introduce check-code.py...
r10281 ]
pyfilters = [
Benoit Boissinot
check-code: more tests and more robust python filtering
r10727 (r"""(?msx)(?P<comment>\#.*?$)|
((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
(?P<text>(([^\\]|\\.)*?))
(?P=quote))""", reppython),
Matt Mackall
Introduce check-code.py...
r10281 ]
cpats = [
(r'//', "don't use //-style comments"),
(r'^ ', "don't use spaces to indent"),
(r'\S\t', "don't use tabs except for indent"),
(r'(\S\s+|^\s+)\n', "trailing whitespace"),
(r'.{85}', "line too long"),
(r'(while|if|do|for)\(', "use space after while/if/do/for"),
(r'return\(', "return is not a function"),
(r' ;', "no space before ;"),
(r'\w+\* \w+', "use int *foo, not int* foo"),
(r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
(r'\S+ (\+\+|--)', "use foo++, not foo ++"),
(r'\w,\w', "missing whitespace after ,"),
(r'\w[+/*]\w', "missing whitespace in expression"),
(r'^#\s+\w', "use #foo, not # foo"),
(r'[^\n]\Z', "no trailing newline"),
]
cfilters = [
(r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
Benoit Boissinot
check-code: improve quote detection regexp, add tests
r10722 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
Matt Mackall
Introduce check-code.py...
r10281 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
(r'(\()([^)]+\))', repcallspaces),
]
checks = [
('python', r'.*\.(py|cgi)$', pyfilters, pypats),
('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
('c', r'.*\.c$', cfilters, cpats),
Matt Mackall
check-code: add some basic support for unified tests
r12364 ('unified test', r'.*\.t$', utestfilters, utestpats),
Matt Mackall
Introduce check-code.py...
r10281 ]
Pierre-Yves David
code-code: Add a logfunc argument to checkfile...
r10719 class norepeatlogger(object):
def __init__(self):
self._lastseen = None
Matt Mackall
check-code: add --blame switch
r11604 def log(self, fname, lineno, line, msg, blame):
Pierre-Yves David
code-code: Add a logfunc argument to checkfile...
r10719 """print error related a to given line of a given file.
The faulty line will also be printed but only once in the case
of multiple errors.
Matt Mackall
Introduce check-code.py...
r10281
Pierre-Yves David
code-code: Add a logfunc argument to checkfile...
r10719 :fname: filename
:lineno: line number
:line: actual content of the line
:msg: error message
"""
msgid = fname, lineno, line
if msgid != self._lastseen:
Matt Mackall
check-code: add --blame switch
r11604 if blame:
print "%s:%d (%s):" % (fname, lineno, blame)
else:
print "%s:%d:" % (fname, lineno)
Pierre-Yves David
code-code: Add a logfunc argument to checkfile...
r10719 print " > %s" % line
self._lastseen = msgid
print " " + msg
_defaultlogger = norepeatlogger()
Matt Mackall
check-code: add --blame switch
r11604 def getblame(f):
lines = []
for l in os.popen('hg annotate -un %s' % f):
start, line = l.split(':', 1)
user, rev = start.split()
lines.append((line[1:-1], user, rev))
return lines
def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
blame=False):
Pierre-Yves David
code-code: Add a logfunc argument to checkfile...
r10719 """checks style and portability of a given file
:f: filepath
:logfunc: function used to report error
logfunc(filename, linenumber, linecontent, errormessage)
:maxerr: number of error to display before arborting.
Set to None (default) to report all errors
Pierre-Yves David
check-code: add a return value to checkfile function...
r10720
return True if no error is found, False otherwise.
Pierre-Yves David
code-code: Add a logfunc argument to checkfile...
r10719 """
Matt Mackall
check-code: add --blame switch
r11604 blamecache = None
Pierre-Yves David
check-code: add a return value to checkfile function...
r10720 result = True
Matt Mackall
Introduce check-code.py...
r10281 for name, match, filters, pats in checks:
fc = 0
if not re.match(match, f):
continue
pre = post = open(f).read()
Matt Mackall
check-code: add some ignore hints
r10287 if "no-" + "check-code" in pre:
break
Matt Mackall
Introduce check-code.py...
r10281 for p, r in filters:
post = re.sub(p, r, post)
# print post # uncomment to show filtered version
z = enumerate(zip(pre.splitlines(), post.splitlines(True)))
for n, l in z:
Matt Mackall
check-code: add some ignore hints
r10287 if "check-code" + "-ignore" in l[0]:
continue
Matt Mackall
Introduce check-code.py...
r10281 for p, msg in pats:
Matt Mackall
check-code: add a warnings level...
r10895 if not warnings and msg.startswith("warning"):
continue
Matt Mackall
Introduce check-code.py...
r10281 if re.search(p, l[1]):
Matt Mackall
check-code: add --blame switch
r11604 bd = ""
if blame:
bd = 'working directory'
if not blamecache:
blamecache = getblame(f)
if n < len(blamecache):
bl, bu, br = blamecache[n]
if bl == l[0]:
bd = '%s@%s' % (bu, br)
logfunc(f, n + 1, l[0], msg, bd)
Matt Mackall
Introduce check-code.py...
r10281 fc += 1
Pierre-Yves David
check-code: add a return value to checkfile function...
r10720 result = False
Pierre-Yves David
check-code: Add a ``maxerr`` argument to the ``checkfile`` function...
r10718 if maxerr is not None and fc >= maxerr:
Matt Mackall
Introduce check-code.py...
r10281 print " (too many errors, giving up)"
break
break
Pierre-Yves David
check-code: add a return value to checkfile function...
r10720 return result
Pierre-Yves David
check-code: Add a ``checkfile`` function...
r10717
Pierre-Yves David
check-code: Only call check-code if __name__ = "__main__"....
r10716 if __name__ == "__main__":
Matt Mackall
check-code: add a warnings level...
r10895 parser = optparse.OptionParser("%prog [options] [files]")
parser.add_option("-w", "--warnings", action="store_true",
help="include warning-level checks")
parser.add_option("-p", "--per-file", type="int",
help="max warnings per file")
Matt Mackall
check-code: add --blame switch
r11604 parser.add_option("-b", "--blame", action="store_true",
help="use annotate to generate blame info")
Matt Mackall
check-code: add a warnings level...
r10895
Matt Mackall
check-code: add --blame switch
r11604 parser.set_defaults(per_file=15, warnings=False, blame=False)
Matt Mackall
check-code: add a warnings level...
r10895 (options, args) = parser.parse_args()
if len(args) == 0:
Pierre-Yves David
check-code: Only call check-code if __name__ = "__main__"....
r10716 check = glob.glob("*")
else:
Matt Mackall
check-code: add a warnings level...
r10895 check = args
Matt Mackall
Introduce check-code.py...
r10281
Pierre-Yves David
check-code: Only call check-code if __name__ = "__main__"....
r10716 for f in check:
Alecs King
check-code: add exit status...
r11816 ret = 0
if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
blame=options.blame):
ret = 1
sys.exit(ret)