Check space around '?', after ':', not before ','
[xapian.git] / xapian-maintainer-tools / xapian-check-patch
blob0e379d429cbee43a342db209323216267c9bde7c
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 $fnm !~ m!\bcommon/msvc_dirent\.h$!) {
81 $lang = 'h';
82 $want_tabs = 1 unless ($fnm =~ m!/omega/cdb!);
84 } elsif ($fnm =~ /\.py(?:\.in)?$/) {
85 $lang = 'py';
86 $want_tabs = 0;
88 $check_trailing =
89 $fnm !~ /\.sbl$/ &&
90 $fnm !~ m!\bcommon/msvc_dirent\.! &&
91 $fnm !~ m!/lemon\.c$! &&
92 $fnm !~ m!/queryparser\.lt$! &&
93 $fnm !~ m!\bcdb! &&
94 $fnm !~ m!/testdata/etext\.txt$!;
95 $check_space_tab =
96 $fnm !~ /\.sbl$/;
97 # print STDERR "$fnm: lang=" . ($lang // "UNKNOWN") . "\;
98 next;
100 my $pre3 = substr($_, 0, 3);
101 if ($pre3 eq '@@ ') {
102 /^\@\@ -\d+,\d+ \+(\d+),\d+\b/ and $lineno = $1;
103 $in_comment = 0;
104 next;
106 if ($pre3 eq '---' || $pre3 eq '+++') {
107 next;
109 my $msg;
110 my $fullline = $_;
111 # Uncomment commented out parameter names: foo(int /*bar*/) -> foo(int bar)
112 s!/\*([A-Za-z_][A-Za-z_0-9]*)\*/([,)])!$1$2!g;
113 # Trim comments:
114 if (s!/(?:\*.*?\*/|/.*)!!g) {
115 s/\s+$//;
117 if (s!/\*.*!!g) {
118 s/\s+$//;
119 $in_comment = 1;
121 # Trim content of comments ending on this line:
122 if (s!^(.).*\*/!$1*/!) {
123 $in_comment = 0;
125 if ($in_comment) {
126 $_ = '';
127 } else {
128 # Drop comment content for "*" continuation lines (when /* isn't in hunk):
129 s/^(.)(\s*\*).*/$1$2/;
132 # Replace multiple spaces before line continuation marker:
133 s! +\\$! \\!;
135 if (defined $lang && ($lang eq 'c++' || $lang eq 'h' || $lang eq 'c') && !(/^[-+]\s*\#/)) {
136 # Replace string literals containing escaped quotes:
137 if (/['"]/) {
138 my $quote = substr($_, $-[0], 1);
139 my $start = $+[0];
140 my $i = $start;
141 my $esc = 0;
142 QUOTELOOP: while (1) {
143 if ($i >= length($_)) {
144 $_ = substr($_, 0, $start) . "X\n";
145 last;
147 my $c = substr($_, $i, 1);
148 if ($c eq $quote) {
149 $_ = substr($_, 0, $start) . "X" . substr($_, $i);
150 $i = $start + 2;
151 # See if there's another string after this one:
152 while ($i != length($_)) {
153 $c = substr($_, $i, 1);
154 ++$i;
155 if ($c eq '"' || $c eq "'") {
156 $quote = $c;
157 $start = $i;
158 $esc = 0;
159 next QUOTELOOP;
162 last;
164 if ($c eq '\\') {
165 ++$i;
166 $c = substr($_, $i, 1);
167 if ($c eq 'x') {
168 ++$i while (substr($_, $i, 1) =~ /^[A-Fa-f0-9]$/);
169 next;
170 } elsif ($c =~ /^[0-7]/) {
171 my $j = $i;
172 ++$i while ($i - $j <= 3 && substr($_, $i, 1) =~ /^[0-7]$/);
173 next;
174 } elsif ($c eq '"' || $c eq "'") {
175 ++$esc;
178 ++$i;
183 if ($check_trailing && /^\+.*[ \t]$/) {
184 $msg = "added/changed line has trailing whitespace:";
185 } elsif ($check_space_tab && /^\+.* \t/) {
186 $msg = "added/changed line has space before tab:";
187 } elsif ($want_tabs == 1 and /^\+\t* {8}/) {
188 $msg = "added/changed line uses spaces for indentation rather than tab:";
189 } elsif (!$want_tabs and /^\+ *\t/) {
190 $msg = "added/changed line uses tab for indentation rather than spaces:";
191 } elsif (/^-.*\bFIX(?:ME)\b/) {
192 $msg = "FIX"."ME removed:";
193 } elsif (/^\+.*\bFIX(?:ME)\b/) {
194 $msg = "FIX"."ME added:";
195 } elsif (defined $lang && /^\+.*\\([abcefp]|brief|code|deprecated|endcode|exception|file|internal|li|param|private|return|todo)\b/) {
196 $msg = "Doxygen command '\\$1' introduced by '\\' not '\@':";
197 } elsif (m!^\+(?:.*[;{])?\s*/[/*]{1,2}\w!) {
198 $msg = "added/changed line has comment without whitespace before the text:";
199 } else {
200 if (defined $lang && ($lang eq 'c++' || $lang eq 'h' || $lang eq 'c')) {
201 if (m!^\+\s*(case|catch|class|do|for|if|namespace|struct|switch|try|union|while)\b([^ ]| \s)!) {
202 $msg = "'$1' not followed by exactly one space:";
203 } elsif (m!^\+\s*(return)\b([^ ;]| \s)!) {
204 $msg = "'$1' not followed by exactly one space:";
205 } elsif (m!^\+\s*(else)\b([^ \n]| \s)!) {
206 $msg = "'$1' not followed by exactly one space:";
207 } elsif (m!^(?:}|}\s{2,}|}\t|^[^}]*)\b(catch)\b!) {
208 $msg = "'$1' not preceded by exactly '} ':";
209 } elsif (m!^(?:}|}\s{2,}|}\t)\b(else|while)\b!) {
210 $msg = "'}' and '$1' not separated by exactly one space:";
211 } elsif (m!^\+.*\((?: [^;]|\t)!) {
212 # Allow: for ( ; i != 10; ++i)
213 $msg = "Whitespace after '(':";
214 } elsif (m!^\+.*\H.*\h\)!) {
215 $msg = "Whitespace before ')':";
216 } elsif (m!^\+.*; (\w+)([-+]{2})\)!) {
217 $msg = "Prefer '$2$1' to '$1$2':";
218 } elsif (m,^\+\s*[^#].*[\w)](?!-[->]|\+\+)((?:[-+/*%~=<>!&|^]|\&\&|\|\||<<|>>)=?|[?]),) {
219 my $op = $1;
220 $msg = "Missing space before '$op':";
221 if (substr($_, $-[1] - 8, 8) eq 'operator') {
222 $msg = undef;
223 } elsif (($op eq '-' || $op eq '+') &&
224 substr($_, 0, $-[1]) =~ /[0-9]\.?e$/) {
225 # 1.2e-3, 7.e+3
226 $msg = undef;
227 } elsif ($op =~ /^[*&<>|]$/) {
228 # FIXME: *: const char* x;
229 # FIXME: &: const char& x;
230 # FIXME: < >: y = static_cast<char>(x);
231 # FIXME: |: FOO|BAR
232 $msg = undef;
234 } elsif (m@^\+\s*[^#].*[^-+](?!-[->]|\+\+)((?:[-+/*%~=<>!&|^]|\&\&|\|\||<<|>>)=?|[?:,])(?<!(?:->|::))(?:[\w\(]| \s)@) {
235 my $op = $1;
236 $msg = "Should have exactly one space after '$op':";
237 if ($op eq '~' && substr($_, $+[1]) =~ /^[A-Za-z][A-Za-z0-9_]*\(/) {
238 # Destructor - e.g. ~Foo();
239 $msg = undef;
240 } elsif (($op eq '-' || $op eq '+' || $op eq '!' || $op eq '~') &&
241 substr($_, 0, $-[1]) =~ m@(?:[-+/*%~=<>&|,;?:] |[\[(]|\b(?:return|case) |^\+\s*)$@) {
242 # Unary -, +, !, ~: e.g. foo = +1; bar = x * (-y); baz = a * -b;
243 $msg = undef;
244 } elsif ($op eq ',' && (
245 /\b(?:AssertRel(?:Paranoid)?|TEST_REL)\(/ ||
246 /{[^()]*}/)) {
247 # AssertRel(a,<,b);
248 $msg = undef;
249 } elsif ($op =~ /^[*&<>|]$/) {
250 # FIXME: *: const char *x;
251 # FIXME: *: const char &x;
252 # FIXME: < >: y = static_cast<char>(x);
253 # FIXME: |: FOO|BAR
254 $msg = undef;
256 } elsif (m@^\+[^#]*?[^#\h] +,@) {
257 $msg = "Space before ',':";
258 } elsif (m,^\+[^#]*?[^#\h] ,) {
259 $msg = "Multiple spaces:";
263 if (defined $msg) {
264 print "$fnm:$lineno: $msg";
265 if ($msg =~ /:$/) {
266 print " $fullline";
267 } else {
268 print "\n";
271 my $first_char = substr($fullline, 0, 1);
272 if ($first_char eq ' ') {
273 ++$lineno;
274 } elsif ($first_char eq '+') {
275 ++$lineno;
276 ++$add_lines;
277 } elsif ($first_char eq '-') {
278 ++$del_lines;
281 print STDERR <<"__END__";
282 Files patched:\t$files
283 Lines added:\t$add_lines
284 Lines removed:\t$del_lines
285 __END__