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
26 if (defined $ARGV[0] && $ARGV[0] eq '--help') {
30 Nit-pick Xapian patches.
32 A patch can be supplied on stdin, or one or more patch files listed on the
35 Produces output suitable for use with vim's quick-fix mode, and similar
36 features in other editors.
40 git diff master.. | xapian-check-patch > tmp.qf
50 # SVN property changes don't have an "Index: [...]" line.
52 my $check_trailing = 0;
53 my $check_space_tab = 0;
57 if (/^Index: (.+)/ || m!^diff --git a/.+ b/(.+)!) {
65 if ($fnm =~ /\.cc$/) {
66 if ($fnm !~ m!\b(?:cdb|portability/)! &&
67 $fnm !~ m!\bcommon/msvc_dirent\.cc$!) {
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$!) {
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$!) {
82 $want_tabs = 1 unless ($fnm =~ m!/omega/cdb!);
84 } elsif ($fnm =~ /\.py(?:\.in)?$/) {
90 $fnm !~ m!\bcommon/msvc_dirent\.! &&
91 $fnm !~ m!/lemon\.c$! &&
92 $fnm !~ m!/queryparser\.lt$! &&
94 $fnm !~ m!/testdata/etext\.txt$!;
97 # print STDERR "$fnm: lang=" . ($lang // "UNKNOWN") . "\;
100 my $pre3 = substr($_, 0, 3);
101 if ($pre3 eq '@@ ') {
102 /^\@\@ -\d+,\d+ \+(\d+),\d+\b/ and $lineno = $1;
106 if ($pre3 eq '---' || $pre3 eq '+++') {
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;
114 if (s!/(?:\*.*?\*/|/.*)!!g) {
121 # Trim content of comments ending on this line:
122 if (s!^(.).*\*/!$1*/!) {
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:
135 if (defined $lang && ($lang eq 'c++' || $lang eq 'h' || $lang eq 'c') && !(/^[-+]\s*\#/)) {
136 # Replace string literals containing escaped quotes:
138 my $quote = substr($_, $-[0], 1);
142 QUOTELOOP
: while (1) {
143 if ($i >= length($_)) {
144 $_ = substr($_, 0, $start) . "X\n";
147 my $c = substr($_, $i, 1);
149 $_ = substr($_, 0, $start) . "X" . substr($_, $i);
151 # See if there's another string after this one:
152 while ($i != length($_)) {
153 $c = substr($_, $i, 1);
155 if ($c eq '"' || $c eq "'") {
166 $c = substr($_, $i, 1);
168 ++$i while (substr($_, $i, 1) =~ /^[A-Fa-f0-9]$/);
170 } elsif ($c =~ /^[0-7]/) {
172 ++$i while ($i - $j <= 3 && substr($_, $i, 1) =~ /^[0-7]$/);
174 } elsif ($c eq '"' || $c eq "'") {
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:";
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)](?!-[->]|\+\+)((?:\&\&|\|\||<<|>>|[-+/*%~=<>!&|^])=?|[?]),) {
222 $msg = "Missing space before '$op':";
223 if (substr($_, $pre[1] - 8, 8) eq 'operator') {
226 } elsif ($op eq '>' && substr($_, 0, $pre[1]) =~ /[A-Za-z0-9_]</) {
227 # y = static_cast<char>(x);
229 } elsif ($op eq '>') {
231 } elsif ($op eq '<' && substr($_, $pre[1] - 1, 1) =~ /^[A-Za-z0-9_]$/ && substr($_, $post[1]) =~ />/) {
232 # y = static_cast<char>(x);
234 } elsif ($op eq '<' &&
235 substr($_, 0, $pre[1]) =~ /(?:list|map|multimap|multiset|priority_queue|set|unordered_map|unordered_set|vector)$/) {
236 # y = priority_queue<Foo*,
239 } elsif ($op eq '&&' && substr($_, $pre[1] - 4, 4) eq 'auto') {
242 } elsif (($op eq '<<' || $op eq '>>') &&
243 substr($_, 0, $pre[1]) =~ /\b(?:0x[0-9a-fA-F]+|[0-9]+)$/ &&
244 substr($_, $post[1]) =~ /^(?:0x[0-9a-fA-F]+|[0-9]+)\b/) {
247 } elsif (($op eq '-' || $op eq '+') &&
248 substr($_, 0, $pre[1]) =~ /[0-9]\.?e$/) {
251 } elsif ($op eq '>>' &&
252 /[A-Za-z0-9_]<.+</) {
253 # vector<vector<int>> v;
255 } elsif ($op =~ /^[*&|]$/) {
256 # FIXME: *: const char* x;
257 # FIXME: &: const char& x;
261 } elsif (m@
^\
+\s
*[^#\s].*((?:\&\&|\|\||<<|>>|[-+/*%~=<>!&|^])=?|[?:,])(?<!(?:-[->]|\+\+|::))(?:[\w\(]| \s)@) {
265 $msg = "Should have exactly one space after '$op':";
266 if ($op eq '~' && substr($_, $post[1]) =~ /^[A-Za-z][A-Za-z0-9_]*\(/) {
267 # Destructor - e.g. ~Foo();
269 } elsif (($op eq '-' || $op eq '+' || $op eq '!' || $op eq '~') &&
270 substr($_, 0, $pre[1]) =~ m@
(?
:[-+/*%~=<>&|,;?
:] |[\
[(]|\b(?
:return|case
) |^\
+\s
*)$@
) {
271 # Unary -, +, !, ~: e.g. foo = +1; bar = x * (-y); baz = a * -b;
273 } elsif ($op eq ',' && (
274 /\b(?:AssertRel(?:Paranoid)?|TEST_REL)\(/ ||
278 } elsif ($op eq '>>' &&
279 /[A-Za-z0-9_]<.+</) {
280 # vector<vector<int>>&
282 } elsif ($op =~ /^[*&<>|]$/) {
283 # FIXME: *: const char *x;
284 # FIXME: *: const char &x;
285 # FIXME: < >: y = static_cast<char>(x);
289 } elsif (m@
^\
+[^#]*?[^#\h] +(,|->)@) {
290 $msg = "Space before '$1':";
291 } elsif (m
,^\
+[^#]*?[^#\h] ,) {
292 $msg = "Multiple spaces:";
297 print "$fnm:$lineno: $msg";
304 my $first_char = substr($fullline, 0, 1);
305 if ($first_char eq ' ') {
307 } elsif ($first_char eq '+') {
310 } elsif ($first_char eq '-') {
314 print STDERR
<<"__END__";
315 Files patched
:\t$files
316 Lines added
:\t$add_lines
317 Lines removed
:\t$del_lines