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
This commit is contained in:
Alexander Udalov
2012-10-24 19:40:53 +04:00
parent eb6da4bb8b
commit dd6d7d90bc
4 changed files with 87 additions and 0 deletions
@@ -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<JetFile> files = Collections2
.filter(JetFilesProvider.getInstance(project).allInScope(searchScope), new Predicate<JetFile>() {
@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;
}
@@ -0,0 +1,6 @@
fun a() {
val f = {
null!!
}
f()
}
@@ -0,0 +1,6 @@
fun main(args: Array<String>) {
val f = {
null!!
}
f()
}
@@ -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);
}
}