Skip to content

Commit 5204fb5

Browse files
committed
Initial public release
0 parents  commit 5204fb5

30 files changed

Lines changed: 1141 additions & 0 deletions

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/.gitattributes export-ignore
2+
/.gitignore export-ignore

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/vendor/
2+
/composer.lock
3+
/phpucd.phar

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# PHP Unsafe Code Detector
2+
3+
## Why?
4+
5+
To detect unsafe places in your code.
6+
7+
## Usage
8+
9+
```bash
10+
phpucd analyze $SOURCE_DIRECTORY
11+
```
12+
13+
## Installation
14+
15+
Download latest version, then follow steps:
16+
17+
```
18+
composer.phar update --no-dev
19+
bin/compile
20+
chmod +x phpucd.phar
21+
mv phpucd.phar /usr/local/bin/phpucd
22+
```
23+
24+
## Example:
25+
26+
Command:
27+
28+
```bash
29+
phpucd analyze test_errors
30+
```
31+
32+
Output:
33+
34+
![Output](resources/example.png)
35+
36+
## Versioning
37+
38+
39+
The version numbers follow the [Semantic Versioning 2.0.0](http://semver.org/) scheme.
40+
41+
## Requirements
42+
43+
* PHP ^5.5.9
44+
* [Composer.phar](https://getcomposer.org/) (required only during installation)

bin/compile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env php
2+
<?php
3+
4+
use Greywizard\Phpucd\Console\Application;
5+
use Greywizard\Phpucd\Console\CompilerCommand;
6+
use Symfony\Component\Console\Input\ArgvInput;
7+
8+
require_once __DIR__ . DIRECTORY_SEPARATOR . 'init.php';
9+
10+
$application = new Application();
11+
$application->add(new CompilerCommand());
12+
$application->run(new ArgvInput([__FILE__, CompilerCommand::COMMAND_NAME]));

bin/init.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
$composerInstalled = false;
4+
foreach (['/../../autoload.php', '/../vendor/autoload.php', '/vendor/autoload.php'] as $file) {
5+
if (file_exists(__DIR__ . $file)) {
6+
require_once __DIR__ . $file;
7+
$composerInstalled = true;
8+
break;
9+
}
10+
}
11+
12+
if (!$composerInstalled) {
13+
fwrite(
14+
STDERR,
15+
'You need to set up the project dependencies using the following commands:' . PHP_EOL .
16+
'wget http://getcomposer.org/composer.phar' . PHP_EOL .
17+
'php composer.phar install --no-dev' . PHP_EOL
18+
);
19+
20+
exit(1);
21+
}

bin/phpucd

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env php
2+
<?php
3+
4+
require_once __DIR__ . DIRECTORY_SEPARATOR . 'init.php';
5+
6+
(new Greywizard\Phpucd\Console\Application())->run();

composer.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "greywizard/phpucd",
3+
"description": "Unsafe code detector",
4+
"type": "library",
5+
"bin": ["bin/phpucd"],
6+
"require": {
7+
"php": "^5.5 || ^7.0",
8+
"symfony/console": "^3.2.7",
9+
"symfony/finder": "^3.2.7"
10+
},
11+
"require-dev": {
12+
"phpunit/phpunit": "^4.8.35"
13+
},
14+
"license": "MIT",
15+
"authors": [
16+
{
17+
"name": "Bartłomiej Krukowski",
18+
"email": "bkrukowski@greywizard.com"
19+
}
20+
],
21+
"autoload": {
22+
"files": ["src/functions.inc.php"],
23+
"psr-4": {
24+
"Greywizard\\Phpucd\\": "src/"
25+
}
26+
}
27+
}

resources/example.png

117 KB
Loading

src/Console/AnalyzeCommand.php

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
<?php
2+
3+
namespace Greywizard\Phpucd\Console;
4+
5+
use Greywizard\Phpucd\Errors\ErrorInterface;
6+
use Greywizard\Phpucd\Phpucd;
7+
use Greywizard\Phpucd\Rules\StandardRules;
8+
use Greywizard\Phpucd\Tokens\Tokenizer;
9+
use Symfony\Component\Console\Command\Command;
10+
use Symfony\Component\Console\Helper\ProgressBar;
11+
use Symfony\Component\Console\Input\InputArgument;
12+
use Symfony\Component\Console\Input\InputInterface;
13+
use Symfony\Component\Console\Input\InputOption;
14+
use Symfony\Component\Console\Output\OutputInterface;
15+
use Symfony\Component\Finder\Finder;
16+
use Symfony\Component\Finder\SplFileInfo;
17+
18+
/**
19+
* @internal
20+
*/
21+
class AnalyzeCommand extends Command
22+
{
23+
const COMMAND_NAME = 'analyze';
24+
const COMMAND_ALIAS = 'analyse';
25+
const COMMAND_DESCRIPTION = 'Analyze PHP code';
26+
27+
const DEFAULT_EXT = '*.php';
28+
29+
protected function configure()
30+
{
31+
$this
32+
->setName(static::COMMAND_NAME)
33+
->setAliases([static::COMMAND_ALIAS])
34+
->setDescription(static::COMMAND_DESCRIPTION)
35+
->addArgument('directory', InputArgument::REQUIRED)
36+
->addOption(
37+
'name',
38+
null,
39+
InputOption::VALUE_OPTIONAL,
40+
'Pattern e.g. *.php',
41+
static::DEFAULT_EXT
42+
)
43+
;
44+
}
45+
46+
protected function execute(InputInterface $input, OutputInterface $output)
47+
{
48+
$output->writeln(sprintf(
49+
'%s <info>%s</info>',
50+
Application::APPLICATION_NAME,
51+
Application::APPLICATION_VERSION
52+
));
53+
$output->writeln('');
54+
55+
$finder = new Finder();
56+
$dirArgument = rtrim($input->getArgument('directory'), '/\\');
57+
$extOption = $input->getOption('name');
58+
/** @var SplFileInfo[] $files */
59+
$files = $finder->files()->in($dirArgument)->name($extOption);
60+
$progressBar = new ProgressBar($output, count($files));
61+
$progressBar->setRedrawFrequency(1);
62+
$rules = new StandardRules();
63+
$errors = [];
64+
$countErrors = 0;
65+
$countFiles = 0;
66+
$phpucd = new Phpucd();
67+
68+
$progressBar->display();
69+
foreach ($files as $file) {
70+
$output->write(' ' . $file->getRelativePathname());
71+
$fileErrors = \Greywizard\Phpucd\toArray($phpucd->getUnsafeTokens(
72+
Tokenizer::createFromFile($file),
73+
$rules
74+
));
75+
if (count($fileErrors)) {
76+
$countErrors += count($fileErrors);
77+
$errors[$file->getPathname()] = $fileErrors;
78+
}
79+
$countFiles++;
80+
$progressBar->advance();
81+
}
82+
83+
$output->writeln('');
84+
$output->writeln('');
85+
86+
if ($countErrors) {
87+
$this->handleErrors($output, $countErrors, $errors, $countFiles);
88+
return 1;
89+
}
90+
91+
$this->handleNoErrors($output, $countFiles);
92+
}
93+
94+
/**
95+
* @param OutputInterface $output
96+
* @param int $countErrors
97+
* @param array $errors e.g. [filename => [TokenInterface, TokenInterface, ...], ...]
98+
* @param int $countFiles
99+
*/
100+
private function handleErrors(OutputInterface $output, $countErrors, $errors, $countFiles)
101+
{
102+
$margin = 2;
103+
$errorMsg = sprintf('Ooops... Found %d errors in %d files', $countErrors, $countFiles);
104+
$emptyLine = '<error>' . str_pad('', strlen($errorMsg) + $margin * 2, ' ') . '</error>';
105+
$errorMsgFormat = '<error>' . str_pad('', $margin, ' ') . $errorMsg . str_pad('', $margin, ' ') . '</error>';
106+
107+
$output->writeln($emptyLine);
108+
$output->writeln($errorMsgFormat);
109+
$output->writeln($emptyLine);
110+
$output->writeln('');
111+
112+
$indent = ' * ';
113+
foreach ($errors as $filename => $fileErrors) {
114+
$output->writeln('<comment>' . $filename . ':</comment>');
115+
foreach ($fileErrors as $error) {
116+
/** @var ErrorInterface $error */
117+
$output->writeln($indent . '<comment>' . $error->getLine() . '</comment>: ' . $this->decorateSourceToken($error));
118+
}
119+
}
120+
}
121+
122+
/**
123+
* @param OutputInterface $output
124+
* @param int $countFiles
125+
*/
126+
private function handleNoErrors(OutputInterface $output, $countFiles)
127+
{
128+
$output->writeln(sprintf('<info>Success - 0 errors in %d files.</info>', $countFiles));
129+
}
130+
131+
private function decorateSourceToken(ErrorInterface $error)
132+
{
133+
$result = preg_replace('{\\s+}', ' ', $error->getMessage());
134+
if ($error->hasHighlightedPart()) {
135+
$result = str_replace(
136+
$error->getHighlightedPart(),
137+
'<error>' . $error->getHighlightedPart() . '</error>',
138+
$result
139+
);
140+
}
141+
142+
return $result;
143+
}
144+
}

src/Console/Application.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Greywizard\Phpucd\Console;
4+
5+
/**
6+
* @internal
7+
*/
8+
class Application extends \Symfony\Component\Console\Application
9+
{
10+
const APPLICATION_NAME = 'Greywizard PHP Unsafe Code Detector';
11+
12+
const APPLICATION_VERSION = '0.1.0';
13+
14+
final public function __construct()
15+
{
16+
parent::__construct(static::APPLICATION_NAME, static::APPLICATION_VERSION);
17+
$this->initCommands();
18+
}
19+
20+
private function initCommands()
21+
{
22+
$this->add(new AnalyzeCommand());
23+
}
24+
}

0 commit comments

Comments
 (0)