Skip to content

Commit e43937d

Browse files
committed
fix(type-safety): eliminate 35 PHPStan level-9 errors across codebase
Runners (CsFixerRunner, PhpStanRunner, PhpUnitRunner, PsalmRunner, RectorRunner, ComposerAuditRunner): - Add #[Override] attribute to toolName(), vendorBin(), defaultArguments() PhpStanConfigGenerator: - Remove checkMissingIterableValueType and checkGenericClassInNonGenericObjectType (removed from PHPStan 2.x core; cause Invalid configuration errors) CsFixerConfigGenerator: - Add (string) cast on preg_replace() return values in exportRules() Devkit.php: - Add false !== guard before str_contains() in appendGitignore() - Add SplFileInfo @var in removeRecursive() foreach DevkitConfig.php: - Add @var array<string, mixed> before assigning overrides - Fix toolVersions() to filter array<string,string> via array_filter MigrationDetector.php + MigrationReport.php: - Add false !== guard on file_get_contents() and json_encode() - Extract requireDev with @var and is_array() for type-safe access - Fix removePackagesFromComposer() to write back updated requireDev - Add SplFileInfo @var in removeRecursive() foreach ProjectDetector.php: - Add false !== guard on file_get_contents() - Extract psr4Source, psr4Test, projectName with proper is_array() guards - Fix detectNamespace(): double is_array() for autoload + psr-4 sub-access - Fix detectPhpVersion(): use is_string() instead of (string) cast - Precise @var annotations for all array parameters Result: PHPStan level 9 -> 0 errors, PHPUnit 41/41 OK
1 parent a784a42 commit e43937d

35 files changed

Lines changed: 604 additions & 357 deletions

bin/build-phar.php

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* Manual PHAR builder for kcode — bypasses Box chdir() bug on PHP 8.4
6+
*/
7+
8+
$root = dirname(__DIR__);
9+
$output = $root . '/build/kcode.phar';
10+
11+
if (file_exists($output)) {
12+
unlink($output);
13+
}
14+
15+
@mkdir($root . '/build', 0755, true);
16+
17+
$phar = new Phar($output, 0, 'kcode.phar');
18+
$phar->startBuffering();
19+
20+
echo "📦 Building kcode.phar...\n";
21+
22+
// ── 1. Add src/ ────────────────────────────────────────────
23+
$added = 0;
24+
$srcDir = $root . '/src';
25+
$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($srcDir, FilesystemIterator::SKIP_DOTS));
26+
foreach ($it as $file) {
27+
if ($file->isFile() && $file->getExtension() === 'php') {
28+
$relative = 'src/' . $it->getSubPathname();
29+
$phar[$relative] = file_get_contents($file->getPathname());
30+
$added++;
31+
}
32+
}
33+
echo " + src/: $added PHP files\n";
34+
35+
// ── 2. Add vendor/ (PHP files only, no tests/docs) ─────────
36+
$vendorDir = $root . '/vendor';
37+
$excludeDirs = ['Tests', 'tests', 'test', 'doc', 'docs', 'examples', '.github'];
38+
39+
$vendorIt = new RecursiveIteratorIterator(
40+
new RecursiveDirectoryIterator($vendorDir, FilesystemIterator::SKIP_DOTS),
41+
RecursiveIteratorIterator::LEAVES_ONLY
42+
);
43+
44+
$vendorAdded = 0;
45+
foreach ($vendorIt as $file) {
46+
if (!$file->isFile()) continue;
47+
$path = $file->getPathname();
48+
49+
// Skip test/doc directories
50+
$skip = false;
51+
foreach ($excludeDirs as $ex) {
52+
if (str_contains($path, DIRECTORY_SEPARATOR . $ex . DIRECTORY_SEPARATOR)) {
53+
$skip = true;
54+
break;
55+
}
56+
}
57+
if ($skip) continue;
58+
59+
// Only PHP and JSON files
60+
$ext = $file->getExtension();
61+
if (!in_array($ext, ['php', 'json'], true)) continue;
62+
63+
$relative = 'vendor/' . substr($path, strlen($vendorDir) + 1);
64+
$phar[$relative] = file_get_contents($path);
65+
$vendorAdded++;
66+
}
67+
echo " + vendor/: $vendorAdded files\n";
68+
69+
// ── 3. Add LICENSE ──────────────────────────────────────────
70+
$phar['LICENSE'] = file_get_contents($root . '/LICENSE');
71+
echo " + LICENSE\n";
72+
73+
// ── 4. Set bin/kcode as the entry point (stub) ─────────────
74+
$kcodeContent = file_get_contents($root . '/bin/kcode');
75+
$phar['bin/kcode'] = $kcodeContent;
76+
77+
$stub = <<<'STUB'
78+
#!/usr/bin/env php
79+
<?php
80+
/*
81+
* KaririCode Devkit — kcode
82+
*
83+
* Unified quality toolchain for KaririCode Framework.
84+
*
85+
* (c) Walmir Silva <walmir.silva@kariricode.org>
86+
*
87+
* For the full copyright and license information, please view
88+
* the LICENSE file that was distributed with this source code.
89+
*/
90+
Phar::mapPhar('kcode.phar');
91+
require 'phar://kcode.phar/bin/kcode';
92+
__HALT_COMPILER();
93+
STUB;
94+
95+
$phar->setStub($stub);
96+
echo " + stub (bin/kcode entry point)\n";
97+
98+
// ── 5. Finalize ─────────────────────────────────────────────
99+
$phar->stopBuffering();
100+
$phar->compressFiles(Phar::GZ);
101+
chmod($output, 0755);
102+
103+
$size = round(filesize($output) / 1024 / 1024, 2);
104+
echo "\n✅ Built: $output ($size MB)\n";
105+
echo " Files: " . count($phar) . "\n";

src/Command/AbstractCommand.php

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,27 +24,27 @@ abstract public function execute(Devkit $devkit, array $arguments): int;
2424

2525
protected function info(string $message): void
2626
{
27-
\fwrite(\STDOUT, "\033[32m✓\033[0m {$message}" . \PHP_EOL);
27+
fwrite(\STDOUT, "\033[32m✓\033[0m {$message}" . \PHP_EOL);
2828
}
2929

3030
protected function warning(string $message): void
3131
{
32-
\fwrite(\STDOUT, "\033[33m⚠\033[0m {$message}" . \PHP_EOL);
32+
fwrite(\STDOUT, "\033[33m⚠\033[0m {$message}" . \PHP_EOL);
3333
}
3434

3535
protected function error(string $message): void
3636
{
37-
\fwrite(\STDERR, "\033[31m✗\033[0m {$message}" . \PHP_EOL);
37+
fwrite(\STDERR, "\033[31m✗\033[0m {$message}" . \PHP_EOL);
3838
}
3939

4040
protected function line(string $message = ''): void
4141
{
42-
\fwrite(\STDOUT, $message . \PHP_EOL);
42+
fwrite(\STDOUT, $message . \PHP_EOL);
4343
}
4444

4545
protected function banner(string $title): void
4646
{
47-
$ruler = \str_repeat('', 60);
47+
$ruler = str_repeat('', 60);
4848
$this->line("\033[36m{$ruler}\033[0m");
4949
$this->line("\033[1m {$title}\033[0m");
5050
$this->line("\033[36m{$ruler}\033[0m");
@@ -65,15 +65,15 @@ protected function section(string $title): void
6565
protected function confirm(string $question, bool $default = false): bool
6666
{
6767
$hint = $default ? '[Y/n]' : '[y/N]';
68-
\fwrite(\STDOUT, "\033[33m?\033[0m {$question} {$hint} ");
68+
fwrite(\STDOUT, "\033[33m?\033[0m {$question} {$hint} ");
6969

70-
$input = \trim((string) \fgets(\STDIN));
70+
$input = trim((string) fgets(\STDIN));
7171

7272
if ('' === $input) {
7373
return $default;
7474
}
7575

76-
return \in_array(\strtolower($input), ['y', 'yes', 'sim', 's'], true);
76+
return \in_array(strtolower($input), ['y', 'yes', 'sim', 's'], true);
7777
}
7878

7979
// ── Argument Helpers ──────────────────────────────────────────
@@ -100,8 +100,8 @@ protected function option(array $arguments, string $key, ?string $default = null
100100
$prefix = "--{$key}=";
101101

102102
foreach ($arguments as $arg) {
103-
if (\str_starts_with($arg, $prefix)) {
104-
return \substr($arg, \strlen($prefix));
103+
if (str_starts_with($arg, $prefix)) {
104+
return substr($arg, \strlen($prefix));
105105
}
106106
}
107107

@@ -116,9 +116,9 @@ protected function option(array $arguments, string $key, ?string $default = null
116116
*/
117117
protected function positional(array $arguments): array
118118
{
119-
return \array_values(\array_filter(
119+
return array_values(array_filter(
120120
$arguments,
121-
static fn (string $arg): bool => !\str_starts_with($arg, '--'),
121+
static fn (string $arg): bool => ! str_starts_with($arg, '--'),
122122
));
123123
}
124124

@@ -131,9 +131,9 @@ protected function positional(array $arguments): array
131131
*/
132132
protected function passthrough(array $arguments, array $consume = []): array
133133
{
134-
return \array_values(\array_filter(
134+
return array_values(array_filter(
135135
$arguments,
136-
static fn (string $arg): bool => !\in_array($arg, $consume, true),
136+
static fn (string $arg): bool => ! \in_array($arg, $consume, true),
137137
));
138138
}
139139
}

src/Command/AnalyseCommand.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,27 @@
1313
*/
1414
final class AnalyseCommand extends AbstractCommand
1515
{
16+
#[\Override]
1617
public function name(): string
1718
{
1819
return 'analyse';
1920
}
2021

22+
#[\Override]
2123
public function description(): string
2224
{
2325
return 'Run static analysis (PHPStan + Psalm)';
2426
}
2527

28+
#[\Override]
2629
public function execute(Devkit $devkit, array $arguments): int
2730
{
2831
$this->banner('KaririCode Devkit — Analyse');
2932

3033
$exitCode = 0;
3134

3235
foreach (['phpstan', 'psalm'] as $tool) {
33-
if (!$devkit->isToolAvailable($tool)) {
36+
if (! $devkit->isToolAvailable($tool)) {
3437
$this->warning("{$tool} not available — skipping");
3538

3639
continue;
@@ -44,7 +47,7 @@ public function execute(Devkit $devkit, array $arguments): int
4447
$this->info(\sprintf('%s passed (%.2fs)', $tool, $result->elapsedSeconds));
4548
} else {
4649
$this->error(\sprintf('%s failed — exit code %d (%.2fs)', $tool, $result->exitCode, $result->elapsedSeconds));
47-
$exitCode = \max($exitCode, $result->exitCode);
50+
$exitCode = max($exitCode, $result->exitCode);
4851
}
4952

5053
$this->line();

src/Command/Application.php

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public function register(AbstractCommand $command): void
3333
public function run(array $argv): int
3434
{
3535
// Strip script name
36-
\array_shift($argv);
36+
array_shift($argv);
3737

3838
if ([] === $argv || $this->isHelp($argv)) {
3939
$this->printUsage();
@@ -47,20 +47,20 @@ public function run(array $argv): int
4747
return 0;
4848
}
4949

50-
$commandName = \array_shift($argv);
50+
$commandName = array_shift($argv);
5151
$command = $this->commands[$commandName] ?? null;
5252

5353
if (null === $command) {
54-
\fwrite(\STDERR, "\033[31m✗\033[0m Unknown command: {$commandName}" . \PHP_EOL);
55-
\fwrite(\STDERR, " Run \033[1mkcode --help\033[0m for available commands." . \PHP_EOL);
54+
fwrite(\STDERR, "\033[31m✗\033[0m Unknown command: {$commandName}" . \PHP_EOL);
55+
fwrite(\STDERR, " Run \033[1mkcode --help\033[0m for available commands." . \PHP_EOL);
5656

5757
return 1;
5858
}
5959

6060
try {
6161
return $command->execute($this->devkit, $argv);
6262
} catch (\Throwable $exception) {
63-
\fwrite(\STDERR, "\033[31m✗\033[0m {$exception->getMessage()}" . \PHP_EOL);
63+
fwrite(\STDERR, "\033[31m✗\033[0m {$exception->getMessage()}" . \PHP_EOL);
6464

6565
return 1;
6666
}
@@ -82,7 +82,7 @@ private function isVersion(array $argv): bool
8282

8383
private function printVersion(): void
8484
{
85-
\fwrite(\STDOUT, \sprintf(
85+
fwrite(\STDOUT, \sprintf(
8686
"\033[1mKaririCode Devkit\033[0m %s" . \PHP_EOL,
8787
Devkit::version(),
8888
));
@@ -91,28 +91,28 @@ private function printVersion(): void
9191
private function printUsage(): void
9292
{
9393
$this->printVersion();
94-
\fwrite(\STDOUT, \PHP_EOL);
95-
\fwrite(\STDOUT, "\033[33mUsage:\033[0m" . \PHP_EOL);
96-
\fwrite(\STDOUT, " kcode <command> [options] [arguments]" . \PHP_EOL . \PHP_EOL);
97-
\fwrite(\STDOUT, "\033[33mAvailable commands:\033[0m" . \PHP_EOL);
94+
fwrite(\STDOUT, \PHP_EOL);
95+
fwrite(\STDOUT, "\033[33mUsage:\033[0m" . \PHP_EOL);
96+
fwrite(\STDOUT, " kcode <command> [options] [arguments]" . \PHP_EOL . \PHP_EOL);
97+
fwrite(\STDOUT, "\033[33mAvailable commands:\033[0m" . \PHP_EOL);
9898

9999
$maxLen = 0;
100100

101101
foreach ($this->commands as $name => $command) {
102-
$maxLen = \max($maxLen, \strlen($name));
102+
$maxLen = max($maxLen, \strlen($name));
103103
}
104104

105105
foreach ($this->commands as $name => $command) {
106-
\fwrite(\STDOUT, \sprintf(
106+
fwrite(\STDOUT, \sprintf(
107107
" \033[32m%-{$maxLen}s\033[0m %s" . \PHP_EOL,
108108
$name,
109109
$command->description(),
110110
));
111111
}
112112

113-
\fwrite(\STDOUT, \PHP_EOL);
114-
\fwrite(\STDOUT, "\033[33mOptions:\033[0m" . \PHP_EOL);
115-
\fwrite(\STDOUT, " \033[32m-h, --help\033[0m Show this help" . \PHP_EOL);
116-
\fwrite(\STDOUT, " \033[32m-V, --version\033[0m Show version" . \PHP_EOL);
113+
fwrite(\STDOUT, \PHP_EOL);
114+
fwrite(\STDOUT, "\033[33mOptions:\033[0m" . \PHP_EOL);
115+
fwrite(\STDOUT, " \033[32m-h, --help\033[0m Show this help" . \PHP_EOL);
116+
fwrite(\STDOUT, " \033[32m-V, --version\033[0m Show version" . \PHP_EOL);
117117
}
118118
}

src/Command/CleanCommand.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,19 @@
1313
*/
1414
final class CleanCommand extends AbstractCommand
1515
{
16+
#[\Override]
1617
public function name(): string
1718
{
1819
return 'clean';
1920
}
2021

22+
#[\Override]
2123
public function description(): string
2224
{
2325
return 'Remove .kcode/build/ (caches, coverage, reports)';
2426
}
2527

28+
#[\Override]
2629
public function execute(Devkit $devkit, array $arguments): int
2730
{
2831
$this->banner('KaririCode Devkit — Clean');

src/Command/CsFixCommand.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,19 @@
1313
*/
1414
final class CsFixCommand extends AbstractCommand
1515
{
16+
#[\Override]
1617
public function name(): string
1718
{
1819
return 'cs:fix';
1920
}
2021

22+
#[\Override]
2123
public function description(): string
2224
{
2325
return 'Fix code style (--check for dry-run)';
2426
}
2527

28+
#[\Override]
2629
public function execute(Devkit $devkit, array $arguments): int
2730
{
2831
$dryRun = $this->hasFlag($arguments, '--check', '--dry-run');

src/Command/FormatCommand.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,19 @@
1313
*/
1414
final class FormatCommand extends AbstractCommand
1515
{
16+
#[\Override]
1617
public function name(): string
1718
{
1819
return 'format';
1920
}
2021

22+
#[\Override]
2123
public function description(): string
2224
{
2325
return 'Apply all formatting (cs:fix + rector --fix)';
2426
}
2527

28+
#[\Override]
2629
public function execute(Devkit $devkit, array $arguments): int
2730
{
2831
$this->banner('KaririCode Devkit — Format');
@@ -55,7 +58,7 @@ public function execute(Devkit $devkit, array $arguments): int
5558
$this->info(\sprintf('Rector done (%.2fs)', $result->elapsedSeconds));
5659
} else {
5760
$this->error(\sprintf('Rector failed (%.2fs)', $result->elapsedSeconds));
58-
$exitCode = \max($exitCode, $result->exitCode);
61+
$exitCode = max($exitCode, $result->exitCode);
5962
}
6063
}
6164

0 commit comments

Comments
 (0)