JsHintTask::main()   F
last analyzed

Complexity

Conditions 23
Paths 297

Size

Total Lines 122
Code Lines 91

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 552

Importance

Changes 0
Metric Value
eloc 91
dl 0
loc 122
ccs 0
cts 94
cp 0
rs 2.1208
c 0
b 0
f 0
cc 23
nc 297
nop 0
crap 552

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\JsHint;
22
23
use Phing\Task;
24
use Phing\Type\Element\FileSetAware;
25
use Phing\Exception\BuildException;
26
use Phing\Io\File as PhingFile;
27
use Phing\Project;
28
29
/**
30
 * JsHintTask
31
 *
32
 * Checks the JavaScript code using JSHint
33
 * See http://www.jshint.com/
34
 *
35
 * @author  Martin Hujer <[email protected]>
36
 * @package phing.tasks.ext
37
 * @since   2.6.2
38
 */
39
class JsHintTask extends Task
40
{
41
    use FileSetAware;
42
43
    /**
44
     * The source file (from xml attribute)
45
     *
46
     * @var string
47
     */
48
    protected $file;
49
50
    /**
51
     * Should the build fail on JSHint errors
52
     *
53
     * @var boolean
54
     */
55
    private $haltOnError = false;
56
57
    /**
58
     * Should the build fail on JSHint warnings
59
     *
60
     * @var boolean
61
     */
62
    private $haltOnWarning = false;
63
64
    /**
65
     * reporter
66
     *
67
     * @var string
68
     */
69
    private $reporter = 'checkstyle';
70
71
    /**
72
     * xmlAttributes
73
     *
74
     * @var array
75
     */
76
    private $xmlAttributes = [
77
        'severity' => [
78
            'error' => 'error',
79
            'warning' => 'warning',
80
            'info' => 'info'
81
        ],
82
        'fileError' => 'error',
83
        'line' => 'line',
84
        'column' => 'column',
85
        'message' => 'message',
86
    ];
87
88
    /**
89
     * Path where the the report in Checkstyle format should be saved
90
     *
91
     * @var string
92
     */
93
    private $checkstyleReportPath;
94
95
    /**
96
     * @var string $config
97
     */
98
    private $config;
99
100
    /**
101
     * @var string
102
     */
103
    private $executable = 'jshint';
104
105
    /**
106
     * File to be performed syntax check on
107
     *
108
     * @param string $file
109
     */
110
    public function setFile($file)
111
    {
112
        $this->file = new PhingFile($file);
113
    }
114
115
    /**
116
     * @param $haltOnError
117
     */
118
    public function setHaltOnError($haltOnError)
119
    {
120
        $this->haltOnError = $haltOnError;
121
    }
122
123
    /**
124
     * @param $haltOnWarning
125
     */
126
    public function setHaltOnWarning($haltOnWarning)
127
    {
128
        $this->haltOnWarning = $haltOnWarning;
129
    }
130
131
    /**
132
     * @param $checkstyleReportPath
133
     */
134
    public function setCheckstyleReportPath($checkstyleReportPath)
135
    {
136
        $this->checkstyleReportPath = $checkstyleReportPath;
137
    }
138
139
    /**
140
     * @param string $reporter
141
     */
142
    public function setReporter($reporter)
143
    {
144
        $this->reporter = $reporter;
145
146
        if ($this->reporter === 'jslint') {
147
            $this->xmlAttributes = [
148
                'severity' => ['error' => 'E', 'warning' => 'W', 'info' => 'I'],
149
                'fileError' => 'issue',
150
                'line' => 'line',
151
                'column' => 'char',
152
                'message' => 'reason',
153
            ];
154
        }
155
    }
156
157
    /**
158
     * @param string $config
159
     */
160
    public function setConfig($config)
161
    {
162
        $this->config = $config;
163
    }
164
165
    /**
166
     * @param string $path
167
     */
168
    public function setExecutable($path)
169
    {
170
        $this->executable = $path;
171
    }
172
173
    /**
174
     * @throws BuildException
175
     */
176
    public function main()
177
    {
178
        if (!isset($this->file) && count($this->filesets) === 0) {
179
            throw new BuildException("Missing either a nested fileset or attribute 'file' set");
180
        }
181
182
        if (!isset($this->file)) {
183
            $fileList = [];
184
            $project = $this->getProject();
185
            foreach ($this->filesets as $fs) {
186
                $ds = $fs->getDirectoryScanner($project);
187
                $files = $ds->getIncludedFiles();
188
                $dir = $fs->getDir($this->project)->getAbsolutePath();
189
                foreach ($files as $file) {
190
                    $fileList[] = $dir . DIRECTORY_SEPARATOR . $file;
191
                }
192
            }
193
        } else {
194
            $fileList = [$this->file];
195
        }
196
197
        $this->checkJsHintIsInstalled();
198
199
        $fileList = array_map('escapeshellarg', $fileList);
200
        if ($this->config) {
201
            $command = sprintf(
202
                '%s --config=%s --reporter=%s %s',
203
                $this->executable,
204
                $this->config,
205
                $this->reporter,
206
                implode(' ', $fileList)
207
            );
208
        } else {
209
            $command = sprintf(
210
                '%s --reporter=%s %s',
211
                $this->executable,
212
                $this->reporter,
213
                implode(' ', $fileList)
214
            );
215
        }
216
        $this->log('Execute: ' . PHP_EOL . $command, Project::MSG_VERBOSE);
217
        $output = [];
218
        exec($command, $output);
219
        $output = implode(PHP_EOL, $output);
220
221
        if ($this->checkstyleReportPath) {
222
            file_put_contents($this->checkstyleReportPath, $output);
223
            $this->log('');
224
            $this->log('Checkstyle report saved to ' . $this->checkstyleReportPath);
225
        }
226
227
        libxml_clear_errors();
228
        libxml_use_internal_errors(true);
229
        $xml = simplexml_load_string($output, 'SimpleXMLElement', LIBXML_PARSEHUGE);
230
        if (false === $xml) {
231
            $errors = libxml_get_errors();
232
            if (!empty($errors)) {
233
                foreach ($errors as $error) {
234
                    $msg = $xml[$error->line - 1] . "\n";
235
                    $msg .= str_repeat('-', $error->column) . "^\n";
236
237
                    switch ($error->level) {
238
                        case LIBXML_ERR_WARNING:
239
                            $msg .= 'Warning ' . $error->code . ': ';
240
                            break;
241
                        case LIBXML_ERR_ERROR:
242
                            $msg .= 'Error ' . $error->code . ': ';
243
                            break;
244
                        case LIBXML_ERR_FATAL:
245
                            $msg .= 'Fatal error ' . $error->code . ': ';
246
                            break;
247
                    }
248
                    $msg .= trim($error->message) . PHP_EOL . '  Line: ' . $error->line . PHP_EOL . '  Column: ' . $error->column;
249
                    $this->log($msg, Project::MSG_VERBOSE);
250
                }
251
                throw new BuildException('Unable to parse output of JSHint, use checkstyleReportPath="/path/to/report.xml" to debug');
252
            }
253
        }
254
        $projectBasedir = $this->getProjectBasedir();
255
        $errorsCount = 0;
256
        $warningsCount = 0;
257
        $fileError = $this->xmlAttributes['fileError'];
258
        foreach ($xml->file as $file) {
259
            $fileAttributes = $file->attributes();
0 ignored issues
show
Bug introduced by
The method attributes() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

259
            /** @scrutinizer ignore-call */ 
260
            $fileAttributes = $file->attributes();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
260
            $fileName = (string) $fileAttributes['name'];
261
            foreach ($file->$fileError as $error) {
262
                $errAttr = (array) $error->attributes();
263
                $attrs = current($errAttr);
264
265
                if ($attrs['severity'] === $this->xmlAttributes['severity']['error']) {
266
                    $errorsCount++;
267
                } elseif ($attrs['severity'] === $this->xmlAttributes['severity']['warning']) {
268
                    $warningsCount++;
269
                } elseif ($attrs['severity'] !== $this->xmlAttributes['severity']['info']) {
270
                    throw new BuildException(sprintf('Unknown severity "%s"', $attrs['severity']));
271
                }
272
                $e = sprintf(
273
                    '%s: line %d, col %d, %s',
274
                    str_replace($projectBasedir, '', $fileName),
275
                    $attrs[$this->xmlAttributes['line']],
276
                    $attrs[$this->xmlAttributes['column']],
277
                    $attrs[$this->xmlAttributes['message']]
278
                );
279
                $this->log($e);
280
            }
281
        }
282
283
        $message = sprintf(
284
            'JSHint detected %d errors and %d warnings.',
285
            $errorsCount,
286
            $warningsCount
287
        );
288
        if ($this->haltOnError && $errorsCount) {
289
            throw new BuildException($message);
290
        }
291
292
        if ($this->haltOnWarning && $warningsCount) {
293
            throw new BuildException($message);
294
        }
295
296
        $this->log('');
297
        $this->log($message);
298
    }
299
300
    /**
301
     * @return string Path to the project basedir
302
     * @throws BuildException
303
     */
304
    private function getProjectBasedir()
305
    {
306
        return $this->getProject()->getBasedir()->getAbsolutePath() . DIRECTORY_SEPARATOR;
307
    }
308
309
    /**
310
     * Checks, whether the JSHint can be executed
311
     *
312
     * @throws BuildException
313
     */
314
    private function checkJsHintIsInstalled()
315
    {
316
        $command = sprintf('%s -v 2>&1', $this->executable);
317
        exec($command, $output, $return);
318
        if ($return !== 0) {
319
            throw new BuildException('JSHint is not installed!');
320
        }
321
    }
322
}
323