public function process(XHPASTNode $root) { $parser = new PhutilDocblockParser(); $classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION'); foreach ($classes as $class) { $is_final = false; $is_abstract = false; $is_concrete_extensible = false; $attributes = $class->getChildOfType(0, 'n_CLASS_ATTRIBUTES'); foreach ($attributes->getChildren() as $child) { if ($child->getConcreteString() == 'final') { $is_final = true; } if ($child->getConcreteString() == 'abstract') { $is_abstract = true; } } $docblock = $class->getDocblockToken(); if ($docblock) { list($text, $specials) = $parser->parse($docblock->getValue()); $is_concrete_extensible = idx($specials, 'concrete-extensible'); } if (!$is_final && !$is_abstract && !$is_concrete_extensible) { $this->raiseLintAtNode($class->getChildOfType(1, 'n_CLASS_NAME'), pht('This class is neither `%s` nor `%s`, and does not have ' . 'a docblock marking it `%s`.', 'final', 'abstract', '@concrete-extensible')); } } }
public function setDocblockRaw($docblock_raw) { $this->docblockRaw = $docblock_raw; $parser = new PhutilDocblockParser(); list($text, $meta) = $parser->parse($docblock_raw); $this->docblockText = $text; $this->docblockMeta = $meta; return $this; }
private function parseDocblock($doc_file) { $contents = Filesystem::readFile($doc_file); $file = basename($doc_file); $parser = new PhutilDocblockParser(); list($docblock, $specials) = $parser->parse($contents); switch ($file) { case 'embedded-specials.docblock': $this->assertEqual(array(), $specials); $this->assertEqual("So long as a @special does not appear at the beginning of a line,\n" . "it is parsed as normal text.", $docblock); break; case 'indented-block.docblock': $this->assertEqual(array(), $specials); $this->assertEqual('Cozy lummox gives smart squid who asks for job pen.', $docblock); break; case 'indented-text.docblock': $this->assertEqual(array(), $specials); $this->assertEqual('Cozy lummox gives smart squid who asks for job pen.', $docblock); break; case 'multiline-special.docblock': $this->assertEqual(array('special' => 'x y z'), $specials); $this->assertEqual('', $docblock); break; case 'multi-specials.docblock': $this->assertEqual(array('special' => array('north', 'south'), 'stable' => true), $specials); $this->assertEqual('', $docblock); break; case 'specials.docblock': $this->assertEqual(array('type' => 'type', 'task' => 'task', 'special' => array('dot', 'dot', 'dash')), $specials); $this->assertEqual('', $docblock); break; case 'linebreak-breaks-specials.docblock': $this->assertEqual(array('title' => 'title'), $specials); $this->assertEqual('This is normal text, not part of the @title.', $docblock); break; case 'specials-with-hyphen.docblock': $this->assertEqual(array('repeat-hyphen' => array('a', 'b'), 'multiline-hyphen' => 'mmm nnn', 'normal-hyphen' => 'x'), $specials); break; case 'indented-specials.docblock': $this->assertEqual(array('title' => 'sendmail', 'special' => 'only a little bit indented'), $specials); break; case 'flag-specials.docblock': $this->assertEqual("stuff above\n\nstuff in the middle\n\nstuff below", $docblock); $this->assertEqual(array('flag' => true, 'stuff' => true, 'zebra' => true, 'apple' => true), $specials); break; case 'mixed-types.docblock': $this->assertEqual(array('special' => array('squirrels', true)), $specials); break; default: throw new Exception(pht("No test case to handle file '%s'!", $file)); } }
private function parseDocblock($doc_file) { $contents = Filesystem::readFile($doc_file); $file = basename($doc_file); $parser = new PhutilDocblockParser(); list($docblock, $specials) = $parser->parse($contents); switch ($file) { case 'embedded-specials.docblock': $this->assertEqual(array(), $specials); $this->assertEqual("So long as a @special does not appear at the beginning of a line,\n" . "it is parsed as normal text.", $docblock); break; case 'indented-block.docblock': $this->assertEqual(array(), $specials); $this->assertEqual("Cozy lummox gives smart squid who asks for job pen.", $docblock); break; case 'indented-text.docblock': $this->assertEqual(array(), $specials); $this->assertEqual("Cozy lummox gives smart squid who asks for job pen.", $docblock); break; case 'multiline-special.docblock': $this->assertEqual(array('special' => "x y z"), $specials); $this->assertEqual("", $docblock); break; case 'multi-specials.docblock': $this->assertEqual(array('special' => "north\nsouth"), $specials); $this->assertEqual("", $docblock); break; case 'specials.docblock': $this->assertEqual(array('type' => 'type', 'task' => 'task'), $specials); $this->assertEqual("", $docblock); break; case 'linebreak-breaks-specials.docblock': $this->assertEqual(array('title' => 'title'), $specials); $this->assertEqual("This is normal text, not part of the @title.", $docblock); break; case 'specials-with-hyphen.docblock': $this->assertEqual(array('repeat-hyphen' => "a\nb", 'multiline-hyphen' => "mmm nnn", 'normal-hyphen' => "x"), $specials); break; case 'indented-specials.docblock': $this->assertEqual(array('title' => 'sendmail'), $specials); break; case 'flag-specials.docblock': $this->assertEqual("stuff above\n\nstuff in the middle\n\nstuff below", $docblock); $this->assertEqual(array('flag' => true, 'stuff' => true, 'zebra' => true, 'apple' => true), $specials); break; default: throw new Exception("No test case to handle file '{$file}'!"); } }
$hash = md5($hash . $path . $resource_hash); $file_map[$path] = array('hash' => $hash, 'disk' => $path); } echo "\n"; $resource_graph = array(); $hash_map = array(); $parser = new PhutilDocblockParser(); foreach ($file_map as $path => $info) { $type = CelerityResourceTransformer::getResourceType($path); $data = Filesystem::readFile($root . $info['disk']); $matches = array(); $ok = preg_match('@/[*][*].*?[*]/@s', $data, $matches); if (!$ok) { throw new Exception("File {$path} does not have a header doc comment. Encode dependency " . "data in a header docblock."); } list($description, $metadata) = $parser->parse($matches[0]); $provides = preg_split('/\\s+/', trim(idx($metadata, 'provides'))); $requires = preg_split('/\\s+/', trim(idx($metadata, 'requires'))); $provides = array_filter($provides); $requires = array_filter($requires); if (!$provides) { // Tests and documentation-only JS is permitted to @provide no targets. continue; } if (count($provides) > 1) { throw new Exception("File {$path} must @provide at most one Celerity target."); } $provides = reset($provides); $uri = '/res/' . substr($info['hash'], 0, 8) . $path; $hash_map[$provides] = $info['hash']; $resource_graph[$provides] = $requires;
private function lintRaggedClasstreeEdges($root) { $parser = new PhutilDocblockParser(); $classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION'); foreach ($classes as $class) { $is_final = false; $is_abstract = false; $is_concrete_extensible = false; $attributes = $class->getChildOfType(0, 'n_CLASS_ATTRIBUTES'); foreach ($attributes->getChildren() as $child) { if ($child->getConcreteString() == 'final') { $is_final = true; } if ($child->getConcreteString() == 'abstract') { $is_abstract = true; } } $docblock = $class->getDocblockToken(); if ($docblock) { list($text, $specials) = $parser->parse($docblock->getValue()); $is_concrete_extensible = idx($specials, 'concrete-extensible'); } if (!$is_final && !$is_abstract && !$is_concrete_extensible) { $this->raiseLintAtNode($class->getChildOfType(1, 'n_CLASS_NAME'), self::LINT_RAGGED_CLASSTREE_EDGE, "This class is neither 'final' nor 'abstract', and does not have " . "a docblock marking it '@concrete-extensible'."); } } }
$requirements->addSourceDependency($name, $value); } else { if ($call_name == 'phutil_require_module') { analyze_phutil_require_module($call, $requirements, true); } } } } else { $has_files = true; $requirements->addSourceDeclaration(basename($file)); // Find symbols declared as "@phutil-external-symbol function example", // and ignore these in building dependency lists. $externals = array(); foreach ($root->getTokens() as $token) { if ($token->getTypeName() == 'T_DOC_COMMENT') { list($block, $special) = $doc_parser->parse($token->getValue()); $ext_list = idx($special, 'phutil-external-symbol'); $ext_list = explode("\n", $ext_list); $ext_list = array_filter($ext_list); foreach ($ext_list as $ext_ref) { $matches = null; if (preg_match('/^\\s*(\\S+)\\s+(\\S+)/', $ext_ref, $matches)) { $externals[$matches[1]][$matches[2]] = true; } } } } // Function uses: // - Explicit call // TODO?: String literal in ReflectionFunction(). $calls = $root->selectDescendantsOfType('n_FUNCTION_CALL');
/** * Parse the `@provides` and `@requires` symbols out of a text resource, like * JS or CSS. * * @param string Resource name. * @param string Resource data. * @return pair<string|null, list<string>|null> The `@provides` symbol and the * list of `@requires` symbols. If the resource is not part of the * dependency graph, both are null. */ private function getProvidesAndRequires($name, $data) { $parser = new PhutilDocblockParser(); $matches = array(); $ok = preg_match('@/[*][*].*?[*]/@s', $data, $matches); if (!$ok) { throw new Exception(pht('Resource "%s" does not have a header doc comment. Encode ' . 'dependency data in a header docblock.', $name)); } list($description, $metadata) = $parser->parse($matches[0]); $provides = preg_split('/\\s+/', trim(idx($metadata, 'provides'))); $requires = preg_split('/\\s+/', trim(idx($metadata, 'requires'))); $provides = array_filter($provides); $requires = array_filter($requires); if (!$provides) { // Tests and documentation-only JS is permitted to @provide no targets. return array(null, null); } if (count($provides) > 1) { throw new Exception(pht('Resource "%s" must @provide at most one Celerity target.', $name)); } return array(head($provides), $requires); }