This is the part(3) of a series. I suggest you read parts 1 and 2 before this one.
In part 2, I made sure that the test passed correctly. The focus now is how we traverse a block of code inside its scope. We should be able to increase the variables in the formula at the right moment.
If you have read part 1, you know that we need to implement the validate function that is mandatory in the trait Rule
.
impl Rule for E009 {
fn validate(
&self,
statement: &php_parser_rs::parser::ast::Statement,
) -> Vec<crate::project::Suggestion> {
}
}
In part 2 we have learned that :
Nodes are like the conditional statement: if, else, while, for, etc.
Edges are the paths that can be taken.
The validate
function will be executed for every statement in a source code. In the source code below, there are 32 lines of code.
<?php
class Index{
public function tooComplex(){
$a = 1;
$b = 2;
if($a > $b){
if($b < $a){
if($a == $b){
while($b > $a){
}
}
}
}
}
}
Phanalist needs a way to detect when we are in the function's scope with the name tooComplex().
With pattern matching, it is super easy to detect if the statement is either: if
,else
,etc..
The first statement that I want to match for is class Index{
and from there, we will continue down the tree. If you think the same way as I do you know that I will be using recursion to calculate the cyclomatic complexity. After we have matched the scope of the tooComplex()
function.
impl Rule for E009 {
fn validate(
&self,
statement: &php_parser_rs::parser::ast::Statement,
) -> Vec<crate::project::Suggestion> {
let mut suggestions = Vec::new();
let mut graph = Graph { n: 0, e: 0, p: 0 };
match statement {
Statement::Class(class) => {
for member in &class.body.members {
match member {
ClassMember::ConcreteMethod(concretemethod) => {
match concretemethod.body.clone() {
MethodBody {
comments: _,
left_brace: _,
statements,
right_brace: _,
} => {
/// HERE !!
}
}
}
}
_ => {}
}
}
}
_ => {}
}
suggestions
}
}
If you aren't familiar with pattern matching, it's kind of a switch keyword on steroids. Look for the Look for /// HERE !!
in the above code.
MethodBody {
comments: _,
left_brace: _,
statements,
right_brace: _,
} => {
/// HERE 1
}
}
As you can see we have four parameters that we can use. The ones we are looking for are the statements. The type of this parameter is Vec<Statement>
meaning it can be anything.
Let's use the Graph
we previously created in part 2
In the source code below, Phanalist calls a function with let graph = calculate _cyclomatic_complexity();
This function will return a struct of the type Graph
. The first parameter passed is statements.clone()
. This function uses recursion to traverse down the tree. In part 4 I will explain the recursion we were thinking about.
let graph = calculate_cyclomatic_complexity(
statements.clone(),
&mut graph,
);
if graph.calculate() > 10 {
suggestions.push(Suggestion::from("This method body is too complex. Make it easier to understand.".to_string(),
concretemethod.function,
"E009".to_string() ));
}
The Graph
struct has the calculate
function we previously used to calculate the cyclomatic complexity. My threshold is always ten, so I will be using that.
If the value exceeds ten, I push the Suggestion
onto the vector. The vector of Suggestion
is the value returned to the validate function's caller.
Recursion
In computer science, recursion is a method of solving a computational problem where the solution depends on solutions to smaller instances of the same problem. Recursion solves such recursive problems using functions that call themselves from within their code. Wikipedia
Conclusion
If you are a PHP developer, you know that a developer can make many more mistakes in PHP. In the future, the list of Rules will continue to grow into something more useful.
Thanks for reading!
Contribution is always welcome if you have a mistake you would like to add to Phanalist.
Top comments (0)