Skip to content

Commit

Permalink
Add tests, refactor to improve testability
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisdicarlo committed Sep 19, 2024
1 parent 19a9154 commit 9723fa0
Show file tree
Hide file tree
Showing 46 changed files with 717 additions and 370 deletions.
23 changes: 0 additions & 23 deletions src/Actions/ListFiles.php

This file was deleted.

217 changes: 62 additions & 155 deletions src/Commands/LaravelConfigCheckerCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

namespace ChrisDiCarlo\LaravelConfigChecker\Commands;

use ChrisDiCarlo\LaravelConfigChecker\Support\BladeFiles;
use ChrisDiCarlo\LaravelConfigChecker\Support\FileChecker;
use ChrisDiCarlo\LaravelConfigChecker\Support\LoadConfigKeys;
use ChrisDiCarlo\LaravelConfigChecker\Support\PhpFiles;
use Illuminate\Console\Command;
use Symfony\Component\Finder\Finder;
use Illuminate\Support\Collection;

use function Laravel\Prompts\error;
use function Laravel\Prompts\info;
Expand All @@ -16,192 +20,95 @@ class LaravelConfigCheckerCommand extends Command

public $description = 'Check all references to config values in PHP and Blade files';

private $configKeys = [];
private Collection $configKeys;

private array $issues = [];
private array $bladeIssues = [];

public function handle(): int
{
$this->loadConfigKeys();

if ($this->output->isVerbose()) {
$this->outputConfigKeys();
}

$this->checkPhpFiles();
$this->checkBladeFiles();

$this->displayResults();

return self::SUCCESS;
}

private function outputConfigKeys(): void
{
table(
['File', '# of Keys'],
collect($this->configKeys)->groupBy(fn ($key) => explode('.', $key)[0])
->map(fn ($subkeys, $key) => ['key' => $key, 'count' => $subkeys->count()])
->sort()
->values()
->toArray()
);
}
private array $phpIssues = [];

private function flattenConfig($config, $prefix = '')
public function getIssues(): Collection
{
foreach ($config as $key => $value) {
$fullKey = $prefix ? "{$prefix}.{$key}" : $key;

$this->configKeys[] = $fullKey;

if (is_array($value)) {
$this->flattenConfig($value, $fullKey);
}
}
}

private function loadConfigKeys()
{
$configPath = $this->laravel->configPath();
$finder = new Finder;
$finder->files()->in($configPath)->name('*.php');

foreach ($finder as $file) {
$this->configKeys[] = basename($file->getFilename(), '.php');

$config = include $file->getRealPath();

$this->flattenConfig($config, basename($file->getFilename(), '.php'));
}
}

private function displayResults(): void
{
$issues = collect($this->issues)->filter(fn ($issue) => ! empty($issue));

if ($issues->isEmpty()) {
info('No issues found. All config references are valid.');
$combinedIssues = collect([...$this->phpIssues, ...$this->bladeIssues])
->filter(fn ($issue) => ! empty($issue));

return;
}

error('Issues found! Invalid config references detected:');

table(
['File', 'Line Number', 'Key', 'Reference Type'],
$issues->sort()->map(function ($issues, $file) {
return collect($issues)->map(function ($issue) use ($file) {
return [
'file' => $file,
'line' => $issue['line'],
'key' => $issue['key'],
'type' => $issue['type'],
];
});
})->flatten(1)->toArray()
);
return $combinedIssues;
}

private function checkPhpFiles(): void
{
$finder = new Finder;
$finder->files()->in($this->laravel->basePath())
->name('*.php')
->path('app')
->path('database')
->path('routes')
->path('bootstrap')
->notPath('vendor');
public function handle(
LoadConfigKeys $loadConfigKeys,
PhpFiles $phpFiles,
BladeFiles $bladeFiles
): int {
$this->configKeys = $loadConfigKeys();

$progress = progress(
label: 'Checking PHP files...',
steps: $finder,
steps: $phpFiles(),
callback: function ($file, $progress) {
$progress->hint = "Checking {$file->getRelativePathname()}";

$this->issues[$file->getRelativePathname()] = array_merge(
$this->issues[$file->getRelativePathname()] ?? [],
$this->checkForFacadeUsage($file),
$this->checkForHelperUsage($file)
);
$content = file_get_contents($file->getRealPath());

$fileChecker = new FileChecker($this->configKeys, $content);

foreach ($fileChecker->check() as $issue) {
$this->phpIssues[$file->getRelativePathname()][] = $issue;
}
}
);
}

private function checkBladeFiles(): void
{
$finder = new Finder;
$finder->files()->in($this->laravel->basePath())
->name('*.blade.php')
->notPath('vendor');

$progress = progress(
label: 'Checking Blade files...',
steps: $finder,
steps: $bladeFiles(),
callback: function ($file, $progress) {
$progress->hint = "Checking {$file->getRelativePathname()}";

$this->issues[$file->getRelativePathname()] = array_merge(
$this->issues[$file->getRelativePathname()] ?? [],
$this->checkForFacadeUsage($file),
$this->checkForHelperUsage($file)
);
$content = file_get_contents($file->getRealPath());
$fileChecker = new FileChecker($this->configKeys, $content);

foreach ($fileChecker->check() as $issue) {
$this->bladeIssues[$file->getRelativePathname()][] = $issue;
}
}
);
}

private function checkForFacadeUsage($file): array
{
$content = file_get_contents($file->getRealPath());
$matches = [];

preg_match_all('/Config::(get|has)\([\'"]([^\'"]+)[\'"]\)/', $content, $matches, PREG_OFFSET_CAPTURE);

$issues = [];

foreach ($matches[2] as $index => $match) {
$key = $match[0];
$offset = (int) $match[1];
$lineNumber = substr_count(substr($content, 0, $offset), "\n") + 1;
if ($this->getIssues()->isEmpty()) {
info('No issues found. All config references are valid.');

if (! in_array($key, $this->configKeys)) {
$issues[] = [
'file' => $file->getRelativePathname(),
'key' => $key,
'type' => sprintf('Config::%s()', $matches[1][$index][0]),
'line' => $lineNumber,
];
}
return self::SUCCESS;
}

return $issues;
$this->displayResults();

return self::SUCCESS;
}

private function checkForHelperUsage($file): array
private function displayResults(): void
{
$content = file_get_contents($file->getRealPath());
$matches = [];

preg_match_all('/config\([\'"]([^\'"]+)[\'"]\)/', $content, $matches, PREG_OFFSET_CAPTURE);
error('Invalid config references found:');

$issues = [];
$rowData = $this->formatIssuesOutput();

foreach ($matches[1] as $match) {
$key = $match[0];
$offset = (int) $match[1];
$lineNumber = substr_count(substr($content, 0, $offset), "\n") + 1;

if (! in_array($key, $this->configKeys)) {
$issues[] = [
'file' => $file->getRelativePathname(),
'key' => $key,
'type' => 'config()',
'line' => $lineNumber,
];
}
}
table(
['File', 'Line Number', 'Key Referenced', 'Reference Type'],
$rowData,
);
}

return $issues;
private function formatIssuesOutput(): array
{
return $this->getIssues()->sort()
->flatMap(function ($issues, $file) {
return collect($issues)
->sortBy('line')
->map(fn ($issue) => [
$file,
$issue->line,
$issue->key,
$issue->type,
]);
})
// ->flatten(1)
->toArray();
}
}
30 changes: 30 additions & 0 deletions src/Support/BladeFiles.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace ChrisDiCarlo\LaravelConfigChecker\Support;

use Symfony\Component\Finder\Finder;

class BladeFiles
{
private string $basePath;

public function __construct(?string $basePath = null)
{
if (! $basePath) {
$basePath = base_path();
}

$this->basePath = $basePath;
}

public function __invoke(): Finder
{
$finder = new Finder;
$finder->files()->in($this->basePath)
->name('*.blade.php')
->path('resources/views')
->notPath('vendor');

return $finder;
}
}
12 changes: 12 additions & 0 deletions src/Support/FileCheckInfo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace ChrisDiCarlo\LaravelConfigChecker\Support;

class FileCheckInfo
{
public function __construct(
public readonly int $line,
public readonly string $key,
public readonly string $type
) {}
}
Loading

0 comments on commit 9723fa0

Please sign in to comment.