diff --git a/metals-bench/src/main/scala/bench/MetalsBench.scala b/metals-bench/src/main/scala/bench/MetalsBench.scala index abdd2321ff6..6fbd1ec5a4a 100644 --- a/metals-bench/src/main/scala/bench/MetalsBench.scala +++ b/metals-bench/src/main/scala/bench/MetalsBench.scala @@ -180,7 +180,7 @@ class MetalsBench { def mtagsJavaParse(): Unit = { javaDependencySources.foreach { input => JavaMtags - .index(input, includeMembers = true) + .index(input, includeMembers = true)(EmptyReportContext) .index() } } diff --git a/mtags-shared/src/main/scala/scala/meta/internal/metals/ReportContext.scala b/mtags-shared/src/main/scala/scala/meta/internal/metals/ReportContext.scala index 6c1e9c66e9a..7179f9b9a7e 100644 --- a/mtags-shared/src/main/scala/scala/meta/internal/metals/ReportContext.scala +++ b/mtags-shared/src/main/scala/scala/meta/internal/metals/ReportContext.scala @@ -6,6 +6,7 @@ import java.nio.file.Path import java.nio.file.Paths import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicReference +import java.util.logging.Logger import scala.util.Try import scala.util.control.NonFatal @@ -90,6 +91,8 @@ class StdReporter( level: ReportLevel, override val name: String ) extends Reporter { + private val logger = Logger.getLogger(classOf[ReportContext].getName) + val maybeReportsDir: Path = workspace.resolve(pathToReports).resolve(name) private lazy val reportsDir = maybeReportsDir.createDirectories() @@ -146,12 +149,18 @@ class StdReporter( duplicate <- reportedMap.get(id) } yield duplicate - optDuplicate.orElse { + val pathToReport = optDuplicate.getOrElse { path.createDirectories() path.writeText(sanitize(report.fullText(withIdAndSummary = true))) - Some(path) + path + } + if (!ifVerbose) { + logger.severe( + s"${report.shortSummary} (full report at: $pathToReport)" + ) } - }.toOption.flatten + pathToReport + }.toOption override def sanitize(text: String): String = { val textAfterWokspaceReplace = diff --git a/mtags/src/main/scala-2/scala/meta/internal/mtags/MtagsEnrichments.scala b/mtags/src/main/scala-2/scala/meta/internal/mtags/MtagsEnrichments.scala index 155add980c9..83c4129b4cb 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/mtags/MtagsEnrichments.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/mtags/MtagsEnrichments.scala @@ -208,28 +208,6 @@ trait MtagsEnrichments extends ScalametaCommonEnrichments { def toLocation(uri: URI): l.Location = new l.Location(uri.toString(), range) } - implicit class XtensionPositionLspInverse(pos: l.Position) { - - /** - * LSP position translated to scalameta position. Might return None if - * pos is not contained in input - * - * @param input file input the position relates to - * @return scalameta position with offset if the pos is contained in the file - */ - def toMeta(input: m.Input): Option[m.Position] = { - Try( - m.Position.Range( - input, - pos.getLine, - pos.getCharacter, - pos.getLine, - pos.getCharacter - ) - ).toOption - } - } - implicit class XtensionPositionMtags(pos: Position) { def encloses(other: Position): Boolean = pos.start <= other.start && pos.end >= other.end diff --git a/mtags/src/main/scala/scala/meta/internal/metals/Docstrings.scala b/mtags/src/main/scala/scala/meta/internal/metals/Docstrings.scala index 04b053dcea2..23e0667b0c2 100644 --- a/mtags/src/main/scala/scala/meta/internal/metals/Docstrings.scala +++ b/mtags/src/main/scala/scala/meta/internal/metals/Docstrings.scala @@ -31,7 +31,7 @@ import scala.meta.pc.SymbolDocumentation * * Handles both javadoc and scaladoc. */ -class Docstrings(index: GlobalSymbolIndex) { +class Docstrings(index: GlobalSymbolIndex)(implicit rc: ReportContext) { val cache = new TrieMap[Content, SymbolDocumentation]() private val logger = Logger.getLogger(classOf[Docstrings].getName) diff --git a/mtags/src/main/scala/scala/meta/internal/metals/JavadocIndexer.scala b/mtags/src/main/scala/scala/meta/internal/metals/JavadocIndexer.scala index e15bca9f32d..9a8415d0c14 100644 --- a/mtags/src/main/scala/scala/meta/internal/metals/JavadocIndexer.scala +++ b/mtags/src/main/scala/scala/meta/internal/metals/JavadocIndexer.scala @@ -32,7 +32,8 @@ class JavadocIndexer( input: Input.VirtualFile, fn: SymbolDocumentation => Unit, contentType: ContentType -) extends JavaMtags(input, includeMembers = true) { +)(implicit rc: ReportContext) + extends JavaMtags(input, includeMembers = true) { override def visitClass( cls: JavaClass, pos: Position, @@ -171,7 +172,7 @@ object JavadocIndexer { def all( input: Input.VirtualFile, contentType: ContentType - ): List[SymbolDocumentation] = { + )(implicit rc: ReportContext): List[SymbolDocumentation] = { val buf = List.newBuilder[SymbolDocumentation] foreach(input, contentType)(buf += _) buf.result() @@ -179,7 +180,7 @@ object JavadocIndexer { def foreach( input: Input.VirtualFile, contentType: ContentType - )(fn: SymbolDocumentation => Unit): Unit = { + )(fn: SymbolDocumentation => Unit)(implicit rc: ReportContext): Unit = { new JavadocIndexer(input, fn, contentType).indexRoot() } } diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/JavaMtags.scala b/mtags/src/main/scala/scala/meta/internal/mtags/JavaMtags.scala index 36b370cb87c..fe3b5e1beeb 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/JavaMtags.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/JavaMtags.scala @@ -1,11 +1,18 @@ package scala.meta.internal.mtags import java.io.StringReader +import java.net.URI import java.util.Comparator +import scala.util.control.NonFatal + import scala.meta.inputs.Input import scala.meta.inputs.Position import scala.meta.internal.jdk.CollectionConverters._ +import scala.meta.internal.metals.CompilerRangeParamsUtils +import scala.meta.internal.metals.EmptyCancelToken +import scala.meta.internal.metals.Report +import scala.meta.internal.metals.ReportContext import scala.meta.internal.mtags.ScalametaCommonEnrichments._ import scala.meta.internal.semanticdb.Language import scala.meta.internal.semanticdb.SymbolInformation.Kind @@ -19,16 +26,18 @@ import com.thoughtworks.qdox.model.JavaMember import com.thoughtworks.qdox.model.JavaMethod import com.thoughtworks.qdox.model.JavaModel import com.thoughtworks.qdox.parser.ParseException +import org.eclipse.lsp4j object JavaMtags { def index( input: Input.VirtualFile, includeMembers: Boolean - ): MtagsIndexer = + )(implicit rc: ReportContext): MtagsIndexer = new JavaMtags(input, includeMembers) } -class JavaMtags(virtualFile: Input.VirtualFile, includeMembers: Boolean) - extends MtagsIndexer { self => +class JavaMtags(virtualFile: Input.VirtualFile, includeMembers: Boolean)( + implicit rc: ReportContext +) extends MtagsIndexer { self => val builder = new JavaProjectBuilder() override def language: Language = Language.JAVA @@ -47,9 +56,14 @@ class JavaMtags(virtualFile: Input.VirtualFile, includeMembers: Boolean) } source.getClasses.asScala.foreach(visitClass) } catch { - case _: ParseException | _: NullPointerException => - // Parse errors are ignored because the Java source files we process - // are not written by the user so there is nothing they can do about it. + case e: ParseException => + reportError( + "parse error", + e, + Some(new lsp4j.Position(e.getLine() - 1, e.getColumn())) + ) + case e: NullPointerException => + reportError("null pointer exception", e, None) } } @@ -207,4 +221,45 @@ class JavaMtags(virtualFile: Input.VirtualFile, includeMembers: Boolean) def lineNumber: Int = m.getLineNumber - 1 } + private def reportError( + errorName: String, + e: Exception, + position: Option[lsp4j.Position] + ) = { + try { + val content = position + .flatMap(_.toMeta(virtualFile)) + .map(pos => + CompilerRangeParamsUtils.fromPos(pos, EmptyCancelToken).printed() + ) + .map(content => s"""| + |file content: + |```java + |$content + |``` + |""".stripMargin) + .getOrElse("") + + val shortFileName = { + val index = virtualFile.path.indexOf("jar!") + if (index > 0) virtualFile.path.substring(index + 4) + else virtualFile.path + } + + rc.unsanitized.create( + new Report( + name = "qdox-error", + text = s"""|error in qdox parser$content + |""".stripMargin, + error = Some(e), + path = Some(new URI(virtualFile.path)), + shortSummary = s"QDox $errorName in $shortFileName", + id = Some(virtualFile.path) + ) + ) + } catch { + case NonFatal(_) => + } + } + } diff --git a/mtags/src/main/scala/scala/meta/internal/mtags/ScalametaCommonEnrichments.scala b/mtags/src/main/scala/scala/meta/internal/mtags/ScalametaCommonEnrichments.scala index ee139e1b6a5..0de6b673bf2 100644 --- a/mtags/src/main/scala/scala/meta/internal/mtags/ScalametaCommonEnrichments.scala +++ b/mtags/src/main/scala/scala/meta/internal/mtags/ScalametaCommonEnrichments.scala @@ -173,6 +173,28 @@ trait ScalametaCommonEnrichments extends CommonMtagsEnrichments { } } + implicit class XtensionPositionLspInverse(pos: l.Position) { + + /** + * LSP position translated to scalameta position. Might return None if + * pos is not contained in input + * + * @param input file input the position relates to + * @return scalameta position with offset if the pos is contained in the file + */ + def toMeta(input: m.Input): Option[m.Position] = { + Try( + m.Position.Range( + input, + pos.getLine, + pos.getCharacter, + pos.getLine, + pos.getCharacter + ) + ).toOption + } + } + protected def filenameToLanguage(filename: String): Language = { if (filename.endsWith(".java")) Language.JAVA else if (