Georg Brandl avatar Georg Brandl committed b598a0f

Refactor for multiple file types. Add minimal test suite.

Comments (0)

Files changed (2)

     hgcodesmell
     ~~~~~~~~~~~
 
-    Mercurial extension to warn about smelly changes before committing.
+    Mercurial extension to warn about smelly changes in added code
+    before allowing a commit.
 
-    Usage: activate the extension and set the name of your changelog in hgrc::
+    Usage: activate the extension in your hgrc file::
 
         [extensions]
         hgcodesmell = path/to/hgcodesmell.py
 
-    :copyright: 2009 by Georg Brandl.
+    :copyright: 2009, 2010 by Georg Brandl.
     :license:
-        This program is free software; you can redistribute it and/or modify it
-        under the terms of the GNU General Public License as published by the
-        Free Software Foundation; either version 2 of the License, or (at your
-        option) any later version.
+        This program is free software; you can redistribute it and/or
+        modify it under the terms of the GNU General Public License as
+        published by the Free Software Foundation; either version 2 of
+        the License, or (at your option) any later version.
 
-        This program is distributed in the hope that it will be useful, but
-        WITHOUT ANY WARRANTY; without even the implied warranty of
-        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
-        Public License for more details.
+        This program is distributed in the hope that it will be useful,
+        but WITHOUT ANY WARRANTY; without even the implied warranty of
+        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+        GNU General Public License for more details.
 
-        You should have received a copy of the GNU General Public License along
-        with this program; if not, write to the Free Software Foundation, Inc.,
-        51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+        You should have received a copy of the GNU General Public
+        License along with this program; if not, write to the Free
+        Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+        Boston, MA 02110-1301 USA.
 """
-import re, os
+
+import os
+import re
+import fnmatch
+
 from mercurial import commands, cmdutil, extensions, patch
+
 try:
+    # use the color extension to render diffs, if it is recent enough
     from hgext.color import colorwrap
 except ImportError:
     colorwrap = lambda o, s: o(s)
 
-BAD_STUFF = [
-    (re.compile(r'^\+\s*print\b'), 'print statement'),
-    (re.compile(r'^\+\s*1/0'), 'zero division error'),
-    (re.compile(r'\bpdb\.set_trace\(\)'), 'set_trace'),
-    (re.compile(r':(w|wq|q|x)$', re.M), 'stupid vim command'),
-]
+# smelly things are tuples (regex, reason)
+print_stmt = (re.compile(r'^\+\s*print\b'), 'print statement')
+zero_div = (re.compile(r'^\+\s*1/0'), 'zero division error')
+set_trace = (re.compile(r'\bpdb\.set_trace\(\)'), 'set_trace')
+vim_cmd = (re.compile(r':(w|wq|q|x)$', re.M), 'vim exit command')
+windows_nl = (re.compile(r'\r'), 'Windows newline')
+
+# maps glob patterns to a list of smelly things
+SMELLY_STUFF = {
+    '*.py': [print_stmt, zero_div, set_trace],
+    '*': [vim_cmd],
+}
 
 if os.name != 'nt':
-    BAD_STUFF.append((re.compile(r'\r'), 'Windows newline'))
+    # only pick on Windows newlines if not on Windows
+    SMELLY_STUFF['*'].append(windows_nl)
+
 
 def new_commit(orig_commit, ui, repo, *pats, **opts):
-    smelly = 0
     match = cmdutil.match(repo, pats, opts)
     diff = patch.diff(repo, *cmdutil.revpair(repo, None), match=match)
+    smelly_count = 0
+    smellies = []
     for chunk in diff:
         chunklines = chunk.splitlines(True)
         indexline = 0
         for i, line in enumerate(chunklines):
             if line.startswith('diff'):
                 indexline = i
+                # new file: collect all smelly patterns for it
+                filename = line.split()[-1]
+                smellies = []
+                for pat, smelly in SMELLY_STUFF.iteritems():
+                    if not fnmatch.fnmatch(filename, pat):
+                        continue
+                    smellies.extend(smelly)
             elif line.startswith('@@'):
                 hunkstart = i
             elif line.startswith('+'):
-                for rex, reason in BAD_STUFF:
+                for rex, reason in smellies:
                     if rex.search(line):
                         ui.warn('Smelly change (%s):\n' % reason)
-                        colorwrap(ui.write,
-                                  ''.join(chunklines[indexline:indexline+3]
-                                          + chunklines[hunkstart:i+4]))
-                        smelly += 1
+                        diff = ''.join(chunklines[indexline:indexline+3]
+                                       + chunklines[hunkstart:i+4])
+                        colorwrap(ui.write, diff)
+                        smelly_count += 1
                         break
                 else:
                     continue
                 break
-    if smelly:
+    if smelly_count:
         if not ui.prompt('Found %d smelly change%s. Continue (y/N)?' %
-                         (smelly, smelly != 1 and 's' or ''),
+                         (smelly_count, smelly_count != 1 and 's' or ''),
                          default='n').lower().startswith('y'):
-            return
+            return smelly_count
     return orig_commit(ui, repo, *pats, **opts)
 
 def uisetup(ui):
+#!/bin/sh
+
+# Test the hgcodesmell extension.
+
+HG="hg --config extensions.codesmell=`pwd`/hgcodesmell.py"
+msg() { echo "[ test ] $1"; }
+die() { echo "[failed] $1"; exit 1; }
+suc() { echo "[passed] $1"; exit 0; }
+
+msg "Removing old test repository, if present."
+rm -rf repo
+
+msg "Making test repository in ./repo."
+$HG init repo || die "repo creation failed"
+cd repo
+
+msg "Committing some files without smell."
+echo "# A normal Python file." > file.py
+echo "A text file." > file.txt
+$HG addremove
+$HG commit -vm "Initial commit." || die "initial commit failed"
+
+msg "Trying to commit file.py with smell. codesmell should complain."
+echo "1/0" >> file.py
+echo | $HG commit -vm "Trying to commit file with smell."
+[ $? -eq 1 ] || die "codesmell didn't exit with error"
+$HG revert -a || die "revert failed"
+
+msg "Trying to commit file.txt without smell. codesmell should not complain."
+echo "1/0" >> file.txt
+$HG commit -vm "Blah, blah." || die "commit failed"
+
+msg "Removing test repository."
+cd ..
+rm -r repo
+
+suc "all tests successful"
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.