contrib: add checkpatch.pl

A naive code compliance checker. Invoke directly:

  contrib/scripts/checkpatch.pl 0001-switch-comments-to-klingon.patch
  contrib/scripts/checkpatch.pl hello.[ch] world.c

Use from a commit hook:

  echo 'git format-patch --stdout -1 |contrib/scripts/checkpatch.pl || :>' \
      >.git/hooks/post-commit

Or view the documentation with "perldoc contrib/scripts/checkpatch.pl"
This commit is contained in:
Lubomir Rintel 2018-07-10 10:40:25 +02:00
parent 73fa1b54ef
commit 0f3f56695a

224
contrib/scripts/checkpatch.pl Executable file
View file

@ -0,0 +1,224 @@
#!/usr/bin/perl -n
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Copyright 2018 Red Hat, Inc.
# $ perldoc checkpatch.pl for eye-pleasing view of the manual:
=head1 NAME
checkpatch.pl - emulate a serial modem
=head1 SYNOPSIS
checkpatch.pl [<file> ...]
=head1 DESCRIPTION
B<checkpatch.pl> checks source files or patches for common mistakes.
=head1 OPTIONS
=over 4
=item B<< <file> >>
A C source file or an unified diff.
=back
=cut
use strict;
use warnings;
chomp;
our $is_patch;
our $is_file;
our $seen_error;
our $line; # Current line
our $check_line; # Complain if errors are found on this line
our @functions_seen;
our $type;
our $filename;
sub new_hunk
{
$type = undef;
}
sub new_file
{
$filename = shift;
@functions_seen = ();
}
sub complain
{
my $message = shift;
return unless $check_line;
warn "$ARGV:$.: $message:\n";
warn "> $line\n\n";
$seen_error = 1;
}
if ($is_patch) {
# This is a line of an unified diff
/^@@/ and new_hunk;
if (/^\+\+\+ (.*)/) {
new_file ($1);
next;
}
/^([ \+])(.*)/ or next;
$check_line = $1 eq '+';
$line = $2;
} elsif ($is_file) {
# This is a line from full C file
$check_line = 1;
$line = $_;
} else {
# We don't handle these yet
/^diff --cc/ and exit 0;
# We don't know if we're dealing with a patch or a C file yet
/^#/ and $is_file = 1;
/^---/ and $is_patch = 1;
$filename = '';
next;
}
if ($is_file and $filename ne $ARGV) {
new_file ($ARGV);
new_hunk;
}
next unless $filename =~ /\.[ch]$/;
complain ('Tab following a space') if $line =~ / \t/;
complain ('Trailing whitespace') if $line =~ /[ \t]$/;
# Further on we process stuff without comments.
$_ = $line;
s/\s*\/\*.*\*\///;
s/\s*\/\*.*//;
s/\s*\/\/.*//;
/^\s* \* / and next;
new_hunk if $_ eq '';
if (/^typedef*/) {
# We expect the { on the same line as the typedef. Otherwise it
# looks too much like a function declaration
complain ('Unexpected line break following a typedef') unless /[;{,]$/;
next;
} elsif (/^[A-Za-z_][A-Za-z0-9_ ]*\*?$/ and /[a-z]/) {
# A function type
$type = $_;
#print "TYPE: >>> $line <<<\n";
next;
} elsif ($type and /^([A-Za-z_][A-Za-z0-9_]*)(\s*)\(/) {
my @order = qw/^get_property$ ^set_property$ (?<!_iface|_class)_init$ ^constructor$ ^constructed
_new$ ^dispose$ ^finalize$ _class_init/;
my @following = ();
my @tmp = ();
# A function name
my $name = $1;
complain ('A single space should follow the function name') unless $2 eq ' ';
# Determine which function must not be preceding this one
foreach my $func (reverse @order) {
if ($name =~ /$func/) {
@following = @tmp;
#warn "$name ($func): ".join (' ', @following);
last;
}
push @tmp, $func;
}
# Check if an out-of-order function was seen
foreach my $func (@following) {
my @wrong = grep { /$func/ } @functions_seen;
complain (join (', ', map { "'$_'" } @wrong)." should follow '$name'") if @wrong;
}
push @functions_seen, $1;
$type = undef;
next;
}
if ($type) {
# We've seen what looked like a type in a function declaration,
# but the function declaration didn't follow.
if ($type =~ /^(struct|union)/ and $line eq '{') {
complain ("Brace should be one the same line as the '$type' declaration");
} else {
complain ("Expected a function declaration following '$type', but found something else");
}
$type = undef;
}
END {
if ($seen_error) {
warn "The patch does not validate.\n" if $is_patch;
warn "The file does not validate.\n" if $is_file;
$? = 1
}
};
=head1 EXAMPLES
=over
=item B<checkpatch.pl hello.c>
Check a single file.
=item B<git diff --cached |checkpatch.pl>
Check the currently staged changes.
=item B<git format-patch --stdout -1 |contrib/scripts/checkpatch.pl || :>
A F<.git/hooks/post-commit> oneliner that, wisely, tolerates failures while
still providing advice.
=back
=head1 BUGS
Proabably too many.
=head1 SEE ALSO
F<CONTRIBUTING>
=head1 COPYRIGHT
Copyright 2018 Red Hat
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
=head1 AUTHOR
Lubomir Rintel C<lkundrak@v3.sk>
=cut