Provide script 'check-style' for validating patches

Run the script with a patch file as the first argument in order
to validate the patch in terms of coding style, like so:

./tools/check-style my-patch.diff

As a shortcut, you can also check current changes:

./tools/check-style .
This commit is contained in:
Christian Dywan 2010-04-27 23:56:27 +02:00
parent 3aad0189c4
commit 0ca6c68f0c

161
tools/check-style Executable file
View file

@ -0,0 +1,161 @@
#! /usr/bin/env python
# Copyright (C) 2010 Christian Dywan <christian@twotoasts.de>
# Copyright (C) 2010 Arno Renevier <arno@renevier.net>
#
# 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+(?<!\\breturn)\s+\*\s*\w*\s*[;,]', 'Space between type and asterisk'],
["(\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'],
]
# 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)