Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2894012
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
13 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/applications/diviner/atomizer/DivinerAtomizer.php b/src/applications/diviner/atomizer/DivinerAtomizer.php
index 5ee949ad86..700399e256 100644
--- a/src/applications/diviner/atomizer/DivinerAtomizer.php
+++ b/src/applications/diviner/atomizer/DivinerAtomizer.php
@@ -1,77 +1,77 @@
<?php
/**
* Generate @{class:DivinerAtom}s from source code.
*/
abstract class DivinerAtomizer {
private $book;
private $fileName;
private $atomContext;
/**
- * If you make a significant change to an atomizer, you can bump this
- * version to drop all the old atom caches.
+ * If you make a significant change to an atomizer, you can bump this version
+ * to drop all the old atom caches.
*/
public static function getAtomizerVersion() {
return 1;
}
final public function atomize($file_name, $file_data, array $context) {
$this->fileName = $file_name;
$this->atomContext = $context;
$atoms = $this->executeAtomize($file_name, $file_data);
- // Promote the "@group" special to a property. If there's no "@group" on
+ // Promote the `@group` special to a property. If there's no `@group` on
// an atom but the file it's in matches a group pattern, associate it with
// the right group.
foreach ($atoms as $atom) {
$group = null;
try {
$group = $atom->getDocblockMetaValue('group');
} catch (Exception $ex) {
// There's no docblock metadata.
}
// If there's no group, but the file matches a group, use that group.
if ($group === null && isset($context['group'])) {
$group = $context['group'];
}
if ($group !== null) {
$atom->setProperty('group', $group);
}
}
return $atoms;
}
abstract protected function executeAtomize($file_name, $file_data);
final public function setBook($book) {
$this->book = $book;
return $this;
}
final public function getBook() {
return $this->book;
}
protected function newAtom($type) {
return id(new DivinerAtom())
->setBook($this->getBook())
->setFile($this->fileName)
->setType($type);
}
protected function newRef($type, $name, $book = null, $context = null) {
$book = coalesce($book, $this->getBook());
return id(new DivinerAtomRef())
->setBook($book)
->setContext($context)
->setType($type)
->setName($name);
}
}
diff --git a/src/applications/diviner/atomizer/DivinerPHPAtomizer.php b/src/applications/diviner/atomizer/DivinerPHPAtomizer.php
index 948baa4f2d..d6ddc16c86 100644
--- a/src/applications/diviner/atomizer/DivinerPHPAtomizer.php
+++ b/src/applications/diviner/atomizer/DivinerPHPAtomizer.php
@@ -1,323 +1,327 @@
<?php
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(
$file_data,
$future->resolve());
$atoms = array();
-
$root = $tree->getRootNode();
$func_decl = $root->selectDescendantsOfType('n_FUNCTION_DECLARATION');
foreach ($func_decl as $func) {
-
$name = $func->getChildByIndex(2);
// Don't atomize closures
if ($name->getTypeName() === 'n_EMPTY') {
continue;
}
$atom = $this->newAtom(DivinerAtom::TYPE_FUNCTION)
->setName($name->getConcreteString())
->setLine($func->getLineNumber())
->setFile($file_name);
$this->findAtomDocblock($atom, $func);
-
$this->parseParams($atom, $func);
$this->parseReturnType($atom, $func);
$atoms[] = $atom;
}
$class_types = array(
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 = $this->newAtom($atom_type)
->setName($name->getConcreteString())
->setFile($file_name)
->setLine($class->getLineNumber());
- // This parses "final" and "abstract".
+ // This parses `final` and `abstract`.
$attributes = $class->getChildByIndex(0, 'n_CLASS_ATTRIBUTES');
foreach ($attributes->selectDescendantsOfType('n_STRING') as $attr) {
$atom->setProperty($attr->getConcreteString(), true);
}
- // If this exists, it is n_EXTENDS_LIST.
+ // 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(
$this->newRef(
DivinerAtom::TYPE_CLASS,
$parent_class->getConcreteString()));
}
- // If this exists, it is n_IMPLEMENTS_LIST.
+ // If this exists, it is `n_IMPLEMENTS_LIST`.
$implements = $class->getChildByIndex(3);
$iface_names = $implements->selectDescendantsOfType('n_CLASS_NAME');
foreach ($iface_names as $iface_name) {
$atom->addExtends(
$this->newRef(
DivinerAtom::TYPE_INTERFACE,
$iface_name->getConcreteString()));
}
$this->findAtomDocblock($atom, $class);
$methods = $class->selectDescendantsOfType('n_METHOD_DECLARATION');
foreach ($methods as $method) {
$matom = $this->newAtom(DivinerAtom::TYPE_METHOD);
$this->findAtomDocblock($matom, $method);
$attribute_list = $method->getChildByIndex(0);
$attributes = $attribute_list->selectDescendantsOfType('n_STRING');
if ($attributes) {
foreach ($attributes as $attribute) {
$attr = strtolower($attribute->getConcreteString());
switch ($attr) {
case 'final':
case 'abstract':
case 'static':
$matom->setProperty($attr, true);
break;
case 'public':
case 'protected':
case 'private':
$matom->setProperty('access', $attr);
break;
}
}
} else {
$matom->setProperty('access', 'public');
}
$this->parseParams($matom, $method);
$matom->setName($method->getChildByIndex(2)->getConcreteString());
$matom->setLine($method->getLineNumber());
$matom->setFile($file_name);
$this->parseReturnType($matom, $method);
$atom->addChild($matom);
$atoms[] = $matom;
}
$atoms[] = $atom;
}
}
return $atoms;
}
private function parseParams(DivinerAtom $atom, AASTNode $func) {
$params = $func
->getChildByIndex(3, 'n_DECLARATAION_PARAMETER_LIST')
->selectDescendantsOfType('n_DECLARATION_PARAMETER');
$param_spec = array();
if ($atom->getDocblockRaw()) {
$metadata = $atom->getDocblockMeta();
} else {
$metadata = array();
}
$docs = idx($metadata, 'param');
if ($docs) {
$docs = explode("\n", $docs);
$docs = array_filter($docs);
} else {
$docs = array();
}
if (count($docs)) {
if (count($docs) < count($params)) {
$atom->addWarning(
pht(
'This call takes %d parameters, but only %d are documented.',
count($params),
count($docs)));
}
}
foreach ($params as $param) {
$name = $param->getChildByIndex(1)->getConcreteString();
$dict = array(
'type' => $param->getChildByIndex(0)->getConcreteString(),
'default' => $param->getChildByIndex(2)->getConcreteString(),
);
if ($docs) {
$doc = array_shift($docs);
if ($doc) {
$dict += $this->parseParamDoc($atom, $doc, $name);
}
}
$param_spec[] = array(
'name' => $name,
) + $dict;
}
if ($docs) {
foreach ($docs as $doc) {
if ($doc) {
$param_spec[] = $this->parseParamDoc($atom, $doc, null);
}
}
}
// TODO: Find `assert_instances_of()` calls in the function body and
// add their type information here. See T1089.
$atom->setProperty('parameters', $param_spec);
}
-
private function findAtomDocblock(DivinerAtom $atom, XHPASTNode $node) {
$token = $node->getDocblockToken();
if ($token) {
$atom->setDocblockRaw($token->getValue());
return true;
} else {
$tokens = $node->getTokens();
if ($tokens) {
$prev = head($tokens);
while ($prev = $prev->getPrevToken()) {
if ($prev->isAnyWhitespace()) {
continue;
}
break;
}
if ($prev && $prev->isComment()) {
$value = $prev->getValue();
$matches = null;
if (preg_match('/@(return|param|task|author)/', $value, $matches)) {
$atom->addWarning(
pht(
'Atom "%s" is preceded by a comment containing "@%s", but the '.
'comment is not a documentation comment. Documentation '.
- 'comments must begin with "/**", followed by a newline. Did '.
+ 'comments must begin with "%s", followed by a newline. Did '.
'you mean to use a documentation comment? (As the comment is '.
'not a documentation comment, it will be ignored.)',
$atom->getName(),
- $matches[1]));
+ $matches[1],
+ '/**'));
}
}
}
$atom->setDocblockRaw('');
return false;
}
}
protected function parseParamDoc(DivinerAtom $atom, $doc, $name) {
$dict = array();
- $split = preg_split('/\s+/', trim($doc), $limit = 2);
+ $split = preg_split('/\s+/', trim($doc), 2);
if (!empty($split[0])) {
$dict['doctype'] = $split[0];
}
if (!empty($split[1])) {
$docs = $split[1];
- // If the parameter is documented like "@param int $num Blah blah ..",
+ // If the parameter is documented like `@param int $num Blah blah ..`,
// get rid of the `$num` part (which Diviner considers optional). If it
// is present and different from the declared name, raise a warning.
$matches = null;
if (preg_match('/^(\\$\S+)\s+/', $docs, $matches)) {
if ($name !== null) {
if ($matches[1] !== $name) {
$atom->addWarning(
pht(
'Parameter "%s" is named "%s" in the documentation. The '.
'documentation may be out of date.',
$name,
$matches[1]));
}
}
$docs = substr($docs, strlen($matches[0]));
}
$dict['docs'] = $docs;
}
return $dict;
}
private function parseReturnType(DivinerAtom $atom, XHPASTNode $decl) {
$return_spec = array();
$metadata = $atom->getDocblockMeta();
$return = idx($metadata, 'return');
if (!$return) {
$return = idx($metadata, 'returns');
if ($return) {
$atom->addWarning(
- pht('Documentation uses `@returns`, but should use `@return`.'));
+ pht(
+ 'Documentation uses `%s`, but should use `%s`.',
+ '@returns',
+ '@return'));
}
}
if ($atom->getName() == '__construct' && $atom->getType() == 'method') {
$return_spec = array(
'doctype' => 'this',
'docs' => '//Implicit.//',
);
if ($return) {
$atom->addWarning(
- 'Method __construct() has explicitly documented @return. The '.
- '__construct() method always returns $this. Diviner documents '.
- 'this implicitly.');
+ pht(
+ 'Method %s has explicitly documented %s. The %s method always '.
+ 'returns %s. Diviner documents this implicitly.',
+ '__construct()',
+ '@return',
+ '__construct()',
+ '$this'));
}
} else if ($return) {
- $split = preg_split('/(?<!,)\s+/', trim($return), $limit = 2);
+ $split = preg_split('/(?<!,)\s+/', trim($return), 2);
if (!empty($split[0])) {
$type = $split[0];
}
if ($decl->getChildByIndex(1)->getTypeName() == 'n_REFERENCE') {
$type = $type.' &';
}
$docs = null;
if (!empty($split[1])) {
$docs = $split[1];
}
$return_spec = array(
'doctype' => $type,
'docs' => $docs,
);
} else {
$return_spec = array(
'type' => 'wild',
);
}
$atom->setProperty('return', $return_spec);
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Jan 19 2025, 19:14 (4 w, 6 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1127862
Default Alt Text
(13 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment