From e6ce9f86527939df56c72467265f6f73412fe32c Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 8 Feb 2026 11:58:24 +0100 Subject: [PATCH] add NoDiscard attributes --- psalm.xml | 3 +++ src/Command.php | 2 ++ src/Command/Arguments.php | 4 ++++ src/Command/Name.php | 2 ++ src/Command/Options.php | 3 +++ src/Command/Usage.php | 15 +++++++++++++++ src/Commands.php | 3 +++ src/Console.php | 10 ++++++++++ src/Environment.php | 10 ++++++++++ src/Environment/ExitCode.php | 2 ++ src/Question/ChoiceQuestion.php | 2 ++ src/Question/Question.php | 2 ++ tests/Command/PatternTest.php | 2 +- tests/Command/UsageTest.php | 2 +- 14 files changed, 60 insertions(+), 2 deletions(-) diff --git a/psalm.xml b/psalm.xml index 510148d..feccc34 100644 --- a/psalm.xml +++ b/psalm.xml @@ -14,4 +14,7 @@ + + + diff --git a/src/Command.php b/src/Command.php index bca2db6..c85ba22 100644 --- a/src/Command.php +++ b/src/Command.php @@ -11,10 +11,12 @@ interface Command /** * @return Attempt */ + #[\NoDiscard] public function __invoke(Console $console): Attempt; /** * @psalm-mutation-free */ + #[\NoDiscard] public function usage(): Usage; } diff --git a/src/Command/Arguments.php b/src/Command/Arguments.php index c02d17d..c075bdd 100644 --- a/src/Command/Arguments.php +++ b/src/Command/Arguments.php @@ -30,6 +30,7 @@ public function __construct(?Map $arguments = null, ?Sequence $pack = null) $this->pack = $pack ?? Sequence::strings(); } + #[\NoDiscard] public function get(string $argument): string { return $this->maybe($argument)->match( @@ -41,6 +42,7 @@ public function get(string $argument): string /** * @return Maybe */ + #[\NoDiscard] public function maybe(string $argument): Maybe { return $this->arguments->get($argument); @@ -49,11 +51,13 @@ public function maybe(string $argument): Maybe /** * @return Sequence */ + #[\NoDiscard] public function pack(): Sequence { return $this->pack; } + #[\NoDiscard] public function contains(string $argument): bool { return $this->arguments->contains($argument); diff --git a/src/Command/Name.php b/src/Command/Name.php index a505392..73d8270 100644 --- a/src/Command/Name.php +++ b/src/Command/Name.php @@ -21,11 +21,13 @@ public function __construct( /** * @return non-empty-string */ + #[\NoDiscard] public function name(): string { return $this->name; } + #[\NoDiscard] public function shortDescription(): ?string { return $this->shortDescription; diff --git a/src/Command/Options.php b/src/Command/Options.php index 4808296..d27aa3d 100644 --- a/src/Command/Options.php +++ b/src/Command/Options.php @@ -25,6 +25,7 @@ public function __construct(?Map $options = null) $this->options = $options ?? Map::of(); } + #[\NoDiscard] public function get(string $option): string { return $this->maybe($option)->match( @@ -36,11 +37,13 @@ public function get(string $option): string /** * @return Maybe */ + #[\NoDiscard] public function maybe(string $option): Maybe { return $this->options->get($option); } + #[\NoDiscard] public function contains(string $option): bool { return $this->options->contains($option); diff --git a/src/Command/Usage.php b/src/Command/Usage.php index 84b7483..e890d39 100644 --- a/src/Command/Usage.php +++ b/src/Command/Usage.php @@ -47,6 +47,7 @@ private function __construct( * * @param non-empty-string $name */ + #[\NoDiscard] public static function of(string $name): self { /** @var ?string */ @@ -67,6 +68,7 @@ public static function of(string $name): self * * @param class-string $class */ + #[\NoDiscard] public static function for(string $class): self { $refl = new \ReflectionClass($class); @@ -94,6 +96,7 @@ public static function for(string $class): self /** * @psalm-pure */ + #[\NoDiscard] public static function parse(string $usage): self { $declaration = Str::of($usage)->trim(); @@ -144,6 +147,7 @@ public static function parse(string $usage): self /** * @param non-empty-string $name */ + #[\NoDiscard] public function argument(string $name): self { $_ = $this @@ -168,6 +172,7 @@ public function argument(string $name): self /** * @param non-empty-string $name */ + #[\NoDiscard] public function optionalArgument(string $name): self { return new self( @@ -180,6 +185,7 @@ public function optionalArgument(string $name): self ); } + #[\NoDiscard] public function packArguments(): self { return new self( @@ -196,6 +202,7 @@ public function packArguments(): self * @param non-empty-string $name * @param ?non-empty-string $short */ + #[\NoDiscard] public function option(string $name, ?string $short = null): self { return new self( @@ -212,6 +219,7 @@ public function option(string $name, ?string $short = null): self * @param non-empty-string $name * @param ?non-empty-string $short */ + #[\NoDiscard] public function flag(string $name, ?string $short = null): self { return new self( @@ -224,6 +232,7 @@ public function flag(string $name, ?string $short = null): self ); } + #[\NoDiscard] public function withShortDescription(string $description): self { if (Str::of($description)->contains("\n")) { @@ -240,6 +249,7 @@ public function withShortDescription(string $description): self ); } + #[\NoDiscard] public function withDescription(string $description): self { /** @var ?string */ @@ -262,6 +272,7 @@ public function withDescription(string $description): self * * @param callable(): self $load */ + #[\NoDiscard] public function load(callable $load): self { $usage = Identity::defer($load); @@ -287,6 +298,7 @@ public function load(callable $load): self /** * @return non-empty-string */ + #[\NoDiscard] public function name(): string { return $this->name; @@ -334,11 +346,13 @@ public function matches(string $command): bool ); } + #[\NoDiscard] public function shortDescription(): string { return $this->shortDescription ?? ''; } + #[\NoDiscard] public function pattern(): Pattern { return new Pattern( @@ -351,6 +365,7 @@ public function pattern(): Pattern /** * @return non-empty-string */ + #[\NoDiscard] public function toString(): string { $string = $this->name; diff --git a/src/Commands.php b/src/Commands.php index 5c7534d..66e3d1e 100644 --- a/src/Commands.php +++ b/src/Commands.php @@ -27,6 +27,7 @@ private function __construct( /** * @return Attempt */ + #[\NoDiscard] public function __invoke(Environment $env): Attempt { if ($this->commands instanceof Command) { @@ -36,6 +37,7 @@ public function __invoke(Environment $env): Attempt return self::find($env, $this->commands); } + #[\NoDiscard] public static function of(Command $command, Command ...$commands): self { return new self(match ($commands) { @@ -50,6 +52,7 @@ public static function of(Command $command, Command ...$commands): self * * @param Sequence $commands */ + #[\NoDiscard] public static function for(Sequence $commands): self { return new self($commands); diff --git a/src/Console.php b/src/Console.php index 2ca6b0c..3c9b73b 100644 --- a/src/Console.php +++ b/src/Console.php @@ -41,11 +41,13 @@ public static function of( ); } + #[\NoDiscard] public function arguments(): Arguments { return $this->arguments; } + #[\NoDiscard] public function options(): Options { return $this->options; @@ -56,6 +58,7 @@ public function options(): Options * * @return array{Attempt, self} */ + #[\NoDiscard] public function read(?int $length = null): array { [$data, $env] = $this->env->read($length); @@ -70,6 +73,7 @@ public function read(?int $length = null): array /** * @return Attempt */ + #[\NoDiscard] public function output(Str $data): Attempt { return $this->env->output($data)->map( @@ -84,6 +88,7 @@ public function output(Str $data): Attempt /** * @return Attempt */ + #[\NoDiscard] public function error(Str $data): Attempt { return $this->env->error($data)->map( @@ -95,6 +100,7 @@ public function error(Str $data): Attempt ); } + #[\NoDiscard] public function interactive(): bool { return $this->env->interactive(); @@ -103,11 +109,13 @@ public function interactive(): bool /** * @return Map */ + #[\NoDiscard] public function variables(): Map { return $this->env->variables(); } + #[\NoDiscard] public function workingDirectory(): Path { return $this->env->workingDirectory(); @@ -116,6 +124,7 @@ public function workingDirectory(): Path /** * @param int<0, 254> $exit */ + #[\NoDiscard] public function exit(int $exit): self { return new self( @@ -128,6 +137,7 @@ public function exit(int $exit): self /** * @internal */ + #[\NoDiscard] public function environment(): Environment { return $this->env; diff --git a/src/Environment.php b/src/Environment.php index 2089f29..0cccb1b 100644 --- a/src/Environment.php +++ b/src/Environment.php @@ -63,6 +63,7 @@ public static function inMemory( /** * True if the environment running the script is an interactive terminal */ + #[\NoDiscard] public function interactive(): bool { return $this->implementation->interactive(); @@ -73,6 +74,7 @@ public function interactive(): bool * * @return array{Attempt, self} */ + #[\NoDiscard] public function read(?int $length = null): array { [$read, $implementation] = $this->implementation->read($length); @@ -83,6 +85,7 @@ public function read(?int $length = null): array /** * @return Attempt */ + #[\NoDiscard] public function output(Str $data): Attempt { return $this @@ -94,6 +97,7 @@ public function output(Str $data): Attempt /** * @return Attempt */ + #[\NoDiscard] public function error(Str $data): Attempt { return $this @@ -105,6 +109,7 @@ public function error(Str $data): Attempt /** * @return Sequence */ + #[\NoDiscard] public function arguments(): Sequence { return $this->implementation->arguments(); @@ -113,6 +118,7 @@ public function arguments(): Sequence /** * @return Map */ + #[\NoDiscard] public function variables(): Map { return $this->implementation->variables(); @@ -121,6 +127,7 @@ public function variables(): Map /** * @param int<0, 254> $code */ + #[\NoDiscard] public function exit(int $code): self { return new self($this->implementation->exit($code)); @@ -129,11 +136,13 @@ public function exit(int $code): self /** * @return Maybe */ + #[\NoDiscard] public function exitCode(): Maybe { return $this->implementation->exitCode(); } + #[\NoDiscard] public function workingDirectory(): Path { return $this->implementation->workingDirectory(); @@ -144,6 +153,7 @@ public function workingDirectory(): Path * * @return Sequence */ + #[\NoDiscard] public function outputted(): Sequence { return $this->implementation->outputted(); diff --git a/src/Environment/ExitCode.php b/src/Environment/ExitCode.php index 9c78858..e25f112 100644 --- a/src/Environment/ExitCode.php +++ b/src/Environment/ExitCode.php @@ -21,11 +21,13 @@ public function __construct(private int $code) /** * @return int<0, 254> */ + #[\NoDiscard] public function toInt(): int { return $this->code; } + #[\NoDiscard] public function successful(): bool { return $this->code === 0; diff --git a/src/Question/ChoiceQuestion.php b/src/Question/ChoiceQuestion.php index 73f6966..9a0fe4d 100644 --- a/src/Question/ChoiceQuestion.php +++ b/src/Question/ChoiceQuestion.php @@ -34,6 +34,7 @@ private function __construct( * * @return Attempt>, T}> Returns nothing when no interactions available */ + #[\NoDiscard] public function __invoke(Environment|Console $env): Attempt { $noInteraction = match ($env::class) { @@ -73,6 +74,7 @@ public function __invoke(Environment|Console $env): Attempt * * @param Map $values */ + #[\NoDiscard] public static function of(string $question, Map $values): self { return new self(Str::of($question), $values); diff --git a/src/Question/Question.php b/src/Question/Question.php index 28aaad0..aa38f49 100644 --- a/src/Question/Question.php +++ b/src/Question/Question.php @@ -28,6 +28,7 @@ private function __construct(private Str $question) * * @return Attempt, T}> Returns nothing when no interactions available */ + #[\NoDiscard] public function __invoke(Environment|Console $env): Attempt { $noInteraction = match ($env::class) { @@ -52,6 +53,7 @@ public function __invoke(Environment|Console $env): Attempt /** * @psalm-pure */ + #[\NoDiscard] public static function of(string $question): self { return new self(Str::of($question)->append(' ')); diff --git a/tests/Command/PatternTest.php b/tests/Command/PatternTest.php index 4bcfe77..42e977b 100644 --- a/tests/Command/PatternTest.php +++ b/tests/Command/PatternTest.php @@ -52,7 +52,7 @@ public function testThrowWhenRequirementArgumentFoundAfterAnOptionalOne() $this->expectException(\LogicException::class); $this->expectExceptionMessage('No required argument after an optional one'); - Usage::of('name') + $_ = Usage::of('name') ->argument('baz') ->optionalArgument('foo') ->flag('foo') diff --git a/tests/Command/UsageTest.php b/tests/Command/UsageTest.php index 1f36da0..8b84c60 100644 --- a/tests/Command/UsageTest.php +++ b/tests/Command/UsageTest.php @@ -153,7 +153,7 @@ public function testThrowWhenEmptyDeclaration() $this->expectException(\LogicException::class); $this->expectExceptionMessage('Empty usage'); - Usage::parse(' '); + $_ = Usage::parse(' '); } private function names(): Set