Skip to content

Commit 812c8f7

Browse files
committed
- fix: correctly resolve paths in hash
- feature: add parameter autocompletion for `{{component x param=1 ...}}`
1 parent 98b8094 commit 812c8f7

File tree

7 files changed

+142
-119
lines changed

7 files changed

+142
-119
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
Changelog
44
===============================================================================
5+
## v2020.3.27
6+
- fix: correctly resolve paths in hash
7+
- feature: add parameter autocompletion for `{{component x param=1 ...}}`
8+
59
## v2020.3.26
610
- feature: add support for args autocompletion in templates (`@xxx`) and reference to own args
711

src/main/kotlin/com/emberjs/EmberXmlElementDescriptor.kt

Lines changed: 1 addition & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,18 @@
11
package com.emberjs
2-
import com.dmarcotte.handlebars.psi.impl.HbDataImpl
3-
import com.dmarcotte.handlebars.psi.impl.HbPathImpl
42
import com.emberjs.psi.EmberNamedElement
53
import com.emberjs.utils.*
64
import com.intellij.codeInsight.documentation.DocumentationManager.ORIGINAL_ELEMENT_KEY
7-
import com.intellij.lang.javascript.psi.JSElement
8-
import com.intellij.lang.javascript.psi.jsdoc.JSDocComment
95
import com.intellij.psi.*
10-
import com.intellij.psi.impl.file.PsiDirectoryImpl
116
import com.intellij.psi.impl.source.html.dtd.HtmlNSDescriptorImpl
127
import com.intellij.psi.impl.source.xml.XmlDescriptorUtil
13-
import com.intellij.psi.util.PsiTreeUtil
148
import com.intellij.psi.xml.XmlAttribute
159
import com.intellij.psi.xml.XmlTag
1610
import com.intellij.xml.XmlAttributeDescriptor
1711
import com.intellij.xml.XmlElementDescriptor
1812
import com.intellij.xml.XmlElementsGroup
1913
import com.intellij.xml.XmlNSDescriptor
2014

21-
class ArgData(
22-
var value: String = "",
23-
var description: String? = null,
24-
var reference: AttrPsiReference? = null) {}
2515

26-
class ComponentReferenceData(
27-
public val hasSplattributes: Boolean = false,
28-
public val yields: MutableList<EmberXmlElementDescriptor.YieldReference> = mutableListOf(),
29-
public val args: MutableList<ArgData> = mutableListOf()
30-
) {
31-
32-
}
3316

3417
class EmberXmlElementDescriptor(private val tag: XmlTag, private val declaration: PsiElement) : XmlElementDescriptor {
3518
val project = tag.project
@@ -82,25 +65,6 @@ class EmberXmlElementDescriptor(private val tag: XmlTag, private val declaration
8265
}
8366
}
8467

85-
fun getFileByPath(directory: PsiDirectory?, path: String): PsiFile? {
86-
if (directory == null) return null
87-
var dir: PsiDirectory = directory
88-
val parts = path.split("/").toMutableList()
89-
while (parts.isNotEmpty()) {
90-
val p = parts.removeAt(0)
91-
val d: Any? = dir.findSubdirectory(p) ?: dir.findFile(p)
92-
if (d is PsiFile) {
93-
return d
94-
}
95-
if (d is PsiDirectory) {
96-
dir = d
97-
continue
98-
}
99-
return null
100-
}
101-
return null
102-
}
103-
10468
/**
10569
* finds yields and data mustache `@xxx`
10670
* also check .ts/d.ts files for Component<Args>
@@ -116,80 +80,8 @@ class EmberXmlElementDescriptor(private val tag: XmlTag, private val declaration
11680
}
11781
f = EmberUtils.followReferences(target)?.containingFile?.originalFile
11882
val file = f ?: target.containingFile.originalFile
119-
var name = file.name.split(".").first()
120-
val dir = file.parent as PsiDirectoryImpl?
121-
var template: PsiFile? = null
122-
var path = ""
123-
var parentModule: PsiDirectory? = null
124-
val tplArgs = emptyArray<ArgData>().toMutableList()
125-
var tplYields = mutableListOf<YieldReference>()
126-
127-
if (dir != null) {
128-
// co-located
129-
if (name == "component") {
130-
name = "template"
131-
}
132-
template = dir.findFile("$name.hbs")
133-
parentModule = file.parents.find { it is PsiDirectory && it.virtualFile == file.originalVirtualFile?.parentEmberModule} as PsiDirectory?
134-
path = file.parents
135-
.takeWhile { it != parentModule }
136-
.reversed()
137-
.map { (it as PsiFileSystemItem).name }
138-
.joinToString("/")
139-
140-
val fullPathToHbs = path.replace("app/", "addon/") + "/$name.hbs"
141-
template = template
142-
?: getFileByPath(parentModule, fullPathToHbs)
143-
?: getFileByPath(parentModule, fullPathToHbs.replace("/components/", "/templates/components/"))
144-
145-
if (template?.node?.psi != null) {
146-
val args = PsiTreeUtil.collectElementsOfType(template.node.psi, HbDataImpl::class.java)
147-
for (arg in args) {
148-
val argName = arg.text.split(".").first()
149-
if (tplArgs.find { it.value == argName } == null) {
150-
tplArgs.add(ArgData(argName, "", AttrPsiReference(arg)))
151-
}
152-
}
153-
154-
val yields = PsiTreeUtil.collectElements(template.node.psi, { it is HbPathImpl && it.text == "yield" })
155-
for (y in yields) {
156-
tplYields.add(YieldReference(y))
157-
}
158-
}
159-
}
160-
16183

162-
if (name == "template") {
163-
name = "component"
164-
}
165-
val hasSplattributes = template?.text?.contains("...attributes") ?: false
166-
val fullPathToTs = path.replace("app/", "addon/").replace("/templates/", "/") + "/$name.ts"
167-
val fullPathToDts = path.replace("app/", "addon/").replace("/templates/", "/") + "/$name.d.ts"
168-
var containingFile = target.containingFile
169-
if (containingFile.name.endsWith(".js")) {
170-
containingFile = dir?.findFile(containingFile.name.replace(".js", ".d.ts"))
171-
}
172-
val tsFile = getFileByPath(parentModule, fullPathToTs) ?: getFileByPath(parentModule, fullPathToDts) ?: containingFile
173-
val cls = tsFile?.let {EmberUtils.findDefaultExportClass(tsFile)}
174-
?: containingFile?.let {EmberUtils.findDefaultExportClass(containingFile)}
175-
?: target
176-
if (cls is JSElement) {
177-
val argsElem = EmberUtils.findComponentArgsType(cls)
178-
val signatures = argsElem?.properties ?: emptyList()
179-
for (sign in signatures) {
180-
val comment = sign.memberSource.singleElement?.children?.find { it is JSDocComment }
181-
// val s: TypeScriptSingleTypeImpl? = sign.children.find { it is TypeScriptSingleTypeImpl } as TypeScriptSingleTypeImpl?
182-
val attr = sign.toString().split(":").last()
183-
val data = tplArgs.find { it.value == attr } ?: ArgData()
184-
data.value = attr
185-
data.reference = AttrPsiReference(sign.memberSource.singleElement!!)
186-
data.description = comment?.text ?: ""
187-
if (tplArgs.find { it.value == attr } == null) {
188-
tplArgs.add(data)
189-
}
190-
}
191-
}
192-
return ComponentReferenceData(hasSplattributes, tplYields, tplArgs)
84+
return EmberUtils.getComponentReferenceData(file)
19385
}
19486

19587
override fun getAttributesDescriptors(context: XmlTag?): Array<out XmlAttributeDescriptor> {

src/main/kotlin/com/emberjs/hbs/HbsLocalCompletion.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import com.intellij.lang.Language
1818
import com.intellij.lang.ecmascript6.psi.ES6ImportDeclaration
1919
import com.intellij.lang.javascript.psi.*
2020
import com.intellij.lang.javascript.psi.ecma6.JSTypedEntity
21+
import com.intellij.lang.javascript.psi.ecmal4.JSClass
2122
import com.intellij.lang.javascript.psi.impl.JSVariableImpl
2223
import com.intellij.lang.javascript.psi.jsdoc.impl.JSDocCommentImpl
2324
import com.intellij.lang.javascript.psi.types.JSRecordTypeImpl
@@ -121,6 +122,10 @@ class HbsLocalCompletion : CompletionProvider<CompletionParameters>() {
121122
if (file is JSFunction) {
122123
func = file
123124
}
125+
if (file is JSClass) {
126+
val ref = EmberUtils.getComponentReferenceData(file.containingFile)
127+
result.addAllElements(ref.args.map { LookupElementBuilder.create(it.value + "=") })
128+
}
124129
if (file is PsiFile) {
125130
func = EmberUtils.resolveHelper(file)
126131
}
@@ -255,6 +260,10 @@ class HbsLocalCompletion : CompletionProvider<CompletionParameters>() {
255260
val helperElement = EmberUtils.findFirstHbsParamFromParam(element)
256261
if (helperElement != null) {
257262
addHelperCompletions(helperElement, result)
263+
val r = EmberUtils.handleEmberHelpers(helperElement.parent)
264+
if (r != null) {
265+
addHelperCompletions(r.children[0].children[0], result)
266+
}
258267
}
259268

260269
if (parameters.position.parent.prevSibling.elementType == HbTokenTypes.SEP) {

src/main/kotlin/com/emberjs/hbs/HbsLocalReference.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,11 @@ class HbsLocalReference(private val leaf: PsiElement, val target: PsiElement?) :
190190
if (any.children[1].text == "hash") {
191191
val name = path.first()
192192
val res = any.children.find { it.elementType == HbTokenTypes.HASH && it.children[0].text == name }
193+
if (res != null && res.children[2].children.firstOrNull() is HbMustacheName) {
194+
val mustacheImpl = res.children[2].children.first()
195+
val lastId = mustacheImpl.children[0].children.findLast { it.elementType == HbTokenTypes.ID }
196+
return resolveToJs(lastId, path.subList(1, max(path.lastIndex, 1)), resolveIncomplete) ?: res
197+
}
193198
val ref = PsiTreeUtil.collectElements(res, { it.references.find { it is HbsLocalReference } != null }).firstOrNull()
194199
if (ref != null) {
195200
val hbsRef = ref.references.find { it is HbsLocalReference }!!

src/main/kotlin/com/emberjs/hbs/HbsParameterInfoHandler.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.dmarcotte.handlebars.psi.HbParam
55
import com.emberjs.utils.*
66
import com.intellij.codeInsight.lookup.LookupElement
77
import com.intellij.lang.javascript.psi.*
8+
import com.intellij.lang.javascript.psi.ecmal4.JSClass
89
import com.intellij.lang.javascript.psi.types.JSArrayType
910
import com.intellij.lang.javascript.psi.types.JSTupleType
1011
import com.intellij.lang.parameterInfo.*

src/main/kotlin/com/emberjs/hbs/HbsParameterNameHints.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import com.intellij.lang.javascript.psi.JSRecordType
1313
import com.intellij.lang.javascript.psi.JSType
1414
import com.intellij.lang.javascript.psi.ecma6.TypeScriptTupleType
1515
import com.intellij.lang.javascript.psi.ecma6.impl.TypeScriptTupleTypeImpl
16+
import com.intellij.lang.javascript.psi.ecmal4.JSClass
1617
import com.intellij.lang.javascript.psi.types.JSTupleType
1718
import com.intellij.lang.javascript.psi.types.JSTypeImpl
1819
import com.intellij.psi.PsiElement
@@ -28,7 +29,7 @@ class HbsParameterNameHints : InlayParameterHintsProvider {
2829

2930
override fun getParameterHints(psiElement: PsiElement): MutableList<InlayInfo> {
3031
if (psiElement is HbParam) {
31-
val firstParam = EmberUtils.findFirstHbsParamFromParam(psiElement)
32+
var firstParam = EmberUtils.findFirstHbsParamFromParam(psiElement)
3233
if (firstParam == null) {
3334
return emptyList<InlayInfo>().toMutableList()
3435
}

src/main/kotlin/com/emberjs/utils/EmberUtils.kt

Lines changed: 120 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ package com.emberjs.utils
22

33
import com.dmarcotte.handlebars.parsing.HbTokenTypes
44
import com.dmarcotte.handlebars.psi.*
5+
import com.dmarcotte.handlebars.psi.impl.HbDataImpl
56
import com.dmarcotte.handlebars.psi.impl.HbPathImpl
6-
import com.emberjs.EmberAttrDec
7+
import com.emberjs.*
78
import com.emberjs.hbs.HbsLocalReference
89
import com.emberjs.hbs.HbsModuleReference
910
import com.emberjs.hbs.ImportNameReferences
@@ -18,18 +19,30 @@ import com.intellij.lang.javascript.psi.*
1819
import com.intellij.lang.javascript.psi.ecma6.TypeScriptTypeArgumentList
1920
import com.intellij.lang.javascript.psi.ecma6.impl.TypeScriptClassImpl
2021
import com.intellij.lang.javascript.psi.ecmal4.JSClass
22+
import com.intellij.lang.javascript.psi.jsdoc.JSDocComment
2123
import com.intellij.lang.javascript.psi.types.JSArrayType
22-
import com.intellij.psi.PsiElement
23-
import com.intellij.psi.PsiFile
24-
import com.intellij.psi.PsiFileSystemItem
25-
import com.intellij.psi.PsiReference
24+
import com.intellij.psi.*
25+
import com.intellij.psi.impl.file.PsiDirectoryImpl
2626
import com.intellij.psi.impl.source.tree.LeafPsiElement
2727
import com.intellij.psi.util.PsiTreeUtil
2828
import com.intellij.psi.util.elementType
2929
import com.intellij.psi.util.parents
3030
import com.intellij.psi.xml.XmlAttribute
3131
import com.intellij.psi.xml.XmlTag
3232

33+
class ArgData(
34+
var value: String = "",
35+
var description: String? = null,
36+
var reference: AttrPsiReference? = null) {}
37+
38+
class ComponentReferenceData(
39+
public val hasSplattributes: Boolean = false,
40+
public val yields: MutableList<EmberXmlElementDescriptor.YieldReference> = mutableListOf(),
41+
public val args: MutableList<ArgData> = mutableListOf()
42+
) {
43+
44+
}
45+
3346

3447
class EmberUtils {
3548
companion object {
@@ -251,16 +264,17 @@ class EmberUtils {
251264
}
252265

253266
fun handleEmberHelpers(element: PsiElement?): PsiElement? {
254-
if (element is PsiElement && element is HbParam && element.text.contains(Regex("^\\(component\\b"))) {
255-
val param = element.children.filterIsInstance<HbParam>().get(1)
267+
if (element is PsiElement && element.text.contains(Regex("^(\\(|\\{\\{)component\\b"))) {
268+
val idx = element.children.indexOfFirst { it.text == "component" }
269+
val param = element.children.get(idx + 1)
256270
if (param.children.firstOrNull()?.children?.firstOrNull() is HbStringLiteral) {
257271
return TagReferencesProvider.forTagName(param.project, param.text.dropLast(1).drop(1).camelize())
258272
}
259273
return param
260274
}
261-
if (element is PsiElement && element is HbParam && element.text.contains(Regex("^\\(or\\b"))) {
275+
if (element is PsiElement && element.text.contains(Regex("^(\\(|\\{\\{)or\\b"))) {
262276
return element.children.find { it is HbParam && it.text != "or" && it.children[0].children[0].references.isNotEmpty() } ?:
263-
element.children.find { it is HbParam && it.children[0].children[0] is HbStringLiteral && it.parent.parent.text.contains(Regex("^\\(component\\b")) }?.let { TagReferencesProvider.forTagName(it.project, it.text.dropLast(1).drop(1).camelize()) }
277+
element.children.find { it is HbParam && it.children[0].children[0] is HbStringLiteral && it.parent.parent.text.contains(Regex("^(\\(|\\{\\{)component\\b")) }?.let { TagReferencesProvider.forTagName(it.project, it.text.dropLast(1).drop(1).camelize()) }
264278
}
265279
if (element is PsiElement && element.parent is HbOpenBlockMustache) {
266280
val mustacheName = element.parent.children.find { it is HbMustacheName }?.text
@@ -328,5 +342,102 @@ class EmberUtils {
328342

329343
return null
330344
}
345+
346+
fun getFileByPath(directory: PsiDirectory?, path: String): PsiFile? {
347+
if (directory == null) return null
348+
var dir: PsiDirectory = directory
349+
val parts = path.split("/").toMutableList()
350+
while (parts.isNotEmpty()) {
351+
val p = parts.removeAt(0)
352+
val d: Any? = dir.findSubdirectory(p) ?: dir.findFile(p)
353+
if (d is PsiFile) {
354+
return d
355+
}
356+
if (d is PsiDirectory) {
357+
dir = d
358+
continue
359+
}
360+
return null
361+
}
362+
return null
363+
}
364+
365+
fun getComponentReferenceData(file: PsiFile): ComponentReferenceData {
366+
var name = file.name.split(".").first()
367+
val dir = file.parent as PsiDirectoryImpl?
368+
var template: PsiFile? = null
369+
var path = ""
370+
var parentModule: PsiDirectory? = null
371+
val tplArgs = emptyArray<ArgData>().toMutableList()
372+
var tplYields = mutableListOf<EmberXmlElementDescriptor.YieldReference>()
373+
374+
if (dir != null) {
375+
// co-located
376+
if (name == "component") {
377+
name = "template"
378+
}
379+
template = dir.findFile("$name.hbs")
380+
parentModule = file.parents.find { it is PsiDirectory && it.virtualFile == file.originalVirtualFile?.parentEmberModule} as PsiDirectory?
381+
path = file.parents(true)
382+
.takeWhile { it != parentModule }
383+
.toList()
384+
.reversed()
385+
.map { (it as PsiFileSystemItem).name }
386+
.joinToString("/")
387+
388+
val fullPathToHbs = path.replace("app/", "addon/") + "/$name.hbs"
389+
template = template
390+
?: getFileByPath(parentModule, fullPathToHbs)
391+
?: getFileByPath(parentModule, fullPathToHbs.replace("/components/", "/templates/components/"))
392+
393+
if (template?.node?.psi != null) {
394+
val args = PsiTreeUtil.collectElementsOfType(template.node.psi, HbDataImpl::class.java)
395+
for (arg in args) {
396+
val argName = arg.text.split(".").first()
397+
if (tplArgs.find { it.value == argName } == null) {
398+
tplArgs.add(ArgData(argName, "", AttrPsiReference(arg)))
399+
}
400+
}
401+
402+
val yields = PsiTreeUtil.collectElements(template.node.psi, { it is HbPathImpl && it.text == "yield" })
403+
for (y in yields) {
404+
tplYields.add(EmberXmlElementDescriptor.YieldReference(y))
405+
}
406+
}
407+
}
408+
409+
410+
if (name == "template") {
411+
name = "component"
412+
}
413+
val hasSplattributes = template?.text?.contains("...attributes") ?: false
414+
val fullPathToTs = path.replace("app/", "addon/").replace("/templates/", "/") + "/$name.ts"
415+
val fullPathToDts = path.replace("app/", "addon/").replace("/templates/", "/") + "/$name.d.ts"
416+
var containingFile = file.containingFile
417+
if (containingFile.name.endsWith(".js")) {
418+
containingFile = dir?.findFile(containingFile.name.replace(".js", ".d.ts"))
419+
}
420+
val tsFile = getFileByPath(parentModule, fullPathToTs) ?: getFileByPath(parentModule, fullPathToDts) ?: containingFile
421+
val cls = tsFile?.let {findDefaultExportClass(tsFile)}
422+
?: containingFile?.let {findDefaultExportClass(containingFile)}
423+
?: file
424+
if (cls is JSElement) {
425+
val argsElem = findComponentArgsType(cls)
426+
val signatures = argsElem?.properties ?: emptyList()
427+
for (sign in signatures) {
428+
val comment = sign.memberSource.singleElement?.children?.find { it is JSDocComment }
429+
// val s: TypeScriptSingleTypeImpl? = sign.children.find { it is TypeScriptSingleTypeImpl } as TypeScriptSingleTypeImpl?
430+
val attr = sign.toString().split(":").last()
431+
val data = tplArgs.find { it.value == attr } ?: ArgData()
432+
data.value = attr
433+
data.reference = AttrPsiReference(sign.memberSource.singleElement!!)
434+
data.description = comment?.text ?: ""
435+
if (tplArgs.find { it.value == attr } == null) {
436+
tplArgs.add(data)
437+
}
438+
}
439+
}
440+
return ComponentReferenceData(hasSplattributes, tplYields, tplArgs)
441+
}
331442
}
332443
}

0 commit comments

Comments
 (0)