midori/tools/check-style

174 lines
6.4 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'],
['[^a-z](asctime|ctime|getgrgid|getprgnam|getlogin \
|getpwnam|getpwuid|gmtime|localtime \
|rand|readdir|strtok|ttyname)[^a-z]', '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
previous_i = 0
previous_violation = ''
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 = []
previous_violation = ''
continue
if line[:2] == '@@':
i = int (line.split (' ')[2].split (',')[0][1:]) - 1
curly = []
if line[0] == '-':
i = i -1
if line[0] != '+':
previous_violation = ''
continue
line = line[1:]
# Spurious blank lines
if previous == line == '':
print (fmt % (filename, i, 'Spurious blank line'))
previous = line
previous_violation = ''
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
previous_violation = ''
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()
previous_violation = ''
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
if previous_violation != '' and violation == previous_violation:
continue
previous_violation = violation
print (fmt % (filename, i, violation[1]))
if violated:
print (line)