From f42478f82f829c25d48f7f8b9087b12a9666519a Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Wed, 24 Oct 2012 16:07:50 +0400 Subject: [PATCH] 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 --- .../plugin/filters/JetExceptionFilter.java | 43 ++++------- .../jet/plugin/util/DebuggerUtils.java | 77 +++++++++++++++++++ .../exceptionFilter/multiSameName/1/foo.kt | 6 ++ .../exceptionFilter/multiSameName/2/foo.kt | 6 ++ .../filters/JetExceptionFilterTest.java | 6 ++ 5 files changed, 109 insertions(+), 29 deletions(-) create mode 100644 idea/src/org/jetbrains/jet/plugin/util/DebuggerUtils.java create mode 100644 idea/testData/filters/exceptionFilter/multiSameName/1/foo.kt create mode 100644 idea/testData/filters/exceptionFilter/multiSameName/2/foo.kt diff --git a/idea/src/org/jetbrains/jet/plugin/filters/JetExceptionFilter.java b/idea/src/org/jetbrains/jet/plugin/filters/JetExceptionFilter.java index 1476b243712..9dc5504201e 100644 --- a/idea/src/org/jetbrains/jet/plugin/filters/JetExceptionFilter.java +++ b/idea/src/org/jetbrains/jet/plugin/filters/JetExceptionFilter.java @@ -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 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); - } - }); + 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" diff --git a/idea/src/org/jetbrains/jet/plugin/util/DebuggerUtils.java b/idea/src/org/jetbrains/jet/plugin/util/DebuggerUtils.java new file mode 100644 index 00000000000..6919d8a1f17 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/util/DebuggerUtils.java @@ -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 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 files = Collections2.filter(filesInScope, new Predicate() { + @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 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); + } +} diff --git a/idea/testData/filters/exceptionFilter/multiSameName/1/foo.kt b/idea/testData/filters/exceptionFilter/multiSameName/1/foo.kt new file mode 100644 index 00000000000..987e0e22851 --- /dev/null +++ b/idea/testData/filters/exceptionFilter/multiSameName/1/foo.kt @@ -0,0 +1,6 @@ +package multiSameName + +fun foo() { + val f = { null!! } + f() +} diff --git a/idea/testData/filters/exceptionFilter/multiSameName/2/foo.kt b/idea/testData/filters/exceptionFilter/multiSameName/2/foo.kt new file mode 100644 index 00000000000..0c5c00fb455 --- /dev/null +++ b/idea/testData/filters/exceptionFilter/multiSameName/2/foo.kt @@ -0,0 +1,6 @@ +package multiSameName + +fun foo(x: Int) { + 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 e87d5a2bf99..42e28e792d5 100644 --- a/idea/tests/org/jetbrains/jet/plugin/filters/JetExceptionFilterTest.java +++ b/idea/tests/org/jetbrains/jet/plugin/filters/JetExceptionFilterTest.java @@ -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); + } }