Merge "Require indentation of CASE statements in PHP code"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 19 Dec 2017 12:21:59 +0000 (12:21 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 19 Dec 2017 12:21:59 +0000 (12:21 +0000)
1  2 
includes/page/WikiPage.php
includes/parser/Parser.php
includes/utils/AutoloadGenerator.php

@@@ -23,7 -23,6 +23,7 @@@
  use MediaWiki\Edit\PreparedEdit;
  use \MediaWiki\Logger\LoggerFactory;
  use \MediaWiki\MediaWikiServices;
 +use Wikimedia\Assert\Assert;
  use Wikimedia\Rdbms\FakeResultWrapper;
  use Wikimedia\Rdbms\IDatabase;
  use Wikimedia\Rdbms\DBError;
@@@ -195,15 -194,15 +195,15 @@@ class WikiPage implements Page, IDBAcce
         */
        private static function convertSelectType( $type ) {
                switch ( $type ) {
-               case 'fromdb':
-                       return self::READ_NORMAL;
-               case 'fromdbmaster':
-                       return self::READ_LATEST;
-               case 'forupdate':
-                       return self::READ_LOCKING;
-               default:
-                       // It may already be an integer or whatever else
-                       return $type;
+                       case 'fromdb':
+                               return self::READ_NORMAL;
+                       case 'fromdbmaster':
+                               return self::READ_LATEST;
+                       case 'forupdate':
+                               return self::READ_LOCKING;
+                       default:
+                               // It may already be an integer or whatever else
+                               return $type;
                }
        }
  
                        $revision = Revision::newFromPageId( $this->getId(), $latest, $flags );
                } else {
                        $dbr = wfGetDB( DB_REPLICA );
 -                      $revision = Revision::newKnownCurrent( $dbr, $this->getId(), $latest );
 +                      $revision = Revision::newKnownCurrent( $dbr, $this->getTitle(), $latest );
                }
  
                if ( $revision ) { // sanity
                        $conditions['page_latest'] = $lastRevision;
                }
  
 +              $revId = $revision->getId();
 +              Assert::parameter( $revId > 0, '$revision->getId()', 'must be > 0' );
 +
                $row = [ /* SET */
 -                      'page_latest'      => $revision->getId(),
 +                      'page_latest'      => $revId,
                        'page_touched'     => $dbw->timestamp( $revision->getTimestamp() ),
                        'page_is_new'      => ( $lastRevision === 0 ) ? 1 : 0,
                        'page_is_redirect' => $rt !== null ? 1 : 0,
@@@ -406,6 -406,13 +406,6 @@@ class Parser 
                $text, Title $title, ParserOptions $options,
                $linestart = true, $clearState = true, $revid = null
        ) {
 -              /**
 -               * First pass--just handle <nowiki> sections, pass the rest off
 -               * to internalParse() which does all the real work.
 -               */
 -
 -              global $wgShowHostnames;
 -
                if ( $clearState ) {
                        // We use U+007F DELETE to construct strip markers, so we have to make
                        // sure that this character does not occur in the input text.
                        }
                }
  
 -              # Done parsing! Compute runtime adaptive expiry if set
 +              # Compute runtime adaptive expiry if set
                $this->mOutput->finalizeAdaptiveCacheExpiry();
  
                # Warn if too many heavyweight parser functions were used
                        );
                }
  
 -              # Information on include size limits, for the benefit of users who try to skirt them
 +              # Information on limits, for the benefit of users who try to skirt them
                if ( $this->mOptions->getEnableLimitReport() ) {
 -                      $max = $this->mOptions->getMaxIncludeSize();
 -
 -                      $cpuTime = $this->mOutput->getTimeSinceStart( 'cpu' );
 -                      if ( $cpuTime !== null ) {
 -                              $this->mOutput->setLimitReportData( 'limitreport-cputime',
 -                                      sprintf( "%.3f", $cpuTime )
 -                              );
 -                      }
 -
 -                      $wallTime = $this->mOutput->getTimeSinceStart( 'wall' );
 -                      $this->mOutput->setLimitReportData( 'limitreport-walltime',
 -                              sprintf( "%.3f", $wallTime )
 -                      );
 -
 -                      $this->mOutput->setLimitReportData( 'limitreport-ppvisitednodes',
 -                              [ $this->mPPNodeCount, $this->mOptions->getMaxPPNodeCount() ]
 -                      );
 -                      $this->mOutput->setLimitReportData( 'limitreport-ppgeneratednodes',
 -                              [ $this->mGeneratedPPNodeCount, $this->mOptions->getMaxGeneratedPPNodeCount() ]
 -                      );
 -                      $this->mOutput->setLimitReportData( 'limitreport-postexpandincludesize',
 -                              [ $this->mIncludeSizes['post-expand'], $max ]
 -                      );
 -                      $this->mOutput->setLimitReportData( 'limitreport-templateargumentsize',
 -                              [ $this->mIncludeSizes['arg'], $max ]
 -                      );
 -                      $this->mOutput->setLimitReportData( 'limitreport-expansiondepth',
 -                              [ $this->mHighestExpansionDepth, $this->mOptions->getMaxPPExpandDepth() ]
 -                      );
 -                      $this->mOutput->setLimitReportData( 'limitreport-expensivefunctioncount',
 -                              [ $this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit() ]
 -                      );
 -                      Hooks::run( 'ParserLimitReportPrepare', [ $this, $this->mOutput ] );
 -
 -                      $limitReport = "NewPP limit report\n";
 -                      if ( $wgShowHostnames ) {
 -                              $limitReport .= 'Parsed by ' . wfHostname() . "\n";
 -                      }
 -                      $limitReport .= 'Cached time: ' . $this->mOutput->getCacheTime() . "\n";
 -                      $limitReport .= 'Cache expiry: ' . $this->mOutput->getCacheExpiry() . "\n";
 -                      $limitReport .= 'Dynamic content: ' .
 -                              ( $this->mOutput->hasDynamicContent() ? 'true' : 'false' ) .
 -                              "\n";
 -
 -                      foreach ( $this->mOutput->getLimitReportData() as $key => $value ) {
 -                              if ( Hooks::run( 'ParserLimitReportFormat',
 -                                      [ $key, &$value, &$limitReport, false, false ]
 -                              ) ) {
 -                                      $keyMsg = wfMessage( $key )->inLanguage( 'en' )->useDatabase( false );
 -                                      $valueMsg = wfMessage( [ "$key-value-text", "$key-value" ] )
 -                                              ->inLanguage( 'en' )->useDatabase( false );
 -                                      if ( !$valueMsg->exists() ) {
 -                                              $valueMsg = new RawMessage( '$1' );
 -                                      }
 -                                      if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
 -                                              $valueMsg->params( $value );
 -                                              $limitReport .= "{$keyMsg->text()}: {$valueMsg->text()}\n";
 -                                      }
 -                              }
 -                      }
 -                      // Since we're not really outputting HTML, decode the entities and
 -                      // then re-encode the things that need hiding inside HTML comments.
 -                      $limitReport = htmlspecialchars_decode( $limitReport );
 -                      // Run deprecated hook
 -                      Hooks::run( 'ParserLimitReport', [ $this, &$limitReport ], '1.22' );
 -
 -                      // Sanitize for comment. Note '‐' in the replacement is U+2010,
 -                      // which looks much like the problematic '-'.
 -                      $limitReport = str_replace( [ '-', '&' ], [ '‐', '&amp;' ], $limitReport );
 -                      $text .= "\n<!-- \n$limitReport-->\n";
 -
 -                      // Add on template profiling data in human/machine readable way
 -                      $dataByFunc = $this->mProfiler->getFunctionStats();
 -                      uasort( $dataByFunc, function ( $a, $b ) {
 -                              return $a['real'] < $b['real']; // descending order
 -                      } );
 -                      $profileReport = [];
 -                      foreach ( array_slice( $dataByFunc, 0, 10 ) as $item ) {
 -                              $profileReport[] = sprintf( "%6.2f%% %8.3f %6d %s",
 -                                      $item['%real'], $item['real'], $item['calls'],
 -                                      htmlspecialchars( $item['name'] ) );
 -                      }
 -                      $text .= "<!--\nTransclusion expansion time report (%,ms,calls,template)\n";
 -                      $text .= implode( "\n", $profileReport ) . "\n-->\n";
 -
 -                      $this->mOutput->setLimitReportData( 'limitreport-timingprofile', $profileReport );
 -
 -                      // Add other cache related metadata
 -                      if ( $wgShowHostnames ) {
 -                              $this->mOutput->setLimitReportData( 'cachereport-origin', wfHostname() );
 -                      }
 -                      $this->mOutput->setLimitReportData( 'cachereport-timestamp',
 -                              $this->mOutput->getCacheTime() );
 -                      $this->mOutput->setLimitReportData( 'cachereport-ttl',
 -                              $this->mOutput->getCacheExpiry() );
 -                      $this->mOutput->setLimitReportData( 'cachereport-transientcontent',
 -                              $this->mOutput->hasDynamicContent() );
 -
 -                      if ( $this->mGeneratedPPNodeCount > $this->mOptions->getMaxGeneratedPPNodeCount() / 10 ) {
 -                              wfDebugLog( 'generated-pp-node-count', $this->mGeneratedPPNodeCount . ' ' .
 -                                      $this->mTitle->getPrefixedDBkey() );
 -                      }
 +                      $text .= $this->makeLimitReport();
                }
  
                # Wrap non-interface parser output in a <div> so it can be targeted
                return $this->mOutput;
        }
  
 +      /**
 +       * Set the limit report data in the current ParserOutput, and return the
 +       * limit report HTML comment.
 +       *
 +       * @return string
 +       */
 +      protected function makeLimitReport() {
 +              global $wgShowHostnames;
 +
 +              $maxIncludeSize = $this->mOptions->getMaxIncludeSize();
 +
 +              $cpuTime = $this->mOutput->getTimeSinceStart( 'cpu' );
 +              if ( $cpuTime !== null ) {
 +                      $this->mOutput->setLimitReportData( 'limitreport-cputime',
 +                              sprintf( "%.3f", $cpuTime )
 +                      );
 +              }
 +
 +              $wallTime = $this->mOutput->getTimeSinceStart( 'wall' );
 +              $this->mOutput->setLimitReportData( 'limitreport-walltime',
 +                      sprintf( "%.3f", $wallTime )
 +              );
 +
 +              $this->mOutput->setLimitReportData( 'limitreport-ppvisitednodes',
 +                      [ $this->mPPNodeCount, $this->mOptions->getMaxPPNodeCount() ]
 +              );
 +              $this->mOutput->setLimitReportData( 'limitreport-ppgeneratednodes',
 +                      [ $this->mGeneratedPPNodeCount, $this->mOptions->getMaxGeneratedPPNodeCount() ]
 +              );
 +              $this->mOutput->setLimitReportData( 'limitreport-postexpandincludesize',
 +                      [ $this->mIncludeSizes['post-expand'], $maxIncludeSize ]
 +              );
 +              $this->mOutput->setLimitReportData( 'limitreport-templateargumentsize',
 +                      [ $this->mIncludeSizes['arg'], $maxIncludeSize ]
 +              );
 +              $this->mOutput->setLimitReportData( 'limitreport-expansiondepth',
 +                      [ $this->mHighestExpansionDepth, $this->mOptions->getMaxPPExpandDepth() ]
 +              );
 +              $this->mOutput->setLimitReportData( 'limitreport-expensivefunctioncount',
 +                      [ $this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit() ]
 +              );
 +              Hooks::run( 'ParserLimitReportPrepare', [ $this, $this->mOutput ] );
 +
 +              $limitReport = "NewPP limit report\n";
 +              if ( $wgShowHostnames ) {
 +                      $limitReport .= 'Parsed by ' . wfHostname() . "\n";
 +              }
 +              $limitReport .= 'Cached time: ' . $this->mOutput->getCacheTime() . "\n";
 +              $limitReport .= 'Cache expiry: ' . $this->mOutput->getCacheExpiry() . "\n";
 +              $limitReport .= 'Dynamic content: ' .
 +                      ( $this->mOutput->hasDynamicContent() ? 'true' : 'false' ) .
 +                      "\n";
 +
 +              foreach ( $this->mOutput->getLimitReportData() as $key => $value ) {
 +                      if ( Hooks::run( 'ParserLimitReportFormat',
 +                              [ $key, &$value, &$limitReport, false, false ]
 +                      ) ) {
 +                              $keyMsg = wfMessage( $key )->inLanguage( 'en' )->useDatabase( false );
 +                              $valueMsg = wfMessage( [ "$key-value-text", "$key-value" ] )
 +                                      ->inLanguage( 'en' )->useDatabase( false );
 +                              if ( !$valueMsg->exists() ) {
 +                                      $valueMsg = new RawMessage( '$1' );
 +                              }
 +                              if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
 +                                      $valueMsg->params( $value );
 +                                      $limitReport .= "{$keyMsg->text()}: {$valueMsg->text()}\n";
 +                              }
 +                      }
 +              }
 +              // Since we're not really outputting HTML, decode the entities and
 +              // then re-encode the things that need hiding inside HTML comments.
 +              $limitReport = htmlspecialchars_decode( $limitReport );
 +              // Run deprecated hook
 +              Hooks::run( 'ParserLimitReport', [ $this, &$limitReport ], '1.22' );
 +
 +              // Sanitize for comment. Note '‐' in the replacement is U+2010,
 +              // which looks much like the problematic '-'.
 +              $limitReport = str_replace( [ '-', '&' ], [ '‐', '&amp;' ], $limitReport );
 +              $text = "\n<!-- \n$limitReport-->\n";
 +
 +              // Add on template profiling data in human/machine readable way
 +              $dataByFunc = $this->mProfiler->getFunctionStats();
 +              uasort( $dataByFunc, function ( $a, $b ) {
 +                      return $a['real'] < $b['real']; // descending order
 +              } );
 +              $profileReport = [];
 +              foreach ( array_slice( $dataByFunc, 0, 10 ) as $item ) {
 +                      $profileReport[] = sprintf( "%6.2f%% %8.3f %6d %s",
 +                              $item['%real'], $item['real'], $item['calls'],
 +                              htmlspecialchars( $item['name'] ) );
 +              }
 +              $text .= "<!--\nTransclusion expansion time report (%,ms,calls,template)\n";
 +              $text .= implode( "\n", $profileReport ) . "\n-->\n";
 +
 +              $this->mOutput->setLimitReportData( 'limitreport-timingprofile', $profileReport );
 +
 +              // Add other cache related metadata
 +              if ( $wgShowHostnames ) {
 +                      $this->mOutput->setLimitReportData( 'cachereport-origin', wfHostname() );
 +              }
 +              $this->mOutput->setLimitReportData( 'cachereport-timestamp',
 +                      $this->mOutput->getCacheTime() );
 +              $this->mOutput->setLimitReportData( 'cachereport-ttl',
 +                      $this->mOutput->getCacheExpiry() );
 +              $this->mOutput->setLimitReportData( 'cachereport-transientcontent',
 +                      $this->mOutput->hasDynamicContent() );
 +
 +              if ( $this->mGeneratedPPNodeCount > $this->mOptions->getMaxGeneratedPPNodeCount() / 10 ) {
 +                      wfDebugLog( 'generated-pp-node-count', $this->mGeneratedPPNodeCount . ' ' .
 +                              $this->mTitle->getPrefixedDBkey() );
 +              }
 +              return $text;
 +      }
 +
        /**
         * Half-parse wikitext to half-parsed HTML. This recursive parser entry point
         * can be called from an extension tag hook.
         * @return Revision|bool False if missing
         */
        public static function statelessFetchRevision( Title $title, $parser = false ) {
 -              $pageId = $title->getArticleID();
 -              $revId = $title->getLatestRevID();
 -
 -              $rev = Revision::newKnownCurrent( wfGetDB( DB_REPLICA ), $pageId, $revId );
 -              if ( $rev ) {
 -                      $rev->setTitle( $title );
 -              }
 +              $rev = Revision::newKnownCurrent( wfGetDB( DB_REPLICA ), $title );
  
                return $rev;
        }
                        # Decode HTML entities
                        $safeHeadline = Sanitizer::decodeCharReferences( $safeHeadline );
  
 -                      $safeHeadline = $this->normalizeSectionName( $safeHeadline );
 +                      $safeHeadline = self::normalizeSectionName( $safeHeadline );
  
                        $fallbackHeadline = Sanitizer::escapeIdForAttribute( $safeHeadline, Sanitizer::ID_FALLBACK );
                        $linkAnchor = Sanitizer::escapeIdForLink( $safeHeadline );
                                                $paramName = $paramMap[$magicName];
  
                                                switch ( $paramName ) {
-                                               case 'gallery-internal-alt':
-                                                       $alt = $this->stripAltText( $match, false );
-                                                       break;
-                                               case 'gallery-internal-link':
-                                                       $linkValue = strip_tags( $this->replaceLinkHoldersText( $match ) );
-                                                       $chars = self::EXT_LINK_URL_CLASS;
-                                                       $addr = self::EXT_LINK_ADDR;
-                                                       $prots = $this->mUrlProtocols;
-                                                       // check to see if link matches an absolute url, if not then it must be a wiki link.
-                                                       if ( preg_match( '/^-{R|(.*)}-$/', $linkValue ) ) {
-                                                               // Result of LanguageConverter::markNoConversion
-                                                               // invoked on an external link.
-                                                               $linkValue = substr( $linkValue, 4, -2 );
-                                                       }
-                                                       if ( preg_match( "/^($prots)$addr$chars*$/u", $linkValue ) ) {
-                                                               $link = $linkValue;
-                                                               $this->mOutput->addExternalLink( $link );
-                                                       } else {
-                                                               $localLinkTitle = Title::newFromText( $linkValue );
-                                                               if ( $localLinkTitle !== null ) {
-                                                                       $this->mOutput->addLink( $localLinkTitle );
-                                                                       $link = $localLinkTitle->getLinkURL();
+                                                       case 'gallery-internal-alt':
+                                                               $alt = $this->stripAltText( $match, false );
+                                                               break;
+                                                       case 'gallery-internal-link':
+                                                               $linkValue = strip_tags( $this->replaceLinkHoldersText( $match ) );
+                                                               $chars = self::EXT_LINK_URL_CLASS;
+                                                               $addr = self::EXT_LINK_ADDR;
+                                                               $prots = $this->mUrlProtocols;
+                                                               // check to see if link matches an absolute url, if not then it must be a wiki link.
+                                                               if ( preg_match( '/^-{R|(.*)}-$/', $linkValue ) ) {
+                                                                       // Result of LanguageConverter::markNoConversion
+                                                                       // invoked on an external link.
+                                                                       $linkValue = substr( $linkValue, 4, -2 );
+                                                               }
+                                                               if ( preg_match( "/^($prots)$addr$chars*$/u", $linkValue ) ) {
+                                                                       $link = $linkValue;
+                                                                       $this->mOutput->addExternalLink( $link );
+                                                               } else {
+                                                                       $localLinkTitle = Title::newFromText( $linkValue );
+                                                                       if ( $localLinkTitle !== null ) {
+                                                                               $this->mOutput->addLink( $localLinkTitle );
+                                                                               $link = $localLinkTitle->getLinkURL();
+                                                                       }
+                                                               }
+                                                               break;
+                                                       default:
+                                                               // Must be a handler specific parameter.
+                                                               if ( $handler->validateParam( $paramName, $match ) ) {
+                                                                       $handlerOptions[$paramName] = $match;
+                                                               } else {
+                                                                       // Guess not, consider it as caption.
+                                                                       wfDebug( "$parameterMatch failed parameter validation\n" );
+                                                                       $label = '|' . $parameterMatch;
                                                                }
-                                                       }
-                                                       break;
-                                               default:
-                                                       // Must be a handler specific parameter.
-                                                       if ( $handler->validateParam( $paramName, $match ) ) {
-                                                               $handlerOptions[$paramName] = $match;
-                                                       } else {
-                                                               // Guess not, consider it as caption.
-                                                               wfDebug( "$parameterMatch failed parameter validation\n" );
-                                                               $label = '|' . $parameterMatch;
-                                                       }
                                                }
  
                                        } else {
                                        } else {
                                                # Validate internal parameters
                                                switch ( $paramName ) {
-                                               case 'manualthumb':
-                                               case 'alt':
-                                               case 'class':
-                                                       # @todo FIXME: Possibly check validity here for
-                                                       # manualthumb? downstream behavior seems odd with
-                                                       # missing manual thumbs.
-                                                       $validated = true;
-                                                       $value = $this->stripAltText( $value, $holders );
-                                                       break;
-                                               case 'link':
-                                                       $chars = self::EXT_LINK_URL_CLASS;
-                                                       $addr = self::EXT_LINK_ADDR;
-                                                       $prots = $this->mUrlProtocols;
-                                                       if ( $value === '' ) {
-                                                               $paramName = 'no-link';
-                                                               $value = true;
+                                                       case 'manualthumb':
+                                                       case 'alt':
+                                                       case 'class':
+                                                               # @todo FIXME: Possibly check validity here for
+                                                               # manualthumb? downstream behavior seems odd with
+                                                               # missing manual thumbs.
                                                                $validated = true;
-                                                       } elseif ( preg_match( "/^((?i)$prots)/", $value ) ) {
-                                                               if ( preg_match( "/^((?i)$prots)$addr$chars*$/u", $value, $m ) ) {
-                                                                       $paramName = 'link-url';
-                                                                       $this->mOutput->addExternalLink( $value );
-                                                                       if ( $this->mOptions->getExternalLinkTarget() ) {
-                                                                               $params[$type]['link-target'] = $this->mOptions->getExternalLinkTarget();
-                                                                       }
-                                                                       $validated = true;
-                                                               }
-                                                       } else {
-                                                               $linkTitle = Title::newFromText( $value );
-                                                               if ( $linkTitle ) {
-                                                                       $paramName = 'link-title';
-                                                                       $value = $linkTitle;
-                                                                       $this->mOutput->addLink( $linkTitle );
+                                                               $value = $this->stripAltText( $value, $holders );
+                                                               break;
+                                                       case 'link':
+                                                               $chars = self::EXT_LINK_URL_CLASS;
+                                                               $addr = self::EXT_LINK_ADDR;
+                                                               $prots = $this->mUrlProtocols;
+                                                               if ( $value === '' ) {
+                                                                       $paramName = 'no-link';
+                                                                       $value = true;
                                                                        $validated = true;
+                                                               } elseif ( preg_match( "/^((?i)$prots)/", $value ) ) {
+                                                                       if ( preg_match( "/^((?i)$prots)$addr$chars*$/u", $value, $m ) ) {
+                                                                               $paramName = 'link-url';
+                                                                               $this->mOutput->addExternalLink( $value );
+                                                                               if ( $this->mOptions->getExternalLinkTarget() ) {
+                                                                                       $params[$type]['link-target'] = $this->mOptions->getExternalLinkTarget();
+                                                                               }
+                                                                               $validated = true;
+                                                                       }
+                                                               } else {
+                                                                       $linkTitle = Title::newFromText( $value );
+                                                                       if ( $linkTitle ) {
+                                                                               $paramName = 'link-title';
+                                                                               $value = $linkTitle;
+                                                                               $this->mOutput->addLink( $linkTitle );
+                                                                               $validated = true;
+                                                                       }
                                                                }
-                                                       }
-                                                       break;
-                                               case 'frameless':
-                                               case 'framed':
-                                               case 'thumbnail':
-                                                       // use first appearing option, discard others.
-                                                       $validated = !$seenformat;
-                                                       $seenformat = true;
-                                                       break;
-                                               default:
-                                                       # Most other things appear to be empty or numeric...
-                                                       $validated = ( $value === false || is_numeric( trim( $value ) ) );
+                                                               break;
+                                                       case 'frameless':
+                                                       case 'framed':
+                                                       case 'thumbnail':
+                                                               // use first appearing option, discard others.
+                                                               $validated = !$seenformat;
+                                                               $seenformat = true;
+                                                               break;
+                                                       default:
+                                                               # Most other things appear to be empty or numeric...
+                                                               $validated = ( $value === false || is_numeric( trim( $value ) ) );
                                                }
                                        }
  
                return $this->mDefaultSort;
        }
  
 +      private static function getSectionNameFromStrippedText( $text ) {
 +              $text = Sanitizer::normalizeSectionNameWhitespace( $text );
 +              $text = Sanitizer::decodeCharReferences( $text );
 +              $text = self::normalizeSectionName( $text );
 +              return $text;
 +      }
 +
 +      private static function makeAnchor( $sectionName ) {
 +              return '#' . Sanitizer::escapeIdForLink( $sectionName );
 +      }
 +
 +      private static function makeLegacyAnchor( $sectionName ) {
 +              global $wgFragmentMode;
 +              if ( isset( $wgFragmentMode[1] ) && $wgFragmentMode[1] === 'legacy' ) {
 +                      // ForAttribute() and ForLink() are the same for legacy encoding
 +                      $id = Sanitizer::escapeIdForAttribute( $text, Sanitizer::ID_FALLBACK );
 +              } else {
 +                      $id = Sanitizer::escapeIdForLink( $text );
 +              }
 +
 +              return "#$id";
 +      }
 +
        /**
         * Try to guess the section anchor name based on a wikitext fragment
         * presumably extracted from a heading, for example "Header" from
         * "== Header ==".
         *
         * @param string $text
 -       *
 -       * @return string
 +       * @return string Anchor (starting with '#')
         */
        public function guessSectionNameFromWikiText( $text ) {
                # Strip out wikitext links(they break the anchor)
                $text = $this->stripSectionName( $text );
 -              $text = Sanitizer::normalizeSectionNameWhitespace( $text );
 -              $text = Sanitizer::decodeCharReferences( $text );
 -              $text = $this->normalizeSectionName( $text );
 -
 -              return '#' . Sanitizer::escapeIdForLink( $text );
 +              $sectionName = self::getSectionNameFromStrippedText( $text );
 +              return self::makeAnchor( $sectionName );
        }
  
        /**
         * than UTF-8, resulting in breakage.
         *
         * @param string $text The section name
 -       * @return string An anchor
 +       * @return string Anchor (starting with '#')
         */
        public function guessLegacySectionNameFromWikiText( $text ) {
 -              global $wgFragmentMode;
 -
                # Strip out wikitext links(they break the anchor)
                $text = $this->stripSectionName( $text );
 -              $text = Sanitizer::normalizeSectionNameWhitespace( $text );
 -              $text = Sanitizer::decodeCharReferences( $text );
 -              $text = $this->normalizeSectionName( $text );
 -
 -              if ( isset( $wgFragmentMode[1] ) && $wgFragmentMode[1] === 'legacy' ) {
 -                      // ForAttribute() and ForLink() are the same for legacy encoding
 -                      $id = Sanitizer::escapeIdForAttribute( $text, Sanitizer::ID_FALLBACK );
 -              } else {
 -                      $id = Sanitizer::escapeIdForLink( $text );
 -              }
 +              $sectionName = self::getSectionNameFromStrippedText( $text );
 +              return self::makeLegacyAnchor( $sectionName );
 +      }
  
 -              return "#$id";
 +      /**
 +       * Like guessSectionNameFromWikiText(), but takes already-stripped text as input.
 +       * @param string $text Section name (plain text)
 +       * @return string Anchor (starting with '#')
 +       */
 +      public static function guessSectionNameFromStrippedText( $text ) {
 +              $sectionName = self::getSectionNameFromStrippedText( $text );
 +              return self::makeAnchor( $sectionName );
        }
  
        /**
         * @param string $text
         * @return string
         */
 -      private function normalizeSectionName( $text ) {
 +      private static function normalizeSectionName( $text ) {
                # T90902: ensure the same normalization is applied for IDs as to links
                $titleParser = MediaWikiServices::getInstance()->getTitleParser();
                try {
@@@ -42,13 -42,6 +42,13 @@@ class AutoloadGenerator 
         */
        protected $overrides = [];
  
 +      /**
 +       * Directories that should be excluded
 +       *
 +       * @var string[]
 +       */
 +      protected $excludePaths = [];
 +
        /**
         * @param string $basepath Root path of the project being scanned for classes
         * @param array|string $flags
                }
        }
  
 +      /**
 +       * Directories that should be excluded
 +       *
 +       * @since 1.31
 +       * @param string[] $paths
 +       */
 +      public function setExcludePaths( array $paths ) {
 +              foreach ( $paths as $path ) {
 +                      $this->excludePaths[] = self::normalizePathSeparator( $path );
 +              }
 +      }
 +
 +      /**
 +       * Whether the file should be excluded
 +       *
 +       * @param string $path File path
 +       * @return bool
 +       */
 +      private function shouldExclude( $path ) {
 +              foreach ( $this->excludePaths as $dir ) {
 +                      if ( strpos( $path, $dir ) === 0 ) {
 +                              return true;
 +                      }
 +              }
 +
 +              return false;
 +      }
 +
        /**
         * Force a class to be autoloaded from a specific path, regardless of where
         * or if it was detected.
                if ( substr( $inputPath, 0, $len ) !== $this->basepath ) {
                        throw new \Exception( "Path is not within basepath: $inputPath" );
                }
 +              if ( $this->shouldExclude( $inputPath ) ) {
 +                      return;
 +              }
                $result = $this->collector->getClasses(
                        file_get_contents( $inputPath )
                );
@@@ -392,18 -354,18 +392,18 @@@ class ClassCollector 
                // Note: When changing class name discovery logic,
                // AutoLoaderTest.php may also need to be updated.
                switch ( $token[0] ) {
-               case T_NAMESPACE:
-               case T_CLASS:
-               case T_INTERFACE:
-               case T_TRAIT:
-               case T_DOUBLE_COLON:
-                       $this->startToken = $token;
-                       break;
-               case T_STRING:
-                       if ( $token[1] === 'class_alias' ) {
+                       case T_NAMESPACE:
+                       case T_CLASS:
+                       case T_INTERFACE:
+                       case T_TRAIT:
+                       case T_DOUBLE_COLON:
                                $this->startToken = $token;
-                               $this->alias = [];
-                       }
+                               break;
+                       case T_STRING:
+                               if ( $token[1] === 'class_alias' ) {
+                                       $this->startToken = $token;
+                                       $this->alias = [];
+                               }
                }
        }
  
         */
        protected function tryEndExpect( $token ) {
                switch ( $this->startToken[0] ) {
-               case T_DOUBLE_COLON:
-                       // Skip over T_CLASS after T_DOUBLE_COLON because this is something like
-                       // "self::static" which accesses the class name. It doens't define a new class.
-                       $this->startToken = null;
-                       break;
-               case T_NAMESPACE:
-                       if ( $token === ';' || $token === '{' ) {
-                               $this->namespace = $this->implodeTokens() . '\\';
-                       } else {
-                               $this->tokens[] = $token;
-                       }
-                       break;
-               case T_STRING:
-                       if ( $this->alias !== null ) {
-                               // Flow 1 - Two string literals:
-                               // - T_STRING  class_alias
-                               // - '('
-                               // - T_CONSTANT_ENCAPSED_STRING 'TargetClass'
-                               // - ','
-                               // - T_WHITESPACE
-                               // - T_CONSTANT_ENCAPSED_STRING 'AliasName'
-                               // - ')'
-                               // Flow 2 - Use of ::class syntax for first parameter
-                               // - T_STRING  class_alias
-                               // - '('
-                               // - T_STRING TargetClass
-                               // - T_DOUBLE_COLON ::
-                               // - T_CLASS class
-                               // - ','
-                               // - T_WHITESPACE
-                               // - T_CONSTANT_ENCAPSED_STRING 'AliasName'
-                               // - ')'
-                               if ( $token === '(' ) {
-                                       // Start of a function call to class_alias()
-                                       $this->alias = [ 'target' => false, 'name' => false ];
-                               } elseif ( $token === ',' ) {
-                                       // Record that we're past the first parameter
-                                       if ( $this->alias['target'] === false ) {
-                                               $this->alias['target'] = true;
-                                       }
-                               } elseif ( is_array( $token ) && $token[0] === T_CONSTANT_ENCAPSED_STRING ) {
-                                       if ( $this->alias['target'] === true ) {
-                                               // We already saw a first argument, this must be the second.
-                                               // Strip quotes from the string literal.
-                                               $this->alias['name'] = substr( $token[1], 1, -1 );
+                       case T_DOUBLE_COLON:
+                               // Skip over T_CLASS after T_DOUBLE_COLON because this is something like
+                               // "self::static" which accesses the class name. It doens't define a new class.
+                               $this->startToken = null;
+                               break;
+                       case T_NAMESPACE:
+                               if ( $token === ';' || $token === '{' ) {
+                                       $this->namespace = $this->implodeTokens() . '\\';
+                               } else {
+                                       $this->tokens[] = $token;
+                               }
+                               break;
+                       case T_STRING:
+                               if ( $this->alias !== null ) {
+                                       // Flow 1 - Two string literals:
+                                       // - T_STRING  class_alias
+                                       // - '('
+                                       // - T_CONSTANT_ENCAPSED_STRING 'TargetClass'
+                                       // - ','
+                                       // - T_WHITESPACE
+                                       // - T_CONSTANT_ENCAPSED_STRING 'AliasName'
+                                       // - ')'
+                                       // Flow 2 - Use of ::class syntax for first parameter
+                                       // - T_STRING  class_alias
+                                       // - '('
+                                       // - T_STRING TargetClass
+                                       // - T_DOUBLE_COLON ::
+                                       // - T_CLASS class
+                                       // - ','
+                                       // - T_WHITESPACE
+                                       // - T_CONSTANT_ENCAPSED_STRING 'AliasName'
+                                       // - ')'
+                                       if ( $token === '(' ) {
+                                               // Start of a function call to class_alias()
+                                               $this->alias = [ 'target' => false, 'name' => false ];
+                                       } elseif ( $token === ',' ) {
+                                               // Record that we're past the first parameter
+                                               if ( $this->alias['target'] === false ) {
+                                                       $this->alias['target'] = true;
+                                               }
+                                       } elseif ( is_array( $token ) && $token[0] === T_CONSTANT_ENCAPSED_STRING ) {
+                                               if ( $this->alias['target'] === true ) {
+                                                       // We already saw a first argument, this must be the second.
+                                                       // Strip quotes from the string literal.
+                                                       $this->alias['name'] = substr( $token[1], 1, -1 );
+                                               }
+                                       } elseif ( $token === ')' ) {
+                                               // End of function call
+                                               $this->classes[] = $this->alias['name'];
+                                               $this->alias = null;
+                                               $this->startToken = null;
+                                       } elseif ( !is_array( $token ) || (
+                                               $token[0] !== T_STRING &&
+                                               $token[0] !== T_DOUBLE_COLON &&
+                                               $token[0] !== T_CLASS &&
+                                               $token[0] !== T_WHITESPACE
+                                       ) ) {
+                                               // Ignore this call to class_alias() - compat/Timestamp.php
+                                               $this->alias = null;
+                                               $this->startToken = null;
                                        }
-                               } elseif ( $token === ')' ) {
-                                       // End of function call
-                                       $this->classes[] = $this->alias['name'];
-                                       $this->alias = null;
-                                       $this->startToken = null;
-                               } elseif ( !is_array( $token ) || (
-                                       $token[0] !== T_STRING &&
-                                       $token[0] !== T_DOUBLE_COLON &&
-                                       $token[0] !== T_CLASS &&
-                                       $token[0] !== T_WHITESPACE
-                               ) ) {
-                                       // Ignore this call to class_alias() - compat/Timestamp.php
-                                       $this->alias = null;
-                                       $this->startToken = null;
                                }
-                       }
-                       break;
-               case T_CLASS:
-               case T_INTERFACE:
-               case T_TRAIT:
-                       $this->tokens[] = $token;
-                       if ( is_array( $token ) && $token[0] === T_STRING ) {
-                               $this->classes[] = $this->namespace . $this->implodeTokens();
-                       }
+                               break;
+                       case T_CLASS:
+                       case T_INTERFACE:
+                       case T_TRAIT:
+                               $this->tokens[] = $token;
+                               if ( is_array( $token ) && $token[0] === T_STRING ) {
+                                       $this->classes[] = $this->namespace . $this->implodeTokens();
+                               }
                }
        }