mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-27 01:02:42 +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 {
|
final class DivinerPHPAtomizer extends DivinerAtomizer {
|
||||||
|
|
||||||
|
protected function newAtom($type) {
|
||||||
|
return parent::newAtom($type)->setLanguage('php');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
protected function executeAtomize($file_name, $file_data) {
|
protected function executeAtomize($file_name, $file_data) {
|
||||||
$future = xhpast_get_parser_future($file_data);
|
$future = xhpast_get_parser_future($file_data);
|
||||||
$tree = XHPASTTree::newFromDataAndResolvedExecFuture(
|
$tree = XHPASTTree::newFromDataAndResolvedExecFuture(
|
||||||
|
@ -17,8 +22,7 @@ final class DivinerPHPAtomizer extends DivinerAtomizer {
|
||||||
|
|
||||||
$name = $func->getChildByIndex(2);
|
$name = $func->getChildByIndex(2);
|
||||||
|
|
||||||
$atom = id(new DivinerAtom())
|
$atom = $this->newAtom(DivinerAtom::TYPE_FUNCTION)
|
||||||
->setType('function')
|
|
||||||
->setName($name->getConcreteString())
|
->setName($name->getConcreteString())
|
||||||
->setLine($func->getLineNumber())
|
->setLine($func->getLineNumber())
|
||||||
->setFile($file_name);
|
->setFile($file_name);
|
||||||
|
@ -32,30 +36,29 @@ final class DivinerPHPAtomizer extends DivinerAtomizer {
|
||||||
}
|
}
|
||||||
|
|
||||||
$class_types = array(
|
$class_types = array(
|
||||||
'class' => 'n_CLASS_DECLARATION',
|
DivinerAtom::TYPE_CLASS => 'n_CLASS_DECLARATION',
|
||||||
'interface' => 'n_INTERFACE_DECLARATION',
|
DivinerAtom::TYPE_INTERFACE => 'n_INTERFACE_DECLARATION',
|
||||||
);
|
);
|
||||||
foreach ($class_types as $atom_type => $node_type) {
|
foreach ($class_types as $atom_type => $node_type) {
|
||||||
$class_decls = $root->selectDescendantsOfType($node_type);
|
$class_decls = $root->selectDescendantsOfType($node_type);
|
||||||
foreach ($class_decls as $class) {
|
foreach ($class_decls as $class) {
|
||||||
$name = $class->getChildByIndex(1, 'n_CLASS_NAME');
|
$name = $class->getChildByIndex(1, 'n_CLASS_NAME');
|
||||||
|
|
||||||
$atom = id(new DivinerAtom())
|
$atom = $this->newAtom($atom_type)
|
||||||
->setType($atom_type)
|
|
||||||
->setName($name->getConcreteString())
|
->setName($name->getConcreteString())
|
||||||
->setFile($file_name)
|
->setFile($file_name)
|
||||||
->setLine($class->getLineNumber());
|
->setLine($class->getLineNumber());
|
||||||
|
|
||||||
|
// TODO: Parse "abstract" and "final".
|
||||||
|
|
||||||
// If this exists, it is n_EXTENDS_LIST.
|
// If this exists, it is n_EXTENDS_LIST.
|
||||||
$extends = $class->getChildByIndex(2);
|
$extends = $class->getChildByIndex(2);
|
||||||
$extends_class = $extends->selectDescendantsOfType('n_CLASS_NAME');
|
$extends_class = $extends->selectDescendantsOfType('n_CLASS_NAME');
|
||||||
foreach ($extends_class as $parent_class) {
|
foreach ($extends_class as $parent_class) {
|
||||||
$atom->addExtends(
|
$atom->addExtends(
|
||||||
DivinerAtomRef::newFromDictionary(
|
$this->newRef(
|
||||||
array(
|
DivinerAtom::TYPE_CLASS,
|
||||||
'type' => 'class',
|
$parent_class->getConcreteString()));
|
||||||
'name' => $parent_class->getConcreteString(),
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this exists, it is n_IMPLEMENTS_LIST.
|
// If this exists, it is n_IMPLEMENTS_LIST.
|
||||||
|
@ -63,19 +66,16 @@ final class DivinerPHPAtomizer extends DivinerAtomizer {
|
||||||
$iface_names = $implements->selectDescendantsOfType('n_CLASS_NAME');
|
$iface_names = $implements->selectDescendantsOfType('n_CLASS_NAME');
|
||||||
foreach ($iface_names as $iface_name) {
|
foreach ($iface_names as $iface_name) {
|
||||||
$atom->addExtends(
|
$atom->addExtends(
|
||||||
DivinerAtomRef::newFromDictionary(
|
$this->newRef(
|
||||||
array(
|
DivinerAtom::TYPE_INTERFACE,
|
||||||
'type' => 'interface',
|
$iface_name->getConcreteString()));
|
||||||
'name' => $iface_name->getConcreteString(),
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->findAtomDocblock($atom, $class);
|
$this->findAtomDocblock($atom, $class);
|
||||||
|
|
||||||
$methods = $class->selectDescendantsOfType('n_METHOD_DECLARATION');
|
$methods = $class->selectDescendantsOfType('n_METHOD_DECLARATION');
|
||||||
foreach ($methods as $method) {
|
foreach ($methods as $method) {
|
||||||
$matom = id(new DivinerAtom())
|
$matom = $this->newAtom(DivinerAtom::TYPE_METHOD);
|
||||||
->setType('method');
|
|
||||||
|
|
||||||
$this->findAtomDocblock($matom, $method);
|
$this->findAtomDocblock($matom, $method);
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,8 @@ final class DivinerAtomController extends DivinerController {
|
||||||
id(new PhabricatorTagView())
|
id(new PhabricatorTagView())
|
||||||
->setType(PhabricatorTagView::TYPE_STATE)
|
->setType(PhabricatorTagView::TYPE_STATE)
|
||||||
->setBackgroundColor(PhabricatorTagView::COLOR_BLUE)
|
->setBackgroundColor(PhabricatorTagView::COLOR_BLUE)
|
||||||
->setName(DivinerAtom::getAtomTypeNameString($atom->getType())));
|
->setName(DivinerAtom::getAtomTypeNameString($atom->getType())))
|
||||||
|
->setSubheader($this->renderFullSignature($symbol));
|
||||||
|
|
||||||
$properties = id(new PhabricatorPropertyListView());
|
$properties = id(new PhabricatorPropertyListView());
|
||||||
|
|
||||||
|
@ -98,30 +99,20 @@ final class DivinerAtomController extends DivinerController {
|
||||||
->setSeverity(AphrontErrorView::SEVERITY_WARNING);
|
->setSeverity(AphrontErrorView::SEVERITY_WARNING);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$methods = $this->composeMethods($symbol);
|
||||||
|
|
||||||
$field = 'default';
|
$field = 'default';
|
||||||
$engine = id(new PhabricatorMarkupEngine())
|
$engine = id(new PhabricatorMarkupEngine())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->addObject($symbol, $field)
|
->addObject($symbol, $field);
|
||||||
->process();
|
foreach ($methods as $method) {
|
||||||
|
foreach ($method['atoms'] as $matom) {
|
||||||
$content = $engine->getOutput($symbol, $field);
|
$engine->addObject($matom, $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);
|
|
||||||
}
|
}
|
||||||
|
$engine->process();
|
||||||
|
|
||||||
|
$content = $this->renderDocumentationText($symbol, $engine);
|
||||||
|
|
||||||
$toc = $engine->getEngineMetadata(
|
$toc = $engine->getEngineMetadata(
|
||||||
$symbol,
|
$symbol,
|
||||||
|
@ -136,30 +127,9 @@ final class DivinerAtomController extends DivinerController {
|
||||||
->appendChild($warnings)
|
->appendChild($warnings)
|
||||||
->appendChild($content);
|
->appendChild($content);
|
||||||
|
|
||||||
$parameters = $atom->getProperty('parameters');
|
$document->appendChild($this->buildParametersAndReturn(array($symbol)));
|
||||||
if ($parameters !== null) {
|
|
||||||
$document->appendChild(
|
|
||||||
id(new PhabricatorHeaderView())
|
|
||||||
->setHeader(pht('Parameters')));
|
|
||||||
|
|
||||||
$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) {
|
if ($methods) {
|
||||||
|
|
||||||
$tasks = $this->composeTasks($symbol);
|
$tasks = $this->composeTasks($symbol);
|
||||||
|
|
||||||
if ($tasks) {
|
if ($tasks) {
|
||||||
|
@ -201,8 +171,9 @@ final class DivinerAtomController extends DivinerController {
|
||||||
id(new PhabricatorHeaderView())
|
id(new PhabricatorHeaderView())
|
||||||
->setHeader(pht('Methods')));
|
->setHeader(pht('Methods')));
|
||||||
foreach ($methods as $spec) {
|
foreach ($methods as $spec) {
|
||||||
|
$matom = last($spec['atoms']);
|
||||||
$method_header = id(new PhabricatorHeaderView())
|
$method_header = id(new PhabricatorHeaderView())
|
||||||
->setHeader(last($spec['atoms'])->getName());
|
->setHeader($matom->getName());
|
||||||
|
|
||||||
$inherited = $spec['inherited'];
|
$inherited = $spec['inherited'];
|
||||||
if ($inherited) {
|
if ($inherited) {
|
||||||
|
@ -213,7 +184,15 @@ final class DivinerAtomController extends DivinerController {
|
||||||
->setName(pht('Inherited')));
|
->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;
|
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