[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |