diff --git a/javascript/src/main/kotlin/cc/unitmesh/ide/javascript/flow/ReactAutoPage.kt b/javascript/src/main/kotlin/cc/unitmesh/ide/javascript/flow/ReactAutoPage.kt index 85137426b8..920632130f 100644 --- a/javascript/src/main/kotlin/cc/unitmesh/ide/javascript/flow/ReactAutoPage.kt +++ b/javascript/src/main/kotlin/cc/unitmesh/ide/javascript/flow/ReactAutoPage.kt @@ -1,23 +1,19 @@ package cc.unitmesh.ide.javascript.flow -import cc.unitmesh.ide.javascript.util.JSPsiUtil +import cc.unitmesh.ide.javascript.util.ReactUtil import com.intellij.lang.ecmascript6.JSXHarmonyFileType import com.intellij.lang.javascript.JavaScriptFileType import com.intellij.lang.javascript.TypeScriptJSXFileType import com.intellij.lang.javascript.dialects.TypeScriptJSXLanguageDialect import com.intellij.lang.javascript.psi.JSFile -import com.intellij.lang.javascript.psi.ecma6.TypeScriptClass -import com.intellij.lang.javascript.psi.ecma6.TypeScriptFunction -import com.intellij.lang.javascript.psi.ecma6.TypeScriptFunctionExpression -import com.intellij.lang.javascript.psi.ecma6.TypeScriptVariable import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.intellij.psi.search.FileTypeIndex import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.ProjectScope -import com.intellij.psi.util.PsiTreeUtil // keep this import +import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json enum class RouterFile(val filename: String) { @@ -85,7 +81,7 @@ class ReactAutoPage( override fun getPages(): List = pages.mapNotNull { when (it.language) { is TypeScriptJSXLanguageDialect -> { - Companion.tsxComponentToComponent(it) + ReactUtil.tsxComponentToComponent(it) } else -> null @@ -124,45 +120,5 @@ class ReactAutoPage( override fun clarify(): String { TODO("Not yet implemented") } +} - companion object { - fun tsxComponentToComponent(jsFile: JSFile): List { - val exportElements = JSPsiUtil.getExportElements(jsFile) - return exportElements.map { psiElement -> - val name = psiElement.name ?: return@map null - val path = jsFile.virtualFile.canonicalPath ?: return@map null - // is React Functional Component - return@map when (psiElement) { - is TypeScriptFunction -> { - DsComponent(name = name, path) - } - - is TypeScriptClass -> { - DsComponent(name = name, path) - } - - is TypeScriptVariable -> { - val funcExpr = PsiTreeUtil - .findChildrenOfType(psiElement, TypeScriptFunctionExpression::class.java) - .firstOrNull() ?: return@map null - - val map = funcExpr.parameterList?.parameters?.mapNotNull { parameter -> - if (parameter.typeElement != null) { - parameter.typeElement - } else { - null - } - } ?: emptyList() - - DsComponent(name = name, path) - } - - else -> { - println("unknown type: ${psiElement::class.java}") - null - } - } - }.filterNotNull() - } - } -} \ No newline at end of file diff --git a/javascript/src/main/kotlin/cc/unitmesh/ide/javascript/util/JSPsiUtil.kt b/javascript/src/main/kotlin/cc/unitmesh/ide/javascript/util/JSPsiUtil.kt index 918b8caea7..46e8b5ffa2 100644 --- a/javascript/src/main/kotlin/cc/unitmesh/ide/javascript/util/JSPsiUtil.kt +++ b/javascript/src/main/kotlin/cc/unitmesh/ide/javascript/util/JSPsiUtil.kt @@ -57,30 +57,6 @@ object JSPsiUtil { } } - fun getExportElements(file: JSFile): List { - val exportDeclarations = - PsiTreeUtil.getChildrenOfTypeAsList(file, ES6ExportDeclaration::class.java) - - val map = exportDeclarations.map { exportDeclaration -> - exportDeclaration.exportSpecifiers - .asSequence() - .mapNotNull { - it.alias?.findAliasedElement() - } - .filterIsInstance() - .toList() - }.flatten() - - val defaultAssignments = PsiTreeUtil.getChildrenOfTypeAsList(file, ES6ExportDefaultAssignment::class.java) - val defaultAssignment = defaultAssignments.mapNotNull { - val jsReferenceExpression = it.expression as? JSReferenceExpression ?: return@mapNotNull null - val resolveReference = JSResolveResult.resolveReference(jsReferenceExpression) - resolveReference.firstOrNull() as? PsiNameIdentifierOwner - } - - return map + defaultAssignment - } - private fun skipDeclaration(element: PsiElement): Boolean { return when (element) { is JSParameter, is TypeScriptGenericOrMappedTypeParameter -> true diff --git a/javascript/src/main/kotlin/cc/unitmesh/ide/javascript/util/ReactUtil.kt b/javascript/src/main/kotlin/cc/unitmesh/ide/javascript/util/ReactUtil.kt new file mode 100644 index 0000000000..d01c91e120 --- /dev/null +++ b/javascript/src/main/kotlin/cc/unitmesh/ide/javascript/util/ReactUtil.kt @@ -0,0 +1,94 @@ +package cc.unitmesh.ide.javascript.util + +import cc.unitmesh.ide.javascript.flow.DsComponent +import com.intellij.lang.ecmascript6.psi.ES6ExportDeclaration +import com.intellij.lang.ecmascript6.psi.ES6ExportDefaultAssignment +import com.intellij.lang.javascript.psi.JSFile +import com.intellij.lang.javascript.psi.JSFunctionExpression +import com.intellij.lang.javascript.psi.JSReferenceExpression +import com.intellij.lang.javascript.psi.JSVariable +import com.intellij.lang.javascript.psi.ecma6.TypeScriptClass +import com.intellij.lang.javascript.psi.ecma6.TypeScriptFunction +import com.intellij.lang.javascript.psi.ecma6.TypeScriptFunctionExpression +import com.intellij.lang.javascript.psi.ecma6.TypeScriptVariable +import com.intellij.lang.javascript.psi.resolve.JSResolveResult +import com.intellij.psi.PsiNameIdentifierOwner +import com.intellij.psi.util.PsiTreeUtil + +object ReactUtil { + private fun getExportElements(file: JSFile): List { + val exportDeclarations = + PsiTreeUtil.getChildrenOfTypeAsList(file, ES6ExportDeclaration::class.java) + + val map = exportDeclarations.map { exportDeclaration -> + exportDeclaration.exportSpecifiers + .asSequence() + .mapNotNull { + it.alias?.findAliasedElement() + } + .filterIsInstance() + .toList() + }.flatten() + + val defaultAssignments = PsiTreeUtil.getChildrenOfTypeAsList(file, ES6ExportDefaultAssignment::class.java) + val defaultAssignment = defaultAssignments.mapNotNull { + val jsReferenceExpression = it.expression as? JSReferenceExpression ?: return@mapNotNull null + val resolveReference = JSResolveResult.resolveReference(jsReferenceExpression) + resolveReference.firstOrNull() as? PsiNameIdentifierOwner + } + + return map + defaultAssignment + } + + fun tsxComponentToComponent(jsFile: JSFile): List { + val exportElements = getExportElements(jsFile) + return exportElements.map { psiElement -> + val name = psiElement.name ?: return@map null + val path = jsFile.virtualFile.canonicalPath ?: return@map null + return@map when (psiElement) { + is TypeScriptFunction -> { + DsComponent(name = name, path) + } + + is TypeScriptClass -> { + DsComponent(name = name, path) + } + + is TypeScriptVariable -> { + val funcExpr = PsiTreeUtil.findChildrenOfType(psiElement, TypeScriptFunctionExpression::class.java) + .firstOrNull() ?: return@map null + + val map = funcExpr.parameterList?.parameters?.mapNotNull { parameter -> + if (parameter.typeElement != null) { + parameter.typeElement + } else { + null + } + } ?: emptyList() + + DsComponent(name = name, path) + } + + is JSVariable -> { + val funcExpr = PsiTreeUtil.findChildrenOfType(psiElement, JSFunctionExpression::class.java) + .firstOrNull() ?: return@map null + + val map = funcExpr.parameterList?.parameters?.mapNotNull { parameter -> + if (parameter.typeElement != null) { + parameter.typeElement + } else { + null + } + } ?: emptyList() + + DsComponent(name = name, path) + } + + else -> { + println("unknown type: ${psiElement::class.java}") + null + } + } + }.filterNotNull() + } +} \ No newline at end of file diff --git a/javascript/src/test/kotlin/cc/unitmesh/ide/javascript/util/ReactUtilTest.kt b/javascript/src/test/kotlin/cc/unitmesh/ide/javascript/util/ReactUtilTest.kt new file mode 100644 index 0000000000..872f18551e --- /dev/null +++ b/javascript/src/test/kotlin/cc/unitmesh/ide/javascript/util/ReactUtilTest.kt @@ -0,0 +1,28 @@ +package cc.unitmesh.ide.javascript.util; + +import com.intellij.lang.javascript.JavascriptLanguage +import com.intellij.lang.javascript.dialects.TypeScriptJSXLanguageDialect +import com.intellij.lang.javascript.dialects.TypeScriptLanguageDialect +import com.intellij.lang.javascript.psi.JSFile +import com.intellij.lang.javascript.psi.ecmal4.JSClass +import com.intellij.psi.PsiFileFactory +import com.intellij.testFramework.LightPlatformTestCase + +class ReactUtilTest : LightPlatformTestCase() { + fun testShouldHandleExportReactComponent() { + val code = """ + import type { AppProps } from 'next/app'; + + const MyApp = ({ Component, pageProps }: AppProps) => ( + + ); + + export default MyApp; + """.trimIndent() + + val file = PsiFileFactory.getInstance(project).createFileFromText(JavascriptLanguage.INSTANCE, code) + val result = ReactUtil.tsxComponentToComponent(file as JSFile) + + assertEquals(1, result.size) + } +}