Skip to content

Commit 121fc72

Browse files
committed
Merge branch 'feature/larastan-pre-commit-hook' into develop
2 parents 46f8221 + 0331e86 commit 121fc72

13 files changed

+234
-56
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"require-dev": {
3333
"laravel/pint": "^1.2",
3434
"mockery/mockery": "^1.5.1",
35+
"nunomaduro/larastan": "^2.0",
3536
"orchestra/testbench": "^7.0",
3637
"pestphp/pest": "^1.22",
3738
"pestphp/pest-plugin-mock": "^1.0",

config/git-hooks.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,11 @@
178178
'phpcbf_path' => env('PHPCBF_PATH', 'vendor/bin/phpcbf'),
179179
'standard' => env('PHPCS_STANDARD', 'phpcs.xml'),
180180
],
181+
'larastan' => [
182+
'path' => env('LARASTAN_PATH', 'vendor/bin/phpstan'),
183+
'config' => env('LARASTAN_CONFIG', 'phpstan.neon'),
184+
'additional_params' => env('LARASTAN_ADDITIONAL_PARAMS', '--xdebug'),
185+
],
181186
'blade_formatter' => [
182187
'path' => env('BLADE_FORMATTER_PATH', 'node_modules/.bin/blade-formatter'),
183188
'config' => env('BLADE_FORMATTER_CONFIG', '.bladeformatterrc.json'),

src/Console/Commands/Hooks/BaseCodeAnalyzerPreCommitHook.php

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ abstract class BaseCodeAnalyzerPreCommitHook
2424

2525
/**
2626
* Name of the hook
27+
*
2728
* @var string
2829
*/
2930
protected $name;
@@ -99,10 +100,7 @@ protected function analizeCommittedFiles($commitFiles)
99100
{
100101
/** @var ChangedFile $file */
101102
foreach ($commitFiles as $file) {
102-
if (
103-
(is_array($this->fileExtensions) && ! in_array($file->extension(), $this->fileExtensions)) ||
104-
(is_string($this->fileExtensions) && ! preg_match($this->fileExtensions, $file->getFilePath()))
105-
) {
103+
if (! $this->canFileBeAnalyzed($file)) {
106104
continue;
107105
}
108106

@@ -126,6 +124,19 @@ protected function analizeCommittedFiles($commitFiles)
126124
return $this;
127125
}
128126

127+
/**
128+
* Checks whether the given ChangedFile can be analyzed based on its file extension and the list of allowed extensions.
129+
*/
130+
protected function canFileBeAnalyzed(ChangedFile $file): bool
131+
{
132+
if (empty($this->fileExtensions) || $this->fileExtensions === 'all') {
133+
return true;
134+
}
135+
136+
return (is_array($this->fileExtensions) && in_array($file->extension(), $this->fileExtensions)) ||
137+
(is_string($this->fileExtensions) && preg_match($this->fileExtensions, $file->getFilePath()));
138+
}
139+
129140
/**
130141
* Returns the message to display when the commit fails.
131142
*
@@ -175,7 +186,9 @@ protected function checkAnalyzerInstallation()
175186
*/
176187
protected function suggestAutoFixOrExit()
177188
{
178-
if (Terminal::hasSttyAvailable() &&
189+
$hasFixerCommand = ! empty($this->fixerCommand());
190+
191+
if (Terminal::hasSttyAvailable() && $hasFixerCommand &&
179192
$this->command->confirm('Would you like to attempt to correct files automagically?')
180193
) {
181194
$errorFilesString = implode(' ', $this->filesBadlyFormattedPaths);
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
namespace Igorsgm\GitHooks\Console\Commands\Hooks;
4+
5+
use Closure;
6+
use Igorsgm\GitHooks\Contracts\CodeAnalyzerPreCommitHook;
7+
use Igorsgm\GitHooks\Git\ChangedFiles;
8+
9+
class LarastanPreCommitHook extends BaseCodeAnalyzerPreCommitHook implements CodeAnalyzerPreCommitHook
10+
{
11+
/**
12+
* @var string
13+
*/
14+
protected $configParam;
15+
16+
/**
17+
* Name of the hook
18+
*
19+
* @var string
20+
*/
21+
protected $name = 'Larastan';
22+
23+
/**
24+
* Analyzes committed files using Larastan
25+
*
26+
* @param ChangedFiles $files The list of committed files to analyze.
27+
* @param Closure $next The next hook in the chain to execute.
28+
* @return mixed|null
29+
*/
30+
public function handle(ChangedFiles $files, Closure $next)
31+
{
32+
$this->configParam = $this->configParam();
33+
34+
return $this->setAnalyzerExecutable(config('git-hooks.code_analyzers.larastan.path'))
35+
->handleCommittedFiles($files, $next);
36+
}
37+
38+
/**
39+
* Returns the command to run Larastan analyzer with the given configuration file.
40+
* By default, it turns off XDebug if it’s enabled to achieve better performance.
41+
*/
42+
public function analyzerCommand(): string
43+
{
44+
$additionalParams = config('git-hooks.code_analyzers.larastan.additional_params');
45+
46+
if (! empty($additionalParams)) {
47+
// Removing configuration/c/xdebug parameters from additional parameters to avoid conflicts
48+
// because they are already set in the command by default.
49+
$additionalParams = preg_replace('/\s*--(configuration|c|xdebug)\b(=\S*)?\s*/', '', $additionalParams);
50+
}
51+
52+
return trim(
53+
sprintf('%s analyse %s --xdebug %s', $this->getAnalyzerExecutable(), $this->configParam, $additionalParams)
54+
);
55+
}
56+
57+
/**
58+
* Empty fixer command because Larastan doesn't provide any type of auto-fixing.
59+
*/
60+
public function fixerCommand(): string
61+
{
62+
return '';
63+
}
64+
65+
/**
66+
* Gets the command-line parameter for specifying the configuration file for Larastan.
67+
*
68+
* @return string The command-line parameter for the configuration file, or an empty string if not set.
69+
*/
70+
protected function configParam(): string
71+
{
72+
$phpStanConfigFile = rtrim(config('git-hooks.code_analyzers.larastan.config'), '/');
73+
74+
return empty($phpStanConfigFile) ? '' : '--configuration='.$phpStanConfigFile;
75+
}
76+
}

tests/Datasets/PreCommitHooksDataset.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
<?php
22

3+
use Igorsgm\GitHooks\Console\Commands\Hooks\BladeFormatterPreCommitHook;
4+
use Igorsgm\GitHooks\Console\Commands\Hooks\LarastanPreCommitHook;
5+
use Igorsgm\GitHooks\Console\Commands\Hooks\PHPCodeSnifferPreCommitHook;
6+
use Igorsgm\GitHooks\Console\Commands\Hooks\PintPreCommitHook;
7+
38
dataset('pintConfigurations', [
49
'Config File' => [
510
[
@@ -33,3 +38,41 @@
3338
],
3439
],
3540
]);
41+
42+
dataset('larastanConfiguration', [
43+
'phpstan.neon file & additional params' => [
44+
[
45+
'path' => '../../../bin/phpstan',
46+
'config' => __DIR__.'/../Fixtures/phpstanFixture.neon',
47+
'additional_params' => '--xdebug',
48+
],
49+
],
50+
]);
51+
52+
$nonExistentPath = [
53+
'path' => 'nonexistent/path',
54+
'phpcs_path' => 'nonexistent/path',
55+
];
56+
57+
dataset('codeAnalyzersList', [
58+
'Laravel Pint' => [
59+
'laravel_pint',
60+
$nonExistentPath,
61+
PintPreCommitHook::class,
62+
],
63+
'PHP Code Sniffer' => [
64+
'php_code_sniffer',
65+
$nonExistentPath,
66+
PHPCodeSnifferPreCommitHook::class,
67+
],
68+
'Blade Formatter' => [
69+
'blade_formatter',
70+
$nonExistentPath,
71+
BladeFormatterPreCommitHook::class,
72+
],
73+
'Larastan' => [
74+
'larastan',
75+
$nonExistentPath,
76+
LarastanPreCommitHook::class,
77+
],
78+
]);

tests/Features/Commands/Hooks/BaseCodeAnalyzerPreCommitHookTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,20 @@
3838
$result = $hook->handleCommittedFiles($changedFiles, $next);
3939
expect($result)->toBe('passed');
4040
})->with('modifiedFilesList');
41+
42+
test('Throws HookFailException and notifies when Code Analyzer is not installed',
43+
function ($configName, $nonExistentPathConfig, $preCommitHookClass, $listOfFixableFiles) {
44+
$this->config->set('git-hooks.code_analyzers.'.$configName, $nonExistentPathConfig);
45+
46+
$this->config->set('git-hooks.pre-commit', [
47+
$preCommitHookClass,
48+
]);
49+
50+
GitHooks::shouldReceive('isMergeInProgress')->andReturn(false);
51+
GitHooks::shouldReceive('getListOfChangedFiles')->andReturn($listOfFixableFiles);
52+
53+
$preCommitHook = new $preCommitHookClass();
54+
$this->artisan('git-hooks:pre-commit')
55+
->expectsOutputToContain($preCommitHook->getName().' is not installed.')
56+
->assertExitCode(1);
57+
})->with('codeAnalyzersList', 'listOfFixableFiles');

tests/Features/Commands/Hooks/BladeFormatterPreCommitHookTest.php

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,23 +26,6 @@
2626
$this->artisan('git-hooks:pre-commit')->assertSuccessful();
2727
})->with('phpcsConfiguration');
2828

29-
test('Throws HookFailException and notifies when Blade Formatter is not installed', function ($listOfFixableFiles) {
30-
$this->config->set('git-hooks.code_analyzers.blade_formatter', [
31-
'path' => 'inexistent/path/to/blade-formatter',
32-
]);
33-
34-
$this->config->set('git-hooks.pre-commit', [
35-
BladeFormatterPreCommitHook::class,
36-
]);
37-
38-
GitHooks::shouldReceive('isMergeInProgress')->andReturn(false);
39-
GitHooks::shouldReceive('getListOfChangedFiles')->andReturn($listOfFixableFiles);
40-
41-
$this->artisan('git-hooks:pre-commit')
42-
->expectsOutputToContain('Blade Formatter is not installed.')
43-
->assertExitCode(1);
44-
})->with('listOfFixableFiles');
45-
4629
test('Fails commit when Blade Formatter is not passing and user does not autofix the files',
4730
function ($bladeFormatterConfiguration, $listOfFixableFiles) {
4831
$this->config->set('git-hooks.code_analyzers.blade_formatter', $bladeFormatterConfiguration);
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
use Igorsgm\GitHooks\Console\Commands\Hooks\LarastanPreCommitHook;
4+
use Igorsgm\GitHooks\Facades\GitHooks;
5+
use Igorsgm\GitHooks\Traits\GitHelper;
6+
7+
uses(GitHelper::class);
8+
beforeEach(function () {
9+
$this->gitInit();
10+
$this->initializeTempDirectory(base_path('temp'));
11+
});
12+
13+
test('Fails commit when Larastan is not passing',
14+
function ($larastanConfiguration) {
15+
$this->config->set('git-hooks.code_analyzers.larastan', $larastanConfiguration);
16+
$this->config->set('git-hooks.pre-commit', [
17+
LarastanPreCommitHook::class,
18+
]);
19+
20+
$this->makeTempFile('ClassWithFixableIssues.php',
21+
file_get_contents(__DIR__.'/../../../Fixtures/ClassWithFixableIssues.php')
22+
);
23+
24+
GitHooks::shouldReceive('isMergeInProgress')->andReturn(false);
25+
GitHooks::shouldReceive('getListOfChangedFiles')->andReturn(implode(PHP_EOL, [
26+
'AM temp/ClassWithFixableIssues.php',
27+
]));
28+
29+
$this->artisan('git-hooks:pre-commit')
30+
->expectsOutputToContain('Larastan Failed')
31+
->expectsOutputToContain('COMMIT FAILED')
32+
->assertExitCode(1);
33+
})->with('larastanConfiguration');
34+
35+
test('Commit passes when Larastan check is OK', function ($larastanConfiguration) {
36+
$this->config->set('git-hooks.code_analyzers.larastan', $larastanConfiguration);
37+
$this->config->set('git-hooks.pre-commit', [
38+
LarastanPreCommitHook::class,
39+
]);
40+
41+
$tempFileName = 'ClassWithoutFixableIssues.php';
42+
$this->makeTempFile($tempFileName,
43+
file_get_contents(__DIR__.'/../../../Fixtures/'.$tempFileName)
44+
);
45+
46+
GitHooks::shouldReceive('isMergeInProgress')->andReturn(false);
47+
GitHooks::shouldReceive('getListOfChangedFiles')->andReturn(implode(PHP_EOL, [
48+
'AM temp/'.$tempFileName,
49+
]));
50+
51+
$this->artisan('git-hooks:pre-commit')
52+
->doesntExpectOutputToContain('Larastan Failed')
53+
->assertSuccessful();
54+
})->with('larastanConfiguration');

tests/Features/Commands/Hooks/PHPCodeSnifferPreCommitHookTest.php

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,23 +26,6 @@
2626
$this->artisan('git-hooks:pre-commit')->assertSuccessful();
2727
})->with('phpcsConfiguration');
2828

29-
test('Throws HookFailException and notifies when PHPCS is not installed', function ($listOfFixableFiles) {
30-
$this->config->set('git-hooks.code_analyzers.php_code_sniffer', [
31-
'phpcs_path' => 'inexistent/path/to/phpcs',
32-
]);
33-
34-
$this->config->set('git-hooks.pre-commit', [
35-
PHPCodeSnifferPreCommitHook::class,
36-
]);
37-
38-
GitHooks::shouldReceive('isMergeInProgress')->andReturn(false);
39-
GitHooks::shouldReceive('getListOfChangedFiles')->andReturn($listOfFixableFiles);
40-
41-
$this->artisan('git-hooks:pre-commit')
42-
->expectsOutputToContain('PHP_CodeSniffer is not installed.')
43-
->assertExitCode(1);
44-
})->with('listOfFixableFiles');
45-
4629
test('Fails commit when PHPCS is not passing and user does not autofix the files',
4730
function ($phpCSConfiguration, $listOfFixableFiles) {
4831
$this->config->set('git-hooks.code_analyzers.php_code_sniffer', $phpCSConfiguration);

tests/Features/Commands/Hooks/PintPreCommitHookTest.php

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,23 +26,6 @@
2626
$this->artisan('git-hooks:pre-commit')->assertSuccessful();
2727
})->with('pintConfigurations');
2828

29-
test('Throws HookFailException and notifies when Pint is not installed', function ($listOfFixableFiles) {
30-
$this->config->set('git-hooks.code_analyzers.laravel_pint', [
31-
'path' => 'inexistent/path/to/pint',
32-
]);
33-
34-
$this->config->set('git-hooks.pre-commit', [
35-
PintPreCommitHook::class,
36-
]);
37-
38-
GitHooks::shouldReceive('isMergeInProgress')->andReturn(false);
39-
GitHooks::shouldReceive('getListOfChangedFiles')->andReturn($listOfFixableFiles);
40-
41-
$this->artisan('git-hooks:pre-commit')
42-
->expectsOutputToContain('Pint is not installed.')
43-
->assertExitCode(1);
44-
})->with('listOfFixableFiles', 'pintConfigurations');
45-
4629
test('Fails commit when Pint is not passing and user does not autofix the files',
4730
function ($pintConfiguration, $listOfFixableFiles) {
4831
$this->config->set('git-hooks.code_analyzers.laravel_pint', $pintConfiguration);

0 commit comments

Comments
 (0)