diff --git a/src/main/java/dev/blachut/svelte/lang/inspections/SvelteAddImportExecutor.kt b/src/main/java/dev/blachut/svelte/lang/inspections/SvelteAddImportExecutor.kt new file mode 100644 index 00000000..17c365bf --- /dev/null +++ b/src/main/java/dev/blachut/svelte/lang/inspections/SvelteAddImportExecutor.kt @@ -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("", 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) + } + } +} diff --git a/src/main/java/dev/blachut/svelte/lang/inspections/SvelteImportES6ModuleFix.kt b/src/main/java/dev/blachut/svelte/lang/inspections/SvelteImportES6ModuleFix.kt new file mode 100644 index 00000000..0ff7a5e7 --- /dev/null +++ b/src/main/java/dev/blachut/svelte/lang/inspections/SvelteImportES6ModuleFix.kt @@ -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) + } + + + +} diff --git a/src/main/java/dev/blachut/svelte/lang/inspections/SvelteModulesDependenciesElementVisitor.kt b/src/main/java/dev/blachut/svelte/lang/inspections/SvelteModulesDependenciesElementVisitor.kt index e7e7a7d8..3db83d4b 100644 --- a/src/main/java/dev/blachut/svelte/lang/inspections/SvelteModulesDependenciesElementVisitor.kt +++ b/src/main/java/dev/blachut/svelte/lang/inspections/SvelteModulesDependenciesElementVisitor.kt @@ -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 @@ -12,6 +14,6 @@ internal class SvelteModulesDependenciesElementVisitor(holder: ProblemsHolder) : } override fun createSuggester(node: PsiElement, info: ModuleReferenceInfo, resolveResults: Array): JsModulesSuggester? { - return if (!NodeModuleUtil.isWrappedInAmdDefinition(node)) ES6ModulesSuggester(info, node) else null + return SvelteModulesSuggester(info, node) } } diff --git a/src/main/java/dev/blachut/svelte/lang/inspections/SvelteModulesSuggester.kt b/src/main/java/dev/blachut/svelte/lang/inspections/SvelteModulesSuggester.kt new file mode 100644 index 00000000..597587af --- /dev/null +++ b/src/main/java/dev/blachut/svelte/lang/inspections/SvelteModulesSuggester.kt @@ -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): MultiMap { + 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 = ArrayList() + val result = MultiMap.createLinked() + 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 = 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 + } + } +} diff --git a/src/main/java/dev/blachut/svelte/lang/psi/SvelteHtmlFile.kt b/src/main/java/dev/blachut/svelte/lang/psi/SvelteHtmlFile.kt index 4d3cdfbf..3c7dee0c 100644 --- a/src/main/java/dev/blachut/svelte/lang/psi/SvelteHtmlFile.kt +++ b/src/main/java/dev/blachut/svelte/lang/psi/SvelteHtmlFile.kt @@ -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) { @@ -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" +}