Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
EmptyTagSniff
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 3
110
0.00% covered (danger)
0.00%
0 / 1
 register
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 process
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
56
 findContext
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3/**
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 * http://www.gnu.org/copyleft/gpl.html
18 *
19 * @file
20 */
21
22namespace MediaWiki\Sniffs\Commenting;
23
24use PHP_CodeSniffer\Files\File;
25use PHP_CodeSniffer\Sniffs\Sniff;
26use PHP_CodeSniffer\Util\Tokens;
27
28/**
29 * Check for tags with nothing after them
30 *
31 * @author DannyS712
32 */
33class EmptyTagSniff implements Sniff {
34
35    private const DISALLOWED_EMPTY_TAGS = [
36        // There are separate sniffs that cover @param, @return, @throws, and @covers
37        '@access' => '@access',
38        '@author' => '@author',
39        '@dataProvider' => '@dataProvider',
40        '@depends' => '@depends',
41        '@group' => '@group',
42        '@license' => '@license',
43        '@link' => '@link',
44        '@see' => '@see',
45        '@since' => '@since',
46        '@suppress' => '@suppress',
47    ];
48
49    /**
50     * @inheritDoc
51     */
52    public function register(): array {
53        return [ T_DOC_COMMENT_OPEN_TAG ];
54    }
55
56    /**
57     * Processes this test, when one of its tokens is encountered.
58     *
59     * @param File $phpcsFile The file being scanned.
60     * @param int $stackPtr The position of the current token in the stack passed in $tokens.
61     *
62     * @return void
63     */
64    public function process( File $phpcsFile, $stackPtr ) {
65        $tokens = $phpcsFile->getTokens();
66        // Delay this because we typically (when there are no errors) don't need it
67        $where = null;
68
69        foreach ( $tokens[$stackPtr]['comment_tags'] as $tag ) {
70            $content = $tokens[$tag]['content'];
71
72            if ( !isset( self::DISALLOWED_EMPTY_TAGS[$content] ) ||
73                !isset( $tokens[$tag + 2] ) ||
74                // The tag is "not empty" only when it's followed by something on the same line
75                ( $tokens[$tag + 2]['code'] === T_DOC_COMMENT_STRING &&
76                    $tokens[$tag + 2]['line'] === $tokens[$tag]['line'] )
77            ) {
78                continue;
79            }
80
81            if ( !$where ) {
82                $where = $this->findContext( $phpcsFile, $tokens[$stackPtr]['comment_closer'] + 1 );
83            }
84
85            $phpcsFile->addError(
86                'Content missing for %s tag in %s comment',
87                $tag,
88                ucfirst( $where ) . ucfirst( substr( $content, 1 ) ),
89                [ $content, $where ]
90            );
91        }
92    }
93
94    /**
95     * @param File $phpcsFile
96     * @param int $start
97     *
98     * @return string Either "property" or "function"
99     */
100    private function findContext( File $phpcsFile, int $start ): string {
101        $tokens = $phpcsFile->getTokens();
102        $skip = array_merge(
103            Tokens::$emptyTokens,
104            Tokens::$methodPrefixes,
105            [
106                // Skip outdated `var` keywords as well
107                T_VAR,
108                // Skip type hints, e.g. in `public ?Foo\Bar $var`
109                T_STRING,
110                T_NS_SEPARATOR,
111                T_NULLABLE,
112            ]
113        );
114        $next = $phpcsFile->findNext( $skip, $start, null, true );
115        return $tokens[$next]['code'] === T_VARIABLE ? 'property' : $tokens[$next]['content'];
116    }
117
118}