Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
guanguans opened this issue May 6, 2023 · 0 comments
Open

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

guanguans opened this issue May 6, 2023 · 0 comments
Labels
2023 2023 Laravel Laravel PHP PHP

Comments

@guanguans
Copy link
Owner

guanguans commented May 6, 2023

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

最近在迁移一个 上古 项目到 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\ConfiguredCodeSample;
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 ConfiguredCodeSample(
                    <<<'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
                    ,
                    ['exceptName']
                ),
            ]);
    }

    /**
     * {@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',
    ]);
    ...
};

参考链接

原文链接

@guanguans guanguans added PHP PHP Laravel Laravel 2023 2023 labels May 6, 2023
@guanguans guanguans changed the title 分享一个规范化命名风格的 rector 规则 统一规范化项目的命名风格 May 6, 2023
@guanguans guanguans changed the title 统一规范化项目的命名风格 统一规范化代码的命名风格 May 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2023 2023 Laravel Laravel PHP PHP
Projects
None yet
Development

No branches or pull requests

1 participant