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'));
         }
     }
 }
Beispiel #2
0
 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}'!");
     }
 }
Beispiel #5
0
    $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'.");
         }
     }
 }
Beispiel #7
0
             $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);
 }