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 Cop.documentation_url #8579

Merged
merged 2 commits into from Aug 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -13,6 +13,9 @@
* [#8472](https://github.com/rubocop-hq/rubocop/issues/8472): Add new `Lint/UselessMethodDefinition` cop. ([@fatkodima][])
* [#8531](https://github.com/rubocop-hq/rubocop/issues/8531): Add new `Lint/EmptyFile` cop. ([@fatkodima][])
* Add new `Lint/TrailingCommaInAttributeDeclaration` cop. ([@drenmi][])
* [#8578](https://github.com/rubocop-hq/rubocop/pull/8578): Add `:restore_registry` context and `stub_cop_class` helper class. ([@marcandre][])
* [#8579](https://github.com/rubocop-hq/rubocop/pull/8579): Add `Cop.documentation_url`. ([@marcandre][])


### Bug fixes

Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Expand Up @@ -34,6 +34,7 @@
require_relative 'rubocop/cop/base'
require_relative 'rubocop/cop/cop'
require_relative 'rubocop/cop/commissioner'
require_relative 'rubocop/cop/documentation'
require_relative 'rubocop/cop/corrector'
require_relative 'rubocop/cop/force'
require_relative 'rubocop/cop/severity'
Expand Down
16 changes: 16 additions & 0 deletions lib/rubocop/cop/base.rb
Expand Up @@ -56,6 +56,14 @@ def self.autocorrect_incompatible_with
[]
end

# Cops (other than builtin) are encouraged to implement this
# @return [String, nil]
#
# @api public
def self.documentation_url
Documentation.url_for(self) if builtin?
end

def initialize(config = nil, options = nil)
@config = config || Config.new
@options = options || { debug: false }
Expand Down Expand Up @@ -297,6 +305,14 @@ def complete_investigation

### Actually private methods

def self.builtin?
return false unless (m = instance_methods(false).first) # any custom method will do

path, _line = instance_method(m).source_location
path.start_with?(__dir__)
end
private_class_method :builtin?

def reset_investigation
@currently_disabled_lines = @current_offenses = @processed_source = @current_corrector = nil
end
Expand Down
22 changes: 22 additions & 0 deletions lib/rubocop/cop/documentation.rb
@@ -0,0 +1,22 @@
# frozen_string_literal: true

module RuboCop
module Cop
# Helpers for builtin documentation
module Documentation
module_function

# @api private
def department_to_basename(department)
"cops_#{department.downcase}"
end

# @api private
def url_for(cop_class)
base = department_to_basename(cop_class.department)
fragment = cop_class.cop_name.downcase.gsub(/[^a-z]/, '')
"https://docs.rubocop.org/rubocop/#{base}.html##{fragment}"
end
end
end
end
5 changes: 3 additions & 2 deletions lib/rubocop/cops_documentation_generator.rb
Expand Up @@ -2,6 +2,7 @@

# Class for generating documentation of all cops departments
class CopsDocumentationGenerator # rubocop:disable Metrics/ClassLength
include ::RuboCop::Cop::Documentation
# This class will only generate documentation for cops that belong to one of
# the departments given in the `departments` array. E.g. if we only wanted
# documentation for Lint cops:
Expand Down Expand Up @@ -210,7 +211,7 @@ def print_cops_of_department(department)
selected_cops.each do |cop|
content << print_cop_with_doc(cop)
end
file_name = "#{Dir.pwd}/docs/modules/ROOT/pages/cops_#{department.downcase}.adoc"
file_name = "#{Dir.pwd}/docs/modules/ROOT/pages/#{department_to_basename(department)}.adoc"
File.open(file_name, 'w') do |file|
puts "* generated #{file_name}"
file.write("#{content.strip}\n")
Expand Down Expand Up @@ -243,7 +244,7 @@ def cop_code(cop)

def table_of_content_for_department(department)
type_title = department[0].upcase + department[1..-1]
filename = "cops_#{department.downcase}.adoc"
filename = "#{department_to_basename(department)}.adoc"
content = +"=== Department xref:#{filename}[#{type_title}]\n\n"
cops_of_department(department).each do |cop|
anchor = cop.cop_name.sub('/', '').downcase
Expand Down
12 changes: 12 additions & 0 deletions lib/rubocop/rspec/shared_contexts.rb
Expand Up @@ -40,6 +40,18 @@
end
end

RSpec.shared_context 'maintain registry', :restore_registry do
around(:each) do |example|
RuboCop::Cop::Registry.with_temporary_global { example.run }
end

def stub_cop_class(name, inherit: RuboCop::Cop::Base, &block)
klass = Class.new(inherit, &block)
stub_const(name, klass)
klass
end
end

# This context assumes nothing and defines `cop`, among others.
RSpec.shared_context 'config', :config do # rubocop:disable Metrics/BlockLength
### Meant to be overridden at will
Expand Down
6 changes: 1 addition & 5 deletions spec/rubocop/config_loader_spec.rb
Expand Up @@ -621,11 +621,7 @@ def enabled?(cop)
include_examples 'resolves enabled/disabled for all cops', true, false
end

context 'when a third party require defines a new gem' do
around do |example|
RuboCop::Cop::Registry.with_temporary_global { example.run }
end

context 'when a third party require defines a new gem', :restore_registry do
context 'when the gem is not loaded' do
before do
create_file('.rubocop.yml', <<~YAML)
Expand Down
10 changes: 2 additions & 8 deletions spec/rubocop/config_spec.rb
Expand Up @@ -845,22 +845,16 @@ def cop_enabled(cop_class)
end
end

describe '#for_department' do
describe '#for_department', :restore_registry do
let(:hash) do
{
'Foo' => { 'Bar' => 42, 'Baz' => true },
'Foo/Foo' => { 'Bar' => 42, 'Qux' => true }
}
end

around do |test|
RuboCop::Cop::Registry.with_temporary_global do
test.run
end
end

before do
stub_const('RuboCop::Foo::Foo', Class.new(RuboCop::Cop::Base))
stub_cop_class('RuboCop::Foo::Foo')
end

it "always returns the department's config" do
Expand Down
21 changes: 18 additions & 3 deletions spec/rubocop/cop/cop_spec.rb
Expand Up @@ -56,6 +56,22 @@
end
end

describe '.documentation_url' do
subject(:url) { cop_class.documentation_url }

describe 'for a builtin cop class' do
let(:cop_class) { RuboCop::Cop::Layout::BlockEndNewline }

it { is_expected.to eq 'https://docs.rubocop.org/rubocop/cops_layout.html#layoutblockendnewline' } # rubocop:disable Layout/LineLength
end

describe 'for a custom cop class', :restore_registry do
let(:cop_class) { stub_cop_class('Some::Cop') { def foo; end } }

it { is_expected.to eq nil }
end
end

it 'keeps track of offenses' do
cop.add_offense(nil, location: location, message: 'message')

Expand Down Expand Up @@ -166,12 +182,11 @@
end
end

context 'when cop supports autocorrection' do
context 'when cop supports autocorrection', :restore_registry do
let(:cop_class) do
stub_cop = Class.new(RuboCop::Cop::Cop) do
stub_cop_class('RuboCop::Cop::Test::StubCop', inherit: described_class) do
def autocorrect(node); end
end
stub_const('RuboCop::Cop::Test::StubCop', stub_cop)
end

context 'when offense was corrected' do
Expand Down
14 changes: 2 additions & 12 deletions spec/rubocop/cop/mixin/enforce_superclass_spec.rb
@@ -1,15 +1,14 @@
# frozen_string_literal: true

# rubocop:disable RSpec/FilePath
RSpec.describe RuboCop::Cop::EnforceSuperclass do
RSpec.describe RuboCop::Cop::EnforceSuperclass, :restore_registry do
subject(:cop) { cop_class.new }

let(:cop_class) { RuboCop::Cop::RSpec::ApplicationRecord }
let(:msg) { 'Models should subclass `ApplicationRecord`' }

before do
stub_const('RuboCop::Cop::RSpec::ApplicationRecord',
Class.new(RuboCop::Cop::Cop))
stub_cop_class('RuboCop::Cop::RSpec::ApplicationRecord')
stub_const("#{cop_class}::MSG",
'Models should subclass `ApplicationRecord`')
stub_const("#{cop_class}::SUPERCLASS", 'ApplicationRecord')
Expand All @@ -18,15 +17,6 @@
RuboCop::Cop::RSpec::ApplicationRecord.include(described_class)
end

# `RuboCop::Cop::Cop` mutates its `registry` when inherited from.
# This can introduce nondeterministic failures in other parts of the
# specs if this mutation occurs before code that depends on this global cop
# store. The workaround is to replace the global cop store with a temporary
# store during these tests
around do |test|
RuboCop::Cop::Registry.with_temporary_global { test.run }
end

shared_examples 'no offense' do |code|
it "registers no offenses for `#{code}`" do
expect_no_offenses(code)
Expand Down
25 changes: 12 additions & 13 deletions spec/rubocop/cop/team_spec.rb
Expand Up @@ -211,13 +211,13 @@ def a
end
end

context 'when done twice' do
context 'when done twice', :restore_registry do
let(:persisting_cop_class) do
klass = Class.new(RuboCop::Cop::Base)
klass.exclude_from_registry
klass.define_singleton_method(:support_multiple_source?) { true }
stub_const('Test::Persisting', klass)
klass
stub_cop_class('Test::Persisting') do
def self.support_multiple_source?
true
end
end
end
let(:cop_classes) { [persisting_cop_class, RuboCop::Cop::Base] }

Expand Down Expand Up @@ -358,14 +358,13 @@ def a
end
end

context 'when cop with different checksum joins' do
context 'when cop with different checksum joins', :restore_registry do
before do
stub_const('Test::CopWithExternalDeps',
Class.new(::RuboCop::Cop::Cop) do
def external_dependency_checksum
'something other than nil'
end
end)
stub_cop_class('Test::CopWithExternalDeps') do
def external_dependency_checksum
'something other than nil'
end
end
end

let(:new_cop_classes) do
Expand Down
11 changes: 4 additions & 7 deletions spec/rubocop/formatter/disabled_config_formatter_spec.rb
Expand Up @@ -249,14 +249,11 @@ def io.path
end
end

context 'with auto-correct supported cop' do
context 'with auto-correct supported cop', :restore_registry do
before do
stub_const('Test::Cop3',
Class.new(::RuboCop::Cop::Cop) do
def autocorrect
# Dummy method to respond to #support_autocorrect?
end
end)
stub_cop_class('Test::Cop3') do
extend RuboCop::Cop::AutoCorrector
end

formatter.started(['test_auto_correct.rb'])
formatter.file_started('test_auto_correct.rb', {})
Expand Down