@@ -2,8 +2,9 @@ package com.emberjs.utils
22
33import com.dmarcotte.handlebars.parsing.HbTokenTypes
44import com.dmarcotte.handlebars.psi.*
5+ import com.dmarcotte.handlebars.psi.impl.HbDataImpl
56import com.dmarcotte.handlebars.psi.impl.HbPathImpl
6- import com.emberjs.EmberAttrDec
7+ import com.emberjs.*
78import com.emberjs.hbs.HbsLocalReference
89import com.emberjs.hbs.HbsModuleReference
910import com.emberjs.hbs.ImportNameReferences
@@ -18,18 +19,30 @@ import com.intellij.lang.javascript.psi.*
1819import com.intellij.lang.javascript.psi.ecma6.TypeScriptTypeArgumentList
1920import com.intellij.lang.javascript.psi.ecma6.impl.TypeScriptClassImpl
2021import com.intellij.lang.javascript.psi.ecmal4.JSClass
22+ import com.intellij.lang.javascript.psi.jsdoc.JSDocComment
2123import 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
2626import com.intellij.psi.impl.source.tree.LeafPsiElement
2727import com.intellij.psi.util.PsiTreeUtil
2828import com.intellij.psi.util.elementType
2929import com.intellij.psi.util.parents
3030import com.intellij.psi.xml.XmlAttribute
3131import 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
3447class 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