1
0
Fork 0
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:
epriestley 2013-08-30 09:17:27 -07:00
parent cf0bf34255
commit bf50e0f870
2 changed files with 225 additions and 62 deletions

View file

@ -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);

View file

@ -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;
}
}