diff --git a/idea/src/org/jetbrains/kotlin/idea/KotlinFileTypeFactory.java b/idea/src/org/jetbrains/kotlin/idea/KotlinFileTypeFactory.java index 9eecc66b352..37d98c456ec 100644 --- a/idea/src/org/jetbrains/kotlin/idea/KotlinFileTypeFactory.java +++ b/idea/src/org/jetbrains/kotlin/idea/KotlinFileTypeFactory.java @@ -22,6 +22,8 @@ import org.jetbrains.annotations.NotNull; public class KotlinFileTypeFactory extends FileTypeFactory { + public final static String[] KOTLIN_EXTENSIONS = new String[] { "kt", "kts" }; + @Override public void createFileTypes(@NotNull FileTypeConsumer consumer) { consumer.consume(KotlinFileType.INSTANCE, "kt;kts"); diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/DebuggerUtils.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/DebuggerUtils.kt index 1e6f38348f4..a7b5dfa907b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/DebuggerUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/DebuggerUtils.kt @@ -22,6 +22,7 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.util.io.FileUtilRt import com.intellij.psi.search.GlobalSearchScope import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.idea.KotlinFileTypeFactory import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde import org.jetbrains.kotlin.idea.resolve.ResolutionFacade import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope @@ -39,9 +40,6 @@ import org.jetbrains.kotlin.serialization.deserialization.descriptors.Deserializ import java.util.* object DebuggerUtils { - - private val KOTLIN_EXTENSIONS = Sets.newHashSet("kt", "kts") - fun findSourceFileForClassIncludeLibrarySources( project: Project, scope: GlobalSearchScope, @@ -62,7 +60,7 @@ object DebuggerUtils { className: JvmClassName, fileName: String): KtFile? { val extension = FileUtilRt.getExtension(fileName) - if (!KOTLIN_EXTENSIONS.contains(extension)) return null + if (extension !in KotlinFileTypeFactory.KOTLIN_EXTENSIONS) return null if (DumbService.getInstance(project).isDumb) return null val filesWithExactName = scopes.findFirstNotEmpty { findFilesByNameInPackage(className, fileName, project, it) } ?: return null diff --git a/idea/src/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilter.kt b/idea/src/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilter.kt index 09563cbea2a..5ffb0c91b30 100644 --- a/idea/src/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilter.kt +++ b/idea/src/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilter.kt @@ -16,15 +16,17 @@ package org.jetbrains.kotlin.idea.filters -import com.intellij.execution.filters.ExceptionFilter -import com.intellij.execution.filters.Filter -import com.intellij.execution.filters.HyperlinkInfo -import com.intellij.execution.filters.OpenFileHyperlinkInfo +import com.intellij.execution.filters.* +import com.intellij.execution.filters.impl.HyperlinkInfoFactoryImpl import com.intellij.openapi.project.Project +import com.intellij.openapi.util.io.FileUtilRt import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.search.FilenameIndex import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.idea.KotlinFileTypeFactory import org.jetbrains.kotlin.idea.debugger.* import org.jetbrains.kotlin.resolve.jvm.JvmClassName +import java.util.* import java.util.regex.Pattern class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter { @@ -36,31 +38,62 @@ class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter } private fun patchResult(result: Filter.Result, line: String): Filter.Result { - val newHyperlinkInfo = createHyperlinkInfo(line) ?: return result + val newHyperlinkInfo = createHyperlinkInfo(line, result) ?: return result return Filter.Result(result.resultItems.map { Filter.ResultItem(it.getHighlightStartOffset(), it.getHighlightEndOffset(), newHyperlinkInfo, it.getHighlightAttributes()) }) } - private fun createHyperlinkInfo(line: String): HyperlinkInfo? { + private fun createHyperlinkInfo(line: String, defaultResult: Filter.Result): HyperlinkInfo? { val project = searchScope.project ?: return null - val element = parseStackTraceLine(line) ?: return null + val stackTraceElement = parseStackTraceLine(line) ?: return null // All true classes should be handled correctly in the default ExceptionFilter. Special cases: // - static facades; // - package facades / package parts (generated by pre-M13 compiled); // - local classes (and closures) in top-level function and property declarations. - val fileName = element.fileName + // - bad line numbers for inline functions + // - already applied smap for inline functions + + val fileName = stackTraceElement.fileName + + val extension = FileUtilRt.getExtension(fileName).toLowerCase() + if (extension !in KotlinFileTypeFactory.KOTLIN_EXTENSIONS) return null + // fullyQualifiedName is of format "package.Class$Inner" - val fullyQualifiedName = element.className - val lineNumber = element.lineNumber - 1 + val fullyQualifiedName = stackTraceElement.className + val lineNumber = stackTraceElement.lineNumber - 1 val internalName = fullyQualifiedName.replace('.', '/') val jvmClassName = JvmClassName.byInternalName(internalName) - val file = DebuggerUtils.findSourceFileForClassIncludeLibrarySources(project, searchScope, jvmClassName, fileName) ?: return null + val file = DebuggerUtils.findSourceFileForClassIncludeLibrarySources(project, searchScope, jvmClassName, fileName) + + if (file == null) { + // File can't be found by class name and file name this can happen when smap info is already applied. + // Default filter favours looking for file from class name and that can lead to wrong navigation to inline fun call file and + // line from inline function definition. + val defaultLinkFileNames = defaultResult.resultItems.mapNotNullTo(HashSet()) { (it as? FileHyperlinkInfo)?.descriptor?.file?.name } + if (!defaultLinkFileNames.contains(fileName)) { + val filesByName = FilenameIndex.getFilesByName(project, fileName, searchScope).mapNotNullTo(HashSet()) { + if (!it.isValid) return@mapNotNullTo null + it.virtualFile + } + + if (filesByName.isNotEmpty()) { + if (filesByName.size > 1) { + return HyperlinkInfoFactoryImpl.getInstance().createMultipleFilesHyperlinkInfo(filesByName.toList(), lineNumber, project) + } + else { + return OpenFileHyperlinkInfo(project, filesByName.first(), lineNumber) + } + } + } + + return null + } val virtualFile = file.virtualFile ?: return null diff --git a/idea/testData/debugger/exceptionFilter/inlineFunctionAnotherFileWithSmapApplied/inlineFunctionAnotherFileWithSmapApplied.kt b/idea/testData/debugger/exceptionFilter/inlineFunctionAnotherFileWithSmapApplied/inlineFunctionAnotherFileWithSmapApplied.kt new file mode 100644 index 00000000000..9dc5780c96d --- /dev/null +++ b/idea/testData/debugger/exceptionFilter/inlineFunctionAnotherFileWithSmapApplied/inlineFunctionAnotherFileWithSmapApplied.kt @@ -0,0 +1,13 @@ +package inlineFunSameFile + +import inlineFunPackage.* + +fun box() { + foo { + println() + } +} + +// SMAP_APPLIED +// FILE: inlineFunctionFile.kt +// LINE: 4 \ No newline at end of file diff --git a/idea/testData/debugger/exceptionFilter/inlineFunctionAnotherFileWithSmapApplied/inlineFunctionFile.kt b/idea/testData/debugger/exceptionFilter/inlineFunctionAnotherFileWithSmapApplied/inlineFunctionFile.kt new file mode 100644 index 00000000000..eb880e7ec22 --- /dev/null +++ b/idea/testData/debugger/exceptionFilter/inlineFunctionAnotherFileWithSmapApplied/inlineFunctionFile.kt @@ -0,0 +1,6 @@ +package inlineFunPackage + +inline fun foo(f: () -> Unit) { + null!! + f() +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/filters/AbstractKotlinExceptionFilterTest.kt b/idea/tests/org/jetbrains/kotlin/idea/filters/AbstractKotlinExceptionFilterTest.kt index 33595541394..0f952a7c122 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/filters/AbstractKotlinExceptionFilterTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/filters/AbstractKotlinExceptionFilterTest.kt @@ -42,7 +42,7 @@ import java.net.URLClassLoader private var MOCK_LIBRARY_JAR: File? = null private val MOCK_LIBRARY_SOURCES = PluginTestCaseBase.getTestDataPathBase() + "/debugger/mockLibraryForExceptionFilter" -abstract class AbstractKotlinExceptionFilterTest: KotlinCodeInsightTestCase() { +abstract class AbstractKotlinExceptionFilterTest : KotlinCodeInsightTestCase() { override fun getTestDataPath() = "" protected fun doTest(path: String) { @@ -101,7 +101,22 @@ abstract class AbstractKotlinExceptionFilterTest: KotlinCodeInsightTestCase() { val filter = KotlinExceptionFilterFactory().create(GlobalSearchScope.allScope(project)) val prefix = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// PREFIX: ") ?: "at" - val result = filter.applyFilter("$prefix $stackTraceElement", 0) ?: throw AssertionError("Couldn't apply filter to $stackTraceElement") + val stackTraceString = stackTraceElement.toString() + var result = filter.applyFilter("$prefix $stackTraceString", 0) ?: throw AssertionError("Couldn't apply filter to $stackTraceElement") + + if (InTextDirectivesUtils.isDirectiveDefined(fileText, "SMAP_APPLIED")) { + val fileHyperlinkInfo = result.firstHyperlinkInfo as FileHyperlinkInfo + val descriptor = fileHyperlinkInfo.descriptor!! + + val file = descriptor.file + val line = descriptor.line + 1 + + val newStackString = stackTraceString + .replace(mainFile.name, file.name) + .replace(Regex("\\:\\d+\\)"), ":$line)") + + result = filter.applyFilter("$prefix $newStackString", 0) ?: throw AssertionError("Couldn't apply filter to $stackTraceElement") + } val info = result.firstHyperlinkInfo as FileHyperlinkInfo val descriptor = info.descriptor!! diff --git a/idea/tests/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilterTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilterTestGenerated.java index 8f0e3d16369..f824258b780 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilterTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilterTestGenerated.java @@ -66,6 +66,12 @@ public class KotlinExceptionFilterTestGenerated extends AbstractKotlinExceptionF doTest(fileName); } + @TestMetadata("inlineFunctionAnotherFileWithSmapApplied") + public void testInlineFunctionAnotherFileWithSmapApplied() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/exceptionFilter/inlineFunctionAnotherFileWithSmapApplied/"); + doTest(fileName); + } + @TestMetadata("inlineFunctionInnerClass") public void testInlineFunctionInnerClass() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/exceptionFilter/inlineFunctionInnerClass/");