Skip to content
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

Dynamically loaded plugins #41

Closed
vovapolu opened this issue Jul 24, 2019 · 11 comments
Closed

Dynamically loaded plugins #41

vovapolu opened this issue Jul 24, 2019 · 11 comments

Comments

@vovapolu
Copy link

Is your feature request related to a problem? Please describe.

There's no way to extend Metals functionality for now, except only by hardcoding it into a custom fork of the code base. The problem is one of modularity—we can't augment functionality of the language server in an easily maintainable way.

In the long run, it will be beneficial for users and the whole tooling community if users can extend and customize Metals with functionality of their choosing.

Describe the solution you'd like

I'd like the ability to add dynamically-loaded plugins. One of the possible solutions is SIP that loads JARs implementing some plugin interface.

I haven't yet studied the Metals codebase enough to describe the general plugin interface I'd like, but I have some thoughts:

  • It should have access to all main language server functionality, reading and writing files, publishing diagnostics, inspecting SemanticDb and other information about code.
  • There should be callbacks for the various endpoints of language server events. Possibly these events will be the same as LSP endpoints (didChange, didOpen and etc.), but I assume there will be additional events specifically for Metals and for Scala-related events.
  • A first version of the plug-in interface can be created by determining what would be required to implement parts of Metals itself using the plug-in interface. I'm thinking about scalafmt and scalafix. There is no need to implement any part of Metals with plugins, but the functionality required to implement Scalafmt and Scalafix as plugins would give a very good idea of what a plugin interface should look like. Scalafmt is a good example for a plugin, because it's not deeply connected with internals of the language server and it performs only one function— formatting the code. Scalafix also performs quite unique and discrete functions, and at the same time has more intersections with other popular projects in terms of linting, for example. (https://github.com/wartremover/wartremover or https://github.com/sksamuel/scapegoat).

I could see some users may want to use one of those with extended support in the IDE. Of course, my thoughts about scalafmt and scalafix are purely hypothetical, they aren't listed here only as examples or thought experiments.

Describe alternatives you've considered

I don't thinks there are alternatives for the idea of plugins for Metals, but I've thought of some alternatives for my specific case that can show the necessity for plugins.

I'm developing code analysis tool, and for my specific case I have considered two options: compiler plugin and Scalafix rules. Both of them lack the ability to perform flow control of the source code, e.g. inspecting semantic and syntactic information for several files simultaneously.

The next big problem here is to do it incrementally, load and save intermediate info for only some files depending on what files were changed, how often they are changing and how these files are connected.

I feel such functionality can be implemented only on IDE level.

Search terms:

plugin, linting, code analysis, modularity

@jdegoes
Copy link

jdegoes commented Jul 24, 2019

@vovapolu

I'm a big supporter and frequent user of Metals, and I'd love to see the tool grow to become the de facto language server for Scala.

One of the things that could help facilitate this is having the ability to extend Metals in new ways as dictated by the requirements of businesses, without invasively modifying any code.

A plug-in architecture would be a welcome addition and help cement Metal's role in the future of the Scala programming language.

@jdegoes
Copy link

jdegoes commented Jul 25, 2019

@olafurpg

If this is considered a valuable direction, we could put together a pull request.

@olafurpg
Copy link
Member

olafurpg commented Aug 1, 2019

Thank you for reporting! Can you please provide more examples of functionality in Metals that you would like to extend with plugins?

The benefit of built-in integrations over plugins is that we can ensure the built-in integrations work consistently and play nicely with each other. Composing plugins is a hard problem.

Linting

I am on the fence about allowing plugins to report custom diagnostics since I think compile error in Metals should always reflect compile errors in the build. Both Wartremover and Scapegoat already work out-of-the-box with Metals since they are compiler plugins and we report diagnostics from the compiler. For linters like Scalafix and Scalastyle, my idea so far has been to implement custom integrations in the Metals codebase.

I'm developing code analysis tool, and for my specific case I have considered two options: compiler plugin and Scalafix rules. Both of them lack the ability to perform flow control of the source code, e.g. inspecting semantic and syntactic information for several files simultaneously.

I would prefer to discuss how to enrich the Scalafix API to enable your more advanced use-case. I estimate it wouldn't require a large change in Scalafix to allow rules to inspect several files simultaneously. A lot of design work is required to implement a rich semantic API, we should strive for a solution that makes it possible to run those analyzers inside Metals as well as build tools (sbt, gradle, maven, pants, ...) so they can be enforced in CI.

Code formatting

I would be happy to merge a PR that adds an integration with, for example, Scalariform. The benefit of this approach is that we can code review the Scalariform integration to make sure it works as smoothly as the Scalafmt integration.

Refactoring

Once refactoring support is implemented, my idea so far has been to build on top of Scalafix in a similar way as with linting. Some more design work is required to enable IntelliJ-style inspections (example: convert single line string to multiline """), and we might want to implement a plugin architecture here 🤔

@tgodzik
Copy link
Contributor

tgodzik commented Aug 1, 2019

We could also do plugins as extensions to LSP - similar to TreeViewModel one. So each plugin would provide additional methods based on the information Metals provide.

Although I agree that we should most likely keep everything with Metals and we can enable certain plugins by settings.

@jdegoes
Copy link

jdegoes commented Aug 24, 2019

@olafurpg

Can you please provide more examples of functionality in Metals that you would like to extend with plugins?

Examples:

  • Warnings or errors for Spring applications
  • Warnings or errors for writing pure functional Scala
  • Warnings or errors for SQL queries embedded into strings
  • Etc.

I am on the fence about allowing plugins to report custom diagnostics since I think compile error in Metals should always reflect compile errors in the build.

This is a good argument for errors being reproducible in the build. Are compiler plug-ins the only way to generate build failures that can be used by Metals?

For linters like Scalafix and Scalastyle, my idea so far has been to implement custom integrations in the Metals codebase.

The problem that I see it is that some linters depend on what tools you are using (Spring, JDBC, Spark, etc.), and others depend on personal preferences (e.g. functional Scala).

Personally, I want to lint purely functional Scala. I don't think that would be great to push on other users. How would you suggest doing that without a Metals plug-in of some kind?

I would prefer to discuss how to enrich the Scalafix API to enable your more advanced use-case. I estimate it wouldn't require a large change in Scalafix to allow rules to inspect several files simultaneously.

Control flow analysis in Scalafix would be awesome. @vovapolu Do you have anything you could share / show with @olafurpg?

@olafurpg
Copy link
Member

Are compiler plug-ins the only way to generate build failures that can be used by Metals?

At the moment yes. After #7, it will also be possible generate custom compile errors in Metals and the build with custom Scalafix rules.

How would you suggest doing that without a Metals plug-in of some kind?

Scalafix already has a plugin architecture that allows people to implement their own code analyzers that report diagnostics along with optional auto-fixes https://scalacenter.github.io/scalafix/docs/developers/tutorial.html. The way I see people using this in Metals is that they update their .scalafix.conf to enable analyzers that match their preferences

# .scalafix.conf
plugins = "io.vovapolu:functional-scala:1.0.0" # (hypothetical example)
rules = [
  MySpringAnalyzer,
  MyFunctionalScalaAnalyzer,
  ...
]

This configuration would be picked up by Metals and sbt-scalafix as well as any other Scalafix build tool integrations for Maven, Gradle, Mill, ..

@vovapolu
Copy link
Author

@jdegoes @olafurpg
Scalafix plugins are great way to implement various static code analyzers and etc. The main problem here is flow control. For instance, I want to track certain properties of symbols (functions or classes), e.g. does function use println in their body.

// NastyFunction.scala
object NastyFunction {
  def usePrintln(foo: String) = {
    println(foo)
  }
}
// MyBeautifulCode.scala
object MyBeautifulCode {
  def myFunc = {
    NastyFunction.usePrintln("Hello world") // desirable error
  }
}

And here is everything becomes complicated. I need to store information about such properties and update it. In IDE I need to update it incrementally in order to provide quick response. Another problem is memory -- with a sufficiently large number of analyzing files (in IDE or other tool like some CLI) we can't store all that info in memory, we need to cache it on the hard disk.

I doubt that the Scalafix should solve such problems. But it can be very helpful in analyzing the dedicated files and providing information that later will be stored in memory and hard disk.

I see some similar ideas in metals and overall I feel SemanticDb is the way to the same direction. But now I can't describe the generic and elegant solution for flow analysis without hardcoding logic to the metals fork or some kind of plugins for metals. Probably some work should be done both from Scalafix side and metals.

@olafurpg What do you think? If there's some existing solution for problems I've described can you show me it? I did not fully understand some parts of metals yet.

@vovapolu
Copy link
Author

@olafurpg As the temporary solution for the current topic can we give users a way to use metals forks in their editors (at least in the vscode as the most popular editor)? In vscode there's a setting to manually set up metals version, we could allow to set up organization with some nasty warning (USE IT AT YOUR OWN RISK or so).

@vovapolu
Copy link
Author

@olafurpg Any thoughts about flow control or providing access to metals forks in the editors?

@olafurpg
Copy link
Member

I believe it's possible and desirable to extend Scalafix to support inter-project and inter-file analysis. I don't have a design proposal at the top of my head but I'm open for suggestions. I agree it's important to take into account incremental analysis so the Scalafix rules could be used in the context of an IDE such as Metals.

For running forks of Metals, the VS Code extension already supports providing a custom Metals version as well as adding custom repositories for resolution. Have you tried publishing a fork of Metals under the org.scalameta domain with a custom version suffix like 0.7.5-vovapolu?

@olafurpg
Copy link
Member

olafurpg commented Oct 7, 2019

Closing this feature request as a wontfix. See #41 (comment) for more details.

The benefit of built-in integrations over plugins is that we can ensure the built-in integrations work consistently and play nicely with each other. Composing plugins is a hard problem.

@vovapolu I'm happy to continue the discussion in the Scalafix issue tracker for enabling incremental analysis across multiple files. I'm also happy to merge PRs that refactor Metals internals to easier to extend with new functionality.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants