Skip to content

Commit 2734789

Browse files
committed
feat(vue): enhance Vue-related class provider and add tests #309
- Added `VueRelatedClassProviderTest` for testing Vue-related class lookup functionality. - Enhanced `VueRelatedClassProvider` to handle Vue file imports and resolve module references. - Included `bundledPlugin("org.jetbrains.plugins.vue")` in the IntelliJ platform configuration. - Added `plugin.xml` for Vue module registration.
1 parent eef9456 commit 2734789

File tree

4 files changed

+182
-6
lines changed

4 files changed

+182
-6
lines changed

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,7 @@ project(":exts:ext-vue") {
607607
intellijPlatform {
608608
intellijIde(prop("ideaVersion"))
609609
intellijPlugins(ideaPlugins + prop("vuePlugin"))
610+
bundledPlugin("org.jetbrains.plugins.vue")
610611
}
611612

612613
implementation(project(":core"))
Lines changed: 122 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,137 @@
1+
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
12
package cc.unitmesh.vue.provider
23

34
import cc.unitmesh.devti.provider.RelatedClassesProvider
5+
import com.intellij.javascript.nodejs.PackageJsonData
6+
import com.intellij.lang.ecmascript6.psi.*
7+
import com.intellij.lang.ecmascript6.psi.impl.ES6ImportPsiUtil.ES6_IMPORT_DECLARATION
8+
import com.intellij.lang.ecmascript6.resolve.ES6PsiUtil
9+
import com.intellij.lang.ecmascript6.resolve.JSFileReferencesUtil
10+
import com.intellij.lang.javascript.buildTools.npm.PackageJsonUtil
11+
import com.intellij.lang.javascript.psi.ecma6.TypeScriptPropertySignature
12+
import com.intellij.lang.javascript.psi.resolve.JSResolveUtil
13+
import com.intellij.openapi.diagnostic.logger
14+
import com.intellij.openapi.util.text.StringUtil
415
import com.intellij.psi.PsiElement
516
import com.intellij.psi.PsiFile
6-
import org.jetbrains.vuejs.lang.html.VueLanguage
17+
import com.intellij.psi.impl.source.html.HtmlFileImpl
18+
import com.intellij.util.asSafely
19+
import com.intellij.webSymbols.SymbolKind
20+
import org.jetbrains.vuejs.index.findModule
21+
import org.jetbrains.vuejs.index.findScriptTag
22+
import org.jetbrains.vuejs.lang.html.VueFile
23+
import java.util.*
24+
725

826
/**
927
* AutoDev Modular for Vue
1028
*/
1129
class VueRelatedClassProvider : RelatedClassesProvider {
12-
override fun lookup(element: PsiElement): MutableList<PsiElement> {
13-
if (element.language !is VueLanguage) return mutableListOf<PsiElement>()
30+
override fun lookup(element: PsiElement): List<PsiElement> {
31+
if (element !is VueFile) return emptyList()
32+
33+
return emptyList()
34+
}
35+
36+
override fun lookup(psiFile: PsiFile): List<PsiElement> {
37+
if (psiFile !is VueFile) return emptyList()
38+
39+
val scriptTag = findScriptTag(psiFile, true) ?: findScriptTag(psiFile, false) ?: return emptyList()
40+
// val jsContent = PsiTreeUtil.getChildOfType(scriptTag, JSEmbeddedContent::class.java)
41+
// ?: return mutableListOf<PsiElement>()
42+
val kind = ""
43+
val localImports = sequenceOf(findModule(scriptTag, false), findModule(scriptTag, true))
44+
.flatMap {
45+
JSResolveUtil.getStubbedChildren(it, ES6_IMPORT_DECLARATION).asSequence()
46+
}
47+
.filterIsInstance<ES6ImportDeclaration>()
48+
.map {
49+
it.children.mapNotNull { source ->
50+
when (source) {
51+
is ES6ImportSpecifierAlias -> symbolLocationsFromSpecifier(source.findSpecifierElement() as? ES6ImportSpecifier, kind)
52+
is ES6ImportSpecifier -> symbolLocationsFromSpecifier(source, kind)
53+
is ES6ImportedBinding -> symbolLocationsForModule(source, source.declaration?.fromClause?.referenceText, "default", kind)
54+
is TypeScriptPropertySignature -> symbolLocationFromPropertySignature(source, kind)?.let { listOf(it) }
55+
is ES6ExportDefaultAssignment, is HtmlFileImpl -> source.containingFile.virtualFile?.url?.let {
56+
listOf(WebTypesSymbolLocation(it, "default", kind))
57+
}
58+
else -> null
59+
}
60+
61+
}
62+
}.flatten()
63+
64+
65+
logger<VueRelatedClassProvider>().info("imports: $localImports")
66+
/// resolve file in local
67+
return emptyList()
68+
}
1469

15-
return mutableListOf<PsiElement>()
70+
private fun symbolLocationsFromSpecifier(specifier: ES6ImportSpecifier?, symbolKind: String): List<WebTypesSymbolLocation> {
71+
if (specifier?.specifierKind == ES6ImportExportSpecifier.ImportExportSpecifierKind.IMPORT) {
72+
val symbolName = if (specifier.isDefault) "default" else specifier.referenceName
73+
val moduleName = specifier.declaration?.fromClause?.referenceText
74+
return symbolLocationsForModule(specifier, moduleName, symbolName, symbolKind)
75+
}
76+
return emptyList()
1677
}
1778

18-
override fun lookup(element: PsiFile): MutableList<PsiElement> {
19-
return mutableListOf<PsiElement>()
79+
private fun symbolLocationsForModule(context: PsiElement,
80+
moduleName: String?,
81+
symbolName: String?,
82+
symbolKind: String): List<WebTypesSymbolLocation> =
83+
if (symbolName != null && moduleName != null) {
84+
val result = mutableListOf<WebTypesSymbolLocation>()
85+
val unquotedModule = StringUtil.unquoteString(moduleName)
86+
if (!unquotedModule.startsWith(".")) {
87+
result.add(WebTypesSymbolLocation(unquotedModule.lowercase(Locale.US), symbolName, symbolKind))
88+
}
89+
90+
if (unquotedModule.contains('/')) {
91+
val modules = JSFileReferencesUtil.resolveModuleReference(context, unquotedModule)
92+
modules.mapNotNullTo(result) {
93+
it.containingFile?.originalFile?.virtualFile?.url?.let { url ->
94+
WebTypesSymbolLocation(url, symbolName, symbolKind)
95+
}
96+
}
97+
// A workaround to avoid full resolution in case of components in subpackages
98+
if (symbolName == "default"
99+
&& !unquotedModule.startsWith(".")
100+
&& unquotedModule.count { it == '/' } == 1) {
101+
102+
modules.mapNotNullTo(result) {
103+
ES6PsiUtil.findDefaultExport(it)
104+
?.asSafely<ES6ExportDefaultAssignment>()
105+
?.initializerReference
106+
?.let { symbolName ->
107+
WebTypesSymbolLocation(unquotedModule.takeWhile { it != '/' }, symbolName, symbolKind)
108+
}
109+
}
110+
}
111+
}
112+
result
113+
}
114+
else emptyList()
115+
116+
private fun symbolLocationFromPropertySignature(property: TypeScriptPropertySignature, kind: SymbolKind): WebTypesSymbolLocation? {
117+
if (!property.isValid) return null
118+
119+
// TypeScript GlobalComponents definition
120+
val symbolName = property.memberName.takeIf { it.isNotEmpty() }
121+
?: return null
122+
123+
// Locate module
124+
val packageName = property.containingFile?.originalFile?.virtualFile?.let { PackageJsonUtil.findUpPackageJson(it) }
125+
?.let { PackageJsonData.getOrCreate(it) }
126+
?.name
127+
?: return null
128+
129+
return WebTypesSymbolLocation(packageName.lowercase(Locale.US), symbolName, kind)
20130
}
131+
132+
private data class WebTypesSymbolLocation(
133+
val moduleName: String,
134+
val symbolName: String,
135+
val symbolKind: String,
136+
)
21137
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package cc.unitmesh.vue.provider
2+
3+
import com.intellij.psi.PsiFileFactory
4+
import com.intellij.sql.psi.SqlLanguage
5+
import com.intellij.testFramework.LightPlatformTestCase
6+
import org.intellij.lang.annotations.Language
7+
import org.jetbrains.vuejs.lang.html.VueLanguage
8+
9+
class VueRelatedClassProviderTest : LightPlatformTestCase() {
10+
@Language("Vue")
11+
private val code = """
12+
<template>
13+
<div>
14+
<h1>Hello, Vue!</h1>
15+
</div>
16+
</template>
17+
18+
<script>
19+
export default {
20+
name: 'HelloWorld',
21+
data() {
22+
return {
23+
message: 'Welcome to Your Vue.js App'
24+
}
25+
},
26+
methods: {
27+
greet() {
28+
alert(this.message)
29+
}
30+
}
31+
}
32+
</script>
33+
34+
<style scoped>
35+
h1 {
36+
color: #42b983;
37+
}
38+
</style>
39+
""".trimIndent()
40+
41+
fun testShouldReturnEmptyListWhenLookupElementIsCalledWithNonVueElement() {
42+
val vueRelatedClassProvider = VueRelatedClassProvider()
43+
44+
// createVueFileFromText
45+
val file =
46+
PsiFileFactory.getInstance(project).createFileFromText("temp.vue", VueLanguage.INSTANCE, code)
47+
48+
vueRelatedClassProvider.lookup(file)
49+
}
50+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<idea-plugin package="cc.unitmesh" xmlns:xi="http://www.w3.org/2001/XInclude">
2+
<id>cc.unitmesh.devti</id>
3+
<xi:include href="/META-INF/autodev-core.xml" xpointer="xpointer(/idea-plugin/*)"/>
4+
5+
<!--suppress PluginXmlValidity -->
6+
<content>
7+
<module name="cc.unitmesh.vue"/>
8+
</content>
9+
</idea-plugin>

0 commit comments

Comments
 (0)