Completed
Push — master ( 20b0ec...0fa80a )
by Siad
15:26
created

PhpLintTask::setToFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 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
/**
21
 * A PHP lint task. Checking syntax of one or more PHP source file.
22
 *
23
 * @author  Knut Urdalen <[email protected]>
24
 * @author  Stefan Priebsch <[email protected]>
25
 * @package phing.tasks.ext
26
 */
27
class PhpLintTask extends Task
28
{
29
    use FileSetAware;
30
    use LogLevelAware;
31
32
    protected $file; // the source file (from xml attribute)
33
34
    protected $errorProperty;
35
    protected $haltOnFailure = false;
36
    protected $hasErrors = false;
37
    protected $badFiles = [];
38
    protected $interpreter = ''; // php interpreter to use for linting
39
40
    protected $cache = null;
41
42
    protected $tofile = null;
43
44
    protected $deprecatedAsError = false;
45
46
    /**
47
     * Initialize the interpreter with the Phing property php.interpreter
48
     */
49 4
    public function init()
50
    {
51 4
        $this->setInterpreter($this->project->getProperty('php.interpreter'));
52 4
    }
53
54
    /**
55
     * Override default php interpreter
56
     *
57
     * @todo  Do some sort of checking if the path is correct but would
58
     *          require traversing the systems executeable path too
59
     * @param string $sPhp
60
     */
61 4
    public function setInterpreter($sPhp)
62
    {
63 4
        if (strpos($sPhp, ' ') !== false) {
64
            $sPhp = escapeshellarg($sPhp);
65
        }
66 4
        $this->interpreter = $sPhp;
67 4
    }
68
69
    /**
70
     * The haltonfailure property
71
     *
72
     * @param boolean $aValue
73
     */
74 2
    public function setHaltOnFailure($aValue)
75
    {
76 2
        $this->haltOnFailure = $aValue;
77 2
    }
78
79
    /**
80
     * File to be performed syntax check on
81
     *
82
     * @param PhingFile $file
83
     */
84 2
    public function setFile(PhingFile $file)
85
    {
86 2
        $this->file = $file;
87 2
    }
88
89
    /**
90
     * Set an property name in which to put any errors.
91
     *
92
     * @param string $propname
93
     */
94
    public function setErrorproperty($propname)
95
    {
96
        $this->errorProperty = $propname;
97
    }
98
99
    /**
100
     * Whether to store last-modified times in cache
101
     *
102
     * @param PhingFile $file
103
     */
104
    public function setCacheFile(PhingFile $file)
105
    {
106
        $this->cache = new DataStore($file);
107
    }
108
109
    /**
110
     * File to save error messages to
111
     *
112
     * @param    PhingFile $tofile
113
     * @internal param PhingFile $file
114
     */
115
    public function setToFile(PhingFile $tofile)
116
    {
117
        $this->tofile = $tofile;
118
    }
119
120
    /**
121
     * Sets whether to treat deprecated warnings (introduced in PHP 5.3) as errors
122
     *
123
     * @param boolean $deprecatedAsError
124
     */
125 1
    public function setDeprecatedAsError($deprecatedAsError)
126
    {
127 1
        $this->deprecatedAsError = $deprecatedAsError;
128 1
    }
129
130
    /**
131
     * Execute lint check against PhingFile or a FileSet
132
     */
133 4
    public function main()
134
    {
135 4
        if (!isset($this->file) and count($this->filesets) == 0) {
136
            throw new BuildException("Missing either a nested fileset or attribute 'file' set");
137
        }
138
139 4
        if ($this->file instanceof PhingFile) {
140 2
            $this->lint($this->file->getPath());
141
        } else { // process filesets
142 2
            $project = $this->getProject();
143 2
            foreach ($this->filesets as $fs) {
144 2
                $ds = $fs->getDirectoryScanner($project);
145 2
                $files = $ds->getIncludedFiles();
146 2
                $dir = $fs->getDir($this->project)->getPath();
147 2
                foreach ($files as $file) {
148 2
                    $this->lint($dir . DIRECTORY_SEPARATOR . $file);
149
                }
150
            }
151
        }
152
153
        // write list of 'bad files' to file (if specified)
154 4
        if ($this->tofile) {
155
            $writer = new FileWriter($this->tofile);
156
157
            foreach ($this->badFiles as $file => $messages) {
158
                foreach ($messages as $msg) {
159
                    $writer->write($file . "=" . $msg . PHP_EOL);
160
                }
161
            }
162
163
            $writer->close();
164
        }
165
166 4
        $message = '';
167 4
        foreach ($this->badFiles as $file => $messages) {
168 2
            foreach ($messages as $msg) {
169 2
                $message .= $file . "=" . $msg . PHP_EOL;
170
            }
171
        }
172
173
        // save list of 'bad files' with errors to property errorproperty (if specified)
174 4
        if ($this->errorProperty) {
175
            $this->project->setProperty($this->errorProperty, $message);
176
        }
177
178 4
        if (!empty($this->cache)) {
179
            $this->cache->commit();
180
        }
181
182 4
        if ($this->haltOnFailure && $this->hasErrors) {
183 1
            throw new BuildException('Syntax error(s) in PHP files: ' . $message);
184
        }
185 3
    }
186
187
    /**
188
     * Performs the actual syntax check
189
     *
190
     * @param  string $file
191
     * @throws BuildException
192
     */
193 4
    protected function lint($file)
194
    {
195 4
        $command = $this->interpreter == ''
196
            ? 'php'
197 4
            : $this->interpreter;
198
199 4
        if (strpos($command, 'hhvm') !== false) {
200
            $command .= ' --no-config -l';
201
        } else {
202 4
            if ($this->deprecatedAsError) {
203 1
                $command .= ' -d error_reporting=32767 ';
204
            }
205
206 4
            $command .= ' -n -l ';
207
        }
208
209 4
        if (!file_exists($file)) {
210
            throw new BuildException('File not found: ' . $file);
211
        }
212
213 4
        if (!is_readable($file)) {
214
            throw new BuildException('Permission denied: ' . $file);
215
        }
216
217 4
        if ($this->cache) {
218
            $lastmtime = $this->cache->get($file);
219
220
            if ($lastmtime >= filemtime($file)) {
221
                $this->log("Not linting '" . $file . "' due to cache", Project::MSG_DEBUG);
222
223
                return;
224
            }
225
        }
226
227 4
        $messages = [];
228 4
        $errorCount = 0;
229
230 4
        exec($command . '"' . $file . '" 2>&1', $messages);
231
232 4
        for ($i = 0, $messagesCount = count($messages); $i < $messagesCount; $i++) {
233 4
            $message = $messages[$i];
234 4
            if (trim($message) == '') {
235 2
                continue;
236
            }
237
238
            if (
239 4
                (!preg_match('/^(.*)Deprecated:/', $message) ||
240
                    $this->deprecatedAsError) &&
241 4
                !preg_match('/^No syntax errors detected/', $message)
242
            ) {
243 2
                $this->log($message, Project::MSG_ERR);
244
245 2
                if (!isset($this->badFiles[$file])) {
246 2
                    $this->badFiles[$file] = [];
247
                }
248
249 2
                $this->badFiles[$file][] = $message;
250
251 2
                $this->hasErrors = true;
252 2
                $errorCount++;
253
            }
254
        }
255
256 4
        if (!$errorCount) {
257 2
            $this->log($file . ': No syntax errors detected', $this->logLevel);
258
259 2
            if ($this->cache) {
260
                $this->cache->put($file, filemtime($file));
261
            }
262
        }
263 4
    }
264
}
265