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

New scalafmt sbt plugin #1085

Merged
merged 9 commits into from
Dec 12, 2017
Merged

New scalafmt sbt plugin #1085

merged 9 commits into from
Dec 12, 2017

Conversation

vovapolu
Copy link
Collaborator

@vovapolu vovapolu commented Nov 30, 2017

#1081

I renamed old plugin to sbt-cli-scalafmt, because it just calls cli interface (and because I couldn't come up with new name for my plugin :D). I also added new scripted tests in addition to old ones.

Plugin doesn't perform any caching, it just looks to unmanagedSources or sbt files and formats them. *Check tasks throw exceptions and print error messages when check is failed.

@vovapolu vovapolu changed the title New scalafmt plugin New scalafmt sbt plugin Nov 30, 2017
Copy link

@sjrd sjrd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The *Check tasks should definitely throw exceptions (MessageOnlyExceptions), otherwise they are not usable in the CI since they do not cause a non-zero exit code, hence do not fail the build.

moduleName := "sbt-scalafmt",
isOnly(scala212),
sbtPlugin := true,
sbtVersion in Global := "1.0.0"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't put settings in Global in the settings of a project. It is extremely confusing as that affects the whole build, not just the project. It makes no sense.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

old plugin has this setting too :)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then fix the old plugin too ;)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is effectively defining which sbt version the plugin targets, rather than defaulting whatever sbt version you're using for the whole build as defined in build.properties. I've used this in the past to ensure my plugin works on old versions of sbt while using the latest version to build the plugin.

lazy val scalafmtOnCompile =
SettingKey[Boolean]("scalafmt-on-compile", "Format source when compiling")
lazy val scalafmtConfig =
TaskKey[File]("scalafmt-config", "Scalafmtter config file")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: Scalafmtter

SettingKey[Boolean]("scalafmt-on-compile", "Format source when compiling")
lazy val scalafmtConfig =
TaskKey[File]("scalafmt-config", "Scalafmtter config file")
lazy val scalafmtSbt = TaskKey[Unit]("scalafmt-sbt", "Format SBT sources")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The - separated names is a relic of sbt 0.12, not to be used in sbt 1 anymore. This should be named scalafmtSbt, just like the val. In fact, you should probably use taskKey[Unit]("Format sbt sources") instead. Same for the other keys (e.g., scalafmtCheck).

(BuildPaths.projectStandard(proj.base) * GlobFilter("*.scala")).get)

lazy val scalafmtSettings: Seq[Def.Setting[_]] = Seq(
scalafmtOnCompile := false,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This particular default value should be set in the override def buildSettings of the AutoPlugin, so that it is easy for people to just set it for all their projects with scalafmtOnCompile in ThisBuild := true.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I think this one should be in ThisBuild. If you have ProjectRefs to other directories outside your build, you don't want those to get reformatted when they're compiled.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, that's why false is the right default. But rather than define it for every build in the state, just define it once globally, then independent builds (or specific projects) can opt into formatting on compile.

private lazy val projectSources = thisProject.map(proj =>
(BuildPaths.projectStandard(proj.base) * GlobFilter("*.scala")).get)

lazy val scalafmtSettings: Seq[Def.Setting[_]] = Seq(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend calling this scalafmtConfigSettings, to make clear that they are meant to go through an inConfig(...) to be meaningful.

lazy val scalafmtSettings: Seq[Def.Setting[_]] = Seq(
scalafmtOnCompile := false,
scalafmt := formatSources(
unmanagedSources.value,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should read (unmanagedSources in scalafmt).value instead, so that it is possible to override the set of files processed by scalafmt independently of the sources given to the compiler. In complex builds, this can be necessary.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's possible to do something like unmanagedSources.in(scalafmt).or(unmanagedSources).value so that it defaults to unmanagedSources if it's not defined when scoped under the scalafmt task.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(unmanagedSources in scalafmt).value automatically defaults to unmanagedSources.value if the former is not defined. You don't need to do anything for that, it's built in the scopes mechanism.

},
scalafmtCheck :=
checkSources(
unmanagedSources.value,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same: (unmanagedSources in scalafmt).value (not in scalafmtCheck in this case, because scalafmtCheck should always apply on the same set of sources as scalafmt).

checkSources(sbtSources.value, sbtConfig.value, streams.value.log)
checkSources(projectSources.value, scalaConfig.value, streams.value.log)
},
compile := Def.taskDyn {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The very presence of this taskDyn will destroy inspect tree myProject/compile. Please don't do this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is any 100% correct way to do this, but I suspect doing the same as sbt-scalariform should be least controversial https://github.com/sbt/sbt-scalariform/blob/b33f32b43bd267a3b5e2b26a5962a77182709f52/src/main/scala/com/typesafe/sbt/SbtScalariform.scala#L82-L87 override compileInputs and move the dynTask to only cover the scalafmt-related parts

compile := Def.taskDyn {
val defaultCompile = compile.taskValue
if (scalafmtOnCompile.value) {
scalafmt.value
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This unconditionally applies scalafmt even when scalafmtOnCompile is false.

Copy link
Member

@olafurpg olafurpg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! Thanks a lot @sjrd for the review.

I left some comments.

lazy val scalafmtSettings: Seq[Def.Setting[_]] = Seq(
scalafmtOnCompile := false,
scalafmt := formatSources(
unmanagedSources.value,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's possible to do something like unmanagedSources.in(scalafmt).or(unmanagedSources).value so that it defaults to unmanagedSources if it's not defined when scoped under the scalafmt task.

}
import autoImport._

private lazy val scalaConfig =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Call this scalafmtConfigParsed instead?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this means that if the config file changes, sbt needs to be restarted. It would be more idiomatic to assign this to a Task that can be cached based on the file.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scalafmtConfig is a TaskKey[File], and this is calling map on it, which will return an Def.Initialize[Task[Configured[ScalafmtConfig]]] (if I understand correctly) so it's ok.

import autoImport._

private lazy val scalaConfig =
scalafmtConfig.map(Config.fromHoconFile(_).get)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.get throws an exception on failure with full stacktrace, I think we can throw new MessageOnlyException(e.getMessage) to avoid the stack trace

private lazy val scalaConfig =
scalafmtConfig.map(Config.fromHoconFile(_).get)
private lazy val sbtConfig =
scalaConfig.map(conf => conf.copy(runner = conf.runner.forSbt))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would actually be nice to have in the public facing API. Can we move this to ScalafmtConfig.forSbt as well as add a forwarder method Scalafmt.configForSbt exactly like is done here here


The forwarder method is there for binary compatibility reasons since ScalafmtConfig can break binary compatibility.

private lazy val sbtConfig =
scalaConfig.map(conf => conf.copy(runner = conf.runner.forSbt))

private type Input = String
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not help readability IMO, either create a value class or use String.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dwijnand Why did you put a dislike here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just because I disagree (even if the type Input is just an alias, I think it's can be useful to have such aliases).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My view is that you might as well make it a value class then, a type alias like this gives a false sense of type safety

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, it's 2 vs 2, so I leave it :)

Command.args("scalafmt", "run the scalafmt command line interface.") {
case (state, args) =>
org.scalafmt.cli.Cli.main("--non-interactive" +: args.toArray)
PlatformTokenizerCache.megaCache.clear()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You want to run this in the new plugin too, megaCache will eventually go away but for now it's best to call it on every file. scalameta/scalameta#1068

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Call on every file? Your plugin calls it once at start.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And when I should call it? Even when I check source or only when format?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit trickier to decide compared to the cli, but I think we can do it once per config/project. This should ideally run whenever scalafmt is not running, otherwise it shoudl be a well-behaving cache and the only penalty for clearing it too often is a very minor slowdown hit.

onError: (File, Throwable) => T,
onFormat: (File, Input, Output) => T
): Seq[Option[T]] = {
sources.map(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This formats files sequentially, for large projects it may pay off to do .par.map, but I'll leave it to you to decide.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we'll get parallelism for free across projects / configs because of the way sbt works, it's not so clear that parallelism at this level will improve performance. let's measure first.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, since the plugin works at the granularity of project/config then you already get parallelism there. In the cli we use .par to run on all files in the repo

org.scalafmt.cli.Cli.main("--non-interactive" +: args.toArray)
PlatformTokenizerCache.megaCache.clear()
state
lazy val scalafmt = TaskKey[Unit]("scalafmt", "Format Scala sources")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these don't need to be lazy, also probably best to use the sbt macros so you don't need to repeat the name as a string.

@vovapolu
Copy link
Collaborator Author

vovapolu commented Dec 1, 2017

@olafurpg Can you take a look now? I fixed all comments that were unanimously approved :D

Copy link
Member

@olafurpg olafurpg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look great 👍 Second round of review

@@ -0,0 +1,24 @@
> p123/compile:scalafmt
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we maybe add a negative check here?

-> p123/compile:scalafmtCheck

to validate check fails on misformatted files.

state
val scalafmt = taskKey[Unit]("Format Scala sources")
val scalafmtCheck =
taskKey[Boolean]("Check that Scala sources is formatted properly")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fails if a Scala source is mis-formatted. Does not write to files.

val scalafmtCheck =
taskKey[Boolean]("Check that Scala sources is formatted properly")
val scalafmtOnCompile =
settingKey[Boolean]("Format source when compiling")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Format Scala source files on compile, off by default.

taskKey[Boolean]("Check that Scala sources is formatted properly")
val scalafmtOnCompile =
settingKey[Boolean]("Format source when compiling")
val scalafmtConfig = taskKey[File]("Scalafmt config file")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Location of .scalafmt.conf file

val scalafmtOnCompile =
settingKey[Boolean]("Format source when compiling")
val scalafmtConfig = taskKey[File]("Scalafmt config file")
val scalafmtSbt = taskKey[Unit]("Format SBT sources")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Format *.sbt and project/*.scala files for this sbt build

PS. it's written "sbt" in lowercase ;)

}
import autoImport._

private val scalaConfig = scalafmtConfig.map(Config.fromHoconFile(_) match {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since scalafmtConfig is a task key, does that mean this will run on every scalafmt? Parsing config is pretty cheap but it's quite wasteful to do it if you run scalafmt on compile. Can we use sbt's caching utilities to avoid redundant work? http://www.scala-sbt.org/1.x/docs/Faq.html#How+can+a+task+avoid+redoing+work+if+the+input+files+are+unchanged%3F

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's called quite a lot. I'll try to use some caching.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The solution in your link only works for generating output files. It doesn't cache any values of arbitrary type T.

})
private val sbtConfig = scalaConfig.map(_.forSbt)

private type Input = String
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, I don't feel strongly about this. I'm guilty of doing this myself sometimes 😏

): Seq[Option[T]] = {
sources.map(
file => {
val input = IO.read(file)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to make this a cached function? http://www.scala-sbt.org/1.x/docs/Faq.html#How+can+a+task+avoid+redoing+work+if+the+input+files+are+unchanged%3F

Scalafmt is around 6x slower than scalariform because it does a lot of work to figure out the best line wrapping (and the best-first algorithm I used is quite a bad fit for this domain, see #917). Scalafmt performance is usually not a problem for smaller files or if you run scalafmt infrequently but if you run it on compile on large source files it can impact compile times. I'll leave it to you to decide, I would at least keep it in mind.

checkSources(sbtSources.value, sbtConfig.value, streams.value.log)
checkSources(projectSources.value, scalaConfig.value, streams.value.log)
},
compileInputs in compile := Def.taskDyn {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iiuc, this implementation still breaks inspect tree compile, we need to push Def.taskDyn into a scalafmtDoAutoformat task, like is done here:

https://github.com/sbt/sbt-scalariform/blob/master/src/main/scala/com/typesafe/sbt/SbtScalariform.scala#L82-L87

I may be wrong @dwijnand may know better

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, I just copied it from neo-scalafmt-sbt plugin :D

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, Def.taskDyn "breaks" inspect - as in the dependencies don't show up.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition, note that this could/should be a settingDyn, as the test only depends on the value of a setting--the fact that the returned thing is a task is irrelevant. (Too many tasks out there are taskDyns where they could be settingDyns.) If sbt/sbt#3258 was implemented, this problem with breaking inspect wouldn't happen.

Seq(Compile, Test).flatMap(inConfig(_)(scalafmtConfigSettings))

override def buildSettings: Seq[Def.Setting[_]] = Seq(
scalafmtConfig := (baseDirectory in ThisBuild).value / ".scalafmt.conf"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this fail if the file does not exist? I think sbt-scalafmt should work fine out of the box without any .scalafmt.conf, do we have a test for this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can use default config when this file doesn't exist. I first used Option[File] with default None, but changed it thinking .scalafmt file should exists.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I often use scalafmt on smaller personal projects with the default config without making a .scalafmt file.

@vovapolu
Copy link
Collaborator Author

vovapolu commented Dec 3, 2017

@olafurpg I think caching isn't going to be easy to implement. Your link is suitable for formatSources task, for example, but not for caching some values computed from files. So I suggest to leave it for next PRs :)

Copy link
Member

@olafurpg olafurpg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only minor comments remaining, otherwise I think this plugin is soon ready for people to try out! 😄

val scalafmtSbt = taskKey[Unit]("Format SBT sources")
settingKey[Boolean](
"Format Scala source files on compile, off by default.")
val scalafmtConfig = taskKey[Option[File]](
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that you must scalafmtConfig := Some(file(".scalafmt.conf")) to avoid the default settings? I would prefer to make this non-Option and if the file does not exist then use default settings.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It searches for .scalafmt.conf file by default. And you can redefine it for your custom file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, cool. OK this is fine then 👍

else Def.task(())
val previousInputs = (compileInputs in compile).value
task.map(_ => previousInputs)
scalafmtDoFormatOnCompile := Def.taskDyn {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As recommended by @sjrd we should make this a settingDyn

@vovapolu
Copy link
Collaborator Author

vovapolu commented Dec 4, 2017

I'm merging it?

@olafurpg
Copy link
Member

olafurpg commented Dec 4, 2017

Please hold a bit, I want to give the plugin a swing locally first. I was traveling to Munich today, I'll try to take a look tomorrow!

Copy link

@sjrd sjrd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good from my point of view.

I still think the sbtVersion in Global should be fixed, but it could be in a different PR, given that it's already broken in master anyway because of the other plugin.

Copy link
Member

@olafurpg olafurpg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I gave the plugin a swing locally and found a few low hanging improvements

(file, e) => log.error(s"Error in ${file.toString}: $e"),
(file, input, output) => {
if (input != output) {
log.info(s"Successfully formatted ${file.toString}.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This log entry will be quite noisy for large projects with 100s of sources. I propose we call this once per formatSources with a message like "Formatting 1 Scala source"/"Formatting 2 Scala sources" similar to how scalariform does https://github.com/sbt/sbt-scalariform/blob/a378b724a76ee94819939efc0e9adebd896e57d9/src/main/scala/com/typesafe/sbt/Scalariform.scala#L80

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

},
scalafmtDoFormatOnCompile := Def.settingDyn {
if (scalafmtOnCompile.value) {
scalafmt in resolvedScoped.value.scope
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will run scalafmt on all sources on every compile, including nop compiles. It does not take advantage of incrementality. Is this expected/intended behavior? cc/ @fommil I think scalariform uses FileFunction.cached to make this incremental https://github.com/sbt/sbt-scalariform/blob/a378b724a76ee94819939efc0e9adebd896e57d9/src/main/scala/com/typesafe/sbt/Scalariform.scala#L85

Copy link

@fommil fommil Dec 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah it's only supposed to be formatting files that are being compiled, not all of them

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's leave caching for next PRs. I would like to implement it entirely, not only in some specific places. I will write a warning in the task description for now :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@olafurpg that's a wrong solution. I don't know why scalariform uses it. FileFunction.cached accepts set of files, if you modify them (and we modify them by formatting), then it thinks that files are changed and runs the function again. So it needs two iteration to "converge" to formatted source and forget about it.


lazy val scalafmtConfigSettings: Seq[Def.Setting[_]] = Seq(
scalafmt := formatSources(
(unmanagedSources in scalafmt).value,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This includes non-Scala source like *.java files. Running on the akka build results in a lot of parse errors like this

[error] Error in /Users/ollie/dev/akka/akka-testkit/src/test/java/akka/testkit/AkkaJUnitActorSystemResource.java: <input>:48: error: expected class or object definition
[error] public class AkkaJUnitActorSystemResource extends ExternalResource {
[error] ^
[error] Error in /Users/ollie/dev/akka/akka-testkit/src/test/java/akka/testkit/TestActorRefJavaCompile.java: <input>:10: error: expected class or object definition
[error] public class TestActorRefJavaCompile {
[error] ^

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add some *.java files to the scripted tests

@olafurpg
Copy link
Member

olafurpg commented Dec 5, 2017

I just tried out the plugin on one of my projects and noticed that it formatted files that are excluded with project.excludeFilters in .scalafmt.conf. The plugin should use the (config: ScalafmtConfig).project.matcher.matches(String): Boolean to skip files.

The setting is used here for example: https://github.com/scalameta/language-server/blob/573863cb1db6bf99ac73f35ac17f733b92044beb/.scalafmt.conf#L12-L15

The idiomatic way to do this in sbt-scalafmt would be to customize unmanagedSources.in(scalafmt) but that can't be picked up by editor integrations like IntelliJ, so that's why it's supported via .scalafmt.conf.

Copy link
Member

@olafurpg olafurpg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Last fixes look great, we can leave caching for another PR. Only minor nitpick on language of logging, then we're good to merge 💯

@@ -21,7 +21,7 @@ object ScalafmtPlugin extends AutoPlugin {
"Fails if a Scala source is mis-formatted. Does not write to files.")
val scalafmtOnCompile =
settingKey[Boolean](
"Format Scala source files on compile, off by default.")
"Format Scala source files on compile, off by default. Warning: It formats all project sources.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this warning mean? Why should it not format all project sources?

}
}
)
).flatten.sum
if (cnt > 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cnt > 1 and then we avoid "Formatted 1 scala sources"

)
).flatten.sum
if (cnt > 0) {
log.info(s"Successfully formatted $cnt scala files.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reformatted $cnt Scala sources

taskKey[Unit]("Format Scala source files if scalafmtOnCompile is on.")

private val scalaConfig =
scalafmtConfig.map(_.map(Config.fromHoconFile(_) match {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we at least cache this by the timestamp of the file? I feel it's unnecessary to reparse the config for every project/config on every compile.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, sure we can leave it for a separate PR :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's what the IntelliJ plugin uses

@olafurpg maybe simply reuse that object? It does just what we need

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Go ahead, we might want to move it to the core module

"Fails if a Scala source is mis-formatted. Does not write to files.")
val scalafmtOnCompile =
settingKey[Boolean](
"Format Scala source files on compile, off by default. Warning: It formats all project sources.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove this warning, it's not clear exactly what it's warning about and we will fix this in a future PR anyways.

Copy link
Member

@olafurpg olafurpg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍 Nice work @vovapolu !

I opened #1091 to track caching

Merging 💃

@olafurpg olafurpg merged commit 3df7c03 into scalameta:master Dec 12, 2017
@fommil
Copy link

fommil commented Dec 12, 2017

w00t! I'm really looking forward to format on compile in the follow up 😄 sounds like it's currently not doing the right thing?

@olafurpg
Copy link
Member

Format on compile seems to work in the current version but it re-formats all of unmanagedSources.in(scalafmt), regardless of incremental compilation

@olafurpg olafurpg added this to the v1.3.1 milestone Jan 1, 2018
@olafurpg olafurpg mentioned this pull request Jan 1, 2018
xuwei-k added a commit to xuwei-k/S99 that referenced this pull request Jan 9, 2018
xuwei-k added a commit to scala-text/S99 that referenced this pull request Jan 9, 2018
@olafurpg olafurpg mentioned this pull request May 13, 2018
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

Successfully merging this pull request may close these issues.

6 participants