Skip to content

Commit 8399c5f

Browse files
committed
fix(analysis): fix rloc sometimes being off by one #4092
1 parent 759ae94 commit 8399c5f

File tree

8 files changed

+69
-130
lines changed

8 files changed

+69
-130
lines changed

analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/AvailableCollectors.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ enum class AvailableCollectors(
1010
JAVASCRIPT(FileExtension.JAVASCRIPT, ::JavascriptCollector),
1111
KOTLIN(FileExtension.KOTLIN, ::KotlinCollector),
1212
JAVA(FileExtension.JAVA, ::JavaCollector),
13-
CSHARP(FileExtension.CSHARP, ::CSharpCollector)
13+
CSHARP(FileExtension.CSHARP, ::CSharpCollector),
14+
PYTHON(FileExtension.PYTHON, ::PythonCollector)
1415
}

analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/MetricCollector.kt

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ abstract class MetricCollector(
1818
) {
1919
private var lastCountedCommentLine = -1
2020
private var lastCountedCodeLine = -1
21-
private var rootNodeType: String = "" //TODO: schauen wie wir für alle kinder vom rootnode berechnen
21+
private var rootNodeType: String = ""
2222

2323
// maps a metric to its index for the more performant IntArray and the function how to calculate the metric
2424
private val metricInfo = mapOf(
@@ -58,7 +58,9 @@ abstract class MetricCollector(
5858
private fun getRootNode(file: File): TSNode {
5959
val parser = TSParser()
6060
parser.setLanguage(treeSitterLanguage)
61-
return parser.parseString(null, file.readText()).rootNode
61+
val rootNode = parser.parseString(null, file.readText()).rootNode
62+
rootNodeType = rootNode.type
63+
return rootNode
6264
}
6365

6466
private val walkTree = DeepRecursiveFunction<Pair<TSTreeCursor, IntArray>, Unit> { (cursor, metrics) ->
@@ -68,9 +70,11 @@ abstract class MetricCollector(
6870
val startRow = currentNode.startPoint.row
6971
val endRow = currentNode.endPoint.row
7072

71-
for ((_, indexAndCalculateMetricForNodeFn) in metricInfo) {
72-
val (index, calculateMetricForNodeFn) = indexAndCalculateMetricForNodeFn
73-
metrics[index] += calculateMetricForNodeFn(currentNode, nodeType, startRow, endRow)
73+
if (nodeType != rootNodeType) {
74+
for ((_, indexAndCalculateMetricForNodeFn) in metricInfo) {
75+
val (index, calculateMetricForNodeFn) = indexAndCalculateMetricForNodeFn
76+
metrics[index] += calculateMetricForNodeFn(currentNode, nodeType, startRow, endRow)
77+
}
7478
}
7579

7680
if (cursor.gotoFirstChild()) {
@@ -134,8 +138,15 @@ abstract class MetricCollector(
134138
private fun isNestedTypeAllowed(node: TSNode, nodeType: String, nestedTypes: Set<NestedNodeType>): Boolean {
135139
for (nestedType in nestedTypes) {
136140
if (nestedType.baseNodeType == nodeType) {
137-
val childNode = node.getChildByFieldName(nestedType.childNodeFieldName)
138-
if (nestedType.childNodeTypes.contains(childNode.type)) return true
141+
if (nestedType.childNodePosition != null &&
142+
nestedType.childNodeCount == node.childCount
143+
) {
144+
val childNode = node.getChild(nestedType.childNodePosition)
145+
if (nestedType.childNodeTypes.contains(childNode.type)) return true
146+
} else if (nestedType.childNodeFieldName != null) {
147+
val childNode = node.getChildByFieldName(nestedType.childNodeFieldName)
148+
if (nestedType.childNodeTypes.contains(childNode.type)) return true
149+
}
139150
}
140151
}
141152
return false
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,9 @@
11
package de.maibornwolff.codecharta.analysers.parsers.unified.metriccollectors
22

3-
import de.maibornwolff.codecharta.analysers.parsers.unified.metricqueries.PythonQueries
4-
import org.treesitter.TSNode
5-
import org.treesitter.TSTreeCursor
3+
import de.maibornwolff.codecharta.analysers.parsers.unified.metricnodetypes.PythonNodeTypes
64
import org.treesitter.TreeSitterPython
75

86
class PythonCollector : MetricCollector(
97
treeSitterLanguage = TreeSitterPython(),
10-
queryProvider = PythonQueries()
11-
) {
12-
override fun getRealLinesOfCode(root: TSNode): Int {
13-
if (root.childCount == 0) return 0
14-
15-
rootNodeType = root.type
16-
val commentTypes = getParentAndChildNodeTypesFromQuery(queryProvider.commentLinesQuery)
17-
return walkTree(TSTreeCursor(root), commentTypes)
18-
}
19-
20-
private fun walkTree(cursor: TSTreeCursor, commentTypes: List<Pair<String, String?>>): Int {
21-
var realLinesOfCode = 0
22-
val currentNode = cursor.currentNode()
23-
24-
if (!isCommentNode(currentNode, commentTypes)) {
25-
if (currentNode.startPoint.row > lastCountedLine &&
26-
currentNode.type != rootNodeType &&
27-
!areAllChildrenInLineCommentNodes(currentNode, currentNode.startPoint.row, commentTypes)
28-
) {
29-
lastCountedLine = currentNode.startPoint.row
30-
realLinesOfCode++
31-
}
32-
33-
if (currentNode.childCount == 0) {
34-
if (currentNode.endPoint.row > lastCountedLine) {
35-
realLinesOfCode += currentNode.endPoint.row - currentNode.startPoint.row
36-
lastCountedLine = currentNode.endPoint.row
37-
}
38-
} else if (currentNode.endPoint.row > currentNode.startPoint.row && cursor.gotoFirstChild()) {
39-
realLinesOfCode += walkTree(cursor, commentTypes)
40-
}
41-
}
42-
43-
if (cursor.gotoNextSibling()) {
44-
realLinesOfCode += walkTree(cursor, commentTypes)
45-
} else {
46-
cursor.gotoParent()
47-
}
48-
49-
return realLinesOfCode
50-
}
51-
52-
private fun getParentAndChildNodeTypesFromQuery(query: String): List<Pair<String, String?>> {
53-
val regex = Regex("""\((.*?)\)\s*@""", RegexOption.MULTILINE)
54-
val commentNodeTypes = regex.findAll(query).map { it.groupValues[1] }.toList()
55-
56-
val parentToChildTypes = commentNodeTypes.mapNotNull {
57-
val match = Regex("""(\w+)(?:\s*\((\w+)\))?""").find(it)
58-
match?.let { m -> m.groupValues[1] to m.groupValues.getOrNull(2) }
59-
}
60-
return parentToChildTypes
61-
}
62-
63-
private fun isCommentNode(node: TSNode, commentTypes: List<Pair<String, String?>>): Boolean {
64-
for ((parentType, childType) in commentTypes) {
65-
if (childType.isNullOrBlank() && node.type == parentType) {
66-
return true
67-
} else if (node.type == parentType && node.childCount == 1 && node.getChild(0).type == childType) {
68-
return true
69-
}
70-
}
71-
return false
72-
}
73-
74-
private fun areAllChildrenInLineCommentNodes(node: TSNode, line: Int, commentTypes: List<Pair<String, String?>>): Boolean {
75-
if (node.childCount == 0) return isCommentNode(node, commentTypes)
76-
77-
val lookAheadCursor = TSTreeCursor(node)
78-
if (lookAheadCursor.gotoFirstChild()) {
79-
do {
80-
val currentNode = lookAheadCursor.currentNode()
81-
require(
82-
currentNode.startPoint.row >= line
83-
) { "Malformed tree detected, child node start line comes before parent node start like!" }
84-
if (currentNode.startPoint.row > line) return true
85-
if (!isCommentNode(currentNode, commentTypes)) return false
86-
} while (lookAheadCursor.gotoNextSibling())
87-
}
88-
return true
89-
}
90-
}
8+
queryProvider = PythonNodeTypes()
9+
)

analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/MetricNodeTypes.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ class TreeNodeTypes(
2121

2222
class NestedNodeType(
2323
val baseNodeType: String,
24-
val childNodeFieldName: String?,
24+
val childNodeFieldName: String? = null,
25+
val childNodeCount: Int? = null,
26+
val childNodePosition: Int? = null,
2527
val childNodeTypes: Set<String>
2628
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package de.maibornwolff.codecharta.analysers.parsers.unified.metricnodetypes
2+
3+
class PythonNodeTypes : MetricNodeTypes {
4+
override val complexityNodeTypes = TreeNodeTypes(
5+
simpleNodeTypes = setOf(
6+
// if
7+
"if_statement",
8+
"elif_clause",
9+
"if_clause",
10+
// loop
11+
"for_statement",
12+
"while_statement",
13+
"for_in_clause",
14+
// conditional
15+
"conditional_expression",
16+
"list",
17+
// logical binary
18+
"boolean_operator",
19+
// case label
20+
"case_pattern",
21+
// catch block
22+
"except_clause",
23+
// function
24+
"function_definition",
25+
"lambda"
26+
)
27+
)
28+
29+
// in python unassigned strings are used as block comments, meaning an expression that only has string as a child
30+
override val commentLineNodeTypes = TreeNodeTypes(
31+
simpleNodeTypes = setOf(
32+
"comment"
33+
),
34+
nestedNodeTypes = setOf(
35+
NestedNodeType(
36+
baseNodeType = "expression_statement",
37+
childNodeCount = 1,
38+
childNodePosition = 0,
39+
childNodeTypes = setOf("string")
40+
)
41+
)
42+
)
43+
}

analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/PythonQueries.kt

Lines changed: 0 additions & 37 deletions
This file was deleted.

analysis/analysers/parsers/UnifiedParser/src/test/resources/kotlinSample.cc.json renamed to analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/kotlinSample.cc.json

File renamed without changes.

analysis/analysers/parsers/UnifiedParser/src/test/resources/kotlinSample.kt renamed to analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/kotlinSample.kt

File renamed without changes.

0 commit comments

Comments
 (0)