-
Notifications
You must be signed in to change notification settings - Fork 144
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
Make RuboCop runner composable and capture any stdout message from it #275
Conversation
If this is mainly for
|
TIL about the |
The current implementation of running RuboCop tasks is not composable. Instead, we use inheritance to obtain and use the functionality of the `RuboCop::Runner` class, which ends up coupling our interfaces too tightly to that class. Instead, this commit creates a `RuboCopRunner` class that is meant to encapsulate the functionality of the `RuboCop::Runner` class. Both the formatter and the diagnostic request classes are refactored to use this new class in a composable way, and the implementations are much nicer as a result. This new architecture also allows us to add common functionality to how RuboCop runs are made.
b7bffbe
to
0886be8
Compare
@st0012 Yeah, I know about the difference between In any case, I changed my approach and implementation to limit the capture to only RuboCop and to capture only |
0886be8
to
d9edf5a
Compare
Moreover, set `$VERBOSE` to `nil` so that we don't end up seeing messages from `Kernel#warn` in the VSCode code Output pane as well.
d9edf5a
to
0bc4b6f
Compare
Ok, it ended up being much more tricky and I had to change the WDYT? |
0bc4b6f
to
d604ef0
Compare
I think that makes sense 👍 Regarding the implementation, I was a bit worried about swapping However, it's still quite dangerous because if one day the |
$stdout = orig_stdout | ||
$stderr = orig_stderr | ||
$VERBOSE = original_verbosity |
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.
Nit, VERBOSE
is first in the two previous related blocks.
$stdout = orig_stdout | |
$stderr = orig_stderr | |
$VERBOSE = original_verbosity | |
$VERBOSE = original_verbosity | |
$stdout = orig_stdout | |
$stderr = orig_stderr |
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 was explicitly reverting changes in the reverse order :)
orig_stderr = $stderr | ||
|
||
$VERBOSE = nil | ||
$stderr = StringIO.new |
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.
How hard/bad would it be to monkey patch the classes from Rubocop that still print so we noop puts
rather than changing the global variable?
attr_reader :offenses | ||
|
||
DEFAULT_ARGS = T.let([ | ||
"--force-exclusion", |
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 assume we dropped the stderr option because we're now capturing stdout, but should we keep the formatter to reduce the amount of prints from RuboCop?
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, we can still keep things less chatty overall. I'll reinstate.
private | ||
|
||
sig { void } | ||
def display_handled_errors |
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.
Why do we need this? What type of error is this trying to display? If we don't pop up a dialogue, I doubt anyone will actually see it.
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 so that people can see and/or report problems that any formatting/diagnostic operation has encountered. I feel like this is needed more for troubleshooting than making people aware. After all, there is no action to be taken on any of these, since they are already handled.
This is what the output looks like:
[RuboCop] Encountered and handled errors:
[RuboCop] - An error occurred while Layout/BlockEndNewline cop was inspecting /Users/ufuk/src/github.com/Shopify/tapioca/lib/tapioca/static/rbs/parameter_converter.rb:4:17.
def run(path, contents) | ||
@errors = [] | ||
@warnings = [] | ||
@offenses = [] |
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.
Can we use @offenses.clear
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.
That was what I tried first, but it didn't work since it is a frozen array.
@errors = [] | ||
@warnings = [] |
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.
Why do we need these?
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 we don't recycle runners, but only use a single instance, errors and warnings need to cleared at the top of every run so that they don't keep building up run over run.
def file_finished(_file, offenses) | ||
@diagnostics = offenses.map { |offense| Support::RuboCopDiagnostic.new(offense, T.must(@uri)) } | ||
sig { params(uri: String).returns(T::Array[Support::RuboCopDiagnostic]) } | ||
def diagnostics(uri) |
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.
Not sure there's a lot of value in keeping this method around after the refactor. What do you think about moving this to the run
body?
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.
Sure, we can do that!
Btw, I decided to close this PR and open another one that limits scope to only the RuboCop composition refactor and leave output capture to another date. I am no longer sure if that's the source of my problems. |
Motivation
RuboCop is still too chatty and breaks the protocol by printing to stdout despite our best attempts. Today I was having problems with the formatting on Tapioca and realized it was because of the:
problem (which is in pre-1.34 versions of RuboCop).
I am not exactly sure why this was causing a problem, since that specific message should be going to stderr, but the LSP communication was still broken because of it.
Regardless, I realized that we should start capturing the
stdout
from any RuboCop runs.Implementation
In order to do this in an encapsulated and uniform manner, I realized that we were missing a layer of abstraction between
RuboCop::Runner
and our request classes that depend on it. I then realized that the problem was stemming from the fact that we've been using inheritance rather than composition for that part of the codebase.So, I did a refactor to extract a composable
RuboCopRunner
that both theRuboCopDiagnosticsRunner
andRuboCopFormattingRunner
are able to use. That resulted in a much better and easier to read implementation for both of those classes.Afterwards, I implemented a
capture_output
method in theRubocopRunner
which I made all invocations toRuboCop::Runner#run
wrapped in. This method does the following:$stdout
$stderr
$VERBOSE
flag so thatKernel#warn
messages are never printed.This should make sure that we never print anything that we don't control to stdout/err anymore.
Finally, I think showing any errors that were encountered (and handled) during RuboCop processing is helpful, so I made
RuboCopRunner
explicitly print uniq errors to stderr.Automated Tests
I haven't added any but it might be nice to maybe add one.
Manual Tests
Not sure how, but if you can recreate the conditions of rubocop/rubocop#10878 on a repo that is using RuboCop 1.33 or earlier, then it is enough to trigger the broken behaviour and to see that this PR fixes it.