为PHP编写自己的静态分析仪。
#初学者 #php #codequality #codereview

我一直在阅读任何地方生锈的速度快,有记忆安全护栏,最近是最喜欢的语言中的一种。在阅读了这本书并梦见了解该语言之后,我决定为我使用了十多年的语言构建静态分析仪。我将命名为Phanalist,该代码也将在
上提供 https://github.com/denzyldick/phanalist.

要看看这个想法是否有效,我列出了我经常看到的4个常见不良做法/错误。正如您在下面的代码中看到的那样,我们都可以同意,班级不会让您开心。

 <?php

 class uTesting extends FakeClass
  {
    const I_ = null;
    const hello = null;
    $no_= null, $no_modifier = null;

    public function __construct($o)
    {
      $this->fake_variable = 'hellworld';
    }

    function test($a){
      return 1;
    }
  }

为了保持轻松,我首先会发现这四个易于发现的常见错误。此列表将来将继续增长。

  • 放错位置的打开标签。
  • 从小写开始的班级名称
  • 小写常数
  • 定义没有类型的参数。

静态分析仪应该是初学者友好的。与其使开发人员的生活更加艰难,不如说能够以开发人员应该理解的方式来解释错误。但是首先,让我们专注于第一步。

我将使用PHP-Parser可以解析PHP代码并生成AST(抽象语法树)的Rust库。以上PHP代码的解析器输出将是包含所有语句的向量(Vec<Statement)。

   [
    FullOpeningTag(
        Span {
            line: 1,
            column: 1,
            position: 0,
        },
    ),
    Class(
        ClassStatement {
            attributes: [],
            modifiers: ClassModifierGroup {
                modifiers: [],
            },
            class: Span {
                line: 2,
                column: 2,
                position: 7,
            },
            name: SimpleIdentifier {
                span: Span {
                    line: 2,
                    column: 8,
                    position: 13,
                },
                value: "uTesting",
            },
            extends: Some(
                ClassExtends {
                    extends: Span {
                        line: 2,
                        column: 17,
                        position: 22,
                    },
                    parent: SimpleIdentifier {
                        span: Span {
                            line: 2,
                            column: 25,
                            position: 30,
                        },
                        value: "FakeClass",
                    },
                },
            ),
            implements: None,
            body: ClassBody {
                left_brace: Span {
                    line: 3,
                    column: 3,
                    position: 42,
                },
                members: [
                    Constant(
                        ClassishConstant {
                            comments: CommentGroup {
                                comments: [],
                            },
                            attributes: [],
                            modifiers: ConstantModifierGroup {
                                modifiers: [],
                            },
                            const: Span {
                                line: 4,
                                column: 5,
                                position: 48,
                            },
                            entries: [
                                ConstantEntry {
                                    name: SimpleIdentifier {
                                        span: Span {
                                            line: 4,
                                            column: 11,
                                            position: 54,
                                        },
                                        value: "I_",
                                    },
                                    equals: Span {
                                        line: 4,
                                        column: 14,
                                        position: 57,
                                    },
                                    value: Null,
                                },
                            ],
                            semicolon: Span {
                                line: 4,
                                column: 20,
                                position: 63,
                            },
                        },
                    ),
                    Constant(
                        ClassishConstant {
                            comments: CommentGroup {
                                comments: [],
                            },
                            attributes: [],
                            modifiers: ConstantModifierGroup {
                                modifiers: [],
                            },
                            const: Span {
                                line: 5,
                                column: 5,
                                position: 69,
                            },
                            entries: [
                                ConstantEntry {
                                    name: SimpleIdentifier {
                                        span: Span {
                                            line: 5,
                                            column: 11,
                                            position: 75,
                                        },
                                        value: "hello",
                                    },
                                    equals: Span {
                                        line: 5,
                                        column: 17,
                                        position: 81,
                                    },
                                    value: Null,
                                },
                            ],
                            semicolon: Span {
                                line: 5,
                                column: 23,
                                position: 87,
                            },
                        },
                    ),
                    Property(
                        Property {
                            attributes: [],
                            modifiers: PropertyModifierGroup {
                                modifiers: [],
                            },
                            type: None,
                            entries: [
                                Initialized {
                                    variable: SimpleVariable {
                                        span: Span {
                                            line: 6,
                                            column: 5,
                                            position: 93,
                                        },
                                        name: "$no_",
                                    },
                                    equals: Span {
                                        line: 6,
                                        column: 9,
                                        position: 97,
                                    },
                                    value: Null,
                                },
                                Initialized {
                                    variable: SimpleVariable {
                                        span: Span {
                                            line: 6,
                                            column: 17,
                                            position: 105,
                                        },
                                        name: "$no_modifier",
                                    },
                                    equals: Span {
                                        line: 6,
                                        column: 30,
                                        position: 118,
                                    },
                                    value: Null,
                                },
                            ],
                            end: Span {
                                line: 6,
                                column: 36,
                                position: 124,
                            },
                        },
                    ),
                    ConcreteConstructor(
                        ConcreteConstructor {
                            comments: CommentGroup {
                                comments: [],
                            },
                            attributes: [],
                            modifiers: MethodModifierGroup {
                                modifiers: [
                                    Public(
                                        Span {
                                            line: 8,
                                            column: 5,
                                            position: 131,
                                        },
                                    ),
                                ],
                            },
                            function: Span {
                                line: 8,
                                column: 12,
                                position: 138,
                            },
                            ampersand: None,
                            name: SimpleIdentifier {
                                span: Span {
                                    line: 8,
                                    column: 21,
                                    position: 147,
                                },
                                value: "__construct",
                            },
                            parameters: ConstructorParameterList {
                                comments: CommentGroup {
                                    comments: [],
                                },
                                left_parenthesis: Span {
                                    line: 8,
                                    column: 32,
                                    position: 158,
                                },
                                parameters: CommaSeparated {
                                    inner: [
                                        ConstructorParameter {
                                            attributes: [],
                                            comments: CommentGroup {
                                                comments: [],
                                            },
                                            ampersand: None,
                                            name: SimpleVariable {
                                                span: Span {
                                                    line: 8,
                                                    column: 33,
                                                    position: 159,
                                                },
                                                name: "$o",
                                            },
                                            data_type: None,
                                            ellipsis: None,
                                            default: None,
                                            modifiers: PromotedPropertyModifierGroup {
                                                modifiers: [],
                                            },
                                        },
                                    ],
                                    commas: [],
                                },
                                right_parenthesis: Span {
                                    line: 8,
                                    column: 35,
                                    position: 161,
                                },
                            },
                            body: MethodBody {
                                comments: CommentGroup {
                                    comments: [],
                                },
                                left_brace: Span {
                                    line: 9,
                                    column: 5,
                                    position: 167,
                                },
                                statements: [
                                    Expression(
                                        ExpressionStatement {
                                            expression: AssignmentOperation(
                                                Assign {
                                                    left: PropertyFetch {
                                                        target: Variable(
                                                            SimpleVariable(
                                                                SimpleVariable {
                                                                    span: Span {
                                                                        line: 10,
                                                                        column: 7,
                                                                        position: 175,
                                                                    },
                                                                    name: "$this",
                                                                },
                                                            ),
                                                        ),
                                                        arrow: Span {
                                                            line: 10,
                                                            column: 12,
                                                            position: 180,
                                                        },
                                                        property: Identifier(
                                                            SimpleIdentifier(
                                                                SimpleIdentifier {
                                                                    span: Span {
                                                                        line: 10,
                                                                        column: 14,
                                                                        position: 182,
                                                                    },
                                                                    value: "fake_variable",
                                                                },
                                                            ),
                                                        ),
                                                    },
                                                    equals: Span {
                                                        line: 10,
                                                        column: 28,
                                                        position: 196,
                                                    },
                                                    right: Literal(
                                                        String(
                                                            LiteralString {
                                                                value: "'hellworld'",
                                                                span: Span {
                                                                    line: 10,
                                                                    column: 30,
                                                                    position: 198,
                                                                },
                                                            },
                                                        ),
                                                    ),
                                                },
                                            ),
                                            ending: Semicolon(
                                                Span {
                                                    line: 10,
                                                    column: 41,
                                                    position: 209,
                                                },
                                            ),
                                        },
                                    ),
                                ],
                                right_brace: Span {
                                    line: 11,
                                    column: 5,
                                    position: 215,
                                },
                            },
                        },
                    ),
                    ConcreteMethod(
                        ConcreteMethod {
                            comments: CommentGroup {
                                comments: [],
                            },
                            attributes: [],
                            modifiers: MethodModifierGroup {
                                modifiers: [],
                            },
                            function: Span {
                                line: 13,
                                column: 5,
                                position: 222,
                            },
                            ampersand: None,
                            name: SimpleIdentifier {
                                span: Span {
                                    line: 13,
                                    column: 14,
                                    position: 231,
                                },
                                value: "test",
                            },
                            parameters: FunctionParameterList {
                                comments: CommentGroup {
                                    comments: [],
                                },
                                left_parenthesis: Span {
                                    line: 13,
                                    column: 18,
                                    position: 235,
                                },
                                parameters: CommaSeparated {
                                    inner: [
                                        FunctionParameter {
                                            comments: CommentGroup {
                                                comments: [],
                                            },
                                            name: SimpleVariable {
                                                span: Span {
                                                    line: 13,
                                                    column: 19,
                                                    position: 236,
                                                },
                                                name: "$a",
                                            },
                                            attributes: [],
                                            data_type: None,
                                            ellipsis: None,
                                            default: None,
                                            ampersand: None,
                                        },
                                    ],
                                    commas: [],
                                },
                                right_parenthesis: Span {
                                    line: 13,
                                    column: 21,
                                    position: 238,
                                },
                            },
                            return_type: None,
                            body: MethodBody {
                                comments: CommentGroup {
                                    comments: [],
                                },
                                left_brace: Span {
                                    line: 13,
                                    column: 22,
                                    position: 239,
                                },
                                statements: [
                                    Return(
                                        ReturnStatement {
                                            return: Span {
                                                line: 14,
                                                column: 7,
                                                position: 247,
                                            },
                                            value: Some(
                                                Literal(
                                                    Integer(
                                                        LiteralInteger {
                                                            value: "1",
                                                            span: Span {
                                                                line: 14,
                                                                column: 14,
                                                                position: 254,
                                                            },
                                                        },
                                                    ),
                                                ),
                                            ),
                                            ending: Semicolon(
                                                Span {
                                                    line: 14,
                                                    column: 15,
                                                    position: 255,
                                                },
                                            ),
                                        },
                                    ),
                                ],
                                right_brace: Span {
                                    line: 15,
                                    column: 5,
                                    position: 261,
                                },
                            },
                        },
                    ),
                ],
                right_brace: Span {
                    line: 17,
                    column: 3,
                    position: 266,
                },
            },
        },
    ),
]

我们将通过导航树来找到我之前定义的四个常见错误。让我们从找到第一个错误开始:

 <?php

根据PSR-2编码标准,PHP打开标签应始终处于文件的开头。当你不
这样做,在执行您的PHP代码之前,空白将发送给客户端。导致header already sent错误。在AST中很容易找到这个错误。我们可以迭代向量中的项目,并使用模式匹配来查找FullOpeningTag语句。

for statement in statements{
        match statement {
            Statement::FullOpeningTag(tag) =>{ 
                 project = project.opening_tag(tag.span, file)
                              }
                    }
}

您可以在AST中看到,我们需要的信息存储在struct Span中。

 FullOpeningTag(
        Span {
            line: 1,
            column: 1,
            position: 0,
        },
    ),

我们只需要检查字段线和列是否高于0。如果它们是的,则意味着开头标签不在正确的位置,我们将push a建议到file参数的字段suggestion。正在传递给opening_tag函数。

 pub fn opening_tag(&mut self, span: Span, file: &mut File) -> &mut Project {
        if span.line > 1 {
            file.suggestions.push(
                Suggestion::from(
                    "The opening tag <?php is not on the right line. This should always be the first line in a PHP file.".to_string(),
                    span
                ))
        }

        if span.column > 1 {
            file.suggestions.push(Suggestion::from(
                format!(
                    "The opening tag doesn't start at the right column: {}.",
                    t.column
                )
                .to_string(),
                span,
            ));
        }
        self
    }

我不会解释如何在其余错误中导航到AST中的正确语句。

班级名称以小写为单位。

class uTesting extends FakeClass

执行此操作时,很难区分变量和方法。

我需要找出班级名称的第一个字母是否大写。 String类型具有一种方法chars,可以将字符串转换为包含所有字母的迭代器。您可以使用next()函数抓住第一个字符。 char类型具有一些有价值的方法。我们需要的是is_uppercase()

pub fn has_capitalized_name(name: String, span: Span) -> Option<Suggestion> {
    if !name.chars().next().unwrap().is_uppercase() {
        Some(Suggestion::from(
                format!("The class name {} is not capitalized. The first letter of the name of the class should be in uppercase.", name).to_string(),
                span
            ));
    }

    None
}

小写常数

    const I_ = null;
    const hello = null;

类似于大写的类名称,如果常数在大写中,则更容易区分正常变量。

我假设常数已经是上层的,因此我初始化了变量is_uppercase = true。在所有字母中迭代时,如果我看到不在大写中的字母,我设置了is_uppercase = false

pub fn uppercased_constant_name(entry: ConstantEntry) -> bool {
    match entry {
        ConstantEntry {
            name,
            equals,
            value,
        } => {
            let mut is_uppercase = true;
            for l in name.value.to_string().chars() {
                if l.is_uppercase() == false && l.is_alphabetic() {
                    is_uppercase = l.is_uppercase()
                }
            }

            return is_uppercase;
        }
    }
}

定义没有类型的参数。

我强烈建议始终为您声明的每个参数定义类型。您需要定义该类型,以避免打开大门,以错过对代码的解释并引入不必要的错误。在AST中,FunctionParameter包含参数。我们可以模式匹配data_type字段,并且具有None的手臂需要返回true。我们将返回Some(_)臂的假。

pub fn function_parameter_without_type(parameter: FunctionParameter) -> bool {
    match parameter {
        FunctionParameter {
            comments,
            name,
            attributes,
            data_type,
            ellipsis,
            default,
            ampersand,
        } => match data_type {
            None => return true,
            Some(_) => return false,
        },
    }
}

结论

如果您是PHP开发人员,则知道开发人员可以在PHP中犯更多错误。将来,此列表将继续成长为更有用的东西。

感谢您的阅读!

如果您想将phanalist添加到phanalist,则始终欢迎贡献。