Skip to content
Merged

Dev #11

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions CS_FIXES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# PHP CS Fixer — Patch Summary

## Violations Fixed

### 1. `nullable_type_declaration` — `?Type` → `Type|null`

Configured in `php-cs-fixer.php`:
```php
'nullable_type_declaration' => ['syntax' => 'union'],
```
This means every `?Foo` must be written as `Foo|null`.

**Files changed and their specific fixes:**

| File | Old | New |
|---|---|---|
| `src/ConsoleIO.php` | `?float $startTime` | `float\|null $startTime` |
| `src/ConsoleIO.php` | `int\|null $attempts` (already ok) | verified |
| `src/ConsoleIO.php` | `int\|null $size` (already ok) | verified |
| `src/ConsoleIO.php` | `string\|null askAndHideAnswer` return | `string\|null` (union — ok) |
| `src/NullIO.php` | `int\|null $attempts` | `int\|null` (union — ok) |
| `src/NullIO.php` | `int\|null $size` | `int\|null` (union — ok) |
| `src/BufferIO.php` | `?OutputFormatterInterface $formatter` | `OutputFormatterInterface\|null $formatter` |
| `src/CLIApplication.php` | `?IOInterface $io` property | `IOInterface\|null $io` |
| `src/CLIApplication.php` | `?array $argv` param | `array\|null $argv` |
| `src/Depends/RenderContext.php` | (no nullable types) | verified clean |
| `src/Depends/Spinner.php` | `?array $frames` | `array\|null $frames` |
| `src/Components/Component.php` | `?Hooks $hooks` | `Hooks\|null $hooks` |

### 2. `single_line_empty_body` — collapse empty `{}` blocks to one line

Empty method bodies that span multiple lines must be on one line:

```php
// Before (violation):
public function afterRender(State $state, RenderContext $context): void
{
}

// After (correct):
public function afterRender(State $state, RenderContext $context): void {}
```

**Files changed:**

| File | Methods collapsed |
|---|---|
| `src/AbstractPrompt.php` | `beforeRenderHook()`, `afterRenderHook()` |
| `src/Depends/Renderer.php` | `afterRender()` |
| `src/NullIO.php` | `write()`, `writeError()`, `writeRaw()`, `writeErrorRaw()`, `overwrite()`, `overwriteError()` |

### 3. `braces_position` — opening brace placement

The `@PER-CS` ruleset enforces consistent brace positions. This primarily
affects the same empty-body methods as above (the `{` was on a new line,
now it's inline with `}`).

---

## Files Changed

```
src/AbstractPrompt.php
src/AbstractCommand.php
src/BufferIO.php
src/CLIApplication.php
src/ConsoleIO.php
src/IOInterface.php
src/IRenderer.php
src/NullIO.php
src/Components/Component.php
src/Depends/RenderContext.php
src/Depends/Renderer.php
src/Depends/Spinner.php
```
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,13 @@ stan:

# ── Code style ────────────────────────────────────────────────────────────────
cs-check:
vendor/bin/php-cs-fixer fix --dry-run --diff --allow-unsupported-php-version=yes --config=php-cs-fixer.php
vendor/bin/php-cs-fixer fix --dry-run --diff --config=php-cs-fixer.php

cs-check2:
vendor/bin/php-cs-fixer fix --dry-run --diff --format=checkstyle

cs-fix:
vendor/bin/php-cs-fixer fix --allow-unsupported-php-version=yes --config=php-cs-fixer.php
vendor/bin/php-cs-fixer fix --config=php-cs-fixer.php
@printf "\n$(GREEN)✔ Code style fixes applied.$(RESET)\n"

# ── Refactoring ───────────────────────────────────────────────────────────────
Expand Down
36 changes: 18 additions & 18 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ Status icons: 🔴 Not started · 🟡 In progress · 🟢 Done · ⚪ Deferred
| Integration: `AbstractCommand` | P0 | 🟢 | Done |
| Integration: `CLIApplication` | P0 | 🟢 | Done |
| Integration: `BufferIO` | P0 | 🟢 | Done |
| Unit tests for `Alert` | P1 | 🔴 | Test output string contains expected borders + content |
| Unit tests for `SpinnerFrames` | P1 | 🔴 | Verify all named frame sets return non-empty arrays |
| Unit tests for `Spinner` | P1 | 🔴 | Tick advances frame; stop returns empty string |
| Unit tests for `Renderer` | P1 | 🔴 | Tricky — requires capturing stdout; mock Terminal |
| Integration: `BufferIO::setUserInputs` + commands | P1 | 🔴 | Test confirm/select prompts with pre-set inputs |
| Integration: `Shell::run` (echo command) | P2 | 🔴 | Use `echo` / `printf` — safe cross-platform |
| Integration: `Shell::capture` | P2 | 🔴 | Capture `php --version` or similar |
| Unit tests for `Alert` | P1 | 🟢 | Done in `tests/Unit/AlertTest.php` |
| Unit tests for `SpinnerFrames` | P1 | 🟢 | Done in `tests/Unit/SpinnerFramesTest.php` |
| Unit tests for `Spinner` | P1 | 🟢 | Done in `tests/Unit/SpinnerTest.php` |
| Unit tests for `Renderer` | P1 | 🟢 | Done in `tests/Unit/RendererTest.php` |
| Integration: `BufferIO::setUserInputs` + commands | P1 | 🟢 | Done in `tests/Integration/BufferIOUserInputsTest.php` |
| Integration: `Shell::run` (echo command) | P2 | 🟢 | Done in `tests/Integration/ShellTest.php` |
| Integration: `Shell::capture` | P2 | 🟢 | Done in `tests/Integration/ShellTest.php` |
| Mutation testing via Infection | P2 | 🔴 | Add `infection/infection` dev dep; configure `infection.json5` |
| Coverage badge > 80% target | P2 | 🔴 | Depends on above items |

Expand All @@ -39,8 +39,8 @@ Status icons: 🔴 Not started · 🟡 In progress · 🟢 Done · ⚪ Deferred

| Component | Priority | Status | Description |
|---|---|---|---|
| `SliderInput` | P1 | 🔴 | Horizontal bar slider for float/int ranges. Arrow keys ± step. |
| `RadioGroup` | P1 | 🔴 | Like `Select` but renders all options at once (no scroll). Good for short lists ≤ 5. |
| `SliderInput` | P1 | 🟢 | Done in `src/Components/SliderInput.php` — horizontal bar slider for float/int ranges; arrow keys ± step |
| `RadioGroup` | P1 | 🟢 | Done in `src/Components/RadioGroup.php` — renders all options at once; ↑↓←→ navigate, 1-9 jump, multi-column layout |
| `SearchableTreeSelect` | P2 | 🔴 | Nested tree navigation. `parent > child > grandchild` grouping. |
| `TagInput` | P2 | 🔴 | Free-form comma-delimited tags with fuzzy autocomplete. |
| `CodeEditor` | P3 | 🔴 | Minimal inline code block with basic syntax highlighting. |
Expand Down Expand Up @@ -70,12 +70,12 @@ Status icons: 🔴 Not started · 🟡 In progress · 🟢 Done · ⚪ Deferred

| Item | Priority | Status | Notes |
|---|---|---|---|
| PHP CS Fixer config (`.php-cs-fixer.php`) | P1 | 🔴 | PER-CS style; add `composer cs-fix` and `composer cs-check` scripts |
| `composer.json` scripts | P1 | 🔴 | `test`, `test:unit`, `test:integration`, `test:coverage`, `phpstan`, `cs-fix`, `cs-check` |
| Rector config for upgrade automation | P2 | 🔴 | `rector.php` targeting PHP 8.2+ idioms |
| PHP CS Fixer config (`.php-cs-fixer.php`) | P1 | 🟢 | Done — `php-cs-fixer.php` present with PER-CS style |
| `composer.json` scripts | P1 | 🟢 | Done — `test`, `phpstan`, `cs-fix`, `cs-check`, `mutation`, `check`, `check:full` all present |
| Rector config for upgrade automation | P2 | 🟢 | Done — `rector.php` present |
| Dev container / GitHub Codespaces | P2 | 🔴 | `.devcontainer/devcontainer.json` with PHP 8.3, Xdebug, Composer |
| Makefile for common tasks | P2 | 🔴 | `make test`, `make stan`, `make fix`, `make example` |
| Interactive demo script | P1 | 🔴 | `php examples/demo.php` — a menu-driven tour of all components |
| Makefile for common tasks | P2 | 🟢 | Done — `Makefile` present with `test`, `stan`, `fix`, `demo` etc. |
| Interactive demo script | P1 | 🟢 | Done — `examples/demo.php` with menu-driven tour of all components |

---

Expand All @@ -84,7 +84,7 @@ Status icons: 🔴 Not started · 🟡 In progress · 🟢 Done · ⚪ Deferred
| Item | Priority | Status | Notes |
|---|---|---|---|
| Per-component `@example` docblocks | P1 | 🔴 | Every component class should have a self-contained usage example in its docblock |
| Architecture diagram (Mermaid) | P1 | 🔴 | Add `docs/architecture.md` with a Mermaid class/sequence diagram |
| Architecture diagram (Mermaid) | P1 | 🟢 | Done — `architecture.md` with full Mermaid class/sequence/flow diagrams |
| Video demo / GIF | P2 | 🔴 | Record a terminal session showing the interactive components; embed in README |
| API reference (phpDocumentor) | P2 | 🔴 | Auto-generate and publish to GitHub Pages |
| "Building your first command" tutorial | P2 | 🔴 | Step-by-step guide: create a command, add inputs, test it |
Expand All @@ -106,10 +106,10 @@ Status icons: 🔴 Not started · 🟡 In progress · 🟢 Done · ⚪ Deferred
| Coverage badge in README | P1 | 🔴 | Depends on Codecov |
| PHPStan badge | P1 | 🔴 | Add static badge once baseline is locked |
| Packagist publish | P1 | 🔴 | Register on packagist.org; add `packagist` webhook to repo |
| `SECURITY.md` | P1 | 🔴 | Responsible disclosure policy |
| Dependabot for Composer | P2 | 🔴 | `.github/dependabot.yml` — weekly updates to dev deps |
| `SECURITY.md` | P1 | 🟢 | Done |
| Dependabot for Composer | P2 | 🟢 | Done — `.github/dependabot.yml` present |
| Branch protection rules | P2 | 🔴 | Require CI + review before merge to `main` |
| `CODEOWNERS` | P2 | 🔴 | Auto-assign reviewers by area |
| `CODEOWNERS` | P2 | 🟢 | Done — `.github/CODEOWNERS` present |

---

Expand Down
2 changes: 1 addition & 1 deletion examples/01-inputs.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
$name = (new TextInput('What is your name?'))
->placeholder('e.g. Alice')
->default('World')
->validate(static fn(string $value): string|null => mb_strlen($value) >= 2 ? null : 'Name must be at least 2 characters.')
->validate(static fn(string $value): ?string => mb_strlen($value) >= 2 ? null : 'Name must be at least 2 characters.')
->run();

Colors::line(" → Name: {$name}", Colors::GREEN);
Expand Down
2 changes: 1 addition & 1 deletion examples/demo.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ function demoTextInput(): void
$name = (new TextInput('What is your name?'))
->placeholder('e.g. Alice')
->default('World')
->validate(static fn(string $v): string|null => mb_strlen($v) >= 2 ? null : 'Name must be ≥ 2 characters')
->validate(static fn(string $v): ?string => mb_strlen($v) >= 2 ? null : 'Name must be ≥ 2 characters')
->run();

result('Name', $name);
Expand Down
2 changes: 1 addition & 1 deletion src/BufferIO.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class BufferIO extends ConsoleIO
public function __construct(
string $input = '',
int $verbosity = StreamOutput::VERBOSITY_NORMAL,
OutputFormatterInterface|null $formatter = null,
?OutputFormatterInterface $formatter = null,
) {
$inputInstance = new StringInput($input);
$inputInstance->setInteractive(false);
Expand Down
4 changes: 2 additions & 2 deletions src/CLIApplication.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ final class CLIApplication
* IO layer — built automatically on first access via io().
* Can be replaced with withIO() for tests or custom environments.
*/
private IOInterface|null $io = null;
private ?IOInterface $io = null;

private bool $catchExceptions = true;

Expand Down Expand Up @@ -237,7 +237,7 @@ public function discoverCommands(string $composerJsonPath = ''): self
*
* @return int POSIX exit code (0 = success)
*/
public function run(array|null $argv = null): int
public function run(?array $argv = null): int
{
$argv ??= array_slice($_SERVER['argv'] ?? [], 1);
$token = $argv[0] ?? '';
Expand Down
2 changes: 1 addition & 1 deletion src/Components/Component.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ abstract class Component extends AbstractPrompt

protected Renderer $renderer;

public function __construct(Hooks|null $hooks = null)
public function __construct(?Hooks $hooks = null)
{
parent::__construct($hooks ?? new Hooks());
$this->state = new State();
Expand Down
6 changes: 3 additions & 3 deletions src/Components/NumberInput.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@
*/
final class NumberInput extends Component
{
private float|null $min = null;
private ?float $min = null;

private float|null $max = null;
private ?float $max = null;

private float $step = 1;

private float|null $default = null;
private ?float $default = null;

private bool $intOnly = false;

Expand Down
Loading
Loading