Skip to content

Commit

Permalink
processing includes as proposed in maglnet#67
Browse files Browse the repository at this point in the history
  • Loading branch information
Idrinth committed Jul 15, 2018
1 parent 9580aa6 commit 5bb2a07
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 7 deletions.
45 changes: 45 additions & 0 deletions src/ComposerRequireChecker/ASTLocator/FileAST.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace ComposerRequireChecker\ASTLocator;

use Traversable;

class FileAST
{
/**
* @var string
*/
private $file;

/**
* @var Traversable
*/
private $ast;

/**
* @param string $file
* @param Traversable|array|null $ast
*
*/
public function __construct(string $file, $ast)
{
$this->file = $file;
$this->ast = $ast;
}

/**
* @return string
*/
public function getFile()
{
return $this->file;
}

/**
* @return Traversable|array
*/
public function getAst()
{
return $this->ast;
}
}
4 changes: 2 additions & 2 deletions src/ComposerRequireChecker/ASTLocator/LocateASTFromFiles.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ public function __construct(Parser $parser, ?ErrorHandler $errorHandler)
/**
* @param Traversable|string[] $files
*
* @return Traversable|array[] a series of AST roots, one for each given file
* @return Traversable|FileAST[] a series of AST roots, one for each given file
*/
public function __invoke(Traversable $files): Traversable
{
foreach ($files as $file) {
yield $this->parser->parse(file_get_contents($file), $this->errorHandler);
yield new FileAST($file, $this->parser->parse(file_get_contents($file), $this->errorHandler));
}
}
}
6 changes: 5 additions & 1 deletion src/ComposerRequireChecker/Cli/CheckCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,16 @@ protected function execute(InputInterface $input, OutputInterface $output): int

$sourcesASTs = $this->getASTFromFilesLocator($input);

$definedVendorSymbols = (new LocateDefinedSymbolsFromASTRoots())->__invoke($sourcesASTs(
list($definedVendorSymbols, $additionalFiles) = (new LocateDefinedSymbolsFromASTRoots())->__invoke($sourcesASTs(
(new ComposeGenerators())->__invoke(
$getPackageSourceFiles($composerJson),
(new LocateComposerPackageDirectDependenciesSourceFiles())->__invoke($composerJson)
)
));
while ($additionalFiles && count($additionalFiles)) {
list($vendorSymbols, $additionalFiles) = (new LocateDefinedSymbolsFromASTRoots())->__invoke($sourcesASTs($additionalFiles));
$definedVendorSymbols = array_unique(array_merge($vendorSymbols, $definedVendorSymbols));
}

$definedExtensionSymbols = (new LocateDefinedSymbolsFromExtensions())->__invoke(
(new DefinedExtensionsResolver())->__invoke($composerJson, $options->getPhpCoreExtensions())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace ComposerRequireChecker\DefinedSymbolsLocator;

use ArrayIterator;
use ComposerRequireChecker\NodeVisitor\DefinedSymbolCollector;
use ComposerRequireChecker\NodeVisitor\IncludeCollector;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor\NameResolver;
use Traversable;
Expand All @@ -21,15 +23,17 @@ public function __invoke(Traversable $ASTs): array

$traverser->addVisitor(new NameResolver());
$traverser->addVisitor($collector = new DefinedSymbolCollector());
$traverser->addVisitor($includes = new IncludeCollector());

$astSymbols = [];
$additionalFiles = [];

foreach ($ASTs as $astRoot) {
$traverser->traverse($astRoot);

$traverser->traverse($astRoot->getAst());
$astSymbols[] = $collector->getDefinedSymbols();
$additionalFiles = array_merge($additionalFiles, $includes->getIncluded($astRoot->getFile()));
}

return array_values(array_unique(array_merge([], ...$astSymbols)));
return [array_values(array_unique(array_merge([], ...$astSymbols))), new ArrayIterator($additionalFiles)];
}
}
129 changes: 129 additions & 0 deletions src/ComposerRequireChecker/NodeVisitor/IncludeCollector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?php

namespace ComposerRequireChecker\NodeVisitor;

use FilesystemIterator;
use InvalidArgumentException;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\BinaryOp\Concat;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\Include_;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Scalar\MagicConst\Dir;
use PhpParser\Node\Scalar\MagicConst\File;
use PhpParser\Node\Scalar\String_;
use PhpParser\NodeVisitorAbstract;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;

final class IncludeCollector extends NodeVisitorAbstract
{
/**
* @var Expr[]
*/
private $included = [];

/**
* {@inheritDoc}
*/
public function beforeTraverse(array $nodes)
{
$this->included = [];
return parent::beforeTraverse($nodes);
}

/**
* @param string $file
* @return string[]
*/
public function getIncluded(string $file): array
{
$included = [];
foreach ($this->included as $exp) {
try {
$this->computePath($included, $this->processIncludePath($exp, $file), $file);
} catch(InvalidArgumentException $x) {
var_dump($x->getMessage());
}
}
return $included;
}

/**
* @param array $included
* @param string $path
* @param string $self
* @return void
*/
private function computePath(array &$included, string $path, string $self)
{
if (!preg_match('#^([A-Z]:)?/#i', str_replace('\\', '/', $path))) {
$path = dirname($self).'/'.$path;
}
if (false === strpos($path, '{var}')) {
$included[] = $path;
return;
}
$parts = explode('{var}', $path);
$regex = [];
foreach($parts as $part) {
$regex[] = preg_quote(str_replace('\\', '/', $part), '/');
}
$regex = '/^'.implode('.+', $regex).'$/';
$self = str_replace('\\', '/', $self);
foreach (new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(
$parts[0],
FilesystemIterator::CURRENT_AS_PATHNAME | FilesystemIterator::SKIP_DOTS
)
) as $file) {
$rfile = str_replace('\\', '/', $file);
if ($rfile !== $self && preg_match('/\\.php$/i', $rfile) && preg_match($regex, $rfile)) {
$included[] = $file;
}
}
}

/**
* @param string|Exp $exp
* @param string $file
* @return string
* @throws InvalidArgumentException
*/
private function processIncludePath($exp, string $file): string
{
if (is_string($exp)) {
return $exp;
}
if ($exp instanceof Concat) {
return $this->processIncludePath($exp->left, $file).$this->processIncludePath($exp->right, $file);
}
if ($exp instanceof Dir) {
return dirname($file);
}
if ($exp instanceof File) {
return $file;
}
if ($exp instanceof ConstFetch && $exp->name === 'DIRECTORY_SEPARATOR') {
return DIRECTORY_SEPARATOR;
}
if ($exp instanceof String_) {
return $exp->value;
}
if ($exp instanceof Variable || $exp instanceof ConstFetch) {
return '{var}';
}
throw new InvalidArgumentException('can\'t yet handle '.$exp->getType());
}

/**
* {@inheritDoc}
*/
public function enterNode(Node $node)
{
if ($node instanceof Include_) {
$this->included[] = $node->expr;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public function __invoke(Traversable $ASTs): array
$astSymbols = [];

foreach ($ASTs as $astRoot) {
$traverser->traverse($astRoot);
$traverser->traverse($astRoot->getAst());

$astSymbols[] = $collector->getCollectedSymbols();
}
Expand Down

0 comments on commit 5bb2a07

Please sign in to comment.