#! /usr/bin/env python # Copyright (C) 2010 Christian Dywan # Copyright (C) 2010 Arno Renevier # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # See the file COPYING for the full license text. # # check-style: Verify C source code according to coding style. import glob, re, string, subprocess, sys, os if len (sys.argv) < 2: name = os.path.basename (sys.argv[0]) print ('Usage:\n ' + name + ' FILENAMES\n' ' Pass "-" to read stdin, eg. "cat my-feature.diff | ' + name + ' -"\n' ' Pass "." to mean "git diff ^HEAD | ' + name + ' -"') sys.exit (1) # Coding style violations violations = [ ['.{101}', 'Line longer than 100 columns'], ['^[ ]{1,3}[^ ]+', 'Indentation is less than 4 spaces'], # FIXME: Don't match empty strings # FIXME: Don't match indented function arguments # ['^(?!(([ ]{4})*[^ ]+))', 'Indentation is not 4 spaces'], ['.*[ ]+$', 'Trailing whitespace'], [r"\t+", 'Tabs instead of spaces'], ["[^0-9],[^ ][^0-9]", 'No space after comma'], # ['(([A-Z][a-z]+)+)\*?[ ][a-z]{2,}', 'Good variable name'], # ['(g?char|g(boolean|pointer))\*?[ ][a-z]{2,}', 'Good variable name'], # ['(g?int|guint)[ ][a-z]+', 'Good iterator name'], # ['(struct)[ ]+[_]?([A-Z][a-z]+)+', 'Good type name'], ['^\s*\w+(?|=]{1,2}\w)", 'No space around operators'], ["\/\*[^ *\n]", 'No space after open comment'], ['[^ *]\*\/', 'No space before close comment'], ['\)\{', 'No space between ) and {'], [';[^ \s]', 'No space or newline after semicolon'], # ['(if)( \([^ ].*[^ ]\))$', 'Good if style'], ['^#\s+(if(n?def)?|define|else|elif)[ ].*$', 'Space between # and cpp'], [r'^\s*\*\w+(\+\+|--);', 'Invalid increment, use (*i)++ or *i += 1'], ['asctime|ctime|getgrgid|getprgnam|getlogin \ |getpwnam|getpwuid|gmtime|localtime \ |rand|readdir|strtok|ttyname', 'Not thread-safe posix, use _r variant'], ['while.*g_list_nth', 'Use g_list_next() instead of g_list_nth'], ['[^a-z]g_new.*1[^0-9]', 'Use g_slice_new() instead of g_new()'], ] # No validation for strings, comments, includes omissions = [ [r'["]{1}.*["]', 'STRING'], ["'\\\?.'", 'CHAR'], ["^\s*\/\*.*\*\/\s*$", 'COMMENT'], ['#include <.*>', 'INCLUDE'], ] # Output format fmt = '%s - %d: %s' # Pre-compile expressions for violation in violations: violation[0] = re.compile (violation[0]) for omission in omissions: omission[0] = re.compile (omission[0]) for filename_or_glob in sys.argv[1:]: if filename_or_glob == '-': handles = [sys.stdin] else: handles = [] for filename in glob.glob (filename_or_glob): if os.path.isdir (filename): gitdiff = subprocess.Popen (['git', 'diff', '^HEAD', '--relative', filename], stdout=subprocess.PIPE) handles.append (gitdiff.stdout) else: handles.append (open (filename)) if not handles: print (filename_or_glob + ' not found') sys.exit (1) for handle in handles: previous = '' i = 0 mode = '' filename = handle.name comment = False curly = [] for line in handle: line = line[:-1] i += 1 # Parse diff, only validate modified lines if i == 1 and 'diff' in line: mode = 'diff' if mode == 'diff': if line[:3] == '+++': filename = line[6:] comment = False curly = [] continue if line[:2] == '@@': i = int (line.split (' ')[2].split (',')[0][1:]) - 1 curly = [] if line[0] == '-': i = i -1 if line[0] != '+': continue line = line[1:] # Spurious blank lines if previous == line == '': print (fmt % (filename, i, 'Spurious blank line')) previous = line continue previous = line # Skip multi-line comment blocks if '/*' in line and not '*/' in line: comment = True if comment: if '*/' in line and not '/*' in line: comment = False continue cleaned = line for omission in omissions: cleaned = omission[0].sub (omission[1], cleaned) # Validate curly bracket indentation if '{' in cleaned and not '}' in cleaned: curly.append ((cleaned.index ('{'), cleaned)) if '}' in cleaned and not '{' in cleaned and not '},' in cleaned: if len (curly) == 0 or curly[-1][0] != cleaned.index ('}'): print (fmt % (filename, i, 'Misindented curly bracket')) print (curly[-1][1]) print (line) curly.pop() continue curly.pop() # Validate preprocessor indentation # FIXME: Don't warn if the *following* line is a curly cpp = cleaned.find ('#if') if cpp != -1: if len (curly) != 0 and cpp != curly[-1][0] + 4: print (fmt % (filename, i, 'Misindented preprocessor #if')) print (curly[-1][1]) print (line) violated = False for violation in violations: if violation[0].search (cleaned): violated = True print (fmt % (filename, i, violation[1])) if violated: print (line)