Skip to content

Commit 38f1246

Browse files
committed
WIP PHPCodeSnifferPreCommitHook
1 parent ca57fd2 commit 38f1246

File tree

5 files changed

+179
-1
lines changed

5 files changed

+179
-1
lines changed

config/git-hooks.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,11 @@
172172
'path' => env('LARAVEL_PINT_PATH', 'vendor/bin/pint'),
173173
'config' => env('LARAVEL_PINT_CONFIG', 'pint.json'),
174174
],
175+
'php_code_sniffer' => [
176+
'phpcs_path' => env('PHPCS_PATH', 'vendor/bin/phpcs'),
177+
'phpcbf_path' => env('PHPCBF_PATH', 'vendor/bin/phpcbf'),
178+
'standard' => env('PHPCS_STANDARD', 'phpcs.xml'),
179+
],
175180
],
176181

177182
];
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
<?php
2+
3+
namespace Igorsgm\GitHooks\Console\Commands\Hooks;
4+
5+
use Closure;
6+
use Igorsgm\GitHooks\Contracts\PreCommitHook;
7+
use Igorsgm\GitHooks\Exceptions\HookFailException;
8+
use Igorsgm\GitHooks\Facades\GitHooks;
9+
use Igorsgm\GitHooks\Git\ChangedFiles;
10+
use Igorsgm\GitHooks\Traits\ProcessHelper;
11+
use Illuminate\Console\Command;
12+
use Symfony\Component\Console\Terminal;
13+
14+
class PHPCodeSnifferPreCommitHook implements PreCommitHook
15+
{
16+
use ProcessHelper;
17+
18+
/**
19+
* Command instance that is bound automatically by Hooks Pipeline, so it can be used inside the Hook.
20+
*
21+
* @var Command
22+
*/
23+
public $command;
24+
25+
/**
26+
* @var string
27+
*/
28+
private $phpCSExecutable;
29+
30+
/**
31+
* @var string
32+
*/
33+
private $phpCBFExecutable;
34+
35+
/**
36+
* @var array
37+
*/
38+
private $filesBadlyFormattedPaths = [];
39+
40+
/**
41+
* Create a new console command instance.
42+
*
43+
* @return void
44+
*/
45+
public function __construct()
46+
{
47+
$this->setCwd(base_path());
48+
$this->phpCSExecutable = './'.trim(config('git-hooks.code_analyzers.php_code_sniffer.phpcs_path'), '/');
49+
$this->phpCBFExecutable = './'.trim(config('git-hooks.code_analyzers.php_code_sniffer.phpcbf_path'), '/');
50+
}
51+
52+
public function getName(): ?string
53+
{
54+
return 'PHP_CodeSniffer';
55+
}
56+
57+
public function handle(ChangedFiles $files, Closure $next)
58+
{
59+
$commitFiles = $files->getAddedToCommit();
60+
61+
if ($commitFiles->isEmpty() || GitHooks::isMergeInProgress()) {
62+
return $next($files);
63+
}
64+
65+
$this->validatePhpCsInstallation();
66+
67+
foreach ($commitFiles as $file) {
68+
if ($file->extension() !== 'php') {
69+
continue;
70+
}
71+
72+
$filePath = $file->getFilePath();
73+
$isPhpCSProperlyFormatted = $this->runCommands(
74+
implode(' ', [
75+
$this->phpCSExecutable,
76+
$this->getPhpCSStandardParam(),
77+
$filePath,
78+
]))->isSuccessful();
79+
80+
if (! $isPhpCSProperlyFormatted) {
81+
if (empty($this->filesBadlyFormattedPaths)) {
82+
$this->command->newLine();
83+
}
84+
85+
$this->command->getOutput()->writeln(
86+
sprintf('<fg=red> %s Failed:</> %s', $this->getName(), $filePath)
87+
);
88+
$this->filesBadlyFormattedPaths[] = $filePath;
89+
}
90+
}
91+
92+
if (empty($this->filesBadlyFormattedPaths)) {
93+
return $next($files);
94+
}
95+
96+
$this->command->newLine();
97+
$this->command->getOutput()->writeln(
98+
'<bg=red;fg=white> COMMIT FAILED </> ' .
99+
sprintf('Your commit contains files that should pass %s but do not. Please fix the %s errors in the files above and try again.', $this->getName(), $this->getName())
100+
);
101+
102+
$this->suggestAutoFixOrExit();
103+
}
104+
105+
private function getPhpCSStandardParam(): string
106+
{
107+
$phpCSStandard = trim(config('git-hooks.code_analyzers.php_code_sniffer.standard'), '/');
108+
return empty($phpCSStandard) ? '' : '--standard='.$phpCSStandard;
109+
}
110+
111+
/**
112+
* @return void
113+
*
114+
* @throws HookFailException
115+
*/
116+
private function validatePhpCsInstallation()
117+
{
118+
$isPhpCSInstalled = file_exists(base_path(config('git-hooks.code_analyzers.php_code_sniffer.phpcs_path')));
119+
120+
if ($isPhpCSInstalled) {
121+
return;
122+
}
123+
124+
$this->command->newLine(2);
125+
$this->command->getOutput()->writeln(
126+
sprintf('<bg=red;fg=white> ERROR </> %s',
127+
'Php_CodeSniffer is not installed. Please run <info>composer require squizlabs/php_codesniffer --dev</info> to install it.')
128+
);
129+
$this->command->newLine();
130+
throw new HookFailException();
131+
}
132+
133+
/**
134+
* @return void
135+
*
136+
* @throws HookFailException
137+
*/
138+
private function suggestAutoFixOrExit()
139+
{
140+
if (Terminal::hasSttyAvailable() &&
141+
$this->command->confirm('Would you like to attempt to correct files automagically?', false)
142+
) {
143+
$errorFilesString = implode(' ', $this->filesBadlyFormattedPaths);
144+
$this->runCommands([
145+
implode(' ', [
146+
$this->phpCBFExecutable,
147+
$this->getPhpCSStandardParam(),
148+
$errorFilesString,
149+
]),
150+
'git add '.$errorFilesString,
151+
]);
152+
} else {
153+
throw new HookFailException();
154+
}
155+
}
156+
}

src/Git/ChangedFile.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ public function isCopied(): bool
116116
return $this->X & static::C || $this->Y & static::C;
117117
}
118118

119+
public function extension()
120+
{
121+
return pathinfo($this->getFilePath(), PATHINFO_EXTENSION);
122+
}
123+
119124
/**
120125
* @return string
121126
*/

src/Git/ChangedFiles.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public function getFiles(): Collection
3232

3333
/**
3434
* Get list of staged files
35+
* @return Collection|ChangedFile[]
3536
*/
3637
public function getStaged(): Collection
3738
{

src/Traits/ProcessHelper.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
*/
1111
trait ProcessHelper
1212
{
13+
private $cwd;
14+
1315
/**
1416
* Run the given commands.
1517
*
@@ -43,7 +45,7 @@ public function runCommands($commands, $params = [])
4345

4446
$process = Process::fromShellCommandline(
4547
implode(' && ', (array) $commands),
46-
data_get($params, 'cwd'),
48+
data_get($params, 'cwd', $this->cwd ?? null),
4749
data_get($params, 'env'),
4850
data_get($params, 'input'),
4951
data_get($params, 'timeout')
@@ -91,4 +93,13 @@ public function buildNoOutputCommand($command = '')
9193
{
9294
return trim($command).' > '.(PHP_OS_FAMILY == 'Windows' ? 'NUL' : '/dev/null 2>&1');
9395
}
96+
97+
/**
98+
* @param string $cwd
99+
* @return void
100+
*/
101+
public function setCwd($cwd)
102+
{
103+
$this->cwd = $cwd;
104+
}
94105
}

0 commit comments

Comments
 (0)