37 require_once __DIR__ .
'/Maintenance.php';
54 parent::__construct();
55 $this->
addDescription(
'Find hooks that are undocumented, missing, or just plain wrong' );
56 $this->
addOption(
'online',
'Check against MediaWiki.org hook documentation' );
77 "$IP/tests/phpunit/suites",
83 "$IP/tests/phpunit/MediaWikiTestCase.php",
86 foreach ( $recurseDirs
as $dir ) {
88 $potentialHooks = array_merge( $potentialHooks,
$ret[
'good'] );
89 $badHooks = array_merge( $badHooks,
$ret[
'bad'] );
91 foreach ( $nonRecurseDirs
as $dir ) {
93 $potentialHooks = array_merge( $potentialHooks,
$ret[
'good'] );
94 $badHooks = array_merge( $badHooks,
$ret[
'bad'] );
96 foreach ( $extraFiles
as $file ) {
97 $potentialHooks = array_merge( $potentialHooks, $this->
getHooksFromFile( $file ) );
101 $documented = array_keys( $documentedHooks );
102 $potential = array_keys( $potentialHooks );
103 $potential = array_unique( $potential );
104 $badHooks = array_diff( array_unique( $badHooks ), self::$ignore );
105 $todo = array_diff( $potential, $documented, self::$ignore );
106 $deprecated = array_diff( $documented, $potential, self::$ignore );
109 $badParameterCount = $badParameterReference = [];
110 foreach ( $potentialHooks
as $hook =>
$args ) {
111 if ( !isset( $documentedHooks[$hook] ) ) {
115 $argsDoc = $documentedHooks[$hook];
116 if (
$args ===
'unknown' || $argsDoc ===
'unknown' ) {
121 $badParameterCount[] = $hook .
': Doc: ' .
count( $argsDoc ) .
' vs. Code: ' .
count(
$args );
124 foreach ( $argsDoc
as $index => $argDoc ) {
125 $arg =
$args[$index];
126 if ( ( $arg[0] ===
'&' ) !== ( $argDoc[0] ===
'&' ) ) {
127 $badParameterReference[] = $hook .
': References different: Doc: ' . $argDoc .
128 ' vs. Code: ' . $arg;
136 $this->
printArray(
'Documented and not found', $deprecated );
137 $this->
printArray(
'Unclear hook calls', $badHooks );
138 $this->
printArray(
'Different parameter count', $badParameterCount );
139 $this->
printArray(
'Different parameter reference', $badParameterReference );
141 if ( !$todo && !$deprecated && !$badHooks
142 && !$badParameterCount && !$badParameterReference
144 $this->
output(
"Looks good!\n" );
146 $this->
error(
'The script finished with errors.', 1 );
170 $content = file_get_contents( $doc );
172 "/\n'(.*?)':.*((?:\n.+)*)/",
180 foreach ( $m
as $match ) {
182 if ( isset( $match[2] ) ) {
184 if ( preg_match_all(
"/\n(&?\\$\w+):.+/", $match[2], $n ) ) {
188 $hooks[$match[1]] =
$args;
200 return array_diff_key( $allhooks, $removed );
210 'list' =>
'categorymembers',
211 'cmtitle' =>
"Category:$title",
225 foreach ( $data[
'query'][
'categorymembers']
as $page ) {
226 if ( preg_match(
'/Manual\:Hooks\/([a-zA-Z0-9- :]+)/', $page[
'title'], $m ) ) {
228 $retval[str_replace(
' ',
'_', $m[1] )] =
'unknown';
231 if ( !isset( $data[
'continue'] ) ) {
244 $content = file_get_contents( $filePath );
248 '/(?:wfRunHooks|Hooks\:\:run)\s*\(\s*' .
254 '(?:\s*(?:array\s*\(|\[)' .
256 '((?:[^\(\)\[\]]|\((?-1)\)|\[(?-1)\])*)' .
266 foreach ( $m
as $match ) {
268 if ( isset( $match[4] ) ) {
270 if ( preg_match_all(
'/((?:[^,\(\)]|\([^\(\)]*\))+)/', $match[4], $n ) ) {
271 $args = array_map(
'trim', $n[1] );
275 } elseif ( isset( $match[3] ) ) {
281 $hooks[$match[2]] =
$args;
293 $content = file_get_contents( $filePath );
296 preg_match_all(
'/(?<!function )wfRunHooks\(\s*[^\s\'"].*/', $content, $m );
298 foreach ( $m[0]
as $match ) {
299 $list[] = $match .
"(" . $filePath .
")";
315 if ( $recurse === self::FIND_RECURSIVE ) {
316 $iterator =
new RecursiveIteratorIterator(
317 new RecursiveDirectoryIterator(
$dir, RecursiveDirectoryIterator::SKIP_DOTS ),
318 RecursiveIteratorIterator::SELF_FIRST
321 $iterator =
new DirectoryIterator(
$dir );
324 foreach ( $iterator
as $info ) {
326 if ( $info->isFile() && in_array( $info->getExtension(), [
'php',
'inc' ] )
328 && $info->getRealPath() !== __FILE__
330 $good = array_merge( $good, $this->
getHooksFromFile( $info->getRealPath() ) );
335 return [
'good' => $good,
'bad' => $bad ];
346 foreach ( $arr
as $v ) {
347 $this->
output(
"$msg: $v\n" );