[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/diviner/atomizer/ -> DivinerPHPAtomizer.php (source)

   1  <?php
   2  
   3  final class DivinerPHPAtomizer extends DivinerAtomizer {
   4  
   5    protected function newAtom($type) {
   6      return parent::newAtom($type)->setLanguage('php');
   7    }
   8  
   9  
  10    protected function executeAtomize($file_name, $file_data) {
  11      $future = xhpast_get_parser_future($file_data);
  12      $tree = XHPASTTree::newFromDataAndResolvedExecFuture(
  13        $file_data,
  14        $future->resolve());
  15  
  16      $atoms = array();
  17  
  18      $root = $tree->getRootNode();
  19  
  20      $func_decl = $root->selectDescendantsOfType('n_FUNCTION_DECLARATION');
  21      foreach ($func_decl as $func) {
  22  
  23        $name = $func->getChildByIndex(2);
  24  
  25        // Don't atomize closures
  26        if ($name->getTypeName() === 'n_EMPTY') {
  27          continue;
  28        }
  29  
  30        $atom = $this->newAtom(DivinerAtom::TYPE_FUNCTION)
  31          ->setName($name->getConcreteString())
  32          ->setLine($func->getLineNumber())
  33          ->setFile($file_name);
  34  
  35        $this->findAtomDocblock($atom, $func);
  36  
  37        $this->parseParams($atom, $func);
  38        $this->parseReturnType($atom, $func);
  39  
  40        $atoms[] = $atom;
  41      }
  42  
  43      $class_types = array(
  44        DivinerAtom::TYPE_CLASS => 'n_CLASS_DECLARATION',
  45        DivinerAtom::TYPE_INTERFACE => 'n_INTERFACE_DECLARATION',
  46      );
  47      foreach ($class_types as $atom_type => $node_type) {
  48        $class_decls = $root->selectDescendantsOfType($node_type);
  49        foreach ($class_decls as $class) {
  50          $name = $class->getChildByIndex(1, 'n_CLASS_NAME');
  51  
  52          $atom = $this->newAtom($atom_type)
  53            ->setName($name->getConcreteString())
  54            ->setFile($file_name)
  55            ->setLine($class->getLineNumber());
  56  
  57          // This parses "final" and "abstract".
  58          $attributes = $class->getChildByIndex(0, 'n_CLASS_ATTRIBUTES');
  59          foreach ($attributes->selectDescendantsOfType('n_STRING') as $attr) {
  60            $atom->setProperty($attr->getConcreteString(), true);
  61          }
  62  
  63          // If this exists, it is n_EXTENDS_LIST.
  64          $extends = $class->getChildByIndex(2);
  65          $extends_class = $extends->selectDescendantsOfType('n_CLASS_NAME');
  66          foreach ($extends_class as $parent_class) {
  67            $atom->addExtends(
  68              $this->newRef(
  69                DivinerAtom::TYPE_CLASS,
  70                $parent_class->getConcreteString()));
  71          }
  72  
  73          // If this exists, it is n_IMPLEMENTS_LIST.
  74          $implements = $class->getChildByIndex(3);
  75          $iface_names = $implements->selectDescendantsOfType('n_CLASS_NAME');
  76          foreach ($iface_names as $iface_name) {
  77            $atom->addExtends(
  78              $this->newRef(
  79                DivinerAtom::TYPE_INTERFACE,
  80                $iface_name->getConcreteString()));
  81          }
  82  
  83          $this->findAtomDocblock($atom, $class);
  84  
  85          $methods = $class->selectDescendantsOfType('n_METHOD_DECLARATION');
  86          foreach ($methods as $method) {
  87            $matom = $this->newAtom(DivinerAtom::TYPE_METHOD);
  88  
  89            $this->findAtomDocblock($matom, $method);
  90  
  91            $attribute_list = $method->getChildByIndex(0);
  92            $attributes = $attribute_list->selectDescendantsOfType('n_STRING');
  93            if ($attributes) {
  94              foreach ($attributes as $attribute) {
  95                $attr = strtolower($attribute->getConcreteString());
  96                switch ($attr) {
  97                  case 'final':
  98                  case 'abstract':
  99                  case 'static':
 100                    $matom->setProperty($attr, true);
 101                    break;
 102                  case 'public':
 103                  case 'protected':
 104                  case 'private':
 105                    $matom->setProperty('access', $attr);
 106                    break;
 107                }
 108              }
 109            } else {
 110              $matom->setProperty('access', 'public');
 111            }
 112  
 113            $this->parseParams($matom, $method);
 114  
 115            $matom->setName($method->getChildByIndex(2)->getConcreteString());
 116            $matom->setLine($method->getLineNumber());
 117            $matom->setFile($file_name);
 118  
 119            $this->parseReturnType($matom, $method);
 120            $atom->addChild($matom);
 121  
 122            $atoms[] = $matom;
 123          }
 124  
 125          $atoms[] = $atom;
 126        }
 127      }
 128  
 129      return $atoms;
 130    }
 131  
 132    private function parseParams(DivinerAtom $atom, AASTNode $func) {
 133      $params = $func
 134        ->getChildByIndex(3, 'n_DECLARATAION_PARAMETER_LIST')
 135        ->selectDescendantsOfType('n_DECLARATION_PARAMETER');
 136  
 137      $param_spec = array();
 138  
 139      if ($atom->getDocblockRaw()) {
 140        $metadata = $atom->getDocblockMeta();
 141      } else {
 142        $metadata = array();
 143      }
 144  
 145      $docs = idx($metadata, 'param');
 146      if ($docs) {
 147        $docs = explode("\n", $docs);
 148        $docs = array_filter($docs);
 149      } else {
 150        $docs = array();
 151      }
 152  
 153      if (count($docs)) {
 154        if (count($docs) < count($params)) {
 155          $atom->addWarning(
 156            pht(
 157              'This call takes %d parameters, but only %d are documented.',
 158              count($params),
 159              count($docs)));
 160        }
 161      }
 162  
 163      foreach ($params as $param) {
 164        $name = $param->getChildByIndex(1)->getConcreteString();
 165        $dict = array(
 166          'type'    => $param->getChildByIndex(0)->getConcreteString(),
 167          'default' => $param->getChildByIndex(2)->getConcreteString(),
 168        );
 169  
 170        if ($docs) {
 171          $doc = array_shift($docs);
 172          if ($doc) {
 173            $dict += $this->parseParamDoc($atom, $doc, $name);
 174          }
 175        }
 176  
 177        $param_spec[] = array(
 178          'name' => $name,
 179        ) + $dict;
 180      }
 181  
 182      if ($docs) {
 183        foreach ($docs as $doc) {
 184          if ($doc) {
 185            $param_spec[] = $this->parseParamDoc($atom, $doc, null);
 186          }
 187        }
 188      }
 189  
 190      // TODO: Find `assert_instances_of()` calls in the function body and
 191      // add their type information here. See T1089.
 192  
 193      $atom->setProperty('parameters', $param_spec);
 194    }
 195  
 196  
 197    private function findAtomDocblock(DivinerAtom $atom, XHPASTNode $node) {
 198      $token = $node->getDocblockToken();
 199      if ($token) {
 200        $atom->setDocblockRaw($token->getValue());
 201        return true;
 202      } else {
 203        $tokens = $node->getTokens();
 204        if ($tokens) {
 205          $prev = head($tokens);
 206          while ($prev = $prev->getPrevToken()) {
 207            if ($prev->isAnyWhitespace()) {
 208              continue;
 209            }
 210            break;
 211          }
 212  
 213          if ($prev && $prev->isComment()) {
 214            $value = $prev->getValue();
 215            $matches = null;
 216            if (preg_match('/@(return|param|task|author)/', $value, $matches)) {
 217              $atom->addWarning(
 218                pht(
 219                  'Atom "%s" is preceded by a comment containing "@%s", but the '.
 220                  'comment is not a documentation comment. Documentation '.
 221                  'comments must begin with "/**", followed by a newline. Did '.
 222                  'you mean to use a documentation comment? (As the comment is '.
 223                  'not a documentation comment, it will be ignored.)',
 224                  $atom->getName(),
 225                  $matches[1]));
 226            }
 227          }
 228        }
 229  
 230        $atom->setDocblockRaw('');
 231        return false;
 232      }
 233    }
 234  
 235    protected function parseParamDoc(DivinerAtom $atom, $doc, $name) {
 236      $dict = array();
 237      $split = preg_split('/\s+/', trim($doc), $limit = 2);
 238      if (!empty($split[0])) {
 239        $dict['doctype'] = $split[0];
 240      }
 241  
 242      if (!empty($split[1])) {
 243        $docs = $split[1];
 244  
 245        // If the parameter is documented like "@param int $num Blah blah ..",
 246        // get rid of the `$num` part (which Diviner considers optional). If it
 247        // is present and different from the declared name, raise a warning.
 248        $matches = null;
 249        if (preg_match('/^(\\$\S+)\s+/', $docs, $matches)) {
 250          if ($name !== null) {
 251            if ($matches[1] !== $name) {
 252              $atom->addWarning(
 253                pht(
 254                  'Parameter "%s" is named "%s" in the documentation. The '.
 255                  'documentation may be out of date.',
 256                  $name,
 257                  $matches[1]));
 258            }
 259          }
 260          $docs = substr($docs, strlen($matches[0]));
 261        }
 262  
 263        $dict['docs'] = $docs;
 264      }
 265  
 266      return $dict;
 267    }
 268  
 269    private function parseReturnType(DivinerAtom $atom, XHPASTNode $decl) {
 270      $return_spec = array();
 271  
 272      $metadata = $atom->getDocblockMeta();
 273      $return = idx($metadata, 'return');
 274  
 275      if (!$return) {
 276        $return = idx($metadata, 'returns');
 277        if ($return) {
 278          $atom->addWarning(
 279            pht('Documentation uses `@returns`, but should use `@return`.'));
 280        }
 281      }
 282  
 283      if ($atom->getName() == '__construct' && $atom->getType() == 'method') {
 284        $return_spec = array(
 285          'doctype' => 'this',
 286          'docs' => '//Implicit.//',
 287        );
 288  
 289        if ($return) {
 290          $atom->addWarning(
 291            'Method __construct() has explicitly documented @return. The '.
 292            '__construct() method always returns $this. Diviner documents '.
 293            'this implicitly.');
 294        }
 295      } else if ($return) {
 296        $split = preg_split('/\s+/', trim($return), $limit = 2);
 297        if (!empty($split[0])) {
 298          $type = $split[0];
 299        }
 300  
 301        if ($decl->getChildByIndex(1)->getTypeName() == 'n_REFERENCE') {
 302          $type = $type.' &';
 303        }
 304  
 305        $docs = null;
 306        if (!empty($split[1])) {
 307          $docs = $split[1];
 308        }
 309  
 310        $return_spec = array(
 311          'doctype' => $type,
 312          'docs'    => $docs,
 313        );
 314      } else {
 315        $return_spec = array(
 316          'type' => 'wild',
 317        );
 318      }
 319  
 320      $atom->setProperty('return', $return_spec);
 321    }
 322  
 323  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1