Source code for pycodeanalyzer.core.languages.analyzers.kotlinanalyzer

import re
from typing import Any, List, Tuple, Union

import kopyt

from pycodeanalyzer.core.abstraction.objects import (
    AbstractClass,
    AbstractEnum,
    AbstractFunction,
    AbstractObject,
    AbstractObjectLanguage,
)
from pycodeanalyzer.core.languages.analyzer import Analyzer


[docs]class KotlinAnalyzer(Analyzer): def __init__(self) -> None: super().__init__(__name__) self.globalImports: List[str] = []
[docs] def analyze(self, rootDir: str, path: str) -> List[AbstractObject]: abstractObjects: List[AbstractObject] = [] self.logger.info("Analysing %s", path) parsed: Any = None with open(path) as file: parser = kopyt.Parser(file.read()) try: parsed = parser.parse() except kopyt.exception.ParserException: self.logger.error("Error parsing %s, will be ommitted.", path) return abstractObjects # Handle imports self.globalImports = [] for item in parsed.imports: importNS = item.name.replace(".", "::") if not item.wildcard: importNS = importNS[: importNS.rindex("::")] self.globalImports.append(importNS) # handle package currentNS = "" if parsed.package: currentNS = parsed.package.name.replace(".", "::") # handle types for item in parsed.declarations: if isinstance(item, kopyt.node.EnumDeclaration): self.handleEnum(abstractObjects, path, currentNS, item) elif isinstance(item, kopyt.node.ClassDeclaration) or isinstance( item, kopyt.node.InterfaceDeclaration ): self.handleClass(abstractObjects, path, currentNS, item) elif isinstance(item, kopyt.node.FunctionDeclaration): self.handleFunction(abstractObjects, path, currentNS, item) return abstractObjects
[docs] def handleClass( self, abstractObjects: List[AbstractObject], path: str, currentNS: str, klass: Union[kopyt.node.ClassDeclaration, kopyt.node.InterfaceDeclaration], ) -> None: abstraction = AbstractClass(klass.name, currentNS, path) for ns in self.globalImports: abstraction.addUsingNamespace(ns) self.handleParent(abstraction, klass.supertypes) if klass.constructor: self.handleConstuctor(abstraction, klass.constructor, True) if klass.generics: for genType in klass.generics: abstraction.addGenericType(str(genType).strip()) if klass.body: for item in klass.body.members: if isinstance(item, kopyt.node.ClassDeclaration): self.handleClass( abstractObjects, path, currentNS + "::" + klass.name, item ) elif isinstance(item, kopyt.node.CompanionObject) and item.body: for subitem in item.body.members: if isinstance(subitem, kopyt.node.PropertyDeclaration): self.handleProperty(abstraction, subitem, True) elif isinstance(item, kopyt.node.FunctionDeclaration): self.handleMethod(abstraction, item, True) else: self.logger.error( "Unhandled companion object member type %s", type(item) ) elif isinstance(item, kopyt.node.SecondaryConstructor): self.handleConstuctor(abstraction, item, False) elif isinstance(item, kopyt.node.PropertyDeclaration): self.handleProperty(abstraction, item, False) elif isinstance(item, kopyt.node.FunctionDeclaration): self.handleMethod(abstraction, item, False) elif isinstance(item, kopyt.node.ObjectDeclaration) and item.body: for subitem in item.body.members: if isinstance(subitem, kopyt.node.PropertyDeclaration): self.handleProperty(abstraction, subitem, True) elif isinstance(item, kopyt.node.FunctionDeclaration): self.handleMethod(abstraction, item, True) else: self.logger.error( "Unhandled object member type %s", type(item) ) elif isinstance(item, kopyt.node.AnonymousInitializer): # Nothing to be done pass else: self.logger.error("Unhandled member type %s", type(item)) abstraction.objectLanguage = AbstractObjectLanguage.Kotlin abstractObjects.append(abstraction)
[docs] def handleParent( self, abstraction: AbstractClass, item: kopyt.node.DelegationSpecifiers, ) -> None: for supertype in item: if isinstance(supertype, kopyt.node.AnnotatedDelegationSpecifier): if isinstance(supertype.delegate, kopyt.node.UserType): for elem in supertype.delegate.sequence: abstraction.addParent(str(elem), elem.name, "public") elif isinstance(supertype.delegate, kopyt.node.ConstructorInvocation): for elem in supertype.delegate.invoker.sequence: abstraction.addParent(str(elem), elem.name, "public") else: self.logger.error( "Unhandled supertype delegation type %s", type(supertype.delegate), ) else: self.logger.error( "Unhandled supertype type %s", type(supertype), )
[docs] def handleConstuctor( self, abstraction: AbstractClass, item: Union[kopyt.node.PrimaryConstructor, kopyt.node.SecondaryConstructor], isPrimary: bool, ) -> None: constructorVisibility = self.deduceVisibility(item) constructorParams: List[Tuple[str, str]] = [] for param in item.parameters: paramVisibility = self.deduceVisibility(param) paramName = param.name paramType = self.deduceType(str(param.type)) constructorParams.append((paramType, paramName)) if isPrimary and param.mutability: paramType = self.deduceType( str(param.mutability) + " " + str(param.type) ) abstraction.addMember(paramType, paramName, paramVisibility) abstraction.addMethod( abstraction.name, "constructor", constructorParams, constructorVisibility )
[docs] def handleProperty( self, abstraction: AbstractClass, item: kopyt.node.PropertyDeclaration, isStatic: bool, ) -> None: visibility = self.deduceVisibility(item) if isinstance(item.declaration, kopyt.node.MultiVariableDeclaration): for decl in item.declaration: name = item.declaration.name type = item.declaration.type if not type: type = self.deduceTypeFromExpression(str(item.value)) type = str(item.mutability) + " " + self.deduceType(str(type)) if isStatic: type = "static " + type abstraction.addMember(type, name, visibility) else: name = item.declaration.name type = item.declaration.type if not type: type = self.deduceTypeFromExpression(str(item.value)) type = str(item.mutability) + " " + self.deduceType(str(type)) if isStatic: type = "static " + type abstraction.addMember(type, name, visibility)
[docs] def handleMethod( self, abstraction: AbstractClass, item: kopyt.node.FunctionDeclaration, isStatic: bool, ) -> None: visibility = self.deduceVisibility(item) rtype = "Unit" if item.type: rtype = self.deduceType(str(item.type)) params: List[Tuple[str, str]] = [] for param in item.parameters: paramType = self.deduceType(str(param.type)) paramName = str(param.name) params.append((paramType, paramName)) abstraction.addMethod(rtype, item.name, params, visibility) if item.generics: for genType in item.generics: abstraction.addGenericType(str(genType).strip())
[docs] def handleEnum( self, abstractObjects: List[AbstractObject], path: str, currentNS: str, enum: kopyt.node.EnumDeclaration, ) -> None: values: List[str] = [] for item in enum.body.entries: values.append(item.name) abstraction = AbstractEnum(enum.name, currentNS, path, values) abstraction.objectLanguage = AbstractObjectLanguage.Kotlin abstractObjects.append(abstraction)
[docs] def handleFunction( self, abstractObjects: List[AbstractObject], path: str, currentNS: str, function: kopyt.node.FunctionDeclaration, ) -> None: rtype = "KotlinTypeNotFound" if function.type: rtype = self.deduceType(str(function.type)) params: List[Tuple[str, str]] = [] for param in function.parameters: paramType = self.deduceType(str(param.type)) paramName = str(param.name) params.append((paramType, paramName)) doxygen = "" abstraction = AbstractFunction( str(function.name), path, rtype, params, currentNS, doxygen, ) if function.generics: for genType in function.generics: abstraction.addGenericType(str(genType).strip()) abstraction.objectLanguage = AbstractObjectLanguage.Kotlin abstractObjects.append(abstraction)
[docs] def deduceVisibility(self, item: Any) -> str: visibility = "public" if "private" in item.modifiers: visibility = "private" if "protected" in item.modifiers: visibility = "protected" return visibility
[docs] def deduceTypeFromExpression(self, expression: str) -> str: if "{" in expression: expression = expression[: expression.index("{")] expression = expression.strip() if expression.startswith('"'): return "String" elif re.match("^ *object *:", expression): index = expression.index(":") + 1 return self.deduceType(expression[index:]) elif re.match("[-+]?\\d+.{0,1}\\d*[fF]$", expression): return "Float" elif re.match("[-+]?\\d+L$", expression): return "Long" elif re.match("[-+]?\\d+$", expression): return "Int" elif re.match("[-+]?\\d+.{0,1}\\d*$", expression): return "Double" elif "<" in expression and expression[-1] == ">": return self.deduceType(expression) elif "(" in expression and expression[-1] == ")": return self.deduceType(expression[: expression.rindex("(")]) elif "." in expression: return self.deduceType(expression[: expression.rindex(".")]) return "KotlinAuto"
[docs] def deduceType(self, type: str) -> str: return type.replace(".", "::").replace(" ", "").strip()