Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.02% covered (success)
93.02%
40 / 43
0.00% covered (danger)
0.00%
0 / 1
CRAP
0.00% covered (danger)
0.00%
0 / 1
MWDoxygenFilter
95.24% covered (success)
95.24%
40 / 42
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 1
 filter
95.24% covered (success)
95.24%
40 / 42
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2/**
3 * Copyright (C) 2012 Tamas Imrei <tamas.imrei@gmail.com> https://virtualtee.blogspot.com/
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @ingroup Maintenance
22 */
23
24namespace MediaWiki\Maintenance;
25
26/**
27 * Doxygen filter to show correct member variable types in documentation.
28 *
29 * Based on
30 * <https://virtualtee.blogspot.co.uk/2012/03/tip-for-using-doxygen-for-php-code.html>
31 *
32 * It has been adapted for MediaWiki to resolve various bugs we experienced
33 * from using Doxygen with our coding conventions:
34 *
35 * - We want to allow documenting class members on a single line by documenting
36 *   them as `/** @var SomeType Description here.`, and in long-form as
37 *   `/**\n * Description here.\n * @var SomeType`.
38 *
39 * - PHP does not support native type-hinting of class members. Instead, we document
40 *   that using `@var` in the doc blocks above it. However, Doxygen only supports
41 *   parsing this from executable code. We achieve this by having the below filter
42 *   take the typehint from the doc block and insert it into the source code in
43 *   front of `$myvar`, like `protected SomeType $myvar`. This result is technically
44 *   invalid PHP code, but Doxygen understands it this way.
45 *
46 * @internal For use by maintenance/mwdoc-filter.php
47 * @ingroup Maintenance
48 */
49class MWDoxygenFilter {
50    /**
51     * @param string $source Original source code
52     * @return string Filtered source code
53     */
54    public static function filter( $source ) {
55        $tokens = token_get_all( $source );
56        $buffer = null;
57        $output = '';
58        foreach ( $tokens as $token ) {
59            if ( is_string( $token ) ) {
60                if ( $buffer !== null && $token === ';' ) {
61                    // If we still have a buffer and the statement has ended,
62                    // flush it and move on.
63                    $output .= $buffer['raw'];
64                    $buffer = null;
65                }
66                $output .= $token;
67                continue;
68            }
69            [ $id, $content ] = $token;
70            switch ( $id ) {
71                case T_DOC_COMMENT:
72                    // Escape slashes so that references to namespaces are not
73                    // wrongly interpreted as a Doxygen "\command".
74                    $content = addcslashes( $content, '\\' );
75                    // Look for instances of "@var SomeType".
76                    if ( preg_match( '#@var\s+\S+#', $content ) ) {
77                        $buffer = [ 'raw' => $content, 'desc' => null, 'type' => null, 'name' => null ];
78                        $buffer['desc'] = preg_replace_callback(
79                            // Strip "@var SomeType" part, but remember the type and optional name
80                            '#@var\s+(\S+)(\s+)?(\S+)?#',
81                            static function ( $matches ) use ( &$buffer ) {
82                                $buffer['type'] = $matches[1];
83                                $buffer['name'] = $matches[3] ?? null;
84                                return ( $matches[2] ?? '' ) . ( $matches[3] ?? '' );
85                            },
86                            $content
87                        );
88                    } else {
89                        $output .= $content;
90                    }
91                    break;
92
93                case T_VARIABLE:
94                    // Doxygen requires class members to be documented in one of two ways:
95                    //
96                    // 1. Fully qualified:
97                    //    /** @var SomeType $name Description here. */
98                    //
99                    //    These result in the creation of a new virtual node called $name
100                    //    with the specified type and description. The real code doesn't
101                    //    even need to exist in this case.
102                    //
103                    // 2. Contextual:
104                    //    /** Description here. */
105                    //    private SomeType? $name;
106                    //
107                    // In MediaWiki, we are mostly like #1 but without the name repeated:
108                    //    /** @var SomeType Description here. */
109                    //    private $name;
110                    //
111                    // These emit a warning in Doxygen because they are missing a variable name.
112                    // Convert these to the "Contextual" kind by stripping ""@var", injecting
113                    // type into the code, and leaving the description in-place.
114                    if ( $buffer !== null ) {
115                        if ( $buffer['name'] === $content ) {
116                            // Fully qualitied "@var" comment, leave as-is.
117                            $output .= $buffer['raw'];
118                            $output .= $content;
119                        } else {
120                            // MW-style "@var" comment. Keep only the description and transplant
121                            // the type into the code.
122                            $output .= $buffer['desc'];
123                            $output .= "{$buffer['type']} $content";
124                        }
125                        $buffer = null;
126                    } else {
127                        $output .= $content;
128                    }
129                    break;
130
131                default:
132                    if ( $buffer !== null ) {
133                        $buffer['raw'] .= $content;
134                        $buffer['desc'] .= $content;
135                    } else {
136                        $output .= $content;
137                    }
138                    break;
139            }
140        }
141        return $output;
142    }
143}
144
145/** @deprecated class alias since 1.43 */
146class_alias( MWDoxygenFilter::class, 'MWDoxygenFilter' );