diff --git a/scripts/symbols/generate_php_symbols.php b/scripts/symbols/generate_php_symbols.php index 4129af557..7e6ffbd1f 100755 --- a/scripts/symbols/generate_php_symbols.php +++ b/scripts/symbols/generate_php_symbols.php @@ -1,115 +1,115 @@ #!/usr/bin/env php limit(8); foreach ($futures as $file => $future) { $tree = XHPASTTree::newFromDataAndResolvedExecFuture( $data[$file], $future->resolve()); $root = $tree->getRootNode(); $scopes = array(); $functions = $root->selectDescendantsOfType('n_FUNCTION_DECLARATION'); foreach ($functions as $function) { $name = $function->getChildByIndex(2); // Skip anonymous functions if (!$name->getConcreteString()) { continue; } print_symbol($file, 'function', $name); } $classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION'); foreach ($classes as $class) { $class_name = $class->getChildByIndex(1); print_symbol($file, 'class', $class_name); $scopes[] = array($class, $class_name); } $interfaces = $root->selectDescendantsOfType('n_INTERFACE_DECLARATION'); foreach ($interfaces as $interface) { $interface_name = $interface->getChildByIndex(1); // We don't differentiate classes and interfaces in highlighters. print_symbol($file, 'class', $interface_name); $scopes[] = array($interface, $interface_name); } $constants = $root->selectDescendantsOfType('n_CONSTANT_DECLARATION_LIST'); foreach ($constants as $constant_list) { foreach ($constant_list->getChildren() as $constant) { $constant_name = $constant->getChildByIndex(0); print_symbol($file, 'constant', $constant_name); } } foreach ($scopes as $scope) { // this prints duplicate symbols in the case of nested classes // luckily, PHP doesn't allow those list($class, $class_name) = $scope; $consts = $class->selectDescendantsOfType( 'n_CLASS_CONSTANT_DECLARATION_LIST'); foreach ($consts as $const_list) { foreach ($const_list->getChildren() as $const) { $const_name = $const->getChildByIndex(0); print_symbol($file, 'class_const', $const_name, $class_name); } } $members = $class->selectDescendantsOfType( 'n_CLASS_MEMBER_DECLARATION_LIST'); foreach ($members as $member_list) { foreach ($member_list->getChildren() as $member) { if ($member->getTypeName() == 'n_CLASS_MEMBER_MODIFIER_LIST') { continue; } $member_name = $member->getChildByIndex(0); print_symbol($file, 'member', $member_name, $class_name); } } $methods = $class->selectDescendantsOfType('n_METHOD_DECLARATION'); foreach ($methods as $method) { $method_name = $method->getChildByIndex(2); print_symbol($file, 'method', $method_name, $class_name); } } } function print_symbol($file, $type, $token, $context = null) { $parts = array( $context ? $context->getConcreteString() : '', // variable tokens are `$name`, not just `name`, so strip the $ off of // class field names ltrim($token->getConcreteString(), '$'), $type, 'php', $token->getLineNumber(), '/'.ltrim($file, './'), ); echo implode(' ', $parts)."\n"; } diff --git a/src/applications/diviner/atomizer/DivinerPHPAtomizer.php b/src/applications/diviner/atomizer/DivinerPHPAtomizer.php index d6ddc16c8..62b63bf15 100644 --- a/src/applications/diviner/atomizer/DivinerPHPAtomizer.php +++ b/src/applications/diviner/atomizer/DivinerPHPAtomizer.php @@ -1,327 +1,327 @@ setLanguage('php'); } protected function executeAtomize($file_name, $file_data) { - $future = xhpast_get_parser_future($file_data); + $future = PhutilXHPASTBinary::getParserFuture($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`. $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`. $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`. $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 "%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], '/**')); } } } $atom->setDocblockRaw(''); return false; } } protected function parseParamDoc(DivinerAtom $atom, $doc, $name) { $dict = array(); $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 ..`, // 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 `%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( 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('/(?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); } } diff --git a/src/applications/phpast/controller/PhabricatorXHPASTViewRunController.php b/src/applications/phpast/controller/PhabricatorXHPASTViewRunController.php index fd4022b40..3e6e2b3fb 100644 --- a/src/applications/phpast/controller/PhabricatorXHPASTViewRunController.php +++ b/src/applications/phpast/controller/PhabricatorXHPASTViewRunController.php @@ -1,57 +1,57 @@ getRequest(); $user = $request->getUser(); if ($request->isFormPost()) { $source = $request->getStr('source'); - $future = xhpast_get_parser_future($source); + $future = PhutilXHPASTBinary::getParserFuture($source); $resolved = $future->resolve(); // This is just to let it throw exceptions if stuff is broken. $parse_tree = XHPASTTree::newFromDataAndResolvedExecFuture( $source, $resolved); list($err, $stdout, $stderr) = $resolved; $storage_tree = new PhabricatorXHPASTViewParseTree(); $storage_tree->setInput($source); $storage_tree->setStdout($stdout); $storage_tree->setAuthorPHID($user->getPHID()); $storage_tree->save(); return id(new AphrontRedirectResponse()) ->setURI('/xhpast/view/'.$storage_tree->getID().'/'); } $form = id(new AphrontFormView()) ->setUser($user) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Source') ->setName('source') ->setValue("setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Parse')); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Generate XHP AST')) ->setForm($form); return $this->buildApplicationPage( $form_box, array( 'title' => pht('XHPAST View'), )); } } diff --git a/src/infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php b/src/infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php index 15b6a3883..14e78c433 100644 --- a/src/infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php +++ b/src/infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php @@ -1,102 +1,102 @@ setName('extract') ->setSynopsis(pht('Extract translatable strings.')) ->setArguments( array( array( 'name' => 'paths', 'wildcard' => true, ), )); } public function execute(PhutilArgumentParser $args) { $console = PhutilConsole::getConsole(); $paths = $args->getArg('paths'); $futures = array(); foreach ($paths as $path) { $root = Filesystem::resolvePath($path); $path_files = id(new FileFinder($root)) ->withType('f') ->withSuffix('php') ->find(); foreach ($path_files as $file) { $full_path = $root.DIRECTORY_SEPARATOR.$file; $data = Filesystem::readFile($full_path); - $futures[$full_path] = xhpast_get_parser_future($data); + $futures[$full_path] = PhutilXHPASTBinary::getParserFuture($data); } } $console->writeOut( "%s\n", pht('Found %s file(s)...', new PhutilNumber(count($futures)))); $results = array(); $bar = id(new PhutilConsoleProgressBar()) ->setTotal(count($futures)); $futures = id(new FutureIterator($futures)) ->limit(8); foreach ($futures as $full_path => $future) { $bar->update(1); $tree = XHPASTTree::newFromDataAndResolvedExecFuture( Filesystem::readFile($full_path), $future->resolve()); $root = $tree->getRootNode(); $calls = $root->selectDescendantsOfType('n_FUNCTION_CALL'); foreach ($calls as $call) { $name = $call->getChildByIndex(0)->getConcreteString(); if ($name == 'pht') { $params = $call->getChildByIndex(1, 'n_CALL_PARAMETER_LIST'); $string_node = $params->getChildByIndex(0); $string_line = $string_node->getLineNumber(); try { $string_value = $string_node->evalStatic(); $results[$string_value][] = array( 'file' => Filesystem::readablePath($full_path), 'line' => $string_line, ); } catch (Exception $ex) { // TODO: Deal with this junks. } } } $tree->dispose(); } $bar->done(); ksort($results); $out = array(); $out[] = ' $locations) { foreach ($locations as $location) { $out[] = ' // '.$location['file'].':'.$location['line']; } $out[] = " '".addcslashes($string, "\0..\37\\'\177..\377")."' => null,"; $out[] = null; } $out[] = ');'; $out[] = null; echo implode("\n", $out); return 0; } }