Skip to content

Commit

Permalink
Merge branch '2.18'
Browse files Browse the repository at this point in the history
  • Loading branch information
keradus committed May 3, 2021
2 parents e9c4f77 + db7e1f1 commit 24e9391
Show file tree
Hide file tree
Showing 16 changed files with 1,148 additions and 486 deletions.
33 changes: 33 additions & 0 deletions doc/rules/function_notation/phpdoc_to_param_type.rst
Expand Up @@ -13,12 +13,27 @@ accordingly the function signature. Requires PHP >= 7.0.
be fixed. [3] Manual actions are required if inherited signatures are not
properly documented.

Configuration
-------------

``scalar_types``
~~~~~~~~~~~~~~~~

Fix also scalar types; may have unexpected behaviour due to PHP bad type
coercion system.

Allowed types: ``bool``

Default value: ``true``

Examples
--------

Example #1
~~~~~~~~~~

*Default* configuration.

.. code-block:: diff
--- Original
Expand All @@ -33,6 +48,8 @@ Example #1
Example #2
~~~~~~~~~~

*Default* configuration.

.. code-block:: diff
--- Original
Expand All @@ -43,3 +60,19 @@ Example #2
-function my_foo($bar)
+function my_foo(?string $bar)
{}
Example #3
~~~~~~~~~~

With configuration: ``['scalar_types' => false]``.

.. code-block:: diff
--- Original
+++ New
<?php
/** @param Foo $foo */
-function foo($foo) {}
+function foo(Foo $foo) {}
/** @param string $foo */
function bar($foo) {}
194 changes: 193 additions & 1 deletion src/AbstractPhpdocToTypeDeclarationFixer.php
Expand Up @@ -12,18 +12,210 @@

namespace PhpCsFixer;

use PhpCsFixer\DocBlock\Annotation;
use PhpCsFixer\DocBlock\DocBlock;
use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer;
use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;

/**
* @internal
*/
abstract class AbstractPhpdocToTypeDeclarationFixer extends AbstractFixer
abstract class AbstractPhpdocToTypeDeclarationFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface
{
/**
* @var string
*/
private $classRegex = '/^\\\\?[a-zA-Z_\\x7f-\\xff](?:\\\\?[a-zA-Z0-9_\\x7f-\\xff]+)*$/';

/**
* @var array<string, int>
*/
private $versionSpecificTypes = [
'void' => 70100,
'iterable' => 70100,
'object' => 70200,
'mixed' => 80000,
];

/**
* @var array<string, bool>
*/
private $scalarTypes = [
'bool' => true,
'float' => true,
'int' => true,
'string' => true,
];

/**
* @var array<string, bool>
*/
private static $syntaxValidationCache = [];

/**
* {@inheritdoc}
*/
public function isRisky()
{
return true;
}

/**
* @param string $type
*
* @return bool
*/
abstract protected function isSkippedType($type);

/**
* {@inheritdoc}
*/
protected function createConfigurationDefinition()
{
return new FixerConfigurationResolver([
(new FixerOptionBuilder('scalar_types', 'Fix also scalar types; may have unexpected behaviour due to PHP bad type coercion system.'))
->setAllowedTypes(['bool'])
->setDefault(true)
->getOption(),
]);
}

/**
* Find all the annotations of given type in the function's PHPDoc comment.
*
* @param string $name
* @param int $index The index of the function token
*
* @return Annotation[]
*/
protected function findAnnotations($name, Tokens $tokens, $index)
{
do {
$index = $tokens->getPrevNonWhitespace($index);
} while ($tokens[$index]->isGivenKind([
T_COMMENT,
T_ABSTRACT,
T_FINAL,
T_PRIVATE,
T_PROTECTED,
T_PUBLIC,
T_STATIC,
]));

if (!$tokens[$index]->isGivenKind(T_DOC_COMMENT)) {
return [];
}

$namespacesAnalyzer = new NamespacesAnalyzer();
$namespace = $namespacesAnalyzer->getNamespaceAt($tokens, $index);

$namespaceUsesAnalyzer = new NamespaceUsesAnalyzer();
$namespaceUses = $namespaceUsesAnalyzer->getDeclarationsInNamespace($tokens, $namespace);

$doc = new DocBlock(
$tokens[$index]->getContent(),
$namespace,
$namespaceUses
);

return $doc->getAnnotationsOfType($name);
}

/**
* @param string $type
* @param bool $isNullable
*
* @return Token[]
*/
protected function createTypeDeclarationTokens($type, $isNullable)
{
static $specialTypes = [
'array' => [CT::T_ARRAY_TYPEHINT, 'array'],
'callable' => [T_CALLABLE, 'callable'],
'static' => [T_STATIC, 'static'],
];

$newTokens = [];

if (true === $isNullable && 'mixed' !== $type) {
$newTokens[] = new Token([CT::T_NULLABLE_TYPE, '?']);
}

if (isset($specialTypes[$type])) {
$newTokens[] = new Token($specialTypes[$type]);
} else {
$typeUnqualified = ltrim($type, '\\');

if (isset($this->scalarTypes[$typeUnqualified]) || isset($this->versionSpecificTypes[$typeUnqualified])) {
// 'scalar's, 'void', 'iterable' and 'object' must be unqualified
$newTokens[] = new Token([T_STRING, $typeUnqualified]);
} else {
foreach (explode('\\', $type) as $nsIndex => $value) {
if (0 === $nsIndex && '' === $value) {
continue;
}

if (0 < $nsIndex) {
$newTokens[] = new Token([T_NS_SEPARATOR, '\\']);
}

$newTokens[] = new Token([T_STRING, $value]);
}
}
}

return $newTokens;
}

/**
* @param bool $isReturnType
*
* @return null|array
*/
protected function getCommonTypeFromAnnotation(Annotation $annotation, $isReturnType)
{
$typesExpression = $annotation->getTypeExpression();

$commonType = $typesExpression->getCommonType();
$isNullable = $typesExpression->allowsNull();

if (null === $commonType) {
return null;
}

if ($isNullable && (\PHP_VERSION_ID < 70100 || 'void' === $commonType)) {
return null;
}

if ('static' === $commonType && (!$isReturnType || \PHP_VERSION_ID < 80000)) {
$commonType = 'self';
}

if ($this->isSkippedType($commonType)) {
return null;
}

if (isset($this->versionSpecificTypes[$commonType]) && \PHP_VERSION_ID < $this->versionSpecificTypes[$commonType]) {
return null;
}

if (isset($this->scalarTypes[$commonType])) {
if (false === $this->configuration['scalar_types']) {
return null;
}
} elseif (1 !== Preg::match($this->classRegex, $commonType)) {
return null;
}

return [$commonType, $isNullable];
}

final protected function isValidSyntax($code)
{
if (!isset(self::$syntaxValidationCache[$code])) {
Expand Down

0 comments on commit 24e9391

Please sign in to comment.