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

RuboCop plugins/extensions API #6012

Open
1 of 4 tasks
palkan opened this issue Jun 17, 2018 · 6 comments
Open
1 of 4 tasks

RuboCop plugins/extensions API #6012

palkan opened this issue Jun 17, 2018 · 6 comments
Labels
enhancement high priority A ticket considered important by RuboCop's Core Team

Comments

@palkan
Copy link
Contributor

palkan commented Jun 17, 2018

Some thoughts on plugins/extensions API after writing a rubocop-md gem (btw, @bbatsov I'd like to transfer this gem to rubocop-hq if you don't mind).

When inheriting from default, the plugin's default configuration should be merged into it.

Right now we have to monkey-patch (see, for example, rubocop-rspec).

Related method is ConfigLoader#default_configuration:

def default_configuration
  @default_configuration ||= begin
    print 'Default ' if debug?
    load_file(DEFAULT_FILE)
  end
end

Suppose that we have a list of plugins. The the method could look like this:

def default_configuration
  @default_configuration ||= begin
    print 'Default ' if debug?
    plugins.inject(load_file(DEFAULT_FILE)) do |config, plugin|
      resolver.merge(config, plugin.default_config)
    end
  end
end

More specific features (but it would be great to have a way to do that without monkey-patching):

  • Source pre-processing.

Ability to modify the source before parsing it (example).

It's a way to generate a Ruby-parseable contents from non-Ruby files.

  • Post-processing after auto-correct.

Ideally, it would be great to make it possible to inject between any two steps in the process:

<start> -> read file -> parse code -> run cop (n times) -> perform auto-correct -> <finish>

These features are mostly useful to use RuboCop with non-Ruby files; there are some existing projects, such as https://github.com/ericqweinstein/ruumba, https://github.com/brigade/haml-lint.

We can introduce a plugin interface like this:

class MyRubocopPlugin < Rubocop::Plugin
  # register default config extension
  default_config File.join(__dir__, "default.yml")

  # conditionally enable plugins
  enable_if ->(filename) { markdown?(filename) }

  # register source preprocessing hook;
  # accepts callable object
  pre_process_source MyRubocopPluging::Preprocessor
  
  # register source post processing hook, which is called
  # when auto-correct generates a new code and before it's written to file,
  # so we can revert our pre-processing
  post_process_source MyRubocopPluging::Postprocessor

  # hook into auto-correct process; for example, we can build interactive auto-correct
  # by asking a user before adding a correction to the Corrector
  # (that's something I was thinking about for a long time, but it's not easy to implement right now)
  pre_autocorrect ->(correction) {
    # when `false` is returned then skip the correction
    prompt "Apply? y/n >\n#{correction}"
  }
end
@bbatsov
Copy link
Collaborator

bbatsov commented Jun 17, 2018

Some thoughts on plugins/extensions API after writing a rubocop-md gem (btw, @bbatsov I'd like to transfer this gem to rubocop-hq if you don't mind).

I've just invited you to RuboCop HQ, so you can transfer the gem at your convenience.

When inheriting from default, the plugin's default configuration should be merged into it.

Yeah, I completely agree. This should be an easy change.

Ideally, it would be great to make it possible to inject between any two steps in the process:

Agree with you here as well.

I seem to recall you owe me a few PRs, so you might start by tackling this. ;-)

@palkan
Copy link
Contributor Author

palkan commented Jun 17, 2018

I seem to recall you owe me a few PRs, so you might start by tackling this. ;-)

Sure. I'll start right after Paris.rb)

@palkan
Copy link
Contributor Author

palkan commented Jun 19, 2018

@bbatsov

I've just invited you to RuboCop HQ, so you can transfer the gem at your convenience.

Done!

@bbatsov bbatsov added enhancement high priority A ticket considered important by RuboCop's Core Team labels Sep 14, 2018
@splattael
Copy link
Contributor

splattael commented Sep 10, 2021

@bbatsov @palkan

Is the current implementation and the use of RuboCop's internal API as done in rubocop-md something you would recommend for other RuboCop plugins?

See https://github.com/rubocop/rubocop-md/blob/2fb1a9a6f847effad88840d0cb3afdb1d301413b/lib/rubocop/markdown/rubocop_ext.rb#L43-L84

Are there plans to enhance the documentation with how to create a plugin? I wasn't able to find anything related.

Just asking for the "best practices" 😀

Are there any other RuboCop plugins available where I can look at? 🤔

Edit: Oh I found some

🎉

@dvandersluis
Copy link
Member

There is no official plugin architecture right now unfortunately. The solutions some of the extension libraries have used (such as the inject.rb file) are stable, but are susceptible to breaking if/when RuboCop itself changes.

A small amount of configuration has been moved to be more extensible by extensions, but there is much more that works now by hacks. https://docs.rubocop.org/rubocop/extensions.html is probably the best "documentation" right now but it's pretty sparse.

@splattael
Copy link
Contributor

@dvandersluis Thanks for the fast and insightful response ❤️

I mixed up extensions with plugins - actually I thought these were two different concepts - they aren't 👍

https://github.com/rubocop/rubocop-extension-generator is a great starting point 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement high priority A ticket considered important by RuboCop's Core Team
Projects
None yet
Development

No branches or pull requests

4 participants