39 require_once __DIR__ .
'/Maintenance.php';
56 parent::__construct();
57 $this->
addDescription(
'Find hooks that are undocumented, missing, or just plain wrong' );
58 $this->
addOption(
'online',
'Check against MediaWiki.org hook documentation' );
79 "$IP/tests/phpunit/suites",
85 "$IP/tests/phpunit/MediaWikiIntegrationTestCase.php",
88 foreach ( $recurseDirs as $dir ) {
90 $potentialHooks = array_merge( $potentialHooks, $ret[
'good'] );
91 $badHooks = array_merge( $badHooks, $ret[
'bad'] );
93 foreach ( $nonRecurseDirs as $dir ) {
95 $potentialHooks = array_merge( $potentialHooks, $ret[
'good'] );
96 $badHooks = array_merge( $badHooks, $ret[
'bad'] );
98 foreach ( $extraFiles as
$file ) {
99 $potentialHooks = array_merge( $potentialHooks, $this->
getHooksFromFile( $file ) );
103 $documented = array_keys( $documentedHooks );
104 $potential = array_keys( $potentialHooks );
105 $potential = array_unique( $potential );
106 $badHooks = array_diff( array_unique( $badHooks ), self::$ignore );
107 $todo = array_diff( $potential, $documented, self::$ignore );
108 $deprecated = array_diff( $documented, $potential, self::$ignore );
111 $badParameterCount = $badParameterReference = [];
112 foreach ( $potentialHooks as $hook =>
$args ) {
113 if ( !isset( $documentedHooks[$hook] ) ) {
117 $argsDoc = $documentedHooks[$hook];
118 if (
$args ===
'unknown' || $argsDoc ===
'unknown' ) {
122 if ( count( $argsDoc ) !== count(
$args ) ) {
123 $badParameterCount[] = $hook .
': Doc: ' . count( $argsDoc ) .
' vs. Code: ' . count(
$args );
126 foreach ( $argsDoc as $index => $argDoc ) {
127 $arg =
$args[$index];
128 if ( ( $arg[0] ===
'&' ) !== ( $argDoc[0] ===
'&' ) ) {
129 $badParameterReference[] = $hook .
': References different: Doc: ' . $argDoc .
130 ' vs. Code: ' . $arg;
138 $this->
printArray(
'Documented and not found', $deprecated );
139 $this->
printArray(
'Unclear hook calls', $badHooks );
140 $this->
printArray(
'Different parameter count', $badParameterCount );
141 $this->
printArray(
'Different parameter reference', $badParameterReference );
143 if ( !$todo && !$deprecated && !$badHooks
144 && !$badParameterCount && !$badParameterReference
146 $this->
output(
"Looks good!\n" );
148 $this->
fatalError(
'The script finished with errors.' );
172 $content = file_get_contents( $doc );
174 "/\n'(.*?)':.*((?:\n.+)*)/",
182 foreach ( $m as $match ) {
184 if ( isset( $match[2] ) ) {
186 if ( preg_match_all(
"/\n(&?\\$\w+):.+/", $match[2], $n ) ) {
190 $hooks[$match[1]] =
$args;
202 return array_diff_key( $allhooks, $removed );
212 'list' =>
'categorymembers',
213 'cmtitle' =>
"Category:$title",
221 $json = MediaWikiServices::getInstance()->getHttpRequestFactory()->get(
222 wfAppendQuery(
'https://www.mediawiki.org/w/api.php', $params ),
227 foreach ( $data[
'query'][
'categorymembers'] as $page ) {
228 if ( preg_match(
'/Manual\:Hooks\/([a-zA-Z0-9- :]+)/', $page[
'title'], $m ) ) {
230 $retval[str_replace(
' ',
'_', $m[1] )] =
'unknown';
233 if ( !isset( $data[
'continue'] ) ) {
236 $params = array_replace( $params, $data[
'continue'] );
246 $content = file_get_contents( $filePath );
250 '/(?:Hooks\:\:run|Hooks\:\:runWithoutAbort)\s*\(\s*' .
256 '(?:\s*(?:array\s*\(|\[)' .
258 '((?:[^\(\)\[\]]|\((?-1)\)|\[(?-1)\])*)' .
268 foreach ( $m as $match ) {
270 if ( isset( $match[4] ) ) {
272 if ( preg_match_all(
'/((?:[^,\(\)]|\([^\(\)]*\))+)/', $match[4], $n ) ) {
273 $args = array_map(
'trim', $n[1] );
277 } elseif ( isset( $match[3] ) ) {
283 $hooks[$match[2]] =
$args;
295 $content = file_get_contents( $filePath );
297 preg_match_all(
'/(?:Hooks\:\:run|Hooks\:\:runWithoutAbort)\(\s*[^\s\'"].*/',
$content, $m );
299 foreach ( $m[0] as $match ) {
300 $list[] = $match .
"(" . $filePath .
")";
316 if ( $recurse === self::FIND_RECURSIVE ) {
317 $iterator =
new RecursiveIteratorIterator(
318 new RecursiveDirectoryIterator( $dir, RecursiveDirectoryIterator::SKIP_DOTS ),
319 RecursiveIteratorIterator::SELF_FIRST
322 $iterator =
new DirectoryIterator( $dir );
326 foreach ( $iterator as $info ) {
328 if ( $info->isFile() && in_array( $info->getExtension(), [
'php',
'inc' ] )
330 && $info->getRealPath() !== __FILE__
332 $good = array_merge( $good, $this->
getHooksFromFile( $info->getRealPath() ) );
337 return [
'good' => $good,
'bad' => $bad ];
348 foreach ( $arr as $v ) {
349 $this->
output(
"$msg: $v\n" );