• 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

54.29
/src/Util/Common.php
1
<?php
2
/**
3
 * Basic util functions.
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\Util;
11

12
use InvalidArgumentException;
13
use Phar;
14

15
class Common
16
{
17

18
    /**
19
     * An array of variable types for param/var we will check.
20
     *
21
     * @var array<string, string>
22
     */
23
    public const ALLOWED_TYPES = [
24
        'array'    => 'array',
25
        'boolean'  => 'boolean',
26
        'float'    => 'float',
27
        'integer'  => 'integer',
28
        'mixed'    => 'mixed',
29
        'object'   => 'object',
30
        'string'   => 'string',
31
        'resource' => 'resource',
32
        'callable' => 'callable',
33
    ];
34

35
    /**
36
     * An array of variable types for param/var we will check.
37
     *
38
     * @var array<string, string>
39
     *
40
     * @deprecated 4.0.0 Use the Common::ALLOWED_TYPES constant instead.
41
     */
42
    public static $allowedTypes = self::ALLOWED_TYPES;
43

44

45
    /**
46
     * Return TRUE if the path is a PHAR file.
47
     *
48
     * @param string $path The path to use.
49
     *
50
     * @return bool
51
     */
52
    public static function isPharFile($path)
×
53
    {
54
        if (strpos($path, 'phar://') === 0) {
×
55
            return true;
×
56
        }
57

58
        return false;
×
59

60
    }//end isPharFile()
61

62

63
    /**
64
     * Checks if a file is readable.
65
     *
66
     * Addresses PHP bug related to reading files from network drives on Windows.
67
     * e.g. when using WSL2.
68
     *
69
     * @param string $path The path to the file.
70
     *
71
     * @return boolean
72
     */
73
    public static function isReadable($path)
×
74
    {
75
        if (@is_readable($path) === true) {
×
76
            return true;
×
77
        }
78

79
        if (@file_exists($path) === true && @is_file($path) === true) {
×
80
            $f = @fopen($path, 'rb');
×
81
            if (fclose($f) === true) {
×
82
                return true;
×
83
            }
84
        }
85

86
        return false;
×
87

88
    }//end isReadable()
89

90

91
    /**
92
     * CodeSniffer alternative for realpath.
93
     *
94
     * Allows for PHAR support.
95
     *
96
     * @param string $path The path to use.
97
     *
98
     * @return string|false
99
     */
100
    public static function realpath($path)
×
101
    {
102
        // Support the path replacement of ~ with the user's home directory.
103
        if (substr($path, 0, 2) === '~/') {
×
104
            $homeDir = getenv('HOME');
×
105
            if ($homeDir !== false) {
×
106
                $path = $homeDir.substr($path, 1);
×
107
            }
108
        }
109

110
        // Check for process substitution.
111
        if (strpos($path, '/dev/fd') === 0) {
×
112
            return str_replace('/dev/fd', 'php://fd', $path);
×
113
        }
114

115
        // No extra work needed if this is not a phar file.
116
        if (self::isPharFile($path) === false) {
×
117
            return realpath($path);
×
118
        }
119

120
        // Before trying to break down the file path,
121
        // check if it exists first because it will mostly not
122
        // change after running the below code.
123
        if (file_exists($path) === true) {
×
124
            return $path;
×
125
        }
126

127
        $phar  = Phar::running(false);
×
128
        $extra = str_replace('phar://'.$phar, '', $path);
×
129
        $path  = realpath($phar);
×
130
        if ($path === false) {
×
131
            return false;
×
132
        }
133

134
        $path = 'phar://'.$path.$extra;
×
135
        if (file_exists($path) === true) {
×
136
            return $path;
×
137
        }
138

139
        return false;
×
140

141
    }//end realpath()
142

143

144
    /**
145
     * Removes a base path from the front of a file path.
146
     *
147
     * @param string $path     The path of the file.
148
     * @param string $basepath The base path to remove. This should not end
149
     *                         with a directory separator.
150
     *
151
     * @return string
152
     */
153
    public static function stripBasepath($path, $basepath)
×
154
    {
155
        if (empty($basepath) === true) {
×
156
            return $path;
×
157
        }
158

159
        $basepathLen = strlen($basepath);
×
160
        if (substr($path, 0, $basepathLen) === $basepath) {
×
161
            $path = substr($path, $basepathLen);
×
162
        }
163

164
        $path = ltrim($path, DIRECTORY_SEPARATOR);
×
165
        if ($path === '') {
×
166
            $path = '.';
×
167
        }
168

169
        return $path;
×
170

171
    }//end stripBasepath()
172

173

174
    /**
175
     * Detects the EOL character being used in a string.
176
     *
177
     * @param string $contents The contents to check.
178
     *
179
     * @return string
180
     */
181
    public static function detectLineEndings($contents)
×
182
    {
183
        if (preg_match("/\r\n?|\n/", $contents, $matches) !== 1) {
×
184
            // Assume there are no newlines.
185
            $eolChar = "\n";
×
186
        } else {
187
            $eolChar = $matches[0];
×
188
        }
189

190
        return $eolChar;
×
191

192
    }//end detectLineEndings()
193

194

195
    /**
196
     * Check if STDIN is a TTY.
197
     *
198
     * @return boolean
199
     */
200
    public static function isStdinATTY()
×
201
    {
202
        // The check is slow (especially calling `tty`) so we static
203
        // cache the result.
204
        static $isTTY = null;
×
205

206
        if ($isTTY !== null) {
×
207
            return $isTTY;
×
208
        }
209

210
        if (defined('STDIN') === false) {
×
211
            return false;
×
212
        }
213

214
        // If PHP has the POSIX extensions we will use them.
215
        if (function_exists('posix_isatty') === true) {
×
216
            $isTTY = (posix_isatty(STDIN) === true);
×
217
            return $isTTY;
×
218
        }
219

220
        // Next try is detecting whether we have `tty` installed and use that.
221
        if (defined('PHP_WINDOWS_VERSION_PLATFORM') === true) {
×
222
            $devnull = 'NUL';
×
223
            $which   = 'where';
×
224
        } else {
225
            $devnull = '/dev/null';
×
226
            $which   = 'which';
×
227
        }
228

229
        $tty = trim(shell_exec("$which tty 2> $devnull"));
×
230
        if (empty($tty) === false) {
×
231
            exec("tty -s 2> $devnull", $output, $returnValue);
×
232
            $isTTY = ($returnValue === 0);
×
233
            return $isTTY;
×
234
        }
235

236
        // Finally we will use fstat.  The solution borrowed from
237
        // https://stackoverflow.com/questions/11327367/detect-if-a-php-script-is-being-run-interactively-or-not
238
        // This doesn't work on Mingw/Cygwin/... using Mintty but they
239
        // have `tty` installed.
240
        $type = [
241
            'S_IFMT'  => 0170000,
×
242
            'S_IFIFO' => 0010000,
243
        ];
244

245
        $stat  = fstat(STDIN);
×
246
        $mode  = ($stat['mode'] & $type['S_IFMT']);
×
247
        $isTTY = ($mode !== $type['S_IFIFO']);
×
248

249
        return $isTTY;
×
250

251
    }//end isStdinATTY()
252

253

254
    /**
255
     * Escape a path to a system command.
256
     *
257
     * @param string $cmd The path to the system command.
258
     *
259
     * @return string
260
     */
261
    public static function escapeshellcmd($cmd)
25✔
262
    {
263
        $cmd = escapeshellcmd($cmd);
25✔
264

265
        if (PHP_OS_FAMILY === 'Windows') {
25✔
266
            // Spaces are not escaped by escapeshellcmd on Windows, but need to be
267
            // for the command to be able to execute.
268
            $cmd = preg_replace('`(?<!^) `', '^ ', $cmd);
10✔
269
        }
270

271
        return $cmd;
25✔
272

273
    }//end escapeshellcmd()
274

275

276
    /**
277
     * Prepares token content for output to screen.
278
     *
279
     * Replaces invisible characters so they are visible. On non-Windows
280
     * operating systems it will also colour the invisible characters.
281
     *
282
     * @param string   $content The content to prepare.
283
     * @param string[] $exclude A list of characters to leave invisible.
284
     *                          Can contain \r, \n, \t and a space.
285
     *
286
     * @return string
287
     */
288
    public static function prepareForOutput($content, $exclude=[])
25✔
289
    {
290
        if (PHP_OS_FAMILY === 'Windows') {
25✔
291
            if (in_array("\r", $exclude, true) === false) {
10✔
292
                $content = str_replace("\r", '\r', $content);
8✔
293
            }
294

295
            if (in_array("\n", $exclude, true) === false) {
10✔
296
                $content = str_replace("\n", '\n', $content);
8✔
297
            }
298

299
            if (in_array("\t", $exclude, true) === false) {
10✔
300
                $content = str_replace("\t", '\t', $content);
10✔
301
            }
302
        } else {
303
            if (in_array("\r", $exclude, true) === false) {
15✔
304
                $content = str_replace("\r", "\033[30;1m\\r\033[0m", $content);
12✔
305
            }
306

307
            if (in_array("\n", $exclude, true) === false) {
15✔
308
                $content = str_replace("\n", "\033[30;1m\\n\033[0m", $content);
12✔
309
            }
310

311
            if (in_array("\t", $exclude, true) === false) {
15✔
312
                $content = str_replace("\t", "\033[30;1m\\t\033[0m", $content);
15✔
313
            }
314

315
            if (in_array(' ', $exclude, true) === false) {
15✔
316
                $content = str_replace(' ', "\033[30;1m·\033[0m", $content);
15✔
317
            }
318
        }//end if
319

320
        return $content;
25✔
321

322
    }//end prepareForOutput()
323

324

325
    /**
326
     * Strip colors from a text for output to screen.
327
     *
328
     * @param string $text The text to process.
329
     *
330
     * @return string
331
     */
332
    public static function stripColors($text)
27✔
333
    {
334
        return preg_replace('`\033\[[0-9;]+m`', '', $text);
27✔
335

336
    }//end stripColors()
337

338

339
    /**
340
     * Returns true if the specified string is in the camel caps format.
341
     *
342
     * @param string  $string      The string the verify.
343
     * @param boolean $classFormat If true, check to see if the string is in the
344
     *                             class format. Class format strings must start
345
     *                             with a capital letter and contain no
346
     *                             underscores.
347
     * @param boolean $public      If true, the first character in the string
348
     *                             must be an a-z character. If false, the
349
     *                             character must be an underscore. This
350
     *                             argument is only applicable if $classFormat
351
     *                             is false.
352
     * @param boolean $strict      If true, the string must not have two capital
353
     *                             letters next to each other. If false, a
354
     *                             relaxed camel caps policy is used to allow
355
     *                             for acronyms.
356
     *
357
     * @return boolean
358
     */
359
    public static function isCamelCaps(
21✔
360
        $string,
361
        $classFormat=false,
362
        $public=true,
363
        $strict=true
364
    ) {
365
        // Check the first character first.
366
        if ($classFormat === false) {
21✔
367
            $legalFirstChar = '';
12✔
368
            if ($public === false) {
12✔
369
                $legalFirstChar = '[_]';
6✔
370
            }
371

372
            if ($strict === false) {
12✔
373
                // Can either start with a lowercase letter, or multiple uppercase
374
                // in a row, representing an acronym.
375
                $legalFirstChar .= '([A-Z]{2,}|[a-z])';
9✔
376
            } else {
377
                $legalFirstChar .= '[a-z]';
12✔
378
            }
379
        } else {
380
            $legalFirstChar = '[A-Z]';
9✔
381
        }
382

383
        if (preg_match("/^$legalFirstChar/", $string) === 0) {
21✔
384
            return false;
12✔
385
        }
386

387
        // Check that the name only contains legal characters.
388
        $legalChars = 'a-zA-Z0-9';
18✔
389
        if (preg_match("|[^$legalChars]|", substr($string, 1)) > 0) {
18✔
390
            return false;
9✔
391
        }
392

393
        if ($strict === true) {
15✔
394
            // Check that there are not two capital letters next to each other.
395
            $length          = strlen($string);
15✔
396
            $lastCharWasCaps = $classFormat;
15✔
397

398
            for ($i = 1; $i < $length; $i++) {
15✔
399
                $ascii = ord($string[$i]);
15✔
400
                if ($ascii >= 48 && $ascii <= 57) {
15✔
401
                    // The character is a number, so it can't be a capital.
402
                    $isCaps = false;
3✔
403
                } else {
404
                    if (strtoupper($string[$i]) === $string[$i]) {
15✔
405
                        $isCaps = true;
15✔
406
                    } else {
407
                        $isCaps = false;
15✔
408
                    }
409
                }
410

411
                if ($isCaps === true && $lastCharWasCaps === true) {
15✔
412
                    return false;
6✔
413
                }
414

415
                $lastCharWasCaps = $isCaps;
15✔
416
            }
417
        }//end if
418

419
        return true;
9✔
420

421
    }//end isCamelCaps()
422

423

424
    /**
425
     * Returns true if the specified string is in the underscore caps format.
426
     *
427
     * @param string $string The string to verify.
428
     *
429
     * @return boolean
430
     */
431
    public static function isUnderscoreName($string)
×
432
    {
433
        // If there is whitespace in the name, it can't be valid.
434
        if (strpos($string, ' ') !== false) {
×
435
            return false;
×
436
        }
437

438
        $validName = true;
×
439
        $nameBits  = explode('_', $string);
×
440

441
        if (preg_match('|^[A-Z]|', $string) === 0) {
×
442
            // Name does not begin with a capital letter.
443
            $validName = false;
×
444
        } else {
445
            foreach ($nameBits as $bit) {
×
446
                if ($bit === '') {
×
447
                    continue;
×
448
                }
449

450
                if ($bit[0] !== strtoupper($bit[0])) {
×
451
                    $validName = false;
×
452
                    break;
×
453
                }
454
            }
455
        }
456

457
        return $validName;
×
458

459
    }//end isUnderscoreName()
460

461

462
    /**
463
     * Returns a valid variable type for param/var tags.
464
     *
465
     * If type is not one of the standard types, it must be a custom type.
466
     * Returns the correct type name suggestion if type name is invalid.
467
     *
468
     * @param string $varType The variable type to process.
469
     *
470
     * @return string
471
     */
472
    public static function suggestType($varType)
135✔
473
    {
474
        if ($varType === '') {
135✔
475
            return '';
6✔
476
        }
477

478
        if (isset(self::ALLOWED_TYPES[$varType]) === true) {
132✔
479
            return $varType;
36✔
480
        } else {
481
            $lowerVarType = strtolower($varType);
105✔
482
            switch ($lowerVarType) {
35✔
483
            case 'bool':
105✔
484
            case 'boolean':
99✔
485
                return 'boolean';
15✔
486
            case 'double':
93✔
487
            case 'real':
87✔
488
            case 'float':
84✔
489
                return 'float';
21✔
490
            case 'int':
78✔
491
            case 'integer':
72✔
492
                return 'integer';
15✔
493
            case 'array()':
66✔
494
            case 'array':
63✔
495
                return 'array';
9✔
496
            }//end switch
497

498
            if (strpos($lowerVarType, 'array(') !== false) {
57✔
499
                // Valid array declaration:
500
                // array, array(type), array(type1 => type2).
501
                $matches = [];
18✔
502
                $pattern = '/^array\(\s*([^\s^=^>]*)(\s*=>\s*(.*))?\s*\)/i';
18✔
503
                if (preg_match($pattern, $varType, $matches) !== 0) {
18✔
504
                    $type1 = '';
15✔
505
                    if (isset($matches[1]) === true) {
15✔
506
                        $type1 = $matches[1];
15✔
507
                    }
508

509
                    $type2 = '';
15✔
510
                    if (isset($matches[3]) === true) {
15✔
511
                        $type2 = $matches[3];
12✔
512
                    }
513

514
                    $type1 = self::suggestType($type1);
15✔
515
                    $type2 = self::suggestType($type2);
15✔
516
                    if ($type2 !== '') {
15✔
517
                        $type2 = ' => '.$type2;
12✔
518
                    }
519

520
                    return "array($type1$type2)";
15✔
521
                } else {
522
                    return 'array';
3✔
523
                }//end if
524
            } else if (isset(self::ALLOWED_TYPES[$lowerVarType]) === true) {
39✔
525
                // A valid type, but not lower cased.
526
                return $lowerVarType;
30✔
527
            } else {
528
                // Must be a custom type name.
529
                return $varType;
9✔
530
            }//end if
531
        }//end if
532

533
    }//end suggestType()
534

535

536
    /**
537
     * Given a sniff class name, returns the code for the sniff.
538
     *
539
     * @param string $sniffClass The fully qualified sniff class name.
540
     *
541
     * @return string
542
     *
543
     * @throws \InvalidArgumentException When $sniffClass is not a non-empty string.
544
     * @throws \InvalidArgumentException When $sniffClass is not a valid FQN for a sniff(test) class.
545
     */
546
    public static function getSniffCode($sniffClass)
54✔
547
    {
548
        if (is_string($sniffClass) === false || $sniffClass === '') {
54✔
549
            throw new InvalidArgumentException('The $sniffClass parameter must be a non-empty string');
6✔
550
        }
551

552
        $parts      = explode('\\', $sniffClass);
48✔
553
        $partsCount = count($parts);
48✔
554
        if ($partsCount < 4
48✔
555
            || ($parts[($partsCount - 3)] !== 'Sniffs'
33✔
556
            && $parts[($partsCount - 3)] !== 'Tests')
33✔
557
            || $parts[($partsCount - 2)] === 'Sniffs'
48✔
558
        ) {
559
            throw new InvalidArgumentException(
21✔
560
                'The $sniffClass parameter was not passed a fully qualified sniff(test) class name. Received: '.$sniffClass
21✔
561
            );
14✔
562
        }
563

564
        $sniff = $parts[($partsCount - 1)];
27✔
565

566
        if ($sniff !== 'Sniff' && substr($sniff, -5) === 'Sniff') {
27✔
567
            // Sniff class name.
568
            $sniff = substr($sniff, 0, -5);
9✔
569
        } else if ($sniff !== 'UnitTest' && substr($sniff, -8) === 'UnitTest') {
18✔
570
            // Unit test class name.
571
            $sniff = substr($sniff, 0, -8);
9✔
572
        } else {
573
            throw new InvalidArgumentException(
9✔
574
                'The $sniffClass parameter was not passed a fully qualified sniff(test) class name. Received: '.$sniffClass
9✔
575
            );
6✔
576
        }
577

578
        $standard = $parts[($partsCount - 4)];
18✔
579
        $category = $parts[($partsCount - 2)];
18✔
580
        return $standard.'.'.$category.'.'.$sniff;
18✔
581

582
    }//end getSniffCode()
583

584

585
    /**
586
     * Removes project-specific information from a sniff class name.
587
     *
588
     * @param string $sniffClass The fully qualified sniff class name.
589
     *
590
     * @return string
591
     */
592
    public static function cleanSniffClass($sniffClass)
×
593
    {
594
        $newName = strtolower($sniffClass);
×
595

596
        $sniffPos = strrpos($newName, '\sniffs\\');
×
597
        if ($sniffPos === false) {
×
598
            // Nothing we can do as it isn't in a known format.
599
            return $newName;
×
600
        }
601

602
        $end   = (strlen($newName) - $sniffPos + 1);
×
603
        $start = strrpos($newName, '\\', ($end * -1));
×
604

605
        if ($start === false) {
×
606
            // Nothing needs to be cleaned.
607
            return $newName;
×
608
        }
609

610
        $newName = substr($newName, ($start + 1));
×
611
        return $newName;
×
612

613
    }//end cleanSniffClass()
614

615

616
}//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