163 lines
6 KiB
Python
Executable file
163 lines
6 KiB
Python
Executable file
#! /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'],
|
|
['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)
|
|
|