Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 53
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
ClassLevelLicenseSniff
0.00% covered (danger)
0.00%
0 / 53
0.00% covered (danger)
0.00%
0 / 2
240
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 / 52
0.00% covered (danger)
0.00%
0 / 1
210
1<?php
2
3namespace MediaWiki\Sniffs\Commenting;
4
5use PHP_CodeSniffer\Files\File;
6use PHP_CodeSniffer\Sniffs\Sniff;
7use PHP_CodeSniffer\Util\Tokens;
8
9/**
10 * Custom sniff that requires all classes, interfaces, traits, and enums in a codebase to have the same
11 * license doc tag. Note this sniff doesn't do anything by default. You need to enable it in your
12 * .phpcs.xml if you want to use it:
13 * <rule ref="MediaWiki.Commenting.ClassLevelLicense">
14 *     <properties>
15 *         <property name="license" value="GPL-2.0-or-later" />
16 *     </properties>
17 * </rule>
18 *
19 * Doing so makes the LicenseComment sniff obsolete. You might want to disable it:
20 * <exclude name="MediaWiki.Commenting.LicenseComment" />
21 *
22 * @license GPL-2.0-or-later
23 * @author Thiemo Kreuz
24 */
25class ClassLevelLicenseSniff implements Sniff {
26
27    /**
28     * @var string Typically "GPL-2.0-or-later", empty by default
29     */
30    public string $license = '';
31
32    /**
33     * @inheritDoc
34     */
35    public function register(): array {
36        return [ T_CLASS, T_INTERFACE, T_TRAIT, T_ENUM ];
37    }
38
39    /**
40     * @inheritDoc
41     */
42    public function process( File $phpcsFile, $stackPtr ) {
43        // This sniff requires you to set a <property name="license" …> in your .phpcs.xml
44        if ( !$this->license ) {
45            return $phpcsFile->numTokens;
46        }
47
48        $tokens = $phpcsFile->getTokens();
49
50        // All auto-fixes below assume we are on the top level
51        if ( $tokens[$stackPtr]['level'] !== 0 ) {
52            // @codeCoverageIgnoreStart
53            return;
54            // @codeCoverageIgnoreEnd
55        }
56
57        $skip = Tokens::$methodPrefixes;
58        $skip[] = T_WHITESPACE;
59        $closer = $phpcsFile->findPrevious( $skip, $stackPtr - 1, null, true );
60
61        if ( $tokens[$closer]['code'] !== T_DOC_COMMENT_CLOSE_TAG ) {
62            $fix = $phpcsFile->addFixableError(
63                'All code in this codebase should have a @license tag',
64                $stackPtr,
65                'Missing'
66            );
67            if ( $fix ) {
68                $phpcsFile->fixer->addContentBefore(
69                    $stackPtr,
70                    "/**\n * @license $this->license\n */\n"
71                );
72            }
73            return;
74        }
75
76        if ( !isset( $tokens[$closer]['comment_opener'] ) ) {
77            // @codeCoverageIgnoreStart
78            // Live coding
79            return;
80            // @codeCoverageIgnoreEnd
81        }
82
83        $opener = $tokens[$closer]['comment_opener'];
84        foreach ( $tokens[$opener]['comment_tags'] as $ptr ) {
85            $tag = $tokens[$ptr]['content'];
86            if ( strncasecmp( $tag, '@licen', 6 ) !== 0 ) {
87                continue;
88            }
89
90            if ( !isset( $tokens[$ptr + 2] )
91                || $tokens[$ptr + 2]['code'] !== T_DOC_COMMENT_STRING
92            ) {
93                $fix = $phpcsFile->addFixableError(
94                    'All code in this codebase should be tagged with "%s %s", found empty "%s" instead',
95                    $ptr,
96                    'Empty',
97                    [ $tag, $this->license, $tag ]
98                );
99                if ( $fix ) {
100                    $phpcsFile->fixer->addContent( $ptr, " $this->license" );
101                }
102            } elseif ( $tokens[$ptr + 2]['content'] !== $this->license ) {
103                $fix = $phpcsFile->addFixableError(
104                    'All code in this codebase should be tagged with "%s %s", found "%s %s" instead',
105                    $ptr + 2,
106                    'WrongLicense',
107                    [ $tag, $this->license, $tag, $tokens[$ptr + 2]['content'] ]
108                );
109                if ( $fix ) {
110                    $phpcsFile->fixer->replaceToken( $ptr + 2, $this->license );
111                }
112            }
113
114            // This sniff intentionally checks the first @license tag only
115            return;
116        }
117
118        $fix = $phpcsFile->addFixableError(
119            'All code in this codebase should have a @license tag',
120            $opener,
121            'Missing'
122        );
123        if ( $fix ) {
124            $phpcsFile->fixer->addContentBefore( $closer, "* @license $this->license\n " );
125        }
126    }
127
128}