mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-23 22:10:55 +01:00
Compose and display method information in Diviner
Summary: Ref T988. As mentioned elsewhere, one broad goal of this iteration is to reduce the amount of boilerplate documentation we need to write. For example: - I want to set `@group` by default in most cases. - I want to inherit things like `@param` and `@return` by default. - I want to inherit method documentation by default. - I want to inherit `@task` information. This implements most of the method inheritance stuff. This //looks// super gross, but I believe we now compose all of the information of interest at display time and can work on rendering it sensibly in the near future. Test Plan: {F56790} Reviewers: btrahan, chad Reviewed By: btrahan CC: aran, sascha-egerer Maniphest Tasks: T988 Differential Revision: https://secure.phabricator.com/D6849
This commit is contained in:
parent
cf0bf34255
commit
bf50e0f870
2 changed files with 225 additions and 62 deletions
|
@ -2,6 +2,11 @@
|
|||
|
||||
final class DivinerPHPAtomizer extends DivinerAtomizer {
|
||||
|
||||
protected function newAtom($type) {
|
||||
return parent::newAtom($type)->setLanguage('php');
|
||||
}
|
||||
|
||||
|
||||
protected function executeAtomize($file_name, $file_data) {
|
||||
$future = xhpast_get_parser_future($file_data);
|
||||
$tree = XHPASTTree::newFromDataAndResolvedExecFuture(
|
||||
|
@ -17,8 +22,7 @@ final class DivinerPHPAtomizer extends DivinerAtomizer {
|
|||
|
||||
$name = $func->getChildByIndex(2);
|
||||
|
||||
$atom = id(new DivinerAtom())
|
||||
->setType('function')
|
||||
$atom = $this->newAtom(DivinerAtom::TYPE_FUNCTION)
|
||||
->setName($name->getConcreteString())
|
||||
->setLine($func->getLineNumber())
|
||||
->setFile($file_name);
|
||||
|
@ -32,30 +36,29 @@ final class DivinerPHPAtomizer extends DivinerAtomizer {
|
|||
}
|
||||
|
||||
$class_types = array(
|
||||
'class' => 'n_CLASS_DECLARATION',
|
||||
'interface' => 'n_INTERFACE_DECLARATION',
|
||||
DivinerAtom::TYPE_CLASS => 'n_CLASS_DECLARATION',
|
||||
DivinerAtom::TYPE_INTERFACE => 'n_INTERFACE_DECLARATION',
|
||||
);
|
||||
foreach ($class_types as $atom_type => $node_type) {
|
||||
$class_decls = $root->selectDescendantsOfType($node_type);
|
||||
foreach ($class_decls as $class) {
|
||||
$name = $class->getChildByIndex(1, 'n_CLASS_NAME');
|
||||
|
||||
$atom = id(new DivinerAtom())
|
||||
->setType($atom_type)
|
||||
$atom = $this->newAtom($atom_type)
|
||||
->setName($name->getConcreteString())
|
||||
->setFile($file_name)
|
||||
->setLine($class->getLineNumber());
|
||||
|
||||
// TODO: Parse "abstract" and "final".
|
||||
|
||||
// If this exists, it is n_EXTENDS_LIST.
|
||||
$extends = $class->getChildByIndex(2);
|
||||
$extends_class = $extends->selectDescendantsOfType('n_CLASS_NAME');
|
||||
foreach ($extends_class as $parent_class) {
|
||||
$atom->addExtends(
|
||||
DivinerAtomRef::newFromDictionary(
|
||||
array(
|
||||
'type' => 'class',
|
||||
'name' => $parent_class->getConcreteString(),
|
||||
)));
|
||||
$this->newRef(
|
||||
DivinerAtom::TYPE_CLASS,
|
||||
$parent_class->getConcreteString()));
|
||||
}
|
||||
|
||||
// If this exists, it is n_IMPLEMENTS_LIST.
|
||||
|
@ -63,19 +66,16 @@ final class DivinerPHPAtomizer extends DivinerAtomizer {
|
|||
$iface_names = $implements->selectDescendantsOfType('n_CLASS_NAME');
|
||||
foreach ($iface_names as $iface_name) {
|
||||
$atom->addExtends(
|
||||
DivinerAtomRef::newFromDictionary(
|
||||
array(
|
||||
'type' => 'interface',
|
||||
'name' => $iface_name->getConcreteString(),
|
||||
)));
|
||||
$this->newRef(
|
||||
DivinerAtom::TYPE_INTERFACE,
|
||||
$iface_name->getConcreteString()));
|
||||
}
|
||||
|
||||
$this->findAtomDocblock($atom, $class);
|
||||
|
||||
$methods = $class->selectDescendantsOfType('n_METHOD_DECLARATION');
|
||||
foreach ($methods as $method) {
|
||||
$matom = id(new DivinerAtom())
|
||||
->setType('method');
|
||||
$matom = $this->newAtom(DivinerAtom::TYPE_METHOD);
|
||||
|
||||
$this->findAtomDocblock($matom, $method);
|
||||
|
||||
|
|
|
@ -76,7 +76,8 @@ final class DivinerAtomController extends DivinerController {
|
|||
id(new PhabricatorTagView())
|
||||
->setType(PhabricatorTagView::TYPE_STATE)
|
||||
->setBackgroundColor(PhabricatorTagView::COLOR_BLUE)
|
||||
->setName(DivinerAtom::getAtomTypeNameString($atom->getType())));
|
||||
->setName(DivinerAtom::getAtomTypeNameString($atom->getType())))
|
||||
->setSubheader($this->renderFullSignature($symbol));
|
||||
|
||||
$properties = id(new PhabricatorPropertyListView());
|
||||
|
||||
|
@ -98,30 +99,20 @@ final class DivinerAtomController extends DivinerController {
|
|||
->setSeverity(AphrontErrorView::SEVERITY_WARNING);
|
||||
}
|
||||
|
||||
$methods = $this->composeMethods($symbol);
|
||||
|
||||
$field = 'default';
|
||||
$engine = id(new PhabricatorMarkupEngine())
|
||||
->setViewer($viewer)
|
||||
->addObject($symbol, $field)
|
||||
->process();
|
||||
|
||||
$content = $engine->getOutput($symbol, $field);
|
||||
|
||||
if (strlen(trim($symbol->getMarkupText($field)))) {
|
||||
$content = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phabricator-remarkup',
|
||||
),
|
||||
array(
|
||||
$content,
|
||||
));
|
||||
} else {
|
||||
$undoc = DivinerAtom::getThisAtomIsNotDocumentedString($atom->getType());
|
||||
$content = id(new AphrontErrorView())
|
||||
->appendChild($undoc)
|
||||
->setSeverity(AphrontErrorView::SEVERITY_NODATA);
|
||||
->addObject($symbol, $field);
|
||||
foreach ($methods as $method) {
|
||||
foreach ($method['atoms'] as $matom) {
|
||||
$engine->addObject($matom, $field);
|
||||
}
|
||||
}
|
||||
$engine->process();
|
||||
|
||||
$content = $this->renderDocumentationText($symbol, $engine);
|
||||
|
||||
$toc = $engine->getEngineMetadata(
|
||||
$symbol,
|
||||
|
@ -136,30 +127,9 @@ final class DivinerAtomController extends DivinerController {
|
|||
->appendChild($warnings)
|
||||
->appendChild($content);
|
||||
|
||||
$parameters = $atom->getProperty('parameters');
|
||||
if ($parameters !== null) {
|
||||
$document->appendChild(
|
||||
id(new PhabricatorHeaderView())
|
||||
->setHeader(pht('Parameters')));
|
||||
$document->appendChild($this->buildParametersAndReturn(array($symbol)));
|
||||
|
||||
$document->appendChild(
|
||||
id(new DivinerParameterTableView())
|
||||
->setParameters($parameters));
|
||||
}
|
||||
|
||||
$return = $atom->getProperty('return');
|
||||
if ($return !== null) {
|
||||
$document->appendChild(
|
||||
id(new PhabricatorHeaderView())
|
||||
->setHeader(pht('Return')));
|
||||
$document->appendChild(
|
||||
id(new DivinerReturnTableView())
|
||||
->setReturn($return));
|
||||
}
|
||||
|
||||
$methods = $this->composeMethods($symbol);
|
||||
if ($methods) {
|
||||
|
||||
$tasks = $this->composeTasks($symbol);
|
||||
|
||||
if ($tasks) {
|
||||
|
@ -201,8 +171,9 @@ final class DivinerAtomController extends DivinerController {
|
|||
id(new PhabricatorHeaderView())
|
||||
->setHeader(pht('Methods')));
|
||||
foreach ($methods as $spec) {
|
||||
$matom = last($spec['atoms']);
|
||||
$method_header = id(new PhabricatorHeaderView())
|
||||
->setHeader(last($spec['atoms'])->getName());
|
||||
->setHeader($matom->getName());
|
||||
|
||||
$inherited = $spec['inherited'];
|
||||
if ($inherited) {
|
||||
|
@ -213,7 +184,15 @@ final class DivinerAtomController extends DivinerController {
|
|||
->setName(pht('Inherited')));
|
||||
}
|
||||
|
||||
$document->appendChild($method_header);
|
||||
$method_header->setSubheader(
|
||||
$this->renderFullSignature($matom));
|
||||
|
||||
$document->appendChild(
|
||||
array(
|
||||
$method_header,
|
||||
$this->renderMethodDocumentationText($symbol, $spec, $engine),
|
||||
$this->buildParametersAndReturn($spec['atoms']),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -421,4 +400,188 @@ final class DivinerAtomController extends DivinerController {
|
|||
return $task_specs + $extends_task_specs;
|
||||
}
|
||||
|
||||
private function renderFullSignature(DivinerLiveSymbol $symbol) {
|
||||
switch ($symbol->getType()) {
|
||||
case DivinerAtom::TYPE_CLASS:
|
||||
case DivinerAtom::TYPE_INTERFACE:
|
||||
case DivinerAtom::TYPE_METHOD:
|
||||
case DivinerAtom::TYPE_FUNCTION:
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
$atom = $symbol->getAtom();
|
||||
|
||||
$out = array();
|
||||
if ($atom->getProperty('final')) {
|
||||
$out[] = 'final';
|
||||
}
|
||||
|
||||
if ($atom->getProperty('abstract')) {
|
||||
$out[] = 'abstract';
|
||||
}
|
||||
|
||||
if ($atom->getProperty('access')) {
|
||||
$out[] = $atom->getProperty('access');
|
||||
}
|
||||
|
||||
if ($atom->getProperty('static')) {
|
||||
$out[] = 'static';
|
||||
}
|
||||
|
||||
switch ($symbol->getType()) {
|
||||
case DivinerAtom::TYPE_CLASS:
|
||||
case DivinerAtom::TYPE_INTERFACE:
|
||||
$out[] = $symbol->getType();
|
||||
break;
|
||||
case DivinerAtom::TYPE_FUNCTION:
|
||||
switch ($atom->getLanguage()) {
|
||||
case 'php':
|
||||
$out[] = $symbol->getType();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case DivinerAtom::TYPE_METHOD:
|
||||
switch ($atom->getLanguage()) {
|
||||
case 'php':
|
||||
$out[] = DivinerAtom::TYPE_FUNCTION;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$out[] = $symbol->getName();
|
||||
|
||||
$out = implode(' ', $out);
|
||||
|
||||
$parameters = $atom->getProperty('parameters');
|
||||
if ($parameters !== null) {
|
||||
$pout = array();
|
||||
foreach ($parameters as $parameter) {
|
||||
$pout[] = $parameter['name'];
|
||||
}
|
||||
$out .= '('.implode(', ', $pout).')';
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
private function buildParametersAndReturn(array $symbols) {
|
||||
assert_instances_of($symbols, 'DivinerLiveSymbol');
|
||||
|
||||
$symbols = array_reverse($symbols);
|
||||
$out = array();
|
||||
|
||||
$collected_parameters = null;
|
||||
foreach ($symbols as $symbol) {
|
||||
$parameters = $symbol->getAtom()->getProperty('parameters');
|
||||
if ($parameters !== null) {
|
||||
if ($collected_parameters === null) {
|
||||
$collected_parameters = array();
|
||||
}
|
||||
foreach ($parameters as $key => $parameter) {
|
||||
if (isset($collected_parameters[$key])) {
|
||||
$collected_parameters[$key] += $parameter;
|
||||
} else {
|
||||
$collected_parameters[$key] = $parameter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($parameters !== null) {
|
||||
$out[] = id(new PhabricatorHeaderView())
|
||||
->setHeader(pht('Parameters'));
|
||||
$out[] = id(new DivinerParameterTableView())
|
||||
->setParameters($parameters);
|
||||
}
|
||||
|
||||
$collected_return = null;
|
||||
foreach ($symbols as $symbol) {
|
||||
$return = $symbol->getAtom()->getProperty('return');
|
||||
if ($return) {
|
||||
if ($collected_return) {
|
||||
$collected_return += $return;
|
||||
} else {
|
||||
$collected_return = $return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($return !== null) {
|
||||
$out[] = id(new PhabricatorHeaderView())
|
||||
->setHeader(pht('Return'));
|
||||
$out[] = id(new DivinerReturnTableView())
|
||||
->setReturn($collected_return);
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
private function renderDocumentationText(
|
||||
DivinerLiveSymbol $symbol,
|
||||
PhabricatorMarkupEngine $engine) {
|
||||
|
||||
$field = 'default';
|
||||
$content = $engine->getOutput($symbol, $field);
|
||||
|
||||
if (strlen(trim($symbol->getMarkupText($field)))) {
|
||||
$content = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phabricator-remarkup',
|
||||
),
|
||||
$content);
|
||||
} else {
|
||||
$atom = $symbol->getAtom();
|
||||
$undoc = DivinerAtom::getThisAtomIsNotDocumentedString($atom->getType());
|
||||
$content = id(new AphrontErrorView())
|
||||
->appendChild($undoc)
|
||||
->setSeverity(AphrontErrorView::SEVERITY_NODATA);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function renderMethodDocumentationText(
|
||||
DivinerLiveSymbol $parent,
|
||||
array $spec,
|
||||
PhabricatorMarkupEngine $engine) {
|
||||
|
||||
$symbols = array_values($spec['atoms']);
|
||||
$implementations = array_values($spec['implementations']);
|
||||
|
||||
$field = 'default';
|
||||
|
||||
$out = array();
|
||||
foreach ($symbols as $key => $symbol) {
|
||||
$impl = $implementations[$key];
|
||||
if ($impl !== $parent) {
|
||||
if (!strlen(trim($symbol->getMarkupText($field)))) {
|
||||
continue;
|
||||
}
|
||||
$out[] = phutil_tag(
|
||||
'div',
|
||||
array(),
|
||||
pht('From parent implementation in %s:', $impl->getName()));
|
||||
} else if ($out) {
|
||||
$out[] = phutil_tag(
|
||||
'div',
|
||||
array(),
|
||||
pht('From this implementation:'));
|
||||
}
|
||||
$out[] = $this->renderDocumentationText($symbol, $engine);
|
||||
}
|
||||
|
||||
// If we only have inherited implementations but none have documentation,
|
||||
// render the last one here so we get the "this thing has no documentation"
|
||||
// element.
|
||||
if (!$out) {
|
||||
$out[] = $this->renderDocumentationText($symbol, $engine);
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue