File Coverage

blib/lib/Perl/Lint/Policy/ValuesAndExpressions/RequireConstantVersion.pm
Criterion Covered Total %
statement 95 99 95.9
branch 67 74 90.5
condition 31 33 93.9
subroutine 7 7 100.0
pod 0 1 0.0
total 200 214 93.4


line stmt bran cond sub pod time code
1             package Perl::Lint::Policy::ValuesAndExpressions::RequireConstantVersion;
2 134     134   99692 use strict;
  134         266  
  134         4982  
3 134     134   680 use warnings;
  134         217  
  134         3902  
4 134     134   1551 use Perl::Lint::Constants::Type;
  134         224  
  134         80013  
5 134     134   773 use parent "Perl::Lint::Policy";
  134         249  
  134         800  
6              
7             use constant {
8 134         89215 DESC => '$VERSION value must be a constant',
9             EXPL => 'Computed $VERSION may tie the code to a single repository, or cause spooky action from a distance',
10 134     134   9648 };
  134         220  
11              
12             sub evaluate {
13 13     13 0 38 my ($class, $file, $tokens, $src, $args) = @_;
14              
15 13         19 my $is_used_version = 0;
16 13 100       42 if (my $this_packages_arg = $args->{require_constant_version}) {
17 1         5 $is_used_version = $this_packages_arg->{allow_version_without_use_on_same_line};
18             }
19              
20 13         16 my @violations;
21              
22 13         17 my $is_version_assigner = 0;
23              
24 13         57 TOP: for (my $i = 0, my $token_type, my $token_data; my $token = $tokens->[$i]; $i++) {
25 1616         1467 $token_type = $token->{type};
26 1616         1621 $token_data = $token->{data};
27              
28             # `use version;` declared?
29 1616 100 100     2583 if ($token_type == USED_NAME && $token_data eq 'version') {
30 10         11 $is_used_version = 1;
31 10         19 next;
32             }
33              
34             # in assigning context?
35 1606 100       2080 if ($token_type == ASSIGN) {
36 7         9 $is_version_assigner = 1;
37 7         14 next;
38             }
39              
40             # reset context information
41 1599 100       1983 if ($token_type == SEMI_COLON) {
42 158         153 $is_version_assigner = 0;
43 158         269 next;
44             }
45              
46 1441 100       1808 if ($token_type == BUILTIN_FUNC) {
47 16 50       38 $token = $tokens->[++$i] or last;
48 16 100       30 if ($token->{type} == LEFT_PAREN) {
49             # skip tokens which are surrounded by parenthesis
50 4         8 my $lpnum = 1;
51 4         23 for ($i++; $token = $tokens->[$i]; $i++) {
52 41         45 $token_type = $token->{type};
53              
54 41 100       102 if ($token_type == LEFT_PAREN) {
    100          
55 2         4 $lpnum++;
56             }
57             elsif ($token_type == RIGHT_PAREN) {
58 6 100       18 last if --$lpnum <= 0;
59             }
60             }
61             }
62             # else: skip a token (means NOP)
63             }
64              
65 1441 100 100     4000 if ($token_type != GLOBAL_VAR && $token_type != VAR) {
66 1244         2107 next;
67             }
68              
69 197 100       306 if ($token_data ne '$VERSION') {
70 6         12 next;
71             }
72              
73 191 100       266 if ($is_version_assigner) {
74             # skip this!
75 4         6 $is_version_assigner = 0;
76 4         11 next;
77             }
78              
79 187         145 my $is_invalid = 0;
80 187         145 my $is_var_assigned = 0;
81              
82             # check assigning context or not
83 187         380 for ($i++; $token = $tokens->[$i]; $i++) {
84 217         210 $token_type = $token->{type};
85              
86 217 100 100     535 if ($token_type == ASSIGN || $token_type == OR_EQUAL) {
    100          
    100          
87 184         165 last;
88             }
89             elsif ($token_type == REG_OK) {
90 1         3 $is_invalid = 1;
91 1         1 last;
92             }
93             elsif ($token_type == SEMI_COLON) {
94 2         7 next TOP;
95             }
96             }
97              
98 185 100       295 if ($is_invalid) {
99 1         6 goto JUDGEMENT;
100             }
101              
102 184         306 for ($i++; $token = $tokens->[$i]; $i++) {
103 450         432 $token_type = $token->{type};
104 450         528 $token_data = $token->{data};
105              
106 450 100 100     4128 if ($token_type == SEMI_COLON) {
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100          
107 61         61 last;
108             }
109             elsif ($token_type == STRING) {
110 27 100       89 if ($is_invalid = $class->_is_interpolation($token_data)) {
111 6         7 last;
112             }
113             }
114             elsif ($token_type == REG_DOUBLE_QUOTE) {
115 5         10 $i += 2; # skip delimiter
116 5 50       16 $token = $tokens->[$i] or last;
117 5 100       25 if ($is_invalid = $class->_is_interpolation($token->{data})) {
118 2         2 last;
119             }
120             }
121             elsif (
122             $token_type == BUILTIN_FUNC ||
123             $token_type == DO || # do {...}
124             $token_type == STRING_MUL || # "a" x 42
125             $token_type == NAMESPACE || # call other package
126             $token_type == REG_OK || # =~
127             $token_type == LEFT_BRACKET # access element of array
128             ) {
129 109         102 $is_invalid = 1;
130 109         87 last;
131             }
132             elsif ($token_type == ASSIGN) {
133 4         9 $is_var_assigned = 0;
134             }
135             elsif ($token_type == VAR || $token_type == GLOBAL_VAR) {
136 14         32 $is_var_assigned = 1;
137             }
138             elsif ($token_type == KEY) {
139 18 100       48 if ($token_data eq 'qv') { # for `qv(...)` notation
    50          
140 10 100       29 if (!$is_used_version) {
141 2         5 $is_invalid = 1;
142 2         3 last;
143             }
144             }
145             elsif ($token_data eq 'version') { # for `version->new(...)` notation
146 8 100       22 if (!$is_used_version) {
147 4         8 $is_invalid = 1;
148 4         4 last;
149             }
150              
151 4 50       17 $token = $tokens->[++$i] or last;
152 4 50       15 if ($token->{type} != POINTER) {
153 0         0 next;
154             }
155              
156 4 50       15 $token = $tokens->[++$i] or last;
157 4 50 33     20 if ($token->{type} != METHOD && $token->{data} ne 'new') {
158 0         0 next;
159             }
160             }
161             else { # for others
162 0         0 $is_invalid = 1;
163 0         0 last;
164             }
165             }
166             }
167              
168             JUDGEMENT:
169 185 100 100     475 if ($is_invalid || $is_var_assigned) {
170 125         617 push @violations, {
171             filename => $file,
172             line => $token->{line},
173             description => DESC,
174             explanation => EXPL,
175             policy => __PACKAGE__,
176             };
177             }
178             }
179              
180 13         126 return \@violations;
181             }
182              
183             sub _is_interpolation {
184 32     32   45 my ($class, $str) = @_;
185              
186 32         119 while ($str =~ /(\\*)(\$\S+)/gc) {
187 9 100       36 if (length($1) % 2 == 0) {
188             # sigil is not escaped
189             # interpolated!
190 8         32 return 1;
191             }
192             else {
193             # sigil is escaped
194 1         3 next;
195             }
196             }
197              
198 24         82 return;
199             }
200              
201             1;
202