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

Add support for --stdin-filename #1343

Merged
merged 2 commits into from
Jun 25, 2018

Conversation

paul
Copy link
Contributor

@paul paul commented Jun 17, 2018

When other tools, like automatic linters within editors, run a sample
through reek, they typically do so via stdin. However, if there are reek
config exceptions to apply to that file, then reek has no way of knowing
what the original filename was, triggering false positives.

This adds an --stdin-filename CLI option that takes the
original filename, and passes it through to the Examiner and SourceCode
file to use as the origin attribute, allowing the configuration
directives to apply.

Fixes #1296

@troessner I'm probably missing a few additional places where this might need to get additional tests. I added a cuke feature to start with, and a spec in application_spec.rb that passed as soon as I added the option, but before I implemented the changes in ReportCommand and Examiner. Do you have any pointers on where to look that you'd want specs added?

@paul paul force-pushed the feature/stdin-filename branch from 9ce6b6f to 2ca1882 Compare June 17, 2018 22:24
@troessner
Copy link
Owner

This looks excellent to me. However travis is failing due to rubocop :)
Other than that I have no additional comments. @mvz ?

@paul
Copy link
Contributor Author

paul commented Jun 18, 2018

@troessner Yeah, I haven't had time to fix it. It's rubocop complaining about the complexity since I added the override || fallback to the case statement. I tried a couple different easy things to make the warning go away, but it resulted in (imo) worse code.

Probably the best thing to do is have sub-classes of SourceCode (IOSourceCode, FileSourceCode, etc...) but that seems like a bigger change that would cascade into further refactoring, and I was hoping to not have to be that invasive for this feature.

The quick and dirty thing to make rubycop happy but the code probably worse would be to extract a couple helper methods, something like this:

def self.from(source, filename: nil)
  new(code: code_from_source(source), origin: origin_from_source(source, override: filename))
end

...but, I personally think "private helper class methods" like code_from_source and origin_from_source to be a worse smell than the rubocop "Complexity" metric in this case. @troessner thoughts?

@troessner
Copy link
Owner

@paul i agree - in this case you can add an exception to our Rubocop configuration

Copy link
Collaborator

@mvz mvz left a comment

Choose a reason for hiding this comment

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

Overall, I think this is on the right track 👍. I hope we can avoid passing the filename through so many layers.

end
"""
Then it succeeds

Copy link
Collaborator

Choose a reason for hiding this comment

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

For completeness, you should probably add 'And it reports nothing' as well.

@@ -6,6 +6,10 @@
reek_with_pipe(stdin, args)
end

When /^I pass a stdin to reek *(.*) with:$/ do |args, stdin|
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍

def initialize(source,
stdin_filename: nil,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since we're in the process of releasing Reek 5, we could also require source to be a Source::SourceCode and avoid passing in this extra parameter.

Notice that stdin_filename is passed to Source::SourceCode.from and then read back through source.origin, so an alternative would be to not pass it to Source::SourceCode but store it and use it in Examiner#description.

context 'when a stdin filename is provided' do
let(:app) { described_class.new ['--stdin-filename', 'foo.rb'] }

it 'assumes that filename' do
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't see the specific filename being checked in the implementation of this spec?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I should probably just remove this spec. I wanted somewhere in the regular specs (not just features) that would test the --stdin-filename option, and this seemed like the most logical place to do so. However, once I dug in, everything in here is so mocked out I wasn't sure how to actually test it, or even what I should be testing. I'm also surprised it passed, had it failed I probably would have just removed it.

when Pathname then new(code: source.read, origin: source.to_s)
when String then new(code: source, origin: STRING_IDENTIFIER)
when String then new(code: source, origin: filename || STRING_IDENTIFIER)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is filename used with both IO and String? For STDIN use I think only IO would be needed, so if we're going to add this to others, we might as well add this to all of them.

However, see my comment on Examiner about not passing filename to SourceCode at all.

@mvz
Copy link
Collaborator

mvz commented Jun 18, 2018

FWIW, I think 'private helper class methods' are perfectly fine. They're just hard to realize in Ruby. In this case, they could be pushed down into the instance and we could drop SourceCode.from. But see my other comments first!

@mvz
Copy link
Collaborator

mvz commented Jun 24, 2018

@paul will you be able to take a look at my comments soon?

@paul
Copy link
Contributor Author

paul commented Jun 24, 2018

@mvz Yup, sorry, seems like my only free time these days is on weekends. I'll get to it today hopefully.

@paul paul force-pushed the feature/stdin-filename branch from 47f566f to 7ab43bc Compare June 25, 2018 02:20
@paul
Copy link
Contributor Author

paul commented Jun 25, 2018

@mvz Changing Examiner to always expect a SourceCode really does simplify the code quite a bit. I didn't originally want to make that big a change, but it is simpler this way, I'm glad we were able to do it.

However, making that change causes 500 specs to fail, since all the tests pass in things that are not SourceCode:

Finished in 0.44828 seconds (files took 0.55276 seconds to load)
996 examples, 500 failures

But, I made it so that SourceCode.from just returns source if its already a SourceCode, and have Examiner still do the SourceCode.from. Let me know if this is ok, but I do not feel qualified at all to try and fix 500 specs without having any experience in this codebase.

@paul paul force-pushed the feature/stdin-filename branch from 7ab43bc to ac4da67 Compare June 25, 2018 02:29
paul added 2 commits June 24, 2018 20:30
When other tools, like automatic linters within editors, run a sample
through reek, they typically do so via stdin. However, if there are reek
config exceptions to apply to that file, then reek has no way of knowing
what the original filename was, triggering false positives.

This adds an `--stdin-filename` CLI option that takes the
original filename, and passes it through to the Examiner and SourceCode
file to use as the `origin` attribute, allowing the configuration
directives to apply.
Also allow `SourceCode.from` to noop if the provided source is already a
`SourceCode`
@paul paul force-pushed the feature/stdin-filename branch from ac4da67 to aa2bea5 Compare June 25, 2018 02:30
@paul
Copy link
Contributor Author

paul commented Jun 25, 2018

I had to rebase master to get CodeClimate to not error. Do you want me to also squash these into a single commit? I left it separate for now for easier review.

Copy link
Collaborator

@mvz mvz left a comment

Choose a reason for hiding this comment

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

I think we should merge this and then see if any further refactoring is needed. @troessner wdyt?

@troessner
Copy link
Owner

Looks good to me as well, merging. Thanks @paul !

@troessner troessner merged commit d2f9378 into troessner:master Jun 25, 2018
@stevenharman
Copy link

stevenharman commented Oct 16, 2018

I'm trying to get Reek working with Ale (https://github.com/w0rp/ale), but the --stdin-filename doesn't seem to be respecting the exclude_paths in my config. Is that intentional with this change? If not, I'll open a new Issue for further discussion, but wanted to check first.

config in .reek.yml:

---
# Directories below will not be scanned at all
exclude_paths:
  - db/migrate
  - tmp

And the command...

$ cat db/migrate/20181015212457_create_foos.rb | reek --config .reek.yml --force-exclusion  --stdin-filename 'db/migrate/20181015212457_create_foos.rb'
db/migrate/20181015212457_create_foos.rb -- 3 warnings:
  [6, 7, 8, 9, 11]:FeatureEnvy: CreateFoos#change refers to 't' more than self (maybe move it to another class?) [https://github.com/troessner/reek/blob/v5.1.0/docs/Feature-Envy.md]
  [4]:TooManyStatements: CreateFoos#change has approx 9 statements [https://github.com/troessner/reek/blob/v5.1.0/docs/Too-Many-Statements.md]
  [5]:UncommunicativeVariableName: CreateFoos#change has the variable name 't' [https://github.com/troessner/reek/blob/v5.1.0/docs/Uncommunicative-Variable-Name.md]

Thanks!

paul added a commit to paul/pronto-reek that referenced this pull request Aug 28, 2019
Awhile back, I fixed reek to be able to know the filename being
examined, even if it wasn't passed a File object:
troessner/reek#1343

When the reek config file has different configuration for different
files/directories, this allows reek to match the filenames when not
using the reek CLI.

However, it wasn't working to match the full path as provided by
pronto-reek. For example, if your reek.yml specifies "lib/foo.rb",
pronto-reek was supplying files as
`Pathname("/home/me/code/project/lib/foo.rb")`, and it was failing to
match.

This change modifies the new_file_full_path from the patch to instead be
a relative path from the pwd, which is correctly matched by the reek
config.

Caveats:

 * Testing this might be pretty complicated, we'd need a `double` that
   knows a whole lot about Reek internals to generate a config.
 * Pronto::Git::Patch doesn't expose an easy way to get a relative
   path. I had to do the relative_path_from work twice in
   Pronto::Reek#run, this could be simpler if Pronto::Git::Patch exposed
   a `#new_file_relative_path` method.
 * I had originally wanted to do
   `patch.new_file_full_path.relative_path_from(Pathname.new(repo.path))`,
   but this did not work in the tests because the fixture git repo is in
   a subdir, so reek was unable to open "./hello.rb". While
   relative_path_from repo.path worked fine testing this against my own
   repository, I'm not familiar enough with how pronto treats submodules
   or nested git directories to know if that approach will work. I also
   don't know if `pwd` is the right approach either, depending on how
   reek/pronto is called. Probably a better thing to do is try and
   figure out the directory containing a reek config file, and do
   everything relative from there.
doomspork pushed a commit to prontolabs/pronto-reek that referenced this pull request Sep 19, 2019
Awhile back, I fixed reek to be able to know the filename being
examined, even if it wasn't passed a File object:
troessner/reek#1343

When the reek config file has different configuration for different
files/directories, this allows reek to match the filenames when not
using the reek CLI.

However, it wasn't working to match the full path as provided by
pronto-reek. For example, if your reek.yml specifies "lib/foo.rb",
pronto-reek was supplying files as
`Pathname("/home/me/code/project/lib/foo.rb")`, and it was failing to
match.

This change modifies the new_file_full_path from the patch to instead be
a relative path from the pwd, which is correctly matched by the reek
config.

Caveats:

 * Testing this might be pretty complicated, we'd need a `double` that
   knows a whole lot about Reek internals to generate a config.
 * Pronto::Git::Patch doesn't expose an easy way to get a relative
   path. I had to do the relative_path_from work twice in
   Pronto::Reek#run, this could be simpler if Pronto::Git::Patch exposed
   a `#new_file_relative_path` method.
 * I had originally wanted to do
   `patch.new_file_full_path.relative_path_from(Pathname.new(repo.path))`,
   but this did not work in the tests because the fixture git repo is in
   a subdir, so reek was unable to open "./hello.rb". While
   relative_path_from repo.path worked fine testing this against my own
   repository, I'm not familiar enough with how pronto treats submodules
   or nested git directories to know if that approach will work. I also
   don't know if `pwd` is the right approach either, depending on how
   reek/pronto is called. Probably a better thing to do is try and
   figure out the directory containing a reek config file, and do
   everything relative from there.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants