-
Notifications
You must be signed in to change notification settings - Fork 330
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Turn Dotty import suggestions into code actions #2536
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -30,4 +30,22 @@ object ScalacDiagnostic { | |||||
case _ => None | ||||||
} | ||||||
} | ||||||
|
||||||
object SymbolNotFoundWithImportSuggestions { | ||||||
// https://github.com/lampepfl/dotty/blob/3b741d67f8631487aa553c52e03ac21157e68563/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala#L311-L344 | ||||||
private val regexes = for { | ||||||
fix <- List("One of the following imports", "The following import") | ||||||
help <- List("fix ", "make progress towards fixing") | ||||||
} yield s"""(?s)$fix imports might $help the problem:(.*)""".r | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
?
Comment on lines
+37
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The exact format of the compiler message strings is not stable and may change at any time, please do not rely on it. Instead, I encoruage you to contribute upstream a way to expose this information semantically in our Reporting API (we also implement scalafix-like rewrites which could be displayed as quick fix actions in vscode). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Revisiting long after the fact. @smarter has any thought gone into what this would look like, exposing this information semantically? I've been thinking about this often lately and sort of year for an error index like rust has and more structured errors that we can use for stuff like this. I see some convos that touch on this a bit like this old convo, but has there been any design though or conversation around what this could look like in Dotty? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
No, this is still waiting for a motivated individual to step up :). |
||||||
def unapply(d: l.Diagnostic): Option[List[String]] = | ||||||
regexes.collectFirst { | ||||||
case regex if regex.findFirstIn(d.getMessage()).isDefined => | ||||||
val suggestedImports = regex | ||||||
.findFirstMatchIn(d.getMessage()) | ||||||
.map( | ||||||
_.group(1).linesIterator.filterNot(_.isEmpty()).map(_.trim).toList | ||||||
) | ||||||
suggestedImports.getOrElse(Nil) | ||||||
} | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package scala.meta.internal.metals.codeactions | ||
|
||
import scala.concurrent.ExecutionContext | ||
import scala.concurrent.Future | ||
|
||
import scala.meta.internal.metals.MetalsEnrichments._ | ||
import scala.meta.internal.metals._ | ||
import scala.meta.pc.CancelToken | ||
|
||
import org.eclipse.{lsp4j => l} | ||
|
||
class ImportMissingGiven(compilers: Compilers) extends CodeAction { | ||
override def kind: String = l.CodeActionKind.QuickFix | ||
|
||
override def contribute( | ||
params: l.CodeActionParams, | ||
token: CancelToken | ||
)(implicit ec: ExecutionContext): Future[Seq[l.CodeAction]] = { | ||
|
||
def addGivenImport( | ||
diagnostic: l.Diagnostic, | ||
packageName: String, | ||
name: String | ||
): Future[Seq[l.CodeAction]] = | ||
importMissingSymbol(diagnostic, packageName, name) | ||
|
||
def importMissingSymbol( | ||
diagnostic: l.Diagnostic, | ||
packageName: String, | ||
name: String | ||
): Future[Seq[l.CodeAction]] = { | ||
val textDocumentPositionParams = new l.TextDocumentPositionParams( | ||
params.getTextDocument(), | ||
diagnostic.getRange.getEnd() | ||
) | ||
compilers | ||
.autoImports(textDocumentPositionParams, name, token) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah, I was just thinking about that. The only difficult thing would be to determine import position |
||
.map { imports => | ||
imports.asScala.filter(_.packageName() == packageName).map { i => | ||
val uri = params.getTextDocument().getUri() | ||
val edit = new l.WorkspaceEdit(Map(uri -> i.edits).asJava) | ||
|
||
val codeAction = new l.CodeAction() | ||
|
||
codeAction.setTitle(ImportMissingGiven.title(name, i.packageName)) | ||
codeAction.setKind(l.CodeActionKind.QuickFix) | ||
codeAction.setDiagnostics(List(diagnostic).asJava) | ||
codeAction.setEdit(edit) | ||
|
||
codeAction | ||
} | ||
} | ||
} | ||
|
||
def parseImport(importSuggestion: String): (String, String) = { | ||
val importMembers = importSuggestion.stripPrefix("import ").split(".") | ||
val packageName = importMembers.dropRight(1).mkString(".") | ||
val name = importMembers.last | ||
(packageName -> name) | ||
} | ||
|
||
val codeActions = params | ||
.getContext() | ||
.getDiagnostics() | ||
.asScala | ||
.collect { | ||
case diag @ ScalacDiagnostic.SymbolNotFoundWithImportSuggestions( | ||
rawSuggestions | ||
) if params.getRange().overlapsWith(diag.getRange()) => | ||
Future | ||
.traverse(rawSuggestions) { suggestion => | ||
val (packageName, name) = parseImport(suggestion) | ||
addGivenImport(diag, packageName, name) | ||
} | ||
.map(_.flatten) | ||
} | ||
.toSeq | ||
|
||
Future.sequence(codeActions).map(_.flatten) | ||
} | ||
} | ||
|
||
object ImportMissingGiven { | ||
def title(name: String, packageName: String): String = | ||
s"Import '$name' from package '$packageName'" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package tests.codeactions | ||
|
||
import scala.meta.internal.metals.codeactions.CreateNewSymbol | ||
import scala.meta.internal.metals.codeactions.ImportMissingGiven | ||
|
||
class ImportMissingGivenLspSuite | ||
extends BaseCodeActionLspSuite("importMissingGiven") { | ||
|
||
check( | ||
"basic", | ||
"""|trait X | ||
|trait Y | ||
|object test: | ||
| def f(using x: X) = ??? | ||
| locally { | ||
| object instances { | ||
| given xFromY(using Y) as X = ??? | ||
| } | ||
| <<f>> // error | ||
| } | ||
""".stripMargin, | ||
s"""|${ImportMissingGiven.title("instances", "xFromY")} | ||
|""".stripMargin, | ||
"""|trait X | ||
|trait Y | ||
|object test: | ||
| def f(using x: X) = ??? | ||
| locally { | ||
| object instances { | ||
| given xFromY(using Y) as X = ??? | ||
| } | ||
| <<f>> // error | ||
| } | ||
|""".stripMargin | ||
) | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also wonder if we could get this from the compiler itself and maybe use completions?