Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -434,12 +434,12 @@ class CompilerConfiguration(
}

case class JavaLazyCompiler(
javaTarget: JvmTarget,
target: JvmTarget,
search: SymbolSearch,
completionItemPriority: CompletionItemPriority,
) extends LazyCompiler {

def buildTargetId: BuildTargetIdentifier = javaTarget.id
def buildTargetId: BuildTargetIdentifier = target.id

protected def newCompiler(
classpath: Seq[Path],
Expand All @@ -449,10 +449,13 @@ class CompilerConfiguration(
val shouldUseOpts = featureFlags
.readBoolean(FeatureFlag.JAVAC_OPTIONS)
.orElse(false)
val options = javaTarget match {
val buildOptions = target match {
case j: JavaTarget if shouldUseOpts => j.options
case _ => Nil
}
val lintOptions =
userConfig().javaLintOptions.values.map(option => s"-Xlint:$option")
val options = buildOptions ++ lintOptions
configure(pc, search, completionItemPriority)
.newInstance(
buildTargetId.getUri(),
Expand Down
31 changes: 17 additions & 14 deletions metals/src/main/scala/scala/meta/internal/metals/Compilers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1765,20 +1765,23 @@ class Compilers(
private def loadJavaCompiler(
targetId: BuildTargetIdentifier
): Option[PresentationCompiler] = {
buildTargets.jvmTarget(targetId).map { javaTarget =>
jcache
.computeIfAbsent(
PresentationCompilerKey.JavaBuildTarget(targetId),
{ _ =>
workDoneProgress.trackBlocking(
s"${config.icons().sync}Loading presentation compiler"
) {
JavaLazyCompiler(javaTarget, search, completionItemPriority())
}
},
)
.await
}
buildTargets
.javaTarget(targetId)
.orElse(buildTargets.jvmTarget(targetId))
.map { javaTarget =>
jcache
.computeIfAbsent(
PresentationCompilerKey.JavaBuildTarget(targetId),
{ _ =>
workDoneProgress.trackBlocking(
s"${config.icons().sync}Loading presentation compiler"
) {
JavaLazyCompiler(javaTarget, search, completionItemPriority())
}
},
)
.await
}
}

private def protoCompiler: PresentationCompiler = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,37 @@ case class JavaFormatConfig(
eclipseFormatProfile: Option[String],
)

case class JavaLintOptions(values: List[String])

object JavaLintOptions {
val allValues: List[String] = List(
"cast", "deprecation", "dep-ann", "divzero", "empty", "fallthrough",
"finally", "lossy-conversions", "overloads", "overrides", "rawtypes",
"removal", "serial", "static", "strictfp", "synchronization", "text-blocks",
"this-escape", "try", "unchecked", "varargs",
)

val default: JavaLintOptions = JavaLintOptions(allValues)

private val allowed: Set[String] =
(allValues :+ "preview").toSet

def fromConfig(
values: Option[List[String]]
): Either[String, JavaLintOptions] =
values match {
case None => Right(default)
case Some(values) =>
values.find(value => !allowed(value)) match {
case Some(invalid) =>
Left(
s"invalid config value '$invalid' for javaLintOptions. Valid values are ${allowed.toSeq.sorted.map(value => s""""$value"""").mkString(", ")}"
)
case None => Right(JavaLintOptions(values))
}
}
}

/**
* Configuration that the user can override via workspace/didChangeConfiguration.
*/
Expand Down Expand Up @@ -82,6 +113,7 @@ case class UserConfiguration(
javaFormatter: Option[JavaFormatterConfig] = None,
scalafixRulesDependencies: List[String] = Nil,
scalafixLintEnabled: Boolean = false,
javaLintOptions: JavaLintOptions = JavaLintOptions.default,
customProjectRoot: Option[String] = None,
verboseCompilation: Boolean = false,
automaticImportBuild: AutoImportBuildKind = AutoImportBuildKind.Off,
Expand Down Expand Up @@ -220,6 +252,7 @@ case class UserConfiguration(
Some(scalafixRulesDependencies),
),
Some(("scalafixLintEnabled", scalafixLintEnabled)),
listField("javaLintOptions", Some(javaLintOptions.values)),
optStringField("customProjectRoot", customProjectRoot),
Some(("verboseCompilation", verboseCompilation)),
Some(
Expand Down Expand Up @@ -521,6 +554,16 @@ object UserConfiguration {
|""".stripMargin,
isBoolean = true,
),
UserConfigurationOption(
"java-lint-options",
JavaLintOptions.default.values.mkString("[", ",", "]"),
"""["deprecation", "unchecked"]""",
"Java lint diagnostics",
"""Javac `-Xlint` options passed to the Java presentation compiler.
|Use an empty array to disable Java lint diagnostics.
|""".stripMargin,
isArray = true,
),
UserConfigurationOption(
"excluded-packages",
"[]",
Expand Down Expand Up @@ -1308,6 +1351,10 @@ object UserConfiguration {

val scalafixLintEnabled =
getBooleanKey("scalafix-lint-enabled").getOrElse(false)
val javaLintOptions = getParsedArrayKey(
"java-lint-options",
JavaLintOptions.fromConfig,
).getOrElse(JavaLintOptions.default)

val customProjectRoot = getStringKey("custom-project-root")
val verboseCompilation =
Expand Down Expand Up @@ -1506,6 +1553,7 @@ object UserConfiguration {
javaFormatter,
scalafixRulesDependencies,
scalafixLintEnabled,
javaLintOptions,
customProjectRoot,
verboseCompilation,
autoImportBuilds,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ final class CodeActionProvider(
new ConvertToNamedLambdaParameters(trees, compilers),
new AddMissingOverrideAnnotation(javaTrees, buffers),
new RemoveUnusedJavaImport(buffers),
new RemoveRedundantCast(buffers),
new SuppressWarnings(javaTrees, buffers),
new GenerateConstructors(javaTrees, buffers),
new GenerateGettersSetters(javaTrees, buffers),
new GenerateEqualsHashCodeToString(javaTrees, buffers),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package scala.meta.internal.metals.codeactions

import scala.concurrent.ExecutionContext
import scala.concurrent.Future

import scala.meta.internal.metals.Buffers
import scala.meta.internal.metals.MetalsEnrichments._
import scala.meta.pc.CancelToken

import org.eclipse.{lsp4j => l}

class RemoveRedundantCast(buffers: Buffers) extends CodeAction {
import RemoveRedundantCast._

override def kind: String = l.CodeActionKind.QuickFix
override def isScala: Boolean = false
override def isJava: Boolean = true

override def contribute(
params: l.CodeActionParams,
token: CancelToken,
)(implicit ec: ExecutionContext): Future[Seq[l.CodeAction]] = Future {
val path = params.getTextDocument().getUri().toAbsolutePath
val range = params.getRange()

for {
text <- buffers.get(path).orElse(path.readTextOpt).toSeq
diagnostic <- params.getContext().getDiagnostics().asScala.toSeq
if isRedundantCast(diagnostic)
if range.overlapsWith(diagnostic.getRange())
edit <- removeCastEdit(text, diagnostic.getRange()).toSeq
} yield CodeActionBuilder.build(
title,
kind,
diagnostics = List(diagnostic),
changes = Seq(path -> Seq(edit)),
)
}
}

object RemoveRedundantCast {
val title = "Remove redundant cast"

private val RedundantCastCode = "compiler.warn.redundant.cast"

private def isRedundantCast(diagnostic: l.Diagnostic): Boolean =
Option(diagnostic.getCode()).exists(code =>
code.isLeft() && code.getLeft() == RedundantCastCode
)

private def removeCastEdit(
text: String,
range: l.Range,
): Option[l.TextEdit] = {
val start = positionToOffset(text, range.getStart())
for {
open <- findOpenParen(text, start)
close <- findCloseParen(text, open + 1)
if close > open
} yield {
val editEnd = skipHorizontalWhitespace(text, close + 1)
val editStart =
if (
editEnd >= text.length || text
.charAt(editEnd) == '\n' || text.charAt(editEnd) == '\r'
)
skipHorizontalWhitespaceBackwards(text, open)
else
open
new l.TextEdit(
new l.Range(
text.indexToLspPosition(editStart),
text.indexToLspPosition(editEnd),
),
"",
)
}
}

private def findOpenParen(text: String, from: Int): Option[Int] = {
var index = from.min(text.length - 1)
var result = -1
var continue = true
while (continue && index >= 0) {
text.charAt(index) match {
case '(' =>
result = index
continue = false
case '\n' | '\r' | ';' | '=' | ',' =>
continue = false
case _ =>
index -= 1
}
}
if (result >= 0) Some(result) else None
}

private def findCloseParen(text: String, from: Int): Option[Int] = {
var index = from.max(0)
var result = -1
var continue = true
while (continue && index < text.length) {
text.charAt(index) match {
case ')' =>
result = index
continue = false
case '\n' | '\r' | ';' =>
continue = false
case _ =>
index += 1
}
}
if (result >= 0) Some(result) else None
}

private def skipHorizontalWhitespace(text: String, from: Int): Int = {
var index = from
while (
index < text.length && {
val ch = text.charAt(index)
ch == ' ' || ch == '\t'
}
) index += 1
index
}

private def skipHorizontalWhitespaceBackwards(
text: String,
from: Int,
): Int = {
var index = from - 1
while (
index >= 0 && (text.charAt(index) == ' ' || text.charAt(index) == '\t')
)
index -= 1
index + 1
}

private def positionToOffset(text: String, position: l.Position): Int = {
var line = 0
var character = 0
var offset = 0
while (
offset < text.length &&
(line < position.getLine() || character < position.getCharacter())
) {
if (text.charAt(offset) == '\n') {
line += 1
character = 0
} else {
character += 1
}
offset += 1
}
offset
}
}
Loading
Loading