diff --git a/README.md b/README.md index 22506a4..62ee377 100644 --- a/README.md +++ b/README.md @@ -40,12 +40,12 @@ includes: - vendor/struggle-for-php/sfp-phpstan-psr-log/extension.neon ``` -and, set parameters `enableLogLevelMethodRule` and `enableContextTypeRule` +and, set parameters `enableLogMethodLevelRule` and `enableContextTypeRule` ```neon parameters: sfpPsrLog: - enableLogLevelMethodRule: true # default:false + enableLogMethodLevelRule: true # default:false enableContextTypeRule: true # default:false ``` diff --git a/composer-require-checker.json b/composer-require-checker.json index c4317ba..b5f420a 100644 --- a/composer-require-checker.json +++ b/composer-require-checker.json @@ -25,6 +25,7 @@ "PHPStan\\Type\\FloatType", "PHPStan\\Type\\IntegerType", "PHPStan\\Type\\StringType", - "PHPStan\\Type\\TypeCombinator" + "PHPStan\\Type\\TypeCombinator", + "PHPStan\\Reflection\\ClassReflection" ] } diff --git a/example/phpstan.default.neon b/example/phpstan.default.neon index afd9d55..b4b54f9 100644 --- a/example/phpstan.default.neon +++ b/example/phpstan.default.neon @@ -4,7 +4,7 @@ # docker run -v $(realpath .):/github/workspace -w=/github/workspace ghcr.io/laminas/laminas-continuous-integration:1 \ # '{"php":"8.2","dependencies":"latest","extensions":[],"ini":["memory_limit=-1"],"command":"./vendor/bin/phpstan analyse -c ./example/phpstan.default.neon --no-progress --error-format=junit | xmllint --format -"}' # docker run -v $(realpath .):/github/workspace -w=/github/workspace ghcr.io/laminas/laminas-continuous-integration:1 \ -# '{"php":"8.2","dependencies":"latest","extensions":[],"ini":["memory_limit=-1"],"command":"diff <(./vendor/bin/phpstan analyse -c ./example/phpstan.default.neon --no-progress --error-format=junit | xmllint --format -) ./test/example.output"}' +# '{"php":"8.2","dependencies":"latest","extensions":[],"ini":["memory_limit=-1"],"command":"diff <(./vendor/bin/phpstan analyse -c ./example/phpstan.default.neon --no-progress --error-format=junit | xmllint --format -) ./test/example.default.output"}' parameters: level: 5 diff --git a/example/phpstan.enableContextTypeRule.neon b/example/phpstan.enableContextTypeRule.neon index 886ea3c..8e2e1fe 100644 --- a/example/phpstan.enableContextTypeRule.neon +++ b/example/phpstan.enableContextTypeRule.neon @@ -4,7 +4,7 @@ # docker run -v $(realpath .):/github/workspace -w=/github/workspace ghcr.io/laminas/laminas-continuous-integration:1 \ # '{"php":"8.2","dependencies":"latest","extensions":[],"ini":["memory_limit=-1"],"command":"./vendor/bin/phpstan analyse -c ./example/phpstan.enableContextTypeRule.neon --no-progress --error-format=junit | xmllint --format -"}' # docker run -v $(realpath .):/github/workspace -w=/github/workspace ghcr.io/laminas/laminas-continuous-integration:1 \ -# '{"php":"8.2","dependencies":"latest","extensions":[],"ini":["memory_limit=-1"],"command":"diff <(./vendor/bin/phpstan analyse -c ./example/phpstan.enableContextTypeRule.neon --no-progress --error-format=junit | xmllint --format -) ./test/example.output"}' +# '{"php":"8.2","dependencies":"latest","extensions":[],"ini":["memory_limit=-1"],"command":"diff <(./vendor/bin/phpstan analyse -c ./example/phpstan.enableContextTypeRule.neon --no-progress --error-format=junit | xmllint --format -) ./test/example.enableContextTypeRule.output"}' parameters: level: 5 @@ -13,7 +13,7 @@ parameters: paths: - %currentWorkingDirectory%/example/src sfpPsrLog: - enableLogLevelMethodRule: true + enableLogMethodLevelRule: true enableContextTypeRule: true # services: diff --git a/example/phpstan.recommend.neon b/example/phpstan.recommend.neon index 26c380c..f0e8506 100644 --- a/example/phpstan.recommend.neon +++ b/example/phpstan.recommend.neon @@ -4,7 +4,7 @@ # docker run -v $(realpath .):/github/workspace -w=/github/workspace ghcr.io/laminas/laminas-continuous-integration:1 \ # '{"php":"8.2","dependencies":"latest","extensions":[],"ini":["memory_limit=-1"],"command":"./vendor/bin/phpstan analyse -c ./example/phpstan.recommend.neon --no-progress --error-format=junit | xmllint --format -"}' # docker run -v $(realpath .):/github/workspace -w=/github/workspace ghcr.io/laminas/laminas-continuous-integration:1 \ -# '{"php":"8.2","dependencies":"latest","extensions":[],"ini":["memory_limit=-1"],"command":"diff <(./vendor/bin/phpstan analyse -c ./example/phpstan.recommend.neon --no-progress --error-format=junit | xmllint --format -) ./test/example.output"}' +# '{"php":"8.2","dependencies":"latest","extensions":[],"ini":["memory_limit=-1"],"command":"diff <(./vendor/bin/phpstan analyse -c ./example/phpstan.recommend.neon --no-progress --error-format=junit | xmllint --format -) ./test/example.recommend.output"}' parameters: level: 5 diff --git a/phpcs.xml b/phpcs.xml index 8712e33..f299cc5 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -3,26 +3,28 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="./vendor/squizlabs/php_codesniffer/phpcs.xsd"> - - - - - + + + + + - - + + - - src - test + + src + test - test/TypeProvider/data/* + test/TypeProvider/data/* - - + + + + - - - - + + + + diff --git a/rules.neon b/rules.neon index 24c0a71..4a28263 100644 --- a/rules.neon +++ b/rules.neon @@ -4,7 +4,7 @@ parametersSchema: enableMessageStaticStringRule: bool(), reportContextExceptionLogLevel: schema(string(), nullable()), contextKeyOriginalPattern: schema(string(), nullable()), - enableLogLevelMethodRule: bool() + enableLogMethodLevelRule: bool() enableContextTypeRule: bool() ]) @@ -14,7 +14,7 @@ parameters: enableMessageStaticStringRule: true reportContextExceptionLogLevel: 'debug' contextKeyOriginalPattern: null - enableLogLevelMethodRule: false + enableLogMethodLevelRule: false enableContextTypeRule: false conditionalTags: @@ -23,7 +23,7 @@ conditionalTags: Sfp\PHPStan\Psr\Log\Rules\MessageStaticStringRule: phpstan.rules.rule: %sfpPsrLog.enableMessageStaticStringRule% Sfp\PHPStan\Psr\Log\Rules\LogMethodLevelRule: - phpstan.rules.rule: %sfpPsrLog.enableLogLevelMethodRule% + phpstan.rules.rule: %sfpPsrLog.enableLogMethodLevelRule% Sfp\PHPStan\Psr\Log\Rules\ContextTypeRule: phpstan.rules.rule: %sfpPsrLog.enableContextTypeRule% @@ -51,20 +51,25 @@ services: - class: Sfp\PHPStan\Psr\Log\Rules\ContextTypeRule - arguments: - contextTypeProvider: @sfpPsrLogContextTypeProvider - sfpPsrLogContextTypeProvider : - class: Sfp\PHPStan\Psr\Log\TypeProvider\Psr3ContextTypeProvider - arguments: - exceptionClass: '\Throwable' + # - + # class: Sfp\PHPStan\Psr\Log\Rules\ContextTypeRule + # arguments: + # contextTypeProviderResolver: @contextTypeProviderResolver + - # # BigQuery + # contextTypeProviderResolver: + + # psrLogContextTypeProvider : + # class: Sfp\PHPStan\Psr\Log\TypeProvider\Psr3ContextTypeProvider + # arguments: + # exceptionClass: '\Exception' + + # BigQuery # - # sfpPsrLogContextTypeProvider : + # psrLogContextTypeProvider : # class: Sfp\PHPStan\Psr\Log\TypeProvider\BigQueryContextTypeProvider # arguments: # schemaFile: - # - # class: Sfp\PHPStan\Psr\Log\TypeMapping\BigQuery\GenericTableFieldSchemaJsonPayloadTypeMapper diff --git a/src/Rules/ContextTypeRule.php b/src/Rules/ContextTypeRule.php index 26223b3..6f629fd 100644 --- a/src/Rules/ContextTypeRule.php +++ b/src/Rules/ContextTypeRule.php @@ -10,8 +10,9 @@ use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; use PHPStan\Type\ObjectType; -use Sfp\PHPStan\Psr\Log\TypeProvider\ContextTypeProviderInterface; use Sfp\PHPStan\Psr\Log\TypeProvider\Psr3ContextTypeProvider; +use Sfp\PHPStan\Psr\Log\TypeProviderResolver\AnyScopeContextTypeProviderResolver; +use Sfp\PHPStan\Psr\Log\TypeProviderResolver\ContextTypeProviderResolverInterface; use function count; use function in_array; @@ -22,12 +23,12 @@ */ final class ContextTypeRule implements Rule { - /** @var ContextTypeProviderInterface */ - private $contextTypeProvider; + /** @var ContextTypeProviderResolverInterface */ + private $contextTypeProviderResolver; - public function __construct(?ContextTypeProviderInterface $contextTypeProvider = null) + public function __construct(?ContextTypeProviderResolverInterface $contextTypeProviderResolver) { - $this->contextTypeProvider = $contextTypeProvider ?? new Psr3ContextTypeProvider(); + $this->contextTypeProviderResolver = $contextTypeProviderResolver ?? new AnyScopeContextTypeProviderResolver(new Psr3ContextTypeProvider()); } public function getNodeType(): string @@ -62,22 +63,6 @@ public function processNode(Node $node, Scope $scope): array $contextArgumentNo = 1; if ($methodName === 'log') { - if (count($args) < 2) { - return []; - } - - $logLevelType = $scope->getType($args[0]->value); - - $logLevels = []; - foreach ($logLevelType->getConstantStrings() as $constantString) { - $logLevels[] = $constantString->getValue(); - } - - if (count($logLevels) === 0) { - // cant find logLevels - return []; - } - $contextArgumentNo = 2; } elseif (! in_array($methodName, LogLevelListInterface::LOGGER_LEVEL_METHODS, true)) { return []; @@ -87,8 +72,9 @@ public function processNode(Node $node, Scope $scope): array return []; } - $expectedContextType = $this->contextTypeProvider->getType(); - $argContextType = $scope->getType($args[$contextArgumentNo]->value); + $argContextType = $scope->getType($args[$contextArgumentNo]->value); + + $expectedContextType = $this->contextTypeProviderResolver->resolveContextTypeProvider($scope, $argContextType)->getType(); $ret = $expectedContextType->accepts($argContextType, true); diff --git a/src/Rules/LogMethodLevelRule.php b/src/Rules/LogMethodLevelRule.php index 1543ef8..3d87623 100644 --- a/src/Rules/LogMethodLevelRule.php +++ b/src/Rules/LogMethodLevelRule.php @@ -12,7 +12,6 @@ use PHPStan\Type\ObjectType; use function count; -use function implode; use function in_array; use function sprintf; @@ -22,7 +21,7 @@ final class LogMethodLevelRule implements Rule { private const ERROR_INVALID_LEVEL = <<<'MESSAGE' -Parameter #1 $level of method Psr\Log\LoggerInterface::log() expects 'alert'|'critical'|'debug'|'emergency'|'error'|'info'|'notice'|'warning', '%s' given. +Parameter #1 $level of method Psr\Log\LoggerInterface::log() expects 'alert'|'critical'|'debug'|'emergency'|'error'|'info'|'notice'|'warning', %s given. MESSAGE; public function getNodeType(): string @@ -67,12 +66,9 @@ public function processNode(Node $node, Scope $scope): array } if (count($logLevels) === 0) { - // cant find logLevels return [ RuleErrorBuilder::message( - <<<'MESSAGE' -Parameter #1 $level of method Psr\Log\LoggerInterface::log() expects 'alert'|'critical'|'debug'|'emergency'|'error'|'info'|'notice'|'warning'. -MESSAGE + sprintf(self::ERROR_INVALID_LEVEL, $logLevelType->toPhpDocNode()->__toString()) )->identifier('sfpPsrLog.logMethodLevel')->build(), ]; } @@ -90,7 +86,7 @@ public function processNode(Node $node, Scope $scope): array return [ RuleErrorBuilder::message( - sprintf(self::ERROR_INVALID_LEVEL, implode(', ', $invalidLogLevels)) + sprintf(self::ERROR_INVALID_LEVEL, $logLevelType->toPhpDocNode()->__toString()) )->identifier('sfpPsrLog.logMethodLevel')->build(), ]; } diff --git a/src/TypeMapping/BigQuery/TableFieldSchemaJsonPayloadTypeMapperInterface.php b/src/TypeMapping/BigQuery/TableFieldSchemaJsonPayloadTypeMapperInterface.php index f23f848..745ff5a 100644 --- a/src/TypeMapping/BigQuery/TableFieldSchemaJsonPayloadTypeMapperInterface.php +++ b/src/TypeMapping/BigQuery/TableFieldSchemaJsonPayloadTypeMapperInterface.php @@ -9,6 +9,7 @@ /** * @see https://cloud.google.com/bigquery/docs/reference/rest/v2/tables?hl=en#TableFieldSchema * + * @api * @phpstan-type non_record_field_type 'STRING'|'BYTES'|'INTEGER'|'INT64'|'FLOAT'|'FLOAT64'|'BOOLEAN'|'BOOL'|'TIMESTAMP'|'DATE'|'TIME'|'DATETIME'|'GEOGRAPHY'|'NUMERIC'|'BIGNUMERIC'|'JSON'|'RANGE' * @phpstan-type field_type non_record_field_type|'RECORD'|'STRUCT' * @phpstan-type schema_item_minimal array{name: string, type: field_type} diff --git a/src/TypeProvider/ContextTypeProviderInterface.php b/src/TypeProvider/ContextTypeProviderInterface.php index 3765b97..e779dd2 100644 --- a/src/TypeProvider/ContextTypeProviderInterface.php +++ b/src/TypeProvider/ContextTypeProviderInterface.php @@ -6,6 +6,9 @@ use PHPStan\Type\Type; +/** + * @api + */ interface ContextTypeProviderInterface { public function getType(): Type; diff --git a/src/TypeProviderResolver/AnyScopeContextTypeProviderResolver.php b/src/TypeProviderResolver/AnyScopeContextTypeProviderResolver.php new file mode 100644 index 0000000..7f01f27 --- /dev/null +++ b/src/TypeProviderResolver/AnyScopeContextTypeProviderResolver.php @@ -0,0 +1,24 @@ +contextTypeProvider = $contextTypeProvider; + } + + public function resolveContextTypeProvider(Scope $scope, Type $contextType): ContextTypeProviderInterface + { + return $this->contextTypeProvider; + } +} diff --git a/src/TypeProviderResolver/ContextTypeProviderResolverInterface.php b/src/TypeProviderResolver/ContextTypeProviderResolverInterface.php new file mode 100644 index 0000000..7549c15 --- /dev/null +++ b/src/TypeProviderResolver/ContextTypeProviderResolverInterface.php @@ -0,0 +1,14 @@ + */ + private $layerSet; + + /** @var AnyScopeContextTypeProviderResolver */ + private $anyScopeContextTypeProviderResolver; + + /** @var bool */ + private $fallbackAnyScope; + + /** + * @phpstan-param array $layerSet + */ + public function __construct(array $layerSet, bool $fallbackAnyScope = true) + { + $this->layerSet = $layerSet; + $this->fallbackAnyScope = $fallbackAnyScope; + $this->anyScopeContextTypeProviderResolver = new AnyScopeContextTypeProviderResolver(new Psr3ContextTypeProvider()); + } + + public function resolveContextTypeProvider(Scope $scope, Type $contextType): ContextTypeProviderInterface + { + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + if ($this->fallbackAnyScope) { + return $this->anyScopeContextTypeProviderResolver->resolveContextTypeProvider($scope, $contextType); + } + throw new LogicException('can not find belongs to '); + } + + foreach ($this->layerSet as $interface => $contextTypeProvider) { + if ($classReflection->implementsInterface($interface)) { + return $contextTypeProvider; + } + } + + if (! $this->fallbackAnyScope) { + throw new LogicException('can not find belongs to '); + } + + return $this->anyScopeContextTypeProviderResolver->resolveContextTypeProvider($scope, $contextType); + } +} diff --git a/test/Rules/ContextTypeRuleTest.php b/test/Rules/ContextTypeRuleTest.php index d442562..af5f74e 100644 --- a/test/Rules/ContextTypeRuleTest.php +++ b/test/Rules/ContextTypeRuleTest.php @@ -9,7 +9,8 @@ use Sfp\PHPStan\Psr\Log\Rules\ContextTypeRule; use Sfp\PHPStan\Psr\Log\TypeMapping\BigQuery\GenericTableFieldSchemaJsonPayloadTypeMapper; use Sfp\PHPStan\Psr\Log\TypeProvider\BigQueryContextTypeProvider; -use Sfp\PHPStan\Psr\Log\TypeProvider\ContextTypeProviderInterface; +use Sfp\PHPStan\Psr\Log\TypeProviderResolver\AnyScopeContextTypeProviderResolver; +use Sfp\PHPStan\Psr\Log\TypeProviderResolver\ContextTypeProviderResolverInterface; use function sprintf; @@ -19,12 +20,12 @@ */ final class ContextTypeRuleTest extends RuleTestCase { - /** @var null|ContextTypeProviderInterface */ - private $contextTypeProvider; + /** @var null|ContextTypeProviderResolverInterface */ + private $contextTypeProviderResolver; protected function getRule(): Rule { - return new ContextTypeRule($this->contextTypeProvider); + return new ContextTypeRule($this->contextTypeProviderResolver); } /** @@ -32,7 +33,7 @@ protected function getRule(): Rule */ public function testProcessNode(): void { - $this->contextTypeProvider = null; + $this->contextTypeProviderResolver = null; $this->analyse([__DIR__ . '/data/contextType.php'], [ [ sprintf( @@ -49,7 +50,8 @@ public function testProcessNode(): void */ public function testProcessNodeWithBigQueryContextTypeProvider(): void { - $this->contextTypeProvider = new BigQueryContextTypeProvider(__DIR__ . '/../TypeProvider/data/bigQuerySchema.json', new GenericTableFieldSchemaJsonPayloadTypeMapper()); + $contextTypeProvider = new BigQueryContextTypeProvider(__DIR__ . '/../TypeProvider/data/bigQuerySchema.json', new GenericTableFieldSchemaJsonPayloadTypeMapper()); + $this->contextTypeProviderResolver = new AnyScopeContextTypeProviderResolver($contextTypeProvider); $this->analyse([__DIR__ . '/data/contextType.php'], [ [ sprintf( diff --git a/test/Rules/LogMethodLevelRuleTest.php b/test/Rules/LogMethodLevelRuleTest.php index f7bc4a7..6ca060b 100644 --- a/test/Rules/LogMethodLevelRuleTest.php +++ b/test/Rules/LogMethodLevelRuleTest.php @@ -27,13 +27,17 @@ public function testLogMethodLevel(): void 23, ], [ - "Parameter #1 \$level of method Psr\Log\LoggerInterface::log() expects 'alert'|'critical'|'debug'|'emergency'|'error'|'info'|'notice'|'warning'.", + "Parameter #1 \$level of method Psr\Log\LoggerInterface::log() expects 'alert'|'critical'|'debug'|'emergency'|'error'|'info'|'notice'|'warning', string given.", 24, ], [ - "Parameter #1 \$level of method Psr\Log\LoggerInterface::log() expects 'alert'|'critical'|'debug'|'emergency'|'error'|'info'|'notice'|'warning', 'foo, panic' given.", + "Parameter #1 \$level of method Psr\Log\LoggerInterface::log() expects 'alert'|'critical'|'debug'|'emergency'|'error'|'info'|'notice'|'warning', ('foo' | 'info' | 'panic') given.", 25, ], + [ + "Parameter #1 \$level of method Psr\Log\LoggerInterface::log() expects 'alert'|'critical'|'debug'|'emergency'|'error'|'info'|'notice'|'warning', 100 given.", + 26, + ], ]); } } diff --git a/test/Rules/data/logMethodLevel.php b/test/Rules/data/logMethodLevel.php index d398327..4bfe377 100644 --- a/test/Rules/data/logMethodLevel.php +++ b/test/Rules/data/logMethodLevel.php @@ -23,4 +23,5 @@ function main( $logger->log('panic', 'message'); $logger->log($unknownLevel, 'message'); $logger->log($invalidLevels, 'message'); + $logger->log(100, 'message'); } diff --git a/test/example.enableContextTypeRule.output b/test/example.enableContextTypeRule.output index 5276f50..732f50e 100644 --- a/test/example.enableContextTypeRule.output +++ b/test/example.enableContextTypeRule.output @@ -1,7 +1,7 @@ - +