src/SampleExtractor.php
<?php
/**
* SampleExtractor extracts samples from a given AST.
*
* PHP version 7
*
* @category Testing
* @package AaronHipple\Sampler
* @author Aaron Hipple <ahipple@gmail.com>
* @license https://github.com/aaronhipple/sampler/blob/master/LICENSE (MIT)
* @link https://github.com/aaronhipple/sampler
*/
namespace AaronHipple\Sampler;
use DOMDocument;
use Parsedown;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
use phpDocumentor\Reflection\DocBlockFactory;
/**
* SampleExtractor extracts samples from a given AST.
*
* @category Testing
* @package AaronHipple\Sampler
* @author Aaron Hipple <ahipple@gmail.com>
* @license https://github.com/aaronhipple/sampler/blob/master/LICENSE (MIT)
* @link https://github.com/aaronhipple/sampler
*/
class SampleExtractor extends NodeVisitorAbstract
{
/**
* Extracted samples.
*
* @var []string
*/
protected $samples = [];
/**
* Extract sample tests from certain nodes.
*
* @param Node $node A node in the AST.
*
* @return void
*/
public function leaveNode(Node $node)
{
if ($node instanceof Node\Stmt\Class_) {
$this->getClassSamples($node);
$this->getClassMethodSamples($node);
}
if ($node instanceof Node\Stmt\Function_) {
$this->getFunctionSamples($node);
}
}
/**
* Get samples from a given class.
*
* @param Node\Stmt\Class_ $class A 'class' node in the AST.
*
* @return void
*/
protected function getClassSamples(Node\Stmt\Class_ $class)
{
$classComment = $class->getDocComment();
if (!empty($classComment)) {
$classSamples = $this->extractSamples(
$classComment->getReformattedText()
);
array_walk(
$classSamples, function ($sample, $index) use ($class) {
$key = sprintf(
'%s #%d',
$class->namespacedName,
$index
);
$this->samples[$key] = $sample;
}
);
}
}
/**
* Get samples for each method of a given class.
*
* @param Node\Stmt\Class_ $class A 'class' node in the AST.
*
* @return void
*/
protected function getClassMethodSamples(Node\Stmt\Class_ $class)
{
foreach ($class->getMethods() as $method) {
$methodComment = $method->getDocComment();
if (empty($methodComment)) {
return;
}
$methodSamples = $this->extractSamples(
$methodComment->getReformattedText()
);
array_walk(
$methodSamples, function ($sample, $index) use ($class, $method) {
$key = sprintf(
'%s::%s #%d',
$class->namespacedName,
$method->name,
$index
);
$this->samples[$key] = $sample;
}
);
}
}
/**
* Get samples from a given function.
*
* @param Node\Stmt\function_ $function A 'function' node in the AST.
*
* @return void
*/
protected function getFunctionSamples(Node\Stmt\Function_ $function)
{
$functionComment = $function->getDocComment();
if (empty($functionComment)) {
return;
}
$samples = $this->extractSamples(
$functionComment->getReformattedText()
);
array_walk(
$samples, function ($sample, $index) use ($function) {
$key = sprintf(
'%s #%d',
$function->namespacedName,
$index
);
$this->samples[$key] = $sample;
}
);
}
/**
* Extract sample code tagged with `@sample`-style annotations.
*
* @param string $block A documentation block.
*
* @return []string
*/
protected function extractSamples($block)
{
if (!isset($this->_factory)) {
$this->_factory = DocBlockFactory::createInstance();
}
$docblock = $this->_factory->create($block);
$tags = $docblock->getTagsByName('sample');
$tagSamples = array_map(
function ($tag) {
return (string)$tag;
}, $tags
);
$codeSamples = $this->extractCodeSamples(
$docblock->getDescription()
);
return $tagSamples + $codeSamples;
}
/**
* Extract sample code from HTML and markdown blocks.
*
* @param string $description A documentation block description.
*
* @return []string
*/
protected function extractCodeSamples($description)
{
$parsed = Parsedown::instance()->text($description);
if (empty($parsed)) {
return [];
}
$dom = new DOMDocument();
$dom->loadHTML($parsed);
$samples = [];
foreach ($dom->getElementsByTagName('code') as $node) {
$classAttr = $node->attributes->getNamedItem('class');
// Take only unmarked or 'php' blocks.
if (is_null($classAttr) || $classAttr->value === 'language-php') {
$samples[] = $node->textContent;
}
}
return $samples;
}
/**
* Retrieve a list of extracted samples.
*
* @return []string
*/
public function getSamples()
{
return $this->samples;
}
}