From dd6d7d90bc4e27f6c913afaba1cf29b8de3f0c0d Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Wed, 24 Oct 2012 19:40:53 +0400 Subject: [PATCH] Fix navigation from stack trace JetExceptionFilter now correctly assigns hyperlinks to items of form "namespace$...", found in exception stack traces #KT-2489 Fixed #KT-2941 Fixed --- .../plugin/filters/JetExceptionFilter.java | 70 +++++++++++++++++++ .../filters/exceptionFilter/kt2489/a.kt | 6 ++ .../filters/exceptionFilter/kt2489/main.kt | 6 ++ .../filters/JetExceptionFilterTest.java | 5 ++ 4 files changed, 87 insertions(+) create mode 100644 idea/testData/filters/exceptionFilter/kt2489/a.kt create mode 100644 idea/testData/filters/exceptionFilter/kt2489/main.kt diff --git a/idea/src/org/jetbrains/jet/plugin/filters/JetExceptionFilter.java b/idea/src/org/jetbrains/jet/plugin/filters/JetExceptionFilter.java index 0e9354e2b01..1476b243712 100644 --- a/idea/src/org/jetbrains/jet/plugin/filters/JetExceptionFilter.java +++ b/idea/src/org/jetbrains/jet/plugin/filters/JetExceptionFilter.java @@ -16,22 +16,92 @@ package org.jetbrains.jet.plugin.filters; +import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; 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.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.search.GlobalSearchScope; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.lang.psi.JetFile; +import org.jetbrains.jet.lang.psi.JetPsiUtil; +import org.jetbrains.jet.lang.resolve.java.JetFilesProvider; +import org.jetbrains.jet.lang.resolve.java.JvmAbi; +import org.jetbrains.jet.lang.resolve.name.FqName; + +import java.util.Collection; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class JetExceptionFilter implements Filter { @NotNull private final ExceptionFilter exceptionFilter; + @NotNull private final GlobalSearchScope searchScope; public JetExceptionFilter(@NotNull GlobalSearchScope searchScope) { exceptionFilter = new ExceptionFilter(searchScope); + this.searchScope = searchScope; } @Nullable private HyperlinkInfo createHyperlinkInfo(@NotNull String line) { + Project project = searchScope.getProject(); + if (project == null) return null; + + final StackTraceElement element = parseStackTraceLine(line); + if (element == null) return null; + + // We don't want to rely on FqName here, since this name can contain dollar signs + String fullyQualifiedName = element.getClassName(); + int lastDot = fullyQualifiedName.lastIndexOf('.'); + String classNameWithInners = fullyQualifiedName.substring(lastDot + 1); + final String packageName = lastDot >= 0 ? fullyQualifiedName.substring(0, lastDot) : ""; + + // All classes except 'namespace' and its inner classes are handled correctly in the default ExceptionFilter + if (!classNameWithInners.equals(JvmAbi.PACKAGE_CLASS) && !classNameWithInners.startsWith(JvmAbi.PACKAGE_CLASS + "$")) { + return null; + } + + // Only consider files with the file name from the stack trace and in the given package + Collection files = Collections2 + .filter(JetFilesProvider.getInstance(project).allInScope(searchScope), new Predicate() { + @Override + public boolean apply(@Nullable JetFile file) { + return file != null + && file.getName().equals(element.getFileName()) + && JetPsiUtil.getFQName(file).getFqName().equals(packageName); + } + }); + + if (files.isEmpty()) return null; + + if (files.size() == 1) { + JetFile file = files.iterator().next(); + VirtualFile virtualFile = file.getVirtualFile(); + return virtualFile == null ? null : new OpenFileHyperlinkInfo(project, virtualFile, element.getLineNumber() - 1); + } + + // TODO multiple files with the same name within the same package + + return null; + } + + // Matches strings like "\tat test.namespace$foo$f$1.invoke(a.kt:3)\n" + private static final Pattern STACK_TRACE_ELEMENT_PATTERN = Pattern.compile("^\\s*at\\s+(.+)\\.(.+)\\((.+):(\\d+)\\)\\s*$"); + + @Nullable + private StackTraceElement parseStackTraceLine(@NotNull String line) { + Matcher matcher = STACK_TRACE_ELEMENT_PATTERN.matcher(line); + if (matcher.matches()) { + String declaringClass = matcher.group(1); + String methodName = matcher.group(2); + String fileName = matcher.group(3); + int lineNumber = Integer.parseInt(matcher.group(4)); + return new StackTraceElement(declaringClass, methodName, fileName, lineNumber); + } return null; } diff --git a/idea/testData/filters/exceptionFilter/kt2489/a.kt b/idea/testData/filters/exceptionFilter/kt2489/a.kt new file mode 100644 index 00000000000..f6374bc9f7f --- /dev/null +++ b/idea/testData/filters/exceptionFilter/kt2489/a.kt @@ -0,0 +1,6 @@ +fun a() { + val f = { + null!! + } + f() +} diff --git a/idea/testData/filters/exceptionFilter/kt2489/main.kt b/idea/testData/filters/exceptionFilter/kt2489/main.kt new file mode 100644 index 00000000000..51d9bdc4134 --- /dev/null +++ b/idea/testData/filters/exceptionFilter/kt2489/main.kt @@ -0,0 +1,6 @@ +fun main(args: Array) { + val f = { + null!! + } + f() +} diff --git a/idea/tests/org/jetbrains/jet/plugin/filters/JetExceptionFilterTest.java b/idea/tests/org/jetbrains/jet/plugin/filters/JetExceptionFilterTest.java index ff35469037e..e87d5a2bf99 100644 --- a/idea/tests/org/jetbrains/jet/plugin/filters/JetExceptionFilterTest.java +++ b/idea/tests/org/jetbrains/jet/plugin/filters/JetExceptionFilterTest.java @@ -105,4 +105,9 @@ public class JetExceptionFilterTest extends MultiFileTestCase { public void testSimple() { doTest("simple.kt", "namespace", 2); } + + public void testKt2489() { + doTest("a.kt", "namespace$a$f$1", 3); + doTest("main.kt", "namespace$main$f$1", 3); + } }