Skip to content

Commit

Permalink
implement first class callable syntax (see #324)
Browse files Browse the repository at this point in the history
  • Loading branch information
llaville committed Jan 10, 2022
1 parent be10e49 commit afa640d
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG-6.x.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ using the [Keep a CHANGELOG](http://keepachangelog.com) principles.
- [Simplify database initialization processus](https://github.com/llaville/php-compatinfo/issues/321) with bridge to new command `db:create` (from CompatInfoDB)
- [#322](https://github.com/llaville/php-compatinfo/issues/322) : new sniff to detect PHP (8.1) Enumerations
- [#323](https://github.com/llaville/php-compatinfo/issues/323) : new sniff to detect PHP (8.1) Readonly Properties
- [#324](https://github.com/llaville/php-compatinfo/issues/324) : new sniff to detect PHP (8.1) First class callable syntax

### Removed

Expand Down
1 change: 1 addition & 0 deletions docs/01_Components/03_Sniffs/Features.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ Here is the list of features supported and their corresponding sniffs :
|---|---|---|
| Classes | ReadonlyPropertySniff | [Readonly Properties](https://www.php.net/manual/en/language.oop5.properties.php#language.oop5.properties.readonly-properties) |
| Enumerations | EnumerationSniff | [Enumerations](https://www.php.net/manual/en/migration81.new-features.php#migration81.new-features.core.enums) |
| FunctionDeclarations | FirstClassCallableSniff | [First class callable](https://www.php.net/manual/en/functions.first_class_callable_syntax.php) |

## Special cases

Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ They are grouped by categories to solve PHP features (from 4.0 to 8.1)
- ControlStructures (2)
- Enumerations (1)
- Expressions (3)
- FunctionDeclarations (3)
- FunctionDeclarations (4)
- Generators (1)
- Keywords (1)
- Numbers (1)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php declare(strict_types=1);
/**
* This file is part of the PHP_CompatInfo package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Bartlett\CompatInfo\Application\Sniffs\FunctionDeclarations;

use Bartlett\CompatInfo\Application\Sniffs\SniffAbstract;

use PhpParser\Node;

use Generator;

/**
* First class callable syntax (since PHP 8.1)
*
* @author Laurent Laville
* @since Release 6.2.0
*
* @link https://www.php.net/manual/en/functions.first_class_callable_syntax.php
* @link https://wiki.php.net/rfc/first_class_callable_syntax
* @link https://php.watch/versions/8.1/first-class-callable-syntax
* @see tests/Sniffs/FirstClassCallableSniffTest.php
*/
final class FirstClassCallableSniff extends SniffAbstract
{
// Rules identifiers for SARIF report
private const CA81 = 'CA8103';

/**
* {@inheritDoc}
*/
public function getRules(): Generator
{
yield self::CA81 => [
'name' => $this->getShortClass(),
'fullDescription' => "First class callable syntax is available since PHP 8.1.0",
'helpUri' => '%baseHelpUri%/01_Components/03_Sniffs/Features/#php-81',
];
}

/**
* {@inheritDoc}
*/
public function enterNode(Node $node)
{
if (!$node instanceof Node\Expr\CallLike) {
return null;
}
if (!$node->isFirstClassCallable()) {
return null;
}

$this->updateNodeElementVersion($node, $this->attributeKeyStore, ['php.min' => '8.1.0beta1']);
$this->updateNodeElementRule($node, $this->attributeKeyStore, self::CA81);
return null;
}
}
112 changes: 112 additions & 0 deletions tests/Sniffs/FirstClassCallableSniffTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<?php declare(strict_types=1);
/**
* This file is part of the PHP_CompatInfo package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Bartlett\CompatInfo\Tests\Sniffs;

use Exception;

/**
* First class callable syntax (since PHP 8.1)
*
* @author Laurent Laville
* @since Release 6.2.0
*
* @link https://www.php.net/manual/en/functions.first_class_callable_syntax.php
* @link https://wiki.php.net/rfc/first_class_callable_syntax
* @link https://php.watch/versions/8.1/first-class-callable-syntax
* @see tests/Sniffs/FirstClassCallableSniffTest.php
*/
final class FirstClassCallableSniffTest extends SniffTestCase
{
/**
* {@inheritDoc}
*/
public static function setUpBeforeClass(): void
{
parent::setUpBeforeClass();

self::$fixtures .= 'functions' . DIRECTORY_SEPARATOR;
}

/**
* Regression test for issue #324
*
* @link https://github.com/llaville/php-compatinfo/issues/324
* First class callable syntax is detected as PHP 8.1
* @group regression
* @return void
* @throws Exception
*/
public function testFuncCall()
{
$dataSource = 'callable_expr_function.php';
$metrics = $this->executeAnalysis($dataSource);
$versions = $metrics[self::$analyserId]['versions'];

$this->assertEquals(
'8.1.0beta1',
$versions['php.min']
);

$this->assertEquals(
'',
$versions['php.max']
);
}

/**
* Regression test for issue #324
*
* @link https://github.com/llaville/php-compatinfo/issues/324
* First class callable syntax is detected as PHP 8.1
* @group regression
* @return void
* @throws Exception
*/
public function testMethodCall()
{
$dataSource = 'callable_expr_method.php';
$metrics = $this->executeAnalysis($dataSource);
$versions = $metrics[self::$analyserId]['versions'];

$this->assertEquals(
'8.1.0beta1',
$versions['php.min']
);

$this->assertEquals(
'',
$versions['php.max']
);
}

/**
* Regression test for issue #324
*
* @link https://github.com/llaville/php-compatinfo/issues/324
* First class callable syntax is detected as PHP 8.1
* @group regression
* @return void
* @throws Exception
*/
public function testStaticCall()
{
$dataSource = 'callable_expr_static.php';
$metrics = $this->executeAnalysis($dataSource);
$versions = $metrics[self::$analyserId]['versions'];

$this->assertEquals(
'8.1.0beta1',
$versions['php.min']
);

$this->assertEquals(
'',
$versions['php.max']
);
}
}
3 changes: 3 additions & 0 deletions tests/fixtures/sniffs/functions/callable_expr_function.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php
$callable = strtoupper(...);
echo $callable('foo'); // FOO
14 changes: 14 additions & 0 deletions tests/fixtures/sniffs/functions/callable_expr_method.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php
class Clock {
public function getClockCallable(): callable {
return $this->getTime(...);
}

private function getTime(): int {
return time();
}
}

$clock = new Clock();
$clock_callback = $clock->getClockCallable();
echo $clock_callback();
10 changes: 10 additions & 0 deletions tests/fixtures/sniffs/functions/callable_expr_static.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php
class Item
{
public static function doSomething()
{
echo __METHOD__ . PHP_EOL;
}
}

Item::doSomething(...);

0 comments on commit afa640d

Please sign in to comment.