-
Notifications
You must be signed in to change notification settings - Fork 321
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
Add ConvertToNamedArguments code action #3971
Conversation
@camgraff This is really great!!! Thank you very much for working on this. > tests. I started by trying to extend BasicCodeActionLspSuite, but ran into this error with the presentation compiler. I'd appreciate some direction here :)I think this is because there's no implementation in scala3/....../ScalaPresentationCompiler.scala, you have to add something like this override def convertToNamedArguments(
params: OffsetParams,
numUnnamedArgs: Int
): CompletableFuture[ju.List[TextEdit]] = ??? > Scala3 Support
Other improvementAlso, there's something to improve:
We should separate showing a code action tooltip and constructing the TextEdit part. For example InsertInferredType code action do the thing:
> There's probably some edge cases that I haven't considered. One is when the apply is inside a block which can't have named args.I think you don't have to worry too much about these edge cases :) If users add parameter names and compile failed, users can immediately notice it doesn't work and undo the code action. There might be other edge cases, but we can improve this feature iteratively 👍 > Maybe we'd want to consider the next enclosing apply (ie, addCompilationUnit) as a candidate?Sounds good! However, I'm concerned that it may introduce complexities.
|
mtags/src/main/scala-2/scala/meta/internal/pc/ConvertToNamedArgumentsProvider.scala
Show resolved
Hide resolved
Also, I think it's ok to work on Scala3 support in another PR if you want :) |
Thanks @tanishiking for the pointers! I've made the code action delegate to a |
Looks like many of the LSP code action tests are failing due to this action now being returned by the server. Is the best way to fix this just going through and updating |
@camgraff Thank you for working on this! I'll take a look today |
Good point! option1Yes, basically we can fix those test failures by updating However, I personally don't think this is not the best way to fix failing tests (even though we'd been doing that). diff --git a/tests/unit/src/test/scala/tests/codeactions/ExtractValueLspSuite.scala b/tests/unit/src/test/scala/tests/codeactions/ExtractValueLspSuite.scala
index fc5373cb2e..0868d10afd 100644
--- a/tests/unit/src/test/scala/tests/codeactions/ExtractValueLspSuite.scala
+++ b/tests/unit/src/test/scala/tests/codeactions/ExtractValueLspSuite.scala
@@ -1,6 +1,7 @@
package tests.codeactions
import scala.meta.internal.metals.codeactions.ExtractValueCodeAction
+import scala.meta.internal.metals.codeactions.ConvertToNamedArguments
class ExtractValueLspSuite
extends BaseCodeActionLspSuite("extractValueRewrite") {
@@ -15,7 +16,8 @@ class ExtractValueLspSuite
|
|}
|""".stripMargin,
- ExtractValueCodeAction.title,
+ s"""|${ExtractValueCodeAction.title}
+ |${ConvertToNamedArguments.title}""".stripMargin,
"""|object Main {
| def method2(i: Int) = ???
| def method1(s: String): Unit = { option2Instead of updating diff --git a/tests/unit/src/main/scala/tests/TestingServer.scala b/tests/unit/src/main/scala/tests/TestingServer.scala
index 41c22befc1..7553009606 100644
--- a/tests/unit/src/main/scala/tests/TestingServer.scala
+++ b/tests/unit/src/main/scala/tests/TestingServer.scala
@@ -1131,10 +1131,17 @@ final class TestingServer(
query: String,
expected: String,
kind: List[String],
- root: AbsolutePath = workspace
+ root: AbsolutePath = workspace,
+ filterAction: l.CodeAction => Boolean = _ => true
)(implicit loc: munit.Location): Future[List[l.CodeAction]] =
for {
- (codeActions, codeActionString) <- codeAction(filename, query, root, kind)
+ (codeActions, codeActionString) <- codeAction(
+ filename,
+ query,
+ root,
+ kind,
+ filterAction
+ )
} yield {
Assertions.assertNoDiff(codeActionString, expected)
codeActions
@@ -1175,7 +1182,8 @@ final class TestingServer(
filename: String,
query: String,
root: AbsolutePath,
- kind: List[String]
+ kind: List[String],
+ filterAction: l.CodeAction => Boolean = _ => true
): Future[(List[l.CodeAction], String)] =
for {
(_, params) <- codeActionParams(
@@ -1188,10 +1196,13 @@ final class TestingServer(
)
)
codeActions <- server.codeAction(params).asScala
- } yield (
- codeActions.asScala.toList,
- codeActions.map(_.getTitle()).asScala.mkString("\n")
- )
+ } yield {
+ val actions = codeActions.asScala.filter(filterAction)
+ (
+ actions.toList,
+ actions.map(_.getTitle()).mkString("\n")
+ )
+ }
def assertHighlight(
filename: String,
diff --git a/tests/unit/src/main/scala/tests/codeactions/BaseCodeActionLspSuite.scala b/tests/unit/src/main/scala/tests/codeactions/BaseCodeActionLspSuite.scala
index ccd3c9c8e7..522d3b30a7 100644
--- a/tests/unit/src/main/scala/tests/codeactions/BaseCodeActionLspSuite.scala
+++ b/tests/unit/src/main/scala/tests/codeactions/BaseCodeActionLspSuite.scala
@@ -7,6 +7,7 @@ import scala.meta.internal.metals.{BuildInfo => V}
import munit.Location
import munit.TestOptions
+import org.eclipse.{lsp4j => l}
import tests.BaseLspSuite
abstract class BaseCodeActionLspSuite(suiteName: String)
@@ -97,7 +98,8 @@ abstract class BaseCodeActionLspSuite(suiteName: String)
extraOperations: => Unit = (),
fileName: String = "A.scala",
changeFile: String => String = identity,
- expectError: Boolean = false
+ expectError: Boolean = false,
+ filterAction: l.CodeAction => Boolean = _ => true
)(implicit loc: Location): Unit = {
val scalacOptionsJson =
if (scalacOptions.nonEmpty)
@@ -133,7 +135,8 @@ abstract class BaseCodeActionLspSuite(suiteName: String)
path,
changeFile(input),
expectedActions,
- kind
+ kind,
+ filterAction = filterAction
)
.recover {
case _: Throwable if expectError => Nil
diff --git a/tests/unit/src/test/scala/tests/codeactions/ExtractValueLspSuite.scala b/tests/unit/src/test/scala/tests/codeactions/ExtractValueLspSuite.scala
index fc5373cb2e..e3942ee4f1 100644
--- a/tests/unit/src/test/scala/tests/codeactions/ExtractValueLspSuite.scala
+++ b/tests/unit/src/test/scala/tests/codeactions/ExtractValueLspSuite.scala
@@ -24,7 +24,8 @@ class ExtractValueLspSuite
| }
|
|}
- |""".stripMargin
+ |""".stripMargin,
+ filterAction = action => action.getTitle() == ExtractValueCodeAction.title
)
check( |
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 left several comments on the test, and some nitpick comments, but overall it looks good to me!
metals/src/main/scala/scala/meta/internal/metals/codeactions/ConvertToNamedArguments.scala
Outdated
Show resolved
Hide resolved
metals/src/main/scala/scala/meta/internal/metals/codeactions/ConvertToNamedArguments.scala
Outdated
Show resolved
Hide resolved
metals/src/main/scala/scala/meta/internal/metals/codeactions/ConvertToNamedArguments.scala
Outdated
Show resolved
Hide resolved
mtags/src/main/scala-2/scala/meta/internal/pc/ConvertToNamedArgumentsProvider.scala
Show resolved
Hide resolved
mtags/src/main/scala-2/scala/meta/internal/pc/ConvertToNamedArgumentsProvider.scala
Outdated
Show resolved
Hide resolved
metals/src/main/scala/scala/meta/internal/metals/ServerCommands.scala
Outdated
Show resolved
Hide resolved
metals/src/main/scala/scala/meta/internal/metals/ServerCommands.scala
Outdated
Show resolved
Hide resolved
tests/unit/src/test/scala/tests/codeactions/ConvertToNamedArgumentsSuite.scala
Outdated
Show resolved
Hide resolved
tests/unit/src/test/scala/tests/codeactions/ConvertToNamedArgumentsSuite.scala
Outdated
Show resolved
Hide resolved
tests/unit/src/test/scala/tests/codeactions/ConvertToNamedArgumentsSuite.scala
Outdated
Show resolved
Hide resolved
8883d35
to
4a82843
Compare
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.
This looks great! There are few tests failing, but I think it's fine to add the code action in most of them. I wouldn't filter it out everywhere (maybe just in case of noAction
), since there might be situations that we show the code action in too many places
args.zipWithIndex | ||
.zip(fun.tpe.params) | ||
.collect { | ||
case ((arg, index), param) if argIndices.contains(index) => { |
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.
Does the compiler have any idea that there is a named parameter here? This might actually be the case, but just wanted to double check. We wouldn't need to calculate it before in that case. Otherwise, that's probably the right approach.
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.
Looks like ArgCompletions
check if the argument is named one or not like
metals/mtags/src/main/scala-2/scala/meta/internal/pc/completions/ArgCompletions.scala
Lines 29 to 38 in 2cee762
lazy val isNamed: Set[Name] = apply.args.iterator | |
.filterNot(_ == ident) | |
.zip(baseParams.iterator) | |
.map { | |
case (AssignOrNamedArg(Ident(name), _), _) => | |
name | |
case (_, param) => | |
param.name | |
} | |
.toSet |
I haven't confirmed yet, but we might be able to check it's named argument or not without indices.
edit: sorry, this is from Scala2
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 had tried this originally, but it seems like the compiler doesn't pick up on NamedArg
. e.g. when I trigger the code action on
addCompilationUnit(
params.text(),
filename = params.uri().toString(),
cursor = None
)
and add some logging, it shows the args as:
ConvertToNamedArgumentsProvider.scala:25 a: Apply(
fun = Select(
qualifier = Select(qualifier = This(qual = ConvertToNamedArgumentsProvider), name = params),
name = text
),
args = List()
)
ConvertToNamedArgumentsProvider.scala:26 a.isInstanceOf[NamedArg]: false
ConvertToNamedArgumentsProvider.scala:25 a: Apply(
fun = Select(
qualifier = Apply(
fun = Select(
qualifier = Select(qualifier = This(qual = ConvertToNamedArgumentsProvider), name = params),
name = uri
),
args = List()
),
name = toString
),
args = List()
)
ConvertToNamedArgumentsProvider.scala:26 a.isInstanceOf[NamedArg]: false
ConvertToNamedArgumentsProvider.scala:25 a: Select(qualifier = Ident(name = scala), name = None)
ConvertToNamedArgumentsProvider.scala:26 a.isInstanceOf[NamedArg]: false
extends BaseCodeActionLspSuite("insertInferredType") { | ||
extends BaseCodeActionLspSuite( | ||
"insertInferredType", | ||
filterAction = act => |
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.
Maybe we could add this in a specific test case? I thinking that it actually helps us to see where the new action pops up and double check if it actually shows up there correctly.
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.
sounds good, I will move it to check()
val range = params.getRange() | ||
|
||
val maybeApply = for { | ||
term <- trees.findLastEnclosingAt[Term.Apply](path, range.getStart()) |
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.
term <- trees.findLastEnclosingAt[Term.Apply](path, range.getStart()) | |
term <- trees.findLastEnclosingAt[Term.Apply](path, range.getStart()) | |
if !term.fun.encloses(range) |
So that we don't get the code action when at for example F<<>>uture.succesfull(1)
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.
LGTM! great job!
Let's wait for @tgodzik review, BTW, it would be awesome if you can squash commits into more meaningful granularities, at least squashing "scalafmt", "scalafix", and "add/fix tests" commits would be helpful. |
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.
LGTM!
And of course I forgot to squash 🤦♂️ Maybe we should set it as a default |
@ maintainers thanks for all the work you do! This is my first PR, I appreciate any feedback 🙂
Fixes #4059
Adds a code action which converts all positional arguments in the enclosing
![metals-convert-to-named-args](https://user-images.githubusercontent.com/46980357/171309818-97dff848-1e2c-4e13-a95a-77b5c5e12aae.gif)
apply
method to named args.TODOS/open questions:
[ ] - Scala 3 support
[ ] - tests. I started by trying to extend
BasicCodeActionLspSuite
, but ran into this error with the presentation compiler. I'd appreciate some direction here :)[ ] - Currently, if there are no args in the enclosing
apply
the code action is not available which means if my cursor were onparams.text()
in the gif above, I'd see no code action. Maybe we'd want to consider the next enclosingapply
(ieaddCompilationUnit
) as a candidate?[ ] - There's probably some edge cases that I haven't considered. One is when the
apply
is inside a block which can't have named args.