Security update for Core, with self-updated composer
[yaffs-website] / vendor / consolidation / annotated-command / src / Parser / Internal / BespokeDocBlockParser.php
similarity index 56%
rename from vendor/consolidation/annotated-command/src/Parser/Internal/AbstractCommandDocBlockParser.php
rename to vendor/consolidation/annotated-command/src/Parser/Internal/BespokeDocBlockParser.php
index 0543900fb6e8cbb01f576e20c36305389ca3e731..b53545c6216316a6bfd03adb952c7a24cf9a718a 100644 (file)
@@ -9,22 +9,9 @@ use Consolidation\AnnotatedCommand\Parser\DefaultsWithDescriptions;
  * DocBlock comment, and provide accessor methods for all of
  * the elements that are needed to create an annotated Command.
  */
-abstract class AbstractCommandDocBlockParser
+class BespokeDocBlockParser
 {
-    /**
-     * @var CommandInfo
-     */
-    protected $commandInfo;
-
-    /**
-     * @var \ReflectionMethod
-     */
-    protected $reflection;
-
-    /**
-     * @var string
-     */
-    protected $optionParamName;
+    protected $fqcnCache;
 
     /**
      * @var array
@@ -33,7 +20,7 @@ abstract class AbstractCommandDocBlockParser
         'command' => 'processCommandTag',
         'name' => 'processCommandTag',
         'arg' => 'processArgumentTag',
-        'param' => 'processParamTag',
+        'param' => 'processArgumentTag',
         'return' => 'processReturnTag',
         'option' => 'processOptionTag',
         'default' => 'processDefaultTag',
@@ -43,31 +30,22 @@ abstract class AbstractCommandDocBlockParser
         'desc' => 'processAlternateDescriptionTag',
     ];
 
-    public function __construct(CommandInfo $commandInfo, \ReflectionMethod $reflection)
+    public function __construct(CommandInfo $commandInfo, \ReflectionMethod $reflection, $fqcnCache = null)
     {
         $this->commandInfo = $commandInfo;
         $this->reflection = $reflection;
+        $this->fqcnCache = $fqcnCache ?: new FullyQualifiedClassCache();
     }
 
-    protected function processAllTags($phpdoc)
-    {
-        // Iterate over all of the tags, and process them as necessary.
-        foreach ($phpdoc->getTags() as $tag) {
-            $processFn = [$this, 'processGenericTag'];
-            if (array_key_exists($tag->getName(), $this->tagProcessors)) {
-                $processFn = [$this, $this->tagProcessors[$tag->getName()]];
-            }
-            $processFn($tag);
-        }
-    }
-
-    abstract protected function getTagContents($tag);
-
     /**
      * Parse the docBlock comment for this command, and set the
      * fields of this class with the data thereby obtained.
      */
-    abstract public function parse();
+    public function parse()
+    {
+        $doc = $this->reflection->getDocComment();
+        $this->parseDocBlock($doc);
+    }
 
     /**
      * Save any tag that we do not explicitly recognize in the
@@ -75,7 +53,7 @@ abstract class AbstractCommandDocBlockParser
      */
     protected function processGenericTag($tag)
     {
-        $this->commandInfo->addAnnotation($tag->getName(), $this->getTagContents($tag));
+        $this->commandInfo->addAnnotation($tag->getTag(), $tag->getContent());
     }
 
     /**
@@ -83,11 +61,14 @@ abstract class AbstractCommandDocBlockParser
      */
     protected function processCommandTag($tag)
     {
-        $commandName = $this->getTagContents($tag);
+        if (!$tag->hasWordAndDescription($matches)) {
+            throw new \Exception('Could not determine command name from tag ' . (string)$tag);
+        }
+        $commandName = $matches['word'];
         $this->commandInfo->setName($commandName);
         // We also store the name in the 'other annotations' so that is is
         // possible to determine if the method had a @command annotation.
-        $this->commandInfo->addAnnotation($tag->getName(), $commandName);
+        $this->commandInfo->addAnnotation($tag->getTag(), $commandName);
     }
 
     /**
@@ -99,7 +80,7 @@ abstract class AbstractCommandDocBlockParser
      */
     protected function processAlternateDescriptionTag($tag)
     {
-        $this->commandInfo->setDescription($this->getTagContents($tag));
+        $this->commandInfo->setDescription($tag->getContent());
     }
 
     /**
@@ -107,10 +88,13 @@ abstract class AbstractCommandDocBlockParser
      */
     protected function processArgumentTag($tag)
     {
-        if (!$this->pregMatchNameAndDescription((string)$tag->getDescription(), $match)) {
+        if (!$tag->hasVariable($matches)) {
+            throw new \Exception('Could not determine argument name from tag ' . (string)$tag);
+        }
+        if ($matches['variable'] == $this->optionParamName()) {
             return;
         }
-        $this->addOptionOrArgumentTag($tag, $this->commandInfo->arguments(), $match);
+        $this->addOptionOrArgumentTag($tag, $this->commandInfo->arguments(), $matches['variable'], $matches['description']);
     }
 
     /**
@@ -118,17 +102,16 @@ abstract class AbstractCommandDocBlockParser
      */
     protected function processOptionTag($tag)
     {
-        if (!$this->pregMatchOptionNameAndDescription((string)$tag->getDescription(), $match)) {
-            return;
+        if (!$tag->hasVariable($matches)) {
+            throw new \Exception('Could not determine option name from tag ' . (string)$tag);
         }
-        $this->addOptionOrArgumentTag($tag, $this->commandInfo->options(), $match);
+        $this->addOptionOrArgumentTag($tag, $this->commandInfo->options(), $matches['variable'], $matches['description']);
     }
 
-    protected function addOptionOrArgumentTag($tag, DefaultsWithDescriptions $set, $nameAndDescription)
+    protected function addOptionOrArgumentTag($tag, DefaultsWithDescriptions $set, $name, $description)
     {
-        $variableName = $this->commandInfo->findMatchingOption($nameAndDescription['name']);
-        $desc = $nameAndDescription['description'];
-        $description = static::removeLineBreaks($desc);
+        $variableName = $this->commandInfo->findMatchingOption($name);
+        $description = static::removeLineBreaks($description);
         $set->add($variableName, $description);
     }
 
@@ -138,11 +121,11 @@ abstract class AbstractCommandDocBlockParser
      */
     protected function processDefaultTag($tag)
     {
-        if (!$this->pregMatchNameAndDescription((string)$tag->getDescription(), $match)) {
-            return;
+        if (!$tag->hasVariable($matches)) {
+            throw new \Exception('Could not determine parameter name for default value from tag ' . (string)$tag);
         }
-        $variableName = $match['name'];
-        $defaultValue = $this->interpretDefaultValue($match['description']);
+        $variableName = $matches['variable'];
+        $defaultValue = $this->interpretDefaultValue($matches['description']);
         if ($this->commandInfo->arguments()->exists($variableName)) {
             $this->commandInfo->arguments()->setDefaultValue($variableName, $defaultValue);
             return;
@@ -158,9 +141,11 @@ abstract class AbstractCommandDocBlockParser
      */
     protected function processUsageTag($tag)
     {
-        $lines = explode("\n", $this->getTagContents($tag));
-        $usage = array_shift($lines);
-        $description = static::removeLineBreaks(implode("\n", $lines));
+        $lines = explode("\n", $tag->getContent());
+        $usage = trim(array_shift($lines));
+        $description = static::removeLineBreaks(implode("\n", array_map(function ($line) {
+            return trim($line);
+        }, $lines)));
 
         $this->commandInfo->setExampleUsage($usage, $description);
     }
@@ -170,7 +155,106 @@ abstract class AbstractCommandDocBlockParser
      */
     protected function processAliases($tag)
     {
-        $this->commandInfo->setAliases((string)$tag->getDescription());
+        $this->commandInfo->setAliases((string)$tag->getContent());
+    }
+
+    /**
+     * Store the data from a @return annotation in our argument descriptions.
+     */
+    protected function processReturnTag($tag)
+    {
+        // The return type might be a variable -- '$this'. It will
+        // usually be a type, like RowsOfFields, or \Namespace\RowsOfFields.
+        if (!$tag->hasVariableAndDescription($matches)) {
+            throw new \Exception('Could not determine return type from tag ' . (string)$tag);
+        }
+        // Look at namespace and `use` statments to make returnType a fqdn
+        $returnType = $matches['variable'];
+        $returnType = $this->findFullyQualifiedClass($returnType);
+        $this->commandInfo->setReturnType($returnType);
+    }
+
+    protected function findFullyQualifiedClass($className)
+    {
+        if (strpos($className, '\\') !== false) {
+            return $className;
+        }
+
+        return $this->fqcnCache->qualify($this->reflection->getFileName(), $className);
+    }
+
+    private function parseDocBlock($doc)
+    {
+        // Remove the leading /** and the trailing */
+        $doc = preg_replace('#^\s*/\*+\s*#', '', $doc);
+        $doc = preg_replace('#\s*\*+/\s*#', '', $doc);
+
+        // Nothing left? Exit.
+        if (empty($doc)) {
+            return;
+        }
+
+        $tagFactory = new TagFactory();
+        $lines = [];
+
+        foreach (explode("\n", $doc) as $row) {
+            // Remove trailing whitespace and leading space + '*'s
+            $row = rtrim($row);
+            $row = preg_replace('#^[ \t]*\**#', '', $row);
+
+            if (!$tagFactory->parseLine($row)) {
+                $lines[] = $row;
+            }
+        }
+
+        $this->processDescriptionAndHelp($lines);
+        $this->processAllTags($tagFactory->getTags());
+    }
+
+    protected function processDescriptionAndHelp($lines)
+    {
+        // Trim all of the lines individually.
+        $lines =
+            array_map(
+                function ($line) {
+                    return trim($line);
+                },
+                $lines
+            );
+
+        // Everything up to the first blank line goes in the description.
+        $description = array_shift($lines);
+        while ($this->nextLineIsNotEmpty($lines)) {
+            $description .= ' ' . array_shift($lines);
+        }
+
+        // Everything else goes in the help.
+        $help = trim(implode("\n", $lines));
+
+        $this->commandInfo->setDescription($description);
+        $this->commandInfo->setHelp($help);
+    }
+
+    protected function nextLineIsNotEmpty($lines)
+    {
+        if (empty($lines)) {
+            return false;
+        }
+
+        $nextLine = trim($lines[0]);
+        return !empty($nextLine);
+    }
+
+    protected function processAllTags($tags)
+    {
+        // Iterate over all of the tags, and process them as necessary.
+        foreach ($tags as $tag) {
+            $processFn = [$this, 'processGenericTag'];
+            if (array_key_exists($tag->getTag(), $this->tagProcessors)) {
+                $processFn = [$this, $this->tagProcessors[$tag->getTag()]];
+            }
+            $processFn($tag);
+        }
     }
 
     protected function lastParameterName()
@@ -201,25 +285,6 @@ abstract class AbstractCommandDocBlockParser
         return $this->optionParamName;
     }
 
-    /**
-     * Store the data from a @param annotation in our argument descriptions.
-     */
-    protected function processParamTag($tag)
-    {
-        $variableName = $tag->getVariableName();
-        $variableName = str_replace('$', '', $variableName);
-        $description = static::removeLineBreaks((string)$tag->getDescription());
-        if ($variableName == $this->optionParamName()) {
-            return;
-        }
-        $this->commandInfo->arguments()->add($variableName, $description);
-    }
-
-    /**
-     * Store the data from a @return annotation in our argument descriptions.
-     */
-    abstract protected function processReturnTag($tag);
-
     protected function interpretDefaultValue($defaultValue)
     {
         $defaults = [
@@ -237,34 +302,6 @@ abstract class AbstractCommandDocBlockParser
         return $defaultValue;
     }
 
-    /**
-     * Given a docblock description in the form "$variable description",
-     * return the variable name and description via the 'match' parameter.
-     */
-    protected function pregMatchNameAndDescription($source, &$match)
-    {
-        $nameRegEx = '\\$(?P<name>[^ \t]+)[ \t]+';
-        $descriptionRegEx = '(?P<description>.*)';
-        $optionRegEx = "/{$nameRegEx}{$descriptionRegEx}/s";
-
-        return preg_match($optionRegEx, $source, $match);
-    }
-
-    /**
-     * Given a docblock description in the form "$variable description",
-     * return the variable name and description via the 'match' parameter.
-     */
-    protected function pregMatchOptionNameAndDescription($source, &$match)
-    {
-        // Strip type and $ from the text before the @option name, if present.
-        $source = preg_replace('/^[a-zA-Z]* ?\\$/', '', $source);
-        $nameRegEx = '(?P<name>[^ \t]+)[ \t]+';
-        $descriptionRegEx = '(?P<description>.*)';
-        $optionRegEx = "/{$nameRegEx}{$descriptionRegEx}/s";
-
-        return preg_match($optionRegEx, $source, $match);
-    }
-
     /**
      * Given a list that might be 'a b c' or 'a, b, c' or 'a,b,c',
      * convert the data into the last of these forms.