JetExceptionFilter handles correctly the case of several files with the same name

Extract the logic to DebuggerUtils.findSourceFileForClass()

This fixes navigation from stack trace to a file, when there's more than one
file with this name in this package in the project
This commit is contained in:
Alexander Udalov
2012-10-24 16:07:50 +04:00
parent 880852861c
commit f42478f82f
5 changed files with 109 additions and 29 deletions
@@ -16,8 +16,6 @@
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;
@@ -28,12 +26,10 @@ 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 org.jetbrains.jet.lang.resolve.java.JvmClassName;
import org.jetbrains.jet.plugin.util.DebuggerUtils;
import java.util.Collection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -51,42 +47,31 @@ public class JetExceptionFilter implements Filter {
Project project = searchScope.getProject();
if (project == null) return null;
final StackTraceElement element = parseStackTraceLine(line);
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 fileName = element.getFileName();
// fullyQualifiedName is of format "package.Class$Inner"
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);
}
});
String internalName = fullyQualifiedName.replace('.', '/');
JvmClassName jvmClassName = JvmClassName.byInternalName(internalName);
if (files.isEmpty()) return null;
JetFile file = DebuggerUtils.findSourceFileForClass(searchScope, jvmClassName, fileName);
if (files.size() == 1) {
JetFile file = files.iterator().next();
VirtualFile virtualFile = file.getVirtualFile();
return virtualFile == null ? null : new OpenFileHyperlinkInfo(project, virtualFile, element.getLineNumber() - 1);
}
if (file == null) return null;
VirtualFile virtualFile = file.getVirtualFile();
if (virtualFile == null) return null;
// TODO multiple files with the same name within the same package
return null;
return new OpenFileHyperlinkInfo(project, virtualFile, element.getLineNumber() - 1);
}
// Matches strings like "\tat test.namespace$foo$f$1.invoke(a.kt:3)\n"
@@ -0,0 +1,77 @@
/*
* Copyright 2010-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.plugin.util;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.intellij.psi.search.GlobalSearchScope;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.analyzer.AnalyzeExhaust;
import org.jetbrains.jet.codegen.binding.PsiCodegenPredictor;
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.JvmClassName;
import org.jetbrains.jet.lang.resolve.name.FqName;
import org.jetbrains.jet.plugin.project.WholeProjectAnalyzerFacade;
import java.util.Collection;
import java.util.List;
public class DebuggerUtils {
@Nullable
public static JetFile findSourceFileForClass(
@NotNull GlobalSearchScope searchScope,
@NotNull JvmClassName className,
@NotNull final String fileName
) {
JetFilesProvider filesProvider = JetFilesProvider.getInstance(searchScope.getProject());
Collection<JetFile> filesInScope = filesProvider.allInScope(searchScope);
final FqName packageFqName = className.getOuterClassFqName().parent();
// Only consider files with the file name from the stack trace and in the given package
Collection<JetFile> files = Collections2.filter(filesInScope, new Predicate<JetFile>() {
@Override
public boolean apply(@Nullable JetFile file) {
return file != null
&& file.getName().equals(fileName)
&& JetPsiUtil.getFQName(file).equals(packageFqName);
}
});
if (files.isEmpty()) return null;
JetFile anyFile = files.iterator().next();
if (files.size() == 1) {
return anyFile;
}
List<JetFile> allNamespaceFiles = filesProvider.allNamespaceFiles().fun(anyFile);
JetFile file = PsiCodegenPredictor.getFileForNamespacePartName(allNamespaceFiles, className);
if (file != null) {
return file;
}
// In the rare case that there's more than one file with this name in this package,
// we may actually need to analyze the project in order to find a file which produces this class
AnalyzeExhaust analyzeExhaust = WholeProjectAnalyzerFacade.analyzeProjectWithCacheOnAFile(anyFile);
return PsiCodegenPredictor.getFileForCodegenNamedClass(analyzeExhaust.getBindingContext(), allNamespaceFiles, className);
}
}
@@ -0,0 +1,6 @@
package multiSameName
fun foo() {
val f = { null!! }
f()
}
@@ -0,0 +1,6 @@
package multiSameName
fun foo(x: Int) {
val f = { null!! }
f()
}
@@ -110,4 +110,10 @@ public class JetExceptionFilterTest extends MultiFileTestCase {
doTest("a.kt", "namespace$a$f$1", 3);
doTest("main.kt", "namespace$main$f$1", 3);
}
public void testMultiSameName() {
// The order and the exact names do matter here
doTest("1/foo.kt", "multiSameName.namespace$foo$f$1", 4);
doTest("2/foo.kt", "multiSameName.namespace$foo$f$2", 4);
}
}