diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5d66bc4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,27 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true + +[*.{php,phpt}] +indent_style = tab +indent_size = 4 + +[*.xml] +indent_style = tab +indent_size = 4 + +[*.neon] +indent_style = tab +indent_size = 4 + +[*.{yaml,yml}] +indent_style = space +indent_size = 2 + +[composer.json] +indent_style = tab +indent_size = 4 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..4838772 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +*.php text eol=lf + +.github export-ignore +tests export-ignore +tmp export-ignore +.gitattributes export-ignore +.gitignore export-ignore +Makefile export-ignore +phpcs.xml export-ignore +phpstan.neon export-ignore +phpunit.xml export-ignore diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..7877c31 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,18 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: +- package-ecosystem: github-actions + directory: '/' + schedule: + interval: weekly + open-pull-requests-limit: 10 + +- package-ecosystem: composer + directory: '/' + schedule: + interval: weekly + open-pull-requests-limit: 10 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..3525498 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,162 @@ +# https://help.github.com/en/categories/automating-your-workflow-with-github-actions + +name: "Build" + +on: + pull_request: + push: + branches: + - "1.1.x" + +jobs: + lint: + name: "Lint" + runs-on: "ubuntu-latest" + + strategy: + matrix: + php-version: + - "7.2" + - "7.3" + - "7.4" + - "8.0" + - "8.1" + - "8.2" + + steps: + - name: "Checkout" + uses: actions/checkout@v3 + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + coverage: "none" + php-version: "${{ matrix.php-version }}" + + - name: "Validate Composer" + run: "composer validate" + + - name: "Install dependencies" + run: "composer install --no-interaction --no-progress" + + - name: "Downgrade PHPUnit" + if: matrix.php-version == '7.2' || matrix.php-version == '7.3' + run: "composer require --dev phpunit/phpunit:^7.5.20 --update-with-dependencies" + + - name: "Lint" + run: "make lint" + + coding-standards: + name: "Coding Standard" + + runs-on: "ubuntu-latest" + + steps: + - name: "Checkout" + uses: actions/checkout@v3 + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + coverage: "none" + php-version: "8.0" + + - name: "Validate Composer" + run: "composer validate" + + - name: "Install dependencies" + run: "composer install --no-interaction --no-progress" + + - name: "Lint" + run: "make lint" + + - name: "Coding Standard" + run: "make cs" + + tests: + name: "Tests" + runs-on: "ubuntu-latest" + + strategy: + fail-fast: false + matrix: + php-version: + - "7.2" + - "7.3" + - "7.4" + - "8.0" + - "8.1" + - "8.2" + dependencies: + - "lowest" + - "highest" + + steps: + - name: "Checkout" + uses: actions/checkout@v3 + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + coverage: "none" + php-version: "${{ matrix.php-version }}" + + - name: "Install lowest dependencies" + if: ${{ matrix.dependencies == 'lowest' }} + run: "composer update --prefer-lowest --no-interaction --no-progress" + + - name: "Install highest dependencies" + if: ${{ matrix.dependencies == 'highest' }} + run: "composer update --no-interaction --no-progress" + + - name: "Downgrade PHPUnit" + if: matrix.php-version == '7.2' || matrix.php-version == '7.3' + run: "composer require --dev phpunit/phpunit:^7.5.20 --update-with-dependencies" + + - name: "Tests" + run: "make tests" + + static-analysis: + name: "PHPStan" + runs-on: "ubuntu-latest" + + strategy: + fail-fast: false + matrix: + php-version: + - "7.2" + - "7.3" + - "7.4" + - "8.0" + - "8.1" + - "8.2" + dependencies: + - "lowest" + - "highest" + + steps: + - name: "Checkout" + uses: actions/checkout@v3 + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + coverage: "none" + php-version: "${{ matrix.php-version }}" + extensions: mbstring + tools: composer:v2 + + - name: "Install lowest dependencies" + if: ${{ matrix.dependencies == 'lowest' }} + run: "composer update --prefer-lowest --no-interaction --no-progress" + + - name: "Install highest dependencies" + if: ${{ matrix.dependencies == 'highest' }} + run: "composer update --no-interaction --no-progress" + + - name: "Downgrade PHPUnit" + if: matrix.php-version == '7.2' || matrix.php-version == '7.3' + run: "composer require --dev phpunit/phpunit:^7.5.20 --update-with-dependencies" + + - name: "PHPStan" + run: "make phpstan" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2db2131 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/tests/tmp +/vendor +/composer.lock +.phpunit.result.cache diff --git a/README.md b/README.md new file mode 100644 index 0000000..37a419f --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# Rules for detecting usage of pseudo-private functions, classes, and methods. + +[![Build](https://github.com/swissspidy/phpstan-no-private/workflows/Build/badge.svg)](https://github.com/swissspidy/phpstan-no-private/actions) +[![Latest Stable Version](https://poser.pugx.org/swissspidy/phpstan-no-private/v/stable)](https://packagist.org/packages/swissspidy/phpstan-no-private) +[![License](https://poser.pugx.org/swissspidy/phpstan-no-private/license)](https://packagist.org/packages/swissspidy/phpstan-no-private) + +This extension emits deprecation warnings on code which uses properties/functions/methods/classes which are annotated as `@access private`. + +## Installation + +To use this extension, require it in [Composer](https://getcomposer.org/): + +``` +composer require --dev swissspidy/phpstan-no-private +``` + +If you also install [phpstan/extension-installer](https://github.com/phpstan/extension-installer) then you're all set! + +
+ Manual installation + +If you don't want to use `phpstan/extension-installer`, include rules.neon in your project's PHPStan config: + +``` +includes: + - vendor/swissspidy/phpstan-no-private/rules.neon +``` +
+ +## Credits + +This project is a fork of the excellent [phpstan/phpstan-deprecation-rules](https://github.com/phpstan/phpstan-deprecation-rules), +which provides rules that detect usage of deprecated classes, methods, properties, constants and traits. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..84dccad --- /dev/null +++ b/composer.json @@ -0,0 +1,55 @@ +{ + "name": "swissspidy/phpstan-no-private", + "type": "phpstan-extension", + "description": "PHPStan rules for detecting usage of pseudo-private functions, classes, and methods.", + "license": [ + "MIT" + ], + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.10.3" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-php-parser": "^1.1", + "phpunit/phpunit": "^9.5", + "slevomat/coding-standard": "^8.8.0", + "squizlabs/php_codesniffer": "^3.5.3" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + }, + "platform": { + "php": "7.4.6" + }, + "sort-packages": true + }, + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Swissspidy\\PHPStan\\": "src/" + } + }, + "autoload-dev": { + "classmap": [ + "tests/" + ] + }, + "minimum-stability": "dev", + "prefer-stable": true, + "scripts": { + "lint": "vendor/bin/phpcs", + "lint:fix": "vendor/bin/phpcbf", + "phpstan": "vendor/bin/phpstan analyse -l 8 -c phpstan.neon src tests", + "test": "vendor/bin/phpunit" + } +} diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..4d95842 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,99 @@ + + + + + + + + + src + tests + + + + + + + + + + + + + + + + + + 10 + + + + + + 10 + + + + + + + + 10 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + tests/*/data + diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..e3c1480 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,9 @@ +includes: + - rules.neon + - vendor/phpstan/phpstan/conf/bleedingEdge.neon + - vendor/phpstan/phpstan-php-parser/extension.neon + - vendor/phpstan/phpstan-phpunit/extension.neon + +parameters: + excludePaths: + - tests/*/data/* diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..edb71a8 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,36 @@ + + + + + ./src + + + + + + + + + + tests + + + + + diff --git a/rules.neon b/rules.neon new file mode 100644 index 0000000..e02da1d --- /dev/null +++ b/rules.neon @@ -0,0 +1,20 @@ +parameters: + deprecationRulesInstalled: true + +rules: + - Swissspidy\PHPStan\Rules\NoPrivate\AccessPrivatePropertyRule + - Swissspidy\PHPStan\Rules\NoPrivate\AccessPrivateStaticPropertyRule + - Swissspidy\PHPStan\Rules\NoPrivate\CallToPrivateFunctionRule + - Swissspidy\PHPStan\Rules\NoPrivate\CallToPrivateMethodRule + - Swissspidy\PHPStan\Rules\NoPrivate\CallToPrivateStaticMethodRule + - Swissspidy\PHPStan\Rules\NoPrivate\FetchingClassConstOfPrivateClassRule + - Swissspidy\PHPStan\Rules\NoPrivate\FetchingPrivateConstRule + - Swissspidy\PHPStan\Rules\NoPrivate\ImplementationOfPrivateInterfaceRule + - Swissspidy\PHPStan\Rules\NoPrivate\InheritanceOfPrivateClassRule + - Swissspidy\PHPStan\Rules\NoPrivate\InheritanceOfPrivateInterfaceRule + - Swissspidy\PHPStan\Rules\NoPrivate\InstantiationOfPrivateClassRule + - Swissspidy\PHPStan\Rules\NoPrivate\TypeHintPrivateInClassMethodSignatureRule + - Swissspidy\PHPStan\Rules\NoPrivate\TypeHintPrivateInClosureSignatureRule + - Swissspidy\PHPStan\Rules\NoPrivate\TypeHintPrivateInFunctionSignatureRule + - Swissspidy\PHPStan\Rules\NoPrivate\UsageOfPrivateCastRule + - Swissspidy\PHPStan\Rules\NoPrivate\UsageOfPrivateTraitRule diff --git a/src/Rules/NoPrivate/CallToPrivateFunctionRule.php b/src/Rules/NoPrivate/CallToPrivateFunctionRule.php new file mode 100644 index 0000000..b3b6b69 --- /dev/null +++ b/src/Rules/NoPrivate/CallToPrivateFunctionRule.php @@ -0,0 +1,74 @@ + + */ +class CallToPrivateFunctionRule implements Rule +{ + + /** @var ReflectionProvider */ + private $reflectionProvider; + + /** @var FileTypeMapper */ + private $fileTypeMapper; + + public function __construct(ReflectionProvider $reflectionProvider, FileTypeMapper $fileTypeMapper) + { + $this->reflectionProvider = $reflectionProvider; + $this->fileTypeMapper = $fileTypeMapper; + } + + public function getNodeType(): string + { + return FuncCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!($node->name instanceof Name)) { + return []; + } + + try { + $function = $this->reflectionProvider->getFunction($node->name, $scope); + } catch (FunctionNotFoundException $e) { + // Other rules will notify if the function is not found + return []; + } + + $docComment = $function->getDocComment(); + + if (!$docComment) { + return []; + } + + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $scope->isInClass() ? $scope->getClassReflection()->getName() : null, + $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, + $function->getName(), + $docComment + ); + if (!$resolvedPhpDoc || (!$function->isInternal() && !PrivateAnnotationHelper::isPrivate($resolvedPhpDoc))) { + return []; + } + + return [sprintf( + 'Call to private function %s().', + $function->getName() + )]; + } + +} diff --git a/src/Rules/NoPrivate/CallToPrivateMethodRule.php b/src/Rules/NoPrivate/CallToPrivateMethodRule.php new file mode 100644 index 0000000..ed8faef --- /dev/null +++ b/src/Rules/NoPrivate/CallToPrivateMethodRule.php @@ -0,0 +1,86 @@ + + */ +class CallToPrivateMethodRule implements Rule +{ + + /** @var ReflectionProvider */ + private $reflectionProvider; + + /** @var FileTypeMapper */ + private $fileTypeMapper; + + public function __construct(ReflectionProvider $reflectionProvider, FileTypeMapper $fileTypeMapper) + { + $this->reflectionProvider = $reflectionProvider; + $this->fileTypeMapper = $fileTypeMapper; + } + + public function getNodeType(): string + { + return MethodCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Identifier) { + return []; + } + + $methodName = $node->name->name; + $methodCalledOnType = $scope->getType($node->var); + $referencedClasses = $methodCalledOnType->getObjectClassNames(); + + foreach ($referencedClasses as $referencedClass) { + try { + $classReflection = $this->reflectionProvider->getClass($referencedClass); + $methodReflection = $classReflection->getMethod($methodName, $scope); + + $docComment = $methodReflection->getDocComment(); + + if (!$docComment) { + continue; + } + + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $scope->isInClass() ? $scope->getClassReflection()->getName() : null, + $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, + $methodReflection->getName(), + $docComment + ); + if (!$resolvedPhpDoc || (!$methodReflection->isInternal() && !PrivateAnnotationHelper::isPrivate($resolvedPhpDoc))) { + continue; + } + + return [sprintf( + 'Call to private method %s() of class %s.', + $methodReflection->getName(), + $methodReflection->getDeclaringClass()->getName() + )]; + } catch (ClassNotFoundException $e) { + // Other rules will notify if the class is not found + } catch (MissingMethodFromReflectionException $e) { + // Other rules will notify if the the method is not found + } + } + + return []; + } + +} diff --git a/src/Rules/NoPrivate/CallToPrivateStaticMethodRule.php b/src/Rules/NoPrivate/CallToPrivateStaticMethodRule.php new file mode 100644 index 0000000..3e466af --- /dev/null +++ b/src/Rules/NoPrivate/CallToPrivateStaticMethodRule.php @@ -0,0 +1,124 @@ + + */ +class CallToPrivateStaticMethodRule implements Rule +{ + + /** @var ReflectionProvider */ + private $reflectionProvider; + + /** @var RuleLevelHelper */ + private $ruleLevelHelper; + + /** @var FileTypeMapper */ + private $fileTypeMapper; + + public function __construct(ReflectionProvider $reflectionProvider, RuleLevelHelper $ruleLevelHelper, FileTypeMapper $fileTypeMapper) + { + $this->reflectionProvider = $reflectionProvider; + $this->ruleLevelHelper = $ruleLevelHelper; + $this->fileTypeMapper = $fileTypeMapper; + } + + public function getNodeType(): string + { + return StaticCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Identifier) { + return []; + } + + $methodName = $node->name->name; + $referencedClasses = []; + + if ($node->class instanceof Name) { + $referencedClasses[] = $scope->resolveName($node->class); + } else { + $classTypeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->class, + '', // We don't care about the error message + static function (Type $type) use ($methodName): bool { + return $type->canCallMethods()->yes() && $type->hasMethod($methodName)->yes(); + } + ); + + if ($classTypeResult->getType() instanceof ErrorType) { + return []; + } + + $referencedClasses = $classTypeResult->getReferencedClasses(); + } + + $errors = []; + + foreach ($referencedClasses as $referencedClass) { + try { + $class = $this->reflectionProvider->getClass($referencedClass); + $methodReflection = $class->getMethod($methodName, $scope); + } catch (ClassNotFoundException $e) { + continue; + } catch (MissingMethodFromReflectionException $e) { + continue; + } + + $resolvedPhpDoc = $class->getResolvedPhpDoc(); + if (($class->isInternal() || ( $resolvedPhpDoc && PrivateAnnotationHelper::isPrivate($resolvedPhpDoc)))) { + $errors[] = sprintf( + 'Call to method %s() of private class %s.', + $methodReflection->getName(), + $methodReflection->getDeclaringClass()->getName() + ); + continue; + } + + $docComment = $methodReflection->getDocComment(); + + if (!$docComment) { + continue; + } + + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $scope->isInClass() ? $scope->getClassReflection()->getName() : null, + $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, + $methodReflection->getName(), + $docComment + ); + if (!$resolvedPhpDoc || (!$methodReflection->isInternal() && !PrivateAnnotationHelper::isPrivate($resolvedPhpDoc))) { + continue; + } + + $errors[] = sprintf( + 'Call to private method %s() of class %s.', + $methodReflection->getName(), + $methodReflection->getDeclaringClass()->getName() + ); + } + + return $errors; + } + +} diff --git a/src/Rules/NoPrivate/InheritanceOfPrivateClassRule.php b/src/Rules/NoPrivate/InheritanceOfPrivateClassRule.php new file mode 100644 index 0000000..d4931dc --- /dev/null +++ b/src/Rules/NoPrivate/InheritanceOfPrivateClassRule.php @@ -0,0 +1,78 @@ + + */ +class InheritanceOfPrivateClassRule implements Rule +{ + + /** @var ReflectionProvider */ + private $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + + public function getNodeType(): string + { + return Class_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->extends === null) { + return []; + } + + $errors = []; + + $className = isset($node->namespacedName) + ? (string) $node->namespacedName + : (string) $node->name; + + try { + $class = $this->reflectionProvider->getClass($className); + } catch (ClassNotFoundException $e) { + return []; + } + + $parentClassName = (string) $node->extends; + + try { + $parentClass = $this->reflectionProvider->getClass($parentClassName); + $resolvedPhpDoc = $parentClass->getResolvedPhpDoc(); + if (!$resolvedPhpDoc || (!$parentClass->isInternal() && !PrivateAnnotationHelper::isPrivate($resolvedPhpDoc))) { + return $errors; + } + + if (!$class->isAnonymous()) { + $errors[] = sprintf( + 'Class %s extends private class %s.', + $className, + $parentClassName + ); + } else { + $errors[] = sprintf( + 'Anonymous class extends private class %s.', + $parentClassName + ); + } + } catch (ClassNotFoundException $e) { + // Other rules will notify if the interface is not found + } + + return $errors; + } + +} diff --git a/src/Rules/NoPrivate/InstantiationOfPrivateClassRule.php b/src/Rules/NoPrivate/InstantiationOfPrivateClassRule.php new file mode 100644 index 0000000..39f8b47 --- /dev/null +++ b/src/Rules/NoPrivate/InstantiationOfPrivateClassRule.php @@ -0,0 +1,92 @@ + + */ +class InstantiationOfPrivateClassRule implements Rule +{ + + /** @var ReflectionProvider */ + private $reflectionProvider; + + /** @var RuleLevelHelper */ + private $ruleLevelHelper; + + public function __construct(ReflectionProvider $reflectionProvider, RuleLevelHelper $ruleLevelHelper) + { + $this->reflectionProvider = $reflectionProvider; + $this->ruleLevelHelper = $ruleLevelHelper; + } + + public function getNodeType(): string + { + return New_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $referencedClasses = []; + + if ($node->class instanceof Name) { + $referencedClasses[] = $scope->resolveName($node->class); + } elseif ($node->class instanceof Class_) { + if (!isset($node->class->namespacedName)) { + return []; + } + + $referencedClasses[] = $scope->resolveName($node->class->namespacedName); + } else { + $classTypeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->class, + '', // We don't care about the error message + static function (): bool { + return true; + } + ); + + if ($classTypeResult->getType() instanceof ErrorType) { + return []; + } + + $referencedClasses = $classTypeResult->getReferencedClasses(); + } + + $errors = []; + + foreach ($referencedClasses as $referencedClass) { + try { + $class = $this->reflectionProvider->getClass($referencedClass); + } catch (ClassNotFoundException $e) { + continue; + } + + $resolvedPhpDoc = $class->getResolvedPhpDoc(); + if (!$resolvedPhpDoc || (!$class->isInternal() && !PrivateAnnotationHelper::isPrivate($resolvedPhpDoc))) { + continue; + } + + $errors[] = sprintf( + 'Instantiation of private class %s.', + $referencedClass + ); + } + + return $errors; + } + +} diff --git a/src/Rules/NoPrivate/PrivateAnnotationHelper.php b/src/Rules/NoPrivate/PrivateAnnotationHelper.php new file mode 100644 index 0000000..48be3aa --- /dev/null +++ b/src/Rules/NoPrivate/PrivateAnnotationHelper.php @@ -0,0 +1,30 @@ +getPhpDocNodes() as $phpDocNode) { + foreach(array_column($phpDocNode->getTagsByName('@access'), 'value') as $accessTagValue) { + $scope = (string) $accessTagValue; + if ( 'private' === $scope) { + return true; + } + } + } + + return false; + } + +} diff --git a/tests/Rules/NoPrivate/CallToPrivateFunctionRuleTest.php b/tests/Rules/NoPrivate/CallToPrivateFunctionRuleTest.php new file mode 100644 index 0000000..6457f10 --- /dev/null +++ b/tests/Rules/NoPrivate/CallToPrivateFunctionRuleTest.php @@ -0,0 +1,38 @@ + + */ +class CallToPrivateFunctionRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new CallToPrivateFunctionRule($this->createReflectionProvider(), self::getContainer()->getByType(FileTypeMapper::class)); + } + + public function testPrivateFunctionCall(): void + { + require_once __DIR__ . '/data/call-to-private-function-definition.php'; + $this->analyse( + [__DIR__ . '/data/call-to-private-function.php'], + [ + [ + 'Call to private function CheckPrivateFunctionCall\private_function().', + 8, + ], + [ + 'Call to private function CheckPrivateFunctionCall\private_function().', + 9, + ], + ] + ); + } + +} diff --git a/tests/Rules/NoPrivate/CallToPrivateMethodRuleTest.php b/tests/Rules/NoPrivate/CallToPrivateMethodRuleTest.php new file mode 100644 index 0000000..b7954c2 --- /dev/null +++ b/tests/Rules/NoPrivate/CallToPrivateMethodRuleTest.php @@ -0,0 +1,34 @@ + + */ +class CallToPrivateMethodRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new CallToPrivateMethodRule($this->createReflectionProvider(), self::getContainer()->getByType(FileTypeMapper::class)); + } + + public function testPrivateMethodCall(): void + { + require_once __DIR__ . '/data/call-to-private-method-definition.php'; + $this->analyse( + [__DIR__ . '/data/call-to-private-method.php'], + [ + [ + 'Call to private method privateFoo() of class CheckPrivateMethodCall\Foo.', + 7, + ], + ] + ); + } + +} diff --git a/tests/Rules/NoPrivate/CallToPrivateStaticMethodRuleTest.php b/tests/Rules/NoPrivate/CallToPrivateStaticMethodRuleTest.php new file mode 100644 index 0000000..ad89921 --- /dev/null +++ b/tests/Rules/NoPrivate/CallToPrivateStaticMethodRuleTest.php @@ -0,0 +1,67 @@ + + */ +class CallToPrivateStaticMethodRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new CallToPrivateStaticMethodRule($this->createReflectionProvider(), self::getContainer()->getByType(RuleLevelHelper::class), self::getContainer()->getByType(FileTypeMapper::class)); + } + + public function testPrivateStaticMethodCall(): void + { + require_once __DIR__ . '/data/call-to-private-static-method-definition.php'; + $this->analyse( + [__DIR__ . '/data/call-to-private-static-method.php'], + [ + [ + 'Call to private method privateFoo() of class CheckPrivateStaticMethodCall\Foo.', + 6, + ], + [ + 'Call to method foo() of private class CheckPrivateStaticMethodCall\Foo.', + 10, + ], + [ + 'Call to method privateFoo() of private class CheckPrivateStaticMethodCall\Foo.', + 11, + ], + [ + 'Call to private method privateFoo() of class CheckPrivateStaticMethodCall\Foo.', + 19, + ], + [ + 'Call to private method privateFoo() of class CheckPrivateStaticMethodCall\Foo.', + 28, + ], + [ + 'Call to private method privateFoo() of class CheckPrivateStaticMethodCall\Foo.', + 44, + ], + [ + 'Call to private method privateOtherFoo() of class CheckPrivateStaticMethodCall\Child.', + 45, + ], + [ + 'Call to private method privateFoo() of class CheckPrivateStaticMethodCall\Foo.', + 46, + ], + [ + 'Call to private method privateOtherFoo() of class CheckPrivateStaticMethodCall\Child.', + 47, + ], + ] + ); + } + +} diff --git a/tests/Rules/NoPrivate/InheritanceOfPrivateClassRuleTest.php b/tests/Rules/NoPrivate/InheritanceOfPrivateClassRuleTest.php new file mode 100644 index 0000000..28ee130 --- /dev/null +++ b/tests/Rules/NoPrivate/InheritanceOfPrivateClassRuleTest.php @@ -0,0 +1,47 @@ + + */ +class InheritanceOfPrivateClassRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new InheritanceOfPrivateClassRule($this->createReflectionProvider()); + } + + public function testInheritanceOfPrivateClassInClasses(): void + { + require_once __DIR__ . '/data/inheritance-of-private-class-definition.php'; + $this->analyse( + [__DIR__ . '/data/inheritance-of-private-class-in-classes.php'], + [ + [ + 'Class InheritanceOfPrivateClass\Bar2 extends private class InheritanceOfPrivateClass\PrivateFoo.', + 10, + ], + ] + ); + } + + public function testInheritanceOfPrivateClassInAnonymousClasses(): void + { + require_once __DIR__ . '/data/inheritance-of-private-class-definition.php'; + $this->analyse( + [__DIR__ . '/data/inheritance-of-private-class-in-anonymous-classes.php'], + [ + [ + 'Anonymous class extends private class InheritanceOfPrivateClass\PrivateFoo.', + 9, + ], + ] + ); + } + +} diff --git a/tests/Rules/NoPrivate/InstantiationOfPrivateClassRuleTest.php b/tests/Rules/NoPrivate/InstantiationOfPrivateClassRuleTest.php new file mode 100644 index 0000000..e2e1ba3 --- /dev/null +++ b/tests/Rules/NoPrivate/InstantiationOfPrivateClassRuleTest.php @@ -0,0 +1,34 @@ + + */ +class InstantiationOfPrivateClassRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new InstantiationOfPrivateClassRule($this->createReflectionProvider(), self::getContainer()->getByType(RuleLevelHelper::class)); + } + + public function testInstantiationOfPrivateClass(): void + { + require_once __DIR__ . '/data/instantiation-of-private-class-definition.php'; + $this->analyse( + [__DIR__ . '/data/instantiation-of-private-class.php'], + [ + [ + 'Instantiation of private class InstantiationOfPrivateClass\PrivateFoo.', + 6, + ], + ] + ); + } + +} diff --git a/tests/Rules/NoPrivate/data/call-to-private-function-definition.php b/tests/Rules/NoPrivate/data/call-to-private-function-definition.php new file mode 100644 index 0000000..b217f24 --- /dev/null +++ b/tests/Rules/NoPrivate/data/call-to-private-function-definition.php @@ -0,0 +1,17 @@ +foo(); +$foo->privateFoo(); diff --git a/tests/Rules/NoPrivate/data/call-to-private-static-method-definition.php b/tests/Rules/NoPrivate/data/call-to-private-static-method-definition.php new file mode 100644 index 0000000..e3d1e54 --- /dev/null +++ b/tests/Rules/NoPrivate/data/call-to-private-static-method-definition.php @@ -0,0 +1,38 @@ +