Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F18734999
D9792.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
9 KB
Referenced Files
None
Subscribers
None
D9792.id.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D9792: Add namespace support in Diviner's PHP atomizer
Attached
Detach File
Event Timeline
Log In to Comment