@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Generate some amount of PHP class documentation

Summary:
Ref T988. This brings the class/interface atomizer over. A lot of parts of this are still varying degrees of very-rough, but most of the data ends up in approximatley the right place.

ALSO: PROGRESS BARS

Test Plan: See screenshots.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T988

Differential Revision: https://secure.phabricator.com/D6817

+342 -20
+5
resources/sql/patches/20130826.divinernode.sql
··· 1 + ALTER TABLE {$NAMESPACE}_diviner.diviner_livesymbol 2 + ADD nodeHash VARCHAR(64) COLLATE utf8_bin; 3 + 4 + ALTER TABLE {$NAMESPACE}_diviner.diviner_livesymbol 5 + ADD UNIQUE KEY (nodeHash);
+9
src/applications/diviner/atom/DivinerAtom.php
··· 4 4 5 5 const TYPE_FILE = 'file'; 6 6 const TYPE_ARTICLE = 'article'; 7 + const TYPE_METHOD = 'method'; 7 8 8 9 private $type; 9 10 private $name; ··· 181 182 return mpull($this->extends, 'toDictionary'); 182 183 } 183 184 185 + public function getExtends() { 186 + return $this->extends; 187 + } 188 + 184 189 public function getHash() { 185 190 if ($this->hash) { 186 191 return $this->hash; ··· 324 329 325 330 foreach (idx($dictionary, 'childHashes', array()) as $child) { 326 331 $atom->addChildHash($child); 332 + } 333 + 334 + foreach (idx($dictionary, 'extends', array()) as $extends) { 335 + $atom->addExtends(DivinerAtomRef::newFromDictionary($extends)); 327 336 } 328 337 329 338 return $atom;
+84
src/applications/diviner/atomizer/DivinerPHPAtomizer.php
··· 31 31 $atoms[] = $atom; 32 32 } 33 33 34 + $class_types = array( 35 + 'class' => 'n_CLASS_DECLARATION', 36 + 'interface' => 'n_INTERFACE_DECLARATION', 37 + ); 38 + foreach ($class_types as $atom_type => $node_type) { 39 + $class_decls = $root->selectDescendantsOfType($node_type); 40 + foreach ($class_decls as $class) { 41 + $name = $class->getChildByIndex(1, 'n_CLASS_NAME'); 42 + 43 + $atom = id(new DivinerAtom()) 44 + ->setType($atom_type) 45 + ->setName($name->getConcreteString()) 46 + ->setFile($file_name) 47 + ->setLine($class->getLineNumber()); 48 + 49 + // If this exists, it is n_EXTENDS_LIST. 50 + $extends = $class->getChildByIndex(2); 51 + $extends_class = $extends->selectDescendantsOfType('n_CLASS_NAME'); 52 + foreach ($extends_class as $parent_class) { 53 + $atom->addExtends( 54 + DivinerAtomRef::newFromDictionary( 55 + array( 56 + 'type' => 'class', 57 + 'name' => $parent_class->getConcreteString(), 58 + ))); 59 + } 60 + 61 + // If this exists, it is n_IMPLEMENTS_LIST. 62 + $implements = $class->getChildByIndex(3); 63 + $iface_names = $implements->selectDescendantsOfType('n_CLASS_NAME'); 64 + foreach ($iface_names as $iface_name) { 65 + $atom->addExtends( 66 + DivinerAtomRef::newFromDictionary( 67 + array( 68 + 'type' => 'interface', 69 + 'name' => $iface_name->getConcreteString(), 70 + ))); 71 + } 72 + 73 + $this->findAtomDocblock($atom, $class); 74 + 75 + $methods = $class->selectDescendantsOfType('n_METHOD_DECLARATION'); 76 + foreach ($methods as $method) { 77 + $matom = id(new DivinerAtom()) 78 + ->setType('method'); 79 + 80 + $this->findAtomDocblock($matom, $method); 81 + 82 + $attribute_list = $method->getChildByIndex(0); 83 + $attributes = $attribute_list->selectDescendantsOfType('n_STRING'); 84 + if ($attributes) { 85 + foreach ($attributes as $attribute) { 86 + $attr = strtolower($attribute->getConcreteString()); 87 + switch ($attr) { 88 + case 'static': 89 + $matom->setProperty($attr, true); 90 + break; 91 + case 'public': 92 + case 'protected': 93 + case 'private': 94 + $matom->setProperty('access', $attr); 95 + break; 96 + } 97 + } 98 + } else { 99 + $matom->setProperty('access', 'public'); 100 + } 101 + 102 + $this->parseParams($matom, $method); 103 + 104 + $matom->setName($method->getChildByIndex(2)->getConcreteString()); 105 + $matom->setLine($method->getLineNumber()); 106 + $matom->setFile($file_name); 107 + 108 + $this->parseReturnType($matom, $method); 109 + $atom->addChild($matom); 110 + 111 + $atoms[] = $matom; 112 + } 113 + 114 + $atoms[] = $atom; 115 + } 116 + } 117 + 34 118 return $atoms; 35 119 } 36 120
+93
src/applications/diviner/controller/DivinerAtomController.php
··· 46 46 ->withContexts(array($this->atomContext)) 47 47 ->withIndexes(array($this->atomIndex)) 48 48 ->needAtoms(true) 49 + ->needExtends(true) 49 50 ->executeOne(); 50 51 51 52 if (!$symbol) { ··· 53 54 } 54 55 55 56 $atom = $symbol->getAtom(); 57 + 58 + $extends = $atom->getExtends(); 59 + 60 + $child_hashes = $atom->getChildHashes(); 61 + if ($child_hashes) { 62 + $children = id(new DivinerAtomQuery()) 63 + ->setViewer($viewer) 64 + ->withIncludeUndocumentable(true) 65 + ->withNodeHashes($child_hashes) 66 + ->execute(); 67 + } else { 68 + $children = array(); 69 + } 56 70 57 71 $crumbs = $this->buildApplicationCrumbs(); 58 72 ··· 89 103 $properties->addProperty( 90 104 pht('Defined'), 91 105 $atom->getFile().':'.$atom->getLine()); 106 + 107 + 108 + $this->buildExtendsAndImplements($properties, $symbol); 92 109 93 110 $warnings = $atom->getWarnings(); 94 111 if ($warnings) { ··· 157 174 ->setReturn($return)); 158 175 } 159 176 177 + if ($children) { 178 + $document->appendChild( 179 + id(new PhabricatorHeaderView()) 180 + ->setHeader(pht('Methods'))); 181 + foreach ($children as $child) { 182 + $document->appendChild( 183 + id(new PhabricatorHeaderView()) 184 + ->setHeader($child->getName())); 185 + } 186 + } 187 + 160 188 if ($toc) { 161 189 $side = new PHUIListView(); 162 190 $side->addMenuItem( ··· 186 214 187 215 private function renderAtomTypeName($name) { 188 216 return phutil_utf8_ucwords($name); 217 + } 218 + 219 + private function buildExtendsAndImplements( 220 + PhabricatorPropertyListView $view, 221 + DivinerLiveSymbol $symbol) { 222 + 223 + $lineage = $this->getExtendsLineage($symbol); 224 + if ($lineage) { 225 + $lineage = mpull($lineage, 'getName'); 226 + $lineage = implode(' > ', $lineage); 227 + $view->addProperty(pht('Extends'), $lineage); 228 + } 229 + 230 + $implements = $this->getImplementsLineage($symbol); 231 + if ($implements) { 232 + $items = array(); 233 + foreach ($implements as $spec) { 234 + $via = $spec['via']; 235 + $iface = $spec['interface']; 236 + if ($via == $symbol) { 237 + $items[] = $iface->getName(); 238 + } else { 239 + $items[] = $iface->getName().' (via '.$via->getName().')'; 240 + } 241 + } 242 + 243 + $view->addProperty( 244 + pht('Implements'), 245 + phutil_implode_html(phutil_tag('br'), $items)); 246 + } 247 + 248 + } 249 + 250 + private function getExtendsLineage(DivinerLiveSymbol $symbol) { 251 + foreach ($symbol->getExtends() as $extends) { 252 + if ($extends->getType() == 'class') { 253 + $lineage = $this->getExtendsLineage($extends); 254 + $lineage[] = $extends; 255 + return $lineage; 256 + } 257 + } 258 + return array(); 259 + } 260 + 261 + private function getImplementsLineage(DivinerLiveSymbol $symbol) { 262 + $implements = array(); 263 + 264 + // Do these first so we get interfaces ordered from most to least specific. 265 + foreach ($symbol->getExtends() as $extends) { 266 + if ($extends->getType() == 'interface') { 267 + $implements[$extends->getName()] = array( 268 + 'interface' => $extends, 269 + 'via' => $symbol, 270 + ); 271 + } 272 + } 273 + 274 + // Now do parent interfaces. 275 + foreach ($symbol->getExtends() as $extends) { 276 + if ($extends->getType() == 'class') { 277 + $implements += $this->getImplementsLineage($extends); 278 + } 279 + } 280 + 281 + return $implements; 189 282 } 190 283 191 284 }
+9 -5
src/applications/diviner/publisher/DivinerLivePublisher.php
··· 63 63 } 64 64 65 65 protected function loadAllPublishedHashes() { 66 - $symbols = id(new DivinerLiveSymbol())->loadAllWhere( 67 - 'bookPHID = %s AND graphHash IS NOT NULL', 68 - $this->loadBook()->getPHID()); 66 + $symbols = id(new DivinerAtomQuery()) 67 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 68 + ->withBookPHIDs(array($this->loadBook()->getPHID())) 69 + ->withIncludeUndocumentable(true) 70 + ->execute(); 69 71 70 72 return mpull($symbols, 'getGraphHash'); 71 73 } ··· 84 86 foreach (PhabricatorLiskDAO::chunkSQL($strings, ', ') as $chunk) { 85 87 queryfx( 86 88 $conn_w, 87 - 'UPDATE %T SET graphHash = NULL WHERE graphHash IN (%Q)', 89 + 'UPDATE %T SET graphHash = NULL, nodeHash = NULL 90 + WHERE graphHash IN (%Q)', 88 91 $symbol_table->getTableName(), 89 92 $chunk); 90 93 } ··· 111 114 ->setGraphHash($hash) 112 115 ->setIsDocumentable((int)$is_documentable) 113 116 ->setTitle($ref->getTitle()) 114 - ->setGroupName($ref->getGroup()); 117 + ->setGroupName($ref->getGroup()) 118 + ->setNodeHash($atom->getHash()); 115 119 116 120 if ($is_documentable) { 117 121 $renderer = $this->getRenderer();
+1
src/applications/diviner/publisher/DivinerPublisher.php
··· 142 142 143 143 protected function shouldGenerateDocumentForAtom(DivinerAtom $atom) { 144 144 switch ($atom->getType()) { 145 + case DivinerAtom::TYPE_METHOD: 145 146 case DivinerAtom::TYPE_FILE: 146 147 return false; 147 148 case DivinerAtom::TYPE_ARTICLE:
+97
src/applications/diviner/query/DivinerAtomQuery.php
··· 12 12 private $indexes; 13 13 private $includeUndocumentable; 14 14 private $includeGhosts; 15 + private $nodeHashes; 15 16 16 17 private $needAtoms; 18 + private $needExtends; 17 19 18 20 public function withIDs(array $ids) { 19 21 $this->ids = $ids; ··· 47 49 48 50 public function withIndexes(array $indexes) { 49 51 $this->indexes = $indexes; 52 + return $this; 53 + } 54 + 55 + public function withNodeHashes(array $hashes) { 56 + $this->nodeHashes = $hashes; 50 57 return $this; 51 58 } 52 59 ··· 55 62 return $this; 56 63 } 57 64 65 + 58 66 /** 59 67 * Include "ghosts", which are symbols which used to exist but do not exist 60 68 * currently (for example, a function which existed in an older version of ··· 78 86 return $this; 79 87 } 80 88 89 + 90 + public function needExtends($need) { 91 + $this->needExtends = $need; 92 + return $this; 93 + } 94 + 81 95 public function withIncludeUndocumentable($include) { 82 96 $this->includeUndocumentable = $include; 83 97 return $this; ··· 116 130 $atom->attachBook($book); 117 131 } 118 132 133 + $need_atoms = $this->needAtoms; 134 + 119 135 if ($this->needAtoms) { 120 136 $atom_data = id(new DivinerLiveAtom())->loadAllWhere( 121 137 'symbolPHID IN (%Ls)', ··· 132 148 } 133 149 } 134 150 151 + // Load all of the symbols this symbol extends, recursively. Commonly, 152 + // this means all the ancestor classes and interfaces it extends and 153 + // implements. 154 + 155 + if ($this->needExtends) { 156 + 157 + // First, load all the matching symbols by name. This does 99% of the 158 + // work in most cases, assuming things are named at all reasonably. 159 + 160 + $names = array(); 161 + foreach ($atoms as $atom) { 162 + foreach ($atom->getAtom()->getExtends() as $xref) { 163 + $names[] = $xref->getName(); 164 + } 165 + } 166 + 167 + if ($names) { 168 + $xatoms = id(new DivinerAtomQuery()) 169 + ->setViewer($this->getViewer()) 170 + ->withNames($names) 171 + ->needExtends(true) 172 + ->needAtoms(true) 173 + ->execute(); 174 + $xatoms = mgroup($xatoms, 'getName', 'getType', 'getBookPHID'); 175 + } else { 176 + $xatoms = array(); 177 + } 178 + 179 + foreach ($atoms as $atom) { 180 + $alang = $atom->getAtom()->getLanguage(); 181 + $extends = array(); 182 + foreach ($atom->getAtom()->getExtends() as $xref) { 183 + 184 + // If there are no symbols of the matching name and type, we can't 185 + // resolve this. 186 + if (empty($xatoms[$xref->getName()][$xref->getType()])) { 187 + continue; 188 + } 189 + 190 + // If we found matches in the same documentation book, prefer them 191 + // over other matches. Otherwise, look at all the the matches. 192 + $matches = $xatoms[$xref->getName()][$xref->getType()]; 193 + if (isset($matches[$atom->getBookPHID()])) { 194 + $maybe = $matches[$atom->getBookPHID()]; 195 + } else { 196 + $maybe = array_mergev($matches); 197 + } 198 + 199 + if (!$maybe) { 200 + continue; 201 + } 202 + 203 + // Filter out matches in a different language, since, e.g., PHP 204 + // classes can not implement JS classes. 205 + $same_lang = array(); 206 + foreach ($maybe as $xatom) { 207 + if ($xatom->getAtom()->getLanguage() == $alang) { 208 + $same_lang[] = $xatom; 209 + } 210 + } 211 + 212 + if (!$same_lang) { 213 + continue; 214 + } 215 + 216 + // If we have duplicates remaining, just pick the first one. There's 217 + // nothing more we can do to figure out which is the real one. 218 + $extends[] = head($same_lang); 219 + } 220 + 221 + $atom->attachExtends($extends); 222 + } 223 + } 224 + 135 225 return $atoms; 136 226 } 137 227 ··· 218 308 $where[] = qsprintf( 219 309 $conn_r, 220 310 'graphHash IS NOT NULL'); 311 + } 312 + 313 + if ($this->nodeHashes) { 314 + $where[] = qsprintf( 315 + $conn_r, 316 + 'nodeHash IN (%Ls)', 317 + $this->nodeHashes); 221 318 } 222 319 223 320 $where[] = $this->buildPagingClause($conn_r);
+16 -10
src/applications/diviner/storage/DivinerLiveSymbol.php
··· 11 11 protected $atomIndex; 12 12 protected $graphHash; 13 13 protected $identityHash; 14 + protected $nodeHash; 14 15 15 16 protected $title; 16 17 protected $groupName; 17 18 protected $summary; 18 19 protected $isDocumentable = 0; 19 20 20 - private $book; 21 - private $atom; 21 + private $book = self::ATTACHABLE; 22 + private $atom = self::ATTACHABLE; 23 + private $extends = self::ATTACHABLE; 22 24 23 25 public function getConfiguration() { 24 26 return array( ··· 33 35 } 34 36 35 37 public function getBook() { 36 - if ($this->book === null) { 37 - throw new Exception("Call attachBook() before getBook()!"); 38 - } 39 - return $this->book; 38 + return $this->assertAttached($this->book); 40 39 } 41 40 42 41 public function attachBook(DivinerLiveBook $book) { ··· 45 44 } 46 45 47 46 public function getAtom() { 48 - if ($this->atom === null) { 49 - throw new Exception("Call attachAtom() before getAtom()!"); 50 - } 51 - return $this->atom; 47 + return $this->assertAttached($this->atom); 52 48 } 53 49 54 50 public function attachAtom(DivinerLiveAtom $atom) { ··· 107 103 $title = $this->getName(); 108 104 } 109 105 return $title; 106 + } 107 + 108 + public function attachExtends(array $extends) { 109 + assert_instances_of($extends, 'DivinerLiveSymbol'); 110 + $this->extends = $extends; 111 + return $this; 112 + } 113 + 114 + public function getExtends() { 115 + return $this->assertAttached($this->extends); 110 116 } 111 117 112 118
+3 -1
src/applications/diviner/workflow/DivinerAtomizeWorkflow.php
··· 86 86 $atoms = $atomizer->atomize($file, $data); 87 87 88 88 foreach ($atoms as $atom) { 89 - $file_atom->addChild($atom); 89 + if (!$atom->getParentHash()) { 90 + $file_atom->addChild($atom); 91 + } 90 92 } 91 93 92 94 $all_atoms[] = $atoms;
+21 -4
src/applications/diviner/workflow/DivinerGenerateWorkflow.php
··· 34 34 35 35 protected function log($message) { 36 36 $console = PhutilConsole::getConsole(); 37 - $console->getServer()->setEnableLog(true); 38 - $console->writeLog($message."\n"); 37 + $console->writeErr($message."\n"); 39 38 } 40 39 41 40 public function execute(PhutilArgumentParser $args) { ··· 154 153 $this->log(pht('Found %d file(s) to atomize.', count($file_atomizers))); 155 154 156 155 $futures = $this->buildAtomizerFutures($file_atomizers); 156 + 157 + $this->log(pht('Atomizing %d file(s).', count($file_atomizers))); 158 + 157 159 if ($futures) { 158 160 $this->resolveAtomizerFutures($futures, $file_hashes); 159 161 $this->log(pht("Atomization complete.")); ··· 278 280 $atomizers[$atomizer][] = $file; 279 281 } 280 282 283 + $root = dirname(phutil_get_library_root('phabricator')); 284 + $config_root = $this->getConfig('root'); 285 + 286 + $bar = id(new PhutilConsoleProgressBar()) 287 + ->setTotal(count($file_atomizers)); 288 + 281 289 $futures = array(); 282 290 foreach ($atomizers as $class => $files) { 283 291 foreach (array_chunk($files, 32) as $chunk) { 284 292 $future = new ExecFuture( 285 293 '%s atomize --ugly --book %s --atomizer %s -- %Ls', 286 - dirname(phutil_get_library_root('phabricator')).'/bin/diviner', 294 + $root.'/bin/diviner', 287 295 $this->getBookConfigPath(), 288 296 $class, 289 297 $chunk); 290 - $future->setCWD($this->getConfig('root')); 298 + $future->setCWD($config_root); 291 299 292 300 $futures[] = $future; 301 + 302 + $bar->update(count($chunk)); 293 303 } 294 304 } 295 305 306 + $bar->done(); 307 + 296 308 return $futures; 297 309 } 298 310 ··· 300 312 assert_instances_of($futures, 'Future'); 301 313 302 314 $atom_cache = $this->getAtomCache(); 315 + $bar = id(new PhutilConsoleProgressBar()) 316 + ->setTotal(count($futures)); 303 317 foreach (Futures($futures)->limit(4) as $key => $future) { 304 318 $atoms = $future->resolveJSON(); 305 319 ··· 310 324 } 311 325 $atom_cache->addAtom($atom); 312 326 } 327 + 328 + $bar->update(1); 313 329 } 330 + $bar->done(); 314 331 } 315 332 316 333
+4
src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
··· 1555 1555 'type' => 'sql', 1556 1556 'name' => $this->getPatchPath('20130820.releephxactions.sql'), 1557 1557 ), 1558 + '20130826.divinernode.sql' => array( 1559 + 'type' => 'sql', 1560 + 'name' => $this->getPatchPath('20130826.divinernode.sql'), 1561 + ), 1558 1562 ); 1559 1563 } 1560 1564 }