diff --git a/src/ArchPresets/AbstractPreset.php b/src/ArchPresets/AbstractPreset.php new file mode 100644 index 00000000..b270a023 --- /dev/null +++ b/src/ArchPresets/AbstractPreset.php @@ -0,0 +1,54 @@ + $userNamespaces + * @param array $expectations + */ + final public function __construct(// @phpstan-ignore-line + protected array $userNamespaces, + protected array $expectations = [], + ) { + // + } + + /** + * Executes the arch preset. + * + * @internal + */ + abstract public function execute(): void; + + /** + * Ignores the given "targets" or "dependencies". + * + * @param array|string $targetsOrDependencies + */ + final public function ignoring(array|string $targetsOrDependencies): void + { + $this->expectations = array_map( + fn (ArchExpectation $expectation): \Pest\Arch\Contracts\ArchExpectation => $expectation->ignoring($targetsOrDependencies), + $this->expectations, + ); + } + + /** + * Flushes the expectations. + */ + final public function flush(): void + { + $this->expectations = []; + } +} diff --git a/src/ArchPresets/Base.php b/src/ArchPresets/Base.php new file mode 100644 index 00000000..ad64459a --- /dev/null +++ b/src/ArchPresets/Base.php @@ -0,0 +1,94 @@ +expectations[] = expect([ + 'debug_zval_dump', + 'debug_backtrace', + 'debug_print_backtrace', + 'dd', + 'ddd', + 'dump', + 'ray', + 'die', + 'goto', + 'global', + 'var_dump', + 'phpinfo', + 'echo', + 'ereg', + 'eregi', + 'mysql_connect', + 'mysql_pconnect', + 'mysql_query', + 'mysql_select_db', + 'mysql_fetch_array', + 'mysql_fetch_assoc', + 'mysql_fetch_object', + 'mysql_fetch_row', + 'mysql_num_rows', + 'mysql_affected_rows', + 'mysql_free_result', + 'mysql_insert_id', + 'mysql_error', + 'mysql_real_escape_string', + 'print', + 'print_r', + 'var_export', + 'xdebug_break', + 'xdebug_call_class', + 'xdebug_call_file', + 'xdebug_call_int', + 'xdebug_call_line', + 'xdebug_code_coverage_started', + 'xdebug_connect_to_client', + 'xdebug_debug_zval', + 'xdebug_debug_zval_stdout', + 'xdebug_dump_superglobals', + 'xdebug_get_code_coverage', + 'xdebug_get_collected_errors', + 'xdebug_get_function_count', + 'xdebug_get_function_stack', + 'xdebug_get_gc_run_count', + 'xdebug_get_gc_total_collected_roots', + 'xdebug_get_gcstats_filename', + 'xdebug_get_headers', + 'xdebug_get_monitored_functions', + 'xdebug_get_profiler_filename', + 'xdebug_get_stack_depth', + 'xdebug_get_tracefile_name', + 'xdebug_info', + 'xdebug_is_debugger_active', + 'xdebug_memory_usage', + 'xdebug_notify', + 'xdebug_peak_memory_usage', + 'xdebug_print_function_stack', + 'xdebug_set_filter', + 'xdebug_start_code_coverage', + 'xdebug_start_error_collection', + 'xdebug_start_function_monitor', + 'xdebug_start_gcstats', + 'xdebug_start_trace', + 'xdebug_stop_code_coverage', + 'xdebug_stop_error_collection', + 'xdebug_stop_function_monitor', + 'xdebug_stop_gcstats', + 'xdebug_stop_trace', + 'xdebug_time_index', + 'xdebug_var_dump', + 'trap', + ])->not->toBeUsed(); + } +} diff --git a/src/ArchPresets/Laravel.php b/src/ArchPresets/Laravel.php new file mode 100644 index 00000000..c85a540c --- /dev/null +++ b/src/ArchPresets/Laravel.php @@ -0,0 +1,61 @@ +expectations[] = expect([ + 'env', + 'exit', + ])->not->toBeUsed(); + + $this->expectations[] = expect('App\Http\Controllers') + ->toHaveSuffix('Controller'); + + $this->expectations[] = expect('App\Http\Middleware') + ->toHaveMethod('handle'); + + $this->expectations[] = expect('App\Models') + ->not->toHaveSuffix('Model'); + + $this->expectations[] = expect('App\Http\Requests') + ->toHaveSuffix('Request') + ->toExtend('Illuminate\Foundation\Http\FormRequest') + ->toHaveMethod('rules'); + + $this->expectations[] = expect('App\Console\Commands') + ->toHaveSuffix('Command') + ->toExtend('Illuminate\Console\Command') + ->toHaveMethod('handle'); + + $this->expectations[] = expect('App\Exceptions') + ->toImplement('Throwable'); + + $this->expectations[] = expect('App\Mail') + ->toExtend('Illuminate\Mail\Mailable'); + + $this->expectations[] = expect('App\Jobs') + ->toHaveMethod('handle'); + + $this->expectations[] = expect('App\Listeners') + ->toHaveMethod('handle'); + + $this->expectations[] = expect('App\Notifications') + ->toExtend('Illuminate\Notifications\Notification'); + + $this->expectations[] = expect('App\Providers') + ->toHaveSuffix('ServiceProvider') + ->toExtend('Illuminate\Support\ServiceProvider') + ->not->toBeUsed(); + } +} diff --git a/src/ArchPresets/Security.php b/src/ArchPresets/Security.php new file mode 100644 index 00000000..c7142748 --- /dev/null +++ b/src/ArchPresets/Security.php @@ -0,0 +1,41 @@ +expectations[] = expect([ + 'md5', + 'sha1', + 'uniqid', + 'rand', + 'mt_rand', + 'tempnam', + 'str_shuffle', + 'shuffle', + 'array_rand', + 'eval', + 'exec', + 'shell_exec', + 'system', + 'passthru', + 'create_function', + 'unserialize', + 'extract', + 'parse_str', + 'mb_parse_str', + 'dl', + 'assert', + ])->not->toBeUsed(); + } +} diff --git a/src/ArchPresets/Strict.php b/src/ArchPresets/Strict.php new file mode 100644 index 00000000..655ee5a0 --- /dev/null +++ b/src/ArchPresets/Strict.php @@ -0,0 +1,27 @@ +userNamespaces as $namespace) { + $this->expectations[] = expect([ + 'sleep', + 'usleep', + ])->not->toBeUsed(); + + $this->expectations[] = expect($namespace) + ->toUseStrictTypes(); + } + } +} diff --git a/src/Contracts/ArchPreset.php b/src/Contracts/ArchPreset.php new file mode 100644 index 00000000..6f5a0fca --- /dev/null +++ b/src/Contracts/ArchPreset.php @@ -0,0 +1,12 @@ +attributes = array_merge($testCase->attributes, $this->testCaseFactoryAttributes); } } + + /** + * Uses the given preset on the test. + */ + public function preset(): Preset + { + return new Preset($this); + } } diff --git a/src/Preset.php b/src/Preset.php new file mode 100644 index 00000000..cc7c8956 --- /dev/null +++ b/src/Preset.php @@ -0,0 +1,108 @@ + + */ + private static ?array $baseNamespaces = null; + + /** + * Creates a new preset instance. + */ + public function __construct(private readonly TestCall $testCall) + { + // + } + + /** + * Uses the Pest base preset and returns the test call instance. + */ + public function base(): Base + { + return $this->executePreset(new Base($this->baseNamespaces())); + } + + /** + * Uses the Pest laravel preset and returns the test call instance. + */ + public function laravel(): Laravel + { + return $this->executePreset(new Laravel($this->baseNamespaces())); + } + + /** + * Uses the Pest strict preset and returns the test call instance. + */ + public function strict(): Strict + { + return $this->executePreset(new Strict($this->baseNamespaces())); + } + + /** + * Uses the Pest security preset and returns the test call instance. + */ + public function security(): AbstractPreset + { + return $this->executePreset(new Security($this->baseNamespaces())); + } + + /** + * Executes the given preset. + * + * @template TPreset of AbstractPreset + * + * @param TPreset $preset + * @return TPreset + */ + private function executePreset(AbstractPreset $preset): AbstractPreset + { + if ((fn (): ?string => $this->description)->call($this->testCall) === null) { + $description = strtolower((new \ReflectionClass($preset))->getShortName()); + + (fn (): string => $this->description = sprintf('arch "%s" preset', $description))->call($this->testCall); + } + + $this->baseNamespaces(); + + $preset->execute(); + + $this->testCall->testCaseMethod->closure = (function () use ($preset): void { + $preset->flush(); + })->bindTo(new stdClass); + + return $preset; + } + + /** + * Get the base namespaces for the application / package. + * + * @return array + */ + private function baseNamespaces(): array + { + if (self::$baseNamespaces === null) { + self::$baseNamespaces = Composer::userNamespaces(); + } + + return self::$baseNamespaces; + } +} diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index cd219811..f546c495 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -1,5 +1,7 @@ PASS Tests\Arch + ✓ arch "base" preset + ✓ arch "strict" preset ✓ globals ✓ dependencies ✓ contracts @@ -1467,4 +1469,4 @@ WARN Tests\Visual\Version - visual snapshot of help command output - Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 13 todos, 24 skipped, 1046 passed (2573 assertions) \ No newline at end of file + Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 13 todos, 24 skipped, 1046 passed (2573 assertions) diff --git a/tests/Arch.php b/tests/Arch.php index 7c508cf2..7e1d0552 100644 --- a/tests/Arch.php +++ b/tests/Arch.php @@ -2,6 +2,26 @@ use Pest\Expectation; +arch()->preset()->base()->ignoring([ + Expectation::class, + 'debug_backtrace', + 'var_export', + 'xdebug_info', +]); + +arch()->preset()->strict()->ignoring([ + 'usleep', +]); + +arch()->preset()->security()->ignoring([ + 'eval', + 'str_shuffle', + 'exec', + 'unserialize', + 'extract', + 'assert', +]); + arch('globals') ->expect(['dd', 'dump', 'ray', 'die', 'var_dump', 'sleep']) ->not->toBeUsed() @@ -30,4 +50,6 @@ arch('contracts') 'NunoMaduro\Collision\Contracts', 'Pest\Factories\TestCaseMethodFactory', 'Symfony\Component\Console', + 'Pest\Arch\Contracts', + 'Pest\PendingCalls', ])->toBeInterfaces();