xapian-check-patch: Check for '> >' and 'template <'
[xapian.git] / xapian-maintainer-tools / xapian-check-patch
blobee1ddac51a1d07c2dabd89250757ecef56be5597
1 #! /usr/bin/perl -w
2 # Copyright (c) 2007-2018 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 %count;
49 sub diagnostic {
50 my ($type, $msg, $fullline) = @_;
51 print "$fnm:$lineno: $type: $msg";
52 if (defined $fullline) {
53 print ": $fullline";
54 } else {
55 print "\n";
57 ++$count{$type};
60 my $add_lines = 0;
61 my $del_lines = 0;
62 my $files = 0;
63 # SVN property changes don't have an "Index: [...]" line.
64 my $want_tabs = -1;
65 my $check_trailing = 0;
66 my $check_space_tab = 0;
67 my $in_comment = 0;
68 my $lang;
69 my $header_guard_macro;
70 my $last_first_char = '';
71 my $in_ternary;
72 my $preproc;
73 my $preproc_continuation;
74 my ($top_level, $next_top_level); # undef for unknown, 0 for no, 1 for yes.
75 my $last_line_blank = 0;
76 while (<>) {
77 if (defined $next_top_level) {
78 $top_level = $next_top_level;
79 $next_top_level = undef;
82 if (/^Index: (.+)/ || m!^diff --git a/.+ b/(.+)!) {
83 ++$files;
84 $fnm = $1;
85 $lineno = 1;
86 $lang = undef;
87 $in_comment = 0;
88 $header_guard_macro = undef;
89 $in_ternary = 0;
90 $preproc = 0;
91 $preproc_continuation = 0;
92 $top_level = undef;
93 # Don't know!
94 $want_tabs = -1;
95 if ($fnm =~ /\.cc$/) {
96 if ($fnm !~ m!\b(?:cdb|portability/)! &&
97 $fnm !~ m!\bcommon/getopt\.cc$! &&
98 $fnm !~ m!\bomega/md5\.cc$! &&
99 $fnm !~ m!\bcommon/msvc_dirent\.cc$!) {
100 $lang = 'c++';
101 $want_tabs = 1 unless ($fnm =~ m!\blanguages/steminternal\.cc$!);
103 } elsif ($fnm =~ /\.c$/) {
104 if ($fnm !~ m!\blanguages/compiler/! &&
105 $fnm !~ m!/lemon\.c$! &&
106 $fnm !~ m!/xapdep\.c$!) {
107 $lang = 'c';
108 $want_tabs = 1;
110 } elsif ($fnm =~ /\.h$/) {
111 if ($fnm !~ m!\binclude/xapian/intrusive_ptr\.h! &&
112 $fnm !~ m!\blanguages/compiler/! &&
113 $fnm !~ m!\bcommon/msvc_dirent\.h$! &&
114 $fnm !~ m!\bcommon/heap\.h$!) {
115 $lang = 'h';
116 $want_tabs = 1 unless ($fnm =~ m!/omega/cdb!);
118 } elsif ($fnm =~ /\.py(?:\.in)?$/) {
119 $lang = 'py';
120 $want_tabs = 0;
121 } elsif ($fnm =~ m!(?:^|/)ChangeLog\b!) {
122 $lang = 'changelog';
123 $want_tabs = 1;
125 $check_trailing =
126 $fnm !~ /\.sbl$/ &&
127 $fnm !~ m!\bcommon/msvc_dirent\.! &&
128 $fnm !~ m!/lemon\.c$! &&
129 $fnm !~ m!/queryparser\.lt$! &&
130 $fnm !~ m!\bcdb! &&
131 $fnm !~ m!/testdata/etext\.txt$!;
132 $check_space_tab =
133 $fnm !~ /\.patch$/ &&
134 $fnm !~ /\.sbl$/;
135 # print STDERR "$fnm: lang=" . ($lang // "UNKNOWN") . "\;
136 next;
138 my $pre3 = substr($_, 0, 3);
139 if ($pre3 eq '@@ ') {
140 /^\@\@ -\d+,\d+ \+(\d+),\d+\b/ and $lineno = $1;
141 $next_top_level = ($lineno == 1) ? 1 : undef;
142 $in_comment = 0;
143 $last_line_blank = 0;
144 next;
146 if ($pre3 eq '---' || $pre3 eq '+++') {
147 next;
150 my $line_blank = /^.\s*$/;
151 if (!$line_blank) {
152 $next_top_level = (/^.\s/ ? 0 : 1);
155 my $fullline = $_;
156 if (defined $lang && ($lang eq 'c++' || $lang eq 'h' || $lang eq 'c')) {
157 # Uncomment commented out parameter names: foo(int /*bar*/) -> foo(int bar)
158 s!/\*([A-Za-z_][A-Za-z_0-9]*)\*/([,)])!$1$2!g;
160 # Check for comments without a space before the comment text.
161 if (m!^\+.*\s/[*/]{1,2}[A-Za-z0-9]!) {
162 diagnostic('error', "Missing space between comment characters and comment text", $fullline);
165 # Trim comments:
166 if (s!/(?:\*.*?\*/|/.*)!!g) {
167 s/\s+$//;
169 if (s!/\*.*!!g) {
170 s/\s+$//;
171 $in_comment = 1;
173 # Trim content of comments ending on this line:
174 if (s!^(.).*\*/!$1*/!) {
175 $in_comment = 0;
177 if ($in_comment) {
178 $_ = '';
179 } else {
180 # Drop comment content for "*" continuation lines (when /* isn't in hunk):
181 s/^(.)(\s*\*).*/$1$2/;
183 } elsif (defined $lang && $lang eq 'py') {
184 # Trim comments:
185 if (s!#.*!!g) {
186 s/\s+$//;
190 # Replace multiple spaces before line continuation marker:
191 s! +\\$! \\!;
193 if (defined $lang && ($lang eq 'c++' || $lang eq 'h' || $lang eq 'c')) {
194 if (substr($_, 0, 1) eq '+') {
195 my $expandedline = '';
196 for my $i (1..length($fullline) - 1) {
197 my $ch = substr($fullline, $i, 1);
198 if ($ch eq "\t") {
199 $expandedline .= ('.' x (8 - length($expandedline) % 8));
200 } else {
201 $expandedline .= $ch;
204 chomp($expandedline);
205 if (length($expandedline) > 80 &&
206 # Logging annotations aren't really for human eyes.
207 !/^\+[ \t]*LOGCALL/ &&
208 # Don't force wrapping of a long #error message.
209 !/^#\d*(error|warning)\b/) {
210 diagnostic('error', "Line extends beyond column 80 (to column ".length($expandedline).")", $fullline);
213 if (m,^\+\s+LOGCALL(?:_[A-Z0-9]+)*\([^"]*"[^"]*(?<!operator)\(,) {
214 diagnostic('error', "Don't include parentheses in debug logging method/class name", $fullline);
216 # Replace string literals containing escaped quotes:
217 if (/['"]/) {
218 my $quote = substr($_, $-[0], 1);
219 my $start = $+[0];
220 my $i = $start;
221 my $esc = 0;
222 QUOTELOOP: while (1) {
223 if ($i >= length($_)) {
224 $_ = substr($_, 0, $start) . "X\n";
225 last;
227 my $c = substr($_, $i, 1);
228 if ($c eq $quote) {
229 $_ = substr($_, 0, $start) . "X" . substr($_, $i);
230 $i = $start + 2;
231 # See if there's another string after this one:
232 while ($i != length($_)) {
233 $c = substr($_, $i, 1);
234 ++$i;
235 if ($c eq '"' || $c eq "'") {
236 $quote = $c;
237 $start = $i;
238 $esc = 0;
239 next QUOTELOOP;
242 last;
244 if ($c eq '\\') {
245 ++$i;
246 $c = substr($_, $i, 1);
247 if ($c eq 'x') {
248 ++$i while (substr($_, $i, 1) =~ /^[A-Fa-f0-9]$/);
249 next;
250 } elsif ($c =~ /^[0-7]/) {
251 my $j = $i;
252 ++$i while ($i - $j <= 3 && substr($_, $i, 1) =~ /^[0-7]$/);
253 next;
254 } elsif ($c eq '"' || $c eq "'") {
255 ++$esc;
258 ++$i;
263 if ($check_trailing && $fullline =~ /^\+.*[ \t]$/) {
264 diagnostic('error', "added/changed line has trailing whitespace", $fullline);
266 if ($check_space_tab && /^\+.* \t/) {
267 diagnostic('error', "added/changed line has space before tab", $fullline);
269 if ($want_tabs == 1 and /^\+\t* {8}/) {
270 diagnostic('error', "added/changed line uses spaces for indentation rather than tab", $fullline);
272 if (!$want_tabs and /^\+ *\t/) {
273 diagnostic('error', "added/changed line uses tab for indentation rather than spaces", $fullline);
275 if ((!defined $lang || $lang ne 'changelog') && $fullline =~ /^([-+]).*\bFIX(?:ME)\b/) {
276 # Break up the string in the regexp above and messages below to avoid
277 # this triggering on its own code!
278 if ($1 eq '-') {
279 # Not an error, but interesting information.
280 diagnostic('info', "FIX"."ME removed", $fullline);
281 } else {
282 # Not an error, but not good.
283 diagnostic('warning', "FIX"."ME added", $fullline);
286 if (defined $lang && $lang ne 'changelog' && /^\+.*\\([abcefp]|brief|code|deprecated|endcode|exception|file|internal|li|param|private|return|todo)\b/) {
287 diagnostic('error', "Doxygen command '\\$1' introduced by '\\' not '\@'", $fullline);
289 if (defined $lang && $lang ne 'changelog' && /^\+.*@\s+([abcefp]|brief|code|deprecated|endcode|exception|file|internal|li|param|private|return|todo)\b/) {
290 diagnostic('error', "Broken Doxygen command: whitespace between '\@' and '$1'", $fullline);
292 if (defined $lang && ($lang eq 'c++' || $lang eq 'h' || $lang eq 'c')) {
293 if ($last_line_blank) {
294 if ($line_blank) {
295 # Allow multiple blank lines at the top level for now.
296 diagnostic('error', "Extra blank line", $fullline) unless ($top_level // 1);
297 } elsif (/^.\s+\}$/) {
298 # Closing } of a namespace often has a blank line before it,
299 # and that seems reasonable.
300 diagnostic('error', "Blank line at end of block", $fullline) unless ($top_level // 1);
304 if (/^([-+ ])(\s*)\#/) {
305 # Avoid misfiring for something like:
306 # #define FOO(x) \
307 # #x
308 if (!$preproc_continuation) {
309 if ($1 eq '+' && $2 ne '') {
310 diagnostic('error', "Whitespace before '#' on preprocessor line", $fullline);
313 $preproc = 1;
314 $preproc_continuation = /\\$/;
315 } elsif ($preproc_continuation) {
316 $preproc_continuation = /\\$/;
317 } else {
318 $preproc = 0;
320 if ($check_space_tab && /^\+( (?:| | | ))[^ \t#].*(?:[^)];|[^);,])\n/) {
321 # Exclude lines ending ');', ')', or ',' to avoid reporting for wrapped function arguments.
322 diagnostic('error', "line indented by ".length($1)." spaces", $fullline);
324 if (m!^\+.*\b([A-Za-z_][A-Za-z_0-9]*)\s+\(! &&
325 $1 !~ /^(bool|case|catch|double|for|if|int|return|switch|throw|void|while)$/) {
326 if (!$preproc) {
327 diagnostic('error', "Whitespace between '$1' and '('", $fullline);
328 } else {
329 # FIXME: We skip preprocessor lines for now to avoid triggering
330 # on things like «#define FOUR (2+2)» but it would be good to
331 # catch «#define FOO(x) foo (x)»
334 if (m!^\+\s*(case|catch|class|do|for|if|namespace|struct|switch|try|union)\b([^ ]| \s)!) {
335 diagnostic('error', "'$1' not followed by exactly one space", $fullline);
337 if (m!^\+.*;[^\s\\]!) {
338 diagnostic('error', "Missing space after ';'", $fullline);
340 if (m!^\+.*[^(;]\s;!) {
341 # Stuff like this is OK: for ( ; ; ) {
342 # though for that exact case I'd suggest: while (true) {
343 diagnostic('error', "Whitespace before ';'", $fullline);
345 if (m!^\+.*?\b(return)\b([^ ;]| \s)!) {
346 diagnostic('error', "'$1' not followed by exactly one space", $fullline);
348 if (m!^\+.*?\b(else)\b([^ \n]| \s)!) {
349 diagnostic('error', "'$1' not followed by exactly one space", $fullline);
351 if (m!^\+.*?\b(while)\b([^ ]| \s)!) {
352 diagnostic('error', "'$1' not followed by exactly one space", $fullline);
354 if (m!^\+.*?(?:}|}\s{2,}|}\t|^[^}]*)\b(catch)\b!) {
355 diagnostic('error', "'$1' not preceded by exactly '} '", $fullline);
357 if (m!^\+.*?(?:}|}\s{2,}|}\t)\b(else|while)\b!) {
358 diagnostic('error', "'}' and '$1' not separated by exactly one space", $fullline);
360 if (m!^\+.*\((?: [^;]|\t)!) {
361 # Allow: for ( ; i != 10; ++i)
362 diagnostic('error', "Whitespace after '('", $fullline);
364 if (m!^\+.*\H.*\h\)!) {
365 diagnostic('error', "Whitespace before ')'", $fullline);
367 if (m!^\+.*;\s*(\w+)([-+]{2})\)!) {
368 diagnostic('error', "Prefer '$2$1' to '$1$2'", $fullline);
370 if (m!^\+.*?>\s+>!) {
371 diagnostic('error', "We assume C++11 so can write '>>' instead of '> >'", $fullline);
373 if (m!^\+.*?\b(?:enable_if|list|map|multimap|multiset|priority_queue|set|template|unordered_map|unordered_set|vector)\s+<!) {
374 diagnostic('error', "Whitespace between template name and '<'", $fullline);
376 if (m,^\+\s*[^#].*[\w)](?!-[->]|\+\+)((?:\&\&|\|\||<<|>>|[-+/*%~=<>!&|^])=?|[?]),) {
377 my @pre = @-;
378 my @post = @+;
379 my $op = $1;
380 if (substr($_, $pre[1] - 8, 8) eq 'operator') {
381 # operator*() etc
382 } elsif ($op eq '>' && substr($_, 0, $pre[1]) =~ /[A-Za-z0-9_]</) {
383 # y = static_cast<char>(x);
384 } elsif ($op eq '>') {
385 } elsif ($op eq '<' && substr($_, $pre[1] - 1, 1) =~ /^[A-Za-z0-9_]$/ && substr($_, $post[1]) =~ />/) {
386 # y = static_cast<char>(x);
387 } elsif ($op eq '<' &&
388 substr($_, 0, $pre[1]) =~ /\b(?:enable_if|list|map|multimap|multiset|priority_queue|set|template|unordered_map|unordered_set|vector)$/) {
389 # y = priority_queue<Foo*,
390 # Bar>;
391 # template<typename A,
392 # typename B>
393 } elsif ($op eq '&&' && substr($_, 0, $pre[1]) =~ /\b(?:auto|bool|double|float|int(?:\d+_t)?|long|string|uint\d+_t|unsigned|[A-Z][A-Za-z0-9_]*)$/) {
394 # auto&& x;
395 # method(Class&& foo);
396 } elsif (($op eq '<<' || $op eq '>>') &&
397 substr($_, 0, $pre[1]) =~ /\b(?:0x[0-9a-fA-F]+|[0-9]+)$/ &&
398 substr($_, $post[1]) =~ /^(?:0x[0-9a-fA-F]+|[0-9]+)\b/) {
399 # 0x00b1<<26
400 } elsif (($op eq '-' || $op eq '+') &&
401 substr($_, 0, $pre[1]) =~ /[0-9]\.?e$/) {
402 # 1.2e-3, 7.e+3
403 } elsif ($op eq '>>' &&
404 /[A-Za-z0-9_]<.+</) {
405 # vector<vector<int>> v;
406 } elsif ($op =~ /^[*&|]$/) {
407 # FIXME: *: const char* x;
408 # FIXME: &: const char& x;
409 # FIXME: |: FOO|BAR
410 } else {
411 diagnostic('error', "Missing space before '$op'", $fullline);
414 if (m@^\+\s*[^#\s].*?((?:\&\&|\|\||<<|>>|[-+/*%~=<>!&|^])=?|[?:,])(?<!(?:-[->]|\+\+|::))(?:[\w\(\.\{!"']| \s)@) {
415 my @pre = @-;
416 my @post = @+;
417 my $op = $1;
418 if ($op eq '~' && substr($_, $post[1]) =~ /^[A-Za-z][A-Za-z0-9_]*\(/) {
419 # Destructor - e.g. ~Foo();
420 } elsif (($op eq '-' || $op eq '+' || $op eq '!' || $op eq '~') &&
421 substr($_, 0, $pre[1]) =~ m@(?:[-+/*%~=<>&|,;?:] |[\[(]|\b(?:return|case) |^\+\s*)$@) {
422 # Unary -, +, !, ~: e.g. foo = +1; bar = x * (-y); baz = a * -b;
423 } elsif ($op eq ',' && (
424 /\b(?:AssertRel(?:Paranoid)?|TEST_REL)\(/ ||
425 /{[^()]*}/)) {
426 # AssertRel(a,<,b);
427 } elsif ($op eq '>>' &&
428 /[A-Za-z0-9_]<.+</) {
429 # vector<vector<int>>&
430 } elsif ($op =~ /^[*&<>|]$/) {
431 # FIXME: *: const char *x;
432 # FIXME: *: const char &x;
433 # FIXME: < >: y = static_cast<char>(x);
434 # FIXME: |: FOO|BAR
435 } elsif (substr($_, $pre[1] - 8, 8) eq 'operator') {
436 # operator==() etc
437 } elsif (($op eq '<<' || $op eq '>>') &&
438 substr($_, 0, $pre[1]) =~ /\b(?:0x[0-9a-fA-F]+|[0-9]+)$/ &&
439 substr($_, $post[1]) =~ /^(?:0x[0-9a-fA-F]+|[0-9]+)\b/) {
440 # 0x00b1<<26
441 } elsif (($op eq '-' || $op eq '+') &&
442 substr($_, 0, $pre[1]) =~ /[0-9]\.?e$/) {
443 # 1.2e-3, 7.e+3
444 } else {
445 diagnostic('error', "Should have exactly one space after '$op'", $fullline);
448 if (/^\+.*;;\s*$/) {
449 diagnostic('error', "Extra ';' at end of line", $fullline);
451 if (m@^\+[^#]*?[^#\h] +(,|->)@) {
452 diagnostic('error', "Space before '$1'", $fullline);
454 if (m,^\+[^#]*?[^#\h] ,) {
455 diagnostic('error', "Multiple spaces", $fullline);
457 if (m!^\+(?:.*[;{])?\s*/[/*]{1,2}\w!) {
458 diagnostic('error', "added/changed line has comment without whitespace before the text", $fullline);
460 if (m!^\+.*?\)\{!) {
461 diagnostic('error', "No space between ')' and '{'", $fullline);
463 if ($fnm !~ m!/(?:md5|posixy_wrapper|perftest)\.cc$! &&
464 m,^\+.*[^\w\.>]([a-z][a-z0-9]*[A-Z]\w*),) {
465 my $symbol = $1;
466 if ($symbol eq 'gzFile' || $symbol eq 'uInt' || $symbol =~ /^(?:de|in)flate[A-Z]/) {
467 # Whitelist symbols from APIs we use.
468 } elsif ($symbol =~ /^[gs]et[A-Z]$/) {
469 # For now, allow setD(), etc.
470 } elsif ($symbol =~ /^h(?:File|Read|Write|Pipe|Client)$/ || $symbol eq 'fdwCtrlType' || $symbol eq 'pShutdownSocket') {
471 # Platform specific names, allow for now.
472 } else {
473 diagnostic('error', "camelCase identifier '$1' - Xapian coding convention is to use lower case and underscores for variables and functions, and CamelCase for class names", $fullline);
476 if ($lineno == 1 && m!^\+!) {
477 if (m!^/\*\* \@file (\S+)!) {
478 my $at_file = $1;
479 if (length $fnm == length $at_file ||
480 (length $fnm > length $at_file && substr($fnm, -length $at_file - 1, 1) eq '/') &&
481 substr($fnm, -length $at_file) eq $at_file) {
482 # @file matches
483 } else {
484 diagnostic('error', "\@file doesn't match filename", $fullline);
486 } else {
487 diagnostic('error', "\@file missing", $fullline);
490 if (/^\+.*\b(?:class|struct)\b.*:\s*$/) {
491 diagnostic('error', "Inheritance list split after ':', should be before", $fullline);
493 # Try to distinguish ternary operator (?:) correctly split after ":" vs
494 # constructor initialiser list incorrectly split after ":".
495 my $last_in_ternary = $in_ternary;
496 $in_ternary = / \?(?: |$)/;
497 if (!$last_in_ternary && !$in_ternary && /^\+.*\)\s*:\s*$/) {
498 diagnostic('error', "Constructor initialiser list split after ':', should be before", $fullline);
500 if (m,^\+\s+([-+/%^]|[&|]{2})\s,) {
501 diagnostic('error', "Expression split before operator '$1', should be after", $fullline);
503 if ($lang eq 'h') {
504 if (m!^\+\s*#\s*(ifndef|define|endif\s*/[*/])\s+((?:[A-Z]+_INCLUDED)?_?\w+_[Hh]\b)!) {
505 my ($type, $guard) = ($1, $2);
506 my $expected_guard;
507 if (!defined $header_guard_macro) {
508 if ($type eq 'ifndef') {
509 $header_guard_macro = [$type, $guard];
510 my $expected_guard = uc $fnm;
511 $expected_guard =~ s![-.]!_!g;
512 my $cut;
513 if (length($expected_guard) > length($guard) &&
514 substr($expected_guard, -length($guard) - 1, 1) eq '/' &&
515 substr($expected_guard, -length($guard)) eq $guard) {
516 $cut = -1;
517 } else {
518 for my $i (1 .. length($guard)) {
519 my $ch_e = substr($expected_guard, -$i, 1);
520 my $ch_g = substr($guard, -$i, 1);
521 next if ($ch_e eq $ch_g);
522 last if ($ch_e ne '/' || $ch_g ne '_');
523 $cut = $i;
526 if (!defined $cut) {
527 diagnostic('error', "include guard macro should match filename", $fullline);
529 my $prefix = 'XAPIAN_INCLUDED_';
530 if ($fnm =~ m!.*omega/(?:.*/)?!) {
531 $prefix = 'OMEGA_INCLUDED_';
533 #} elsif ($fnm =~ s!.*xapian-core/.*/!!) {
534 # $expected_guard = "XAPIAN_INCLUDED_" . $expected_guard;
535 #} elsif ($fnm =~ s!.*xapian-letor/.*/!!) {
536 #$expected_guard = "XAPIAN_INCLUDED_" . $expected_guard;
537 if (defined $cut && $cut == -1) {
538 diagnostic('error', "include guard macro should use prefix '$prefix'", $fullline);
539 } elsif (defined $cut && substr($guard, 0, length($guard) - $cut + 1) ne $prefix) {
540 diagnostic('error', "include guard macro should use prefix '$prefix'", $fullline);
541 } elsif ($guard !~ /^\Q$prefix\E/) {
542 diagnostic('error', "include guard macro should use prefix '$prefix'", $fullline);
545 } else {
546 if (!($type eq 'define' && $header_guard_macro->[0] ne 'ifndef')) {
547 my $expected_guard = $header_guard_macro->[1];
548 $header_guard_macro->[0] = $type;
549 if ($guard ne $expected_guard) {
550 diagnostic('error', "include guard macro should be $expected_guard", $fullline);
555 } else {
556 if (m!^\+\s*#\s*define\s+[A-Z]\+_INCLUDED_!) {
557 diagnostic('error', "include guard macro defined in non-header", $fullline);
560 } elsif (defined $lang && $lang eq 'py') {
561 if (/^\+.*;\s*$/) {
562 diagnostic('error', "';' at end of line of python code", $fullline);
565 if (defined $fnm && $fnm !~ m!xapian-check-patch|ChangeLog|NEWS|stemming/.*/(?:voc|output)\.txt$!) {
566 if (/^\+.*?\b(xapain|the the|initialsing|ipv5|outputing|intened)\b/i ||
567 # Cases which just need to be the prefix of a word
568 /^\+.*?\b((?:deafult|parm|peform|acessor|comptib|seach|seperat|seprat|separater|iteratat|calulat|delimitor|delimeter|charactor|databse|operatoar|implict)[a-z]*\b)/i ||
569 # Case-sensitive cases
570 /^\+.*?\b(and and)\b/) {
571 diagnostic('error', "Typo '$1'", $fullline);
575 my $first_char = substr($fullline, 0, 1);
576 if ($first_char eq ' ') {
577 ++$lineno;
578 } elsif ($first_char eq '+') {
579 ++$lineno;
580 ++$add_lines;
581 } elsif ($first_char eq '-') {
582 ++$del_lines;
583 } elsif ($first_char eq '\\') {
584 # "\ No newline at end of file" - if preceded by a "+" line, this means
585 # that the patch leaves the file missing a newline at the end.
586 if ($last_first_char eq '+') {
587 diagnostic('error', 'No newline at end of file');
590 $last_first_char = $first_char;
591 $last_line_blank = $line_blank;
593 if (scalar keys %count) {
594 for (sort keys %count) {
595 print STDERR "$_ count:\t$count{$_}\n";
597 print STDERR "\n";
599 print STDERR <<"__END__";
600 Files patched:\t$files
601 Lines added:\t$add_lines
602 Lines removed:\t$del_lines
603 __END__
604 exit(exists $count{'error'} ? 1 : 0);