chapi-ast-typescript/src/main/kotlin/chapi/ast/typescriptast/TypeScriptFullIdentListener.kt
package chapi.ast.typescriptast
import chapi.ast.antlr.TypeScriptParser
import chapi.ast.antlr.TypeScriptParser.IdentifierExpressionContext
import chapi.ast.antlr.TypeScriptParser.ParenthesizedExpressionContext
import chapi.ast.antlr.TypeScriptParser.VariableStatementContext
import chapi.domain.core.*
import chapi.infra.Stack
import org.antlr.v4.runtime.tree.TerminalNodeImpl
class TypeScriptFullIdentListener(val node: TSIdentify) : TypeScriptAstListener() {
private var hasHtmlElement: Boolean = false
private var filePath: String = node.filePath
private var exitArrowCount: Int = 0
private var isCallbackOrAnonymousFunction: Boolean = false
private var hasAnnotation: Boolean = false
private var hasEnterClass = false
private var currentExprIdent: String = ""
private var localVars = mutableMapOf<String, String>()
private var nodeMap = mutableMapOf<String, CodeDataStruct>()
private var codeContainer: CodeContainer =
CodeContainer(FullName = node.filePath, PackageName = node.resolvePackage())
private var currentNode = CodeDataStruct()
private var defaultNode = CodeDataStruct()
private var currentFunc = CodeFunction(IsConstructor = false)
private var namespaceName: String = ""
private var currentAnnotations = listOf<CodeAnnotation>()
private var funcsStackForCount = Stack<CodeFunction>()
private var classNodeStack = Stack<CodeDataStruct>()
override fun enterNamespaceDeclaration(ctx: TypeScriptParser.NamespaceDeclarationContext?) {
this.namespaceName = ctx!!.namespaceName().text
}
override fun exitNamespaceDeclaration(ctx: TypeScriptParser.NamespaceDeclarationContext?) {
this.namespaceName = ""
}
override fun enterVariableStatement(ctx: VariableStatementContext?) {
val isExport = ctx!!.parent.parent.getChild(0).text == "export"
if (!isExport && ctx.variableDeclarationList() != null) {
defaultNode.Fields = variableToFields(ctx.variableDeclarationList())
}
// such as: `export const baseURL = '/api'`
// todo: add support for not only const?
if (!isExport) return
var modifier = ""
ctx.children.forEach { child ->
when (child) {
is TypeScriptParser.VarModifierContext -> {
modifier = child.text
}
is TypeScriptParser.VariableDeclarationListContext -> {
val fields = variableToFields(child, listOf(modifier))
if (fields.isNotEmpty()) {
defaultNode.Exports += CodeExport(
Name = fields[0].TypeKey,
Type = DataStructType.Variable,
SourceFile = codeContainer.FullName
)
defaultNode.Fields += fields
}
}
is TerminalNodeImpl -> {
}
else -> {
// println("enterVariableStatement: ${it::class.java.simpleName} === ${it.text}")
}
}
}
}
private fun variableToFields(
varDecl: TypeScriptParser.VariableDeclarationListContext,
modifiers: List<String> = listOf()
): List<CodeField> {
return varDecl.variableDeclaration()
.filter { it.Assign() != null }
.map { decl ->
variableToField(decl, modifiers)
}
}
private fun variableToField(
it: TypeScriptParser.VariableDeclarationContext,
modifiers: List<String>
): CodeField {
val key = it.getChild(0).text
val singleExpression = it.singleExpression()
val lastExpr = singleExpression
val field = CodeField(TypeKey = key, TypeValue = lastExpr.text, Modifiers = modifiers)
when (lastExpr) {
is TypeScriptParser.LiteralExpressionContext -> {
if (lastExpr.literal().StringLiteral() != null) {
field.TypeValue = unQuote(lastExpr.text)
field.TypeType = "String"
}
}
is IdentifierExpressionContext -> {
singleExprToFieldCall(field, lastExpr.singleExpression(), lastExpr.identifierName().text)
}
is TypeScriptParser.YieldExpressionContext -> {
if (lastExpr.yieldStatement().expressionSequence() != null) {
val singeExprs = lastExpr.yieldStatement().expressionSequence().singleExpression()
singeExprs.forEach { expr ->
singleExprToFieldCall(field, expr, expr.text)
}
}
}
else -> {
// println("variableToFields -> ${lastExpr.text} === ${lastExpr.javaClass.simpleName}")
}
}
return field
}
private fun singleExprToFieldCall(
field: CodeField,
singleExpr: TypeScriptParser.SingleExpressionContext?,
funcName: String
) {
if (singleExpr != null) {
when (singleExpr) {
is TypeScriptParser.ParenthesizedExpressionContext -> {
val parameters = parseParenthesizedExpression(singleExpr)
field.Calls += CodeCall("", CallType.FIELD, "", funcName, parameters)
}
is IdentifierExpressionContext -> {
field.Calls += CodeCall("", CallType.FIELD, "", singleExpr.identifierName().text)
}
else -> {
// println("todo -> var -> decl call: $name")
}
}
}
}
override fun enterDecoratorList(ctx: TypeScriptParser.DecoratorListContext?) {
if (!hasEnterClass) {
hasAnnotation = true
ctx!!.decorator()
.asSequence()
.map { buildAnnotation(it) }
.forEach { currentAnnotations += it }
}
}
override fun enterClassDeclaration(ctx: TypeScriptParser.ClassDeclarationContext?) {
val nodeName = ctx!!.identifierName().text
hasEnterClass = true
currentNode = CodeDataStruct(
NodeName = nodeName,
Type = DataStructType.CLASS,
Package = codeContainer.PackageName,
FilePath = codeContainer.FullName,
Position = buildPosition(ctx)
)
val heritageCtx = ctx.classHeritage()
if (heritageCtx.classImplementsClause() != null) {
val typeList = heritageCtx.classImplementsClause().classOrInterfaceTypeList()
currentNode.Implements = buildImplements(typeList)
}
if (heritageCtx.classExtendsClause() != null) {
val refCtx = heritageCtx.classExtendsClause().parameterizedTypeRef()
currentNode.Extend = refCtx.typeName().text
}
// load annotations before class
currentNode.Annotations = currentAnnotations
currentAnnotations = listOf()
hasAnnotation = false
if (ctx.classBody()?.classMemberList() != null) {
this.handleClassBodyElements(ctx.classBody())
}
classNodeStack.push(currentNode)
nodeMap[nodeName] = currentNode
}
private fun handleClassBodyElements(classTailCtx: TypeScriptParser.ClassBodyContext) {
for (clzElementCtx in classTailCtx.classMemberList().classMember()) {
val childCtx = clzElementCtx.getChild(0) ?: continue
when (childCtx) {
is TypeScriptParser.ConstructorDeclarationContext -> {
val codeFunction = this.buildConstructorMethod(childCtx)
codeFunction.FilePath = filePath
currentNode.Functions += codeFunction
}
is TypeScriptParser.PropertyDeclarationExpressionContext -> {
val codeField = CodeField(TypeKey = childCtx.propertyName().text)
val modifier = childCtx.propertyMemberBase().text
if (modifier != "") {
codeField.Modifiers += modifier
}
if (childCtx.typeAnnotation() != null) {
codeField.TypeType = this.buildTypeAnnotation(childCtx.typeAnnotation())!!
}
currentNode.Fields += codeField
}
is TypeScriptParser.MethodDeclarationExpressionContext -> {
val codeFunction = CodeFunction(
Name = childCtx.propertyName().text, Position = buildPosition(childCtx)
)
val callSignCtx = childCtx.callSignature()
if (callSignCtx.typeRef() != null) {
codeFunction.ReturnType = processRef(callSignCtx.typeRef())
}
codeFunction.FilePath = filePath
currentNode.Functions += codeFunction
}
is TypeScriptParser.GetterSetterDeclarationExpressionContext -> {
val name = if (childCtx.getAccessor() != null) {
childCtx.getAccessor().identifierName()?.text ?: "get"
} else {
childCtx.setAccessor().identifierName()?.text ?: "set"
}
currentNode.Functions += CodeFunction(
Name = name, Position = buildPosition(childCtx)
)
}
is TypeScriptParser.AbstractMemberDeclarationContext -> {
childCtx.abstractDeclaration().let {
val name = it.identifierName().text
val type = if (it.typeAnnotation() != null) {
buildTypeAnnotation(it.typeAnnotation())
} else {
"void"
}
currentNode.Functions += CodeFunction(
Name = name, Position = buildPosition(childCtx), ReturnType = type
)
}
}
else -> {
// println("handleClassBodyElements -> childElementType : ${childCtx.javaClass.simpleName}")
}
}
}
}
private fun buildConstructorMethod(ctx: TypeScriptParser.ConstructorDeclarationContext): CodeFunction {
val codeFunction = CodeFunction(
Name = "constructor", Position = this.buildPosition(ctx)
)
if (ctx.accessibilityModifier() != null) {
codeFunction.Modifiers += ctx.accessibilityModifier().text
}
if (ctx.formalParameterList() != null) {
codeFunction.Parameters += buildParameters(ctx.formalParameterList())
}
return codeFunction
}
private fun buildImplements(typeList: TypeScriptParser.ClassOrInterfaceTypeListContext?): List<String> {
return typeList?.parameterizedTypeRef()?.map { typeRefCtx ->
typeRefCtx.typeName().text
} ?: listOf()
}
override fun exitClassDeclaration(ctx: TypeScriptParser.ClassDeclarationContext?) {
hasEnterClass = false
classNodeStack.pop()
}
override fun enterInterfaceDeclaration(ctx: TypeScriptParser.InterfaceDeclarationContext?) {
val nodeName = ctx!!.identifierName().text
val currentType = DataStructType.INTERFACE
currentNode = CodeDataStruct(
NodeName = nodeName,
Type = currentType,
Package = codeContainer.PackageName,
FilePath = codeContainer.FullName,
Position = buildPosition(ctx)
)
if (ctx.interfaceExtendsClause() != null) {
val elements = buildImplements(ctx.interfaceExtendsClause().classOrInterfaceTypeList())
currentNode.Extend = elements[0]
}
if (ctx.interfaceBody() != null) {
this.buildInterfaceBody(ctx.interfaceBody().interfaceMemberList())
}
nodeMap[nodeName] = currentNode
}
override fun exitInterfaceDeclaration(ctx: TypeScriptParser.InterfaceDeclarationContext?) {
currentNode = CodeDataStruct(Package = codeContainer.PackageName, FilePath = codeContainer.FullName)
}
private fun buildInterfaceBody(typeMemberList: TypeScriptParser.InterfaceMemberListContext) {
typeMemberList.interfaceMember()?.forEach { memberContext ->
when (val memberChild = memberContext.getChild(0)) {
is TypeScriptParser.PropertySignatureContext -> {
buildInterfacePropertySignature(memberChild)
}
is TypeScriptParser.MethodSignatureContext -> {
val func = CodeFunction(
Name = memberChild.propertyName().text
)
fillMethodFromCallSignature(memberChild.callSignature(), func)
func.FilePath = filePath
currentNode.Functions += func
}
else -> {
// println("enterInterfaceDeclaration -> buildInterfaceBody")
}
}
}
}
private fun buildInterfacePropertySignature(signCtx: TypeScriptParser.PropertySignatureContext) {
val annotation = signCtx.typeAnnotation()
val typeType = buildTypeAnnotation(annotation)
val typeValue = signCtx.propertyName().text
val codeField = CodeField(TypeType = typeType, TypeValue = typeValue)
currentNode.Fields += codeField
// val isArrowFunc = annotation.typeRef() != null
// if (isArrowFunc) {
// val codeFunction = CodeFunction(
// Name = typeValue
// )
// val param = CodeProperty(
// TypeValue = "any", TypeType = typeType
// )
//
// val returnType = CodeProperty(
// TypeType = annotation.typeRef().text, TypeValue = ""
// )
//
// codeFunction.Parameters += param
// codeFunction.MultipleReturns += returnType
//
// codeFunction.FilePath = filePath
// currentNode.Functions += codeFunction
// } else {
// }
}
override fun enterFromBlock(ctx: TypeScriptParser.FromBlockContext?) {
val imp = unQuote(ctx!!.StringLiteral().text)
val codeImport = CodeImport(
Source = imp
)
if (ctx.moduleItems() != null) {
for (nameContext in ctx.moduleItems().aliasName()) {
if (nameContext.identifierName().isNotEmpty()) {
codeImport.UsageName += nameContext.identifierName()[0].text
if (nameContext.As() != null) {
codeImport.AsName += nameContext.identifierName()[1].text
}
}
// if (nameContext.Of() != null) {
// codeImport.UsageName += nameContext.Of().text
// codeImport.AsName += nameContext.Of().text
// }
}
}
val importNamespace = ctx.importNamespace()
if (importNamespace != null) {
// for examples: import '*' from 'blabla';
val isImportAll = importNamespace.Multiply() != null
if (isImportAll) {
codeImport.UsageName += "*"
if (importNamespace.As() != null) {
codeImport.AsName += importNamespace.identifierName()[0].text
}
} else {
codeImport.UsageName += importNamespace.identifierName()[0].text
if (importNamespace.As() != null) {
codeImport.AsName += importNamespace.identifierName()[1].text
}
}
}
if (ctx.Dollar() != null) {
codeImport.UsageName += ctx.Dollar().text
}
if (ctx.Lodash() != null) {
codeImport.UsageName += ctx.Lodash().text
}
codeContainer.Imports += codeImport
}
private fun unQuote(text: String): String = text.replace("[\"']".toRegex(), "")
override fun enterImportAliasDeclaration(ctx: TypeScriptParser.ImportAliasDeclarationContext?) {
val imp = unQuote(ctx!!.StringLiteral().text)
val codeImport = CodeImport(
Source = imp
)
if (ctx.Require() != null) {
codeImport.UsageName += ctx.identifierName().text
}
codeContainer.Imports += codeImport
}
override fun enterImportAll(ctx: TypeScriptParser.ImportAllContext?) {
codeContainer.Imports += CodeImport(
Source = unQuote(ctx!!.StringLiteral().text)
)
}
// see also in arrow function declaration
override fun enterFunctionDeclaration(ctx: TypeScriptParser.FunctionDeclarationContext?) {
if (ctx == null) return
if (ctx.identifierName() == null) return
val funcName = ctx.identifierName().text
val func = CodeFunction(FilePath = filePath)
func.Name = funcName
fillMethodFromCallSignature(ctx.callSignature(), func)
func.Position = this.buildPosition(ctx)
isCallbackOrAnonymousFunction = false
processingNewArrowFunc(func)
if (ctx.functionBody() == null) {
return
}
ctx.functionBody()?.statementList()?.statement()?.forEach { context ->
parseStatement(context)
}
}
// todo: align logic to arrow functions
override fun exitFunctionDeclaration(ctx: TypeScriptParser.FunctionDeclarationContext?) {
handleFuncDeclExit()
}
// see also in function declaration
override fun enterArrowFunctionDeclaration(ctx: TypeScriptParser.ArrowFunctionDeclarationContext?) {
isCallbackOrAnonymousFunction = false
when (val grad = ctx?.parent?.parent) {
// for: const blabla = () => { }
is TypeScriptParser.VariableDeclarationContext -> {
val func = CodeFunction(
FilePath = filePath,
Name = grad.identifierName().text,
Parameters = this.buildArrowFunctionParameters(ctx.arrowFunctionParameters()),
Position = this.buildPosition(ctx)
)
if (ctx.typeAnnotation() != null) {
func.MultipleReturns += buildReturnTypeByType(ctx.typeAnnotation())
}
isCallbackOrAnonymousFunction = false
processingNewArrowFunc(func)
}
is TypeScriptParser.ArgumentContext -> {
// todo: add arg ctx
isCallbackOrAnonymousFunction = true
currentFunc.FunctionCalls += CodeCall(FunctionName = currentExprIdent, Type = CallType.ARROW)
}
// such as: `(e) => e.stopPropagation()`
is TypeScriptParser.ExpressionSequenceContext -> {
val func = CodeFunction(FilePath = filePath, Name = "")
isCallbackOrAnonymousFunction = true
processingNewArrowFunc(func)
}
else -> {
// val text = ctx!!.parent.parent.text
// println("enterArrowFunctionDeclaration -> $parentName, $text")
}
}
if (ctx?.arrowFunctionBody() == null) return
ctx.arrowFunctionBody()?.statementList()?.statement()?.forEach {
parseStatement(it)
}
if (ctx.arrowFunctionBody().singleExpression() != null) {
parseSingleExpression(ctx.arrowFunctionBody().singleExpression())
}
}
private fun processingNewArrowFunc(func: CodeFunction) {
if (isCallbackOrAnonymousFunction) {
return
}
if (funcsStackForCount.count() != 0) {
currentFunc.InnerFunctions += func
}
funcsStackForCount.push(func)
currentFunc = func
}
override fun exitArrowFunctionDeclaration(ctx: TypeScriptParser.ArrowFunctionDeclarationContext?) {
handleFuncDeclExit()
}
private fun handleFuncDeclExit() {
if (!isCallbackOrAnonymousFunction) {
val pop = funcsStackForCount.pop()
if (pop != null) {
if (funcsStackForCount.count() != 0) {
currentFunc = funcsStackForCount.peek()!!
}
}
exitArrowCount = funcsStackForCount.count()
}
isCallbackOrAnonymousFunction = false
if (funcsStackForCount.count() == 0) {
defaultNode.Functions += currentFunc
currentFunc = CodeFunction()
}
}
private fun buildArrowFunctionParameters(arrowFuncCtx: TypeScriptParser.ArrowFunctionParametersContext?): List<CodeProperty> {
if (arrowFuncCtx!!.formalParameterList() != null) {
return this.buildParameters(arrowFuncCtx.formalParameterList())
}
var parameters: List<CodeProperty> = listOf()
if (arrowFuncCtx.identifierName() != null) {
parameters += CodeProperty(
TypeValue = arrowFuncCtx.identifierName().text, TypeType = "any"
)
}
return parameters
}
override fun enterFunctionExpression(ctx: TypeScriptParser.FunctionExpressionContext?) {
when (val grad = ctx?.parent?.parent) {
is TypeScriptParser.VariableDeclarationContext -> {
currentFunc.Name = grad.identifierName().text
if (ctx.formalParameterList() != null) {
currentFunc.Parameters = this.buildParameters(ctx.formalParameterList())
}
if (ctx.typeAnnotation() != null) {
currentFunc.MultipleReturns += buildReturnTypeByType(ctx.typeAnnotation())
}
currentFunc.Position = this.buildPosition(ctx)
currentFunc.FilePath = filePath
defaultNode.Functions += currentFunc
}
is IdentifierExpressionContext -> {
currentFunc.Position = this.buildPosition(ctx)
currentFunc.FilePath = filePath
defaultNode.Functions += currentFunc
}
else -> {
// println("enterFunctionExpressionDeclaration -> $parentName, ${statementParent.text} ")
}
}
}
private fun parseStatement(context: TypeScriptParser.StatementContext) {
when (val child = context.getChild(0)) {
is TypeScriptParser.ReturnStatementContext -> {
if (child.expressionSequence() != null) {
child.expressionSequence().singleExpression().forEach(::parseSingleExpression)
}
if (node.isJsxFile()) {
currentFunc.IsReturnHtml = true
}
}
else -> {
// println("parseStmt childType -> :${child.javaClass.name}")
}
}
}
private fun singleExpToText(ctx: TypeScriptParser.SingleExpressionContext): String {
var text = ctx.text
when (ctx) {
is TypeScriptParser.LiteralExpressionContext -> {
val singleStr = text.startsWith("'") && text.endsWith("'")
val doubleStr = text.startsWith("\"") && text.endsWith("\"")
val templateStr = text.startsWith("`") && text.endsWith("`")
if (singleStr || doubleStr || templateStr) {
text = text.drop(1).dropLast(1)
}
}
}
return text
}
private fun parseSingleExpression(ctx: TypeScriptParser.SingleExpressionContext) {
when (ctx) {
is IdentifierExpressionContext -> {
currentExprIdent = ctx.identifierName().text
if (ctx.singleExpression() != null) {
parseSingleExpression(ctx.singleExpression())
}
}
is TypeScriptParser.GenericTypesContext -> {
if (ctx.expressionSequence() != null) {
parseExpressionSequence(ctx.expressionSequence())
}
}
is TypeScriptParser.ArgumentsExpressionContext -> {
argumentsExpressionToCall(ctx)
}
is ParenthesizedExpressionContext -> {
val parameters = parseParenthesizedExpression(ctx)
currentFunc.FunctionCalls += CodeCall("", CallType.FUNCTION, "", currentExprIdent, parameters)
}
else -> {
// println("todo -> need support type: ${ctx::class.java.simpleName} ==== ${ctx.text}")
}
}
}
private fun argumentsExpressionToCall(argument: TypeScriptParser.ArgumentsExpressionContext, varName: String = "") {
val argText = argument.singleExpression().text
if (varName != "") {
currentExprIdent = varName
}
if (argText.startsWith("(")) {
// axios("/demo")
val args = parseArguments(argument)
currentFunc.FunctionCalls += CodeCall("", CallType.FUNCTION, "", currentExprIdent, args)
} else {
currentExprIdent = argText
// todo: add other case for call chain in arrow function
if (argText.contains(").")) {
val args = parseArguments(argument)
// println("argText with )." + Json.encodeToString(args))
}
// axios.get() or _.orderBy()
currentFunc.FunctionCalls += CodeCall("", CallType.FUNCTION, "", currentExprIdent, listOf())
}
}
private fun parseArguments(argument: TypeScriptParser.ArgumentsExpressionContext): List<CodeProperty> {
var params = listOf<CodeProperty>()
// for: `axios<Module[]>({parameter}).then`
// create then and update parameter
argument.children.forEach {
when (it) {
is TypeScriptParser.MemberDotExpressionContext -> {
val singleExpression = it.singleExpression()
when (val expr = singleExpression.first()) {
is TypeScriptParser.ParenthesizedExpressionContext -> {
params += parseParenthesizedExpression(expr)
}
is TypeScriptParser.ArgumentsExpressionContext -> {
// request.get('/api/v1/xxx?id=1').then(function(response){console.log(response)}).catch()
parseArguments(expr)
}
is IdentifierExpressionContext -> {
currentExprIdent = expr.identifierName().text
}
else -> {
// println("MemberDotExpressionContext: -> ${expr.text}")
}
}
currentExprIdent += "->${it.identifierName().text}"
}
is ParenthesizedExpressionContext -> {
params += parseParenthesizedExpression(it)
}
else -> {
// println("singleExpression -> ${it.javaClass.simpleName} -> ${it.text}")
}
}
}
return params
}
private fun parseParenthesizedExpression(context: ParenthesizedExpressionContext): List<CodeProperty> {
return context.expressionSequence().singleExpression().map { subSingle ->
var parameter = CodeProperty(TypeValue = "", TypeType = "object")
when (subSingle) {
is TypeScriptParser.ObjectLiteralExpressionContext -> {
val objectLiteral = parseObjectLiteral(subSingle.objectLiteral())
parameter =
CodeProperty(TypeValue = subSingle.text, TypeType = "object", ObjectValue = objectLiteral)
}
is TypeScriptParser.ArgumentsExpressionContext -> {
parseArguments(subSingle)
}
is TypeScriptParser.IdentifierExpressionContext -> {
parameter = CodeProperty(TypeValue = subSingle.text, TypeType = "parameter")
}
is TypeScriptParser.ArrayLiteralExpressionContext -> {
parameter = CodeProperty(TypeValue = "[]", TypeType = "parameter")
}
is TypeScriptParser.LiteralExpressionContext -> {
parameter = CodeProperty(TypeValue = singleExpToText(subSingle), TypeType = "string")
}
else -> {
// println("todo -> ParenthesizedExpressionContext: ${subSingle.javaClass.simpleName}, text: ${subSingle.text}")
}
}
parameter
}
}
private fun parseObjectLiteral(objectLiteral: TypeScriptParser.ObjectLiteralContext): List<CodeProperty> {
return objectLiteral.propertyAssignment().mapNotNull { property ->
when (property) {
is TypeScriptParser.PropertyExpressionAssignmentContext -> {
val text = if (property.singleExpression() != null) {
property.singleExpression().text
} else {
property.text
}
val value = CodeProperty(TypeType = "value", TypeValue = text)
val propText = property.propertyName().text
CodeProperty(TypeType = "key", TypeValue = propText, ObjectValue = listOf(value))
}
is TypeScriptParser.PropertyShorthandContext -> {
val short = property.text
val value = CodeProperty(TypeType = "value", TypeValue = short)
CodeProperty(TypeType = "key", TypeValue = short, ObjectValue = listOf(value))
}
else -> {
null
}
}
}
}
private fun parseExpressionSequence(ctx: TypeScriptParser.ExpressionSequenceContext) {
ctx.singleExpression().forEach { singleExpressionContext ->
parseSingleExpression(singleExpressionContext)
}
}
private fun fillMethodFromCallSignature(
callSignCtx: TypeScriptParser.CallSignatureContext,
currentFunc: CodeFunction
) {
if (callSignCtx.parameterList() != null) {
val parameters = buildMethodParameters(callSignCtx.parameterList())
currentFunc.Parameters = parameters
}
if (callSignCtx.typeRef() != null) {
val returnType = buildReturnTypeByTypeRef(callSignCtx.typeRef()!!)
currentFunc.MultipleReturns += returnType
}
}
private fun buildReturnTypeByType(typeAnnotationContext: TypeScriptParser.TypeAnnotationContext?): CodeProperty =
CodeProperty(
TypeType = buildTypeAnnotation(typeAnnotationContext) ?: "", TypeValue = ""
)
private fun buildReturnTypeByTypeRef(typeRefContext: TypeScriptParser.TypeRefContext): CodeProperty =
CodeProperty(
TypeType = processRef(typeRefContext) ?: "", TypeValue = ""
)
override fun enterExpressionStatement(ctx: TypeScriptParser.ExpressionStatementContext?) {
if (ctx?.expressionSequence() == null) return
for (singleExprCtx in ctx.expressionSequence().singleExpression()) {
when (singleExprCtx) {
is TypeScriptParser.ArgumentsExpressionContext -> {
currentFunc.FunctionCalls += CodeCall(
Parameters = processArgumentList(singleExprCtx.argumentList()),
FunctionName = buildFunctionName(singleExprCtx),
NodeName = wrapTargetType(singleExprCtx),
Position = buildPosition(ctx)
)
}
is TypeScriptParser.IdentifierExpressionContext -> {
currentFunc.FunctionCalls += CodeCall(
Parameters = listOf(),
FunctionName = singleExprCtx.identifierName().text,
NodeName = "",
Position = buildPosition(ctx)
)
}
else -> {
// println("enterExpressionStatement : ${singleExprCtx.javaClass.simpleName}")
}
}
}
}
private fun buildFunctionName(argsCtx: TypeScriptParser.ArgumentsExpressionContext): String {
val name = functionNameFromArguments(argsCtx)
if (name.isEmpty()) {
var text = argsCtx.singleExpression().text
if (text.contains(".")) {
val split = text.split(".")
text = split[split.size - 1]
}
return text
}
return name
}
private fun functionNameFromArguments(argsCtx: TypeScriptParser.ArgumentsExpressionContext): String {
return when (val singleExpr = argsCtx.singleExpression()) {
is TypeScriptParser.ArgumentsExpressionContext -> {
functionNameFromArguments(singleExpr)
}
is TypeScriptParser.MemberDotExpressionContext -> {
when (val child = singleExpr.singleExpression().first()) {
is TypeScriptParser.ArgumentsExpressionContext -> {
functionNameFromArguments(child)
}
else -> {
""
}
}
}
is IdentifierExpressionContext -> {
singleExpr.identifierName().text
}
else -> {
""
}
}
}
private fun wrapTargetType(argsCtx: TypeScriptParser.ArgumentsExpressionContext): String {
var text = argsCtx.singleExpression().text
if (text.contains(".")) {
text = text.split(".")[0]
}
if (localVars[text] != null && localVars[text] != "") {
text = localVars[text]
}
if (text == null) {
text = ""
}
return text
}
/**
* create local var when new object
*/
override fun enterVariableDeclaration(ctx: TypeScriptParser.VariableDeclarationContext?) {
if (ctx == null) {
return
}
if (ctx.children == null) {
return
}
val varName = ctx.getChild(0).text
when (val singleExprCtx = ctx.singleExpression()) {
is TypeScriptParser.NewExpressionContext -> {
when (val newSingleExpr = singleExprCtx.singleExpression()) {
// todo: legacy expression, remove
is IdentifierExpressionContext -> {
localVars[varName] = newSingleExpr.identifierName().text
}
is TypeScriptParser.ArgumentsExpressionContext -> {
localVars[varName] = newSingleExpr.singleExpression().text
}
else -> {
// println("enterVariableDeclaration : $singleCtxType === ${ctx.text}")
}
}
}
is IdentifierExpressionContext -> {
when (singleExprCtx.identifierName().text) {
"await" -> {
parseSingleExpression(singleExprCtx.singleExpression())
}
"Number" -> {
parseSingleExpression(singleExprCtx.singleExpression())
}
else -> {
// println("IdentifierExpressionContext -> ${singleExprCtx.text}")
}
}
}
is TypeScriptParser.AwaitExpressionContext -> {
parseSingleExpression(singleExprCtx.singleExpression())
}
is TypeScriptParser.ArrowFunctionExpressionLContext -> {
// will recall by ArrowFunctionDeclaration
}
is TypeScriptParser.ArgumentsExpressionContext -> {
argumentsExpressionToCall(singleExprCtx, varName)
}
is ParenthesizedExpressionContext -> {
parseParenthesizedExpression(singleExprCtx)
}
}
}
private fun buildArguments(arguments: TypeScriptParser.ArgumentsContext?): List<CodeProperty> {
if (arguments?.getChild(1)?.text == ")") {
return listOf()
}
val argumentList = arguments?.argumentList()
return processArgumentList(argumentList)
}
private fun processArgumentList(argumentList: TypeScriptParser.ArgumentListContext?) =
argumentList?.argument()?.map {
parseSingleExpression(it.singleExpression())
val typeValue: String = when (val expr = it.singleExpression()) {
is TypeScriptParser.LiteralExpressionContext -> {
if (expr.literal().templateStringLiteral() != null) {
expr.literal().templateStringLiteral().text.drop(1).dropLast(1)
} else {
it.text
}
}
else -> {
it.text;
}
}
CodeProperty(TypeValue = typeValue, TypeType = "")
} ?: listOf()
override fun enterExportElementDirectly(ctx: TypeScriptParser.ExportElementDirectlyContext?) {
when (val stmt = ctx?.declarationStatement()?.getChild(0)) {
is VariableStatementContext -> {
stmt.variableDeclarationList().variableDeclaration().forEach {
val text = it.identifierName().text
currentNode.Exports += CodeExport(text)
defaultNode.Exports += CodeExport(text)
}
}
}
}
override fun enterExportDefaultDeclaration(ctx: TypeScriptParser.ExportDefaultDeclarationContext?) {
val name = ctx!!.identifierName()
if (name != null) {
currentNode.Exports += CodeExport(name.text)
defaultNode.Exports += CodeExport(name.text)
}
}
fun getNodeInfo(): CodeContainer {
for (entry in nodeMap) {
codeContainer.DataStructures += entry.value
}
// for: `export const baseURL = '/api'`
val fieldOnly = defaultNode.Fields.isNotEmpty()
// for export default function
val functionOnly = defaultNode.Functions.isNotEmpty()
if (functionOnly) {
defaultNode.NodeName = "default"
defaultNode.FilePath = codeContainer.FullName
defaultNode.Package = codeContainer.PackageName
codeContainer.DataStructures += defaultNode
} else if (fieldOnly) {
defaultNode.NodeName = "default"
defaultNode.FilePath = codeContainer.FullName
defaultNode.Package = codeContainer.PackageName
codeContainer.DataStructures += defaultNode
}
return codeContainer
}
// override fun enterEveryRule(ctx: ParserRuleContext?) {
// println(ctx!!.javaClass.simpleName)
// }
}