-
Notifications
You must be signed in to change notification settings - Fork 276
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
New scalafmt sbt plugin #1085
Conversation
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.
The *Check
tasks should definitely throw exceptions (MessageOnlyException
s), 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" |
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.
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.
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.
old plugin has this setting too :)
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.
Then fix the old plugin too ;)
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 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") |
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.
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") |
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.
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, |
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 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
.
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.
Better yet, in global settings. http://www.scala-sbt.org/1.0/docs/Plugins.html#projectSettings+and+buildSettings
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.
No, I think this one should be in ThisBuild
. If you have ProjectRef
s to other directories outside your build, you don't want those to get reformatted when they're compiled.
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.
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( |
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 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, |
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.
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.
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 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.
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.
(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, |
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.
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 { |
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.
The very presence of this taskDyn
will destroy inspect tree myProject/compile
. Please don't do this.
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 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 |
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 unconditionally applies scalafmt
even when scalafmtOnCompile
is false
.
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.
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, |
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 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 = |
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.
Call this scalafmtConfigParsed
instead?
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 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.
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.
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) |
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.
.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)) |
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 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
def configWithDialect( |
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 |
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 does not help readability IMO, either create a value class or use String.
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.
@dwijnand Why did you put a dislike here?
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.
Just because I disagree (even if the type Input
is just an alias, I think it's can be useful to have such aliases).
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.
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
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.
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() |
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.
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
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.
Call on every file? Your plugin calls it once at start.
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.
And when I should call it? Even when I check source or only when format?
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.
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( |
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 formats files sequentially, for large projects it may pay off to do .par.map
, but I'll leave it to you to decide.
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.
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.
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.
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") |
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.
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.
@olafurpg Can you take a look now? I fixed all comments that were unanimously approved :D |
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.
Changes look great 👍 Second round of review
@@ -0,0 +1,24 @@ | |||
> p123/compile:scalafmt |
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.
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") |
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.
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") |
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.
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") |
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.
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") |
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.
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 { |
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.
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
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.
Yeah, it's called quite a lot. I'll try to use some caching.
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.
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 |
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.
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) |
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.
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 { |
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.
iiuc, this implementation still breaks inspect tree compile
, we need to push Def.taskDyn
into a scalafmtDoAutoformat
task, like is done here:
I may be wrong @dwijnand may know better
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.
well, I just copied it from neo-scalafmt-sbt
plugin :D
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.
Yes, Def.taskDyn
"breaks" inspect
- as in the dependencies don't show up.
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.
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 taskDyn
s where they could be settingDyn
s.) 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" |
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.
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?
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 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.
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 often use scalafmt on smaller personal projects with the default config without making a .scalafmt file.
@olafurpg I think caching isn't going to be easy to implement. Your link is suitable for |
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.
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]]( |
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 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.
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.
It searches for .scalafmt.conf
file by default. And you can redefine it for your custom file.
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 see, cool. OK this is fine then 👍
else Def.task(()) | ||
val previousInputs = (compileInputs in compile).value | ||
task.map(_ => previousInputs) | ||
scalafmtDoFormatOnCompile := Def.taskDyn { |
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.
As recommended by @sjrd we should make this a settingDyn
I'm merging it? |
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! |
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 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.
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 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}.") |
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 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
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.
Output from testing plugin on akka build https://gist.github.com/olafurpg/7a33d069604324a8bf4249cb067b7dd0
}, | ||
scalafmtDoFormatOnCompile := Def.settingDyn { | ||
if (scalafmtOnCompile.value) { | ||
scalafmt in resolvedScoped.value.scope |
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 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
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.
yeah it's only supposed to be formatting files that are being compiled, not all of them
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.
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 :)
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.
@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, |
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 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] ^
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.
We should add some *.java files to the scripted tests
I just tried out the plugin on one of my projects and noticed that it formatted files that are excluded with scalafmt/scalafmt-core/shared/src/main/scala/org/scalafmt/config/ProjectFiles.scala Line 13 in 6cddfe9
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 |
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.
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.") |
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.
What does this warning mean? Why should it not format all project sources?
} | ||
} | ||
) | ||
).flatten.sum | ||
if (cnt > 0) { |
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.
cnt > 1
and then we avoid "Formatted 1 scala sources"
) | ||
).flatten.sum | ||
if (cnt > 0) { | ||
log.info(s"Successfully formatted $cnt scala files.") |
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.
Reformatted $cnt Scala sources
taskKey[Unit]("Format Scala source files if scalafmtOnCompile is on.") | ||
|
||
private val scalaConfig = | ||
scalafmtConfig.map(_.map(Config.fromHoconFile(_) match { |
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.
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.
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.
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 see, sure we can leave it for a separate PR :)
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.
Here's what the IntelliJ plugin uses
@olafurpg maybe simply reuse that object? It does just what we need
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.
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.") |
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.
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.
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.
w00t! I'm really looking forward to format on compile in the follow up 😄 sounds like it's currently not doing the right thing? |
Format on compile seems to work in the current version but it re-formats all of |
#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.