Merge "PHPDoc: Fix non-carnonical order of elements in phpdoc"
[lhc/web/wiklou.git] / includes / utils / ClassCollector.php
index 25231df..c987354 100644 (file)
@@ -1,4 +1,22 @@
 <?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
 
 /**
  * Reads PHP code and returns the FQCN of every class defined within it.
@@ -26,13 +44,19 @@ class ClassCollector {
        protected $tokens;
 
        /**
-        * @var string $code PHP code (including <?php) to detect class names from
+        * @var array Class alias with target/name fields
+        */
+       protected $alias;
+
+       /**
+        * @param string $code PHP code (including <?php) to detect class names from
         * @return array List of FQCN detected within the tokens
         */
        public function getClasses( $code ) {
                $this->namespace = '';
                $this->classes = [];
                $this->startToken = null;
+               $this->alias = null;
                $this->tokens = [];
 
                foreach ( token_get_all( $code ) as $token ) {
@@ -55,20 +79,29 @@ class ClassCollector {
                if ( is_string( $token ) ) {
                        return;
                }
+               // Note: When changing class name discovery logic,
+               // AutoLoaderStructureTest.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:
+                       case T_NEW:
                                $this->startToken = $token;
+                               break;
+                       case T_STRING:
+                               if ( $token[1] === 'class_alias' ) {
+                                       $this->startToken = $token;
+                                       $this->alias = [];
+                               }
                }
        }
 
        /**
         * Accepts the next token in an expect sequence
         *
-        * @param array
+        * @param array $token
         */
        protected function tryEndExpect( $token ) {
                switch ( $this->startToken[0] ) {
@@ -77,6 +110,12 @@ class ClassCollector {
                                // "self::static" which accesses the class name. It doens't define a new class.
                                $this->startToken = null;
                                break;
+                       case T_NEW:
+                               // Skip over T_CLASS after T_NEW because this is a PHP 7 anonymous class.
+                               if ( !is_array( $token ) || $token[0] !== T_WHITESPACE ) {
+                                       $this->startToken = null;
+                               }
+                               break;
                        case T_NAMESPACE:
                                if ( $token === ';' || $token === '{' ) {
                                        $this->namespace = $this->implodeTokens() . '\\';
@@ -85,6 +124,58 @@ class ClassCollector {
                                }
                                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;
+                                       }
+                               }
+                               break;
+
                        case T_CLASS:
                        case T_INTERFACE:
                        case T_TRAIT: