File Coverage

blib/lib/Perl/Lint/Policy/InputOutput/RequireBriefOpen.pm
Criterion Covered Total %
statement 111 114 97.3
branch 61 74 82.4
condition 54 65 83.0
subroutine 6 6 100.0
pod 0 1 0.0
total 232 260 89.2


line stmt bran cond sub pod time code
1             package Perl::Lint::Policy::InputOutput::RequireBriefOpen;
2 133     133   70482 use strict;
  133         184  
  133         3017  
3 133     133   419 use warnings;
  133         167  
  133         2371  
4 133     133   797 use Perl::Lint::Constants::Type;
  133         155  
  133         58886  
5 133     133   574 use parent "Perl::Lint::Policy";
  133         170  
  133         581  
6              
7             use constant {
8 133         100368 DESC => 'Close filehandles as soon as possible after opening them..',
9             EXPL => [209],
10 133     133   6604 };
  133         176  
11              
12             sub evaluate {
13 33     33 0 48 my ($class, $file, $tokens, $src, $args) = @_;
14              
15 33         26 my @violations;
16              
17 33         22 my $line_gap = 9;
18              
19 33 100       68 if (my $this_policies_arg = $args->{require_brief_open}) {
20 1   50     5 $line_gap = $this_policies_arg->{lines} // 9;
21             }
22              
23 33         17 my $depth = 0;
24              
25 33         52 my @opened_file_handlers_for_each_depth = ([]);
26              
27 33         33 my %opened_file_globs_for_each_depth;
28             my %closed_file_globs_for_each_depth;
29              
30 0         0 my %function_declared_depth;
31              
32 0         0 my @not_closed_file_handlers;
33              
34 33         75 for (my $i = 0, my $token_type, my $token_data; my $token = $tokens->[$i]; $i++) {
35 618         457 $token_type = $token->{type};
36 618         403 $token_data = $token->{data};
37              
38 618 100       710 if ($token_type == LEFT_BRACE) {
39 17         16 $depth++;
40 17         26 next;
41             }
42              
43 601 100       625 if ($token_type == RIGHT_BRACE) {
44 17 100       12 my %not_closed_file_handlers = %{$opened_file_handlers_for_each_depth[-1]->[$depth] || {}};
  17         59  
45 17         24 for my $not_closed_fh_name (keys %not_closed_file_handlers) {
46 3         6 push @not_closed_file_handlers, $not_closed_file_handlers{$not_closed_fh_name};
47             }
48              
49 17         12 $depth--;
50              
51 17 100       29 if ($function_declared_depth{$depth}) {
52 4         3 pop @opened_file_handlers_for_each_depth;
53 4 50       13 if (!@opened_file_handlers_for_each_depth) {
54 0         0 @opened_file_handlers_for_each_depth = ([]);
55             }
56             }
57              
58 17         33 next;
59             }
60              
61             # to support CORE(::GLOBAL)::open or close
62 584         358 my $is_core_func = 0;
63 584 100 66     776 if ($token_type == NAMESPACE && $token_data eq 'CORE') {
64 9 50       18 $token = $tokens->[++$i] or last;
65 9 50       12 if ($token->{type} == NAMESPACE_RESOLVER) {
66 9 50       14 $token = $tokens->[++$i] or last;
67 9 100 66     28 if ($token->{type} == NAMESPACE && $token->{data} eq 'GLOBAL') {
68 7 50       13 $token = $tokens->[++$i] or last;
69 7 50       9 if ($token->{type} == NAMESPACE_RESOLVER) {
70 7 50       12 $token = $tokens->[++$i] or last;
71             }
72             }
73 9         7 $is_core_func = 1;
74 9         8 $token_type = $token->{type};
75 9         10 $token_data = $token->{data};
76              
77             }
78              
79             # fall through
80             }
81              
82             # for open()
83 584 100 100     1526 if (
      66        
      100        
      66        
84             ($token_type == BUILTIN_FUNC && $token_data eq 'open') ||
85             ($is_core_func && $token_type == NAMESPACE && $token_data eq 'open')
86             ) {
87 53         39 my $lbnum = 0;
88 53         101 for ($i++; $token = $tokens->[$i]; $i++) {
89 111         86 $token_type = $token->{type};
90 111         116 $token_data = $token->{data};
91 111 100 100     761 if (
    100 100        
    100 100        
    100 100        
      100        
92             $token_type == GLOBAL_VAR ||
93             $token_type == LOCAL_VAR ||
94             $token_type == VAR
95             ) {
96 34         65 $opened_file_handlers_for_each_depth[-1]->[$depth]->{$token_data} = $token;
97 34         38 last;
98             }
99             elsif ($token_type == KEY) {
100 10 100       31 if ($token_data =~ /\A[A-Z0-9_]+\z/) { # check UPPER_CASE or not
101 7         15 $opened_file_globs_for_each_depth{$token_data} = $token;
102             }
103 10         11 last;
104             }
105             elsif ($token_type == LEFT_BRACE) {
106 5         8 $lbnum++;
107             }
108             elsif (
109             $token_type == TYPE_STDIN ||
110             $token_type == TYPE_STDOUT ||
111             $token_type == TYPE_STDERR ||
112             $token_type == COMMA # <= fail safe
113             ) {
114 9         11 last;
115             }
116             }
117              
118 53 100       76 if ($lbnum) {
119 5         8 for ($i++; $token = $tokens->[$i]; $i++) {
120 5         6 $token_type = $token->{type};
121 5 50       8 if ($token_type == RIGHT_BRACE) {
122 5 50       9 last if --$lbnum <= 0;
123             }
124             }
125             }
126              
127 53         92 next;
128             }
129              
130             # for close() or return
131             # If file handler is returned by function, it is equivalent to be closed.
132 531 50 100     1565 if (
      66        
      66        
      66        
      66        
133             $token_type == RETURN || ($token_type == BUILTIN_FUNC && $token_data eq 'close') ||
134             ($is_core_func && $token_type == NAMESPACE && $token_data eq 'close')
135             ) {
136 27 50       44 $token = $tokens->[++$i] or last;
137 27         21 $token_type = $token->{type};
138 27 100       35 if ($token_type == LEFT_PAREN) {
139 2 50       5 $token = $tokens->[++$i] or last;
140 2         3 $token_type = $token->{type};
141             }
142              
143 27         23 $token_data = $token->{data};
144              
145 27 100 66     106 if (
    100 100        
146             $token_type == GLOBAL_VAR ||
147             $token_type == LOCAL_VAR ||
148             $token_type == VAR
149             ) {
150 21         35 for my $d (reverse 0 .. $depth) {
151 26         26 my $hit = $opened_file_handlers_for_each_depth[-1]->[$d]->{$token_data};
152              
153 26 100       39 if (defined $hit) {
154 16 100       36 if ($token->{line} - $hit->{line} <= $line_gap) {
155 13         18 delete $opened_file_handlers_for_each_depth[-1]->[$d]->{$token_data};
156             }
157 16         15 last;
158             }
159             }
160             }
161             elsif ($token_type == KEY) {
162 3         5 $closed_file_globs_for_each_depth{$token_data} = 1;
163             }
164             }
165              
166             # for close method (OOP style)
167 531 100 100     715 if ($token_type == METHOD && $token_data eq 'close') {
168 3         6 my $var_token = $tokens->[$i-2];
169 3         3 my $var_type = $var_token->{type};
170 3         5 my $var_name = $var_token->{data};
171              
172 3 100 66     16 if (
      100        
173             $var_type == GLOBAL_VAR ||
174             $var_type == LOCAL_VAR ||
175             $var_type == VAR
176             ) {
177 2         4 for my $d (reverse 0 .. $depth) {
178 2         2 my $hit = $opened_file_handlers_for_each_depth[-1]->[$d]->{$var_name};
179 2 100       13 if (defined $hit) {
180 1 50       4 if ($var_token->{line} - $hit->{line} <= $line_gap) {
181 1         2 delete $opened_file_handlers_for_each_depth[-1]->[$d]->{$var_name};
182             }
183 1         2 last;
184             }
185             }
186             }
187             }
188              
189             # to separate scope by function inside and outside
190 531 100       959 if ($token_type == FUNCTION_DECL) {
191 4         8 $function_declared_depth{$depth} = 1;
192              
193 4         6 push @opened_file_handlers_for_each_depth, [];
194 4         9 next;
195             }
196             }
197              
198             # for file handlers (variable)
199 33 100       26 my %not_closed_file_handlers = %{$opened_file_handlers_for_each_depth[-1]->[0] || {}};
  33         146  
200 33         61 for my $not_closed_fh_name (keys %not_closed_file_handlers) {
201 17         22 push @not_closed_file_handlers, $not_closed_file_handlers{$not_closed_fh_name};
202             }
203              
204 33         37 for my $not_closed_file_handler (@not_closed_file_handlers) {
205             push @violations, {
206             filename => $file,
207             line => $not_closed_file_handler->{line},
208 20         66 description => DESC,
209             explanation => EXPL,
210             policy => __PACKAGE__,
211             };
212             }
213              
214             # If glob is used as file handler, it beyonds the scope
215 33         39 for my $not_closed_file_glob (keys %closed_file_globs_for_each_depth) {
216 3         5 delete $opened_file_globs_for_each_depth{$not_closed_file_glob};
217             }
218              
219 33         34 for my $not_closed_file_glob (keys %opened_file_globs_for_each_depth) {
220             push @violations, {
221             filename => $file,
222             line => $opened_file_globs_for_each_depth{$not_closed_file_glob}->{line},
223 5         17 description => DESC,
224             explanation => EXPL,
225             policy => __PACKAGE__,
226             };
227             }
228              
229 33         145 return \@violations;
230             }
231              
232             1;
233