contrib/check-code.py
changeset 41821 14e8d042993a
parent 41761 e2472b12c842
child 41822 55ae5cd31f76
equal deleted inserted replaced
41820:9d38b4b52061 41821:14e8d042993a
   229 utestfilters = [
   229 utestfilters = [
   230     (r"<<(\S+)((.|\n)*?\n  > \1)", rephere),
   230     (r"<<(\S+)((.|\n)*?\n  > \1)", rephere),
   231     (r"( +)(#([^!][^\n]*\S)?)", repcomment),
   231     (r"( +)(#([^!][^\n]*\S)?)", repcomment),
   232 ]
   232 ]
   233 
   233 
   234 pypats = [
   234 # common patterns to check *.py
       
   235 commonpypats = [
   235   [
   236   [
   236     (r'\\$', 'Use () to wrap long lines in Python, not \\'),
   237     (r'\\$', 'Use () to wrap long lines in Python, not \\'),
   237     (r'^\s*def\s*\w+\s*\(.*,\s*\(',
   238     (r'^\s*def\s*\w+\s*\(.*,\s*\(',
   238      "tuple parameter unpacking not available in Python 3+"),
   239      "tuple parameter unpacking not available in Python 3+"),
   239     (r'lambda\s*\(.*,.*\)',
   240     (r'lambda\s*\(.*,.*\)',
   260         # more lines at the same indent level
   261         # more lines at the same indent level
   261         r'((?P=indent)[^\n]+\n)*'
   262         r'((?P=indent)[^\n]+\n)*'
   262         # a pass at the same indent level, which is bogus
   263         # a pass at the same indent level, which is bogus
   263         r'(?P=indent)pass[ \t\n#]'
   264         r'(?P=indent)pass[ \t\n#]'
   264       ), 'omit superfluous pass'),
   265       ), 'omit superfluous pass'),
   265     (r'.{81}', "line too long"),
       
   266     (r'[^\n]\Z', "no trailing newline"),
   266     (r'[^\n]\Z', "no trailing newline"),
   267     (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
   267     (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
   268 #    (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
   268 #    (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
   269 #     "don't use underbars in identifiers"),
   269 #     "don't use underbars in identifiers"),
   270     (r'^\s+(self\.)?[A-Za-z][a-z0-9]+[A-Z]\w* = ',
   270     (r'^\s+(self\.)?[A-Za-z][a-z0-9]+[A-Z]\w* = ',
   298      "missing whitespace around operator"),
   298      "missing whitespace around operator"),
   299     (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
   299     (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
   300      "wrong whitespace around ="),
   300      "wrong whitespace around ="),
   301     (r'\([^()]*( =[^=]|[^<>!=]= )',
   301     (r'\([^()]*( =[^=]|[^<>!=]= )',
   302      "no whitespace around = for named parameters"),
   302      "no whitespace around = for named parameters"),
   303     (r'raise Exception', "don't raise generic exceptions"),
       
   304     (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
   303     (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
   305      "don't use old-style two-argument raise, use Exception(message)"),
   304      "don't use old-style two-argument raise, use Exception(message)"),
   306     (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
   305     (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
   307     (r' [=!]=\s+(True|False|None)',
   306     (r' [=!]=\s+(True|False|None)',
   308      "comparison with singleton, use 'is' or 'is not' instead"),
   307      "comparison with singleton, use 'is' or 'is not' instead"),
   314      'instead', r'#.*hasattr-py3-only'),
   313      'instead', r'#.*hasattr-py3-only'),
   315     (r'opener\([^)]*\).read\(',
   314     (r'opener\([^)]*\).read\(',
   316      "use opener.read() instead"),
   315      "use opener.read() instead"),
   317     (r'opener\([^)]*\).write\(',
   316     (r'opener\([^)]*\).write\(',
   318      "use opener.write() instead"),
   317      "use opener.write() instead"),
   319     (r'[\s\(](open|file)\([^)]*\)\.read\(',
       
   320      "use util.readfile() instead"),
       
   321     (r'[\s\(](open|file)\([^)]*\)\.write\(',
       
   322      "use util.writefile() instead"),
       
   323     (r'^[\s\(]*(open(er)?|file)\([^)]*\)(?!\.close\(\))',
       
   324      "always assign an opened file to a variable, and close it afterwards"),
       
   325     (r'[\s\(](open|file)\([^)]*\)\.(?!close\(\))',
       
   326      "always assign an opened file to a variable, and close it afterwards"),
       
   327     (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
   318     (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
   328     (r'\.debug\(\_', "don't mark debug messages for translation"),
   319     (r'\.debug\(\_', "don't mark debug messages for translation"),
   329     (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
   320     (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
   330     (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
   321     (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
   331     (r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
   322     (r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
   332      'legacy exception syntax; use "as" instead of ","'),
   323      'legacy exception syntax; use "as" instead of ","'),
   333     (r':\n(    )*( ){1,3}[^ ]', "must indent 4 spaces"),
       
   334     (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
   324     (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
   335     (r'\bdef\s+__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
   325     (r'\bdef\s+__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
   336     (r'os\.path\.join\(.*, *(""|\'\')\)',
   326     (r'os\.path\.join\(.*, *(""|\'\')\)',
   337      "use pathutil.normasprefix(path) instead of os.path.join(path, '')"),
   327      "use pathutil.normasprefix(path) instead of os.path.join(path, '')"),
   338     (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'),
   328     (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'),
   339     # XXX only catch mutable arguments on the first line of the definition
   329     # XXX only catch mutable arguments on the first line of the definition
   340     (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
   330     (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
   341     (r'\butil\.Abort\b', "directly use error.Abort"),
   331     (r'\butil\.Abort\b', "directly use error.Abort"),
   342     (r'^@(\w*\.)?cachefunc', "module-level @cachefunc is risky, please avoid"),
   332     (r'^@(\w*\.)?cachefunc', "module-level @cachefunc is risky, please avoid"),
   343     (r'^import atexit', "don't use atexit, use ui.atexit"),
       
   344     (r'^import Queue', "don't use Queue, use pycompat.queue.Queue + "
   333     (r'^import Queue', "don't use Queue, use pycompat.queue.Queue + "
   345                        "pycompat.queue.Empty"),
   334                        "pycompat.queue.Empty"),
   346     (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"),
   335     (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"),
   347     (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
   336     (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
   348     (r'^import SocketServer', "don't use SockerServer, use util.socketserver"),
   337     (r'^import SocketServer', "don't use SockerServer, use util.socketserver"),
   356      "use mercurial.policy.importmod instead"),
   345      "use mercurial.policy.importmod instead"),
   357     (r'\.next\(\)', "don't use .next(), use next(...)"),
   346     (r'\.next\(\)', "don't use .next(), use next(...)"),
   358     (r'([a-z]*).revision\(\1\.node\(',
   347     (r'([a-z]*).revision\(\1\.node\(',
   359      "don't convert rev to node before passing to revision(nodeorrev)"),
   348      "don't convert rev to node before passing to revision(nodeorrev)"),
   360     (r'platform\.system\(\)', "don't use platform.system(), use pycompat"),
   349     (r'platform\.system\(\)', "don't use platform.system(), use pycompat"),
       
   350 
       
   351   ],
       
   352   # warnings
       
   353   [
       
   354   ]
       
   355 ]
       
   356 
       
   357 # patterns to check normal *.py files
       
   358 pypats = [
       
   359   [
       
   360     # Ideally, these should be placed in "commonpypats" for
       
   361     # consistency of coding rules in Mercurial source tree.
       
   362     # But on the other hand, these are not so seriously required for
       
   363     # python code fragments embedded in test scripts. Fixing test
       
   364     # scripts for these patterns requires many changes, and has less
       
   365     # profit than effort.
       
   366     (r'.{81}', "line too long"),
       
   367     (r'raise Exception', "don't raise generic exceptions"),
       
   368     (r'[\s\(](open|file)\([^)]*\)\.read\(',
       
   369      "use util.readfile() instead"),
       
   370     (r'[\s\(](open|file)\([^)]*\)\.write\(',
       
   371      "use util.writefile() instead"),
       
   372     (r'^[\s\(]*(open(er)?|file)\([^)]*\)(?!\.close\(\))',
       
   373      "always assign an opened file to a variable, and close it afterwards"),
       
   374     (r'[\s\(](open|file)\([^)]*\)\.(?!close\(\))',
       
   375      "always assign an opened file to a variable, and close it afterwards"),
       
   376     (r':\n(    )*( ){1,3}[^ ]', "must indent 4 spaces"),
       
   377     (r'^import atexit', "don't use atexit, use ui.atexit"),
   361 
   378 
   362     # rules depending on implementation of repquote()
   379     # rules depending on implementation of repquote()
   363     (r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
   380     (r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
   364      'string join across lines with no space'),
   381      'string join across lines with no space'),
   365     (r'''(?x)ui\.(status|progress|write|note|warn)\(
   382     (r'''(?x)ui\.(status|progress|write|note|warn)\(
   375          ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
   392          ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
   376          (?# this regexp can't use [^...] style,
   393          (?# this regexp can't use [^...] style,
   377            # because _preparepats forcibly adds "\n" into [^...],
   394            # because _preparepats forcibly adds "\n" into [^...],
   378            # even though this regexp wants match it against "\n")''',
   395            # even though this regexp wants match it against "\n")''',
   379      "missing _() in ui message (use () to hide false-positives)"),
   396      "missing _() in ui message (use () to hide false-positives)"),
   380   ],
   397   ] + commonpypats[0],
   381   # warnings
   398   # warnings
   382   [
   399   [
   383     # rules depending on implementation of repquote()
   400     # rules depending on implementation of repquote()
   384     (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
   401     (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
   385   ]
   402   ] + commonpypats[1]
   386 ]
   403 ]
   387 
   404 
   388 pyfilters = [
   405 # common filters to convert *.py
       
   406 commonpyfilters = [
   389     (r"""(?msx)(?P<comment>\#.*?$)|
   407     (r"""(?msx)(?P<comment>\#.*?$)|
   390          ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
   408          ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
   391           (?P<text>(([^\\]|\\.)*?))
   409           (?P<text>(([^\\]|\\.)*?))
   392           (?P=quote))""", reppython),
   410           (?P=quote))""", reppython),
   393 ]
   411 ]
       
   412 
       
   413 # filters to convert normal *.py files
       
   414 pyfilters = [
       
   415 ] + commonpyfilters
   394 
   416 
   395 # non-filter patterns
   417 # non-filter patterns
   396 pynfpats = [
   418 pynfpats = [
   397     [
   419     [
   398     (r'pycompat\.osname\s*[=!]=\s*[\'"]nt[\'"]', "use pycompat.iswindows"),
   420     (r'pycompat\.osname\s*[=!]=\s*[\'"]nt[\'"]', "use pycompat.iswindows"),