Skip to content

Commit d1323f9

Browse files
committed
feat(javascript): add ReactUtil and ReactUtilTest #81
This commit adds the `ReactUtil` object, which contains functions for extracting React components from JavaScript files. It also includes the corresponding unit test `ReactUtilTest` to ensure the functionality of the `ReactUtil` methods.
1 parent 146ea56 commit d1323f9

File tree

4 files changed

+126
-72
lines changed

4 files changed

+126
-72
lines changed

javascript/src/main/kotlin/cc/unitmesh/ide/javascript/flow/ReactAutoPage.kt

Lines changed: 4 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
11
package cc.unitmesh.ide.javascript.flow
22

3-
import cc.unitmesh.ide.javascript.util.JSPsiUtil
3+
import cc.unitmesh.ide.javascript.util.ReactUtil
44
import com.intellij.lang.ecmascript6.JSXHarmonyFileType
55
import com.intellij.lang.javascript.JavaScriptFileType
66
import com.intellij.lang.javascript.TypeScriptJSXFileType
77
import com.intellij.lang.javascript.dialects.TypeScriptJSXLanguageDialect
88
import com.intellij.lang.javascript.psi.JSFile
9-
import com.intellij.lang.javascript.psi.ecma6.TypeScriptClass
10-
import com.intellij.lang.javascript.psi.ecma6.TypeScriptFunction
11-
import com.intellij.lang.javascript.psi.ecma6.TypeScriptFunctionExpression
12-
import com.intellij.lang.javascript.psi.ecma6.TypeScriptVariable
139
import com.intellij.openapi.editor.Editor
1410
import com.intellij.openapi.project.Project
1511
import com.intellij.openapi.project.guessProjectDir
1612
import com.intellij.psi.search.FileTypeIndex
1713
import com.intellij.psi.search.GlobalSearchScope
1814
import com.intellij.psi.search.ProjectScope
19-
import com.intellij.psi.util.PsiTreeUtil
2015
// keep this import
16+
import kotlinx.serialization.decodeFromString
2117
import kotlinx.serialization.json.Json
2218

2319
enum class RouterFile(val filename: String) {
@@ -85,7 +81,7 @@ class ReactAutoPage(
8581
override fun getPages(): List<DsComponent> = pages.mapNotNull {
8682
when (it.language) {
8783
is TypeScriptJSXLanguageDialect -> {
88-
Companion.tsxComponentToComponent(it)
84+
ReactUtil.tsxComponentToComponent(it)
8985
}
9086

9187
else -> null
@@ -124,45 +120,5 @@ class ReactAutoPage(
124120
override fun clarify(): String {
125121
TODO("Not yet implemented")
126122
}
123+
}
127124

128-
companion object {
129-
fun tsxComponentToComponent(jsFile: JSFile): List<DsComponent> {
130-
val exportElements = JSPsiUtil.getExportElements(jsFile)
131-
return exportElements.map { psiElement ->
132-
val name = psiElement.name ?: return@map null
133-
val path = jsFile.virtualFile.canonicalPath ?: return@map null
134-
// is React Functional Component
135-
return@map when (psiElement) {
136-
is TypeScriptFunction -> {
137-
DsComponent(name = name, path)
138-
}
139-
140-
is TypeScriptClass -> {
141-
DsComponent(name = name, path)
142-
}
143-
144-
is TypeScriptVariable -> {
145-
val funcExpr = PsiTreeUtil
146-
.findChildrenOfType(psiElement, TypeScriptFunctionExpression::class.java)
147-
.firstOrNull() ?: return@map null
148-
149-
val map = funcExpr.parameterList?.parameters?.mapNotNull { parameter ->
150-
if (parameter.typeElement != null) {
151-
parameter.typeElement
152-
} else {
153-
null
154-
}
155-
} ?: emptyList()
156-
157-
DsComponent(name = name, path)
158-
}
159-
160-
else -> {
161-
println("unknown type: ${psiElement::class.java}")
162-
null
163-
}
164-
}
165-
}.filterNotNull()
166-
}
167-
}
168-
}

javascript/src/main/kotlin/cc/unitmesh/ide/javascript/util/JSPsiUtil.kt

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -57,30 +57,6 @@ object JSPsiUtil {
5757
}
5858
}
5959

60-
fun getExportElements(file: JSFile): List<PsiNameIdentifierOwner> {
61-
val exportDeclarations =
62-
PsiTreeUtil.getChildrenOfTypeAsList(file, ES6ExportDeclaration::class.java)
63-
64-
val map = exportDeclarations.map { exportDeclaration ->
65-
exportDeclaration.exportSpecifiers
66-
.asSequence()
67-
.mapNotNull {
68-
it.alias?.findAliasedElement()
69-
}
70-
.filterIsInstance<PsiNameIdentifierOwner>()
71-
.toList()
72-
}.flatten()
73-
74-
val defaultAssignments = PsiTreeUtil.getChildrenOfTypeAsList(file, ES6ExportDefaultAssignment::class.java)
75-
val defaultAssignment = defaultAssignments.mapNotNull {
76-
val jsReferenceExpression = it.expression as? JSReferenceExpression ?: return@mapNotNull null
77-
val resolveReference = JSResolveResult.resolveReference(jsReferenceExpression)
78-
resolveReference.firstOrNull() as? PsiNameIdentifierOwner
79-
}
80-
81-
return map + defaultAssignment
82-
}
83-
8460
private fun skipDeclaration(element: PsiElement): Boolean {
8561
return when (element) {
8662
is JSParameter, is TypeScriptGenericOrMappedTypeParameter -> true
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package cc.unitmesh.ide.javascript.util
2+
3+
import cc.unitmesh.ide.javascript.flow.DsComponent
4+
import com.intellij.lang.ecmascript6.psi.ES6ExportDeclaration
5+
import com.intellij.lang.ecmascript6.psi.ES6ExportDefaultAssignment
6+
import com.intellij.lang.javascript.psi.JSFile
7+
import com.intellij.lang.javascript.psi.JSFunctionExpression
8+
import com.intellij.lang.javascript.psi.JSReferenceExpression
9+
import com.intellij.lang.javascript.psi.JSVariable
10+
import com.intellij.lang.javascript.psi.ecma6.TypeScriptClass
11+
import com.intellij.lang.javascript.psi.ecma6.TypeScriptFunction
12+
import com.intellij.lang.javascript.psi.ecma6.TypeScriptFunctionExpression
13+
import com.intellij.lang.javascript.psi.ecma6.TypeScriptVariable
14+
import com.intellij.lang.javascript.psi.resolve.JSResolveResult
15+
import com.intellij.psi.PsiNameIdentifierOwner
16+
import com.intellij.psi.util.PsiTreeUtil
17+
18+
object ReactUtil {
19+
private fun getExportElements(file: JSFile): List<PsiNameIdentifierOwner> {
20+
val exportDeclarations =
21+
PsiTreeUtil.getChildrenOfTypeAsList(file, ES6ExportDeclaration::class.java)
22+
23+
val map = exportDeclarations.map { exportDeclaration ->
24+
exportDeclaration.exportSpecifiers
25+
.asSequence()
26+
.mapNotNull {
27+
it.alias?.findAliasedElement()
28+
}
29+
.filterIsInstance<PsiNameIdentifierOwner>()
30+
.toList()
31+
}.flatten()
32+
33+
val defaultAssignments = PsiTreeUtil.getChildrenOfTypeAsList(file, ES6ExportDefaultAssignment::class.java)
34+
val defaultAssignment = defaultAssignments.mapNotNull {
35+
val jsReferenceExpression = it.expression as? JSReferenceExpression ?: return@mapNotNull null
36+
val resolveReference = JSResolveResult.resolveReference(jsReferenceExpression)
37+
resolveReference.firstOrNull() as? PsiNameIdentifierOwner
38+
}
39+
40+
return map + defaultAssignment
41+
}
42+
43+
fun tsxComponentToComponent(jsFile: JSFile): List<DsComponent> {
44+
val exportElements = getExportElements(jsFile)
45+
return exportElements.map { psiElement ->
46+
val name = psiElement.name ?: return@map null
47+
val path = jsFile.virtualFile.canonicalPath ?: return@map null
48+
return@map when (psiElement) {
49+
is TypeScriptFunction -> {
50+
DsComponent(name = name, path)
51+
}
52+
53+
is TypeScriptClass -> {
54+
DsComponent(name = name, path)
55+
}
56+
57+
is TypeScriptVariable -> {
58+
val funcExpr = PsiTreeUtil.findChildrenOfType(psiElement, TypeScriptFunctionExpression::class.java)
59+
.firstOrNull() ?: return@map null
60+
61+
val map = funcExpr.parameterList?.parameters?.mapNotNull { parameter ->
62+
if (parameter.typeElement != null) {
63+
parameter.typeElement
64+
} else {
65+
null
66+
}
67+
} ?: emptyList()
68+
69+
DsComponent(name = name, path)
70+
}
71+
72+
is JSVariable -> {
73+
val funcExpr = PsiTreeUtil.findChildrenOfType(psiElement, JSFunctionExpression::class.java)
74+
.firstOrNull() ?: return@map null
75+
76+
val map = funcExpr.parameterList?.parameters?.mapNotNull { parameter ->
77+
if (parameter.typeElement != null) {
78+
parameter.typeElement
79+
} else {
80+
null
81+
}
82+
} ?: emptyList()
83+
84+
DsComponent(name = name, path)
85+
}
86+
87+
else -> {
88+
println("unknown type: ${psiElement::class.java}")
89+
null
90+
}
91+
}
92+
}.filterNotNull()
93+
}
94+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package cc.unitmesh.ide.javascript.util;
2+
3+
import com.intellij.lang.javascript.JavascriptLanguage
4+
import com.intellij.lang.javascript.dialects.TypeScriptJSXLanguageDialect
5+
import com.intellij.lang.javascript.dialects.TypeScriptLanguageDialect
6+
import com.intellij.lang.javascript.psi.JSFile
7+
import com.intellij.lang.javascript.psi.ecmal4.JSClass
8+
import com.intellij.psi.PsiFileFactory
9+
import com.intellij.testFramework.LightPlatformTestCase
10+
11+
class ReactUtilTest : LightPlatformTestCase() {
12+
fun testShouldHandleExportReactComponent() {
13+
val code = """
14+
import type { AppProps } from 'next/app';
15+
16+
const MyApp = ({ Component, pageProps }: AppProps) => (
17+
<Component {...pageProps} />
18+
);
19+
20+
export default MyApp;
21+
""".trimIndent()
22+
23+
val file = PsiFileFactory.getInstance(project).createFileFromText(JavascriptLanguage.INSTANCE, code)
24+
val result = ReactUtil.tsxComponentToComponent(file as JSFile)
25+
26+
assertEquals(1, result.size)
27+
}
28+
}

0 commit comments

Comments
 (0)