Page MenuHomePhabricator

D9792.id.diff
No OneTemporary

D9792.id.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -5,7 +5,6 @@
* @generated
* @phutil-library-version 2
*/
-
phutil_register_library_map(array(
'__library_version__' => 2,
'class' =>
@@ -617,6 +616,7 @@
'DivinerPHIDTypeAtom' => 'applications/diviner/phid/DivinerPHIDTypeAtom.php',
'DivinerPHIDTypeBook' => 'applications/diviner/phid/DivinerPHIDTypeBook.php',
'DivinerPHPAtomizer' => 'applications/diviner/atomizer/DivinerPHPAtomizer.php',
+ 'DivinerPHPAtomizerTestCase' => 'applications/diviner/atomizer/__tests__/DivinerPHPAtomizerTestCase.php',
'DivinerParameterTableView' => 'applications/diviner/view/DivinerParameterTableView.php',
'DivinerPublishCache' => 'applications/diviner/cache/DivinerPublishCache.php',
'DivinerPublisher' => 'applications/diviner/publisher/DivinerPublisher.php',
@@ -3316,6 +3316,7 @@
'DivinerPHIDTypeAtom' => 'PhabricatorPHIDType',
'DivinerPHIDTypeBook' => 'PhabricatorPHIDType',
'DivinerPHPAtomizer' => 'DivinerAtomizer',
+ 'DivinerPHPAtomizerTestCase' => 'ArcanistPhutilTestCase',
'DivinerParameterTableView' => 'AphrontTagView',
'DivinerPublishCache' => 'DivinerDiskCache',
'DivinerRemarkupRuleSymbol' => 'PhutilRemarkupRule',
diff --git a/src/applications/diviner/atomizer/DivinerPHPAtomizer.php b/src/applications/diviner/atomizer/DivinerPHPAtomizer.php
--- a/src/applications/diviner/atomizer/DivinerPHPAtomizer.php
+++ b/src/applications/diviner/atomizer/DivinerPHPAtomizer.php
@@ -13,12 +13,61 @@
$file_data,
$future->resolve());
- $atoms = array();
-
$root = $tree->getRootNode();
+ $ns_declarations = $root->selectDescendantsOfType('n_NAMESPACE');
+
+ if (!count($ns_declarations)) {
+ return $this->atomizeNodeInNSRange($file_name, $root, '\\',
+ array('start' => 0, 'end' => 0));
+ }
+
+ $atom_vectors = array();
+
+ $ranges = array();
+ $prev_ns = '';
+ foreach ($ns_declarations as $ns_declaration) {
+ // Always prefix to root namespace
+ $ns = '\\'.$ns_declaration->getChildByIndex(0)->getConcreteString();
+ $ranges[$ns] = array('start' => $ns_declaration->getID(), 'end' => 0);;
+ if ($prev_ns) {
+ $ranges[$prev_ns]['end'] = $ns_declaration->getID() - 1;
+ }
+ $prev_ns = $ns;
+ }
+ foreach ($ranges as $ns => $range) {
+ $atom_vectors[] = $this->atomizeNodeInNSRange($file_name, $root, $ns, $range);
+ }
+
+ return array_mergev($atom_vectors);
+ }
+
+ /**
+ * Check if a given XHPASTNode falls within an ID range
+ * @param XHPASTNode Node to check
+ * @param int start of range (inclusive)
+ * @param int end of range (inclusive)
+ * @retun bool True if node is in range
+ */
+ private function isNodeInRange(XHPASTNode $node, $start, $end) {
+ if ($start && $node->getID() <= $start) {
+ return false;
+ }
+ if ($end && $node->getID() >= $end) {
+ return false;
+ }
+ return true;
+ } // isNodeInRange
+
+ private function atomizeNodeInNSRange($file_name, XHPASTNode $root, $namespace, array $range) {
+ $start = $range['start'];
+ $end = $range['end'];
+ $atoms = array();
$func_decl = $root->selectDescendantsOfType('n_FUNCTION_DECLARATION');
foreach ($func_decl as $func) {
+ if (!$this->isNodeInRange($func, $start, $end)) {
+ continue;
+ }
$name = $func->getChildByIndex(2);
@@ -28,6 +77,7 @@
}
$atom = $this->newAtom(DivinerAtom::TYPE_FUNCTION)
+ ->setContext($namespace)
->setName($name->getConcreteString())
->setLine($func->getLineNumber())
->setFile($file_name);
@@ -47,9 +97,13 @@
foreach ($class_types as $atom_type => $node_type) {
$class_decls = $root->selectDescendantsOfType($node_type);
foreach ($class_decls as $class) {
+ if (!$this->isNodeInRange($class, $start, $end)) {
+ continue;
+ }
$name = $class->getChildByIndex(1, 'n_CLASS_NAME');
$atom = $this->newAtom($atom_type)
+ ->setContext($namespace)
->setName($name->getConcreteString())
->setFile($file_name)
->setLine($class->getLineNumber());
@@ -112,6 +166,12 @@
$this->parseParams($matom, $method);
+ $context = $namespace;
+ if (strlen($context) > 1) {
+ $context .= '\\';
+ }
+ $context .= $name->getConcreteString();
+ $matom->setContext($context);
$matom->setName($method->getChildByIndex(2)->getConcreteString());
$matom->setLine($method->getLineNumber());
$matom->setFile($file_name);
diff --git a/src/applications/diviner/atomizer/__tests__/DivinerPHPAtomizerTestCase.php b/src/applications/diviner/atomizer/__tests__/DivinerPHPAtomizerTestCase.php
new file mode 100644
--- /dev/null
+++ b/src/applications/diviner/atomizer/__tests__/DivinerPHPAtomizerTestCase.php
@@ -0,0 +1,82 @@
+<?php
+
+class DivinerPHPAtomizerTestCase extends ArcanistPhutilTestCase {
+
+ public function testBracketNamespaceAtomizer() {
+ $php_atomizer = new DivinerPHPAtomizer();
+ $file = dirname(__FILE__).'/data/NamespaceBrackets.phps';
+ $atoms = $php_atomizer->atomize($file, file_get_contents($file), array());
+
+ $this->checkAtoms($atoms, array(
+ array(DivinerAtom::TYPE_CLASS, '\Test1', 'Test'),
+ array(DivinerAtom::TYPE_METHOD, '\Test1\Test', 'testMethod'),
+ array(DivinerAtom::TYPE_FUNCTION, '\Test1', 'test'),
+ array(DivinerAtom::TYPE_CLASS, '\Test2', 'Test'),
+ array(DivinerAtom::TYPE_METHOD, '\Test2\Test', 'testMethod'),
+ array(DivinerAtom::TYPE_FUNCTION, '\Test2', 'test'),
+ array(DivinerAtom::TYPE_CLASS, '\\', 'Test'),
+ array(DivinerAtom::TYPE_METHOD, '\Test', 'testMethod'),
+ array(DivinerAtom::TYPE_FUNCTION, '\\', 'test'),
+ ));
+ }
+
+ public function testSemicolonNamespaceAtomizer() {
+ $php_atomizer = new DivinerPHPAtomizer();
+ $file = dirname(__FILE__).'/data/NamespaceSemicolons.phps';
+ $atoms = $php_atomizer->atomize($file, file_get_contents($file), array());
+
+ $this->checkAtoms($atoms, array(
+ array(DivinerAtom::TYPE_CLASS, '\Test1', 'Test'),
+ array(DivinerAtom::TYPE_METHOD, '\Test1\Test', 'testMethod'),
+ array(DivinerAtom::TYPE_FUNCTION, '\Test1', 'test'),
+ array(DivinerAtom::TYPE_CLASS, '\Test2', 'Test'),
+ array(DivinerAtom::TYPE_METHOD, '\Test2\Test', 'testMethod'),
+ array(DivinerAtom::TYPE_FUNCTION, '\Test2', 'test'),
+ ));
+ }
+
+ public function testNoNamespaceAtomizer() {
+ $php_atomizer = new DivinerPHPAtomizer();
+ $file = dirname(__FILE__).'/data/NoNamespace.phps';
+ $atoms = $php_atomizer->atomize($file, file_get_contents($file), array());
+
+ $this->checkAtoms($atoms, array(
+ array(DivinerAtom::TYPE_CLASS, '\\', 'Test'),
+ array(DivinerAtom::TYPE_METHOD, '\Test', 'testMethod'),
+ array(DivinerAtom::TYPE_FUNCTION, '\\', 'test'),
+ ));
+ }
+
+ /**
+ * Helper method to test for the presence of atom specs out of the unsorted
+ * return value of the atomizer
+ *
+ * @param array Raw atomizer output array
+ * @param array List of atom specs
+ */
+ private function checkAtoms(array $atoms, array $spec) {
+ foreach ($atoms as $atom) {
+ $this->assertTrue($atom instanceof DivinerAtom,
+ "An atom was not an instace of DivinerAtom");
+ }
+ // This approach is roughly O(n^2), but n stays fairly small so it
+ // shouldn't be too bad in practice
+ foreach ($spec as $expected_atom) {
+ foreach ($atoms as $i => $atom) {
+ if ($atom->getType() !== $expected_atom[0]) {
+ continue;
+ }
+ if ($atom->getContext() !== $expected_atom[1]) {
+ continue;
+ }
+ if ($atom->getName() !== $expected_atom[2]) {
+ continue;
+ }
+ // It's a match
+ unset($atoms[$i]);
+ }
+ }
+ $this->assertEqual(0, count($atoms), "All atoms should have been found");
+ }
+
+}
diff --git a/src/applications/diviner/atomizer/__tests__/data/NamespaceBrackets.phps b/src/applications/diviner/atomizer/__tests__/data/NamespaceBrackets.phps
new file mode 100644
--- /dev/null
+++ b/src/applications/diviner/atomizer/__tests__/data/NamespaceBrackets.phps
@@ -0,0 +1,49 @@
+<?php
+
+namespace Test1 {
+
+ class Test {
+
+ public function testMethod() {
+ return;
+ }
+
+ }
+
+ function test() {
+ return;
+ }
+
+}
+
+namespace Test2 {
+
+ class Test {
+
+ public function testMethod() {
+ return;
+ }
+
+ }
+
+ function test() {
+ return;
+ }
+
+}
+
+namespace {
+
+ class Test {
+
+ public function testMethod() {
+ return;
+ }
+
+ }
+
+ function test() {
+ return;
+ }
+
+}
\ No newline at end of file
diff --git a/src/applications/diviner/atomizer/__tests__/data/NamespaceSemicolons.phps b/src/applications/diviner/atomizer/__tests__/data/NamespaceSemicolons.phps
new file mode 100644
--- /dev/null
+++ b/src/applications/diviner/atomizer/__tests__/data/NamespaceSemicolons.phps
@@ -0,0 +1,29 @@
+<?php
+
+namespace Test1;
+
+class Test {
+
+ public function testMethod() {
+ return;
+ }
+
+}
+
+function test() {
+ return;
+}
+
+namespace Test2;
+
+class Test {
+
+ public function testMethod() {
+ return;
+ }
+
+}
+
+function test() {
+ return;
+}
diff --git a/src/applications/diviner/atomizer/__tests__/data/NoNamespace.phps b/src/applications/diviner/atomizer/__tests__/data/NoNamespace.phps
new file mode 100644
--- /dev/null
+++ b/src/applications/diviner/atomizer/__tests__/data/NoNamespace.phps
@@ -0,0 +1,13 @@
+<?php
+
+class Test {
+
+ public function testMethod() {
+ return;
+ }
+
+}
+
+function test() {
+ return;
+}

File Metadata

Mime Type
text/plain
Expires
Oct 1 2025, 11:07 PM (11 w, 5 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
8708392
Default Alt Text
D9792.id.diff (9 KB)

Event Timeline