3 * Copyright (C) 2012 Tamas Imrei <tamas.imrei@gmail.com> https://virtualtee.blogspot.com/
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.
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.
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
21 * @ingroup Maintenance
22 * @phan-file-suppress PhanInvalidCommentForDeclarationType False negative about about `@var`
23 * @phan-file-suppress PhanUnextractableAnnotation False negative about about `@var`
27 * Doxygen filter to show correct member variable types in documentation.
30 * <https://virtualtee.blogspot.co.uk/2012/03/tip-for-using-doxygen-for-php-code.html>
32 * It has been adapted for MediaWiki to resolve various bugs we experienced
33 * from using Doxygen with our coding conventions:
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`.
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.
46 * @internal For use by maintenance/mwdoc-filter.php
47 * @ingroup Maintenance
49 class MWDoxygenFilter
{
51 * @param string $source Original source code
52 * @return string Filtered source code
54 public static function filter( $source ) {
55 $tokens = token_get_all( $source );
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'];
69 list( $id, $content ) = $token;
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+#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+)?#s',
81 function ( $matches ) use ( &$buffer ) {
82 $buffer['type'] = $matches[1];
83 $buffer['name'] = $matches[3] ??
null;
84 return ( $matches[2] ??
'' ) . ( $matches[3] ??
'' );
94 // Doxygen requires class members to be documented in one of two ways:
96 // 1. Fully qualified:
97 // /** @var SomeType $name Description here. */
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.
104 // /** Description here. */
105 // private SomeType? $name;
107 // In MediaWiki, we are mostly like #1 but without the name repeated:
108 // /** @var SomeType Description here. */
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'];
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";
132 if ( $buffer !== null ) {
133 $buffer['raw'] .= $content;
134 $buffer['desc'] .= $content;