Passed
Push — main ( f5700b...aaf4f3 )
by Michiel
32:12 queued 25:00
created

src/Phing/Task/Ext/Analyzer/Phpcpd/PHPCPDTask.php (2 issues)

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\Analyzer\Phpcpd;
22
23
use Phing\Exception\BuildException;
24
use Phing\Io\File;
25
use Phing\Task;
26
use Phing\Type\Element\FileSetAware;
27
use SebastianBergmann\PHPCPD\Detector\Detector;
28
use SebastianBergmann\PHPCPD\Detector\Strategy\DefaultStrategy;
29
use Composer\Autoload\ClassLoader;
30
31
/**
32
 * Runs PHP Copy & Paste Detector. Checking PHP files for duplicated code.
33
 * Refactored original PhpCpdTask provided by
34
 * Timo Haberkern <[email protected]>
35
 *
36
 * @package phing.tasks.ext.phpcpd
37
 * @author  Benjamin Schultz <[email protected]>
38
 */
39
class PHPCPDTask extends Task
40
{
41
    use FileSetAware;
42
43
    /**
44
     * A php source code filename or directory
45
     *
46
     * @var File
47
     */
48
    protected $file = null;
49
50
    /**
51
     * Minimum number of identical lines.
52
     *
53
     * @var integer
54
     */
55
    protected $minLines = 5;
56
57
    /**
58
     * Minimum number of identical tokens.
59
     *
60
     * @var integer
61
     */
62
    protected $minTokens = 70;
63
64
    /**
65
     * Allow for fuzzy matches.
66
     *
67
     * @var boolean
68
     */
69
    protected $fuzzy = false;
70
71
    /**
72
     * List of valid file extensions for analyzed files.
73
     *
74
     * @var array
75
     */
76
    protected $allowedFileExtensions = ['php'];
77
78
    /**
79
     * List of exclude directory patterns.
80
     *
81
     * @var array
82
     */
83
    protected $ignorePatterns = ['.git', '.svn', 'CVS', '.bzr', '.hg'];
84
85
    /**
86
     * The format for the report
87
     *
88
     * @var string
89
     */
90
    protected $format = 'default';
91
92
    /**
93
     * Formatter elements.
94
     *
95
     * @var PHPCPDFormatterElement[]
96
     */
97
    protected $formatters = [];
98
99
    /**
100
     * @var string
101
     */
102
    private $pharLocation = "";
103
104
    /**
105
     * Set the input source file or directory.
106
     *
107
     * @param File $file The input source file or directory.
108
     */
109
    public function setFile(File $file)
110
    {
111
        $this->file = $file;
112
    }
113
114
    /**
115
     * Sets the minimum number of identical lines (default: 5).
116
     *
117
     * @param integer $minLines Minimum number of identical lines
118
     */
119
    public function setMinLines($minLines)
120
    {
121
        $this->minLines = $minLines;
122
    }
123
124
    /**
125
     * Sets the minimum number of identical tokens (default: 70).
126
     *
127
     * @param integer $minTokens Minimum number of identical tokens
128
     */
129
    public function setMinTokens($minTokens)
130
    {
131
        $this->minTokens = $minTokens;
132
    }
133
134
    /**
135
     * Sets the fuzzy match (default: false).
136
     *
137
     * @param boolean $fuzzy fuzzy match
138
     */
139
    public function setFuzzy($fuzzy)
140
    {
141
        $this->fuzzy = $fuzzy;
142
    }
143
144
    /**
145
     * Sets a list of filename extensions for valid php source code files.
146
     *
147
     * @param string $fileExtensions List of valid file extensions.
148
     */
149
    public function setAllowedFileExtensions($fileExtensions)
150
    {
151
        $this->allowedFileExtensions = [];
152
153
        $token = ' ,;';
154
        $ext = strtok($fileExtensions, $token);
155
156
        while ($ext !== false) {
157
            $this->allowedFileExtensions[] = $ext;
158
            $ext = strtok($token);
159
        }
160
    }
161
162
    /**
163
     * Sets a list of ignore patterns that is used to exclude directories from the source analysis.
164
     *
165
     * @param string $ignorePatterns List of ignore patterns.
166
     */
167
    public function setIgnorePatterns($ignorePatterns)
168
    {
169
        $this->ignorePatterns = [];
170
171
        $token = ' ,;';
172
        $pattern = strtok($ignorePatterns, $token);
173
174
        while ($pattern !== false) {
175
            $this->ignorePatterns[] = $pattern;
176
            $pattern = strtok($token);
177
        }
178
    }
179
180
    /**
181
     * Sets the output format
182
     *
183
     * @param string $format Format of the report
184
     */
185
    public function setFormat($format)
186
    {
187
        $this->format = $format;
188
    }
189
190
    /**
191
     * Create object for nested formatter element.
192
     *
193
     * @return PHPCPDFormatterElement
194
     */
195 3
    public function createFormatter()
196
    {
197 3
        $num = array_push($this->formatters, new PHPCPDFormatterElement($this));
198
199 3
        return $this->formatters[$num - 1];
200
    }
201
202
    /**
203
     * @param string $pharLocation
204
     */
205
    public function setPharLocation($pharLocation)
206
    {
207
        $this->pharLocation = $pharLocation;
208
    }
209
210
    /**
211
     * @throws BuildException if the phpcpd classes can't be loaded
212
     */
213 3
    private function loadDependencies()
214
    {
215 3
        if (!empty($this->pharLocation)) {
216
            // hack to prevent PHPCPD from starting in CLI mode and halting Phing
217
            eval(
0 ignored issues
show
The use of eval() is discouraged.
Loading history...
218
                "namespace SebastianBergmann\PHPCPD\CLI;
219
class Application
220
{
221
    public function run() {}
222
}"
223
            );
224
225
            ob_start();
226
            include $this->pharLocation;
227
            ob_end_clean();
228
229
            if (class_exists('\\SebastianBergmann\\PHPCPD\\Detector\\Strategy\\DefaultStrategy')) {
230
                return;
231
            }
232
        }
233
234
        if (
235 3
            class_exists(ClassLoader::class, false) &&
236 3
            class_exists(DefaultStrategy::class)
237
        ) {
238 3
            return;
239
        }
240
241
        throw new BuildException(
242
            'PHPCPDTask depends on PHPCPD being installed and on include_path.',
243
            $this->getLocation()
244
        );
245
    }
246
247
    /**
248
     * Executes PHPCPD against PhingFile or a FileSet
249
     *
250
     * @throws BuildException
251
     */
252 3
    public function main()
253
    {
254 3
        $this->loadDependencies();
255
256 3
        if (!isset($this->file) && count($this->filesets) == 0) {
257
            throw new BuildException('Missing either a nested fileset or attribute "file" set');
258
        }
259
260 3
        if (count($this->formatters) == 0) {
261
            // turn legacy format attribute into formatter
262
            $fmt = new PHPCPDFormatterElement($this);
263
            $fmt->setType($this->format);
264
            $fmt->setUseFile(false);
265
266
            $this->formatters[] = $fmt;
267
        }
268
269 3
        $this->validateFormatters();
270
271 3
        $filesToParse = [];
272
273 3
        if ($this->file instanceof File) {
0 ignored issues
show
$this->file is always a sub-type of Phing\Io\File.
Loading history...
274
            $filesToParse[] = $this->file->getPath();
275
        } else {
276
            // append any files in filesets
277 3
            foreach ($this->filesets as $fs) {
278 3
                $files = $fs->getDirectoryScanner($this->project)->getIncludedFiles();
279
280 3
                foreach ($files as $filename) {
281 3
                    $f = new File($fs->getDir($this->project), $filename);
282 3
                    $filesToParse[] = $f->getAbsolutePath();
283
                }
284
            }
285
        }
286
287 3
        $this->log('Processing files...');
288
289 3
        $detector = new Detector(new DefaultStrategy());
290 3
        $clones = $detector->copyPasteDetection(
291 3
            $filesToParse,
292 3
            $this->minLines,
293 3
            $this->minTokens,
294 3
            $this->fuzzy
295 3
        );
296
297 3
        $this->log('Finished copy/paste detection');
298
299 3
        foreach ($this->formatters as $fe) {
300 3
            $formatter = $fe->getFormatter();
301 3
            $formatter->processClones(
302 3
                $clones,
303 3
                $this->project,
304 3
                $fe->getUseFile(),
305 3
                $fe->getOutfile()
306 3
            );
307
        }
308
    }
309
310
    /**
311
     * Validates the available formatters
312
     *
313
     * @throws BuildException
314
     */
315 3
    protected function validateFormatters()
316
    {
317 3
        foreach ($this->formatters as $fe) {
318 3
            if ($fe->getType() == '') {
319
                throw new BuildException('Formatter missing required "type" attribute.');
320
            }
321
322 3
            if ($fe->getUsefile() && $fe->getOutfile() === null) {
323
                throw new BuildException('Formatter requires "outfile" attribute when "useFile" is true.');
324
            }
325
        }
326
    }
327
}
328