diff --git a/.gitattributes b/.gitattributes index d343ad7..c54d546 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,6 +6,4 @@ LICENSE.md export-ignore tests export-ignore phpunit.xml export-ignore .scrutinizer.yml export-ignore -codeception.yml export-ignore -docker export-ignore -docker-compose.yml export-ignore +codeception.yml export-ignore \ No newline at end of file diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 0000000..025a4b6 --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,30 @@ +name: security + +on: + push: + pull_request: + +jobs: + security: + name: Security + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + php: [8.1, 8.2 ] + os: [ubuntu-latest] + steps: + - name: Checkout + uses: actions/checkout@v3.3.0 + - name: Setup PHP ${{ matrix.php }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + - name: Install Dependencies + uses: nick-invision/retry@v1 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer update --prefer-dist --no-interaction --no-progress + - name: Security Advisories + run: composer require --dev roave/security-advisories:dev-latest \ No newline at end of file diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 0000000..89b963b --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,31 @@ +name: static-analysis + +on: + push: + pull_request: + +jobs: + psalm: + name: Psalm + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + php: [8.1, 8.2] + os: [ubuntu-latest] + steps: + - name: Checkout + uses: actions/checkout@v3.3.0 + - name: Setup PHP ${{ matrix.php }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + - name: Install Dependencies + uses: nick-invision/retry@v1 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer update --prefer-dist --no-interaction --no-progress + - name: Static Analysis + continue-on-error: true + run: vendor/bin/psalm --no-cache \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0796c17..872d628 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,10 +4,8 @@ on: push: branches: - master - - develop pull_request: branches: - - develop - master jobs: @@ -17,7 +15,7 @@ jobs: strategy: fail-fast: true matrix: - php: [7.4, 8.0, 8.1] + php: [8.1] stability: [prefer-lowest, prefer-stable] name: PHP ${{ matrix.php }} / ${{ matrix.stability }} @@ -46,10 +44,4 @@ jobs: run: composer validate - name: Execute tests - run: composer test - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 - with: - token: ${{ secrets.CODECOV_TOKEN }} - file: ./coverage.xml \ No newline at end of file + run: composer test \ No newline at end of file diff --git a/.scrutinizer.yml b/.scrutinizer.yml deleted file mode 100644 index 031b87f..0000000 --- a/.scrutinizer.yml +++ /dev/null @@ -1,15 +0,0 @@ -filter: - excluded_paths: [tests/*] - -checks: - php: - remove_extra_empty_lines: true - remove_trailing_whitespace: true - fix_use_statements: - remove_unused: true - preserve_multiple: false - preserve_blanklines: true - order_alphabetically: true - fix_linefeed: true - fix_line_ending: true - fix_identation_4spaces: true \ No newline at end of file diff --git a/README.md b/README.md index 88fb3c5..f81dded 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,14 @@ Install this package with Composer: composer require kafkiansky/service-locator-interrupter "2.1.0" --dev + + + PHP ^8.1 && Laravel ^10.x + + + composer require kafkiansky/service-locator-interrupter "^4.0" --dev + + @@ -268,7 +276,7 @@ This plugin can found issues of service locator usage - helpers, facades, contai ## Testing ``` bash -$ composer test +$ composer codeception ``` ## License diff --git a/codeception.yml b/codeception.yml index fdf0f50..517c3ab 100644 --- a/codeception.yml +++ b/codeception.yml @@ -1,4 +1,4 @@ -namespace: Psalm\PhpUnitPlugin\Tests +namespace: Kafkiansky\ServiceLocatorInterrupter\Tests paths: tests: tests/acceptance diff --git a/composer.json b/composer.json index 23ff2da..4883631 100644 --- a/composer.json +++ b/composer.json @@ -1,53 +1,62 @@ { - "name": "kafkiansky/service-locator-interrupter", - "description": "Psalm plugin for Laravel that interrupt service locator calls.", - "license": "MIT", - "type": "psalm-plugin", - "authors": [ - { - "name": "Vadim Zanfir", - "email": "vadimzanfir@gmail.com" - } - ], - "autoload": { - "psr-4": { - "Kafkiansky\\ServiceLocatorInterrupter\\": "src" - } - }, - "autoload-dev": { - "psr-4": { - "Kafkiansky\\ServiceLocatorInterrupter\\Tests\\": "tests" - } - }, - "require": { - "php": ">=7.4 || <= 8.1", - "ext-simplexml": "*", - "vimeo/psalm": ">= 4.0" - }, - "require-dev": { - "laravel/framework": "^7.0 || ^8.0", - "phpunit/phpunit": "^9.5", - "weirdan/codeception-psalm-module": "^0.13.0" - }, - "config": { - "sort-packages": true, - "process-timeout": 0, - "allow-plugins": { - "composer/package-versions-deprecated": true - } - }, - "extra": { - "psalm": { - "pluginClass": "Kafkiansky\\ServiceLocatorInterrupter\\Plugin" - } - }, - "scripts": { - "test": [ - "@codeception" + "name": "kafkiansky/service-locator-interrupter", + "description": "Psalm plugin for Laravel that interrupt service locator calls.", + "license": "MIT", + "type": "psalm-plugin", + "authors": [ + { + "name": "v.zanfir", + "email": "vadimzanfir@gmail.com", + "role": "maintainer" + } ], - "codeception": [ - "codecept build", - "codecept run -v -g laravel-interrupter" - ] - } + "require": { + "php": "^8.1", + "ext-simplexml": "*", + "vimeo/psalm": "^5.12" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.31", + "laravel/framework": "^10.13", + "phpunit/phpunit": "^10.2", + "squizlabs/php_codesniffer": "^3.7", + "weirdan/codeception-psalm-module": "^0.14.0" + }, + "autoload": { + "psr-4": { + "Kafkiansky\\ServiceLocatorInterrupter\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Kafkiansky\\ServiceLocatorInterrupter\\Tests\\": "tests" + } + }, + "config": { + "allow-plugins": { + "composer/package-versions-deprecated": true, + "ergebnis/composer-normalize": true + }, + "process-timeout": 0, + "sort-packages": true + }, + "extra": { + "psalm": { + "pluginClass": "Kafkiansky\\ServiceLocatorInterrupter\\Plugin" + } + }, + "scripts": { + "codeception": [ + "codecept build", + "codecept run -v -g laravel-interrupter" + ], + "phpcbf": "./vendor/bin/phpcbf -p --standard=phpcs.xml src", + "phpcs": "./vendor/bin/phpcs --standard=phpcs.xml", + "psalm": "./vendor/bin/psalm --no-cache", + "test": [ + "@codeception", + "@psalm", + "@phpcs" + ] + } } diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 10beb30..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,10 +0,0 @@ -version: '3.0' - -services: - php: - build: - context: ./docker - command: docker-php-entrypoint php-fpm - volumes: - - ./:/var/www:delegated - restart: on-failure \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index d855c7a..0000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM php:8.1-fpm - -RUN apt-get update && apt-get install -y \ - libpq-dev \ - wget \ - zlib1g-dev \ - libmcrypt-dev \ - libzip-dev \ - openssl - -RUN docker-php-ext-install bcmath zip sockets pcntl - -RUN wget https://getcomposer.org/installer -O - -q | php -- --install-dir=/bin --filename=composer --quiet - -WORKDIR /var/www \ No newline at end of file diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..502d9b7 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ./src + */tests/* + */vendor/* + \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml index 426e87a..79c3f89 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -20,7 +20,4 @@ src/ - - - \ No newline at end of file diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..94847de --- /dev/null +++ b/psalm.xml @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/Hooks/PreventContainerUsage.php b/src/EventHandler/PreventContainerUsage.php similarity index 67% rename from src/Hooks/PreventContainerUsage.php rename to src/EventHandler/PreventContainerUsage.php index 05d6ab6..879d56b 100644 --- a/src/Hooks/PreventContainerUsage.php +++ b/src/EventHandler/PreventContainerUsage.php @@ -1,15 +1,8 @@ getStmt(); if ($stmt instanceof Node\Stmt\ClassMethod) { - /** @var Node\Param $param */ foreach ($stmt->params as $param) { - if ( - $param->type instanceof Node\Name - && $param->type->hasAttribute('resolvedName') - && self::isServiceLocatorCall($param->type->getAttribute('resolvedName')) - ) { - IssueBuffer::accepts( - new ContainerUsed( - new CodeLocation($event->getStatementsSource(), $param) - ), - $event->getStatementsSource()->getSuppressedIssues() - ); + if ($param->type instanceof Node\Name && $param->type->hasAttribute('resolvedName')) { + /** @psalm-var class-string|interface-string $classOrInterface */ + $classOrInterface = $param->type->getAttribute('resolvedName'); + + if (self::isServiceLocatorCall($classOrInterface)) { + IssueBuffer::accepts( + new ContainerUsed( + new CodeLocation($event->getStatementsSource(), $param) + ), + $event->getStatementsSource()->getSuppressedIssues(), + ); + } } } } @@ -77,6 +66,7 @@ public static function afterExpressionAnalysis(AfterExpressionAnalysisEvent $eve if ($expr instanceof Expr\StaticCall) { if ($expr->class->hasAttribute('resolvedName')) { + /** @psalm-var class-string|interface-string $classOrInterface */ $classOrInterface = $expr->class->getAttribute('resolvedName'); if (self::isServiceLocatorCall($classOrInterface)) { @@ -84,7 +74,7 @@ public static function afterExpressionAnalysis(AfterExpressionAnalysisEvent $eve new ContainerUsed( new CodeLocation($event->getStatementsSource(), $expr) ), - $event->getStatementsSource()->getSuppressedIssues() + $event->getStatementsSource()->getSuppressedIssues(), ); } } @@ -94,9 +84,7 @@ public static function afterExpressionAnalysis(AfterExpressionAnalysisEvent $eve } /** - * @param $resolvedName - * - * @return bool + * @psalm-param class-string|interface-string $resolvedName */ private static function isServiceLocatorCall($resolvedName): bool { @@ -121,17 +109,15 @@ private static function isServiceLocatorCall($resolvedName): bool if (self::instanceOfContainer($parentsClass, self::$containerClasses)) { return true; } - } catch (\ReflectionException $exception) { + } catch (\ReflectionException) { } return false; } /** - * @param array $parents - * @param array $declaringContainers - * - * @return bool + * @param string[] $parents + * @psalm-param class-string[]|interface-string[] $declaringContainers */ private static function instanceOfContainer(array $parents, array $declaringContainers): bool { diff --git a/src/Hooks/PreventFacadeCall.php b/src/EventHandler/PreventFacadeCall.php similarity index 57% rename from src/Hooks/PreventFacadeCall.php rename to src/EventHandler/PreventFacadeCall.php index 1f463eb..0216dd3 100644 --- a/src/Hooks/PreventFacadeCall.php +++ b/src/EventHandler/PreventFacadeCall.php @@ -1,15 +1,8 @@ getExpr(); - if ($expr instanceof Expr\StaticCall) { - if (self::isFacadeCall($expr->class->getAttribute('resolvedName'))) { + if ($expr instanceof Expr\StaticCall && $expr->class->hasAttribute('resolvedName')) { + /** @var null|string $name */ + $name = $expr->class->getAttribute('resolvedName'); + + if (null !== $name && self::isFacadeCall($name)) { IssueBuffer::accepts( new FacadeCalled( new CodeLocation($event->getStatementsSource(), $expr) ), - $event->getStatementsSource()->getSuppressedIssues() + $event->getStatementsSource()->getSuppressedIssues(), ); } } @@ -41,21 +37,12 @@ public static function afterExpressionAnalysis(AfterExpressionAnalysisEvent $eve return null; } - /** - * @param mixed|null $facadeName - * - * @return bool - */ - private static function isFacadeCall($facadeName): bool + private static function isFacadeCall(string $facadeName): bool { - if (null === $facadeName) { - return false; - } - - if (false !== strpos($facadeName, 'Illuminate\Support\Facades')) { + if (str_contains($facadeName, 'Illuminate\Support\Facades')) { return true; } - return class_exists($facadeName) && is_subclass_of($facadeName, 'Illuminate\Support\Facades\Facade'); + return class_exists($facadeName) && is_subclass_of($facadeName, \Illuminate\Support\Facades\Facade::class); } } diff --git a/src/Hooks/PreventHelpersUsage.php b/src/EventHandler/PreventHelpersUsage.php similarity index 52% rename from src/Hooks/PreventHelpersUsage.php rename to src/EventHandler/PreventHelpersUsage.php index dc7ed4b..3bb9521 100644 --- a/src/Hooks/PreventHelpersUsage.php +++ b/src/EventHandler/PreventHelpersUsage.php @@ -1,17 +1,11 @@ getExpr(); - - if (self::isServiceLocatorHelperCall($expr->name->toString())) { - IssueBuffer::accepts( - new HelperUsed( - new CodeLocation($event->getStatementsSource(), $expr) - ), - $event->getStatementsSource()->getSuppressedIssues() - ); + if ($event->getExpr()->name instanceof Name) { + if (self::isServiceLocatorHelperCall($event->getExpr()->name->toString())) { + IssueBuffer::accepts( + new HelperUsed( + new CodeLocation($event->getStatementsSource(), $event->getExpr()) + ), + $event->getStatementsSource()->getSuppressedIssues(), + ); + } } } - /** - * @param string $functionName - * - * @return bool - */ private static function isServiceLocatorHelperCall(string $functionName): bool { return in_array( @@ -59,7 +48,7 @@ private static function isServiceLocatorHelperCall(string $functionName): bool 'config', 'cookie', 'dispatch', - 'dispatch_now', + 'dispatch_sync', 'redirect', 'report', 'request', @@ -71,7 +60,30 @@ private static function isServiceLocatorHelperCall(string $functionName): bool 'url', 'validator', 'view', - ] + 'encrypt', + 'decrypt', + 'action', + 'app_path', + 'asset', + 'base_path', + 'bcrypt', + 'config_path', + 'csrf_token', + 'database_path', + 'lang_path', + 'old', + 'policy', + 'precognitive', + 'public_path', + 'report_if', + 'report_unless', + 'resource_path', + 'secure_asset', + 'secure_url', + 'storage_path', + 'to_route', + '__', + ], ); } } diff --git a/src/Plugin.php b/src/Plugin.php index ea39de5..985ccdb 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -1,33 +1,37 @@ registerHooksFromClass($hook); + } + } + /** - * {@inheritdoc} + * @return iterable|class-string|class-string> */ - public function __invoke(RegistrationInterface $api, SimpleXMLElement $config = null): void + private static function hooks(): iterable { - require_once __DIR__.'/Hooks/PreventContainerUsage.php'; - require_once __DIR__.'/Hooks/PreventFacadeCall.php'; - require_once __DIR__.'/Hooks/PreventHelpersUsage.php'; - - $api->registerHooksFromClass(Hooks\PreventContainerUsage::class); - $api->registerHooksFromClass(Hooks\PreventFacadeCall::class); - $api->registerHooksFromClass(Hooks\PreventHelpersUsage::class); + yield EventHandler\PreventContainerUsage::class; + yield EventHandler\PreventFacadeCall::class; + yield EventHandler\PreventHelpersUsage::class; } } diff --git a/tests/_run/.gitignore b/tests/_run/.gitignore deleted file mode 100644 index e69de29..0000000 diff --git a/tests/acceptance/_support/AcceptanceTester.php b/tests/acceptance/_support/AcceptanceTester.php index 4fa8351..a537789 100644 --- a/tests/acceptance/_support/AcceptanceTester.php +++ b/tests/acceptance/_support/AcceptanceTester.php @@ -1,12 +1,13 @@ 0. You can disable this by passing `false` as second argument + * + * ```php + * runShellCommand('phpunit'); + * + * // do not fail test when command fails + * $I->runShellCommand('phpunit', false); + * ``` + * @see \Codeception\Module\Cli::runShellCommand() + */ + public function runShellCommand(string $command, bool $failNonZero = true): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('runShellCommand', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that output from last executed command contains text + * @see \Codeception\Module\Cli::seeInShellOutput() + */ + public function seeInShellOutput(string $text): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeInShellOutput', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that output from last executed command contains text + * @see \Codeception\Module\Cli::seeInShellOutput() + */ + public function canSeeInShellOutput(string $text): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeInShellOutput', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that output from latest command doesn't contain text + * @see \Codeception\Module\Cli::dontSeeInShellOutput() + */ + public function dontSeeInShellOutput(string $text): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('dontSeeInShellOutput', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that output from latest command doesn't contain text + * @see \Codeception\Module\Cli::dontSeeInShellOutput() + */ + public function cantSeeInShellOutput(string $text): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeInShellOutput', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * + * @see \Codeception\Module\Cli::seeShellOutputMatches() + */ + public function seeShellOutputMatches(string $regex): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeShellOutputMatches', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * + * @see \Codeception\Module\Cli::seeShellOutputMatches() + */ + public function canSeeShellOutputMatches(string $regex): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeShellOutputMatches', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Returns the output from latest command + * @see \Codeception\Module\Cli::grabShellOutput() + */ + public function grabShellOutput(): string { + return $this->getScenario()->runStep(new \Codeception\Step\Action('grabShellOutput', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks result code. To verify a result code > 0, you need to pass `false` as second argument to `runShellCommand()` + * + * ```php + * seeResultCodeIs(0); + * ``` + * @see \Codeception\Module\Cli::seeResultCodeIs() + */ + public function seeResultCodeIs(int $code): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeResultCodeIs', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks result code. To verify a result code > 0, you need to pass `false` as second argument to `runShellCommand()` + * + * ```php + * seeResultCodeIs(0); + * ``` + * @see \Codeception\Module\Cli::seeResultCodeIs() + */ + public function canSeeResultCodeIs(int $code): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeResultCodeIs', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks result code + * + * ```php + * seeResultCodeIsNot(0); + * ``` + * @see \Codeception\Module\Cli::seeResultCodeIsNot() + */ + public function seeResultCodeIsNot(int $code): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeResultCodeIsNot', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks result code + * + * ```php + * seeResultCodeIsNot(0); + * ``` + * @see \Codeception\Module\Cli::seeResultCodeIsNot() + */ + public function canSeeResultCodeIsNot(int $code): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeResultCodeIsNot', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Enters a directory In local filesystem. + * Project root directory is used by default + * @see \Codeception\Module\Filesystem::amInPath() + */ + public function amInPath(string $path): void { + $this->getScenario()->runStep(new \Codeception\Step\Condition('amInPath', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Opens a file and stores it's content. + * + * Usage: + * + * ``` php + * openFile('composer.json'); + * $I->seeInThisFile('codeception/codeception'); + * ``` + * @see \Codeception\Module\Filesystem::openFile() + */ + public function openFile(string $filename): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('openFile', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Deletes a file + * + * ``` php + * deleteFile('composer.lock'); + * ``` + * @see \Codeception\Module\Filesystem::deleteFile() + */ + public function deleteFile(string $filename): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('deleteFile', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Deletes directory with all subdirectories + * + * ``` php + * deleteDir('vendor'); + * ``` + * @see \Codeception\Module\Filesystem::deleteDir() + */ + public function deleteDir(string $dirname): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('deleteDir', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Copies directory with all contents + * + * ``` php + * copyDir('vendor','old_vendor'); + * ``` + * @see \Codeception\Module\Filesystem::copyDir() + */ + public function copyDir(string $src, string $dst): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('copyDir', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks If opened file has `text` in it. + * + * Usage: + * + * ``` php + * openFile('composer.json'); + * $I->seeInThisFile('codeception/codeception'); + * ``` + * @see \Codeception\Module\Filesystem::seeInThisFile() + */ + public function seeInThisFile(string $text): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeInThisFile', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks If opened file has `text` in it. + * + * Usage: + * + * ``` php + * openFile('composer.json'); + * $I->seeInThisFile('codeception/codeception'); + * ``` + * @see \Codeception\Module\Filesystem::seeInThisFile() + */ + public function canSeeInThisFile(string $text): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeInThisFile', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks If opened file has the `number` of new lines. + * + * Usage: + * + * ``` php + * openFile('composer.json'); + * $I->seeNumberNewLines(5); + * ``` + * + * @param int $number New lines + * @see \Codeception\Module\Filesystem::seeNumberNewLines() + */ + public function seeNumberNewLines(int $number): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeNumberNewLines', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks If opened file has the `number` of new lines. + * + * Usage: + * + * ``` php + * openFile('composer.json'); + * $I->seeNumberNewLines(5); + * ``` + * + * @param int $number New lines + * @see \Codeception\Module\Filesystem::seeNumberNewLines() + */ + public function canSeeNumberNewLines(int $number): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeNumberNewLines', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that contents of currently opened file matches $regex + * @see \Codeception\Module\Filesystem::seeThisFileMatches() + */ + public function seeThisFileMatches(string $regex): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeThisFileMatches', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that contents of currently opened file matches $regex + * @see \Codeception\Module\Filesystem::seeThisFileMatches() + */ + public function canSeeThisFileMatches(string $regex): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeThisFileMatches', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks the strict matching of file contents. + * Unlike `seeInThisFile` will fail if file has something more than expected lines. + * Better to use with HEREDOC strings. + * Matching is done after removing "\r" chars from file content. + * + * ``` php + * openFile('process.pid'); + * $I->seeFileContentsEqual('3192'); + * ``` + * @see \Codeception\Module\Filesystem::seeFileContentsEqual() + */ + public function seeFileContentsEqual(string $text): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeFileContentsEqual', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks the strict matching of file contents. + * Unlike `seeInThisFile` will fail if file has something more than expected lines. + * Better to use with HEREDOC strings. + * Matching is done after removing "\r" chars from file content. + * + * ``` php + * openFile('process.pid'); + * $I->seeFileContentsEqual('3192'); + * ``` + * @see \Codeception\Module\Filesystem::seeFileContentsEqual() + */ + public function canSeeFileContentsEqual(string $text): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeFileContentsEqual', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks If opened file doesn't contain `text` in it + * + * ``` php + * openFile('composer.json'); + * $I->dontSeeInThisFile('codeception/codeception'); + * ``` + * @see \Codeception\Module\Filesystem::dontSeeInThisFile() + */ + public function dontSeeInThisFile(string $text): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('dontSeeInThisFile', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks If opened file doesn't contain `text` in it + * + * ``` php + * openFile('composer.json'); + * $I->dontSeeInThisFile('codeception/codeception'); + * ``` + * @see \Codeception\Module\Filesystem::dontSeeInThisFile() + */ + public function cantSeeInThisFile(string $text): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeInThisFile', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Deletes a file + * @see \Codeception\Module\Filesystem::deleteThisFile() + */ + public function deleteThisFile(): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('deleteThisFile', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks if file exists in path. + * Opens a file when it's exists + * + * ``` php + * seeFileFound('UserModel.php','app/models'); + * ``` + * @see \Codeception\Module\Filesystem::seeFileFound() + */ + public function seeFileFound(string $filename, string $path = ""): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeFileFound', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks if file exists in path. + * Opens a file when it's exists + * + * ``` php + * seeFileFound('UserModel.php','app/models'); + * ``` + * @see \Codeception\Module\Filesystem::seeFileFound() + */ + public function canSeeFileFound(string $filename, string $path = ""): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeFileFound', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks if file does not exist in path + * @see \Codeception\Module\Filesystem::dontSeeFileFound() + */ + public function dontSeeFileFound(string $filename, string $path = ""): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('dontSeeFileFound', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks if file does not exist in path + * @see \Codeception\Module\Filesystem::dontSeeFileFound() + */ + public function cantSeeFileFound(string $filename, string $path = ""): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeFileFound', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Erases directory contents + * + * ``` php + * cleanDir('logs'); + * ``` + * @see \Codeception\Module\Filesystem::cleanDir() + */ + public function cleanDir(string $dirname): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('cleanDir', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Saves contents to file + * @see \Codeception\Module\Filesystem::writeToFile() + */ + public function writeToFile(string $filename, string $contents): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('writeToFile', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @param string[] $options + * @see \Weirdan\Codeception\Psalm\Module::runPsalmOn() + */ + public function runPsalmOn(string $filename, array $options = []): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('runPsalmOn', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @param string[] $options + * @see \Weirdan\Codeception\Psalm\Module::runPsalmIn() + */ + public function runPsalmIn(string $dir, array $options = []): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('runPsalmIn', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @param int|string $exitCode + * @Then I see exit code :code + * @see \Weirdan\Codeception\Psalm\Module::seeExitCode() + */ + public function seeExitCode($exitCode): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeExitCode', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * @param int|string $exitCode + * @Then I see exit code :code + * @see \Weirdan\Codeception\Psalm\Module::seeExitCode() + */ + public function canSeeExitCode($exitCode): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeExitCode', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * + * @see \Weirdan\Codeception\Psalm\Module::seeThisError() + */ + public function seeThisError(string $type, string $message): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeThisError', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * + * @see \Weirdan\Codeception\Psalm\Module::seeThisError() + */ + public function canSeeThisError(string $type, string $message): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeThisError', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @Then I see no errors + * @Then I see no other errors + * @see \Weirdan\Codeception\Psalm\Module::seeNoErrors() + */ + public function seeNoErrors(): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeNoErrors', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * @Then I see no errors + * @Then I see no other errors + * @see \Weirdan\Codeception\Psalm\Module::seeNoErrors() + */ + public function canSeeNoErrors(): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeNoErrors', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @deprecated + * This method is only to maintain the public API; please use `self::haveADependencySatisfied` instead. + * @see \Weirdan\Codeception\Psalm\Module::seePsalmVersionIs() + */ + public function seePsalmVersionIs(string $operator, string $version): bool { + return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seePsalmVersionIs', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * @deprecated + * This method is only to maintain the public API; please use `self::haveADependencySatisfied` instead. + * @see \Weirdan\Codeception\Psalm\Module::seePsalmVersionIs() + */ + public function canSeePsalmVersionIs(string $operator, string $version): bool { + return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seePsalmVersionIs', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @param string|PyStringNode $code + * + * @Given I have the following code preamble :code + * @see \Weirdan\Codeception\Psalm\Module::haveTheFollowingCodePreamble() + */ + public function haveTheFollowingCodePreamble($code): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('haveTheFollowingCodePreamble', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @When I run psalm + * @When I run Psalm + * @see \Weirdan\Codeception\Psalm\Module::runPsalm() + */ + public function runPsalm(): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('runPsalm', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @When I run Psalm with dead code detection + * @When I run psalm with dead code detection + * @see \Weirdan\Codeception\Psalm\Module::runPsalmWithDeadCodeDetection() + */ + public function runPsalmWithDeadCodeDetection(): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('runPsalmWithDeadCodeDetection', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * + * @see \Weirdan\Codeception\Psalm\Module::seePsalmHasTaintAnalysis() + */ + public function seePsalmHasTaintAnalysis(): bool { + return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seePsalmHasTaintAnalysis', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * + * @see \Weirdan\Codeception\Psalm\Module::seePsalmHasTaintAnalysis() + */ + public function canSeePsalmHasTaintAnalysis(): bool { + return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seePsalmHasTaintAnalysis', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @Given I have Psalm with taint analysis + * @Given I have psalm with taint analysis + * @see \Weirdan\Codeception\Psalm\Module::havePsalmWithTaintAnalysis() + */ + public function havePsalmWithTaintAnalysis(): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('havePsalmWithTaintAnalysis', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @When I run Psalm with taint analysis + * @When I run psalm with taint analysis + * @see \Weirdan\Codeception\Psalm\Module::runPsalmWithTaintAnalysis() + */ + public function runPsalmWithTaintAnalysis(): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('runPsalmWithTaintAnalysis', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @When I run Psalm on :arg1 + * @When I run psalm on :arg1 + * @see \Weirdan\Codeception\Psalm\Module::runPsalmOnASingleFile() + */ + public function runPsalmOnASingleFile(string $file): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('runPsalmOnASingleFile', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @param string|PyStringNode $config + * @Given I have the following config :config + * @see \Weirdan\Codeception\Psalm\Module::haveTheFollowingConfig() + */ + public function haveTheFollowingConfig($config): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('haveTheFollowingConfig', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @param string|PyStringNode $code + * @Given I have the following code :code + * @see \Weirdan\Codeception\Psalm\Module::haveTheFollowingCode() + */ + public function haveTheFollowingCode($code): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('haveTheFollowingCode', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @Given I have some future Psalm that supports this feature :ref + * @see \Weirdan\Codeception\Psalm\Module::haveSomeFuturePsalmThatSupportsThisFeature() + */ + public function haveSomeFuturePsalmThatSupportsThisFeature(string $ref): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('haveSomeFuturePsalmThatSupportsThisFeature', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @Given /I have Psalm (newer than|older than) "([0-9.]+)" \(because of "([^"]+)"\)/ + * @see \Weirdan\Codeception\Psalm\Module::havePsalmOfACertainVersionRangeBecauseOf() + */ + public function havePsalmOfACertainVersionRangeBecauseOf(string $operator, string $version, string $reason): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('havePsalmOfACertainVersionRangeBecauseOf', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @Then I see these errors + * @see \Weirdan\Codeception\Psalm\Module::seeTheseErrors() + */ + public function seeTheseErrors(\Behat\Gherkin\Node\TableNode $list): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeTheseErrors', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * @Then I see these errors + * @see \Weirdan\Codeception\Psalm\Module::seeTheseErrors() + */ + public function canSeeTheseErrors(\Behat\Gherkin\Node\TableNode $list): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeTheseErrors', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @param string|PyStringNode $code + * @Given I have the following code in :arg1 :arg2 + * @see \Weirdan\Codeception\Psalm\Module::haveTheFollowingCodeIn() + */ + public function haveTheFollowingCodeIn(string $filename, $code): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('haveTheFollowingCodeIn', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @Given I have the following autoload map + * @Given I have the following classmap + * @Given I have the following class map + * @see \Weirdan\Codeception\Psalm\Module::haveTheFollowingAutoloadMap() + */ + public function haveTheFollowingAutoloadMap(\Behat\Gherkin\Node\TableNode $list): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('haveTheFollowingAutoloadMap', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @Given I have the :package package satisfying the :versionConstraint + * @see \Weirdan\Codeception\Psalm\Module::haveADependencySatisfied() + */ + public function haveADependencySatisfied(string $package, string $versionConstraint): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('haveADependencySatisfied', func_get_args())); + } +} diff --git a/tests/acceptance/acceptance/PreventHelpersUsage.feature b/tests/acceptance/acceptance/PreventHelpersUsage.feature index f785ed3..d670c4d 100644 --- a/tests/acceptance/acceptance/PreventHelpersUsage.feature +++ b/tests/acceptance/acceptance/PreventHelpersUsage.feature @@ -34,35 +34,58 @@ Feature: Helpers call | HelperUsed | Helper uses container as service locator, use dependency injection instead. | Examples: - |func | argument | - |app | \stdClass::class | - |resolve | \stdClass::class | - |event | new \stdClass() | - |info | 'Some info log' | - |logger | 'Some log text' | - |logs | null | - |abort | 404 | - |abort_if | true , 404 | - |abort_unless | true , 404 | - |auth | 'web' | - |back | 302 | - |broadcast | null | - |cache | null | - |config | 'app.some' | - |cookie | 'x-developer' | - |dispatch | new \stdClass() | - |dispatch_now | new \stdClass() | - |redirect | '/dev/null' | - |report | new \Exception() | - |request | 'key' | - |response | 'null content' | - |route | 'home' | - |session | 'user_id' | - |trans | 'service_locator'| - |trans_choice | 'key', 2 | - |url | 'localhost' | - |validator | 'di !== helper' | - |view | 'narrow' | + |func | argument | + |app | \stdClass::class | + |resolve | \stdClass::class | + |event | new \stdClass() | + |info | 'Some info log' | + |logger | 'Some log text' | + |logs | null | + |abort | 404 | + |abort_if | true , 404 | + |abort_unless | true , 404 | + |auth | 'web' | + |back | 302 | + |broadcast | null | + |cache | null | + |config | 'app.some' | + |cookie | 'x-developer' | + |dispatch | new \stdClass() | + |dispatch_sync| new \stdClass() | + |redirect | '/dev/null' | + |report | new \Exception() | + |request | 'key' | + |response | 'null content' | + |route | 'home' | + |session | 'user_id' | + |trans | 'service_locator' | + |trans_choice | 'key', 2 | + |url | 'localhost' | + |validator | 'di !== helper' | + |view | 'narrow' | + |encrypt | 'value' | + |decrypt | 'value' | + |action | 'test' | + |app_path | 'storage' | + |asset | 'css' | + |base_path | '' | + |bcrypt | 'value' | + |config_path | 'app.providers' | + |csrf_token | | + |database_path| '' | + |lang_path | '' | + |old | 'key' | + |policy | \stdClass::class | + |precognitive |null | + |public_path |'' | + |report_if |true, new \Exception()| + |report_unless|true, new \Exception()| + |resource_path|'' | + |secure_asset |'' | + |secure_url |'' | + |storage_path |'' | + |to_route |'main' | + |__ |'key' | Scenario: Assert that we can use simple function and psalm no see errors. Given I have the following code diff --git a/tests/stubs/ImplementedLaravelApplication.php b/tests/stubs/ImplementedLaravelApplication.php index cae610a..ffc3077 100644 --- a/tests/stubs/ImplementedLaravelApplication.php +++ b/tests/stubs/ImplementedLaravelApplication.php @@ -60,7 +60,7 @@ public function resourcePath($path = '') /** * {@inheritdoc} */ - public function storagePath() + public function storagePath($path = '') { // TODO: Implement storagePath() method. } @@ -388,8 +388,53 @@ public function get($id) /** * {@inheritdoc} */ - public function has($id) + public function has($id): bool { - // TODO: Implement has() method. + return true; + } + + public function langPath($path = '') + { + // TODO: Implement langPath() method. + } + + public function publicPath($path = '') + { + // TODO: Implement publicPath() method. + } + + public function hasDebugModeEnabled() + { + // TODO: Implement hasDebugModeEnabled() method. + } + + public function maintenanceMode() + { + // TODO: Implement maintenanceMode() method. + } + + public function terminating($callback) + { + // TODO: Implement terminating() method. + } + + public function bindMethod($method, $callback) + { + // TODO: Implement bindMethod() method. + } + + public function scoped($abstract, $concrete = null) + { + // TODO: Implement scoped() method. + } + + public function scopedIf($abstract, $concrete = null) + { + // TODO: Implement scopedIf() method. + } + + public function beforeResolving($abstract, Closure $callback = null) + { + // TODO: Implement beforeResolving() method. } } diff --git a/tests/stubs/ImplementedLaravelContainer.php b/tests/stubs/ImplementedLaravelContainer.php index d036f00..ae48a09 100644 --- a/tests/stubs/ImplementedLaravelContainer.php +++ b/tests/stubs/ImplementedLaravelContainer.php @@ -172,8 +172,28 @@ public function get($id) /** * {@inheritdoc} */ - public function has($id) + public function has($id): bool { - // TODO: Implement has() method. + return true; + } + + public function bindMethod($method, $callback) + { + // TODO: Implement bindMethod() method. + } + + public function scoped($abstract, $concrete = null) + { + // TODO: Implement scoped() method. + } + + public function scopedIf($abstract, $concrete = null) + { + // TODO: Implement scopedIf() method. + } + + public function beforeResolving($abstract, Closure $callback = null) + { + // TODO: Implement beforeResolving() method. } } diff --git a/tests/stubs/ImplementedPsrContainer.php b/tests/stubs/ImplementedPsrContainer.php index ef5c6c8..8544939 100644 --- a/tests/stubs/ImplementedPsrContainer.php +++ b/tests/stubs/ImplementedPsrContainer.php @@ -19,8 +19,8 @@ public function get($id) /** * {@inheritdoc} */ - public function has($id) + public function has($id): bool { - // TODO: Implement has() method. + return true; } }