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

PHPCSStandards / PHPCSExtra / 15639493787

13 Jun 2025 04:35PM UTC coverage: 97.39% (-2.5%) from 99.85%
15639493787

Pull #367

github

web-flow
Merge 844746ea8 into 10343591c
Pull Request #367: Update for compatibility with PHPCS 4.0

55 of 140 new or added lines in 8 files covered. (39.29%)

1 existing line in 1 file now uncovered.

3358 of 3448 relevant lines covered (97.39%)

3.58 hits per line

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

96.3
/Universal/Sniffs/CodeAnalysis/NoEchoSprintfSniff.php
1
<?php
2
/**
3
 * PHPCSExtra, a collection of sniffs and standards for use with PHP_CodeSniffer.
4
 *
5
 * @package   PHPCSExtra
6
 * @copyright 2023 PHPCSExtra Contributors
7
 * @license   https://opensource.org/licenses/LGPL-3.0 LGPL3
8
 * @link      https://github.com/PHPCSStandards/PHPCSExtra
9
 */
10

11
namespace PHPCSExtra\Universal\Sniffs\CodeAnalysis;
12

13
use PHP_CodeSniffer\Files\File;
14
use PHP_CodeSniffer\Sniffs\Sniff;
15
use PHP_CodeSniffer\Util\Tokens;
16

17
/**
18
 * Detects use of `echo [v]sprintf();.
19
 *
20
 * @link https://www.php.net/manual/en/function.printf.php
21
 * @link https://www.php.net/manual/en/function.sprintf.php
22
 * @link https://www.php.net/manual/en/function.vprintf.php
23
 * @link https://www.php.net/manual/en/function.vsprintf.php
24
 *
25
 * @since 1.1.0
26
 */
27
final class NoEchoSprintfSniff implements Sniff
28
{
29

30
    /**
31
     * Functions to look for with their replacements.
32
     *
33
     * @since 1.1.0
34
     *
35
     * @var array<string, string>
36
     */
37
    private $targetFunctions = [
38
        'sprintf'  => 'printf',
39
        'vsprintf' => 'vprintf',
40
    ];
41

42
    /**
43
     * Returns an array of tokens this test wants to listen for.
44
     *
45
     * @since 1.1.0
46
     *
47
     * @return array<int|string>
48
     */
49
    public function register()
4✔
50
    {
51
        return [\T_ECHO];
4✔
52
    }
53

54
    /**
55
     * Processes this test, when one of its tokens is encountered.
56
     *
57
     * @since 1.1.0
58
     *
59
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
60
     * @param int                         $stackPtr  The position of the current token
61
     *                                               in the stack passed in $tokens.
62
     *
63
     * @return void
64
     */
65
    public function process(File $phpcsFile, $stackPtr)
4✔
66
    {
67
        $tokens = $phpcsFile->getTokens();
4✔
68

69
        $skip   = Tokens::$emptyTokens;
4✔
70
        $skip[] = \T_NS_SEPARATOR;
4✔
71

72
        $next = $phpcsFile->findNext($skip, ($stackPtr + 1), null, true);
4✔
73
        if ($next === false
2✔
74
            || ($tokens[$next]['code'] !== \T_STRING
4✔
75
            && $tokens[$next]['code'] !== \T_NAME_FULLY_QUALIFIED)
4✔
76
        ) {
2✔
77
            // Definitely not our target.
78
            return;
4✔
79
        }
80

81
        $detectedFunction = \strtolower($tokens[$next]['content']);
4✔
82
        if ($tokens[$next]['code'] === \T_NAME_FULLY_QUALIFIED) {
4✔
NEW
83
            $detectedFunction = \ltrim($detectedFunction, '\\');
×
84
        }
85

86
        if (isset($this->targetFunctions[$detectedFunction]) === false) {
4✔
87
            // Not one of our target functions.
88
            return;
4✔
89
        }
90

91
        $openParens = $phpcsFile->findNext(Tokens::$emptyTokens, ($next + 1), null, true);
4✔
92
        if ($openParens === false
2✔
93
            || $tokens[$openParens]['code'] !== \T_OPEN_PARENTHESIS
4✔
94
            || isset($tokens[$openParens]['parenthesis_closer']) === false
4✔
95
        ) {
2✔
96
            // Live coding/parse error.
97
            return;
4✔
98
        }
99

100
        $closeParens       = $tokens[$openParens]['parenthesis_closer'];
4✔
101
        $afterFunctionCall = $phpcsFile->findNext(Tokens::$emptyTokens, ($closeParens + 1), null, true);
4✔
102
        if ($afterFunctionCall === false
2✔
103
            || ($tokens[$afterFunctionCall]['code'] !== \T_SEMICOLON
4✔
104
            && $tokens[$afterFunctionCall]['code'] !== \T_CLOSE_TAG)
4✔
105
        ) {
2✔
106
            // Live coding/parse error or compound echo statement.
107
            return;
4✔
108
        }
109

110
        $fix = $phpcsFile->addFixableError(
4✔
111
            'Unnecessary "echo %s(...)" found. Use "%s(...)" instead.',
4✔
112
            $next,
4✔
113
            'Found',
4✔
114
            [
2✔
115
                $tokens[$next]['content'],
4✔
116
                $this->targetFunctions[$detectedFunction],
4✔
117
            ]
2✔
118
        );
4✔
119

120
        if ($fix === true) {
4✔
121
            $phpcsFile->fixer->beginChangeset();
4✔
122

123
            // Remove echo and whitespace.
124
            $phpcsFile->fixer->replaceToken($stackPtr, '');
4✔
125

126
            for ($i = ($stackPtr + 1); $i < $next; $i++) {
4✔
127
                if ($tokens[$i]['code'] !== \T_WHITESPACE) {
4✔
128
                    break;
4✔
129
                }
130

131
                $phpcsFile->fixer->replaceToken($i, '');
4✔
132
            }
2✔
133

134
            $replacement = $this->targetFunctions[$detectedFunction];
4✔
135
            if ($tokens[$next]['code'] === \T_NAME_FULLY_QUALIFIED) {
4✔
NEW
136
                $replacement = '\\' . $replacement;
×
137
            }
138

139
            $phpcsFile->fixer->replaceToken($next, $replacement);
4✔
140

141
            $phpcsFile->fixer->endChangeset();
4✔
142
        }
2✔
143
    }
2✔
144
}
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