diff --git a/CHANGELOG.md b/CHANGELOG.md index 998607c9..df0a32ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Change Log ## [Unreleased][unreleased] +### Fixed +- Compatibility with new Latte versions ## [0.17.1] - 2024-07-18 ### Updated diff --git a/composer.json b/composer.json index cd142a59..a9874fd5 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "require": { "php": ">=7.4 <8.4", "ext-json": "*", - "phpstan/phpstan": "^1.10.50", + "phpstan/phpstan": "^1.12.13", "phpstan/phpstan-nette": "^1.2.6", "latte/latte": "^2.11.6 | ^3.0.4", "nette/utils": "^3.2|^4.0", diff --git a/phpstan.neon b/phpstan.neon index 47420d46..bc3c6a82 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -72,7 +72,7 @@ parameters: path: src/Compiler/Compiler/AbstractCompiler.php - - message: '#^Parameter \#1 \$value of static method PhpParser\\BuilderHelpers\:\:normalizeValue\(\) expects array\|bool\|float\|int\|PhpParser\\Node\\Expr\|string\|null, mixed given\.$#' + message: '#^Parameter \#1 \$value of static method PhpParser\\BuilderHelpers\:\:normalizeValue\(\) expects .*, mixed given\.$#' path: src/LinkProcessor/LinkParamsProcessor.php - messages: @@ -145,16 +145,6 @@ parameters: - '#^Although PHPStan\\Node\\InClassNode is covered by backward compatibility promise, this instanceof assumption might break because it''s not guaranteed to always stay the same\.$#' - '#^Although PHPStan\\Node\\ClassMethod is covered by backward compatibility promise, this instanceof assumption might break because it''s not guaranteed to always stay the same\.$#' - - - messages: - -'#^Although PHPStan\\Rules\\MetadataRuleError is covered by backward compatibility promise, this instanceof assumption might break because it''s not guaranteed to always stay the same\.$#' - -'#^Although PHPStan\\Rules\\FileRuleError is covered by backward compatibility promise, this instanceof assumption might break because it''s not guaranteed to always stay the same\.$#' - -'#^Although PHPStan\\Rules\\LineRuleError is covered by backward compatibility promise, this instanceof assumption might break because it''s not guaranteed to always stay the same\.$#' - -'#^Although PHPStan\\Rules\\TipRuleError is covered by backward compatibility promise, this instanceof assumption might break because it''s not guaranteed to always stay the same\.$#' - -'#^Although PHPStan\\Rules\\IdentifierRuleError is covered by backward compatibility promise, this instanceof assumption might break because it''s not guaranteed to always stay the same\.$#' - -'#^Although PHPStan\\Rules\\NonIgnorableRuleError is covered by backward compatibility promise, this instanceof assumption might break because it''s not guaranteed to always stay the same\.$#' - path: src/Error/ErrorBuilder.php - # to be done later, no idea how to fix it now - message: '#^Method Efabrica\\PHPStanLatte\\Collector\\Collector\\AbstractCollector::collectItems\(\) should return array\\|null but returns array\\>\.$#' diff --git a/src/Analyser/LatteContextAnalyser.php b/src/Analyser/LatteContextAnalyser.php index 5692688f..8953f183 100644 --- a/src/Analyser/LatteContextAnalyser.php +++ b/src/Analyser/LatteContextAnalyser.php @@ -104,7 +104,11 @@ public function analyseFile(string $file): LatteContextData try { $collectedData = $collector->collectData($node, $scope); } catch (Throwable $e) { - $fileErrors[] = RuleErrorBuilder::message(get_class($collector) . ' error: ' . $e->getMessage())->file($file)->line($node->getLine())->build(); + $fileErrors[] = RuleErrorBuilder::message(get_class($collector) . ' error: ' . $e->getMessage()) + ->identifier('latte.collectorError') + ->file($file) + ->line($node->getLine()) + ->build(); continue; } if ($collectedData === null || $collectedData === []) { @@ -116,12 +120,21 @@ public function analyseFile(string $file): LatteContextData $scope = $this->scopeFactory->create(ScopeContext::create($file)); $this->nodeScopeResolver->processNodes($parserNodes, $scope, $nodeCallback); } catch (Throwable $e) { - $fileErrors[] = RuleErrorBuilder::message('LatteContextAnalyser error: ' . $e->getMessage())->file($file)->build(); + $fileErrors[] = RuleErrorBuilder::message('LatteContextAnalyser error: ' . $e->getMessage()) + ->identifier('latte.failed') + ->file($file) + ->build(); } } elseif (is_dir($file)) { - $fileErrors[] = RuleErrorBuilder::message(sprintf('File %s is a directory.', $file))->file($file)->build(); + $fileErrors[] = RuleErrorBuilder::message(sprintf('File %s is a directory.', $file)) + ->identifier('latte.fileError') + ->file($file) + ->build(); } else { - $fileErrors[] = RuleErrorBuilder::message(sprintf('File %s does not exist.', $file))->file($file)->build(); + $fileErrors[] = RuleErrorBuilder::message(sprintf('File %s does not exist.', $file)) + ->identifier('latte.fileError') + ->file($file) + ->build(); } return new LatteContextData($fileCollectedData, $fileErrors); } diff --git a/src/Analyser/LatteContextData.php b/src/Analyser/LatteContextData.php index a18460f7..5184d14f 100644 --- a/src/Analyser/LatteContextData.php +++ b/src/Analyser/LatteContextData.php @@ -6,12 +6,12 @@ use Efabrica\PHPStanLatte\LatteContext\CollectedData\CollectedError; use Efabrica\PHPStanLatte\LatteContext\CollectedData\CollectedLatteContextObject; -use PHPStan\Rules\RuleError; +use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; final class LatteContextData { - /** @var array */ + /** @var list */ private array $errors; /** @var array */ @@ -22,7 +22,7 @@ final class LatteContextData /** * @param array $collectedData - * @param array $errors + * @param list $errors */ public function __construct(array $collectedData, array $errors) { @@ -34,7 +34,7 @@ public function __construct(array $collectedData, array $errors) } /** - * @return array + * @return list */ public function getErrors(): array { @@ -42,13 +42,17 @@ public function getErrors(): array } /** - * @return array + * @return list */ public function getCollectedErrors(): array { $errors = []; foreach ($this->getCollectedData(CollectedError::class) as $collectedError) { - $errors[] = RuleErrorBuilder::message($collectedError->getMessage())->file($collectedError->getFile())->line($collectedError->getLine() ?? -1)->build(); + $errors[] = RuleErrorBuilder::message($collectedError->getMessage()) + ->identifier('latte.error') + ->file($collectedError->getFile()) + ->line($collectedError->getLine() ?? -1) + ->build(); } return $errors; } diff --git a/src/Compiler/NodeVisitor/AddParametersForBlockNodeVisitor.php b/src/Compiler/NodeVisitor/AddParametersForBlockNodeVisitor.php index 8464cecf..0cb83726 100644 --- a/src/Compiler/NodeVisitor/AddParametersForBlockNodeVisitor.php +++ b/src/Compiler/NodeVisitor/AddParametersForBlockNodeVisitor.php @@ -58,7 +58,7 @@ public function leaveNode(Node $node): ?array $parameters = []; $pattern = '/(?{define\s+(?.*?)\s*,?\s+(?.*)})\s+on line (?\d+)/s'; preg_match($pattern, $comment->getText(), $match); - if (isset($match['parameters'])) { + if (isset($match['define']) && isset($match['block_name']) &&isset($match['parameters'])) { $define = $match['define']; $typesAndVariablesPattern = '/(?[\?\\\[\]\<\>[:alnum:]]*)[ ]*\$(?[[:alnum:]]+)/s'; diff --git a/src/Compiler/NodeVisitor/LinkNodeVisitor.php b/src/Compiler/NodeVisitor/LinkNodeVisitor.php index b3153703..d91abb75 100644 --- a/src/Compiler/NodeVisitor/LinkNodeVisitor.php +++ b/src/Compiler/NodeVisitor/LinkNodeVisitor.php @@ -20,7 +20,7 @@ use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Name; -use PhpParser\Node\Scalar\LNumber; +use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\Echo_; use PhpParser\Node\Stmt\If_; use PhpParser\NodeVisitorAbstract; @@ -143,7 +143,7 @@ private function prepareNodes(MethodCall $methodCall, array $attributes): ?array } return [ - new If_(new Identical(new FuncCall(new Name('mt_rand')), new LNumber(0)), [ + new If_(new Identical(new FuncCall(new Name('uniqid')), new String_('random')), [ 'stmts' => $expressions, ], $attributes), ]; diff --git a/src/Error/ErrorBuilder.php b/src/Error/ErrorBuilder.php index d78d6e24..dcd84d14 100644 --- a/src/Error/ErrorBuilder.php +++ b/src/Error/ErrorBuilder.php @@ -15,7 +15,6 @@ use PHPStan\Rules\LineRuleError; use PHPStan\Rules\MetadataRuleError; use PHPStan\Rules\NonIgnorableRuleError; -use PHPStan\Rules\RuleError; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Rules\TipRuleError; @@ -52,6 +51,9 @@ final class ErrorBuilder '/PHPDoc tag @var for variable \$__variables__ has no value type specified in iterable type array\./', // fake variable $__variables__ can have not specified array type '/Cannot call method startTag\(\) on Nette\\\\Utils\\\\Html\|string\./', // nette/forms error https://github.com/nette/forms/issues/308 '/Cannot call method endTag\(\) on Nette\\\\Utils\\\\Html\|string\./', // nette/forms error https://github.com/nette/forms/issues/308 + '/Variable .* on left side of \?\?= is never defined./', + '/Variable .* on left side of \?\?= always exists and is not nullable./', + '/Property Latte\\\\Runtime\\\\Template::\$parentName .* does not accept mixed./', ]; /** @var string[] */ @@ -89,7 +91,7 @@ public function __construct( /** * @param Error[] $originalErrors - * @return RuleError[] + * @return IdentifierRuleError[] */ public function buildErrors(array $originalErrors, string $templatePath, ?string $compiledTemplatePath, ?string $context = null): array { @@ -110,7 +112,7 @@ public function buildErrors(array $originalErrors, string $templatePath, ?string return $errors; } - public function buildError(Error $originalError, string $templatePath, ?string $compiledTemplatePath, ?string $context = null): ?RuleError + public function buildError(Error $originalError, string $templatePath, ?string $compiledTemplatePath, ?string $context = null): ?IdentifierRuleError { $lineMap = $compiledTemplatePath ? $this->lineMapper->getLineMap($compiledTemplatePath) : new LineMap(); @@ -119,6 +121,7 @@ public function buildError(Error $originalError, string $templatePath, ?string $ $ruleErrorBuilder = RuleErrorBuilder::message($error->getMessage()) ->file($templatePath) + ->identifier('latte.error') ->metadata(array_merge($originalError->getMetadata(), [ 'context' => $context === '' ? null : $context, 'is_warning' => $this->isWarning($error->getMessage()), @@ -138,8 +141,8 @@ public function buildError(Error $originalError, string $templatePath, ?string $ } /** - * @param RuleError[] $ruleErrors - * @return RuleError[] + * @param IdentifierRuleError[] $ruleErrors + * @return list */ public function buildRuleErrors(array $ruleErrors): array { @@ -182,7 +185,7 @@ public function buildRuleErrors(array $ruleErrors): array return $newRuleErrors; } - private function errorSignature(RuleError $error): string + private function errorSignature(IdentifierRuleError $error): string { $values = (array)$error; unset($values['metadata']); diff --git a/src/Error/Transformer/BlockParameterErrorTransformer.php b/src/Error/Transformer/BlockParameterErrorTransformer.php index a4fdc5d7..6de2e331 100644 --- a/src/Error/Transformer/BlockParameterErrorTransformer.php +++ b/src/Error/Transformer/BlockParameterErrorTransformer.php @@ -13,7 +13,7 @@ final class BlockParameterErrorTransformer implements ErrorTransformerInterface public function transform(Error $error): Error { preg_match(self::BLOCK_METHOD, $error->getMessage(), $match); - if (isset($match['block'])) { + if (isset($match[0]) && isset($match['block'])) { $block = lcfirst(str_replace('_', '-', $match['block'])); $message = $error->getMessage(); // replace method name to block name diff --git a/src/LatteTemplateResolver/AbstractClassMethodTemplateResolver.php b/src/LatteTemplateResolver/AbstractClassMethodTemplateResolver.php index 3d2f427c..880fded8 100644 --- a/src/LatteTemplateResolver/AbstractClassMethodTemplateResolver.php +++ b/src/LatteTemplateResolver/AbstractClassMethodTemplateResolver.php @@ -35,6 +35,7 @@ protected function getClassResult(ReflectionClass $reflectionClass, LatteContext !$latteContext->methodFinder()->hasAnyAlwaysTerminated($reflectionClass->getName(), $reflectionMethod->getName()) ) { $result->addErrorFromBuilder(RuleErrorBuilder::message("Cannot resolve latte template for {$reflectionClass->getShortName()}::{$reflectionMethod->getName()}().") + ->identifier('latte.cannotResolve') ->file($reflectionClass->getFileName() ?? 'unknown') ->line($reflectionMethod->getStartLine())); } diff --git a/src/LatteTemplateResolver/LatteTemplateResolverResult.php b/src/LatteTemplateResolver/LatteTemplateResolverResult.php index ea0cf900..7c3849f5 100644 --- a/src/LatteTemplateResolver/LatteTemplateResolverResult.php +++ b/src/LatteTemplateResolver/LatteTemplateResolverResult.php @@ -7,7 +7,7 @@ use Efabrica\PHPStanLatte\LatteContext\CollectedData\CollectedTemplateRender; use Efabrica\PHPStanLatte\Template\Template; use Efabrica\PHPStanLatte\Template\TemplateContext; -use PHPStan\Rules\RuleError; +use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; final class LatteTemplateResolverResult @@ -15,12 +15,12 @@ final class LatteTemplateResolverResult /** @var array */ private array $templates = []; - /** @var RuleError[] */ + /** @var IdentifierRuleError[] */ private array $errors = []; /** * @param Template[] $templates - * @param RuleError[] $errors + * @param IdentifierRuleError[] $errors */ public function __construct(array $templates = [], array $errors = []) { @@ -39,7 +39,7 @@ public function getTemplates(): array } /** - * @return RuleError[] + * @return IdentifierRuleError[] */ public function getErrors(): array { @@ -51,11 +51,14 @@ public function addTemplate(Template $template): void $this->templates[$template->getSignatureHash()] = $template; } - public function addError(RuleError $error): void + public function addError(IdentifierRuleError $error): void { $this->errors[] = $error; } + /** + * @param RuleErrorBuilder $error + */ public function addErrorFromBuilder(RuleErrorBuilder $error): void { $this->errors[] = $error->build(); @@ -69,11 +72,13 @@ public function addTemplateFromRender(CollectedTemplateRender $templateRender, T $templatePath = $templateRender->getTemplatePath(); if ($templatePath === null) { $this->addErrorFromBuilder(RuleErrorBuilder::message('Cannot resolve rendered latte template.') + ->identifier('latte.cannotResolve') ->file($templateRender->getFile()) ->line($templateRender->getLine())); return; } elseif (!is_file($templatePath)) { $this->addErrorFromBuilder(RuleErrorBuilder::message('Rendered latte template ' . $templatePath . ' does not exist.') + ->identifier('latte.notFound') ->file($templateRender->getFile()) ->line($templateRender->getLine())); return; diff --git a/src/LatteTemplateResolver/Nette/NetteApplicationUIPresenter.php b/src/LatteTemplateResolver/Nette/NetteApplicationUIPresenter.php index 4126c3ef..fb69cf50 100644 --- a/src/LatteTemplateResolver/Nette/NetteApplicationUIPresenter.php +++ b/src/LatteTemplateResolver/Nette/NetteApplicationUIPresenter.php @@ -127,8 +127,9 @@ protected function getClassResult(ReflectionClass $reflectionClass, LatteContext foreach ($actionDefinition['templatePaths'] as $template) { if ($template === null) { $result->addErrorFromBuilder(RuleErrorBuilder::message('Cannot automatically resolve latte template from expression.') - ->file($reflectionClass->getFileName() ?? 'unknown') - ->line($actionDefinition['line'])); + ->identifier('latte.cannotResolve') + ->file($reflectionClass->getFileName() ?? 'unknown') + ->line($actionDefinition['line'])); continue; } $result->addTemplate(new Template($template, $reflectionClass->getName(), $actionName, $actionDefinition['templateContext'])); @@ -143,6 +144,7 @@ protected function getClassResult(ReflectionClass $reflectionClass, LatteContext if ($actionDefinition['defaultTemplate'] === null) { if (!$actionDefinition['terminated'] && $actionDefinition['templatePaths'] === []) { // might not be rendered at all (for example redirect or use set template path) $result->addErrorFromBuilder(RuleErrorBuilder::message("Cannot resolve latte template for action $actionName") + ->identifier('latte.cannotResolve') ->file($reflectionClass->getFileName() ?? 'unknown') ->line($actionDefinition['line']) ->identifier($actionName)); diff --git a/src/LinkProcessor/PresenterActionLinkProcessor.php b/src/LinkProcessor/PresenterActionLinkProcessor.php index 9e8e70b4..9c76ac37 100644 --- a/src/LinkProcessor/PresenterActionLinkProcessor.php +++ b/src/LinkProcessor/PresenterActionLinkProcessor.php @@ -69,8 +69,12 @@ public function createLinkExpressions(string $targetName, array $linkParams, arr if (!$presenterFactory instanceof PresenterFactory) { return []; } - $presenterClassName = $presenterFactory->formatPresenterClass($presenterWithModule); - if ($presenterClassName === '') { + if ($presenterWithModule) { + $presenterClassName = $presenterFactory->formatPresenterClass($presenterWithModule); + } else { + $presenterClassName = $this->actualClass; + } + if ($presenterClassName === '' || $presenterClassName === null) { return []; } if (!$this->reflectionProvider->hasClass($presenterClassName)) { diff --git a/src/Rule/LatteTemplatesRule.php b/src/Rule/LatteTemplatesRule.php index 7cbe2727..87226f3a 100644 --- a/src/Rule/LatteTemplatesRule.php +++ b/src/Rule/LatteTemplatesRule.php @@ -24,9 +24,9 @@ use PHPStan\Analyser\Scope; use PHPStan\Collectors\Registry as CollectorsRegistry; use PHPStan\Node\CollectedDataNode; +use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Registry as RuleRegistry; use PHPStan\Rules\Rule; -use PHPStan\Rules\RuleError; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; use Throwable; @@ -123,11 +123,14 @@ public function processNode(Node $collectedDataNode, Scope $scope): array $errors = array_merge(array_filter($errors), $this->analyseTemplates($compiledTemplates)); if (count($errors) > 1000) { - $errors[] = RuleErrorBuilder::message('Too many errors in latte.')->build(); + $errors[] = RuleErrorBuilder::message('Too many errors in latte.') + ->identifier('latte.tooManyErrors') + ->build(); } foreach ($this->analysedTemplatesRegistry->getReportedUnanalysedTemplates() as $templatePath) { $errors[] = RuleErrorBuilder::message('Latte template ' . pathinfo($templatePath, PATHINFO_BASENAME) . ' was not analysed.') + ->identifier('latte.unanalysedTemplate') ->file($templatePath) ->tip('Please make sure your template path is correct. If you use some non-standard way of resolving your templates, read our extension guide https://github.com/efabrica-team/phpstan-latte/blob/main/docs/extension.md#template-resolvers') ->build(); @@ -138,7 +141,7 @@ public function processNode(Node $collectedDataNode, Scope $scope): array /** * @param Template[] $templates - * @param array $errors + * @param array $errors * @param array $alreadyAnalysedInParents * @return array path of compiled template => Template * @throws ShouldNotHappenException @@ -170,6 +173,7 @@ private function compileTemplates(array $templates, array &$errors, array &$alre $compiledTemplates[$compileFilePath] = $template; } catch (CompileException $e) { $ruleErrorBuilder = RuleErrorBuilder::message($e->getMessage()) + ->identifier('latte.compileError') ->file($template->getPath()) ->metadata(['context' => $context]); if ($e->sourceLine) { @@ -236,7 +240,7 @@ private function compileTemplates(array $templates, array &$errors, array &$alre /** * @param array $templates - * @return RuleError[] + * @return IdentifierRuleError[] */ private function analyseTemplates(array $templates): array { diff --git a/tests/Rule/LatteTemplatesRule/PresenterWithoutModule/Fixtures/VariablesPresenter.php b/tests/Rule/LatteTemplatesRule/PresenterWithoutModule/Fixtures/VariablesPresenter.php index d48d9005..1d08533c 100644 --- a/tests/Rule/LatteTemplatesRule/PresenterWithoutModule/Fixtures/VariablesPresenter.php +++ b/tests/Rule/LatteTemplatesRule/PresenterWithoutModule/Fixtures/VariablesPresenter.php @@ -52,7 +52,7 @@ public function actionDefault(): void $this->template->someOtherVariableWithDefault = 'value from presenter'; $this->template->nullOrUrl = null; - if (mt_rand()) { + if (uniqid() == 'random') { $this->template->nullOrUrl = 'https://example.org'; } } diff --git a/tests/Rule/LatteTemplatesRule/PresenterWithoutModule/LatteTemplatesRuleForPresenterTest.php b/tests/Rule/LatteTemplatesRule/PresenterWithoutModule/LatteTemplatesRuleForPresenterTest.php index 6de16efb..efb5830a 100644 --- a/tests/Rule/LatteTemplatesRule/PresenterWithoutModule/LatteTemplatesRuleForPresenterTest.php +++ b/tests/Rule/LatteTemplatesRule/PresenterWithoutModule/LatteTemplatesRuleForPresenterTest.php @@ -866,12 +866,12 @@ public function testFilters(): void 'default.latte', ], [ - 'Parameter #1 $ of closure expects string, int given.', + 'Parameter #1 of closure expects string, int given.', 8, 'default.latte', ], [ - 'Parameter #2 $ of closure expects int, string given.', + 'Parameter #2 of closure expects int, string given.', 8, 'default.latte', ], @@ -881,12 +881,12 @@ public function testFilters(): void 'default.latte', ], [ - 'Parameter #1 $ of closure expects string, int given.', + 'Parameter #1 of closure expects string, int given.', 11, 'default.latte', ], [ - 'Parameter #2 $ of closure expects int, string given.', + 'Parameter #2 of closure expects int, string given.', 11, 'default.latte', ], @@ -896,12 +896,12 @@ public function testFilters(): void 'default.latte', ], [ - 'Parameter #1 $ of callable callable(string, int): string expects string, int given.', + 'Parameter #1 of callable callable(string, int): string expects string, int given.', 14, 'default.latte', ], [ - 'Parameter #2 $ of callable callable(string, int): string expects int, string given.', + 'Parameter #2 of callable callable(string, int): string expects int, string given.', 14, 'default.latte', ], @@ -978,7 +978,7 @@ public function testFilters(): void } else { $expectedErrors[] = [ 'Syntax error, unexpected \')\'', - 2, + -1, 'translate_new.latte', ];