[ovs-dev,v2,3/3] checkpatch: add a comment spell-checker
diff mbox series

Message ID 20180401150654.5941-4-aconole@redhat.com
State Accepted
Headers show
Series
  • checkpatch: the comment years!
Related show

Commit Message

Aaron Conole April 1, 2018, 3:06 p.m. UTC
Grow a new opt-in feature to check comments for possible spelling
mistakes.  Uses the 'enchant' library to provide a default link to
aspell/ispell as the backend.

Additionally, a custom set of kewords is included inline to match what
would be possibly encountered in 'the wild'.  The list is fairly
comprehensive at this point.

Signed-off-by: Aaron Conole <aconole@redhat.com>
---
 utilities/checkpatch.py | 108 ++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 105 insertions(+), 3 deletions(-)

Patch
diff mbox series

diff --git a/utilities/checkpatch.py b/utilities/checkpatch.py
index 5ed85ac15..d7cf9976a 100755
--- a/utilities/checkpatch.py
+++ b/utilities/checkpatch.py
@@ -21,12 +21,66 @@  import os
 import re
 import sys
 
+try:
+    import enchant
+
+    extra_keywords = ['ovs', 'vswitch', 'vswitchd', 'ovs-vswitchd', 'netdev',
+                      'selinux', 'ovs-ctl', 'dpctl', 'ofctl', 'openvswitch',
+                      'dpdk', 'hugepage', 'hugepages', 'pmd', 'upcall',
+                      'vhost', 'rx', 'tx', 'vhostuser', 'openflow', 'qsort',
+                      'rxq', 'txq', 'perf', 'stats', 'struct', 'int',
+                      'char', 'bool', 'upcalls', 'nicira', 'bitmask', 'ipv4',
+                      'ipv6', 'tcp', 'tcp4', 'tcpv4', 'udp', 'udp4', 'udpv4',
+                      'icmp', 'icmp4', 'icmpv6', 'vlan', 'vxlan', 'cksum',
+                      'csum', 'checksum', 'ofproto', 'numa', 'mempool',
+                      'mempools', 'mbuf', 'mbufs', 'hmap', 'cmap', 'smap',
+                      'dhcpv4', 'dhcp', 'dhcpv6', 'opts', 'metadata',
+                      'geneve', 'mutex', 'netdev', 'netdevs', 'subtable',
+                      'virtio', 'qos', 'policer', 'datapath', 'tunctl',
+                      'attr', 'ethernet', 'ether', 'defrag', 'defragment',
+                      'loopback', 'sflow', 'acl', 'initializer', 'recirc',
+                      'xlated', 'unclosed', 'netlink', 'msec', 'usec',
+                      'nsec', 'ms', 'us', 'ns', 'kilobits', 'kbps',
+                      'kilobytes', 'megabytes', 'mbps', 'gigabytes', 'gbps',
+                      'megabits', 'gigabits', 'pkts', 'tuple', 'miniflow',
+                      'megaflow', 'conntrack', 'vlans', 'vxlans', 'arg',
+                      'tpid', 'xbundle', 'xbundles', 'mbundle', 'mbundles',
+                      'netflow', 'localnet', 'odp', 'pre', 'dst', 'dest',
+                      'src', 'ethertype', 'cvlan', 'ips', 'msg', 'msgs',
+                      'liveness', 'userspace', 'eventmask', 'datapaths',
+                      'slowpath', 'fastpath', 'multicast', 'unicast',
+                      'revalidation', 'namespace', 'qdisc', 'uuid', 'ofport',
+                      'subnet', 'revalidation', 'revalidator', 'revalidate',
+                      'l2', 'l3', 'l4', 'openssl', 'mtu', 'ifindex', 'enum',
+                      'enums', 'http', 'https', 'num', 'vconn', 'vconns',
+                      'conn', 'nat', 'memset', 'memcmp', 'strcmp',
+                      'strcasecmp', 'tc', 'ufid', 'api', 'ofpbuf', 'ofpbufs',
+                      'hashmaps', 'hashmap', 'deref', 'dereference', 'hw',
+                      'prio', 'sendmmsg', 'sendmsg', 'malloc', 'free', 'alloc',
+                      'pid', 'ppid', 'pgid', 'uid', 'gid', 'sid', 'utime',
+                      'stime', 'cutime', 'cstime', 'vsize', 'rss', 'rsslim',
+                      'whcan', 'gtime', 'eip', 'rip', 'cgtime', 'dbg', 'gw',
+                      'sbrec', 'bfd', 'sizeof', 'pmds', 'nic', 'nics', 'hwol',
+                      'encap', 'decap', 'tlv', 'tlvs', 'decapsulation', 'fd',
+                      'cacheline', 'xlate', 'skiplist', 'idl', 'comparator',
+                      'natting', 'alg', 'pasv', 'epasv', 'wildcard', 'nated',
+                      'amd64', 'x86_64', 'recirculation']
+
+    spell_check_dict = enchant.Dict("en_US")
+    for kw in extra_keywords:
+        spell_check_dict.add(kw)
+
+    no_spellcheck = False
+except:
+    no_spellcheck = True
+
 __errors = 0
 __warnings = 0
 print_file_name = None
 checking_file = False
 total_line = 0
 colors = False
+spellcheck_comments = False
 
 
 def get_color_end():
@@ -233,7 +287,7 @@  def has_xxx_mark(line):
     return __regex_has_xxx_mark.match(line) is not None
 
 
-def filter_comments(current_line):
+def filter_comments(current_line, keep=False):
     """remove all of the c-style comments in a line"""
     STATE_NORMAL = 0
     STATE_COMMENT_SLASH = 1
@@ -245,6 +299,9 @@  def filter_comments(current_line):
     check_state = STATE_NORMAL
     only_whitespace = True
 
+    if keep:
+        check_state = STATE_COMMENT_CONTENTS
+
     for c in current_line:
         if c == '/':
             if state == STATE_NORMAL:
@@ -287,6 +344,38 @@  def filter_comments(current_line):
     return sanitized_line
 
 
+def check_comment_spelling(line):
+    if no_spellcheck or not spellcheck_comments:
+        return False
+
+    comment_words = filter_comments(line, True).replace(':', ' ').split(' ')
+    for word in comment_words:
+        skip = False
+        strword = re.subn(r'\W+', '', word)[0].replace(',', '')
+        if len(strword) and not spell_check_dict.check(strword.lower()):
+            if any([check_char in word
+                    for check_char in ['=', '(', '-', '_', '/', '\'']]):
+                skip = True
+
+            # special case the '.'
+            if '.' in word and not word.endswith('.'):
+                skip = True
+
+            # skip proper nouns and references to macros
+            if strword.isupper() or (strword[0].isupper() and
+                                     strword[1:].islower()):
+                skip = True
+
+            # skip words that start with numbers
+            if strword.startswith(tuple('0123456789')):
+                skip = True
+
+            if not skip:
+                return True
+
+    return False
+
+
 checks = [
     {'regex': None,
      'match_name':
@@ -330,6 +419,11 @@  checks = [
      'prereq': lambda x: has_comment(x),
      'check': lambda x: has_xxx_mark(x),
      'print': lambda: print_warning("Comment with 'xxx' marker")},
+
+    {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
+     'prereq': lambda x: has_comment(x),
+     'check': lambda x: check_comment_spelling(x),
+     'print': lambda: print_warning("Check for spelling mistakes")},
 ]
 
 
@@ -535,6 +629,7 @@  Check options:
 -b|--skip-block-whitespace     Skips the if/while/for whitespace tests
 -l|--skip-leading-whitespace   Skips the leading whitespace test
 -s|--skip-signoff-lines        Tolerate missing Signed-off-by line
+-S|--spellcheck-comments       Check C comments for possible spelling mistakes
 -t|--skip-trailing-whitespace  Skips the trailing whitespace test"""
           % sys.argv[0])
 
@@ -582,13 +677,14 @@  if __name__ == '__main__':
                                           sys.argv[1:])
         n_patches = int(numeric_options[-1][1:]) if numeric_options else 0
 
-        optlist, args = getopt.getopt(args, 'bhlstf',
+        optlist, args = getopt.getopt(args, 'bhlstfS',
                                       ["check-file",
                                        "help",
                                        "skip-block-whitespace",
                                        "skip-leading-whitespace",
                                        "skip-signoff-lines",
-                                       "skip-trailing-whitespace"])
+                                       "skip-trailing-whitespace",
+                                       "spellcheck-comments"])
     except:
         print("Unknown option encountered. Please rerun with -h for help.")
         sys.exit(-1)
@@ -607,6 +703,12 @@  if __name__ == '__main__':
             skip_trailing_whitespace_check = True
         elif o in ("-f", "--check-file"):
             checking_file = True
+        elif o in ("-S", "--spellcheck-comments"):
+            if no_spellcheck:
+                print("WARNING: The enchant library isn't availble.")
+                print("         Please install python enchant.")
+            else:
+                spellcheck_comments = True
         else:
             print("Unknown option '%s'" % o)
             sys.exit(-1)