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