Skip to content

Commit

Permalink
wip module dependencies inspection
Browse files Browse the repository at this point in the history
  • Loading branch information
tomblachut committed May 3, 2020
1 parent 7706574 commit 5e9be64
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package dev.blachut.svelte.lang.inspections

import com.intellij.lang.ecmascript6.psi.ES6ImportDeclaration
import com.intellij.lang.ecmascript6.psi.ES6ImportExportDeclarationPart
import com.intellij.lang.ecmascript6.psi.impl.ES6CreateImportUtil
import com.intellij.lang.ecmascript6.psi.impl.ES6ImportPsiUtil
import com.intellij.lang.ecmascript6.psi.impl.ES6ImportPsiUtil.CreateImportExportInfo
import com.intellij.lang.javascript.DialectDetector
import com.intellij.lang.javascript.formatter.JSCodeStyleSettings
import com.intellij.lang.javascript.psi.JSEmbeddedContent
import com.intellij.lang.typescript.psi.TypeScriptAutoImportUtil
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiElement
import com.intellij.psi.XmlElementFactory
import com.intellij.psi.xml.XmlTag
import dev.blachut.svelte.lang.SvelteHTMLLanguage
import dev.blachut.svelte.lang.parsing.js.SvelteJSScriptContentProvider
import dev.blachut.svelte.lang.psi.SvelteHtmlFile
import dev.blachut.svelte.lang.psi.findAncestorScript

/**
* Adapted from com.intellij.lang.ecmascript6.actions.ES6AddImportExecutor
*/
class SvelteAddImportExecutor(val editor: Editor?, val place: PsiElement) {
fun createImportOrUseExisting(info: CreateImportExportInfo, externalModule: PsiElement?, quotedModuleName: String): Boolean {
val type = info.importType
val scope = findOrCreateScriptContent()

return if (tryToUseExistingImport(info, quotedModuleName, externalModule, scope)) {
true
} else {
val importPsi = createTypeScriptOrES6Import(quotedModuleName, info)
if (importPsi == null) {
false
} else {
if (importPsi is ES6ImportDeclaration && type !== ES6ImportPsiUtil.ImportExportType.BARE) {
ES6CreateImportUtil.findPlaceAndInsertES6Import(scope, importPsi, StringUtil.unquoteString(quotedModuleName), editor)
} else {
ES6CreateImportUtil.findPlaceAndInsertAnyImport(scope, importPsi, editor)
}
true
}
}
}

private fun findOrCreateScriptContent(): JSEmbeddedContent {
val parentScript = findAncestorScript(place)
if (parentScript != null) {
// parent module or instance script
return SvelteJSScriptContentProvider.getJsEmbeddedContent(parentScript)!!
}

val currentFile = place.containingFile as SvelteHtmlFile
val instanceScript = currentFile.instanceScript
if (instanceScript != null) {
// TODO empty tag
return SvelteJSScriptContentProvider.getJsEmbeddedContent(instanceScript)!!
}

val elementFactory = XmlElementFactory.getInstance(currentFile.project)
val emptyInstanceScript = elementFactory.createTagFromText("<script>\n</script>", SvelteHTMLLanguage.INSTANCE)
val moduleScript = currentFile.moduleScript
val document = currentFile.document!!

val script = if (moduleScript != null) {
document.addAfter(emptyInstanceScript, moduleScript) as XmlTag
} else {
document.addBefore(emptyInstanceScript, document.firstChild) as XmlTag
}

return SvelteJSScriptContentProvider.getJsEmbeddedContent(script)!!
}

private fun tryToUseExistingImport(info: CreateImportExportInfo, quotedModuleOrNamespaceName: String, externalModule: PsiElement?, scope: PsiElement): Boolean {
val parent: PsiElement = place.parent
if (parent is ES6ImportExportDeclarationPart) {
val grandParent = parent.declaration
if (grandParent is ES6ImportDeclaration && grandParent.getFromClause() == null) {
ES6CreateImportUtil.insertFromClause(parent, grandParent, quotedModuleOrNamespaceName)
return true
}
}
val importType = info.importType
return if (JSCodeStyleSettings.isMergeImports(place) && importType.isES6) {
val possibleImport = ES6ImportPsiUtil.findExistingES6Import(scope, externalModule, quotedModuleOrNamespaceName, info)
possibleImport != null && ES6ImportPsiUtil.tryToAddImportToExistingDeclaration(possibleImport, info)
} else {
false
}
}

private fun createTypeScriptOrES6Import(externalModuleName: String, info: CreateImportExportInfo): PsiElement? {
return if (!info.importType.isES6 && !DialectDetector.isJavaScript(place)) {
TypeScriptAutoImportUtil.createTypeScriptImport(place, info, externalModuleName)
} else {
ES6ImportPsiUtil.createImportOrExport(place, info, externalModuleName)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package dev.blachut.svelte.lang.inspections

import com.intellij.lang.ecmascript6.psi.impl.ES6ImportPsiUtil.CreateImportExportInfo
import com.intellij.lang.ecmascript6.psi.impl.ES6ImportPsiUtil.ImportExportType
import com.intellij.lang.javascript.modules.ImportES6ModuleFix
import com.intellij.lang.javascript.modules.JSModuleFixDescriptor
import com.intellij.lang.javascript.modules.JSPlaceTail
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement

class SvelteImportES6ModuleFix(node: PsiElement, descriptor: JSModuleFixDescriptor, tail: JSPlaceTail?, quoteString: String?, needHint: Boolean) : ImportES6ModuleFix(node, descriptor, tail, quoteString, needHint) {
override fun executeImpl(element: PsiElement, editor: Editor?, scope: PsiElement) {
SvelteAddImportExecutor(editor, element).createImportOrUseExisting(importData, null, myQuotes + this.path + myQuotes)
replaceReferences(element, editor)
}

private val importData: ImportData
get() {
val importedName = myFixDescriptor.importedName
val type = myFixDescriptor.importType
val exportedName = myFixDescriptor.exportedName
return if (exportedName != null) {
val typeToUse = type ?: ImportExportType.SPECIFIER
if (exportedName == importedName) ImportData(this, importedName, typeToUse) else ImportData(this, exportedName, importedName, typeToUse)
} else if (type != null) {
ImportData(this, importedName, type)
} else if (myTail != null) {
val tail = myTail.strings[0]
ImportData(this, tail ?: importedName, ImportExportType.SPECIFIER)
} else {
ImportData(this, importedName, ImportExportType.IMPORT_BINDING_ALL)
}
}

private class ImportData : CreateImportExportInfo {
internal constructor(fix: ImportES6ModuleFix?, exportedName: String?, importedName: String, importType: ImportExportType?) : super(exportedName, importedName, importType!!, true, false)
internal constructor(fix: ImportES6ModuleFix, importedName: String, importType: ImportExportType?) : super(null, importedName, importType!!, true, false)
}



}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package dev.blachut.svelte.lang.inspections

import com.intellij.codeInspection.ProblemsHolder
import com.intellij.lang.javascript.JavaScriptBundle
import com.intellij.lang.javascript.modules.*
import com.intellij.lang.javascript.modules.JSBaseModulesDependenciesElementVisitor
import com.intellij.lang.javascript.modules.JsModulesSuggester
import com.intellij.lang.javascript.modules.ModuleReferenceInfo
import com.intellij.psi.PsiElement
import com.intellij.psi.ResolveResult

Expand All @@ -12,6 +14,6 @@ internal class SvelteModulesDependenciesElementVisitor(holder: ProblemsHolder) :
}

override fun createSuggester(node: PsiElement, info: ModuleReferenceInfo, resolveResults: Array<ResolveResult>): JsModulesSuggester? {
return if (!NodeModuleUtil.isWrappedInAmdDefinition(node)) ES6ModulesSuggester(info, node) else null
return SvelteModulesSuggester(info, node)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package dev.blachut.svelte.lang.inspections

import com.intellij.codeInspection.LocalQuickFix
import com.intellij.lang.javascript.formatter.JSCodeStyleSettings
import com.intellij.lang.javascript.modules.ES6ModulesSuggester
import com.intellij.lang.javascript.modules.JSModuleFixDescriptor
import com.intellij.lang.javascript.modules.JSPlaceTail
import com.intellij.lang.javascript.modules.ModuleReferenceInfo
import com.intellij.psi.PsiElement
import com.intellij.psi.ResolveResult
import com.intellij.psi.SmartPointerManager
import com.intellij.util.containers.ContainerUtil
import com.intellij.util.containers.MultiMap
import java.util.*

class SvelteModulesSuggester(info: ModuleReferenceInfo, node: PsiElement) : ES6ModulesSuggester(info, node) {
override fun findFixes(resolveResults: Array<ResolveResult>): MultiMap<PsiElement, LocalQuickFix> {
var descriptors = this.find(resolveResults, false)
if (descriptors.isEmpty()) {
return MultiMap.empty()
} else {
descriptors = ContainerUtil.filter(descriptors) { descriptorx: JSModuleFixDescriptor ->
val fromPath = descriptorx.fromPath
val idx = fromPath.indexOf("node_modules")
idx < 0 || fromPath.indexOf("test", idx) <= 0 && fromPath.indexOf("examples", idx) <= 0
}
val quoteString = JSCodeStyleSettings.getQuote(myNode)
val list: MutableList<LocalQuickFix?> = ArrayList()
val result = MultiMap.createLinked<PsiElement, LocalQuickFix>()
val size = descriptors.size
val var7: Iterator<*> = descriptors.iterator()
while (var7.hasNext()) {
val descriptor = var7.next() as JSModuleFixDescriptor
val hint = size == 1 && myModuleReferenceInfo.needHint()
list.add(SvelteImportES6ModuleFix(myNode, descriptor, null as JSPlaceTail?, quoteString, hint))
}
result.putValues(myNode, list)
val parent = myModuleReferenceInfo.parentRef
if (parent != null) {
val project = myNode.project
val secondWordList: MutableList<LocalQuickFix?> = ArrayList(list)
val var10: Iterator<*> = descriptors.iterator()
while (var10.hasNext()) {
val descriptor = var10.next() as JSModuleFixDescriptor
val tail = JSPlaceTail(SmartPointerManager.getInstance(project).createSmartPsiElementPointer(parent), arrayOf(myModuleReferenceInfo.parentName))
secondWordList.add(SvelteImportES6ModuleFix(parent, descriptor, tail, quoteString, false))
}
result.putValues(parent, secondWordList)
}
return result
}
}
}
8 changes: 6 additions & 2 deletions src/main/java/dev/blachut/svelte/lang/psi/SvelteHtmlFile.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class SvelteHtmlFile(viewProvider: FileViewProvider) : HtmlFileImpl(viewProvider
document ?: return true

val parentScript = findAncestorScript(place)
if (parentScript != null && parentScript.getAttributeValue("context") == "module") {
if (parentScript != null && isModuleScript(parentScript)) {
// place is inside module script, nothing more to process
return true
} else if (parentScript != null) {
Expand All @@ -40,9 +40,13 @@ class SvelteHtmlFile(viewProvider: FileViewProvider) : HtmlFileImpl(viewProvider
}
}

private fun findAncestorScript(place: PsiElement): XmlTag? {
fun findAncestorScript(place: PsiElement): XmlTag? {
val parentScript = PsiTreeUtil.findFirstContext(place, false) {
it is XmlTag && HtmlUtil.isScriptTag(it)
}
return parentScript as XmlTag?
}

fun isModuleScript(tag: XmlTag): Boolean {
return HtmlUtil.isScriptTag(tag) && tag.getAttributeValue("context") == "module"
}

0 comments on commit 5e9be64

Please sign in to comment.