From 1e3c3bd213578dbc1428d14255d7860ae84fddb5 Mon Sep 17 00:00:00 2001 From: nereboss Date: Wed, 18 Jun 2025 10:15:05 +0200 Subject: [PATCH 01/10] feat(analysis): add python support to unifiedParser #4092 --- .../parsers/UnifiedParser/build.gradle.kts | 1 + .../metriccollectors/PythonCollector.kt | 9 ++ .../unified/metricnodetypes/PythonQueries.kt | 35 ++++++ .../parsers/unified/UnifiedParserTest.kt | 3 +- .../src/test/resources/pythonSample.cc.json | 66 +++++++++++ .../src/test/resources/pythonSample.py | 104 ++++++++++++++++++ analysis/gradle/libs.versions.toml | 2 + 7 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollector.kt create mode 100644 analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/PythonQueries.kt create mode 100644 analysis/analysers/parsers/UnifiedParser/src/test/resources/pythonSample.cc.json create mode 100644 analysis/analysers/parsers/UnifiedParser/src/test/resources/pythonSample.py diff --git a/analysis/analysers/parsers/UnifiedParser/build.gradle.kts b/analysis/analysers/parsers/UnifiedParser/build.gradle.kts index a2a9c09768..1a3ef9ee3f 100644 --- a/analysis/analysers/parsers/UnifiedParser/build.gradle.kts +++ b/analysis/analysers/parsers/UnifiedParser/build.gradle.kts @@ -14,6 +14,7 @@ dependencies { implementation(libs.treesitter.kotlin) implementation(libs.treesitter.java) implementation(libs.treesitter.csharp) + implementation(libs.treesitter.python) testImplementation(libs.jsonassert) } diff --git a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollector.kt b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollector.kt new file mode 100644 index 0000000000..7dbfc20b28 --- /dev/null +++ b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollector.kt @@ -0,0 +1,9 @@ +package de.maibornwolff.codecharta.analysers.parsers.unified.metriccollectors + +import de.maibornwolff.codecharta.analysers.parsers.unified.metricqueries.PythonQueries +import org.treesitter.TreeSitterPython + +class PythonCollector : MetricCollector( + treeSitterLanguage = TreeSitterPython(), + queryProvider = PythonQueries() +) diff --git a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/PythonQueries.kt b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/PythonQueries.kt new file mode 100644 index 0000000000..426457e616 --- /dev/null +++ b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/PythonQueries.kt @@ -0,0 +1,35 @@ +package de.maibornwolff.codecharta.analysers.parsers.unified.metricqueries + +class PythonQueries : MetricQueries { + companion object { + private val complexityNodes = listOf( + //if + "if_statement", + "elif_clause", + "if_clause", + // loop + "for_statement", + "while_statement", + "for_in_clause", + // conditional + "conditional_expression", + "list", //in MG deactivated TODO: warum? + "boolean_operator", + //logical binary + //case label + "case_pattern", + //catch block + "except_clause", + //function + "function_definition", + "lambda", + ) + + private val commentNodes = listOf( + "comment" + ) + } + + override val complexityQuery = buildQuery(AvailableMetrics.COMPLEXITY, complexityNodes) + override val commentLinesQuery = buildQuery(AvailableMetrics.COMMENT_LINES, commentNodes) +} diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/UnifiedParserTest.kt b/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/UnifiedParserTest.kt index 7e23635ecc..68e6a91932 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/UnifiedParserTest.kt +++ b/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/UnifiedParserTest.kt @@ -42,7 +42,8 @@ class UnifiedParserTest { Arguments.of("javascript", ".js"), Arguments.of("java", ".java"), Arguments.of("kotlin", ".kt"), - Arguments.of("cSharp", ".cs") + Arguments.of("cSharp", ".cs"), + Arguments.of("python", ".py") ) @ParameterizedTest diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/pythonSample.cc.json b/analysis/analysers/parsers/UnifiedParser/src/test/resources/pythonSample.cc.json new file mode 100644 index 0000000000..d006d06fec --- /dev/null +++ b/analysis/analysers/parsers/UnifiedParser/src/test/resources/pythonSample.cc.json @@ -0,0 +1,66 @@ +{ + "checksum": "89dc48a5569a49c48d520b6e88b13f09", + "data": { + "projectName": "", + "nodes": [ + { + "name": "root", + "type": "Folder", + "attributes": {}, + "link": "", + "children": [ + { + "name": "pythonSample.py", + "type": "File", + "attributes": { + "complexity": 12, + "comment_lines": 1, + "loc": 104, + "rloc": 92 + }, + "link": "", + "children": [] + } + ] + } + ], + "apiVersion": "1.3", + "edges": [], + "attributeTypes": {}, + "attributeDescriptors": { + "complexity": { + "title": "Complexity", + "description": "Complexity of the file based on the number of paths through the code (McCabe Complexity)", + "hintLowValue": "", + "hintHighValue": "", + "link": "https://en.wikipedia.org/wiki/Cyclomatic_complexity", + "direction": -1 + }, + "comment_lines": { + "title": "Comment lines", + "description": "Number of lines containing either a comment or commented-out code", + "hintLowValue": "", + "hintHighValue": "", + "link": "https://codecharta.com/docs/parser/unified", + "direction": -1 + }, + "loc": { + "title": "Lines of Code", + "description": "Lines of code including empty lines and comments", + "hintLowValue": "", + "hintHighValue": "", + "link": "https://codecharta.com/docs/parser/unified", + "direction": -1 + }, + "rloc": { + "title": "Real Lines of Code", + "description": "Number of lines that contain at least one character which is neither a whitespace nor a tabulation nor part of a comment", + "hintLowValue": "", + "hintHighValue": "", + "link": "https://codecharta.com/docs/parser/unified", + "direction": -1 + } + }, + "blacklist": [] + } +} diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/pythonSample.py b/analysis/analysers/parsers/UnifiedParser/src/test/resources/pythonSample.py new file mode 100644 index 0000000000..192ded1aef --- /dev/null +++ b/analysis/analysers/parsers/UnifiedParser/src/test/resources/pythonSample.py @@ -0,0 +1,104 @@ +""" +User service module for managing user operations +""" +from src.models.entities.user import User +from src.utils.helpers import hash_password + + +class UserService: + """Service for user-related operations""" + + def __init__(self): + """Initialize user service with empty user store""" + self.users = {} # username -> User + + def register_user(self, username, email, password, first_name=None, last_name=None): + """ + Register a new user + + Args: + username (str): Username for the new user + email (str): Email address + password (str): User password + first_name (str, optional): User's first name + last_name (str, optional): User's last name + + Returns: + User: The created user object + + Raises: + ValueError: If username already exists + """ + if username in self.users or username == "admin": + raise ValueError(f"Username '{username}' already exists") + + user = User(username, email, password, first_name, last_name) + self.users[username] = user + return user + + def authenticate(self, username, password): + """ + Authenticate a user with username and password + + Args: + username (str): Username to authenticate + password (str): Password to verify + + Returns: + User: Authenticated user if successful, None otherwise + """ + if username not in self.users: + return None + + user = self.users[username] + if user.password_hash == hash_password(password): + user.record_login() + return user + + return None + + def get_user(self, username): + """ + Get user by username + + Args: + username (str): Username to look up + + Returns: + User: User object if found, None otherwise + """ + return self.users.get(username) + + def update_user_password(self, username, old_password, new_password): + """ + Update user password + + Args: + username (str): Username of account to update + old_password (str): Current password for verification + new_password (str): New password to set + + Returns: + bool: True if update successful, False otherwise + """ + user = self.authenticate(username, old_password) + if not user: + return False + + return user.update_password(new_password) + + def deactivate_user(self, username): + """ + Deactivate a user account + + Args: + username (str): Username to deactivate + + Returns: + bool: True if deactivated, False if user not found + """ + user = self.get_user(username) + if user: + user.deactivate() + return True + return False diff --git a/analysis/gradle/libs.versions.toml b/analysis/gradle/libs.versions.toml index 23c1520db2..90648d9f89 100644 --- a/analysis/gradle/libs.versions.toml +++ b/analysis/gradle/libs.versions.toml @@ -33,6 +33,7 @@ tree-sitter-javascript = "0.23.1" tree-sitter-kotlin = "0.3.8.1" tree-sitter-java = "0.23.4" tree-sitter-csharp = "0.23.1" +tree-sitter-python = "0.23.4" [libraries] kotlin-logging-jvm = { group = "io.github.oshai", name = "kotlin-logging-jvm", version.ref = "kotlin-logging" } @@ -77,6 +78,7 @@ treesitter-kotlin = { group = "io.github.bonede", name = "tree-sitter-kotlin", v treesitter-java = { group = "io.github.bonede", name = "tree-sitter-java", version.ref = "tree-sitter-java"} treesitter-csharp = { group = "io.github.bonede", name = "tree-sitter-c-sharp", version.ref = "tree-sitter-csharp"} +treesitter-python = { group = "io.github.bonede", name = "tree-sitter-python", version.ref = "tree-sitter-python"} [plugins] sonarqube = { id = "org.sonarqube", version.ref = "sonarqube" } From a54559a92e388773ae3353e9bf1dc4136d25a5d7 Mon Sep 17 00:00:00 2001 From: nereboss Date: Tue, 24 Jun 2025 12:17:05 +0200 Subject: [PATCH 02/10] feat(analysis): adjust calculation of rloc for python #4092 --- .../metriccollectors/MetricCollector.kt | 1 + .../metriccollectors/PythonCollector.kt | 81 ++++++++++++++++++- .../unified/metricnodetypes/PythonQueries.kt | 18 +++-- .../parsers/unified/UnifiedParserTest.kt | 1 + .../src/test/resources/includeAll.cc.json | 14 +++- .../src/test/resources/mergeResult.cc.json | 14 +++- .../src/test/resources/pythonSample.cc.json | 6 +- .../src/test/resources/sampleProject.cc.json | 14 +++- .../test/resources/typescriptSample.cc.json | 4 +- 9 files changed, 136 insertions(+), 17 deletions(-) diff --git a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/MetricCollector.kt b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/MetricCollector.kt index e2bcc705ea..ad1c29f7b3 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/MetricCollector.kt +++ b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/MetricCollector.kt @@ -18,6 +18,7 @@ abstract class MetricCollector( ) { private var lastCountedCommentLine = -1 private var lastCountedCodeLine = -1 + private var rootNodeType: String = "" //TODO: schauen wie wir für alle kinder vom rootnode berechnen // maps a metric to its index for the more performant IntArray and the function how to calculate the metric private val metricInfo = mapOf( diff --git a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollector.kt b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollector.kt index 7dbfc20b28..e7f514acfb 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollector.kt +++ b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollector.kt @@ -1,9 +1,88 @@ package de.maibornwolff.codecharta.analysers.parsers.unified.metriccollectors import de.maibornwolff.codecharta.analysers.parsers.unified.metricqueries.PythonQueries +import org.treesitter.TSNode +import org.treesitter.TSTreeCursor import org.treesitter.TreeSitterPython class PythonCollector : MetricCollector( treeSitterLanguage = TreeSitterPython(), queryProvider = PythonQueries() -) +) { + override fun getRealLinesOfCode(root: TSNode): Int { + if (root.childCount == 0) return 0 + + rootNodeType = root.type + val commentTypes = getParentAndChildNodeTypesFromQuery(queryProvider.commentLinesQuery) + return walkTree(TSTreeCursor(root), commentTypes) + } + + private fun walkTree(cursor: TSTreeCursor, commentTypes: List>): Int { + var realLinesOfCode = 0 + val currentNode = cursor.currentNode() + + if (!isCommentNode(currentNode, commentTypes)) { + if (currentNode.startPoint.row > lastCountedLine && + currentNode.type != rootNodeType && + !areAllChildrenInLineCommentNodes(currentNode, currentNode.startPoint.row, commentTypes) + ) { + lastCountedLine = currentNode.startPoint.row + realLinesOfCode++ + } + + if (currentNode.childCount == 0) { + if (currentNode.endPoint.row > lastCountedLine) { + realLinesOfCode += currentNode.endPoint.row - currentNode.startPoint.row + lastCountedLine = currentNode.endPoint.row + } + } else if (currentNode.endPoint.row > currentNode.startPoint.row && cursor.gotoFirstChild()) { + realLinesOfCode += walkTree(cursor, commentTypes) + } + } + + if (cursor.gotoNextSibling()) { + realLinesOfCode += walkTree(cursor, commentTypes) + } else { + cursor.gotoParent() + } + + return realLinesOfCode + } + + private fun getParentAndChildNodeTypesFromQuery(query: String): List> { + val regex = Regex("""\((.*?)\)\s*@""", RegexOption.MULTILINE) + val commentNodeTypes = regex.findAll(query).map { it.groupValues[1] }.toList() + + val parentToChildTypes = commentNodeTypes.mapNotNull { + val match = Regex("""(\w+)(?:\s*\((\w+)\))?""").find(it) + match?.let { m -> m.groupValues[1] to m.groupValues.getOrNull(2) } + } + return parentToChildTypes + } + + private fun isCommentNode(node: TSNode, commentTypes: List>): Boolean { + for ((parentType, childType) in commentTypes) { + if (childType.isNullOrBlank() && node.type == parentType) { + return true + } else if (node.type == parentType && node.childCount == 1 && node.getChild(0).type == childType) { + return true + } + } + return false + } + + private fun areAllChildrenInLineCommentNodes(node: TSNode, line: Int, commentTypes: List>): Boolean { + val lookAheadCursor = TSTreeCursor(node) + if (lookAheadCursor.gotoFirstChild()) { + do { + val currentNode = lookAheadCursor.currentNode() + require( + currentNode.startPoint.row >= line + ) { "Malformed tree detected, child node start line comes before parent node start like!" } + if (currentNode.startPoint.row > line) return true + if (!isCommentNode(currentNode, commentTypes)) return false + } while (lookAheadCursor.gotoNextSibling()) + } + return true + } +} diff --git a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/PythonQueries.kt b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/PythonQueries.kt index 426457e616..e0f816e5a8 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/PythonQueries.kt +++ b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/PythonQueries.kt @@ -3,7 +3,7 @@ package de.maibornwolff.codecharta.analysers.parsers.unified.metricqueries class PythonQueries : MetricQueries { companion object { private val complexityNodes = listOf( - //if + // if "if_statement", "elif_clause", "if_clause", @@ -13,20 +13,22 @@ class PythonQueries : MetricQueries { "for_in_clause", // conditional "conditional_expression", - "list", //in MG deactivated TODO: warum? + "list", "boolean_operator", - //logical binary - //case label + // logical binary + // case label "case_pattern", - //catch block + // catch block "except_clause", - //function + // function "function_definition", - "lambda", + "lambda" ) + // in python unassigned strings are used as block comments, meaning an expression that only has string as a child private val commentNodes = listOf( - "comment" + "comment", + "expression_statement (string)" ) } diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/UnifiedParserTest.kt b/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/UnifiedParserTest.kt index 68e6a91932..04a526f294 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/UnifiedParserTest.kt +++ b/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/UnifiedParserTest.kt @@ -136,6 +136,7 @@ class UnifiedParserTest { "bar/hello.kt", "bar/foo.kt", "foo.kt", + "foo.py", "whenCase.kt", "helloWorld.ts" ) diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/includeAll.cc.json b/analysis/analysers/parsers/UnifiedParser/src/test/resources/includeAll.cc.json index e0837414a2..8c35f7ff49 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/test/resources/includeAll.cc.json +++ b/analysis/analysers/parsers/UnifiedParser/src/test/resources/includeAll.cc.json @@ -1,5 +1,5 @@ { - "checksum": "ff9abfcd28324a978ef8b8f7da6a338a", + "checksum": "2e59a5a836cc7dc4d23481bb8d3213ec", "data": { "projectName": "", "nodes": [ @@ -73,6 +73,18 @@ "link": "", "children": [] }, + { + "name": "foo.py", + "type": "File", + "attributes": { + "complexity": 2, + "comment_lines": 4, + "loc": 14, + "rloc": 9 + }, + "link": "", + "children": [] + }, { "name": "helloWorld.ts", "type": "File", diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/mergeResult.cc.json b/analysis/analysers/parsers/UnifiedParser/src/test/resources/mergeResult.cc.json index 268c0394dc..7654df1235 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/test/resources/mergeResult.cc.json +++ b/analysis/analysers/parsers/UnifiedParser/src/test/resources/mergeResult.cc.json @@ -1,5 +1,5 @@ { - "checksum": "85ec89e6fa8d6d561d51df81ae987e30", + "checksum": "4c2c1b4d1165c6e1010c827cefa9b3d7", "data": { "projectName": "", "nodes": [ @@ -71,6 +71,18 @@ "link": "", "children": [] }, + { + "name": "foo.py", + "type": "File", + "attributes": { + "complexity": 2, + "comment_lines": 4, + "loc": 14, + "rloc": 9 + }, + "link": "", + "children": [] + }, { "name": "helloWorld.ts", "type": "File", diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/pythonSample.cc.json b/analysis/analysers/parsers/UnifiedParser/src/test/resources/pythonSample.cc.json index d006d06fec..dc3a3b3c31 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/test/resources/pythonSample.cc.json +++ b/analysis/analysers/parsers/UnifiedParser/src/test/resources/pythonSample.cc.json @@ -1,5 +1,5 @@ { - "checksum": "89dc48a5569a49c48d520b6e88b13f09", + "checksum": "d39de639c38fb69dc5e7e5220d3b54a9", "data": { "projectName": "", "nodes": [ @@ -14,9 +14,9 @@ "type": "File", "attributes": { "complexity": 12, - "comment_lines": 1, + "comment_lines": 61, "loc": 104, - "rloc": 92 + "rloc": 32 }, "link": "", "children": [] diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/sampleProject.cc.json b/analysis/analysers/parsers/UnifiedParser/src/test/resources/sampleProject.cc.json index cef849503e..6d1b01cae8 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/test/resources/sampleProject.cc.json +++ b/analysis/analysers/parsers/UnifiedParser/src/test/resources/sampleProject.cc.json @@ -1,5 +1,5 @@ { - "checksum": "48c91eb854bb71c773cc9a48d9e918ff", + "checksum": "320b420c79ad382ecc31f23e8118fcec", "data": { "projectName": "", "nodes": [ @@ -53,6 +53,18 @@ "link": "", "children": [] }, + { + "name": "foo.py", + "type": "File", + "attributes": { + "complexity": 2, + "comment_lines": 4, + "loc": 14, + "rloc": 9 + }, + "link": "", + "children": [] + }, { "name": "helloWorld.ts", "type": "File", diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/typescriptSample.cc.json b/analysis/analysers/parsers/UnifiedParser/src/test/resources/typescriptSample.cc.json index 37c6b1cb6d..cbba3ce4c6 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/test/resources/typescriptSample.cc.json +++ b/analysis/analysers/parsers/UnifiedParser/src/test/resources/typescriptSample.cc.json @@ -1,5 +1,5 @@ { - "checksum": "cba50e4765cefed97feacc767e7f1d6b", + "checksum": "bd1dc239a00b5fc494324112744118d4", "data": { "projectName": "", "nodes": [ @@ -16,7 +16,7 @@ "complexity": 32, "comment_lines": 61, "loc": 200, - "rloc": 115 + "rloc": 114 }, "link": "", "children": [] From 7b5f35eb1d5071b5efb0c2ccfd707ba279a115e2 Mon Sep 17 00:00:00 2001 From: nereboss Date: Wed, 25 Jun 2025 10:59:20 +0200 Subject: [PATCH 03/10] test(analysis): adjust javascriptSample with correct rloc #4095 --- .../codecharta/analysers/parsers/unified/UnifiedParserTest.kt | 4 ++-- .../test/resources/{ => languageSamples}/cSharpSample.cc.json | 0 .../src/test/resources/{ => languageSamples}/cSharpSample.cs | 0 .../test/resources/{ => languageSamples}/javaSample.cc.json | 0 .../src/test/resources/{ => languageSamples}/javaSample.java | 0 .../resources/{ => languageSamples}/javascriptSample.cc.json | 3 ++- .../test/resources/{ => languageSamples}/javascriptSample.js | 0 .../test/resources/{ => languageSamples}/pythonSample.cc.json | 0 .../src/test/resources/{ => languageSamples}/pythonSample.py | 0 .../resources/{ => languageSamples}/typescriptSample.cc.json | 0 .../test/resources/{ => languageSamples}/typescriptSample.ts | 0 11 files changed, 4 insertions(+), 3 deletions(-) rename analysis/analysers/parsers/UnifiedParser/src/test/resources/{ => languageSamples}/cSharpSample.cc.json (100%) rename analysis/analysers/parsers/UnifiedParser/src/test/resources/{ => languageSamples}/cSharpSample.cs (100%) rename analysis/analysers/parsers/UnifiedParser/src/test/resources/{ => languageSamples}/javaSample.cc.json (100%) rename analysis/analysers/parsers/UnifiedParser/src/test/resources/{ => languageSamples}/javaSample.java (100%) rename analysis/analysers/parsers/UnifiedParser/src/test/resources/{ => languageSamples}/javascriptSample.cc.json (96%) rename analysis/analysers/parsers/UnifiedParser/src/test/resources/{ => languageSamples}/javascriptSample.js (100%) rename analysis/analysers/parsers/UnifiedParser/src/test/resources/{ => languageSamples}/pythonSample.cc.json (100%) rename analysis/analysers/parsers/UnifiedParser/src/test/resources/{ => languageSamples}/pythonSample.py (100%) rename analysis/analysers/parsers/UnifiedParser/src/test/resources/{ => languageSamples}/typescriptSample.cc.json (100%) rename analysis/analysers/parsers/UnifiedParser/src/test/resources/{ => languageSamples}/typescriptSample.ts (100%) diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/UnifiedParserTest.kt b/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/UnifiedParserTest.kt index 04a526f294..e35dcee705 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/UnifiedParserTest.kt +++ b/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/UnifiedParserTest.kt @@ -51,8 +51,8 @@ class UnifiedParserTest { fun `Should produce correct output for a single source file of each supported language`(language: String, fileExtension: String) { // given val pipedProject = "" - val inputFilePath = "${testResourceBaseFolder}${language}Sample$fileExtension" - val expectedResultFile = File("${testResourceBaseFolder}${language}Sample.cc.json") + val inputFilePath = "${testResourceBaseFolder}languageSamples/${language}Sample$fileExtension" + val expectedResultFile = File("${testResourceBaseFolder}languageSamples/${language}Sample.cc.json") // when val result = executeForOutput(pipedProject, arrayOf(inputFilePath)) diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/cSharpSample.cc.json b/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/cSharpSample.cc.json similarity index 100% rename from analysis/analysers/parsers/UnifiedParser/src/test/resources/cSharpSample.cc.json rename to analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/cSharpSample.cc.json diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/cSharpSample.cs b/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/cSharpSample.cs similarity index 100% rename from analysis/analysers/parsers/UnifiedParser/src/test/resources/cSharpSample.cs rename to analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/cSharpSample.cs diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/javaSample.cc.json b/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/javaSample.cc.json similarity index 100% rename from analysis/analysers/parsers/UnifiedParser/src/test/resources/javaSample.cc.json rename to analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/javaSample.cc.json diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/javaSample.java b/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/javaSample.java similarity index 100% rename from analysis/analysers/parsers/UnifiedParser/src/test/resources/javaSample.java rename to analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/javaSample.java diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/javascriptSample.cc.json b/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/javascriptSample.cc.json similarity index 96% rename from analysis/analysers/parsers/UnifiedParser/src/test/resources/javascriptSample.cc.json rename to analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/javascriptSample.cc.json index 2a25d4957e..99d8dab191 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/test/resources/javascriptSample.cc.json +++ b/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/javascriptSample.cc.json @@ -1,4 +1,5 @@ { + "checksum": "1a6e609d76f9b178c4fc56c58e1ddf6f", "checksum": "d65e9fd093e285977c0fd3b66de6ff4d", "data": { "projectName": "", @@ -16,7 +17,7 @@ "complexity": 33, "comment_lines": 57, "loc": 191, - "rloc": 110 + "rloc": 109 }, "link": "", "children": [] diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/javascriptSample.js b/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/javascriptSample.js similarity index 100% rename from analysis/analysers/parsers/UnifiedParser/src/test/resources/javascriptSample.js rename to analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/javascriptSample.js diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/pythonSample.cc.json b/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/pythonSample.cc.json similarity index 100% rename from analysis/analysers/parsers/UnifiedParser/src/test/resources/pythonSample.cc.json rename to analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/pythonSample.cc.json diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/pythonSample.py b/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/pythonSample.py similarity index 100% rename from analysis/analysers/parsers/UnifiedParser/src/test/resources/pythonSample.py rename to analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/pythonSample.py diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/typescriptSample.cc.json b/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/typescriptSample.cc.json similarity index 100% rename from analysis/analysers/parsers/UnifiedParser/src/test/resources/typescriptSample.cc.json rename to analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/typescriptSample.cc.json diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/typescriptSample.ts b/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/typescriptSample.ts similarity index 100% rename from analysis/analysers/parsers/UnifiedParser/src/test/resources/typescriptSample.ts rename to analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/typescriptSample.ts From 759ae941de89ca3a0991873e684042475574049d Mon Sep 17 00:00:00 2001 From: nereboss Date: Thu, 26 Jun 2025 17:00:12 +0200 Subject: [PATCH 04/10] fix(analysis): fixed a bug that rloc was incorrect when function parameters were in next line #4092 --- .../parsers/unified/metriccollectors/PythonCollector.kt | 2 ++ .../test/resources/languageSamples/pythonSample.cc.json | 6 +++--- .../src/test/resources/languageSamples/pythonSample.py | 8 +++++++- gh-pages/_docs/01-overview/01-introduction.md | 2 +- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollector.kt b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollector.kt index e7f514acfb..0ce0348bf3 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollector.kt +++ b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollector.kt @@ -72,6 +72,8 @@ class PythonCollector : MetricCollector( } private fun areAllChildrenInLineCommentNodes(node: TSNode, line: Int, commentTypes: List>): Boolean { + if (node.childCount == 0) return isCommentNode(node, commentTypes) + val lookAheadCursor = TSTreeCursor(node) if (lookAheadCursor.gotoFirstChild()) { do { diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/pythonSample.cc.json b/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/pythonSample.cc.json index dc3a3b3c31..839b8b4735 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/pythonSample.cc.json +++ b/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/pythonSample.cc.json @@ -1,5 +1,5 @@ { - "checksum": "d39de639c38fb69dc5e7e5220d3b54a9", + "checksum": "5f0f11de52d3447ee8ea6b248e72502f", "data": { "projectName": "", "nodes": [ @@ -15,8 +15,8 @@ "attributes": { "complexity": 12, "comment_lines": 61, - "loc": 104, - "rloc": 32 + "loc": 110, + "rloc": 38 }, "link": "", "children": [] diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/pythonSample.py b/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/pythonSample.py index 192ded1aef..7242cb5b6c 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/pythonSample.py +++ b/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/pythonSample.py @@ -32,7 +32,13 @@ def register_user(self, username, email, password, first_name=None, last_name=No if username in self.users or username == "admin": raise ValueError(f"Username '{username}' already exists") - user = User(username, email, password, first_name, last_name) + user = User( + username, + email, + password, + first_name, + last_name + ) self.users[username] = user return user diff --git a/gh-pages/_docs/01-overview/01-introduction.md b/gh-pages/_docs/01-overview/01-introduction.md index 5d209652fa..12a1a50a13 100644 --- a/gh-pages/_docs/01-overview/01-introduction.md +++ b/gh-pages/_docs/01-overview/01-introduction.md @@ -14,7 +14,7 @@ CodeCharta is a **communication tool** that helps you understand and manage comp It turns metrics and your code base into a **city-like map** and makes your code **tangible**! A map where you can move around and find hotspots where your team has always had problems. So you can finally tackle the problems that have always been on your mind. But never talked about. -# Why did we develope CodeCharta? +# Why did we develop CodeCharta? CodeCharta was mainly developed by MaibornWolff to help us with our [Software Health Checks](https://www.maibornwolff.de/en/service/software-health-check/). We needed something to help us find potential issues to discuss with customers or to show the management where their teams are struggling. From 8399c5f05a07fb06e90684e610d7ee3b540911f2 Mon Sep 17 00:00:00 2001 From: nereboss Date: Thu, 17 Jul 2025 12:59:06 +0200 Subject: [PATCH 05/10] fix(analysis): fix rloc sometimes being off by one #4092 --- .../metriccollectors/AvailableCollectors.kt | 3 +- .../metriccollectors/MetricCollector.kt | 25 ++++-- .../metriccollectors/PythonCollector.kt | 87 +------------------ .../metricnodetypes/MetricNodeTypes.kt | 4 +- .../metricnodetypes/PythonNodeTypes.kt | 43 +++++++++ .../unified/metricnodetypes/PythonQueries.kt | 37 -------- .../kotlinSample.cc.json | 0 .../{ => languageSamples}/kotlinSample.kt | 0 8 files changed, 69 insertions(+), 130 deletions(-) create mode 100644 analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/PythonNodeTypes.kt delete mode 100644 analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/PythonQueries.kt rename analysis/analysers/parsers/UnifiedParser/src/test/resources/{ => languageSamples}/kotlinSample.cc.json (100%) rename analysis/analysers/parsers/UnifiedParser/src/test/resources/{ => languageSamples}/kotlinSample.kt (100%) diff --git a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/AvailableCollectors.kt b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/AvailableCollectors.kt index 19546e258a..c8d3ea01b9 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/AvailableCollectors.kt +++ b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/AvailableCollectors.kt @@ -10,5 +10,6 @@ enum class AvailableCollectors( JAVASCRIPT(FileExtension.JAVASCRIPT, ::JavascriptCollector), KOTLIN(FileExtension.KOTLIN, ::KotlinCollector), JAVA(FileExtension.JAVA, ::JavaCollector), - CSHARP(FileExtension.CSHARP, ::CSharpCollector) + CSHARP(FileExtension.CSHARP, ::CSharpCollector), + PYTHON(FileExtension.PYTHON, ::PythonCollector) } diff --git a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/MetricCollector.kt b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/MetricCollector.kt index ad1c29f7b3..168e4d2f7d 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/MetricCollector.kt +++ b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/MetricCollector.kt @@ -18,7 +18,7 @@ abstract class MetricCollector( ) { private var lastCountedCommentLine = -1 private var lastCountedCodeLine = -1 - private var rootNodeType: String = "" //TODO: schauen wie wir für alle kinder vom rootnode berechnen + private var rootNodeType: String = "" // maps a metric to its index for the more performant IntArray and the function how to calculate the metric private val metricInfo = mapOf( @@ -58,7 +58,9 @@ abstract class MetricCollector( private fun getRootNode(file: File): TSNode { val parser = TSParser() parser.setLanguage(treeSitterLanguage) - return parser.parseString(null, file.readText()).rootNode + val rootNode = parser.parseString(null, file.readText()).rootNode + rootNodeType = rootNode.type + return rootNode } private val walkTree = DeepRecursiveFunction, Unit> { (cursor, metrics) -> @@ -68,9 +70,11 @@ abstract class MetricCollector( val startRow = currentNode.startPoint.row val endRow = currentNode.endPoint.row - for ((_, indexAndCalculateMetricForNodeFn) in metricInfo) { - val (index, calculateMetricForNodeFn) = indexAndCalculateMetricForNodeFn - metrics[index] += calculateMetricForNodeFn(currentNode, nodeType, startRow, endRow) + if (nodeType != rootNodeType) { + for ((_, indexAndCalculateMetricForNodeFn) in metricInfo) { + val (index, calculateMetricForNodeFn) = indexAndCalculateMetricForNodeFn + metrics[index] += calculateMetricForNodeFn(currentNode, nodeType, startRow, endRow) + } } if (cursor.gotoFirstChild()) { @@ -134,8 +138,15 @@ abstract class MetricCollector( private fun isNestedTypeAllowed(node: TSNode, nodeType: String, nestedTypes: Set): Boolean { for (nestedType in nestedTypes) { if (nestedType.baseNodeType == nodeType) { - val childNode = node.getChildByFieldName(nestedType.childNodeFieldName) - if (nestedType.childNodeTypes.contains(childNode.type)) return true + if (nestedType.childNodePosition != null && + nestedType.childNodeCount == node.childCount + ) { + val childNode = node.getChild(nestedType.childNodePosition) + if (nestedType.childNodeTypes.contains(childNode.type)) return true + } else if (nestedType.childNodeFieldName != null) { + val childNode = node.getChildByFieldName(nestedType.childNodeFieldName) + if (nestedType.childNodeTypes.contains(childNode.type)) return true + } } } return false diff --git a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollector.kt b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollector.kt index 0ce0348bf3..6605c9d6da 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollector.kt +++ b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollector.kt @@ -1,90 +1,9 @@ package de.maibornwolff.codecharta.analysers.parsers.unified.metriccollectors -import de.maibornwolff.codecharta.analysers.parsers.unified.metricqueries.PythonQueries -import org.treesitter.TSNode -import org.treesitter.TSTreeCursor +import de.maibornwolff.codecharta.analysers.parsers.unified.metricnodetypes.PythonNodeTypes import org.treesitter.TreeSitterPython class PythonCollector : MetricCollector( treeSitterLanguage = TreeSitterPython(), - queryProvider = PythonQueries() -) { - override fun getRealLinesOfCode(root: TSNode): Int { - if (root.childCount == 0) return 0 - - rootNodeType = root.type - val commentTypes = getParentAndChildNodeTypesFromQuery(queryProvider.commentLinesQuery) - return walkTree(TSTreeCursor(root), commentTypes) - } - - private fun walkTree(cursor: TSTreeCursor, commentTypes: List>): Int { - var realLinesOfCode = 0 - val currentNode = cursor.currentNode() - - if (!isCommentNode(currentNode, commentTypes)) { - if (currentNode.startPoint.row > lastCountedLine && - currentNode.type != rootNodeType && - !areAllChildrenInLineCommentNodes(currentNode, currentNode.startPoint.row, commentTypes) - ) { - lastCountedLine = currentNode.startPoint.row - realLinesOfCode++ - } - - if (currentNode.childCount == 0) { - if (currentNode.endPoint.row > lastCountedLine) { - realLinesOfCode += currentNode.endPoint.row - currentNode.startPoint.row - lastCountedLine = currentNode.endPoint.row - } - } else if (currentNode.endPoint.row > currentNode.startPoint.row && cursor.gotoFirstChild()) { - realLinesOfCode += walkTree(cursor, commentTypes) - } - } - - if (cursor.gotoNextSibling()) { - realLinesOfCode += walkTree(cursor, commentTypes) - } else { - cursor.gotoParent() - } - - return realLinesOfCode - } - - private fun getParentAndChildNodeTypesFromQuery(query: String): List> { - val regex = Regex("""\((.*?)\)\s*@""", RegexOption.MULTILINE) - val commentNodeTypes = regex.findAll(query).map { it.groupValues[1] }.toList() - - val parentToChildTypes = commentNodeTypes.mapNotNull { - val match = Regex("""(\w+)(?:\s*\((\w+)\))?""").find(it) - match?.let { m -> m.groupValues[1] to m.groupValues.getOrNull(2) } - } - return parentToChildTypes - } - - private fun isCommentNode(node: TSNode, commentTypes: List>): Boolean { - for ((parentType, childType) in commentTypes) { - if (childType.isNullOrBlank() && node.type == parentType) { - return true - } else if (node.type == parentType && node.childCount == 1 && node.getChild(0).type == childType) { - return true - } - } - return false - } - - private fun areAllChildrenInLineCommentNodes(node: TSNode, line: Int, commentTypes: List>): Boolean { - if (node.childCount == 0) return isCommentNode(node, commentTypes) - - val lookAheadCursor = TSTreeCursor(node) - if (lookAheadCursor.gotoFirstChild()) { - do { - val currentNode = lookAheadCursor.currentNode() - require( - currentNode.startPoint.row >= line - ) { "Malformed tree detected, child node start line comes before parent node start like!" } - if (currentNode.startPoint.row > line) return true - if (!isCommentNode(currentNode, commentTypes)) return false - } while (lookAheadCursor.gotoNextSibling()) - } - return true - } -} + queryProvider = PythonNodeTypes() +) diff --git a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/MetricNodeTypes.kt b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/MetricNodeTypes.kt index 3a7b797a58..75ac4488e2 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/MetricNodeTypes.kt +++ b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/MetricNodeTypes.kt @@ -21,6 +21,8 @@ class TreeNodeTypes( class NestedNodeType( val baseNodeType: String, - val childNodeFieldName: String?, + val childNodeFieldName: String? = null, + val childNodeCount: Int? = null, + val childNodePosition: Int? = null, val childNodeTypes: Set ) diff --git a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/PythonNodeTypes.kt b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/PythonNodeTypes.kt new file mode 100644 index 0000000000..8b4dcbabf6 --- /dev/null +++ b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/PythonNodeTypes.kt @@ -0,0 +1,43 @@ +package de.maibornwolff.codecharta.analysers.parsers.unified.metricnodetypes + +class PythonNodeTypes : MetricNodeTypes { + override val complexityNodeTypes = TreeNodeTypes( + simpleNodeTypes = setOf( + // if + "if_statement", + "elif_clause", + "if_clause", + // loop + "for_statement", + "while_statement", + "for_in_clause", + // conditional + "conditional_expression", + "list", + // logical binary + "boolean_operator", + // case label + "case_pattern", + // catch block + "except_clause", + // function + "function_definition", + "lambda" + ) + ) + + // in python unassigned strings are used as block comments, meaning an expression that only has string as a child + override val commentLineNodeTypes = TreeNodeTypes( + simpleNodeTypes = setOf( + "comment" + ), + nestedNodeTypes = setOf( + NestedNodeType( + baseNodeType = "expression_statement", + childNodeCount = 1, + childNodePosition = 0, + childNodeTypes = setOf("string") + ) + ) + ) +} diff --git a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/PythonQueries.kt b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/PythonQueries.kt deleted file mode 100644 index e0f816e5a8..0000000000 --- a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/PythonQueries.kt +++ /dev/null @@ -1,37 +0,0 @@ -package de.maibornwolff.codecharta.analysers.parsers.unified.metricqueries - -class PythonQueries : MetricQueries { - companion object { - private val complexityNodes = listOf( - // if - "if_statement", - "elif_clause", - "if_clause", - // loop - "for_statement", - "while_statement", - "for_in_clause", - // conditional - "conditional_expression", - "list", - "boolean_operator", - // logical binary - // case label - "case_pattern", - // catch block - "except_clause", - // function - "function_definition", - "lambda" - ) - - // in python unassigned strings are used as block comments, meaning an expression that only has string as a child - private val commentNodes = listOf( - "comment", - "expression_statement (string)" - ) - } - - override val complexityQuery = buildQuery(AvailableMetrics.COMPLEXITY, complexityNodes) - override val commentLinesQuery = buildQuery(AvailableMetrics.COMMENT_LINES, commentNodes) -} diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/kotlinSample.cc.json b/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/kotlinSample.cc.json similarity index 100% rename from analysis/analysers/parsers/UnifiedParser/src/test/resources/kotlinSample.cc.json rename to analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/kotlinSample.cc.json diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/kotlinSample.kt b/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/kotlinSample.kt similarity index 100% rename from analysis/analysers/parsers/UnifiedParser/src/test/resources/kotlinSample.kt rename to analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/kotlinSample.kt From 5e7f165557d4f900214e01da513a309b0aaacd3d Mon Sep 17 00:00:00 2001 From: nereboss Date: Thu, 17 Jul 2025 17:01:55 +0200 Subject: [PATCH 06/10] test(analysis): add language specific tests for python #4092 --- .../metriccollectors/PythonCollector.kt | 16 +- .../metricnodetypes/PythonNodeTypes.kt | 14 +- .../parsers/unified/UnifiedParserTest.kt | 12 +- .../metriccollectors/PythonCollectorTest.kt | 188 ++++++++++++++++++ .../src/test/resources/includeAll.cc.json | 2 +- .../languageSamples/javascriptSample.cc.json | 3 +- .../languageSamples/typescriptSample.cc.json | 2 +- .../src/test/resources/mergeResult.cc.json | 2 +- .../src/test/resources/sampleProject.cc.json | 2 +- .../test/resources/sampleproject/empty.json | 0 10 files changed, 225 insertions(+), 16 deletions(-) create mode 100644 analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollectorTest.kt create mode 100644 analysis/analysers/parsers/UnifiedParser/src/test/resources/sampleproject/empty.json diff --git a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollector.kt b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollector.kt index 6605c9d6da..64878df3ea 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollector.kt +++ b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollector.kt @@ -1,9 +1,23 @@ package de.maibornwolff.codecharta.analysers.parsers.unified.metriccollectors import de.maibornwolff.codecharta.analysers.parsers.unified.metricnodetypes.PythonNodeTypes +import org.treesitter.TSNode import org.treesitter.TreeSitterPython class PythonCollector : MetricCollector( treeSitterLanguage = TreeSitterPython(), queryProvider = PythonNodeTypes() -) +) { + override fun calculateRealLinesOfCodeForNode(node: TSNode, nodeType: String, startRow: Int, endRow: Int): Int { + if (isNodePartOfBlockCommentString(nodeType)) return 0 + return super.calculateRealLinesOfCodeForNode(node, nodeType, startRow, endRow) + } + + // TODO: add case of multiline strings + private fun isNodePartOfBlockCommentString(nodeType: String): Boolean { + return nodeType == "string" || + nodeType == "string_start" || + nodeType == "string_content" || + nodeType == "string_end" + } +} diff --git a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/PythonNodeTypes.kt b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/PythonNodeTypes.kt index 8b4dcbabf6..dfb108392d 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/PythonNodeTypes.kt +++ b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/PythonNodeTypes.kt @@ -21,17 +21,25 @@ class PythonNodeTypes : MetricNodeTypes { // catch block "except_clause", // function - "function_definition", - "lambda" + "function_definition" + ), + nestedNodeTypes = setOf( + // lambda needs to be complex to not be counted double as type of first child is also lambda + NestedNodeType( + baseNodeType = "lambda", + childNodePosition = 0, + childNodeCount = 4, + childNodeTypes = setOf("lambda") + ) ) ) - // in python unassigned strings are used as block comments, meaning an expression that only has string as a child override val commentLineNodeTypes = TreeNodeTypes( simpleNodeTypes = setOf( "comment" ), nestedNodeTypes = setOf( + // in python unassigned strings are used as block comments, meaning an expression that only has string as a child NestedNodeType( baseNodeType = "expression_statement", childNodeCount = 1, diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/UnifiedParserTest.kt b/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/UnifiedParserTest.kt index e35dcee705..6b49c56eb0 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/UnifiedParserTest.kt +++ b/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/UnifiedParserTest.kt @@ -38,11 +38,11 @@ class UnifiedParserTest { } private fun provideSupportedLanguages() = listOf( - Arguments.of("typescript", ".ts"), - Arguments.of("javascript", ".js"), - Arguments.of("java", ".java"), - Arguments.of("kotlin", ".kt"), - Arguments.of("cSharp", ".cs"), +// Arguments.of("typescript", ".ts"), +// Arguments.of("javascript", ".js"), +// Arguments.of("java", ".java"), +// Arguments.of("kotlin", ".kt"), +// Arguments.of("cSharp", ".cs"), Arguments.of("python", ".py") ) @@ -168,7 +168,7 @@ class UnifiedParserTest { // then Assertions.assertThat(errContent.toString()).contains( - "2 Files with the following extensions were ignored as they are currently not supported:\n[.strange, .py]" + "2 Files with the following extensions were ignored as they are currently not supported:\n[.json, .strange]" ) // clean up diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollectorTest.kt b/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollectorTest.kt new file mode 100644 index 0000000000..16990bf41b --- /dev/null +++ b/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollectorTest.kt @@ -0,0 +1,188 @@ +package de.maibornwolff.codecharta.analysers.parsers.unified.metriccollectors + +import de.maibornwolff.codecharta.analysers.parsers.unified.metricnodetypes.AvailableMetrics +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.treesitter.TSParser +import org.treesitter.TreeSitterPython +import java.io.File + +class PythonCollectorTest { + private var parser = TSParser() + private val collector = PythonCollector() + + @BeforeEach + fun setUp() { + parser.setLanguage(TreeSitterPython()) + } + + private fun createTestFile(content: String): File { + val tempFile = File.createTempFile("testFile", ".txt") + tempFile.writeText(content) + tempFile.deleteOnExit() + return tempFile + } + + @Test + fun `should count lambda expressions for complexity`() { + // given + val fileContent = """x = lambda a : a * 5""" + val input = createTestFile(fileContent) + + // when + val result = collector.collectMetricsForFile(input) + + // then + Assertions.assertThat(result.attributes[AvailableMetrics.COMPLEXITY.metricName]).isEqualTo(1) + } + + @Test + fun `should count for in loop for complexity`() { + // given + val fileContent = """ + for x in y: + result += x + """.trimIndent() + val input = createTestFile(fileContent) + + // when + val result = collector.collectMetricsForFile(input) + + // then + Assertions.assertThat(result.attributes[AvailableMetrics.COMPLEXITY.metricName]).isEqualTo(1) + } + + @Test + fun `should count 'and' and 'or' patterns for complexity`() { + // given + val fileContent = """ + if (x == 2 or y != none and y < 5): + print("") + """.trimIndent() + val input = createTestFile(fileContent) + + // when + val result = collector.collectMetricsForFile(input) + + // then + Assertions.assertThat(result.attributes[AvailableMetrics.COMPLEXITY.metricName]).isEqualTo(3) + } + + @Test + fun `should count case pattern for complexity`() { + // given + val fileContent = """ + match lang: + case "JavaScript": + print("You can become a web developer.") + case "Python": + print("You can become a Data Scientist") + case _: + print("The language doesn't matter, what matters is solving problems.") + """.trimIndent() + val input = createTestFile(fileContent) + + // when + val result = collector.collectMetricsForFile(input) + + // then + Assertions.assertThat(result.attributes[AvailableMetrics.COMPLEXITY.metricName]).isEqualTo(3) + } + + @Test + fun `should count line and block comments for comment_lines`() { + // given + val fileContent = """ + # line comment + ${"\"\"\""} + unassigned string used as block comment + ${"\"\"\""} + """.trimIndent() + val input = createTestFile(fileContent) + + // when + val result = collector.collectMetricsForFile(input) + + // then + Assertions.assertThat(result.attributes[AvailableMetrics.COMMENT_LINES.metricName]).isEqualTo(4) + } + + @Test + fun `should count assigned multiline strings for rloc`() { + // given + val fileContent = """ + x = ${"\"\"\""} + normal assigned string + ${"\"\"\""} + """.trimIndent() + val input = createTestFile(fileContent) + + // when + val result = collector.collectMetricsForFile(input) + + // then + Assertions.assertThat(result.attributes[AvailableMetrics.REAL_LINES_OF_CODE.metricName]).isEqualTo(3) + } + + @Test + fun `should not count unassigned multiline strings for rloc`() { + // given + val fileContent = """ + ${"\"\"\""} + unassigned string, used as block comment + ${"\"\"\""} + """.trimIndent() + val input = createTestFile(fileContent) + + // when + val result = collector.collectMetricsForFile(input) + + // then + Assertions.assertThat(result.attributes[AvailableMetrics.COMPLEXITY.metricName]).isEqualTo(0) + } + + @Test + fun `should not include comments or empty lines for rloc`() { + // given + val fileContent = """ + ${"\"\"\""} + unassigned string, used as block comment + ${"\"\"\""} + + if (x == 2 or y != none and y < 5): + # prints x + + print(x) # inline comment + """.trimIndent() + val input = createTestFile(fileContent) + + // when + val result = collector.collectMetricsForFile(input) + + // then + Assertions.assertThat(result.attributes[AvailableMetrics.REAL_LINES_OF_CODE.metricName]).isEqualTo(2) + } + + @Test + fun `should count empty lines and comments for loc`() { + // given + val fileContent = """ + ${"\"\"\""} + unassigned string, used as block comment + ${"\"\"\""} + + if (x == 2 or y != none and y < 5): + # prints x + + print(x) # inline comment + """.trimIndent() + "\n" // this newline simulates end of file + val input = createTestFile(fileContent) + + // when + val result = collector.collectMetricsForFile(input) + + // then + Assertions.assertThat(result.attributes[AvailableMetrics.LINES_OF_CODE.metricName]).isEqualTo(8) + } +} diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/includeAll.cc.json b/analysis/analysers/parsers/UnifiedParser/src/test/resources/includeAll.cc.json index 8c35f7ff49..4e1e664636 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/test/resources/includeAll.cc.json +++ b/analysis/analysers/parsers/UnifiedParser/src/test/resources/includeAll.cc.json @@ -1,5 +1,5 @@ { - "checksum": "2e59a5a836cc7dc4d23481bb8d3213ec", + "checksum": "8116f44f6e2be3a83f516a33d690214a", "data": { "projectName": "", "nodes": [ diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/javascriptSample.cc.json b/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/javascriptSample.cc.json index 99d8dab191..3f1e965810 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/javascriptSample.cc.json +++ b/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/javascriptSample.cc.json @@ -1,6 +1,5 @@ { - "checksum": "1a6e609d76f9b178c4fc56c58e1ddf6f", - "checksum": "d65e9fd093e285977c0fd3b66de6ff4d", + "checksum": "36429191730fdb0005bd49b160803d46", "data": { "projectName": "", "nodes": [ diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/typescriptSample.cc.json b/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/typescriptSample.cc.json index cbba3ce4c6..7cb9fb73a3 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/typescriptSample.cc.json +++ b/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/typescriptSample.cc.json @@ -1,5 +1,5 @@ { - "checksum": "bd1dc239a00b5fc494324112744118d4", + "checksum": "f4a7ca2b3cfe15e5bd0e6bcc5fed4752", "data": { "projectName": "", "nodes": [ diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/mergeResult.cc.json b/analysis/analysers/parsers/UnifiedParser/src/test/resources/mergeResult.cc.json index 7654df1235..e51471909e 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/test/resources/mergeResult.cc.json +++ b/analysis/analysers/parsers/UnifiedParser/src/test/resources/mergeResult.cc.json @@ -1,5 +1,5 @@ { - "checksum": "4c2c1b4d1165c6e1010c827cefa9b3d7", + "checksum": "57c8e122387ce42a8ab80a3caed435aa", "data": { "projectName": "", "nodes": [ diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/sampleProject.cc.json b/analysis/analysers/parsers/UnifiedParser/src/test/resources/sampleProject.cc.json index 6d1b01cae8..92634ee37a 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/test/resources/sampleProject.cc.json +++ b/analysis/analysers/parsers/UnifiedParser/src/test/resources/sampleProject.cc.json @@ -1,5 +1,5 @@ { - "checksum": "320b420c79ad382ecc31f23e8118fcec", + "checksum": "49e750bf6aaabb3c9b1dbbc5f236c082", "data": { "projectName": "", "nodes": [ diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/sampleproject/empty.json b/analysis/analysers/parsers/UnifiedParser/src/test/resources/sampleproject/empty.json new file mode 100644 index 0000000000..e69de29bb2 From ffe85f174fbdf1459411d9cb9b3ea7c72cdabdb1 Mon Sep 17 00:00:00 2001 From: nereboss Date: Fri, 18 Jul 2025 13:08:06 +0200 Subject: [PATCH 07/10] fix(analysis): fix rloc calculation for python #4092 --- .../metriccollectors/MetricCollector.kt | 6 +++++- .../metriccollectors/PythonCollector.kt | 21 +++++++++++++------ .../metricnodetypes/PythonNodeTypes.kt | 2 +- .../parsers/unified/UnifiedParserTest.kt | 10 ++++----- .../languageSamples/pythonSample.cc.json | 2 +- 5 files changed, 27 insertions(+), 14 deletions(-) diff --git a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/MetricCollector.kt b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/MetricCollector.kt index 168e4d2f7d..aa2d7c4b8b 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/MetricCollector.kt +++ b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/MetricCollector.kt @@ -118,7 +118,7 @@ abstract class MetricCollector( rlocForNode++ } - if (node.childCount == 0 && endRow > lastCountedCodeLine) { + if (endRow > lastCountedCodeLine && countWholeNodeLength(node)) { lastCountedCodeLine = endRow rlocForNode += endRow - startRow } @@ -151,4 +151,8 @@ abstract class MetricCollector( } return false } + + protected open fun countWholeNodeLength(node: TSNode): Boolean { + return node.childCount == 0 + } } diff --git a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollector.kt b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollector.kt index 64878df3ea..221e565735 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollector.kt +++ b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/PythonCollector.kt @@ -9,15 +9,24 @@ class PythonCollector : MetricCollector( queryProvider = PythonNodeTypes() ) { override fun calculateRealLinesOfCodeForNode(node: TSNode, nodeType: String, startRow: Int, endRow: Int): Int { - if (isNodePartOfBlockCommentString(nodeType)) return 0 + if (shouldIgnoreNodeType(node, nodeType) || doesNodeStartWithComment(node)) return 0 return super.calculateRealLinesOfCodeForNode(node, nodeType, startRow, endRow) } - // TODO: add case of multiline strings - private fun isNodePartOfBlockCommentString(nodeType: String): Boolean { - return nodeType == "string" || - nodeType == "string_start" || + private fun shouldIgnoreNodeType(node: TSNode, nodeType: String): Boolean { + return nodeType == "string_start" || nodeType == "string_content" || - nodeType == "string_end" + nodeType == "string_end" || + (nodeType == "string" && node.parent.childCount == 1) + } + + private fun doesNodeStartWithComment(node: TSNode): Boolean { + val childNode = node.getChild(0) + if (childNode.isNull) return false + return childNode.type == "expression_statement" && childNode.childCount == 1 + } + + override fun countWholeNodeLength(node: TSNode): Boolean { + return node.childCount == 0 || (node.type == "string" && node.parent.childCount != 1) } } diff --git a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/PythonNodeTypes.kt b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/PythonNodeTypes.kt index dfb108392d..5184d3aa94 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/PythonNodeTypes.kt +++ b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metricnodetypes/PythonNodeTypes.kt @@ -27,8 +27,8 @@ class PythonNodeTypes : MetricNodeTypes { // lambda needs to be complex to not be counted double as type of first child is also lambda NestedNodeType( baseNodeType = "lambda", - childNodePosition = 0, childNodeCount = 4, + childNodePosition = 0, childNodeTypes = setOf("lambda") ) ) diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/UnifiedParserTest.kt b/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/UnifiedParserTest.kt index 6b49c56eb0..b53e2b6444 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/UnifiedParserTest.kt +++ b/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/UnifiedParserTest.kt @@ -38,11 +38,11 @@ class UnifiedParserTest { } private fun provideSupportedLanguages() = listOf( -// Arguments.of("typescript", ".ts"), -// Arguments.of("javascript", ".js"), -// Arguments.of("java", ".java"), -// Arguments.of("kotlin", ".kt"), -// Arguments.of("cSharp", ".cs"), + Arguments.of("typescript", ".ts"), + Arguments.of("javascript", ".js"), + Arguments.of("java", ".java"), + Arguments.of("kotlin", ".kt"), + Arguments.of("cSharp", ".cs"), Arguments.of("python", ".py") ) diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/pythonSample.cc.json b/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/pythonSample.cc.json index 839b8b4735..5a3e9e89c7 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/pythonSample.cc.json +++ b/analysis/analysers/parsers/UnifiedParser/src/test/resources/languageSamples/pythonSample.cc.json @@ -1,5 +1,5 @@ { - "checksum": "5f0f11de52d3447ee8ea6b248e72502f", + "checksum": "595ba57961df876c22c06eb0b42f864f", "data": { "projectName": "", "nodes": [ From c464212bab088594cdc92d20ebfab42460cfaa30 Mon Sep 17 00:00:00 2001 From: nereboss Date: Fri, 18 Jul 2025 14:54:46 +0200 Subject: [PATCH 08/10] fix(analysis): adjust test to hopefully pass pipeline #4092 --- .../codecharta/analysers/parsers/unified/UnifiedParserTest.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/UnifiedParserTest.kt b/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/UnifiedParserTest.kt index b53e2b6444..d1e99325d2 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/UnifiedParserTest.kt +++ b/analysis/analysers/parsers/UnifiedParser/src/test/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/UnifiedParserTest.kt @@ -168,7 +168,9 @@ class UnifiedParserTest { // then Assertions.assertThat(errContent.toString()).contains( - "2 Files with the following extensions were ignored as they are currently not supported:\n[.json, .strange]" + "2 Files with the following extensions were ignored as they are currently not supported:", + ".json", + ".strange" ) // clean up From 7b17184dee8ff9e3e46ff07388e49e5a303f5a19 Mon Sep 17 00:00:00 2001 From: nereboss Date: Fri, 18 Jul 2025 15:04:49 +0200 Subject: [PATCH 09/10] fix(analysis): apply review comments #4092 --- .../metriccollectors/MetricCollector.kt | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/MetricCollector.kt b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/MetricCollector.kt index aa2d7c4b8b..717cf1098f 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/MetricCollector.kt +++ b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/MetricCollector.kt @@ -35,6 +35,7 @@ abstract class MetricCollector( fun collectMetricsForFile(file: File): MutableNode { val rootNode = getRootNode(file) + rootNodeType = rootNode.type lastCountedCommentLine = -1 lastCountedCodeLine = -1 @@ -59,7 +60,6 @@ abstract class MetricCollector( val parser = TSParser() parser.setLanguage(treeSitterLanguage) val rootNode = parser.parseString(null, file.readText()).rootNode - rootNodeType = rootNode.type return rootNode } @@ -137,16 +137,14 @@ abstract class MetricCollector( private fun isNestedTypeAllowed(node: TSNode, nodeType: String, nestedTypes: Set): Boolean { for (nestedType in nestedTypes) { - if (nestedType.baseNodeType == nodeType) { - if (nestedType.childNodePosition != null && - nestedType.childNodeCount == node.childCount - ) { - val childNode = node.getChild(nestedType.childNodePosition) - if (nestedType.childNodeTypes.contains(childNode.type)) return true - } else if (nestedType.childNodeFieldName != null) { - val childNode = node.getChildByFieldName(nestedType.childNodeFieldName) - if (nestedType.childNodeTypes.contains(childNode.type)) return true - } + if (nestedType.baseNodeType != nodeType) continue + + if (nestedType.childNodePosition != null && nestedType.childNodeCount == node.childCount) { + val childNode = node.getChild(nestedType.childNodePosition) + if (nestedType.childNodeTypes.contains(childNode.type)) return true + } else if (nestedType.childNodeFieldName != null) { + val childNode = node.getChildByFieldName(nestedType.childNodeFieldName) + if (nestedType.childNodeTypes.contains(childNode.type)) return true } } return false From 3d708c272adfbfd2251a74ad48accab369e73c43 Mon Sep 17 00:00:00 2001 From: nereboss Date: Fri, 18 Jul 2025 15:35:46 +0200 Subject: [PATCH 10/10] fix(analysis): apply remaining review comments #4092 --- .../parsers/unified/metriccollectors/MetricCollector.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/MetricCollector.kt b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/MetricCollector.kt index 717cf1098f..ddcf5f9be6 100644 --- a/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/MetricCollector.kt +++ b/analysis/analysers/parsers/UnifiedParser/src/main/kotlin/de/maibornwolff/codecharta/analysers/parsers/unified/metriccollectors/MetricCollector.kt @@ -70,7 +70,8 @@ abstract class MetricCollector( val startRow = currentNode.startPoint.row val endRow = currentNode.endPoint.row - if (nodeType != rootNodeType) { + val skipRootToAvoidDoubleCountingLines = nodeType != rootNodeType + if (skipRootToAvoidDoubleCountingLines) { for ((_, indexAndCalculateMetricForNodeFn) in metricInfo) { val (index, calculateMetricForNodeFn) = indexAndCalculateMetricForNodeFn metrics[index] += calculateMetricForNodeFn(currentNode, nodeType, startRow, endRow)