kotlin_analyzer
¶
Full name: tenets.core.analysis.implementations.kotlin_analyzer
kotlin_analyzer¶
Kotlin code analyzer with Android and multiplatform support.
This module provides comprehensive analysis for Kotlin source files, including support for Android development, coroutines, null safety, and Kotlin Multiplatform features.
Classes¶
KotlinAnalyzer¶
Bases: LanguageAnalyzer
Kotlin code analyzer with Android and multiplatform support.
Provides comprehensive analysis for Kotlin files including: - Import statements with aliases - Package declarations - Classes, interfaces, objects, data classes - Sealed classes and interfaces - Extension functions and properties - Coroutines and suspend functions - Null safety features - Inline and reified functions - Companion objects - Delegation patterns - Android-specific patterns (Activities, Fragments, ViewModels) - Kotlin Multiplatform declarations
Supports modern Kotlin features and Android development patterns.
Initialize the Kotlin analyzer with logger.
Source code in tenets/core/analysis/implementations/kotlin_analyzer.py
Functions¶
extract_imports¶
Extract import statements from Kotlin code.
Handles: - import statements: import kotlin.collections.List - Wildcard imports: import java.util.* - Aliased imports: import java.util.List as JList - Package declarations - Static imports (companion object members)
PARAMETER | DESCRIPTION |
---|---|
content | Kotlin source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
RETURNS | DESCRIPTION |
---|---|
List[ImportInfo] | List of ImportInfo objects with import details |
Source code in tenets/core/analysis/implementations/kotlin_analyzer.py
def extract_imports(self, content: str, file_path: Path) -> List[ImportInfo]:
"""Extract import statements from Kotlin code.
Handles:
- import statements: import kotlin.collections.List
- Wildcard imports: import java.util.*
- Aliased imports: import java.util.List as JList
- Package declarations
- Static imports (companion object members)
Args:
content: Kotlin source code
file_path: Path to the file being analyzed
Returns:
List of ImportInfo objects with import details
"""
imports = []
lines = content.split("\n")
# Track current package
current_package = None
for i, line in enumerate(lines, 1):
# Skip comments
if line.strip().startswith("//"):
continue
# Package declaration
package_pattern = r"^\s*package\s+([\w\.]+)"
match = re.match(package_pattern, line)
if match:
current_package = match.group(1)
imports.append(
ImportInfo(
module=current_package,
line=i,
type="package",
is_relative=False,
is_package_declaration=True,
)
)
continue
# Import statements
import_pattern = r"^\s*import\s+([^\s]+?)(?:\s+as\s+(\w+))?(?:\s*//.*)?$"
match = re.match(import_pattern, line)
if match:
module_path = match.group(1)
alias = match.group(2)
# Check for wildcard import
is_wildcard = module_path.endswith(".*")
if is_wildcard:
base_path = module_path[:-2]
category = self._categorize_import(base_path)
else:
category = self._categorize_import(module_path)
# Determine if it's an Android import
is_android = self._is_android_import(module_path)
imports.append(
ImportInfo(
module=module_path,
alias=alias,
line=i,
type="import",
is_relative=False,
is_wildcard=is_wildcard,
category=category,
is_android=is_android,
package_context=current_package,
)
)
return imports
extract_exports¶
Extract exported symbols from Kotlin code.
In Kotlin, exports include: - Public classes, interfaces, and objects - Public functions (including extension functions) - Public properties (including extension properties) - Public type aliases - Sealed class hierarchies - Enum classes - Annotations
PARAMETER | DESCRIPTION |
---|---|
content | Kotlin source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
RETURNS | DESCRIPTION |
---|---|
List[Dict[str, Any]] | List of exported symbols |
Source code in tenets/core/analysis/implementations/kotlin_analyzer.py
def extract_exports(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
"""Extract exported symbols from Kotlin code.
In Kotlin, exports include:
- Public classes, interfaces, and objects
- Public functions (including extension functions)
- Public properties (including extension properties)
- Public type aliases
- Sealed class hierarchies
- Enum classes
- Annotations
Args:
content: Kotlin source code
file_path: Path to the file being analyzed
Returns:
List of exported symbols
"""
exports = []
# Classes (including data, sealed, enum)
class_pattern = r"^\s*(?:(public|internal|private|protected)\s+)?(?:(abstract|open|final|sealed|inner|data|enum|annotation|inline|value)\s+)*class\s+(\w+)"
for match in re.finditer(class_pattern, content, re.MULTILINE):
visibility = match.group(1) or "public"
if visibility != "private":
modifiers = match.group(2).split() if match.group(2) else []
class_name = match.group(3)
class_type = "class"
if "data" in modifiers:
class_type = "data_class"
elif "enum" in modifiers:
class_type = "enum_class"
elif "sealed" in modifiers:
class_type = "sealed_class"
elif "annotation" in modifiers:
class_type = "annotation_class"
elif "value" in modifiers or "inline" in modifiers:
class_type = "value_class"
exports.append(
{
"name": class_name,
"type": class_type,
"line": content[: match.start()].count("\n") + 1,
"visibility": visibility,
"modifiers": modifiers,
"is_public": visibility == "public",
}
)
# Interfaces
interface_pattern = (
r"^\s*(?:(public|internal|private|protected)\s+)?(?:(sealed|fun)\s+)?interface\s+(\w+)"
)
for match in re.finditer(interface_pattern, content, re.MULTILINE):
visibility = match.group(1) or "public"
if visibility != "private":
exports.append(
{
"name": match.group(3),
"type": "sealed_interface" if match.group(2) == "sealed" else "interface",
"line": content[: match.start()].count("\n") + 1,
"visibility": visibility,
"is_fun_interface": match.group(2) == "fun",
"is_public": visibility == "public",
}
)
# Objects (including companion objects)
object_pattern = (
r"^\s*(?:(public|internal|private|protected)\s+)?(?:(companion)\s+)?object\s+(\w+)"
)
for match in re.finditer(object_pattern, content, re.MULTILINE):
visibility = match.group(1) or "public"
if visibility != "private":
exports.append(
{
"name": match.group(3),
"type": "companion_object" if match.group(2) else "object",
"line": content[: match.start()].count("\n") + 1,
"visibility": visibility,
"is_public": visibility == "public",
}
)
# Functions (including extension and suspend functions)
func_pattern = r"^\s*(?:(public|internal|private|protected)\s+)?(?:(suspend|inline|tailrec|operator|infix|external|actual|expect)\s+)*fun\s+(?:<[^>]+>\s+)?(?:(\w+(?:<[^>]*>)?|\w+)\.)?(\w+)"
for match in re.finditer(func_pattern, content, re.MULTILINE):
visibility = match.group(1) or "public"
if visibility != "private":
modifiers = match.group(2).split() if match.group(2) else []
receiver = match.group(3)
func_name = match.group(4)
exports.append(
{
"name": func_name,
"type": "extension_function" if receiver else "function",
"line": content[: match.start()].count("\n") + 1,
"visibility": visibility,
"modifiers": modifiers,
"receiver": receiver,
"is_suspend": "suspend" in modifiers,
"is_inline": "inline" in modifiers,
"is_operator": "operator" in modifiers,
"is_public": visibility == "public",
}
)
# Properties (including extension properties)
prop_pattern = r"^\s*(?:(public|internal|private|protected)\s+)?(?:(const|lateinit)\s+)?(?:override\s+)?(val|var)\s+(?:(\w+)\.)?(\w+)"
for match in re.finditer(prop_pattern, content, re.MULTILINE):
visibility = match.group(1) or "public"
if visibility != "private":
modifier = match.group(2)
prop_type = match.group(3)
receiver = match.group(4)
prop_name = match.group(5)
exports.append(
{
"name": prop_name,
"type": "extension_property" if receiver else "property",
"line": content[: match.start()].count("\n") + 1,
"visibility": visibility,
"is_mutable": prop_type == "var",
"is_const": modifier == "const",
"is_lateinit": modifier == "lateinit",
"receiver": receiver,
"is_public": visibility == "public",
}
)
# Type aliases
typealias_pattern = r"^\s*(?:(public|internal|private)\s+)?typealias\s+(\w+)"
for match in re.finditer(typealias_pattern, content, re.MULTILINE):
visibility = match.group(1) or "public"
if visibility != "private":
exports.append(
{
"name": match.group(2),
"type": "typealias",
"line": content[: match.start()].count("\n") + 1,
"visibility": visibility,
"is_public": visibility == "public",
}
)
return exports
extract_structure¶
Extract code structure from Kotlin file.
Extracts: - Classes with inheritance and interfaces - Data classes and sealed hierarchies - Functions with parameters and return types - Extension functions and properties - Coroutines and suspend functions - Companion objects - Android components (Activities, Fragments, ViewModels) - Delegation patterns - Inline classes/value classes
PARAMETER | DESCRIPTION |
---|---|
content | Kotlin source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
RETURNS | DESCRIPTION |
---|---|
CodeStructure | CodeStructure object with extracted elements |
Source code in tenets/core/analysis/implementations/kotlin_analyzer.py
def extract_structure(self, content: str, file_path: Path) -> CodeStructure:
"""Extract code structure from Kotlin file.
Extracts:
- Classes with inheritance and interfaces
- Data classes and sealed hierarchies
- Functions with parameters and return types
- Extension functions and properties
- Coroutines and suspend functions
- Companion objects
- Android components (Activities, Fragments, ViewModels)
- Delegation patterns
- Inline classes/value classes
Args:
content: Kotlin source code
file_path: Path to the file being analyzed
Returns:
CodeStructure object with extracted elements
"""
structure = CodeStructure()
# Extract package
package_match = re.search(r"^\s*package\s+([\w\.]+)", content, re.MULTILINE)
if package_match:
structure.package = package_match.group(1)
# Detect if it's an Android file
structure.is_android = self._is_android_file(content)
# Extract classes
class_pattern = r"""
^\s*(?:(internal|private|protected|public)\s+)?
(?:(abstract|open|final|sealed|inner|data|enum|annotation|inline|value)\s+)*
class\s+(\w+)
(?:<([^>]+)>)? # Generic parameters
(?:\s*(?:@\w+(?:\([^)]*\))?\s*)*)? # Annotations
(?:\s*\(([^)]*)\))? # Primary constructor
(?:\s*:\s*([^{]+?))? # Inheritance
\s*(?:\{|$)
"""
for match in re.finditer(class_pattern, content, re.VERBOSE | re.MULTILINE):
visibility = match.group(1) or "public"
modifiers = match.group(2).split() if match.group(2) else []
class_name = match.group(3)
type_params = match.group(4)
constructor_params = match.group(5)
inheritance = match.group(6)
# Parse inheritance (superclass and interfaces)
bases = []
interfaces = []
delegates = {}
if inheritance:
for item in self._parse_inheritance(inheritance):
if " by " in item:
# Delegation
parts = item.split(" by ")
interface = parts[0].strip()
delegate = parts[1].strip()
delegates[interface] = delegate
interfaces.append(interface)
elif item.endswith("()") or "(" in item:
# Superclass with constructor call
bases.append(item)
else:
# Interface or superclass without constructor
if self._is_likely_interface(item):
interfaces.append(item)
else:
bases.append(item)
# Extract class body
class_body = self._extract_body(content, match.end())
if class_body:
methods = self._extract_methods(class_body)
properties = self._extract_properties(class_body)
companion = self._extract_companion_object(class_body)
nested_classes = self._extract_nested_classes(class_body)
else:
methods = []
properties = []
companion = None
nested_classes = []
# Check for Android components
android_type = None
if structure.is_android:
# Remove parentheses and generics from base class names for matching
clean_bases = [base.split("(")[0].split("<")[0].strip() for base in bases]
if any(
base in ["Activity", "AppCompatActivity", "ComponentActivity"]
for base in clean_bases
):
android_type = "activity"
elif any(base in ["Fragment", "DialogFragment"] for base in clean_bases):
android_type = "fragment"
elif any(base in ["ViewModel", "AndroidViewModel"] for base in clean_bases):
android_type = "viewmodel"
elif any(base in ["Service", "IntentService"] for base in clean_bases):
android_type = "service"
elif any(base in ["BroadcastReceiver"] for base in clean_bases):
android_type = "receiver"
class_info = ClassInfo(
name=class_name,
line=content[: match.start()].count("\n") + 1,
visibility=visibility,
modifiers=modifiers,
type_parameters=type_params,
constructor_params=(
self._parse_constructor_params(constructor_params) if constructor_params else []
),
bases=bases,
interfaces=interfaces,
delegates=delegates,
methods=methods,
properties=properties,
companion_object=companion,
nested_classes=nested_classes,
is_data_class="data" in modifiers,
is_sealed="sealed" in modifiers,
is_enum="enum" in modifiers,
is_inner="inner" in modifiers,
is_value_class="value" in modifiers or "inline" in modifiers,
android_type=android_type,
)
structure.classes.append(class_info)
# Extract interfaces
interface_pattern = r"""
^\s*(?:(internal|private|protected|public)\s+)?
(?:(sealed|fun)\s+)?
interface\s+(\w+)
(?:<([^>]+)>)?
(?:\s*:\s*([^\{]+?))?
\s*\{?
"""
for match in re.finditer(interface_pattern, content, re.VERBOSE | re.MULTILINE):
interface_name = match.group(3)
# Body may be omitted for marker interfaces
interface_body = self._extract_body(content, match.end())
is_sealed = match.group(2) == "sealed"
structure.interfaces.append(
{
"name": interface_name,
"line": content[: match.start()].count("\n") + 1,
"visibility": match.group(1) or "public",
"is_sealed": is_sealed,
"is_fun_interface": match.group(2) == "fun",
"type_parameters": match.group(4),
"extends": self._parse_inheritance(match.group(5)) if match.group(5) else [],
"methods": (
self._extract_interface_methods(interface_body) if interface_body else []
),
}
)
# Extract objects
object_pattern = r"^\s*(?:(internal|private|protected|public)\s+)?object\s+(\w+)(?:\s*:\s*([^{]+?))?\s*\{"
for match in re.finditer(object_pattern, content, re.MULTILINE):
if not self._is_companion_object(content, match.start()):
object_body = self._extract_body(content, match.end())
structure.objects.append(
{
"name": match.group(2),
"line": content[: match.start()].count("\n") + 1,
"visibility": match.group(1) or "public",
"implements": (
self._parse_inheritance(match.group(3)) if match.group(3) else []
),
"methods": self._extract_methods(object_body) if object_body else [],
"properties": self._extract_properties(object_body) if object_body else [],
}
)
# Extract top-level functions
func_pattern = r"""
^\s*(?:(internal|private|protected|public)\s+)?
(?:(suspend|inline|tailrec|operator|infix|external|actual|expect)\s+)*
fun\s+
(?:<([^>]+)>\s+)? # Type parameters
(?:(\w+)\.)? # Receiver type (for extensions)
(\w+) # Function name
\s*\(([^)]*)\) # Parameters
(?:\s*:\s*([^{=\n]+))? # Return type
\s*[{=]
"""
for match in re.finditer(func_pattern, content, re.VERBOSE | re.MULTILINE):
if not self._is_inside_class(content, match.start()):
visibility = match.group(1) or "public"
modifiers = match.group(2).split() if match.group(2) else []
type_params = match.group(3)
receiver = match.group(4)
func_name = match.group(5)
params = match.group(6)
return_type = match.group(7)
func_info = FunctionInfo(
name=func_name,
line=content[: match.start()].count("\n") + 1,
visibility=visibility,
modifiers=modifiers,
type_parameters=type_params,
receiver_type=receiver,
parameters=self._parse_parameters(params),
return_type=return_type.strip() if return_type else None,
is_extension=receiver is not None,
is_suspend="suspend" in modifiers,
is_inline="inline" in modifiers,
is_operator="operator" in modifiers,
is_infix="infix" in modifiers,
)
structure.functions.append(func_info)
# Extract type aliases
typealias_pattern = (
r"^\s*(?:(internal|private|public)\s+)?typealias\s+(\w+)(?:<[^>]+>)?\s*=\s*([^\n]+)"
)
for match in re.finditer(typealias_pattern, content, re.MULTILINE):
structure.type_aliases.append(
{
"name": match.group(2),
"line": content[: match.start()].count("\n") + 1,
"visibility": match.group(1) or "public",
"definition": match.group(3).strip(),
}
)
# Count coroutine usage
structure.suspend_functions = len(re.findall(r"\bsuspend\s+fun\b", content))
structure.coroutine_launches = len(re.findall(r"\b(?:launch|async)\s*\{", content))
structure.flow_usage = len(re.findall(r"\bFlow<|\bflow\s*\{", content))
# Count null safety features
structure.nullable_types = len(re.findall(r"\w+\?(?:\s|,|\)|>)", content))
structure.null_assertions = len(re.findall(r"!!", content))
structure.safe_calls = len(re.findall(r"\?\.", content))
structure.elvis_operators = len(re.findall(r"\?:", content))
# Count lambda expressions
structure.lambda_expressions = len(re.findall(r"\{[^}]*->[^}]*\}", content))
# Count scope functions
structure.scope_functions = (
len(re.findall(r"\.let\s*\{", content))
+ len(re.findall(r"\.run\s*\{", content))
+ len(re.findall(r"\.apply\s*\{", content))
+ len(re.findall(r"\.also\s*\{", content))
+ len(re.findall(r"\bwith\s*\([^)]+\)\s*\{", content))
)
# Count extension functions and properties
structure.extension_functions = len(
re.findall(r"fun\s+(?:<[^>]*>\s+)?\w+(?:<[^>]*>)?\.\w+", content)
)
structure.extension_properties = len(
re.findall(r"(?:val|var)\s+\w+(?:<[^>]*>)?\.\w+", content)
)
# Detect test file
structure.is_test_file = (
"Test" in file_path.name
or file_path.name.endswith("Test.kt")
or any(part in ["test", "androidTest"] for part in file_path.parts)
)
# Detect main function
structure.has_main = bool(re.search(r"fun\s+main\s*\(", content))
# Multiplatform detection
structure.is_multiplatform = bool(
re.search(r"\b(?:expect|actual)\s+", content)
or re.search(r"@(?:JvmStatic|JvmOverloads|JvmName|JsName)", content)
)
return structure
calculate_complexity¶
Calculate complexity metrics for Kotlin code.
Calculates: - Cyclomatic complexity - Cognitive complexity - Null safety complexity - Coroutine complexity - Android-specific complexity - Functional programming complexity
PARAMETER | DESCRIPTION |
---|---|
content | Kotlin source code TYPE: |
file_path | Path to the file being analyzed TYPE: |
RETURNS | DESCRIPTION |
---|---|
ComplexityMetrics | ComplexityMetrics object with calculated metrics |
Source code in tenets/core/analysis/implementations/kotlin_analyzer.py
def calculate_complexity(self, content: str, file_path: Path) -> ComplexityMetrics:
"""Calculate complexity metrics for Kotlin code.
Calculates:
- Cyclomatic complexity
- Cognitive complexity
- Null safety complexity
- Coroutine complexity
- Android-specific complexity
- Functional programming complexity
Args:
content: Kotlin source code
file_path: Path to the file being analyzed
Returns:
ComplexityMetrics object with calculated metrics
"""
metrics = ComplexityMetrics()
# Calculate cyclomatic complexity
complexity = 1
decision_keywords = [
r"\bif\b",
r"\belse\s+if\b",
r"\belse\b",
r"\bwhen\b",
r"\bfor\b",
r"\bwhile\b",
r"\bdo\b",
r"\btry\b",
r"\bcatch\b",
r"&&",
r"\|\|",
r"\?:", # Elvis operator
]
for keyword in decision_keywords:
complexity += len(re.findall(keyword, content))
metrics.cyclomatic = complexity
# Calculate cognitive complexity
cognitive = 0
nesting_level = 0
max_nesting = 0
lines = content.split("\n")
for line in lines:
# Skip comments
if line.strip().startswith("//"):
continue
# Track nesting
opening_braces = line.count("{")
closing_braces = line.count("}")
nesting_level += opening_braces - closing_braces
max_nesting = max(max_nesting, nesting_level)
# Control structures with nesting penalty
control_patterns = [
(r"\bif\b", 1),
(r"\belse\s+if\b", 1),
(r"\belse\b", 0),
(r"\bwhen\b", 1),
(r"\bfor\b", 1),
(r"\bwhile\b", 1),
(r"\bdo\b", 1),
(r"\btry\b", 1),
(r"\bcatch\b", 1),
]
for pattern, weight in control_patterns:
if re.search(pattern, line):
cognitive += weight * (1 + max(0, nesting_level - 1))
metrics.cognitive = cognitive
metrics.max_depth = max_nesting
# Count code elements
metrics.line_count = len(lines)
metrics.code_lines = len([l for l in lines if l.strip() and not l.strip().startswith("//")])
metrics.comment_lines = len([l for l in lines if l.strip().startswith("//")])
metrics.comment_ratio = (
metrics.comment_lines / metrics.line_count if metrics.line_count > 0 else 0
)
# Count classes and interfaces
metrics.class_count = len(re.findall(r"\bclass\s+\w+", content))
metrics.interface_count = len(re.findall(r"\binterface\s+\w+", content))
metrics.object_count = len(re.findall(r"\bobject\s+\w+", content))
metrics.data_class_count = len(re.findall(r"\bdata\s+class\s+\w+", content))
metrics.sealed_class_count = len(
re.findall(r"\bsealed\s+(?:class|interface)\s+\w+", content)
)
# Null safety metrics
metrics.nullable_types = len(re.findall(r"\w+\?(?:\s|,|\)|>)", content))
metrics.null_assertions = len(re.findall(r"!!", content))
metrics.safe_calls = len(re.findall(r"\?\.", content))
metrics.elvis_operators = len(re.findall(r"\?:", content))
metrics.lateinit_count = len(re.findall(r"\blateinit\s+var\b", content))
metrics.let_calls = len(re.findall(r"\.let\s*\{", content))
# Coroutine metrics
metrics.suspend_functions = len(re.findall(r"\bsuspend\s+fun\b", content))
metrics.coroutine_launches = len(re.findall(r"\b(?:launch|async)\s*\{", content))
metrics.await_calls = len(re.findall(r"\.await\(\)", content))
metrics.flow_usage = len(re.findall(r"\bFlow<|\bflow\s*\{", content))
metrics.channel_usage = len(re.findall(r"\bChannel<|\bchannel\s*\{", content))
metrics.runblocking_usage = len(re.findall(r"\brunBlocking\s*\{", content))
# Functional programming metrics
metrics.lambda_count = len(re.findall(r"\{[^}]*->[^}]*\}", content))
metrics.higher_order_functions = len(
re.findall(r"(?:map|filter|fold|reduce|flatMap|forEach)\s*\{", content)
)
metrics.inline_functions = len(re.findall(r"\binline\s+fun\b", content))
metrics.extension_functions = len(re.findall(r"fun\s+\w+\.\w+", content))
metrics.scope_functions = len(re.findall(r"\.(?:let|run|apply|also)\s*\{", content)) + len(
re.findall(r"\bwith\s*\([^)]+\)\s*\{", content)
)
# When expression metrics
metrics.when_expressions = len(re.findall(r"\bwhen\s*(?:\(|\{)", content))
metrics.when_branches = len(re.findall(r"->\s*(?:\{|[^,\n]+)", content))
# Exception handling
metrics.try_blocks = len(re.findall(r"\btry\s*\{", content))
metrics.catch_blocks = len(re.findall(r"\bcatch\s*\(", content))
metrics.finally_blocks = len(re.findall(r"\bfinally\s*\{", content))
metrics.throw_statements = len(re.findall(r"\bthrow\s+", content))
# Android-specific metrics (if applicable)
if self._is_android_file(content):
metrics.activity_count = len(re.findall(r":\s*(?:AppCompat)?Activity\(\)", content))
metrics.fragment_count = len(re.findall(r":\s*Fragment\(\)", content))
metrics.viewmodel_count = len(re.findall(r":\s*(?:Android)?ViewModel\(\)", content))
metrics.livedata_usage = len(re.findall(r"\bLiveData<|\bMutableLiveData<", content))
metrics.observer_usage = len(re.findall(r"\.observe\(", content))
metrics.binding_usage = len(re.findall(r"Binding\b|\.binding", content))
# Delegation metrics
metrics.delegation_count = len(re.findall(r"\bby\s+\w+", content))
metrics.lazy_properties = len(re.findall(r"\bby\s+lazy\s*\{", content))
metrics.observable_properties = len(
re.findall(r"\bby\s+(?:\w+\.)?(?:observable|vetoable)\s*\(", content)
)
# Calculate maintainability index
import math
if metrics.code_lines > 0:
# Adjusted for Kotlin
null_safety_factor = 1 - (metrics.null_assertions * 0.02)
coroutine_factor = 1 - (metrics.runblocking_usage * 0.05)
functional_factor = 1 + (metrics.lambda_count * 0.001)
scope_factor = 1 + (metrics.scope_functions * 0.001)
mi = (
171
- 5.2 * math.log(max(1, complexity))
- 0.23 * complexity
- 16.2 * math.log(metrics.code_lines)
+ 10 * null_safety_factor
+ 5 * coroutine_factor
+ 5 * functional_factor
+ 5 * scope_factor
)
metrics.maintainability_index = max(0, min(100, mi))
return metrics