统一规范化代码的命名风格

最近在迁移一个 上古 项目到 laravel 中。我这边的做法是先用 rector 做一个整体初步的语法升级与 laravel 写法的替换,然后主要就是手动重写数据操作的部分。到目前为止除了应用到 rector 自带的规则外,还写了一些自定义的规则,其中有一个规范化命名风格的规则(RenameToPsrNameRector)适用于所有的 PHP 项目,所以在此分享出来。该规则主要是针对常量、变量、函数、类、属性、方法等命名进行统一的规范。其中,常量名遵循大写蛇形命名风格,函数名遵循小写蛇形命名风格,类名遵循大驼峰命名风格,变量名、属性名、方法名遵循小驼峰命名风格。

效果

 <?php

 // lower snake
-function functionName(){}
-functionName();
-call_user_func('functionName');
-call_user_func_array('functionName');
-function_exists('functionName');
+function function_name(){}
+\function_name();
+call_user_func('function_name');
+call_user_func_array('function_name');
+function_exists('function_name');

 // ucfirst camel
-class class_name{}
-enum enum_name{}
-enum Enum{case case_name;}
-interface interface_name{}
-trait trait_name{}
-class Foo extends class_name implements interface_name{}
-class_name::$property;
-class_name::CONST;
-class_name::method();
-enum Enum implements interface_name{}
-use class_name;
-use trait_name;
-class_alias('class_name', 'alias_class_name');
-class_exists('class_name');
-class_implements('class_name');
-class_parents('class_name');
-class_uses('class_name');
-enum_exists('enum_name');
-get_class_methods('class_name');
-get_class_vars('class_name');
-get_parent_class('class_name');
-interface_exists('interface_name');
-is_subclass_of('class_name', 'parent_class_name');
-trait_exists('trait_name', true);
+class ClassName{}
+enum EnumName{}
+enum Enum{case CaseName;}
+interface InterfaceName{}
+trait TraitName{}
+class Foo extends \ClassName implements \InterfaceName{}
+\ClassName::$property;
+\ClassName::CONST;
+\ClassName::method();
+enum Enum implements \InterfaceName{}
+use ClassName;
+use TraitName;
+class_alias('ClassName', 'AliasClassName');
+class_exists('ClassName');
+class_implements('ClassName');
+class_parents('ClassName');
+class_uses('ClassName');
+enum_exists('EnumName');
+get_class_methods('ClassName');
+get_class_vars('ClassName');
+get_parent_class('ClassName');
+interface_exists('InterfaceName');
+is_subclass_of('ClassName', 'ParentClassName');
+trait_exists('TraitName', true);

 // upper snake
-class Foo{public const constName = 'const';}
-Foo::constName;
-define('constName', 'const');
-defined('constName');
-constant('constName');
+class Foo{public const CONST_NAME = 'const';}
+Foo::CONST_NAME;
+define('CONST_NAME', 'const');
+defined('CONST_NAME');
+constant('CONST_NAME');
 constant('Foo::constName');
-constName;
+\CONST_NAME;

 // lcfirst camel
-$var_name;
-$object->method_name();
-$object->property_name;
-call_user_method('method_name', $object);
-call_user_method_array('method_name', $object);
-class Foo{public $property_name;}
-class Foo{public function method_name(){}}
-class Foo{public int $property_name;}
-Foo::$property_name;
-Foo::method_name();
-method_exists($object, 'method_name');
-property_exists($object, 'property_name');
+$varName;
+$object->methodName();
+$object->propertyName;
+call_user_method('methodName', $object);
+call_user_method_array('methodName', $object);
+class Foo{public $propertyName;}
+class Foo{public function methodName(){}}
+class Foo{public int $propertyName;}
+Foo::$propertyName;
+Foo::methodName();
+method_exists($object, 'methodName');
+property_exists($object, 'propertyName');

规则(RenameToPsrNameRector)

<?php

namespace App\Support\Rectors;

use Illuminate\Support\Str;
use PhpParser\Node;
use Rector\Core\Contract\Rector\ConfigurableRectorInterface;
use Rector\Core\Rector\AbstractRector;
use RectorPrefix202305\Webmozart\Assert\Assert;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

class RenameToPsrNameRector extends AbstractRector implements ConfigurableRectorInterface
{
    /**
     * @var array<string>
     */
    protected $except = [
        '*::*',
        'class',
        'false',
        'null',
        'self',
        'static',
        'stdClass',
        'true',
    ];

    public function getRuleDefinition(): RuleDefinition
    {
        return new RuleDefinition(
            'Rename to psr name',
            [
                new CodeSample(
                    <<<'CODE_SAMPLE'
// lower snake
function functionName(){}
functionName();
call_user_func('functionName');
call_user_func_array('functionName');
function_exists('functionName');

// ucfirst camel
class class_name{}
enum enum_name{}
enum Enum{case case_name;}
interface interface_name{}
trait trait_name{}
class Foo extends class_name implements interface_name{}
class_name::$property;
class_name::CONST;
class_name::method();
enum Enum implements interface_name{}
use class_name;
use trait_name;
class_alias('class_name', 'alias_class_name');
class_exists('class_name');
class_implements('class_name');
class_parents('class_name');
class_uses('class_name');
enum_exists('enum_name');
get_class_methods('class_name');
get_class_vars('class_name');
get_parent_class('class_name');
interface_exists('interface_name');
is_subclass_of('class_name', 'parent_class_name');
trait_exists('trait_name', true);

// upper snake
class Foo{public const constName = 'const';}
Foo::constName;
define('constName', 'const');
defined('constName');
constant('constName');
constant('Foo::constName');
constName;

// lcfirst camel
$var_name;
$object->method_name();
$object->property_name;
call_user_method('method_name', $object);
call_user_method_array('method_name', $object);
class Foo{public $property_name;}
class Foo{public function method_name(){}}
class Foo{public int $property_name;}
Foo::$property_name;
Foo::method_name();
method_exists($object, 'method_name');
property_exists($object, 'property_name');
CODE_SAMPLE
                    ,
                    <<<'CODE_SAMPLE'
// lower snake
function function_name(){}
function_name();
call_user_func('function_name');
call_user_func_array('function_name');
function_exists('function_name');

// ucfirst camel
class ClassName{}
enum EnumName{}
enum Enum{case CaseName;}
interface InterfaceName{}
trait TraitName{}
class Foo extends ClassName implements InterfaceName{}
ClassName::$property;
ClassName::CONST;
ClassName::method();
enum Enum implements InterfaceName{}
use ClassName;
use TraitName;
class_alias('ClassName', 'AliasClassName');
class_exists('ClassName');
class_implements('ClassName');
class_parents('ClassName');
class_uses('ClassName');
enum_exists('EnumName');
get_class_methods('ClassName');
get_class_vars('ClassName');
get_parent_class('ClassName');
interface_exists('InterfaceName');
is_subclass_of('ClassName', 'ParentClassName');
trait_exists('TraitName', true);

// upper snake
class Foo{public const CONST_NAME = 'const';}
Foo::CONST_NAME;
define('CONST_NAME', 'const');
defined('CONST_NAME');
constant('CONST_NAME');
constant('Foo::CONST_NAME');
CONST_NAME;

// lcfirst camel
$varName
$object->methodName();
$object->propertyName;
class Foo{public $propertyName;}
class Foo{public function methodName(){}}
class Foo{public int $propertyName;}
Foo::$propertyName;
Foo::methodName();
call_user_method('methodName', $object);
call_user_method_array('methodName', $object);
method_exists($object, 'methodName');
property_exists($object, 'propertyName');
CODE_SAMPLE
                ),
            ]);
    }

    /**
     * {@inheritDoc}
     */
    public function getNodeTypes(): array
    {
        return [
            Node\Expr\FuncCall::class,
            Node\Expr\Variable::class,
            Node\Identifier::class,
            Node\Name::class,
        ];
    }

    /**
     * @param  Node\Expr\FuncCall|Node\Expr\Variable|Node\Identifier|Node\Name  $node
     */
    public function refactor(Node $node)
    {
        try {
            if ($this->shouldLowerSnakeName($node)) {
                return $this->rename($node, static fn (string $name): string => Str::lower(Str::snake($name)));
            }

            if ($this->shouldUcfirstCamelName($node)) {
                return $this->rename($node, static fn (string $name): string => Str::ucfirst(Str::camel($name)));
            }

            if ($this->shouldUpperSnakeName($node)) {
                return $this->rename($node, static fn (string $name): string => Str::upper(Str::snake($name)));
            }

            if ($this->shouldLcfirstCamelName($node)) {
                return $this->rename($node, static fn (string $name): string => Str::lcfirst(Str::camel($name)));
            }
        } catch (\RuntimeException $e) {
            // skip
        }

        return null;
    }

    /**
     * @param  Node\Expr\FuncCall|Node\Expr\Variable|Node\Identifier|Node\Name  $node
     */
    protected function rename(Node $node, callable $renamer): Node
    {
        $renamer = fn (string $name): string => $renamer((function (string $name): string {
            if ($this->isMatches($name, $this->except)) {
                throw new \RuntimeException("The name[$name] is skipped.");
            }

            if (ctype_upper(preg_replace('/[^a-zA-Z]/', '', $name))) {
                return mb_strtolower($name, 'UTF-8');
            }

            return $name;
        })($name));

        if ($node instanceof Node\Name) {
            $node->parts[count($node->parts) - 1] = $renamer($node->parts[count($node->parts) - 1]);

            return $node;
        }

        if (
            $this->isSubclasses($node, [
                Node\Expr\Variable::class,
                Node\Identifier::class,
            ])
        ) {
            $node->name = $renamer($node->name);

            return $node;
        }

        if ($node instanceof Node\Expr\FuncCall) {
            if (
                $this->isNames($node, [
                    'call_user_func',
                    'call_user_func_array',
                    'call_user_method',
                    'call_user_method_array',
                    'class_alias',
                    'class_exists',
                    'class_implements',
                    'class_parents',
                    'class_uses',
                    'constant',
                    'define',
                    'defined',
                    'enum_exists',
                    'function_exists',
                    'get_class_methods',
                    'get_class_vars',
                    'get_parent_class',
                    'interface_exists',
                    'is_subclass_of',
                    'trait_exists',
                ])
                && $this->hasFuncCallIndexStringArg($node, 0)
            ) {
                $node->args[0]->value->value = $renamer($node->args[0]->value->value);
            }

            if (
                $this->isNames($node, [
                    'class_alias',
                    'is_subclass_of',
                    'method_exists',
                    'property_exists',
                ])
                && $this->hasFuncCallIndexStringArg($node, 1)
            ) {
                $node->args[1]->value->value = $renamer($node->args[1]->value->value);
            }
        }

        return $node;
    }

    /**
     * @param  Node\Expr\FuncCall|Node\Expr\Variable|Node\Identifier|Node\Name  $node
     */
    protected function shouldLowerSnakeName(Node $node): bool
    {
        $parent = $node->getAttribute('parent');

        // function function_name(){}
        if ($node instanceof Node\Identifier && $parent instanceof Node\Stmt\Function_) {
            return true;
        }

        // function_name();
        if ($node instanceof Node\Name && $parent instanceof Node\Expr\FuncCall) {
            return true;
        }

        if (
            $node instanceof Node\Expr\FuncCall
            && $this->isNames($node, [
                // call_user_func('function_name');
                'call_user_func',
                // call_user_func_array('function_name');
                'call_user_func_array',
                // function_exists('function_name');
                'function_exists',
            ])
            && $this->hasFuncCallIndexStringArg($node, 0)
        ) {
            return true;
        }

        return false;
    }

    /**
     * @param  Node\Expr\FuncCall|Node\Expr\Variable|Node\Identifier|Node\Name  $node
     */
    protected function shouldUcfirstCamelName(Node $node): bool
    {
        $parent = $node->getAttribute('parent');

        if (
            $node instanceof Node\Identifier
            && $this->isSubclasses($parent, [
                // interface InterfaceName{}
                Node\Stmt\Interface_::class,
                // class ClassName{}
                Node\Stmt\Class_::class,
                // trait TraitName{}
                Node\Stmt\Trait_::class,
                // enum EnumName{}
                Node\Stmt\Enum_::class,
                // enum Enum{case CaseName;}
                Node\Stmt\EnumCase::class,
            ])
        ) {
            return true;
        }

        if (
            $node instanceof Node\Name
            && ! $this->isName($node, 'stdClass')
            && $this->isSubclasses($parent, [
                // class Foo extends ClassName implements InterfaceName{}
                Node\Stmt\Class_::class,
                // enum Enum implements InterfaceName{}
                Node\Stmt\Enum_::class,
                // use ClassName;
                Node\Stmt\UseUse::class,
                // use TraitName;
                Node\Stmt\TraitUse::class,
                // ClassName::CONST;
                Node\Expr\ClassConstFetch::class,
                // ClassName::$property;
                Node\Expr\StaticPropertyFetch::class,
                // ClassName::method();
                Node\Expr\StaticCall::class,
            ])
        ) {
            return true;
        }

        if ($node instanceof Node\Expr\FuncCall) {
            if (
                $this->isNames($node, [
                    // class_alias('ClassName', 'AliasClassName');
                    'class_alias',
                    // class_exists('ClassName');
                    'class_exists',
                    // class_implements('ClassName');
                    'class_implements',
                    // class_parents('ClassName');
                    'class_parents',
                    // class_uses('ClassName');
                    'class_uses',
                    // enum_exists('EnumName');
                    'enum_exists',
                    // get_class_methods('ClassName');
                    'get_class_methods',
                    // get_class_vars('ClassName');
                    'get_class_vars',
                    // get_parent_class('ClassName');
                    'get_parent_class',
                    // interface_exists('InterfaceName');
                    'interface_exists',
                    // is_subclass_of('ClassName', 'ParentClassName');
                    'is_subclass_of',
                    // trait_exists('TraitName', true);
                    'trait_exists',
                ])
                && $this->hasFuncCallIndexStringArg($node, 0)
            ) {
                return true;
            }

            if (
                $this->isNames($node, [
                    // class_alias('ClassName', 'AliasClassName');
                    'class_alias',
                    // is_subclass_of('ClassName', 'ParentClassName');
                    'is_subclass_of',
                ])
                && $this->hasFuncCallIndexStringArg($node, 1)
            ) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param  Node\Expr\FuncCall|Node\Expr\Variable|Node\Identifier|Node\Name  $node
     */
    protected function shouldUpperSnakeName(Node $node): bool
    {
        $parent = $node->getAttribute('parent');

        if (
            $node instanceof Node\Identifier
            && ! $this->isName($node, 'class')
            && $this->isSubclasses($parent, [
                // class Foo{public const CONST_NAME = 'const';}
                Node\Const_::class,
                // Foo::CONST_NAME;
                Node\Expr\ClassConstFetch::class,
            ])
        ) {
            return true;
        }

        if (
            $node instanceof Node\Expr\FuncCall
            && $this->isNames($node, [
                // define('CONST_NAME', 'const');
                'define',
                // defined('CONST_NAME');
                'defined',
                // constant('Foo::CONST_NAME');
                'constant',
            ])
            && $this->hasFuncCallIndexStringArg($node, 0)
        ) {
            return true;
        }

        // CONST_NAME;
        if (
            $node instanceof Node\Name
            && ! $this->isNames($node, ['null', 'true', 'false'])
            && $parent instanceof Node\Expr\ConstFetch
        ) {
            return true;
        }

        return false;
    }

    /**
     * @param  Node\Expr\FuncCall|Node\Expr\Variable|Node\Identifier|Node\Name  $node
     */
    protected function shouldLcfirstCamelName(Node $node): bool
    {
        // $varName;
        if ($node instanceof Node\Expr\Variable && is_string($node->name)) {
            return true;
        }

        if (
            $node instanceof Node\Identifier
            && $this->isSubclasses($node->getAttribute('parent'), [
                // class Foo{public $propertyName;}
                Node\Stmt\Property::class,
                // class Foo{public int $propertyName;}
                Node\Stmt\PropertyProperty::class,
                // class Foo{public function methodName(){}}
                Node\Stmt\ClassMethod::class,
                // $object->propertyName;
                Node\Expr\PropertyFetch::class,
                // Foo::$propertyName;
                Node\Expr\StaticPropertyFetch::class,
                // $object->methodName();
                Node\Expr\MethodCall::class,
                // Foo::methodName();
                Node\Expr\StaticCall::class,
            ])
        ) {
            return true;
        }

        if ($node instanceof Node\Expr\FuncCall) {
            if (
                $this->isNames($node, [
                    // call_user_method('methodName', $object);
                    'call_user_method',
                    // call_user_method_array('methodName', $object);
                    'call_user_method_array',
                ])
                && $this->hasFuncCallIndexStringArg($node, 0)
            ) {
                return true;
            }

            if (
                $this->isNames($node, [
                    // method_exists($object, 'methodName');
                    'method_exists',
                    // property_exists($object, 'propertyName');
                    'property_exists',
                ])
                && $this->hasFuncCallIndexStringArg($node, 1)
            ) {
                return true;
            }
        }

        return false;
    }

    protected function isSubclasses($object, array $classes): bool
    {
        if (! is_object($object)) {
            return false;
        }

        foreach ($classes as $class) {
            if ($object instanceof $class) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param  string  $value
     * @param  string|iterable<string>  $patterns
     */
    public function isMatches($value, $patterns): bool
    {
        $value = (string) $value;
        if (! is_iterable($patterns)) {
            $patterns = [$patterns];
        }

        foreach ($patterns as $pattern) {
            $pattern = (string) $pattern;
            if ($pattern === $value) {
                return true;
            }

            $pattern = preg_quote($pattern, '#');
            $pattern = str_replace('\*', '.*', $pattern);
            if (preg_match('#^'.$pattern.'\z#u', $value) === 1) {
                return true;
            }
        }

        return false;
    }

    protected function hasFuncCallIndexStringArg(Node\Expr\FuncCall $funcCall, int $index): bool
    {
        return isset($funcCall->args[$index])
            && $funcCall->args[$index]->name === null
            && $funcCall->args[$index]->value instanceof Node\Scalar\String_;
    }

    protected function hasFuncCallNameStringArg(Node\Expr\FuncCall $funcCall, string $name): bool
    {
        foreach ($funcCall->args as $arg) {
            if (
                $arg->name instanceof Node\Identifier
                && $arg->name->name === $name
                && $arg->value instanceof Node\Scalar\String_
            ) {
                return true;
            }
        }

        return false;
    }

    public function configure(array $configuration): void
    {
        Assert::allStringNotEmpty($configuration);
        $this->except = [...$this->except, ...$configuration];
    }
}

使用

rector 配置文件中配置该规则即可

<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;

return static function (RectorConfig $rectorConfig): void {
    ...
    $rectorConfig->ruleWithConfiguration(\App\Support\Rectors\RenameToPsrNameRector::class, [
        'exceptName',
    ]);
    ...
};

参考链接

原文链接

本作品采用《CC 协议》,转载必须注明作者和本文链接
No practice, no gain in one's wit. 我的 Gitub
本帖由系统于 1年前 自动加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 2

赞,终于看到有分享 rector 的小伙伴了,用好 rector 可以统一团队编码风格、减少 codereview 的时间

2年前 评论
guanguans (楼主) 2年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
未填写
文章
58
粉丝
131
喜欢
991
收藏
1349
排名:45
访问:15.5 万
私信
所有博文
社区赞助商