• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHPCSStandards / PHP_CodeSniffer / 15253296250

26 May 2025 11:55AM UTC coverage: 78.632% (+0.3%) from 78.375%
15253296250

Pull #1105

github

web-flow
Merge d9441d98f into caf806050
Pull Request #1105: Skip tests when 'git' command is not available

19665 of 25009 relevant lines covered (78.63%)

88.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.42
/src/Filters/Filter.php
1
<?php
2
/**
3
 * A base filter class for filtering out files and folders during a run.
4
 *
5
 * @author    Greg Sherwood <gsherwood@squiz.net>
6
 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
7
 * @license   https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
8
 */
9

10
namespace PHP_CodeSniffer\Filters;
11

12
use FilesystemIterator;
13
use PHP_CodeSniffer\Config;
14
use PHP_CodeSniffer\Ruleset;
15
use PHP_CodeSniffer\Util\Common;
16
use RecursiveDirectoryIterator;
17
use RecursiveFilterIterator;
18
use ReturnTypeWillChange;
19

20
class Filter extends RecursiveFilterIterator
21
{
22

23
    /**
24
     * The top-level path we are filtering.
25
     *
26
     * @var string
27
     */
28
    protected $basedir = null;
29

30
    /**
31
     * Whether the basedir is a file or a directory.
32
     *
33
     * TRUE if the basedir is actually a directory.
34
     *
35
     * @var boolean
36
     */
37
    protected $isBasedirDir = false;
38

39
    /**
40
     * The config data for the run.
41
     *
42
     * @var \PHP_CodeSniffer\Config
43
     */
44
    protected $config = null;
45

46
    /**
47
     * The ruleset used for the run.
48
     *
49
     * @var \PHP_CodeSniffer\Ruleset
50
     */
51
    protected $ruleset = null;
52

53
    /**
54
     * A list of ignore patterns that apply to directories only.
55
     *
56
     * @var array
57
     */
58
    protected $ignoreDirPatterns = null;
59

60
    /**
61
     * A list of ignore patterns that apply to files only.
62
     *
63
     * @var array
64
     */
65
    protected $ignoreFilePatterns = null;
66

67
    /**
68
     * A list of file paths we've already accepted.
69
     *
70
     * Used to ensure we aren't following circular symlinks.
71
     *
72
     * @var array
73
     */
74
    protected $acceptedPaths = [];
75

76

77
    /**
78
     * Constructs a filter.
79
     *
80
     * @param \RecursiveIterator       $iterator The iterator we are using to get file paths.
81
     * @param string                   $basedir  The top-level path we are filtering.
82
     * @param \PHP_CodeSniffer\Config  $config   The config data for the run.
83
     * @param \PHP_CodeSniffer\Ruleset $ruleset  The ruleset used for the run.
84
     *
85
     * @return void
86
     */
87
    public function __construct($iterator, $basedir, Config $config, Ruleset $ruleset)
12✔
88
    {
89
        parent::__construct($iterator);
12✔
90
        $this->basedir = $basedir;
12✔
91
        $this->config  = $config;
12✔
92
        $this->ruleset = $ruleset;
12✔
93

94
        if (is_dir($basedir) === true || Common::isPharFile($basedir) === true) {
12✔
95
            $this->isBasedirDir = true;
9✔
96
        }
97

98
    }//end __construct()
4✔
99

100

101
    /**
102
     * Check whether the current element of the iterator is acceptable.
103
     *
104
     * Files are checked for allowed extensions and ignore patterns.
105
     * Directories are checked for ignore patterns only.
106
     *
107
     * @return bool
108
     */
109
    #[ReturnTypeWillChange]
8✔
110
    public function accept()
4✔
111
    {
112
        $filePath = $this->current();
12✔
113
        $realPath = Common::realpath($filePath);
12✔
114

115
        if ($realPath !== false) {
12✔
116
            // It's a real path somewhere, so record it
117
            // to check for circular symlinks.
118
            if (isset($this->acceptedPaths[$realPath]) === true) {
6✔
119
                // We've been here before.
120
                return false;
×
121
            }
122
        }
123

124
        $filePath = $this->current();
12✔
125
        if (is_dir($filePath) === true) {
12✔
126
            if ($this->config->local === true) {
3✔
127
                return false;
1✔
128
            }
129
        } else if ($this->shouldProcessFile($filePath) === false) {
12✔
130
            return false;
3✔
131
        }
132

133
        if ($this->shouldIgnorePath($filePath) === true) {
12✔
134
            return false;
3✔
135
        }
136

137
        $this->acceptedPaths[$realPath] = true;
12✔
138
        return true;
12✔
139

140
    }//end accept()
141

142

143
    /**
144
     * Returns an iterator for the current entry.
145
     *
146
     * Ensures that the ignore patterns are preserved so they don't have
147
     * to be generated each time.
148
     *
149
     * @return \RecursiveIterator
150
     */
151
    #[ReturnTypeWillChange]
152
    public function getChildren()
153
    {
154
        $filterClass = get_called_class();
×
155
        $children    = new $filterClass(
×
156
            new RecursiveDirectoryIterator($this->current(), (RecursiveDirectoryIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS)),
×
157
            $this->basedir,
×
158
            $this->config,
×
159
            $this->ruleset
×
160
        );
161

162
        // Set the ignore patterns so we don't have to generate them again.
163
        $children->ignoreDirPatterns  = $this->ignoreDirPatterns;
×
164
        $children->ignoreFilePatterns = $this->ignoreFilePatterns;
×
165
        $children->acceptedPaths      = $this->acceptedPaths;
×
166
        return $children;
×
167

168
    }//end getChildren()
169

170

171
    /**
172
     * Checks filtering rules to see if a file should be checked.
173
     *
174
     * Checks both file extension filters and path ignore filters.
175
     *
176
     * @param string $path The path to the file being checked.
177
     *
178
     * @return bool
179
     */
180
    protected function shouldProcessFile($path)
12✔
181
    {
182
        // Check that the file's extension is one we are checking.
183
        // We are strict about checking the extension and we don't
184
        // let files through with no extension or that start with a dot.
185
        $fileName  = basename($path);
12✔
186
        $fileParts = explode('.', $fileName);
12✔
187
        if ($fileParts[0] === $fileName || $fileParts[0] === '') {
12✔
188
            if ($this->isBasedirDir === true) {
6✔
189
                // We are recursing a directory, so ignore any
190
                // files with no extension.
191
                return false;
3✔
192
            }
193

194
            // We are processing a single file, so always
195
            // accept files with no extension as they have been
196
            // explicitly requested and there is no config setting
197
            // to ignore them.
198
            return true;
3✔
199
        }
200

201
        // Checking multi-part file extensions, so need to create a
202
        // complete extension list and make sure one is allowed.
203
        $extensions = [];
9✔
204
        array_shift($fileParts);
9✔
205
        while (empty($fileParts) === false) {
9✔
206
            $extensions[implode('.', $fileParts)] = 1;
9✔
207
            array_shift($fileParts);
9✔
208
        }
209

210
        $matches = array_intersect_key($extensions, $this->config->extensions);
9✔
211
        if (empty($matches) === true) {
9✔
212
            return false;
×
213
        }
214

215
        return true;
9✔
216

217
    }//end shouldProcessFile()
218

219

220
    /**
221
     * Checks filtering rules to see if a path should be ignored.
222
     *
223
     * @param string $path The path to the file or directory being checked.
224
     *
225
     * @return bool
226
     */
227
    protected function shouldIgnorePath($path)
12✔
228
    {
229
        if ($this->ignoreFilePatterns === null) {
12✔
230
            $this->ignoreDirPatterns  = [];
12✔
231
            $this->ignoreFilePatterns = [];
12✔
232

233
            $ignorePatterns        = $this->config->ignored;
12✔
234
            $rulesetIgnorePatterns = $this->ruleset->getIgnorePatterns();
12✔
235
            foreach ($rulesetIgnorePatterns as $pattern => $type) {
12✔
236
                // Ignore standard/sniff specific exclude rules.
237
                if (is_array($type) === true) {
6✔
238
                    continue;
6✔
239
                }
240

241
                $ignorePatterns[$pattern] = $type;
6✔
242
            }
243

244
            foreach ($ignorePatterns as $pattern => $type) {
12✔
245
                // If the ignore pattern ends with /* then it is ignoring an entire directory.
246
                if (substr($pattern, -2) === '/*') {
6✔
247
                    // Need to check this pattern for dirs as well as individual file paths.
248
                    $this->ignoreFilePatterns[$pattern] = $type;
6✔
249

250
                    $pattern = substr($pattern, 0, -2).'(?=/|$)';
6✔
251
                    $this->ignoreDirPatterns[$pattern] = $type;
6✔
252
                } else {
253
                    // This is a file-specific pattern, so only need to check this
254
                    // for individual file paths.
255
                    $this->ignoreFilePatterns[$pattern] = $type;
6✔
256
                }
257
            }
258
        }//end if
259

260
        $relativePath = $path;
12✔
261
        if (strpos($path, $this->basedir) === 0) {
12✔
262
            // The +1 cuts off the directory separator as well.
263
            $relativePath = substr($path, (strlen($this->basedir) + 1));
12✔
264
        }
265

266
        if (is_dir($path) === true) {
12✔
267
            $ignorePatterns = $this->ignoreDirPatterns;
3✔
268
        } else {
269
            $ignorePatterns = $this->ignoreFilePatterns;
12✔
270
        }
271

272
        foreach ($ignorePatterns as $pattern => $type) {
12✔
273
            // Maintains backwards compatibility in case the ignore pattern does
274
            // not have a relative/absolute value.
275
            if (is_int($pattern) === true) {
6✔
276
                $pattern = $type;
×
277
                $type    = 'absolute';
×
278
            }
279

280
            $replacements = [
4✔
281
                '\\,' => ',',
6✔
282
                '*'   => '.*',
4✔
283
            ];
4✔
284

285
            // We assume a / directory separator, as do the exclude rules
286
            // most developers write, so we need a special case for any system
287
            // that is different.
288
            if (DIRECTORY_SEPARATOR === '\\') {
6✔
289
                $replacements['/'] = '\\\\';
×
290
            }
291

292
            $pattern = strtr($pattern, $replacements);
6✔
293

294
            if ($type === 'relative') {
6✔
295
                $testPath = $relativePath;
×
296
            } else {
297
                $testPath = $path;
6✔
298
            }
299

300
            $pattern = '`'.$pattern.'`i';
6✔
301
            if (preg_match($pattern, $testPath) === 1) {
6✔
302
                return true;
3✔
303
            }
304
        }//end foreach
305

306
        return false;
12✔
307

308
    }//end shouldIgnorePath()
309

310

311
}//end class
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc