Ejemplo n.º 1
0
 public static function addSourcesToProject(InputInterface $input, Project $project)
 {
     $inputSources = [];
     if (NULL !== $input->getOption('load-from')) {
         $inputSources = array_merge($inputSources, array_map('trim', file($input->getOption('load-from'))));
     }
     if (NULL !== $input->getArgument('sources')) {
         $inputSources = array_merge($inputSources, $input->getArgument('sources'));
     }
     $inputSources = array_unique($inputSources);
     if (empty($inputSources)) {
         throw new \RuntimeException('No input sources/directories for scanning provided.');
     }
     /** @var \SplFileInfo[] $files */
     $files = [];
     foreach ($inputSources as $inputSource) {
         if (is_dir($inputSource)) {
             $files = array_merge($files, Util::scanDir($inputSource));
         } else {
             if (is_file($inputSource)) {
                 $files[] = new \SplFileInfo($inputSource);
             } else {
                 throw new \RuntimeException("File {$inputSource} is not a file nor a directory.");
             }
         }
     }
     if (count($files) === 0) {
         throw new \RuntimeException('No source files to scan found');
     }
     foreach ($files as $file) {
         $project->addSplFileInfo($file);
     }
 }
Ejemplo n.º 2
0
 public function main()
 {
     if (NULL === $this->filesets) {
         throw new \BuildException('No fileset provided');
     }
     $project = new Project(new Phing($this));
     $project->addListener($listener = new PhingListener($this));
     $analyzers = NULL !== $this->getConfigFile() ? require $this->getConfigFile() : Project::getDefaultConfig();
     $project->addAnalyzers($analyzers);
     # Add files
     foreach ($this->filesets as $fs) {
         $ds = $fs->getDirectoryScanner($this->project);
         /** @var \PhingFile $fromDir */
         $fromDir = $fs->getDir($this->project);
         /** @var  $files */
         $files = $ds->getIncludedFiles();
         foreach ($files as $file) {
             $fileName = $fromDir->getAbsolutePath() . DIRECTORY_SEPARATOR . $file;
             $this->log('Adding file ' . $fileName, \Project::MSG_VERBOSE);
             $project->addSplFileInfo(new \SplFileInfo($fileName));
         }
     }
     $project->analyze();
     $buildErrorMessage = $listener->getBuildErrorMessage();
     if ($this->haltonerror && !empty($buildErrorMessage)) {
         throw new \BuildException($buildErrorMessage);
     }
 }
Ejemplo n.º 3
0
 /**
  * @param Project $project
  */
 public function analyze(Project $project)
 {
     $traverser = new NodeTraverser();
     $traverser->addVisitor(new PhpParserNameResolver());
     foreach ($project->getFiles() as $file) {
         $traverser->traverse($file->getTree());
     }
 }
Ejemplo n.º 4
0
 public function projectEnd(Project $project)
 {
     if ($this->logfileWriter && 0 === strpos($this->logFormat, 'json')) {
         $options = $this->logFormat === 'json-pretty' ? JSON_PRETTY_PRINT : 0;
         $formatter = new Json($options);
         /** @noinspection PhpParamsInspection */
         $this->logfileWriter->write($formatter->formatReports($project->getAnalyzerReports()));
     }
 }
Ejemplo n.º 5
0
 /**
  * @param Project $project
  */
 public function analyze(Project $project)
 {
     foreach ($this->graph->getObjects() as $object) {
         if ($object instanceof ParsedInterface) {
             foreach ($object->getMethods() as $method) {
                 if ($method->getMethod()->isAbstract()) {
                     $report = new StringReport('Access type for interface method ' . $object->getName() . '::' . $method->getNameAndParamsSignature() . ' must be ommmited in ' . $object->getFile()->getSplFile()->getRealPath() . ':' . $object->getInterface()->getLine());
                     $report->setSourceFragment(new SourceFragment($object->getFile(), new Lines($method->getMethod()->getAttribute('startLine') - 1 - $this->sourceContext, $method->getMethod()->getAttribute('endLine') - 1 + $this->sourceContext, $method->getMethod()->getAttribute('startLine') - 1)));
                     $project->addReport($report);
                 }
             }
         }
     }
 }
Ejemplo n.º 6
0
 /**
  * @param Project $project
  */
 public function analyze(Project $project)
 {
     foreach ($this->graph->getObjects() as $object) {
         if ($object instanceof ParsedInterface) {
             foreach ($object->getMethods() as $method) {
                 $methodCompare = new MethodSignatureCompare($method);
                 foreach ($this->checkInterfaceMethods($methodCompare, $this->helper->findInterfaceImplements($object)) as $brokenInterfaceAndMethod) {
                     $report = new Report($brokenInterfaceAndMethod, $method);
                     $report->setSourceFragment(new SourceFragment($object->getFile(), new Lines($method->getMethod()->getAttribute('startLine') - 1 - $this->sourceContext, $method->getMethod()->getAttribute('endLine') - 1 + $this->sourceContext, $method->getMethod()->getAttribute('startLine') - 1)));
                     $project->addReport($report);
                 }
             }
         }
     }
 }
Ejemplo n.º 7
0
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     $logger = new SymfonyConsoleOutput($output);
     $project = new Project($logger);
     if ($input->getOption('quiet')) {
         $logger->setReportingLevel(Logger::ERROR);
     } else {
         if ($input->getOption('verbose')) {
             $logger->setReportingLevel(Logger::INFO);
         }
     }
     # Fall back to UTC if date.timezone is not set
     $dateTimezone = ini_get('date.timezone');
     if (empty($dateTimezone)) {
         $logger->warning('date.timezone not set, falling back to UTC');
         date_default_timezone_set('UTC');
     }
     SourceHandler::addSourcesToProject($input, $project);
     $analyzers = NULL !== $input->getOption('config') ? require $input->getOption('config') : Project::getDefaultConfig();
     # Configure listener
     $listener = NULL;
     if (NULL !== $input->getOption('report')) {
         $format = NULL === $input->getOption('format') ? 'plain' : $input->getOption('format');
         if ('plain' === $format) {
             $listener = new Plain(fopen($input->getOption('report'), 'w'));
         } else {
             if (0 === strpos($format, 'json')) {
                 $options = 0;
                 if ('json-pretty' === $format) {
                     $options = JSON_PRETTY_PRINT;
                 }
                 $json = new JsonFormatter($options);
                 $listener = new Json(fopen($input->getOption('report'), 'w'), $json);
             } else {
                 throw new \RuntimeException("Unsupported report format '{$format}'");
             }
         }
     } else {
         if (!$input->getOption('quiet')) {
             $listener = new Plain(\STDOUT);
         }
     }
     if (NULL !== $listener) {
         $project->addListener($listener);
     }
     $project->addAnalyzers($analyzers);
     $project->analyze();
     $analyzerReports = $project->getAnalyzerReports();
     if (count($analyzerReports) > 0) {
         return 1;
     } else {
         $output->writeln('Nothing found to report.');
         return 0;
     }
 }
Ejemplo n.º 8
0
 /**
  * Called when entering a node.
  *
  * Return value semantics:
  *  * null:      $node stays as-is
  *  * otherwise: $node is set to the return value
  *
  * @param Node $node Node
  *
  * @return null|Node Node
  */
 public function enterNode(Node $node)
 {
     if ($node instanceof Node\Stmt\Catch_) {
         $this->subNodeTraverser->traverse($node->stmts);
         $nonComments = $this->nonCommentCounterVisitor->getNonCommentStatements();
         if (0 === $nonComments) {
             $report = new StringReport('Empty catch block found');
             $line = $node->getAttribute('startLine') - 1;
             $report->setSourceFragment(new SourceFragment($this->currentFile, new Lines($line - $this->sourceContext, $line + $this->sourceContext, $line)));
             $this->project->addReport($report);
         }
     }
 }
Ejemplo n.º 9
0
 /**
  * Called when entering a node.
  *
  * Return value semantics:
  *  * null:      $node stays as-is
  *  * otherwise: $node is set to the return value
  *
  * @param Node $node Node
  *
  * @return null|Node Node
  */
 public function enterNode(Node $node)
 {
     if ($node instanceof New_) {
         if ($node->class instanceof Variable) {
             $msg = 'Dynamic class instantiation with variable ';
             if ($node->class->name instanceof Variable) {
                 $msg .= 'variable $' . $node->class->name->name;
             } else {
                 $msg .= '$' . $node->class->name;
             }
             $report = new StringReport($msg . ' in ' . $this->currentFile->getSplFile()->getFilename() . ':' . $node->class->getLine());
             $report->setSourceFragment(new SourceFragment($this->currentFile, new Lines($node->class->getAttribute('startLine') - 1 - $this->sourceContext, $node->class->getAttribute('endLine') - 1 + $this->sourceContext, $node->class->getAttribute('startLine') - 1)));
             $this->project->addReport($report);
         }
     }
 }
Ejemplo n.º 10
0
 /**
  * @param Project $project
  */
 public function analyze(Project $project)
 {
     $this->project = $project;
     $traverser = new NodeTraverser();
     $traverser->addVisitor($this);
     foreach ($project->getFiles() as $file) {
         $this->currentFile = $file;
         $traverser->traverse($file->getTree());
         foreach ($this->variables as $variable) {
             $report = new StringReport('Variable used in constructing raw SQL, is it escaped?');
             $line = $variable->getAttribute('startLine') - 1;
             $report->setSourceFragment(new SourceFragment($file, new Lines($line - $this->sourceContext, $line + $this->sourceContext, $line)));
             $project->addReport($report);
         }
     }
 }
Ejemplo n.º 11
0
 /**
  * @param Project $project
  * @return \string[]
  */
 public function analyze(Project $project)
 {
     $graph = $this->objectGraph;
     /** @var ParsedClass[] $classesMissingMethod */
     $classesMissingMethods = new Map(function ($key) {
         if ($key instanceof ParsedClass) {
             return;
         }
         throw new MapException('Only keys of type Class_ are accepted');
     }, function ($value) {
         if (is_array($value)) {
             return;
         }
         throw new MapException('Only values of type array are accepted');
     });
     # scan all objects (we're actually only interested in classes)
     foreach ($graph->getObjects() as $object) {
         if ($object instanceof ParsedClass) {
             foreach ($object->getMethods() as $method) {
                 # and see if they've abstract methods
                 if ($method->getMethod()->isAbstract()) {
                     $methodName = $method->getNormalizedName();
                     # now find all descendant classes and see if they've implemented it
                     $classesMissingMethod = $this->findSubtypeUntilMethodMatchesRecursive($methodName, $this->helper->findExtends($object));
                     # in case we found ones, store them for reporting later
                     # note: we may find other methods in the same class later too
                     foreach ($classesMissingMethod as $classMissingMethod) {
                         $methods = [];
                         if ($classesMissingMethods->exists($classMissingMethod)) {
                             $methods = $classesMissingMethods->get($classMissingMethod);
                         }
                         $methods[] = $method;
                         $classesMissingMethods->set($classMissingMethod, $methods);
                     }
                 }
             }
         }
     }
     /** @var ParsedClass $class */
     foreach ($classesMissingMethods->keys() as $class) {
         $project->addReport(new AbstractMissingReport($class, $classesMissingMethods->get($class)));
     }
 }
Ejemplo n.º 12
0
 public function enterNode(Node $node)
 {
     if ($node instanceof Namespace_ && NULL !== $node->name) {
         $this->currentNamespace = join('\\', $node->name->parts);
     } else {
         if ($node instanceof Use_ && $node->type === Use_::TYPE_NORMAL) {
             $this->currentUseStatements[] = $node;
         } else {
             if ($node instanceof PhpParserClass || $node instanceof PhpParserInterface) {
                 /** @var ParsedObject|NULL $object */
                 $object = NULL;
                 if ($node instanceof PhpParserClass) {
                     $object = new ParsedClass($this->currentNamespace, $this->currentUseStatements, $node, $this->currentFile);
                 } else {
                     if ($node instanceof PhpParserInterface) {
                         $object = new ParsedInterface($this->currentNamespace, $this->currentUseStatements, $node, $this->currentFile);
                     }
                 }
                 if (NULL !== $object) {
                     try {
                         $this->addObject($object);
                     } catch (ObjectAlreadyExistsException $e) {
                         $existingObject = $this->getObjectByFqn($object->getName());
                         $msg = 'Multiple declarations of the same type are not supported. Symbol ' . $object->getName() . ' from ' . $this->currentFile->getSplFile()->getRealPath() . ':' . $node->getLine();
                         if ($existingObject instanceof ParsedObject) {
                             $msg .= ' already found in ' . $existingObject->getFile()->getSplFile()->getRealPath() . ':' . $existingObject->getNode()->getLine() . ' ; only the first ' . 'encounter is used';
                         } else {
                             if ($existingObject instanceof ReflectedObject) {
                                 $msg .= ' clashes with internal ' . strtolower($existingObject->getKind()) . ' ' . $existingObject->getName();
                             } else {
                                 throw new \RuntimeException('Unknown existing object ' . $existingObject->getName());
                             }
                         }
                         $this->project->getLogger()->warning($msg);
                     }
                 }
             }
         }
     }
 }
Ejemplo n.º 13
0
 /**
  * @param Project $project
  * @return \string[]
  */
 public function analyze(Project $project)
 {
     $graph = $this->objectGraph;
     /** @var ParsedClass[] $classesMissingMethod */
     $classesMissingMethods = new Map(function ($key) {
         if ($key instanceof ParsedClass) {
             return;
         }
         throw new MapException('Only keys of type Class_ are accepted');
     }, function ($value) {
         if (is_array($value)) {
             return;
         }
         throw new MapException('Only values of type array are accepted');
     });
     # scan all objects (we're actually only interested in interfaces)
     foreach ($graph->getObjects() as $object) {
         if ($object instanceof ParsedInterface) {
             foreach ($object->getMethods() as $method) {
                 # now find all classes and class from interfaces extending this
                 # interface
                 $classesMissingMethod = $this->findSubtypeUntilMethodMatchesRecursive($method, $this->helper->findImplements($object));
                 # in case we found ones, store them for reporting later
                 # note: we may find other methods in the same class later too
                 foreach ($classesMissingMethod as $classMissingMethod) {
                     $methods = [];
                     if ($classesMissingMethods->exists($classMissingMethod)) {
                         $methods = $classesMissingMethods->get($classMissingMethod);
                     }
                     $methods[] = $method;
                     $classesMissingMethods->set($classMissingMethod, $methods);
                 }
             }
         }
     }
     /** @var ParsedClass $class */
     foreach ($classesMissingMethods->keys() as $class) {
         $project->addReport(new InterfaceMissingReport($class, $classesMissingMethods->get($class)));
     }
 }
Ejemplo n.º 14
0
 /**
  * @param Project $project
  */
 public function analyze(Project $project)
 {
     $logger = $project->getLogger();
     foreach ($project->getSplFileInfos() as $splFileInfo) {
         try {
             $source = file($splFileInfo->getRealPath());
             $code = join('', $source);
         } catch (\RuntimeException $e) {
             $project->addReport(new StringReport($e->getMessage()));
             continue;
         }
         try {
             $logger->info('Parsing ' . $splFileInfo->getRealPath());
             $tree = $this->parser->parse($code);
             $project->addFile(new File($splFileInfo, $source, $tree));
         } catch (Error $e) {
             $project->getLogger()->warning('[' . $this->getName() . '] ' . 'Error while parsing ' . $splFileInfo->getRealPath() . ' : ' . $e->getMessage());
             $project->addReport(new FileParserErrorReport($splFileInfo, $e));
         }
     }
 }
Ejemplo n.º 15
0
 /**
  * Although this test seems redundant I use it to ensure that as far as it's
  * possible the analyzers do not negatively affect each other.
  */
 public function testAllAnalyzers()
 {
     $project = new Project(Null::getInstance());
     foreach (Util::scanDir(self::getAnalyzerTestsDir(), '/\\.phptest$/') as $file) {
         $project->addSplFileInfo(new \SplFileInfo($file));
     }
     $project->addAnalyzers(Project::getDefaultConfig());
     $project->analyze();
     $reports = $project->getAnalyzerReports();
     $this->assertSame(10, count($reports));
     $this->assertSame('Class Mfn\\PHP\\Analyzer\\Tests\\AbstractMethodMissing\\b misses the following abstract method: Mfn\\PHP\\Analyzer\\Tests\\AbstractMethodMissing\\a::b()', $reports[0]->getTimestampedReport()->getReport()->report());
     $this->assertSame('Class Mfn\\PHP\\Analyzer\\Tests\\InterfaceMethodMissing\\d misses the following interface method: Mfn\\PHP\\Analyzer\\Tests\\InterfaceMethodMissing\\a::c()', $reports[1]->getTimestampedReport()->getReport()->report());
     $this->assertSame('Declaration of Mfn\\PHP\\Analyzer\\Tests\\MethodDeclarationCompatibility\\b::c($a, $a) must be compatible with Mfn\\PHP\\Analyzer\\Tests\\MethodDeclarationCompatibility\\a::c(array $a = 1)', $reports[2]->getTimestampedReport()->getReport()->report());
     # Empty exception catch block reports
     $this->assertSame(27, $reports[3]->getSourceFragment()->getLineSegment()->getHighlightLine());
     $this->assertSame('Dynamic class instantiation with variable $foo in 003_dynamic_class_instantiation.phptest:26', $reports[4]->getTimestampedReport()->getReport()->report());
     $this->assertSame('Variable used in constructing raw SQL, is it escaped?', $reports[5]->getTimestampedReport()->getReport()->report());
     $this->assertSame(29, $reports[5]->getTimestampedReport()->getReport()->getSourceFragment()->getLineSegment()->getHighlightLine());
     $this->assertSame(30, $reports[6]->getTimestampedReport()->getReport()->getSourceFragment()->getLineSegment()->getHighlightLine());
     $this->assertSame(31, $reports[7]->getTimestampedReport()->getReport()->getSourceFragment()->getLineSegment()->getHighlightLine());
     # Empty exception catch block reports
     $this->assertSame(27, $reports[8]->getSourceFragment()->getLineSegment()->getHighlightLine());
     $this->assertSame(32, $reports[9]->getSourceFragment()->getLineSegment()->getHighlightLine());
 }
Ejemplo n.º 16
0
 * Automatically generate analyzers markdown doc from classes
 */
use Mfn\PHP\Analyzer\Analyzers\NameResolver;
use Mfn\PHP\Analyzer\Analyzers\ObjectGraph\Helper;
use Mfn\PHP\Analyzer\Analyzers\ObjectGraph\ObjectGraph;
use Mfn\PHP\Analyzer\Analyzers\Parser;
use Mfn\PHP\Analyzer\Logger\Stdout;
use Mfn\PHP\Analyzer\Project;
use Mfn\PHP\Analyzer\Util\Util;
use PhpParser\Lexer;
require_once __DIR__ . '/../vendor/autoload.php';
error_reporting(E_ALL);
Util::installMinimalError2ExceptionHandler();
$projectRealPath = realpath(__DIR__ . DIRECTORY_SEPARATOR . '..');
# Use analyzer to gather the object graph
$project = new Project(new Stdout());
$project->addSplFileInfos(Util::scanDir(__DIR__ . '/../lib/'));
$project->addAnalyzers([new Parser(new \PhpParser\Parser(new Lexer())), new NameResolver(), $objectGraph = new ObjectGraph()]);
$project->analyze();
$helper = new Helper($objectGraph);
$className = 'Mfn\\PHP\\Analyzer\\Analyzers\\Analyzer';
$class = $objectGraph->getClassByFqn($className);
if (NULL === $class) {
    throw new \RuntimeException("Unable to find class {$className}");
}
unset($className);
$descendants = $helper->findExtends($class, true);
sort($descendants);
/** @var string[] $index */
$index = [];
$index[] = '# Built-in / available Analyzers';
Ejemplo n.º 17
0
 public function projectEnd(Project $project)
 {
     $this->write($this->json->formatReports($project->getAnalyzerReports()));
 }