contrib/check-code.py
changeset 11604 c5d40818b270
parent 11602 ba2520dd1e29
child 11672 dad185761392
equal deleted inserted replaced
11603:2eab5025f804 11604:c5d40818b270
     5 # Copyright 2010 Matt Mackall <mpm@selenic.com>
     5 # Copyright 2010 Matt Mackall <mpm@selenic.com>
     6 #
     6 #
     7 # This software may be used and distributed according to the terms of the
     7 # This software may be used and distributed according to the terms of the
     8 # GNU General Public License version 2 or any later version.
     8 # GNU General Public License version 2 or any later version.
     9 
     9 
    10 import re, glob
    10 import re, glob, os
    11 import optparse
    11 import optparse
    12 
    12 
    13 def repquote(m):
    13 def repquote(m):
    14     t = re.sub(r"\w", "x", m.group('text'))
    14     t = re.sub(r"\w", "x", m.group('text'))
    15     t = re.sub(r"[^\sx]", "o", t)
    15     t = re.sub(r"[^\sx]", "o", t)
   158 
   158 
   159 class norepeatlogger(object):
   159 class norepeatlogger(object):
   160     def __init__(self):
   160     def __init__(self):
   161         self._lastseen = None
   161         self._lastseen = None
   162 
   162 
   163     def log(self, fname, lineno, line, msg):
   163     def log(self, fname, lineno, line, msg, blame):
   164         """print error related a to given line of a given file.
   164         """print error related a to given line of a given file.
   165 
   165 
   166         The faulty line will also be printed but only once in the case
   166         The faulty line will also be printed but only once in the case
   167         of multiple errors.
   167         of multiple errors.
   168 
   168 
   171         :line: actual content of the line
   171         :line: actual content of the line
   172         :msg: error message
   172         :msg: error message
   173         """
   173         """
   174         msgid = fname, lineno, line
   174         msgid = fname, lineno, line
   175         if msgid != self._lastseen:
   175         if msgid != self._lastseen:
   176             print "%s:%d:" % (fname, lineno)
   176             if blame:
       
   177                 print "%s:%d (%s):" % (fname, lineno, blame)
       
   178             else:
       
   179                 print "%s:%d:" % (fname, lineno)
   177             print " > %s" % line
   180             print " > %s" % line
   178             self._lastseen = msgid
   181             self._lastseen = msgid
   179         print " " + msg
   182         print " " + msg
   180 
   183 
   181 _defaultlogger = norepeatlogger()
   184 _defaultlogger = norepeatlogger()
   182 
   185 
   183 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False):
   186 def getblame(f):
       
   187     lines = []
       
   188     for l in os.popen('hg annotate -un %s' % f):
       
   189         start, line = l.split(':', 1)
       
   190         user, rev = start.split()
       
   191         lines.append((line[1:-1], user, rev))
       
   192     return lines
       
   193 
       
   194 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
       
   195               blame=False):
   184     """checks style and portability of a given file
   196     """checks style and portability of a given file
   185 
   197 
   186     :f: filepath
   198     :f: filepath
   187     :logfunc: function used to report error
   199     :logfunc: function used to report error
   188               logfunc(filename, linenumber, linecontent, errormessage)
   200               logfunc(filename, linenumber, linecontent, errormessage)
   189     :maxerr: number of error to display before arborting.
   201     :maxerr: number of error to display before arborting.
   190              Set to None (default) to report all errors
   202              Set to None (default) to report all errors
   191 
   203 
   192     return True if no error is found, False otherwise.
   204     return True if no error is found, False otherwise.
   193     """
   205     """
       
   206     blamecache = None
   194     result = True
   207     result = True
   195     for name, match, filters, pats in checks:
   208     for name, match, filters, pats in checks:
   196         fc = 0
   209         fc = 0
   197         if not re.match(match, f):
   210         if not re.match(match, f):
   198             continue
   211             continue
   208                 continue
   221                 continue
   209             for p, msg in pats:
   222             for p, msg in pats:
   210                 if not warnings and msg.startswith("warning"):
   223                 if not warnings and msg.startswith("warning"):
   211                     continue
   224                     continue
   212                 if re.search(p, l[1]):
   225                 if re.search(p, l[1]):
   213                     logfunc(f, n + 1, l[0], msg)
   226                     bd = ""
       
   227                     if blame:
       
   228                         bd = 'working directory'
       
   229                         if not blamecache:
       
   230                             blamecache = getblame(f)
       
   231                         if n < len(blamecache):
       
   232                             bl, bu, br = blamecache[n]
       
   233                             if bl == l[0]:
       
   234                                 bd = '%s@%s' % (bu, br)
       
   235                     logfunc(f, n + 1, l[0], msg, bd)
   214                     fc += 1
   236                     fc += 1
   215                     result = False
   237                     result = False
   216             if maxerr is not None and fc >= maxerr:
   238             if maxerr is not None and fc >= maxerr:
   217                 print " (too many errors, giving up)"
   239                 print " (too many errors, giving up)"
   218                 break
   240                 break
   219         break
   241         break
   220     return result
   242     return result
   221 
   243 
   222 
       
   223 if __name__ == "__main__":
   244 if __name__ == "__main__":
   224     parser = optparse.OptionParser("%prog [options] [files]")
   245     parser = optparse.OptionParser("%prog [options] [files]")
   225     parser.add_option("-w", "--warnings", action="store_true",
   246     parser.add_option("-w", "--warnings", action="store_true",
   226                       help="include warning-level checks")
   247                       help="include warning-level checks")
   227     parser.add_option("-p", "--per-file", type="int",
   248     parser.add_option("-p", "--per-file", type="int",
   228                       help="max warnings per file")
   249                       help="max warnings per file")
   229 
   250     parser.add_option("-b", "--blame", action="store_true",
   230     parser.set_defaults(per_file=15, warnings=False)
   251                       help="use annotate to generate blame info")
       
   252 
       
   253     parser.set_defaults(per_file=15, warnings=False, blame=False)
   231     (options, args) = parser.parse_args()
   254     (options, args) = parser.parse_args()
   232 
   255 
   233     if len(args) == 0:
   256     if len(args) == 0:
   234         check = glob.glob("*")
   257         check = glob.glob("*")
   235     else:
   258     else:
   236         check = args
   259         check = args
   237 
   260 
   238     for f in check:
   261     for f in check:
   239         checkfile(f, maxerr=options.per_file, warnings=options.warnings)
   262         checkfile(f, maxerr=options.per_file, warnings=options.warnings,
       
   263                   blame=options.blame)