chapi.app.frontend.FrontendApiAnalyser.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of chapi-application Show documentation
Show all versions of chapi-application Show documentation
Chapi is A common language meta information convertor, convert different languages to same meta-data model
package chapi.app.frontend
import chapi.app.frontend.identify.AxiosHttpIdentify
import chapi.app.frontend.identify.UmiHttpIdentify
import chapi.app.frontend.path.ecmaImportConvert
import chapi.app.frontend.path.relativeRoot
import chapi.domain.core.*
import kotlinx.serialization.Serializable
@Serializable
data class ApiCodeCall(val ApiType: String = "") : CodeCall() {
companion object {
fun from(call: CodeCall, apiType: String): ApiCodeCall {
val apiCodeCall = ApiCodeCall(ApiType = apiType)
apiCodeCall.NodeName = call.NodeName
apiCodeCall.FunctionName = call.FunctionName
apiCodeCall.Parameters = call.Parameters
apiCodeCall.Position = call.Position
apiCodeCall.Type = call.Type
apiCodeCall.Package = call.Package
return apiCodeCall
}
}
}
class FrontendApiAnalyser {
private var componentCallMap: HashMap> = hashMapOf()
private var componentInbounds: HashMap> = hashMapOf()
private var callMap: HashMap = hashMapOf()
private var httpAdapterMap: HashMap = hashMapOf()
// for Axios Http Call
private val axiosIdent = AxiosHttpIdentify()
private val umiIdent = UmiHttpIdentify()
// 1. first create Component with FunctionCall maps based on Import
// 2. build axios/umi-request to an API call method
// 3. mapping for results
fun analysis(nodes: Array, workspace: String): Array {
nodes.forEach { node ->
analysisByPath(node, workspace)
}
return toContainerServices()
}
private fun toContainerServices(): Array {
var componentCalls: Array = arrayOf()
componentInbounds.forEach { map ->
val componentRef = ContainerService(name = map.key)
map.value.forEach {
// TODO: add support for multiple level call routes
if (httpAdapterMap[it] != null) {
val call = httpAdapterMap[it]!!
var httpApi = ContainerDemand()
when (call.ApiType) {
"axios" -> {
httpApi = axiosIdent.convert(call)
}
"umi" -> {
httpApi = umiIdent.convert(call)
}
}
httpApi.source_caller = it
httpApi.call_routes = listOf(it)
componentRef.demands += httpApi
} else {
if (callMap[it] != null) {
val codeCall = callMap[it]!!
val name = naming(codeCall.NodeName, codeCall.FunctionName)
if (httpAdapterMap[name] != null) {
val call = httpAdapterMap[name]!!
var httpApi = ContainerDemand()
when (call.ApiType) {
"axios" -> {
httpApi = axiosIdent.convert(call)
}
"umi" -> {
httpApi = umiIdent.convert(call)
}
}
httpApi.source_caller = name
httpApi.call_routes = listOf(it, name)
componentRef.demands += httpApi
}
}
}
}
if (componentRef.demands.isNotEmpty()) {
componentCalls += componentRef
}
}
return componentCalls
}
private fun analysisByPath(node: CodeDataStruct, workspace: String) {
var isComponent: Boolean
val isComponentExt = node.fileExt() == "tsx" || node.fileExt() == "jsx"
val isNotInterface = node.Type != DataStructType.INTERFACE
val inbounds = createInbounds(workspace, node.Imports, node.FilePath)
val moduleName = relativeRoot(workspace, node.FilePath).substringBeforeLast('.', "")
val componentName = naming(moduleName, node.NodeName)
node.Fields.forEach { field ->
fieldToCallMap(field, componentName, inbounds)
}
// lookup CodeCall from Functions
node.Functions.forEach { func ->
isComponent = isNotInterface && isComponentExt && func.IsReturnHtml
if (isComponent) {
componentCallMap[componentName] = mutableListOf()
}
var calleeName = naming(componentName, func.Name)
if (isComponent) {
calleeName = componentName
}
func.FunctionCalls.forEach { call ->
callMap[calleeName] = call
// TODO: refactor by DI
if (axiosIdent.isMatch(call, node.Imports)) {
httpAdapterMap[calleeName] = ApiCodeCall.from(call, "axios")
}
if (umiIdent.isMatch(call, node.Imports)) {
httpAdapterMap[calleeName] = ApiCodeCall.from(call, "umi")
}
if (isComponent) {
componentCallMap[componentName]?.plusAssign((call.FunctionName))
}
}
recursiveCall(func, calleeName, isComponent, componentName, node.Imports)
if (isComponent) {
componentInbounds[componentName] = inbounds
}
}
}
private fun fieldToCallMap(
field: CodeField,
componentName: String,
inbounds: MutableList
) {
field.Calls.forEach {
val calleeName = naming(componentName, field.TypeKey)
callMap[calleeName] = it
it.Parameters.forEach {
inbounds.forEach { inbound ->
if (inbound.endsWith("::${it.TypeValue}")) {
val split = inbound.split("::")
callMap[calleeName] = CodeCall(FunctionName = split[1], NodeName = split[0])
}
}
}
}
}
private fun createInbounds(workspace: String, imports: Array, filePath: String): MutableList {
val inbounds: MutableList = mutableListOf()
imports.forEach { imp ->
imp.UsageName.forEach {
val source = ecmaImportConvert(workspace, filePath, imp.Source)
inbounds += naming(source, it)
}
}
return inbounds
}
private fun recursiveCall(
func: CodeFunction,
calleeName: String,
isComponent: Boolean,
componentName: String,
imports: Array
) {
func.InnerFunctions.forEach { inner ->
run {
inner.FunctionCalls.forEach { innerCall ->
run {
callMap[calleeName] = innerCall
if (umiIdent.isMatch(innerCall, imports)) {
httpAdapterMap[calleeName] = ApiCodeCall.from(innerCall, "umi")
}
if (axiosIdent.isMatch(innerCall, imports)) {
httpAdapterMap[calleeName] = ApiCodeCall.from(innerCall, "axios")
}
if (isComponent) {
componentCallMap[componentName]?.plusAssign((innerCall.FunctionName))
}
}
}
}
func.InnerFunctions.forEach {
recursiveCall(it, calleeName, isComponent, componentName, imports)
}
}
}
}