Skip to content

Commit 31ed4d0

Browse files
committed
wip Pre Commit Hooks for Code Analyzers such as Pint and PHPCS got abstracted for reusage
1 parent 38f1246 commit 31ed4d0

File tree

7 files changed

+323
-233
lines changed

7 files changed

+323
-233
lines changed

config/git-hooks.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@
171171
'laravel_pint' => [
172172
'path' => env('LARAVEL_PINT_PATH', 'vendor/bin/pint'),
173173
'config' => env('LARAVEL_PINT_CONFIG', 'pint.json'),
174+
'preset' => env('LARAVEL_PINT_PRESET', 'psr12'),
174175
],
175176
'php_code_sniffer' => [
176177
'phpcs_path' => env('PHPCS_PATH', 'vendor/bin/phpcs'),
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
<?php
2+
3+
namespace Igorsgm\GitHooks\Console\Commands\Hooks;
4+
5+
use Closure;
6+
use Igorsgm\GitHooks\Exceptions\HookFailException;
7+
use Igorsgm\GitHooks\Facades\GitHooks;
8+
use Igorsgm\GitHooks\Git\ChangedFiles;
9+
use Igorsgm\GitHooks\Traits\ProcessHelper;
10+
use Illuminate\Console\Command;
11+
use Symfony\Component\Console\Terminal;
12+
13+
abstract class BaseCodeAnalyzerPreCommitHook
14+
{
15+
use ProcessHelper;
16+
17+
/**
18+
* Command instance that is bound automatically by Hooks Pipeline, so it can be used inside the Hook.
19+
*
20+
* @var Command
21+
*/
22+
public $command;
23+
24+
/*
25+
* List of files extensions that will be analyzed by the hook
26+
* @var array
27+
*/
28+
public $fileExtensions = [];
29+
30+
/**
31+
* The path to the analyzer executable.
32+
* @var string
33+
*/
34+
protected $analyzerExecutable;
35+
36+
/**
37+
* The path to the fixer executable. In multiple cases it's the same of the analyzer executable.
38+
* @var string
39+
*/
40+
protected $fixerExecutable;
41+
42+
/**
43+
* The list of paths of files that are badly formatted and should be fixed.
44+
* @var array
45+
*/
46+
protected $filesBadlyFormattedPaths = [];
47+
48+
public function __construct()
49+
{
50+
$this->setCwd(base_path());
51+
}
52+
53+
/**
54+
* Handles the committed files and checks if they are properly formatted.
55+
*
56+
* @param ChangedFiles $files The instance of the changed files.
57+
* @param Closure $next The closure to be executed after the files are handled.
58+
* @return mixed|void
59+
*
60+
* @throws HookFailException If the hook fails to analyze the committed files.
61+
*/
62+
public function handleCommittedFiles(ChangedFiles $files, Closure $next)
63+
{
64+
$commitFiles = $files->getAddedToCommit();
65+
66+
if ($commitFiles->isEmpty() || GitHooks::isMergeInProgress()) {
67+
return $next($files);
68+
}
69+
70+
$this->checkAnalyzerInstallation()
71+
->analizeCommittedFiles($commitFiles);
72+
73+
if (empty($this->filesBadlyFormattedPaths)) {
74+
return $next($files);
75+
}
76+
77+
$this->commitFailMessage()
78+
->suggestAutoFixOrExit();
79+
}
80+
81+
/**
82+
* Analyzes the committed files and checks if they are properly formatted.
83+
*
84+
* @param mixed $commitFiles The files to analyze.
85+
* @return $this
86+
*/
87+
protected function analizeCommittedFiles($commitFiles)
88+
{
89+
foreach ($commitFiles as $file) {
90+
if (!in_array($file->extension(), $this->fileExtensions)) {
91+
continue;
92+
}
93+
94+
$filePath = $file->getFilePath();
95+
$command = $this->analyzerCommand().' '.$filePath;
96+
97+
$isProperlyFormatted = $this->runCommands($command)->isSuccessful();
98+
99+
if (!$isProperlyFormatted) {
100+
if (empty($this->filesBadlyFormattedPaths)) {
101+
$this->command->newLine();
102+
}
103+
104+
$this->command->getOutput()->writeln(
105+
sprintf('<fg=red> %s Failed:</> %s', $this->getName(), $filePath)
106+
);
107+
$this->filesBadlyFormattedPaths[] = $filePath;
108+
}
109+
}
110+
111+
return $this;
112+
}
113+
114+
/**
115+
* Returns the message to display when the commit fails.
116+
* @return $this
117+
*/
118+
protected function commitFailMessage()
119+
{
120+
$this->command->newLine();
121+
$this->command->getOutput()->writeln(
122+
'<bg=red;fg=white> COMMIT FAILED </> '.
123+
sprintf('Your commit contains files that should pass %s but do not. Please fix the errors in the files above and try again.',
124+
$this->getName())
125+
);
126+
127+
return $this;
128+
}
129+
130+
/**
131+
* Check if the BaseCodeAnalyzerPreCommitHook is installed.
132+
*
133+
* @return $this
134+
* @throws HookFailException
135+
*/
136+
protected function checkAnalyzerInstallation()
137+
{
138+
if (file_exists($this->analyzerExecutable)) {
139+
return $this;
140+
}
141+
142+
$this->command->newLine(2);
143+
$this->command->getOutput()->writeln(
144+
sprintf('<bg=red;fg=white> ERROR </> %s is not installed. Please install it and try again.',
145+
$this->getName())
146+
);
147+
$this->command->newLine();
148+
149+
throw new HookFailException();
150+
}
151+
152+
/**
153+
* Suggests attempting to automatically fix the incorrectly formatted files or exit.
154+
*
155+
* @return void
156+
* @throws HookFailException
157+
*/
158+
protected function suggestAutoFixOrExit()
159+
{
160+
if (Terminal::hasSttyAvailable() &&
161+
$this->command->confirm('Would you like to attempt to correct files automagically?')
162+
) {
163+
$errorFilesString = implode(' ', $this->filesBadlyFormattedPaths);
164+
165+
$this->runCommands([
166+
$this->fixerCommand().' '.$errorFilesString,
167+
'git add '.$errorFilesString
168+
]);
169+
} else {
170+
throw new HookFailException();
171+
}
172+
}
173+
174+
/**
175+
* @param array|string $fileExtensions
176+
* @return BaseCodeAnalyzerPreCommitHook
177+
*/
178+
public function setFileExtensions($fileExtensions)
179+
{
180+
$this->fileExtensions = (array) $fileExtensions;
181+
return $this;
182+
}
183+
184+
/**
185+
* @param $executablePath
186+
* @return BaseCodeAnalyzerPreCommitHook
187+
*/
188+
public function setAnalyzerExecutable($executablePath, $isSameAsFixer = false)
189+
{
190+
$this->analyzerExecutable = './'.trim($executablePath, '/');
191+
192+
return $isSameAsFixer ? $this->setFixerExecutable($executablePath) : $this;
193+
}
194+
195+
/**
196+
* @return string
197+
*/
198+
public function getAnalyzerExecutable(): string
199+
{
200+
return $this->analyzerExecutable;
201+
}
202+
203+
/**
204+
* @param $exacutablePath
205+
* @return BaseCodeAnalyzerPreCommitHook
206+
*/
207+
public function setFixerExecutable($exacutablePath)
208+
{
209+
$this->fixerExecutable = './'.trim($exacutablePath, '/');
210+
return $this;
211+
}
212+
213+
/**
214+
* @return string
215+
*/
216+
public function getFixerExecutable(): string
217+
{
218+
return $this->fixerExecutable;
219+
}
220+
}

0 commit comments

Comments
 (0)