JslLintTask::lint()   F
last analyzed

Complexity

Conditions 29
Paths 586

Size

Total Lines 110
Code Lines 72

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 870

Importance

Changes 0
Metric Value
eloc 72
dl 0
loc 110
ccs 0
cts 69
cp 0
rs 0.575
c 0
b 0
f 0
cc 29
nc 586
nop 1
crap 870

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
5
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
7
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
8
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
10
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
11
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
12
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
13
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
14
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
 *
16
 * This software consists of voluntary contributions made by many individuals
17
 * and is licensed under the LGPL. For more information please see
18
 * <http://phing.info>.
19
 */
20
21
namespace Phing\Task\Ext;
22
23
use Phing\Exception\BuildException;
24
use Phing\Io\File;
25
use Phing\Io\FileWriter;
26
use Phing\Project;
27
use Phing\Task;
28
use Phing\Type\Element\FileSetAware;
29
use Phing\Util\DataStore;
30
use Phing\Util\StringHelper;
31
32
/**
33
 * A Javascript lint task. Checks syntax of Javascript files.
34
 * Javascript lint (http://www.javascriptlint.com) must be in the system path.
35
 * This class is based on Knut Urdalen's PhpLintTask.
36
 *
37
 * @author Stefan Priebsch <[email protected]>
38
 */
39
class JslLintTask extends Task
40
{
41
    use FileSetAware;
42
43
    /**
44
     * @var File
45
     */
46
    protected $file; // the source file (from xml attribute)
47
48
    /**
49
     * @var bool
50
     */
51
    protected $showWarnings = true;
52
53
    /**
54
     * @var bool
55
     */
56
    protected $haltOnFailure = false;
57
58
    /**
59
     * @var bool
60
     */
61
    protected $haltOnWarning = false;
62
63
    /**
64
     * @var bool
65
     */
66
    protected $hasErrors = false;
67
68
    /**
69
     * @var bool
70
     */
71
    protected $hasWarnings = false;
72
73
    /**
74
     * @var File
75
     */
76
    protected $tofile;
77
78
    /**
79
     * @var array
80
     */
81
    private $badFiles = [];
82
83
    /**
84
     * @var DataStore
85
     */
86
    private $cache;
87
88
    /**
89
     * @var File
90
     */
91
    private $conf;
92
93
    /**
94
     * @var string
95
     */
96
    private $executable = 'jsl';
97
98
    /**
99
     * Sets the flag if warnings should be shown.
100
     *
101
     * @param bool $show
102
     */
103
    public function setShowWarnings($show)
104
    {
105
        $this->showWarnings = StringHelper::booleanValue($show);
106
    }
107
108
    /**
109
     * The haltonfailure property.
110
     *
111
     * @param bool $aValue
112
     */
113
    public function setHaltOnFailure($aValue)
114
    {
115
        $this->haltOnFailure = $aValue;
116
    }
117
118
    /**
119
     * The haltonwarning property.
120
     *
121
     * @param bool $aValue
122
     */
123
    public function setHaltOnWarning($aValue)
124
    {
125
        $this->haltOnWarning = $aValue;
126
    }
127
128
    /**
129
     * File to be performed syntax check on.
130
     */
131
    public function setFile(File $file)
132
    {
133
        $this->file = $file;
134
    }
135
136
    /**
137
     * Whether to store last-modified times in cache.
138
     */
139
    public function setCacheFile(File $file)
140
    {
141
        $this->cache = new DataStore($file);
142
    }
143
144
    /**
145
     * jsl config file.
146
     */
147
    public function setConfFile(File $file)
148
    {
149
        $this->conf = $file;
150
    }
151
152
    /**
153
     * @param string $path
154
     *
155
     * @throws BuildException
156
     */
157
    public function setExecutable($path)
158
    {
159
        $this->executable = $path;
160
161
        if (!@file_exists($path)) {
162
            throw new BuildException("JavaScript Lint executable '{$path}' not found");
163
        }
164
    }
165
166
    /**
167
     * @return string
168
     */
169
    public function getExecutable()
170
    {
171
        return $this->executable;
172
    }
173
174
    /**
175
     * File to save error messages to.
176
     */
177
    public function setToFile(File $tofile)
178
    {
179
        $this->tofile = $tofile;
180
    }
181
182
    /**
183
     * Execute lint check against PhingFile or a FileSet.
184
     */
185
    public function main()
186
    {
187
        if (!isset($this->file) and 0 == count($this->filesets)) {
188
            throw new BuildException("Missing either a nested fileset or attribute 'file' set");
189
        }
190
191
        if (empty($this->executable)) {
192
            throw new BuildException("Missing the 'executable' attribute");
193
        }
194
195
        if ($this->file instanceof File) {
0 ignored issues
show
introduced by
$this->file is always a sub-type of Phing\Io\File.
Loading history...
196
            $this->lint($this->file->getPath());
197
        } else { // process filesets
198
            $project = $this->getProject();
199
            foreach ($this->filesets as $fs) {
200
                $ds = $fs->getDirectoryScanner($project);
201
                $files = $ds->getIncludedFiles();
202
                $dir = $fs->getDir($this->project)->getPath();
203
                foreach ($files as $file) {
204
                    $this->lint($dir . DIRECTORY_SEPARATOR . $file);
205
                }
206
            }
207
        }
208
209
        // write list of 'bad files' to file (if specified)
210
        if ($this->tofile) {
211
            $writer = new FileWriter($this->tofile);
212
213
            foreach ($this->badFiles as $file => $messages) {
214
                foreach ($messages as $msg) {
215
                    $writer->write($file . '=' . $msg . PHP_EOL);
216
                }
217
            }
218
219
            $writer->close();
220
        }
221
222
        if ($this->haltOnFailure && $this->hasErrors) {
223
            throw new BuildException(
224
                'Syntax error(s) in JS files:' . implode(
225
                    ', ',
226
                    array_keys($this->badFiles)
227
                )
228
            );
229
        }
230
        if ($this->haltOnWarning && $this->hasWarnings) {
231
            throw new BuildException(
232
                'Syntax warning(s) in JS files:' . implode(
233
                    ', ',
234
                    array_keys($this->badFiles)
235
                )
236
            );
237
        }
238
    }
239
240
    /**
241
     * Performs the actual syntax check.
242
     *
243
     * @param string $file
244
     *
245
     * @throws BuildException
246
     *
247
     * @return bool|void
248
     */
249
    protected function lint($file)
250
    {
251
        $command = $this->executable . ' -output-format ' . escapeshellarg(
252
            'file:__FILE__;line:__LINE__;message:__ERROR__'
253
        ) . ' ';
254
255
        if (isset($this->conf)) {
256
            $command .= '-conf ' . escapeshellarg($this->conf->getPath()) . ' ';
257
        }
258
259
        $command .= '-process ';
260
261
        if (file_exists($file)) {
262
            if (is_readable($file)) {
263
                if ($this->cache) {
264
                    $lastmtime = $this->cache->get($file);
265
266
                    if ($lastmtime >= filemtime($file)) {
267
                        $this->log("Not linting '" . $file . "' due to cache", Project::MSG_DEBUG);
268
269
                        return false;
270
                    }
271
                }
272
273
                $messages = [];
274
                exec($command . '"' . $file . '"', $messages, $return);
275
276
                if ($return > 100) {
277
                    throw new BuildException("Could not execute Javascript Lint executable '{$this->executable}'");
278
                }
279
280
                $summary = $messages[count($messages) - 1];
281
282
                preg_match('/(\d+)\serror/', $summary, $matches);
283
                $errorCount = (count($matches) > 1 ? $matches[1] : 0);
284
285
                preg_match('/(\d+)\swarning/', $summary, $matches);
286
                $warningCount = (count($matches) > 1 ? $matches[1] : 0);
287
288
                $errors = [];
289
                $warnings = [];
290
                if ($errorCount > 0 || $warningCount > 0) {
291
                    $last = false;
292
                    foreach ($messages as $message) {
293
                        $matches = [];
294
                        if (preg_match('/^(\.*)\^$/', $message)) {
295
                            $column = strlen($message);
296
                            if ('error' == $last) {
297
                                $errors[count($errors) - 1]['column'] = $column;
298
                            } else {
299
                                if ('warning' == $last) {
300
                                    $warnings[count($warnings) - 1]['column'] = $column;
301
                                }
302
                            }
303
                            $last = false;
304
                        }
305
                        if (!preg_match('/^file:(.+);line:(\d+);message:(.+)$/', $message, $matches)) {
306
                            continue;
307
                        }
308
                        $msg = $matches[3];
309
                        $data = ['filename' => $matches[1], 'line' => $matches[2], 'message' => $msg];
310
                        if (preg_match('/^.*error:.+$/i', $msg)) {
311
                            $errors[] = $data;
312
                            $last = 'error';
313
                        } else {
314
                            if (preg_match('/^.*warning:.+$/i', $msg)) {
315
                                $warnings[] = $data;
316
                                $last = 'warning';
317
                            }
318
                        }
319
                    }
320
                }
321
322
                if ($this->showWarnings && $warningCount > 0) {
323
                    $this->log($file . ': ' . $warningCount . ' warnings detected', Project::MSG_WARN);
324
                    foreach ($warnings as $warning) {
325
                        $this->log(
326
                            '- line ' . $warning['line'] . (isset($warning['column']) ? ' column ' . $warning['column'] : '') . ': ' . $warning['message'],
327
                            Project::MSG_WARN
328
                        );
329
                    }
330
                    $this->hasWarnings = true;
331
                }
332
333
                if ($errorCount > 0) {
334
                    $this->log($file . ': ' . $errorCount . ' errors detected', Project::MSG_ERR);
335
                    if (!isset($this->badFiles[$file])) {
336
                        $this->badFiles[$file] = [];
337
                    }
338
339
                    foreach ($errors as $error) {
340
                        $message = 'line ' . $error['line'] . (isset($error['column']) ? ' column ' . $error['column'] : '') . ': ' . $error['message'];
341
                        $this->log('- ' . $message, Project::MSG_ERR);
342
                        $this->badFiles[$file][] = $message;
343
                    }
344
                    $this->hasErrors = true;
345
                } else {
346
                    if (!$this->showWarnings || 0 == $warningCount) {
347
                        $this->log($file . ': No syntax errors detected', Project::MSG_VERBOSE);
348
349
                        if ($this->cache) {
350
                            $this->cache->put($file, filemtime($file));
351
                        }
352
                    }
353
                }
354
            } else {
355
                throw new BuildException('Permission denied: ' . $file);
356
            }
357
        } else {
358
            throw new BuildException('File not found: ' . $file);
359
        }
360
    }
361
}
362