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

[Fix #3904] Build config & sync cop descriptions #6600

Closed
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@
### Changes

* [#595](https://github.com/rubocop-hq/rubocop/issues/595): Exclude files ignored by `git`. ([@AlexWayfer][])
* [#3904](https://github.com/rubocop-hq/rubocop/issues/3904): Deduplicate descriptions by copying cop descriptions into config. ([@garettarrowood][])
* [#6429](https://github.com/rubocop-hq/rubocop/issues/6429): Fix autocorrect in Rails/Validation to not wrap hash options with braces in an extra set of braces. ([@bquorning][])
* [#6533](https://github.com/rubocop-hq/rubocop/issues/6533): Improved warning message for unrecognized cop parameters to include Supported parameters. ([@MagedMilad][])

Expand Down
25 changes: 24 additions & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@ RuboCop::RakeTask.new(:internal_investigation).tap do |task|
end

task default: %i[
documentation_syntax_check generate_cops_documentation
build_config
documentation_syntax_check
generate_cops_documentation
spec ascii_spec
internal_investigation
confirm_config
]

require 'yard'
Expand Down Expand Up @@ -119,3 +122,23 @@ task documentation_syntax_check: :yard_for_generate_documentation do
end
abort unless ok
end

desc 'Build config/default.yml'
task :build_config do
sh('bin/build_config')
end

desc 'Confirm config/default.yml is up to date'
task confirm_config: :build_config do
# Do not print diff and yield whether exit code was zero
sh('git diff --quiet config') do |outcome, _|
return if outcome

# Output diff before raising error
sh('GIT_PAGER=cat git diff config')

warn 'The config/default.yml is out of sync. ' \
'Run `rake build_config` and commit the results.'
exit!
end
end
24 changes: 24 additions & 0 deletions bin/build_config
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

$LOAD_PATH.unshift(File.join(__dir__, '..', 'lib'))

require 'yard'

require 'rubocop'
require 'rubocop/description_extractor'
require 'rubocop/config_formatter'

departments =
"{,#{RuboCop::Cop::Cop.registry.departments.map(&:downcase).join(',')}}"

glob = File.join(__dir__, '..', 'lib', 'rubocop', 'cop', departments, '*.rb')
YARD.parse(Dir[glob], [])

descriptions = RuboCop::DescriptionExtractor.new(YARD::Registry.all).to_h
current_config = YAML.load_file('config/default.yml')

File.write(
'config/default.yml',
RuboCop::ConfigFormatter.new(current_config, descriptions).dump
)
35 changes: 35 additions & 0 deletions lib/rubocop/config_formatter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

require 'yaml'

module RuboCop
# Builds YAML config file from two hashes
class ConfigFormatter
DEPARTMENTS = /^(#{Cop::Cop.registry.departments.join('|')})/x.freeze
STANDALONE_DEPARTMENT_EXCLUSIONS = ['Rails'].freeze

def initialize(config, descriptions)
@config = config
@descriptions = descriptions
end

def dump
YAML.dump(unified_config).gsub(DEPARTMENTS, "\n\\1")
end

private

def unified_config
cops.each_with_object(config.dup) do |cop, unified|
unified[cop] = config.fetch(cop).merge(descriptions.fetch(cop))
end
end

def cops
(descriptions.keys | config.keys).grep(DEPARTMENTS) -
STANDALONE_DEPARTMENT_EXCLUSIONS
end

attr_reader :config, :descriptions
end
end
67 changes: 67 additions & 0 deletions lib/rubocop/description_extractor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# frozen_string_literal: true

module RuboCop
# Extracts top description lines from source code and builds hash
# used to update config/defauly.yml.
class DescriptionExtractor
def initialize(yardocs)
@code_objects = yardocs.map(&CodeObject.public_method(:new))
end

def to_h
code_objects
.select(&:cop?)
.map(&:configuration)
.reduce(:merge)
end

private

attr_reader :code_objects

# Decorator of a YARD code object for working with documented cops
class CodeObject
def initialize(yardoc)
@yardoc = yardoc
end

# Test if the YARD code object documents a concrete cop class
#
# @return [Boolean]
def cop?
class_documentation? && inherits_from_cop?
end

# Configuration for the documented cop that would live in default.yml
#
# @return [Hash]
def configuration
{ cop_name => { 'Description' => description } }
end

private

attr_reader :yardoc

def cop_name
constant.cop_name
end

def description
yardoc.docstring.split("\n\n").first.to_s
end

def class_documentation?
yardoc.type.equal?(:class)
end

def inherits_from_cop?
constant.respond_to?(:cop_name)
end

def constant
Object.const_get(yardoc.to_s)
end
end
end
end
1 change: 0 additions & 1 deletion spec/project_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
cop_names.each do |name|
description = config[name]['Description']
expect(description.nil?).to be(false)
expect(description).not_to include("\n")
end
end

Expand Down
4 changes: 2 additions & 2 deletions spec/rubocop/cli/cli_options_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,7 @@ def full_description_of_cop(cop)
expect(stdout).to match(
['# Supports --auto-correct',
'Layout/Tab:',
' Description: No hard tabs.',
' Description: This cop checks for tabs inside the source code.',
/^ StyleGuide: ('|")#spaces-indentation('|")$/,
' Enabled: true',
/^ VersionAdded: '[0-9\.]+'$/,
Expand All @@ -761,7 +761,7 @@ def full_description_of_cop(cop)
expect(stdout).to match(
['# Supports --auto-correct',
'Layout/Tab:',
' Description: No hard tabs.',
' Description: This cop checks for tabs inside the source code.',
/^ StyleGuide: ('|")#spaces-indentation('|")$/,
' Enabled: true'].join("\n")
)
Expand Down
50 changes: 50 additions & 0 deletions spec/rubocop/config_formatter_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# frozen_string_literal: true

require 'rubocop/config_formatter'

RSpec.describe RuboCop::ConfigFormatter do
let(:config) do
{
'AllCops' => {
'Setting' => 'fourty two'
},
'Style/Foo' => {
'Config' => 2,
'Enabled' => true
},
'Style/Bar' => {
'Enabled' => true
}
}
end

let(:descriptions) do
{
'Style/Foo' => {
'Description' => 'Blah'
},
'Style/Bar' => {
'Description' => 'Wow'
}
}
end

it 'builds a YAML dump with spacing between cops' do
formatter = described_class.new(config, descriptions)

expect(formatter.dump).to eql(<<-YAML.gsub(/^\s+\|/, ''))
|---
|AllCops:
| Setting: fourty two
|
|Style/Foo:
| Config: 2
| Enabled: true
| Description: Blah
|
|Style/Bar:
| Enabled: true
| Description: Wow
YAML
end
end
45 changes: 45 additions & 0 deletions spec/rubocop/description_extractor_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen_string_literal: true

require 'yard'
require 'rubocop/description_extractor'

RSpec.describe RuboCop::DescriptionExtractor do
let(:yardocs) do
YARD.parse_string(<<-RUBY)
# Summary description
#
# Some more description
#
# @example
# # bad
# something
#
# # good
# something
#
# @note only works with foo
#
class RuboCop::Cop::Layout::EmptyLines < RuboCop::Cop::Cop
# Hello
def bar
end
end
# Only line of description
#
class RuboCop::Cop::Layout::EndOfLine < RuboCop::Cop::Cop
# Hello
def bar
end
end
RUBY

YARD::Registry.all
end

it 'builds a hash of descriptions' do
expect(described_class.new(yardocs).to_h).to include(
'Layout/EmptyLines' => { 'Description' => 'Summary description' },
'Layout/EndOfLine' => { 'Description' => 'Only line of description' }
)
end
end