|
| 1 | +package tools |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + |
| 6 | + "github.com/bblfsh/sdk/uast" |
| 7 | +) |
| 8 | + |
| 9 | +// CyclomaticComplexity returns the cyclomatic complexity for the node. The cyclomatic complexity |
| 10 | +// is a quantitative measure of the number of linearly independent paths through a program's source code. |
| 11 | +// It was developed by Thomas J. McCabe, Sr. in 1976. For a formal description see: |
| 12 | +// https://en.wikipedia.org/wiki/Cyclomatic_complexity |
| 13 | +// And the original paper: http://www.literateprogramming.com/mccabe.pdf |
| 14 | + |
| 15 | +// This implementation uses PMD implementation as reference and uses the method of |
| 16 | +// counting one + one of the following UAST Roles if present on any children: |
| 17 | +// If | SwitchCase | For[Each] | [Do]While | TryCatch | Continue | OpBoolean* | Goto |
| 18 | +// Important: since some languages allow for code defined |
| 19 | +// outside function definitions, this won't check that the Node has the role FunctionDeclarationRole |
| 20 | +// so the user should check that if the intended use is calculating the complexity of a function/method. |
| 21 | +// If the children contain more than one function definitions, the value will not be averaged between |
| 22 | +// the total number of function declarations but given as a total. |
| 23 | +// |
| 24 | +// Some practical implementations counting tokens in the code. They sometimes differ; for example |
| 25 | +// some of them count the switch "default" as an incrementor, some consider all return values minus the |
| 26 | +// last, some of them consider "else" (which is wrong IMHO, but not for elifs, remember than the IfElse |
| 27 | +// token in the UAST is really an Else not an "else if", elseifs would have a children If token), some |
| 28 | +// consider throw and finally while others only the catch, etc. |
| 29 | +// |
| 30 | +// Examples: |
| 31 | +// PMD reference implementation: http://pmd.sourceforge.net/pmd-4.3.0/xref/net/sourceforge/pmd/rules/CyclomaticComplexity.html |
| 32 | +// GMetrics: http://gmetrics.sourceforge.net/gmetrics-CyclomaticComplexityMetric.html |
| 33 | +// Go: https://github.com/fzipp/gocyclo/blob/master/gocyclo.go#L214 |
| 34 | +// SonarQube (include rules for many languages): https://docs.sonarqube.org/display/SONAR/Metrics+-+Complexity |
| 35 | +// |
| 36 | +// IMPORTANT DISCLAIMER: McCabe definition specifies clearly that boolean operations should increment the |
| 37 | +// count in 1 for every boolean element when the language if the language evaluates conditions in |
| 38 | +// short-circuit. Unfortunately in the current version of the UAST we don't specify these language invariants |
| 39 | +// and also we still haven't defined how we are going to represent the boolean expressions (which also would |
| 40 | +// need a tree transformation process in the pipeline that we lack) so lacking a better way of detecting for |
| 41 | +// all languages what symbols or literals are part of a boolean expression we count the boolean operators |
| 42 | +// themselves which should work for short-circuit infix languages but not prefix or infix languages that can |
| 43 | +// evaluate more than two items with a single operator. (FIXME when both things are solved in the UAST |
| 44 | +// definition and the SDK). |
| 45 | + |
| 46 | +type CyclomaticComplexity struct{} |
| 47 | + |
| 48 | +func (cc CyclomaticComplexity) Exec(n *uast.Node) error { |
| 49 | + result := cyclomaticComplexity(n) |
| 50 | + fmt.Println("Cyclomatic Complexity = ", result) |
| 51 | + return nil |
| 52 | +} |
| 53 | + |
| 54 | +func cyclomaticComplexity(n *uast.Node) int { |
| 55 | + complexity := 1 |
| 56 | + |
| 57 | + iter := uast.NewOrderPathIter(uast.NewPath(n)) |
| 58 | + |
| 59 | + for { |
| 60 | + p := iter.Next() |
| 61 | + if p.IsEmpty() { |
| 62 | + break |
| 63 | + } |
| 64 | + n := p.Node() |
| 65 | + for _, r := range n.Roles { |
| 66 | + if addsComplexity(r) { |
| 67 | + complexity++ |
| 68 | + } |
| 69 | + } |
| 70 | + } |
| 71 | + return complexity |
| 72 | +} |
| 73 | + |
| 74 | +func addsComplexity(r uast.Role) bool { |
| 75 | + return r == uast.If || r == uast.SwitchCase || r == uast.For || r == uast.ForEach || |
| 76 | + r == uast.DoWhile || r == uast.While || r == uast.TryCatch || r == uast.Continue || |
| 77 | + r == uast.OpBooleanAnd || r == uast.OpBooleanOr || r == uast.OpBooleanXor |
| 78 | +} |
0 commit comments