fix codetest failure - ASSERT_ARGS does not have a ; after and
[parrot.git] / t / codingstd / c_indent.t
blob97961be5b80c82550553c0bb2d5444ed6443b5ff
1 #! perl
2 # Copyright (C) 2006-2009, Parrot Foundation.
3 # $Id$
5 use strict;
6 use warnings;
8 use lib qw( . lib ../lib ../../lib );
9 use Test::More tests => 2;
10 use Parrot::Distribution;
12 =head1 NAME
14 t/codingstd/c_indent.t - checks for rules related to indenting in C source
16 =head1 SYNOPSIS
18     # test all files
19     % prove t/codingstd/c_indent.t
21     # test specific files
22     % perl t/codingstd/c_indent.t src/foo.c include/parrot/bar.h
24 =head1 DESCRIPTION
26 Checks that all C language source files have the proper use of indentation,
27 as specified in PDD07.
29 =head1 SEE ALSO
31 L<docs/pdds/pdd07_codingstd.pod>
33 =cut
35 my @files =
36       @ARGV
37     ? <@ARGV>
38     : map { $_->path() } Parrot::Distribution->new()->get_c_language_files();
40 check_indent(@files);
42 sub check_indent {
43     my ( @pp_indent, @c_indent );
44     my ( %pp_failed, %c_failed );
46     foreach my $path (@_) {
47         my @source;
48         open my $IN, '<', $path
49             or die "Can not open '$path' for reading!\n";
50         @source = <$IN>;
52         my %state = (
53             stack           => [],
54             line_cnt        => 0,
55             bif             => undef,
56             prev_last_char  => '',
57             last_char       => '',
58             in_comment      => 0,
59         );
61         foreach my $line (@source) {
62             $state{line_cnt}++;
63             chomp $line;
64             next unless $line;
66             $state{prev_last_char} = $state{last_char};
67             $state{last_char} = substr( $line, -1, 1 );
69             # ignore multi-line comments (except the first line)
70             $state{in_comment} = 0, next if $state{in_comment} &&
71                 $line =~ m{\*/} &&
72                 $' !~ m{/\*};   #'
73             next if $state{in_comment};
74             $state{in_comment} = 1
75                 if $line =~ m{/\*} &&
76                 $' !~ m{\*/};   #'
78             ## preprocessor scan
79             if ( $line =~ m/^\s*\#(\s*)(ifndef|ifdef|if)\s+(.*)/ )
80             {
81                 my ($prespace, $condition, $postspace) = ($1,$2,$3);
82                 next if ($line =~ m/PARROT_IN_CORE|_GUARD/);
83                 next if ($line =~ m/__cplusplus/);
85                 my $indent = q{  } x @{ $state{stack} };
86                 if ( $prespace ne $indent ) {
87                     push @pp_indent => "$path:$state{line_cnt}\n"
88                         . "     got: $line"
89                         . "expected: #$indent$condition $postspace'\n";
90                     $pp_failed{"$path\n"} = 1;
91                 }
92                 push @{ $state{stack} }, "#$condition $postspace";
93                 $state{bif} = undef;
94                 next;
95             }
96             if ( $line =~ m/^\s*\#(\s*)(else|elif)/)
97             {
99                 my ($prespace, $condition) = ($1,$2);
100                 # stay where we are, but indenting should be
101                 # back even with the opening brace.
102                 my $indent = q{  } x ( @{ $state{stack} } - 1 );
103                 if ( $prespace ne $indent ) {
104                     push @pp_indent => "$path:$state{line_cnt}\n"
105                         . "     got: $line"
106                         . "expected: #$indent$condition -- it's inside of "
107                         . ( join ' > ', @{ $state{stack} } ) . "\n";
108                     $pp_failed{"$path\n"} = 1;
109                 }
110                 next;
111             }
112             if ( $line =~ m/^\s*\#(\s*)(endif)/)
113             {
114                 my ($prespace, $condition) = ($1,$2);
115                 my $indent = q{  } x ( @{ $state{stack} } - 1 );
116                 if ( $prespace ne $indent ) {
117                     push @pp_indent => "$path:$state{line_cnt}\n"
118                         . "     got: $line"
119                         . "expected: #$indent$condition --  it's inside of "
120                         . ( join ' > ', @{ $state{stack} } ) . "\n";
121                     $pp_failed{"$path\n"} = 1;
122                 }
123                 pop @{ $state{stack} };
124                 next;
125             }
126             next unless @{ $state{stack} };
128             if ( $line =~ m/^\s*\#(\s*)(.*)/)
129             {
130                 my ($prespace, $condition) = ($1,$2);
131                 next if ($line =~ m/ASSERT_ARGS_/); # autogenerated by headerizer
132                 my $indent = q{  } x (@{ $state{stack} });
133                 if ( $prespace ne $indent ) {
134                     push @pp_indent => "$path:$state{line_cnt}\n"
135                         . "     got: $line"
136                         . "expected: #$indent$condition -- it's inside of "
137                         . ( join ' > ', @{ $state{stack} } ) . "\n";
138                     $pp_failed{"$path\n"} = 1;
139                 }
140                 next;
141             }
143             ## c source scan
144             # for now just try to catch glaring errors.  A real parser is
145             # probably overkill for this task.  For now we just check the
146             # first line of a function, and assume that more likely than not
147             # indenting is consistent within a func body.
148             if ($line =~ /^(\s*).*\{\s*$/) {
150                 my $prespace = $1;
151                 # note the beginning of a block, and its indent depth.
152                 $state{bif} = length($prespace);
153                 next;
154             }
156             if ($line =~ /^\s*([\#\}])/) {
158                 my $closing_punc = $1;
159                 # skip the last line of the func or cpp directives.
160                 $state{bif} = undef if ( $closing_punc eq "}" );
161                 next;
162             }
164             if ( defined($state{bif}) ) {
166                 # first line of a block
167                 if ( $state{bif} == 0 ) {
169                     # first line of a top-level block (first line of a function,
170                     # in other words)
171                     my ($indent) = $line =~ /^(\s*)/;
172                     if ( length($indent) != 4 ) {
173                         push @c_indent => "$path:$state{line_cnt}\n"
174                             . "    apparent non-4 space indenting ("
175                             . length($indent)
176                             . " spaces)\n";
177                         $c_failed{"$path\n"} = 1;
178                     }
179                 }
180                 $state{bif} = undef;
181             }
183             my ($indent) = $line =~ /^(\s+)/ or next;
184             $indent = length($indent);
186             # Ignore the indentation of the current line if the last
187             # character of the was anything but a ';'.
188             #
189             # The indentation of the previous line is not considered.
190             # Check sanity by verifying that the indentation of the current line
191             # is divisible by four, unless it should be outdented by 2.
192             if ($line =~ m{: (?:\s* /\* .*? \*/)? $}x) {
193                 if ( $indent % 4 != 2 &&
194                     !$state{in_comment} &&
195                     $state{prev_last_char} eq ';'
196                 ) {
197                     push @c_indent => "$path:$state{line_cnt}\n"
198                         . "    apparent non-2 space outdenting ($indent spaces)\n";
199                     $c_failed{"$path\n"} = 1
200                 }
201             }
202             else {
203                 if ( $indent % 4 &&
204                     !$state{in_comment} &&
205                     $state{prev_last_char} eq ';'
206                 ) {
207                     push @c_indent => "$path:$state{line_cnt}\n"
208                         . "    apparent non-4 space indenting ($indent space"
209                         . ( $indent == 1 ? '' : 's' ) . ")\n";
210                     $c_failed{"$path\n"} = 1;
211                 }
212             }
213         }
214     }
216     # get the lists of files failing the test
217     my @c_failed_files  = keys %c_failed;
218     my @pp_failed_files = keys %pp_failed;
220 ## L<PDD07/Code Formatting/"Preprocessor #directives must be indented two columns per nesting level, with two exceptions: neither PARROT_IN_CORE nor the outermost _GUARD #ifdefs cause the level of indenting to increase">
221     ok( !scalar(@pp_indent), 'Correctly indented preprocessor directives' )
222         or diag( "incorrect indenting in preprocessor directive found "
223             . scalar @pp_indent
224             . " occurrences in "
225             . scalar @pp_failed_files
226             . " files:\n@pp_indent" );
228     ok( !scalar(@c_indent), 'Correctly indented C files' )
229         or diag( "incorrect indenting in C file found "
230             . scalar @c_indent
231             . " occurrences in "
232             . scalar @c_failed_files
233             . " files:\n@c_indent" );
236 # dump_state() may be used to diagnose indentation problems.
237 #     dump_state(\%state, $line);
238 # Takes a list of two arguments:  reference to %state and the current line
239 # (once it has been chomped).
240 # Prints pipe-delimited list of important features of current state.
241 sub dump_state {
242     my ($state, $line) = @_;
243     print STDERR (join q{|} => (
244         $state->{line_cnt},
245         (defined($state->{bif}) ? $state->{bif} : q{u}),
246         $state->{in_comment},
247         (join q{*} => @{ $state->{stack} }),
248         $line,
249     ) ), "\n";
252 # Local Variables:
253 #   mode: cperl
254 #   cperl-indent-level: 4
255 #   fill-column: 100
256 # End:
257 # vim: expandtab shiftwidth=4: