diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b3472cf3..735a09d59 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -154,3 +154,29 @@ jobs: - run: bash ./tests/drupal_test.sh shell: "bash" + + phpstan: + name: "PHPStan" + + runs-on: 'ubuntu-latest' + + strategy: + matrix: + php-version: + - '8.4' + + steps: + - name: "Checkout code" + uses: actions/checkout@v4 + + - name: "Install PHP with extensions" + uses: shivammathur/setup-php@v2 + with: + coverage: "none" + php-version: ${{ matrix.php-version }} + ini-values: memory_limit=-1 + + - run: composer install + + - name: "Run tests" + run: vendor/bin/phpstan diff --git a/composer.json b/composer.json index e0c3e6c6c..079f1daf3 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,8 @@ }, "require-dev": { "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0", - "psr/container": "^1.0|^2.0" + "psr/container": "^1.0|^2.0", + "phpstan/phpstan": "^2.0" }, "autoload": { "files": [ diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 000000000..1121ae1b2 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,25 @@ +parameters: + ignoreErrors: + - # The method is dynamically generated by the CheckSecurityNode + message: '#^Call to an undefined method Twig\\Template\:\:checkSecurity\(\)\.$#' + identifier: method.notFound + count: 1 + path: src/Extension/CoreExtension.php + + - # Avoid BC-break + message: '#^Constructor of class Twig\\Node\\ForNode has an unused parameter \$ifexpr\.$#' + identifier: constructor.unusedParameter + count: 1 + path: src/Node/ForNode.php + + - # 2 parameters will be required + message: '#^Method Twig\\Node\\IncludeNode\:\:addGetTemplate\(\) invoked with 2 parameters, 1 required\.$#' + identifier: arguments.count + count: 1 + path: src/Node/IncludeNode.php + + - # int|string will be supported in 4.x + message: '#^PHPDoc tag @param for parameter $name with type int|string is not subtype of native type string\.$#' + identifier: parameter.phpDocType + count: 5 + path: src/Node/Node.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 000000000..6d94e4109 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,9 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: 3 + paths: + - src + excludePaths: + - src/Test diff --git a/src/ExpressionParser.php b/src/ExpressionParser.php index b043f3e34..2fb528518 100644 --- a/src/ExpressionParser.php +++ b/src/ExpressionParser.php @@ -53,9 +53,9 @@ class ExpressionParser public const OPERATOR_LEFT = 1; public const OPERATOR_RIGHT = 2; - /** @var array}> */ + /** @var array}> */ private $unaryOperators; - /** @var array, associativity: self::OPERATOR_*}> */ + /** @var array, associativity: self::OPERATOR_*}> */ private $binaryOperators; private $readyNodes = []; private array $precedenceChanges = []; @@ -125,7 +125,7 @@ class ExpressionParser $expr->setAttribute('operator', 'binary_'.$token->getValue()); - $this->triggerPrecedenceDeprecations($expr, $token); + $this->triggerPrecedenceDeprecations($expr); $token = $this->parser->getCurrentToken(); } @@ -246,7 +246,7 @@ class ExpressionParser $expr->setAttribute('operator', 'unary_'.$token->getValue()); if ($this->deprecationCheck) { - $this->triggerPrecedenceDeprecations($expr, $token); + $this->triggerPrecedenceDeprecations($expr); } return $this->parsePostfixExpression($expr); diff --git a/src/ExtensionSet.php b/src/ExtensionSet.php index 8466cb955..99fcfe56b 100644 --- a/src/ExtensionSet.php +++ b/src/ExtensionSet.php @@ -48,7 +48,7 @@ final class ExtensionSet private $unaryOperators; /** @var array, associativity: ExpressionParser::OPERATOR_*}> */ private $binaryOperators; - /** @var array */ + /** @var array|null */ private $globals; private $functionCallbacks = []; private $filterCallbacks = []; diff --git a/src/Node/Expression/BlockReferenceExpression.php b/src/Node/Expression/BlockReferenceExpression.php index ed88c6094..0094c7adb 100644 --- a/src/Node/Expression/BlockReferenceExpression.php +++ b/src/Node/Expression/BlockReferenceExpression.php @@ -28,7 +28,7 @@ class BlockReferenceExpression extends AbstractExpression public function __construct(Node $name, ?Node $template, int $lineno) { if (!$name instanceof AbstractExpression) { - trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance to the "node" argument of "%s" is deprecated ("%s" given).', AbstractExpression::class, static::class, get_class($node)); + trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance to the "node" argument of "%s" is deprecated ("%s" given).', AbstractExpression::class, static::class, get_class($name)); } $nodes = ['name' => $name]; diff --git a/src/Node/Expression/Variable/AssignTemplateVariable.php b/src/Node/Expression/Variable/AssignTemplateVariable.php index aa9ed1191..98bcdc10e 100644 --- a/src/Node/Expression/Variable/AssignTemplateVariable.php +++ b/src/Node/Expression/Variable/AssignTemplateVariable.php @@ -23,17 +23,20 @@ final class AssignTemplateVariable extends AbstractExpression public function compile(Compiler $compiler): void { + /** @var TemplateVariable $var */ + $var = $this->nodes['var']; + $compiler ->addDebugInfo($this) ->write('$macros[') - ->string($this->nodes['var']->getName($compiler)) + ->string($var->getName($compiler)) ->raw('] = ') ; if ($this->getAttribute('global')) { $compiler ->raw('$this->macros[') - ->string($this->nodes['var']->getName($compiler)) + ->string($var->getName($compiler)) ->raw('] = ') ; } diff --git a/src/Node/ImportNode.php b/src/Node/ImportNode.php index ab9ca7469..124c41ba9 100644 --- a/src/Node/ImportNode.php +++ b/src/Node/ImportNode.php @@ -25,12 +25,9 @@ use Twig\Node\Expression\Variable\AssignTemplateVariable; #[YieldReady] class ImportNode extends Node { - /** - * @param bool $global - */ public function __construct(AbstractExpression $expr, AbstractExpression|AssignTemplateVariable $var, int $lineno) { - if (!\is_bool(\func_num_args() > 3)) { + if (\func_num_args() > 3) { trigger_deprecation('twig/twig', '3.15', \sprintf('Passing more than 3 arguments to "%s()" is deprecated.', __METHOD__)); } diff --git a/src/Node/MacroNode.php b/src/Node/MacroNode.php index d96e98cf2..fb2431cc4 100644 --- a/src/Node/MacroNode.php +++ b/src/Node/MacroNode.php @@ -64,7 +64,9 @@ class MacroNode extends Node ->write(\sprintf('public function macro_%s(', $this->getAttribute('name'))) ; - foreach ($this->getNode('arguments')->getKeyValuePairs() as $pair) { + /** @var ArrayExpression $arguments */ + $arguments = $this->getNode('arguments'); + foreach ($arguments->getKeyValuePairs() as $pair) { $name = $pair['key']; $default = $pair['value']; $compiler @@ -85,7 +87,7 @@ class MacroNode extends Node ->indent() ; - foreach ($this->getNode('arguments')->getKeyValuePairs() as $pair) { + foreach ($arguments->getKeyValuePairs() as $pair) { $name = $pair['key']; $compiler ->write('') diff --git a/src/OperatorPrecedenceChange.php b/src/OperatorPrecedenceChange.php index 12fd98c8d..1d9edefd1 100644 --- a/src/OperatorPrecedenceChange.php +++ b/src/OperatorPrecedenceChange.php @@ -35,7 +35,7 @@ class OperatorPrecedenceChange return $this->version; } - public function getNewPrecedence(): string + public function getNewPrecedence(): int { return $this->newPrecedence; }