我一直在阅读任何地方生锈的速度快,有记忆安全护栏,最近是最喜欢的语言中的一种。在阅读了这本书并梦见了解该语言之后,我决定为我使用了十多年的语言构建静态分析仪。我将命名为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中犯更多错误。将来,此列表将继续成长为更有用的东西。
感谢您的阅读!