diff --git a/src/main/kotlin/io/vlang/ide/inspections/general/VlangCircularImportInspection.kt b/src/main/kotlin/io/vlang/ide/inspections/general/VlangCircularImportInspection.kt index 139daaf8..3ed8f750 100644 --- a/src/main/kotlin/io/vlang/ide/inspections/general/VlangCircularImportInspection.kt +++ b/src/main/kotlin/io/vlang/ide/inspections/general/VlangCircularImportInspection.kt @@ -7,34 +7,46 @@ import io.vlang.ide.inspections.VlangBaseInspection import io.vlang.lang.psi.VlangFile import io.vlang.lang.psi.VlangImportName import io.vlang.lang.psi.VlangVisitor +import io.vlang.lang.psi.impl.VlangModule class VlangCircularImportInspection : VlangBaseInspection() { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { + fun hasCircularImport(module: VlangModule, visited: MutableSet): Boolean { + if (module in visited) return true // circular import detected, stop here + + visited.add(module) + module.directory.files.filterIsInstance().forEach { file -> + val imports = file.getImports() + for (import in imports) { + import.resolve().firstOrNull()?.let{ + return hasCircularImport(it, visited) + } + } + } + visited.remove(module) + + return false // exhausted dfs search, no circular import detected + } + return object : VlangVisitor() { override fun visitImportName(importName: VlangImportName) { - super.visitImportName(importName) - val currentModule = importName.containingFile.containingDirectory val importList = importName.resolve().toList() - // check for circular import importList.forEach { module -> - val files = module.directory.files.filterIsInstance() - for (file in files) { - val imports = file.getImports() - if (imports.any { it.name == currentModule.name }) { - holder.registerProblem( - importName, - "Circular import detected", - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - ) - } + if(hasCircularImport(module, mutableSetOf())){ + holder.registerProblem( + importName, + "Circular import detected", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + ) } } // end check for circular import } } + } } diff --git a/src/test/kotlin/io/vlang/integration/inspections/CircularImportInspectionsTest.kt b/src/test/kotlin/io/vlang/integration/inspections/CircularImportInspectionsTest.kt index 6917fb93..b9ba6ab0 100644 --- a/src/test/kotlin/io/vlang/integration/inspections/CircularImportInspectionsTest.kt +++ b/src/test/kotlin/io/vlang/integration/inspections/CircularImportInspectionsTest.kt @@ -4,13 +4,15 @@ import io.vlang.ide.inspections.general.VlangCircularImportInspection import io.vlang.integration.IntegrationTestBase class CircularImportInspectionsTest : IntegrationTestBase() { + fun `test simple circular import`() = doTest { directory("first") { - file("utils.v", """ + fileNoLangInj("utils.v", + """ module first import second - + pub fn util(){ second.util() } @@ -18,11 +20,12 @@ class CircularImportInspectionsTest : IntegrationTestBase() { } directory("second") { - file("utils.v", """ + fileNoLangInj("utils.v", + """ module second import first - + pub fn util() { first.util() } @@ -32,4 +35,56 @@ class CircularImportInspectionsTest : IntegrationTestBase() { testInspections() } } + + fun `test three way import cycle`() = doTest { + + directory("first") { + fileNoLangInj("utils.v", + """ + module first + + import second + + pub fn util(){ + second.util() + } + """.trimIndent()) + } + + directory("second") { + fileNoLangInj("utils.v", + """ + module second + + import third + + pub fn util() { + third.util() + } + """.trimIndent()) + } + + directory("third") { + fileNoLangInj("utils.v", + """ + module third + + import first + + pub fn util() { + first.util() + } + """.trimIndent()) + + enableInspection(VlangCircularImportInspection()) + testInspections() + } + + } + + //extension lambda to create file without language Sytax injection + val fileNoLangInj : DirectoryContext.(name: String, text: String) -> Unit = { name, text -> + file(name, text) + } + }