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   70828 use strict;
  133         192  
  133         3462  
3 133     133   422 use warnings;
  133         183  
  133         2437  
4 133     133   852 use Perl::Lint::Constants::Type;
  133         164  
  133         60405  
5 133     133   845 use parent "Perl::Lint::Policy";
  133         189  
  133         598  
6              
7             use constant {
8 133         101843 DESC => 'Close filehandles as soon as possible after opening them..',
9             EXPL => [209],
10 133     133   6901 };
  133         182  
11              
12             sub evaluate {
13 33     33 0 61 my ($class, $file, $tokens, $src, $args) = @_;
14              
15 33         25 my @violations;
16              
17 33         36 my $line_gap = 9;
18              
19 33 100       73 if (my $this_policies_arg = $args->{require_brief_open}) {
20 1   50     8 $line_gap = $this_policies_arg->{lines} // 9;
21             }
22              
23 33         35 my $depth = 0;
24              
25 33         54 my @opened_file_handlers_for_each_depth = ([]);
26              
27 33         27 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         80 for (my $i = 0, my $token_type, my $token_data; my $token = $tokens->[$i]; $i++) {
35 618         448 $token_type = $token->{type};
36 618         413 $token_data = $token->{data};
37              
38 618 100       742 if ($token_type == LEFT_BRACE) {
39 17         14 $depth++;
40 17         31 next;
41             }
42              
43 601 100       701 if ($token_type == RIGHT_BRACE) {
44 17 100       19 my %not_closed_file_handlers = %{$opened_file_handlers_for_each_depth[-1]->[$depth] || {}};
  17         73  
45 17         31 for my $not_closed_fh_name (keys %not_closed_file_handlers) {
46 3         10 push @not_closed_file_handlers, $not_closed_file_handlers{$not_closed_fh_name};
47             }
48              
49 17         13 $depth--;
50              
51 17 100       29 if ($function_declared_depth{$depth}) {
52 4         6 pop @opened_file_handlers_for_each_depth;
53 4 50       11 if (!@opened_file_handlers_for_each_depth) {
54 0         0 @opened_file_handlers_for_each_depth = ([]);
55             }
56             }
57              
58 17         41 next;
59             }
60              
61             # to support CORE(::GLOBAL)::open or close
62 584         347 my $is_core_func = 0;
63 584 100 66     744 if ($token_type == NAMESPACE && $token_data eq 'CORE') {
64 9 50       16 $token = $tokens->[++$i] or last;
65 9 50       14 if ($token->{type} == NAMESPACE_RESOLVER) {
66 9 50       14 $token = $tokens->[++$i] or last;
67 9 100 66     31 if ($token->{type} == NAMESPACE && $token->{data} eq 'GLOBAL') {
68 7 50       15 $token = $tokens->[++$i] or last;
69 7 50       14 if ($token->{type} == NAMESPACE_RESOLVER) {
70 7 50       13 $token = $tokens->[++$i] or last;
71             }
72             }
73 9         6 $is_core_func = 1;
74 9         7 $token_type = $token->{type};
75 9         8 $token_data = $token->{data};
76              
77             }
78              
79             # fall through
80             }
81              
82             # for open()
83 584 100 100     1610 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         33 my $lbnum = 0;
88 53         103 for ($i++; $token = $tokens->[$i]; $i++) {
89 111         87 $token_type = $token->{type};
90 111         94 $token_data = $token->{data};
91 111 100 100     762 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         76 $opened_file_handlers_for_each_depth[-1]->[$depth]->{$token_data} = $token;
97 34         40 last;
98             }
99             elsif ($token_type == KEY) {
100 10 100       34 if ($token_data =~ /\A[A-Z0-9_]+\z/) { # check UPPER_CASE or not
101 7         13 $opened_file_globs_for_each_depth{$token_data} = $token;
102             }
103 10         13 last;
104             }
105             elsif ($token_type == LEFT_BRACE) {
106 5         10 $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         14 last;
115             }
116             }
117              
118 53 100       73 if ($lbnum) {
119 5         10 for ($i++; $token = $tokens->[$i]; $i++) {
120 5         4 $token_type = $token->{type};
121 5 50       11 if ($token_type == RIGHT_BRACE) {
122 5 50       11 last if --$lbnum <= 0;
123             }
124             }
125             }
126              
127 53         95 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     1707 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       51 $token = $tokens->[++$i] or last;
137 27         25 $token_type = $token->{type};
138 27 100       42 if ($token_type == LEFT_PAREN) {
139 2 50       4 $token = $tokens->[++$i] or last;
140 2         4 $token_type = $token->{type};
141             }
142              
143 27         30 $token_data = $token->{data};
144              
145 27 100 66     107 if (
    100 100        
146             $token_type == GLOBAL_VAR ||
147             $token_type == LOCAL_VAR ||
148             $token_type == VAR
149             ) {
150 21         46 for my $d (reverse 0 .. $depth) {
151 26         34 my $hit = $opened_file_handlers_for_each_depth[-1]->[$d]->{$token_data};
152              
153 26 100       43 if (defined $hit) {
154 16 100       38 if ($token->{line} - $hit->{line} <= $line_gap) {
155 13         22 delete $opened_file_handlers_for_each_depth[-1]->[$d]->{$token_data};
156             }
157 16         16 last;
158             }
159             }
160             }
161             elsif ($token_type == KEY) {
162 3         4 $closed_file_globs_for_each_depth{$token_data} = 1;
163             }
164             }
165              
166             # for close method (OOP style)
167 531 100 100     681 if ($token_type == METHOD && $token_data eq 'close') {
168 3         9 my $var_token = $tokens->[$i-2];
169 3         7 my $var_type = $var_token->{type};
170 3         7 my $var_name = $var_token->{data};
171              
172 3 100 66     23 if (
      100        
173             $var_type == GLOBAL_VAR ||
174             $var_type == LOCAL_VAR ||
175             $var_type == VAR
176             ) {
177 2         7 for my $d (reverse 0 .. $depth) {
178 2         4 my $hit = $opened_file_handlers_for_each_depth[-1]->[$d]->{$var_name};
179 2 100       24 if (defined $hit) {
180 1 50       4 if ($var_token->{line} - $hit->{line} <= $line_gap) {
181 1         4 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       1145 if ($token_type == FUNCTION_DECL) {
191 4         11 $function_declared_depth{$depth} = 1;
192              
193 4         8 push @opened_file_handlers_for_each_depth, [];
194 4         11 next;
195             }
196             }
197              
198             # for file handlers (variable)
199 33 100       30 my %not_closed_file_handlers = %{$opened_file_handlers_for_each_depth[-1]->[0] || {}};
  33         124  
200 33         56 for my $not_closed_fh_name (keys %not_closed_file_handlers) {
201 17         29 push @not_closed_file_handlers, $not_closed_file_handlers{$not_closed_fh_name};
202             }
203              
204 33         47 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         87 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         45 for my $not_closed_file_glob (keys %closed_file_globs_for_each_depth) {
216 3         6 delete $opened_file_globs_for_each_depth{$not_closed_file_glob};
217             }
218              
219 33         39 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         21 description => DESC,
224             explanation => EXPL,
225             policy => __PACKAGE__,
226             };
227             }
228              
229 33         154 return \@violations;
230             }
231              
232             1;
233