Passed
Push — master ( 601cfd...7bcbf1 )
by Michiel
22:46
created

PhpLintTask::init()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
/**
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the LGPL. For more information please see
17
 * <http://phing.info>.
18
 */
19
20
namespace Phing\Task\System;
21
22
use Phing\Exception\BuildException;
23
use Phing\Io\FileWriter;
24
use Phing\Io\File;
25
use Phing\Project;
26
use Phing\Task;
27
use Phing\Task\System\Element\LogLevelAware;
28
use Phing\Type\Element\FileSetAware;
29
use Phing\Util\DataStore;
30
31
/**
32
 * A PHP lint task. Checking syntax of one or more PHP source file.
33
 *
34
 * @author  Knut Urdalen <[email protected]>
35
 * @author  Stefan Priebsch <[email protected]>
36
 * @package phing.tasks.ext
37
 */
38
class PhpLintTask extends Task
39
{
40
    use FileSetAware;
41
    use LogLevelAware;
42
43
    protected $file; // the source file (from xml attribute)
44
45
    protected $errorProperty;
46
    protected $haltOnFailure = false;
47
    protected $hasErrors = false;
48
    protected $badFiles = [];
49
    protected $interpreter = ''; // php interpreter to use for linting
50
51
    protected $cache = null;
52
53
    protected $tofile = null;
54
55
    protected $deprecatedAsError = false;
56
57
    /**
58
     * Initialize the interpreter with the Phing property php.interpreter
59
     */
60
    public function init()
61
    {
62
        $this->setInterpreter($this->project->getProperty('php.interpreter'));
63
    }
64
65
    /**
66
     * Override default php interpreter
67
     *
68
     * @param string $sPhp
69
     * @todo  Do some sort of checking if the path is correct but would
70
     *          require traversing the systems executeable path too
71
     */
72
    public function setInterpreter($sPhp)
73
    {
74
        if (strpos($sPhp, ' ') !== false) {
75
            $sPhp = escapeshellarg($sPhp);
76
        }
77
        $this->interpreter = $sPhp;
78
    }
79
80
    /**
81
     * The haltonfailure property
82
     *
83
     * @param boolean $aValue
84
     */
85
    public function setHaltOnFailure($aValue)
86
    {
87
        $this->haltOnFailure = $aValue;
88
    }
89
90
    /**
91
     * File to be performed syntax check on
92
     *
93
     * @param File $file
94
     */
95
    public function setFile(File $file)
96
    {
97
        $this->file = $file;
98
    }
99
100
    /**
101
     * Set an property name in which to put any errors.
102
     *
103
     * @param string $propname
104
     */
105
    public function setErrorproperty($propname)
106
    {
107
        $this->errorProperty = $propname;
108
    }
109
110
    /**
111
     * Whether to store last-modified times in cache
112
     *
113
     * @param File $file
114
     */
115
    public function setCacheFile(File $file)
116
    {
117
        $this->cache = new DataStore($file);
118
    }
119
120
    /**
121
     * File to save error messages to
122
     *
123
     * @param File $tofile
124
     * @internal param PhingFile $file
125
     */
126
    public function setToFile(File $tofile)
127
    {
128
        $this->tofile = $tofile;
129
    }
130
131
    /**
132
     * Sets whether to treat deprecated warnings (introduced in PHP 5.3) as errors
133
     *
134
     * @param boolean $deprecatedAsError
135
     */
136
    public function setDeprecatedAsError($deprecatedAsError)
137
    {
138
        $this->deprecatedAsError = $deprecatedAsError;
139
    }
140
141
    /**
142
     * Execute lint check against PhingFile or a FileSet
143
     */
144
    public function main()
145
    {
146
        if (!isset($this->file) and count($this->filesets) == 0) {
147
            throw new BuildException("Missing either a nested fileset or attribute 'file' set");
148
        }
149
150
        if ($this->file instanceof File) {
151
            $this->lint($this->file->getPath());
152
        } else { // process filesets
153
            $project = $this->getProject();
154
            foreach ($this->filesets as $fs) {
155
                $ds = $fs->getDirectoryScanner($project);
156
                $files = $ds->getIncludedFiles();
157
                $dir = $fs->getDir($this->project)->getPath();
158
                foreach ($files as $file) {
159
                    $this->lint($dir . DIRECTORY_SEPARATOR . $file);
160
                }
161
            }
162
        }
163
164
        // write list of 'bad files' to file (if specified)
165
        if ($this->tofile) {
166
            $writer = new FileWriter($this->tofile);
167
168
            foreach ($this->badFiles as $file => $messages) {
169
                foreach ($messages as $msg) {
170
                    $writer->write($file . "=" . $msg . PHP_EOL);
171
                }
172
            }
173
174
            $writer->close();
175
        }
176
177
        $message = '';
178
        foreach ($this->badFiles as $file => $messages) {
179
            foreach ($messages as $msg) {
180
                $message .= $file . "=" . $msg . PHP_EOL;
181
            }
182
        }
183
184
        // save list of 'bad files' with errors to property errorproperty (if specified)
185
        if ($this->errorProperty) {
186
            $this->project->setProperty($this->errorProperty, $message);
187
        }
188
189
        if (!empty($this->cache)) {
190
            $this->cache->commit();
191
        }
192
193
        if ($this->haltOnFailure && $this->hasErrors) {
194
            throw new BuildException('Syntax error(s) in PHP files: ' . $message);
195
        }
196
    }
197
198
    /**
199
     * Performs the actual syntax check
200
     *
201
     * @param string $file
202
     * @throws BuildException
203
     */
204
    protected function lint($file)
205
    {
206
        $command = $this->interpreter == ''
207
            ? 'php'
208
            : $this->interpreter;
209
210
        if (strpos($command, 'hhvm') !== false) {
211
            $command .= ' --no-config -l';
212
        } else {
213
            if ($this->deprecatedAsError) {
214
                $command .= ' -d error_reporting=32767 ';
215
            }
216
217
            $command .= ' -n -l ';
218
        }
219
220
        if (!file_exists($file)) {
221
            throw new BuildException('File not found: ' . $file);
222
        }
223
224
        if (!is_readable($file)) {
225
            throw new BuildException('Permission denied: ' . $file);
226
        }
227
228
        if ($this->cache) {
229
            $lastmtime = $this->cache->get($file);
230
231
            if ($lastmtime >= filemtime($file)) {
232
                $this->log("Not linting '" . $file . "' due to cache", Project::MSG_DEBUG);
233
234
                return;
235
            }
236
        }
237
238
        $messages = [];
239
        $errorCount = 0;
240
241
        exec($command . '"' . $file . '" 2>&1', $messages);
242
243
        for ($i = 0, $messagesCount = count($messages); $i < $messagesCount; $i++) {
244
            $message = $messages[$i];
245
            if (trim($message) == '') {
246
                continue;
247
            }
248
249
            if (
250
                (!preg_match('/^(.*)Deprecated:/', $message) ||
251
                    $this->deprecatedAsError) &&
252
                !preg_match('/^No syntax errors detected/', $message)
253
            ) {
254
                $this->log($message, Project::MSG_ERR);
255
256
                if (!isset($this->badFiles[$file])) {
257
                    $this->badFiles[$file] = [];
258
                }
259
260
                $this->badFiles[$file][] = $message;
261
262
                $this->hasErrors = true;
263
                $errorCount++;
264
            }
265
        }
266
267
        if (!$errorCount) {
268
            $this->log($file . ': No syntax errors detected', $this->logLevel);
269
270
            if ($this->cache) {
271
                $this->cache->put($file, filemtime($file));
272
            }
273
        }
274
    }
275
}
276