This is a great idea! Sadly, because it emits compiler warnings it breaks my workflow and I suspect my approach is not unique.
TL;DR; this plugin does not support TDD if fatal warnings are enabled (and they should be enabled). I'm going to describe my workflow to highlight what is broken, but I'd observe that this also breaks PDD (println-driven-development™) and I think these two approaches combined probably cover most developers.
Every Scala project should have -Xfatal-warnings
enabled. This means things that emit compiler warnings need to be "real" problems, rather than things that are only warnings in a given context. In the case of ???
I would expect this to be a fatal error when building a production jar, but during development (and particularly while tests are compiling and executing) ???
is perfectly normal or indeed even encouraged.
My workflow for Scala code is as follows:
1. Stub functions to see how a part of the program fits together
This means writing the signatures and filling in the implementations with ???
. This allows the computer and I to work together designing a program that makes sense before I start spending time on the implementations.
Using this plugin with fatal warnings (and as above, all Scala projects should have fatal warnings enabled) prevents this approach from working. It may be that if the only remaining compiler errors are from this plugin then the program is ready for the next step, but I'd always be nervous that one of these errors is masking something else that would only be revealed after the implementation removes the typed hole warnings.
2. Write unit tests that capture the expected behaviour of the stubbed functions
Doing this first makes sure I understand the problem before I start implementing, encourages higher quality tests by thinking about them separately to the implementation and makes sure the tests are in place while the code evolves to speed implementation (below).
Using this plugin prevents this step from working as well. In this case we can never get as far as running the tests until all the implementations are in place.
3. Evolve the implementation with help from the tests
It's impossible to work on everything at once so here I'd expect to fill in the building bit by bit using the scaffolding of types and tests. It's important the tests are in place before the code so we can see them turn from red to green as we progress.
Of course, the same applies here. We can only run the tests (or indeed, parts of the program) when the whole thing is finished.
What I'd like
This plugin is a fantastic idea, but I think in this form it isn't quite right. I'd say my "ideal dreamland" for this plugin would be that compilation fails when building a production jar (e.g. sbt commands like build
, start
, package
, release
), but only warn during tests (test
, test*
) - even if fatal warnings is enabled. For locally running the program in dev mode (run
) I could be convinced either way but I suspect most people would expect compilation to succeed while running a partially built application to support PDD™. This is taking some inspiration from Elm, where the Debug package can be used in testing and development, but will cause a compilation error when building a production application.
I'm not sure how this could be achieved by a single plugin, I'd imagine it would need explicit support from all the plugins that do testing and building, so they'd be able to select the correct mode based on which of their commands is being invoked. I mention it in case I'm missing something, Elm hangs this behaviour off the optimize flag (which it uses when creating prod builds). It's possible there's a corresponding piece of state in sbt that could be used to determine whether ???
should be a real warning that causes compilation to fail (Elm output for a use of the equivalent, Debug.todo
is below and here's the docs for it).
Because of all of the above, I don't think this plugin should emit warnings by default, or if it should then it needs to be overridable somehow. If it printed at a message level then the workflow problem would be fixed but we'd lose some of the safety it adds. If this plugin adds a flag then we'd be able to whitelist/blacklist it ourselves to be able to tailor the behaviour to our own needs, particularly if we're using non-standard sbt plugins (warnOnNotImplemented in Release := true
/ warnOnNotImplemented in (Test, Run) := false
, for example).
Sorry for the long issue! I was really excited by the idea, then correspondingly saddened to find I couldn't use it so I put a bit of thought into what it would take to make it work.
Reference
Here's the Elm's output if you try and build an app that contains a TODO, but you can run it locally and run the tests just fine. Elm does not have fancy typed hole messages yet though ;-)
$ elm-app build
Creating an optimized production build...
Failed to compile.
./src/Main.elm
Error: Compiler process exited with error Compilation failed
-- DEBUG REMNANTS --------------------------------------------------------------
There are uses of the `Debug` module in the following modules:
Main
But the --optimize flag only works if all `Debug` functions are removed!
Note: The issue is that --optimize strips out info needed by `Debug` functions.
Here are two examples:
(1) It shortens record field names. This makes the generated JavaScript is
smaller, but `Debug.toString` cannot know the real field names anymore.
(2) Values like `type Height = Height Float` are unboxed. This reduces
allocation, but it also means that `Debug.toString` cannot tell if it is
looking at a `Height` or `Float` value.
There are a few other cases like that, and it will be much worse once we start
inlining code. That optimization could move `Debug.log` and `Debug.todo` calls,
resulting in unpredictable behavior. I hope that clarifies why this restriction
exists!