xapian-check-patch: More checks
[xapian.git] / xapian-maintainer-tools / xapian-check-patch
blob100f4bc7c7b5f69b379ee07ed6c5078b8a185336
1 #! /usr/bin/perl -w
2 # Copyright (c) 2007-2015 Olly Betts
4 # Permission is hereby granted, free of charge, to any person obtaining a copy
5 # of this software and associated documentation files (the "Software"), to
6 # deal in the Software without restriction, including without limitation the
7 # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8 # sell copies of the Software, and to permit persons to whom the Software is
9 # furnished to do so, subject to the following conditions:
11 # The above copyright notice and this permission notice shall be included in
12 # all copies or substantial portions of the Software.
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 # IN THE SOFTWARE.
22 require 5.000;
23 use strict;
24 use POSIX;
26 if (defined $ARGV[0] && $ARGV[0] eq '--help') {
27 print <<END;
28 Syntax: $0 [PATCH]...
30 Nit-pick Xapian patches.
32 A patch can be supplied on stdin, or one or more patch files listed on the
33 command line.
35 Produces output suitable for use with vim's quick-fix mode, and similar
36 features in other editors.
38 Example usage:
40 git diff master.. | xapian-check-patch > tmp.qf
41 vim -q tmp.qf
42 END
43 exit 0;
46 my ($fnm, $lineno);
47 my $add_lines = 0;
48 my $del_lines = 0;
49 my $files = 0;
50 # SVN property changes don't have an "Index: [...]" line.
51 my $want_tabs = -1;
52 my $check_trailing = 0;
53 my $check_space_tab = 0;
54 my $in_comment = 0;
55 my $lang;
56 while (<>) {
57 if (/^Index: (.+)/ || m!^diff --git a/.+ b/(.+)!) {
58 ++$files;
59 $fnm = $1;
60 $lineno = 1;
61 $lang = undef;
62 $in_comment = 0;
63 # Don't know!
64 $want_tabs = -1;
65 if ($fnm =~ /\.cc$/) {
66 if ($fnm !~ m!\b(?:cdb|portability/)! &&
67 $fnm !~ m!\bcommon/msvc_dirent\.cc$!) {
68 $lang = 'c++';
69 $want_tabs = 1 unless ($fnm =~ m!\blanguages/steminternal\.cc$!);
71 } elsif ($fnm =~ /\.c$/) {
72 if ($fnm !~ m!\blanguages/compiler/! &&
73 $fnm !~ m!/lemon\.c$!) {
74 $lang = 'c';
75 $want_tabs = 1;
77 } elsif ($fnm =~ /\.h$/) {
78 if ($fnm !~ m!\binclude/xapian/intrusive_ptr\.h! &&
79 $fnm !~ m!\blanguages/compiler/!) {
80 $lang = 'h';
81 $want_tabs = 1 unless ($fnm =~ m!/omega/cdb!);
83 } elsif ($fnm =~ /\.py(?:\.in)?$/) {
84 $lang = 'py';
85 $want_tabs = 0;
87 $check_trailing =
88 $fnm !~ /\.sbl$/ &&
89 $fnm !~ m!\bcommon/msvc_dirent\.! &&
90 $fnm !~ m!/lemon\.c$! &&
91 $fnm !~ m!/queryparser\.lt$! &&
92 $fnm !~ m!\bcdb! &&
93 $fnm !~ m!/testdata/etext\.txt$!;
94 $check_space_tab =
95 $fnm !~ /\.sbl$/;
96 # print STDERR "$fnm: lang=" . ($lang // "UNKNOWN") . "\;
97 next;
99 my $pre3 = substr($_, 0, 3);
100 if ($pre3 eq '@@ ') {
101 /^\@\@ -\d+,\d+ \+(\d+),\d+\b/ and $lineno = $1;
102 $in_comment = 0;
103 next;
105 if ($pre3 eq '---' || $pre3 eq '+++') {
106 next;
108 my $msg;
109 my $fullline = $_;
110 # Uncomment commented out parameter names: foo(int /*bar*/) -> foo(int bar)
111 s!/\*([A-Za-z_][A-Za-z_0-9]*)\*/([,)])!$1$2!g;
112 # Trim comments:
113 if (s!/(?:\*.*?\*/|/.*)!!g) {
114 s/\s+$//;
116 if (s!/\*.*!!g) {
117 s/\s+$//;
118 $in_comment = 1;
120 # Trim content of comments ending on this line:
121 if (s!^(.).*\*/!$1*/!) {
122 $in_comment = 0;
124 if ($in_comment) {
125 $_ = '';
126 } else {
127 # Drop comment content for "*" continuation lines (when /* isn't in hunk):
128 s/^(.)(\s*\*).*/$1$2/;
131 if (defined $lang && ($lang eq 'c++' || $lang eq 'h' || $lang eq 'c') && !(/^[-+]\s*\#/)) {
132 # Replace string literals containing escaped quotes:
133 if (/['"]/) {
134 my $quote = substr($_, $-[0], 1);
135 my $start = $+[0];
136 my $i = $start;
137 my $esc = 0;
138 QUOTELOOP: while (1) {
139 if ($i >= length($_)) {
140 $_ = substr($_, 0, $start) . "X\n";
141 last;
143 my $c = substr($_, $i, 1);
144 if ($c eq $quote) {
145 $_ = substr($_, 0, $start) . "X" . substr($_, $i);
146 $i = $start + 2;
147 # See if there's another string after this one:
148 while ($i != length($_)) {
149 $c = substr($_, $i, 1);
150 ++$i;
151 if ($c eq '"' || $c eq "'") {
152 $quote = $c;
153 $start = $i;
154 $esc = 0;
155 next QUOTELOOP;
158 last;
160 if ($c eq '\\') {
161 ++$i;
162 $c = substr($_, $i, 1);
163 if ($c eq 'x') {
164 ++$i while (substr($_, $i, 1) =~ /^[A-Fa-f0-9]$/);
165 next;
166 } elsif ($c =~ /^[0-7]/) {
167 my $j = $i;
168 ++$i while ($i - $j <= 3 && substr($_, $i, 1) =~ /^[0-7]$/);
169 next;
170 } elsif ($c eq '"' || $c eq "'") {
171 ++$esc;
174 ++$i;
179 if ($check_trailing && /^\+.*[ \t]$/) {
180 $msg = "added/changed line has trailing whitespace:";
181 } elsif ($check_space_tab && /^\+.* \t/) {
182 $msg = "added/changed line has space before tab:";
183 } elsif ($want_tabs == 1 and /^\+\t* {8}/) {
184 $msg = "added/changed line uses spaces for indentation rather than tab:";
185 } elsif (!$want_tabs and /^\+ *\t/) {
186 $msg = "added/changed line uses tab for indentation rather than spaces:";
187 } elsif (/^-.*\bFIX(?:ME)\b/) {
188 $msg = "FIX"."ME removed:";
189 } elsif (/^\+.*\bFIX(?:ME)\b/) {
190 $msg = "FIX"."ME added:";
191 } elsif (defined $lang && /^\+.*\\([abcefp]|brief|code|deprecated|endcode|exception|file|internal|li|param|private|return|todo)\b/) {
192 $msg = "Doxygen command '\\$1' introduced by '\\' not '\@':";
193 } elsif (m!^\+(?:.*[;{])?\s*/[/*]{1,2}\w!) {
194 $msg = "added/changed line has comment without whitespace before the text:";
195 } else {
196 if (defined $lang && ($lang eq 'c++' || $lang eq 'h' || $lang eq 'c')) {
197 if (m!^\+\s*(case|catch|class|do|for|if|namespace|struct|switch|try|union|while)\b([^ ]| \s)!) {
198 $msg = "'$1' not followed by exactly one space:";
199 } elsif (m!^\+\s*(return)\b([^ ;]| \s)!) {
200 $msg = "'$1' not followed by exactly one space:";
201 } elsif (m!^\+\s*(else)\b([^ \n]| \s)!) {
202 $msg = "'$1' not followed by exactly one space:";
203 } elsif (m!^(?:}|}\s{2,}|}\t|^[^}]*)\b(catch)\b!) {
204 $msg = "'$1' not preceded by exactly '} ':";
205 } elsif (m!^(?:}|}\s{2,}|}\t)\b(else|while)\b!) {
206 $msg = "'}' and '$1' not separated by exactly one space:";
207 } elsif (m!^\+.*\((?: [^;]|\t)!) {
208 # Allow: for ( ; i != 10; ++i)
209 $msg = "Whitespace after '(':";
210 } elsif (m!^\+.*\H.*\h\)!) {
211 $msg = "Whitespace before ')':";
212 } elsif (m!^\+.*; (\w+)([-+]{2})\)!) {
213 $msg = "Prefer '$2$1' to '$1$2':";
214 } elsif (m,^\+\s*[^#].*[\w)](?!-[->]|\+\+)([-+/*%~=<>!&|^]=?|\&\&|\|\|),) {
215 my $op = $1;
216 $msg = "Missing space before '$op':";
217 if (substr($_, $-[1] - 8, 8) eq 'operator') {
218 $msg = undef;
219 } elsif (($op eq '-' || $op eq '+') &&
220 substr($_, 0, $-[1]) =~ /[0-9]\.?e$/) {
221 # 1.2e-3, 7.e+3
222 $msg = undef;
223 } elsif ($op =~ /^[*&<>|]$/) {
224 # FIXME: *: const char* x;
225 # FIXME: &: const char& x;
226 # FIXME: < >: y = static_cast<char>(x);
227 # FIXME: |: FOO|BAR
228 $msg = undef;
230 } elsif (m,^\+\s*[^#].*[^-+](?!-[->]|\+\+)([-+/*%~=<>!&|^]=?|\&\&|\|\|)(?<!->)(?:[\w\(]| \s),) {
231 my $op = $1;
232 $msg = "Should have exactly one space after '$op':";
233 if ($op eq '~' && substr($_, $+[1]) =~ /^[A-Za-z][A-Za-z0-9_]*\(/) {
234 # Destructor - e.g. ~Foo();
235 $msg = undef;
236 } elsif (($op eq '-' || $op eq '+' || $op eq '!' || $op eq '~') &&
237 substr($_, 0, $-[1]) =~ m@(?:[-+/*%~=<>&|,;?:] |[\[(]|\b(?:return|case) |^\+\s*)$@) {
238 # Unary -, +, !, ~: e.g. foo = +1; bar = x * (-y); baz = a * -b;
239 $msg = undef;
240 } elsif ($op =~ /^[*&<>|]$/) {
241 # FIXME: *: const char *x;
242 # FIXME: *: const char &x;
243 # FIXME: < >: y = static_cast<char>(x);
244 # FIXME: |: FOO|BAR
245 $msg = undef;
248 # \S<< <<\S
249 # \S\s\s (OK in comments, etc)
252 if (defined $msg) {
253 print "$fnm:$lineno: $msg";
254 if ($msg =~ /:$/) {
255 print " $fullline";
256 } else {
257 print "\n";
260 my $first_char = substr($fullline, 0, 1);
261 if ($first_char eq ' ') {
262 ++$lineno;
263 } elsif ($first_char eq '+') {
264 ++$lineno;
265 ++$add_lines;
266 } elsif ($first_char eq '-') {
267 ++$del_lines;
270 print STDERR <<"__END__";
271 Files patched:\t$files
272 Lines added:\t$add_lines
273 Lines removed:\t$del_lines
274 __END__