From 7e26087ce2644fedc1eb6476b393325f79b4f29d Mon Sep 17 00:00:00 2001 From: John Backus Date: Tue, 9 Aug 2016 17:18:19 -0400 Subject: [PATCH 1/5] Standardize cop description summaries All cops should now have a brief summary at the beginning of their documentation that should match their `Description` in config/default.yml --- config/default.yml | 34 ++++++++++---------- lib/rubocop/cop/rspec/any_instance.rb | 2 ++ lib/rubocop/cop/rspec/be_eql.rb | 2 +- lib/rubocop/cop/rspec/describe_class.rb | 3 +- lib/rubocop/cop/rspec/describe_method.rb | 3 +- lib/rubocop/cop/rspec/described_class.rb | 2 ++ lib/rubocop/cop/rspec/example_length.rb | 2 ++ lib/rubocop/cop/rspec/example_wording.rb | 5 +-- lib/rubocop/cop/rspec/expect_actual.rb | 2 +- lib/rubocop/cop/rspec/file_path.rb | 2 ++ lib/rubocop/cop/rspec/focus.rb | 2 +- lib/rubocop/cop/rspec/hook_argument.rb | 2 ++ lib/rubocop/cop/rspec/instance_variable.rb | 3 +- lib/rubocop/cop/rspec/leading_subject.rb | 2 +- lib/rubocop/cop/rspec/let_setup.rb | 2 +- lib/rubocop/cop/rspec/message_expectation.rb | 2 +- lib/rubocop/cop/rspec/multiple_describes.rb | 6 ++-- lib/rubocop/cop/rspec/named_subject.rb | 9 +++++- lib/rubocop/cop/rspec/not_to_not.rb | 3 +- lib/rubocop/cop/rspec/subject_stub.rb | 2 +- lib/rubocop/cop/rspec/verified_doubles.rb | 3 +- 21 files changed, 55 insertions(+), 38 deletions(-) diff --git a/config/default.yml b/config/default.yml index 082c8f925..6212862f9 100644 --- a/config/default.yml +++ b/config/default.yml @@ -15,7 +15,7 @@ RSpec/BeEql: Enabled: true RSpec/HookArgument: - Description: 'Check the arguments passed to `before`, `around`, and `after`.' + Description: 'Checks the arguments passed to `before`, `around`, and `after`.' Enabled: true EnforcedStyle: implicit SupportedStyles: @@ -24,20 +24,20 @@ RSpec/HookArgument: - example RSpec/DescribeClass: - Description: 'Check that the first argument to the top level describe is the tested class or module.' + Description: 'Check that the first argument to the top level describe is a constant' Enabled: true RSpec/DescribedClass: - Description: 'Use `described_class` for tested class / module' + Description: 'Checks that tests use `described_class`' SkipBlocks: false Enabled: true RSpec/DescribeMethod: - Description: 'Checks that the second argument to top level describe is the tested method name.' + Description: 'Checks that the second argument to `describe` specifies a method' Enabled: true RSpec/ExampleWording: - Description: 'Do not use should when describing your tests.' + Description: 'Checks that example descriptions do not start with "should"' Enabled: true CustomTransform: be: is @@ -46,7 +46,7 @@ RSpec/ExampleWording: IgnoredWords: [] RSpec/EmptyExampleGroup: - Description: 'Checks for `describe` and `context` groups without tests.' + Description: 'Checks if an example group does not include any tests' Enabled: true CustomIncludeMethods: [] @@ -55,21 +55,21 @@ RSpec/ExpectActual: Enabled: true RSpec/MultipleDescribes: - Description: 'Checks for multiple top level describes.' + Description: 'Checks for multiple top level describes' Enabled: true RSpec/MultipleExpectations: - Description: 'Checks for multiple `expect(...)` calls in one example.' + Description: 'Checks if examples contain too many `expect` calls' Enabled: true Max: 1 RSpec/NestedGroups: - Description: 'Checks for multiple levels of context nesting.' + Description: 'Checks for nested example groups' Enabled: true MaxNesting: 2 RSpec/InstanceVariable: - Description: 'Checks for the usage of instance variables.' + Description: 'Checks for instance variable usage in specs' AssignmentOnly: false Enabled: true @@ -78,11 +78,11 @@ RSpec/LetSetup: Enabled: true RSpec/LeadingSubject: - Description: 'Checks for `subject` definitions that come after `let` definitions.' + Description: 'Checks for `subject` definitions that come after `let` definitions' Enabled: true RSpec/FilePath: - Description: 'Checks the file and folder naming of the spec file.' + Description: 'Checks that spec file paths are consistent with the test subject' Enabled: true CustomTransform: RuboCop: rubocop @@ -94,7 +94,7 @@ RSpec/VerifiedDoubles: IgnoreSymbolicNames: false RSpec/NotToNot: - Description: 'Enforces the usage of the same method on all negative message expectations.' + Description: 'Checks for consistent method usage for negating expectations' EnforcedStyle: not_to SupportedStyles: - not_to @@ -102,16 +102,16 @@ RSpec/NotToNot: Enabled: true RSpec/Focus: - Description: 'Checks if there are focused specs.' + Description: 'Checks if examples are focused.' Enabled: true RSpec/ExampleLength: - Description: 'Checks for long example' + Description: 'Checks for long examples' Enabled: true Max: 5 RSpec/MessageExpectation: - Description: 'Checks for consistent message expectation style.' + Description: 'Checks for consistent message expectation style' Enabled: true EnforcedStyle: allow SupportedStyles: @@ -119,7 +119,7 @@ RSpec/MessageExpectation: - expect RSpec/NamedSubject: - Description: 'Name your RSpec subject if you reference it explicitly' + Description: 'Checks for explicitly referenced test subjects' Enabled: true RSpec/SubjectStub: diff --git a/lib/rubocop/cop/rspec/any_instance.rb b/lib/rubocop/cop/rspec/any_instance.rb index ce2c4b013..9c30e3532 100644 --- a/lib/rubocop/cop/rspec/any_instance.rb +++ b/lib/rubocop/cop/rspec/any_instance.rb @@ -1,6 +1,8 @@ module RuboCop module Cop module RSpec + # Check that instances are not being stubbed globally + # # Prefer instance doubles over stubbing any instance of a class # # @example diff --git a/lib/rubocop/cop/rspec/be_eql.rb b/lib/rubocop/cop/rspec/be_eql.rb index d47c0a986..94bfd17f5 100644 --- a/lib/rubocop/cop/rspec/be_eql.rb +++ b/lib/rubocop/cop/rspec/be_eql.rb @@ -1,7 +1,7 @@ module RuboCop module Cop module RSpec - # Check for test expectations that can use `be` instead of `eql` + # Check for expectations where `be(...)` can be used instead of `eql(...)` # # The `be` matcher compares by identity while the `eql` matcher # compares using `eql?`. Integers, floats, booleans, and symbols diff --git a/lib/rubocop/cop/rspec/describe_class.rb b/lib/rubocop/cop/rspec/describe_class.rb index 2861e317b..b4ad85191 100644 --- a/lib/rubocop/cop/rspec/describe_class.rb +++ b/lib/rubocop/cop/rspec/describe_class.rb @@ -3,8 +3,7 @@ module RuboCop module Cop module RSpec - # Check that the first argument to the top level describe is the tested - # class or module. + # Check that the first argument to the top level describe is a constant # # @example # # bad diff --git a/lib/rubocop/cop/rspec/describe_method.rb b/lib/rubocop/cop/rspec/describe_method.rb index 8416e9818..d51400d7f 100644 --- a/lib/rubocop/cop/rspec/describe_method.rb +++ b/lib/rubocop/cop/rspec/describe_method.rb @@ -3,8 +3,7 @@ module RuboCop module Cop module RSpec - # Checks that the second argument to the top level describe is the tested - # method name. + # Checks that the second argument to `describe` specifies a method # # @example # # bad diff --git a/lib/rubocop/cop/rspec/described_class.rb b/lib/rubocop/cop/rspec/described_class.rb index 7414c18ce..71f9bd7a3 100644 --- a/lib/rubocop/cop/rspec/described_class.rb +++ b/lib/rubocop/cop/rspec/described_class.rb @@ -3,6 +3,8 @@ module RuboCop module Cop module RSpec + # Checks that tests use `described_class` + # # If the first argument of describe is a class, the class is exposed to # each example via described_class - this should be used instead of # repeating the class. diff --git a/lib/rubocop/cop/rspec/example_length.rb b/lib/rubocop/cop/rspec/example_length.rb index 87e906398..fc073ddf9 100644 --- a/lib/rubocop/cop/rspec/example_length.rb +++ b/lib/rubocop/cop/rspec/example_length.rb @@ -3,6 +3,8 @@ module RuboCop module Cop module RSpec + # Checks for long examples + # # A long example is usually more difficult to understand. Consider # extracting out some behaviour, e.g. with a `let` block, or a helper # method. diff --git a/lib/rubocop/cop/rspec/example_wording.rb b/lib/rubocop/cop/rspec/example_wording.rb index dcc2a04d5..79d5e212d 100644 --- a/lib/rubocop/cop/rspec/example_wording.rb +++ b/lib/rubocop/cop/rspec/example_wording.rb @@ -3,8 +3,9 @@ module RuboCop module Cop module RSpec - # Do not use should when describing your tests. - # see: http://betterspecs.org/#should + # Checks that example descriptions do not start with "should" + # + # @see http://betterspecs.org/#should # # The autocorrect is experimental - use with care! It can be configured # with CustomTransform (e.g. have => has) and IgnoredWords (e.g. only). diff --git a/lib/rubocop/cop/rspec/expect_actual.rb b/lib/rubocop/cop/rspec/expect_actual.rb index 381bed35b..820f86f29 100644 --- a/lib/rubocop/cop/rspec/expect_actual.rb +++ b/lib/rubocop/cop/rspec/expect_actual.rb @@ -3,7 +3,7 @@ module RuboCop module Cop module RSpec - # Checks for literal values within `expect(...)` + # Checks for `expect(...)` calls containing literal values # # @example # # bad diff --git a/lib/rubocop/cop/rspec/file_path.rb b/lib/rubocop/cop/rspec/file_path.rb index 5b29e5b10..1115ba041 100644 --- a/lib/rubocop/cop/rspec/file_path.rb +++ b/lib/rubocop/cop/rspec/file_path.rb @@ -3,6 +3,8 @@ module RuboCop module Cop module RSpec + # Checks that spec file paths are consistent with the test subject + # # Checks the path of the spec file and enforces that it reflects the # described class/module and its optionally called out method. # diff --git a/lib/rubocop/cop/rspec/focus.rb b/lib/rubocop/cop/rspec/focus.rb index 601e0c985..8c1c30d6b 100644 --- a/lib/rubocop/cop/rspec/focus.rb +++ b/lib/rubocop/cop/rspec/focus.rb @@ -3,7 +3,7 @@ module RuboCop module Cop module RSpec - # Checks if test is focused. + # Checks if examples are focused. # # @example # # bad diff --git a/lib/rubocop/cop/rspec/hook_argument.rb b/lib/rubocop/cop/rspec/hook_argument.rb index ccba67dec..370ffeffd 100644 --- a/lib/rubocop/cop/rspec/hook_argument.rb +++ b/lib/rubocop/cop/rspec/hook_argument.rb @@ -3,6 +3,8 @@ module RuboCop module Cop module RSpec + # Checks the arguments passed to `before`, `around`, and `after`. + # # This cop checks for consistent style when specifying RSpec # hooks which run for each example. There are three supported # styles: "implicit", "each", and "example." All styles have diff --git a/lib/rubocop/cop/rspec/instance_variable.rb b/lib/rubocop/cop/rspec/instance_variable.rb index d4e735015..17e1dac2c 100644 --- a/lib/rubocop/cop/rspec/instance_variable.rb +++ b/lib/rubocop/cop/rspec/instance_variable.rb @@ -3,8 +3,7 @@ module RuboCop module Cop module RSpec - # When you have to assign a variable instead of using an instance - # variable, use let. + # Checks for instance variable usage in specs # # This cop can be configured with the option `AssignmentOnly` which # will configure the cop to only register offenses on instance diff --git a/lib/rubocop/cop/rspec/leading_subject.rb b/lib/rubocop/cop/rspec/leading_subject.rb index 614714f80..4e76b697e 100644 --- a/lib/rubocop/cop/rspec/leading_subject.rb +++ b/lib/rubocop/cop/rspec/leading_subject.rb @@ -3,7 +3,7 @@ module RuboCop module Cop module RSpec - # Declare test subject before let declarations + # Checks for `subject` definitions that come after `let` definitions # # @example # # bad diff --git a/lib/rubocop/cop/rspec/let_setup.rb b/lib/rubocop/cop/rspec/let_setup.rb index a268a7d76..82415c392 100644 --- a/lib/rubocop/cop/rspec/let_setup.rb +++ b/lib/rubocop/cop/rspec/let_setup.rb @@ -3,7 +3,7 @@ module RuboCop module Cop module RSpec - # Detect unreferenced `let!` calls being used for test setup + # Checks unreferenced `let!` calls being used for test setup. # # @example # # Bad diff --git a/lib/rubocop/cop/rspec/message_expectation.rb b/lib/rubocop/cop/rspec/message_expectation.rb index 93af3940b..2c2a5eaf0 100644 --- a/lib/rubocop/cop/rspec/message_expectation.rb +++ b/lib/rubocop/cop/rspec/message_expectation.rb @@ -3,7 +3,7 @@ module RuboCop module Cop module RSpec - # Checks specs for consistent message expectation style + # Checks for consistent message expectation style # # This cop can be configured in your configuration using the # `EnforcedStyle` option and supports `--auto-gen-config`. diff --git a/lib/rubocop/cop/rspec/multiple_describes.rb b/lib/rubocop/cop/rspec/multiple_describes.rb index 577d9ad73..f4bde99d9 100644 --- a/lib/rubocop/cop/rspec/multiple_describes.rb +++ b/lib/rubocop/cop/rspec/multiple_describes.rb @@ -3,8 +3,10 @@ module RuboCop module Cop module RSpec - # Checks for multiple top level describes. They should be nested if it is - # for the same class or module or separated into different files. + # Checks for multiple top level describes + # + # Multiple descriptions for the same class or module should either + # be nested or separated into different test files. # # @example # # bad diff --git a/lib/rubocop/cop/rspec/named_subject.rb b/lib/rubocop/cop/rspec/named_subject.rb index 42135eae3..7aa59bf25 100644 --- a/lib/rubocop/cop/rspec/named_subject.rb +++ b/lib/rubocop/cop/rspec/named_subject.rb @@ -3,7 +3,14 @@ module RuboCop module Cop module RSpec - # Give `subject` a descriptive name if you reference it directly + # Checks for explicitly referenced test subjects + # + # RSpec lets you declare an "implicit subject" using `subject { ... }` + # which allows for tests like `it { should be_valid }`. If you need to + # reference your test subject you should explicitly name it using + # `subject(:your_subject_name) { ... }`. Your test subjects should be + # the most important object in your tests so they deserve a descriptive + # name. # # @example # # bad diff --git a/lib/rubocop/cop/rspec/not_to_not.rb b/lib/rubocop/cop/rspec/not_to_not.rb index 4e150b6aa..83b7521d5 100644 --- a/lib/rubocop/cop/rspec/not_to_not.rb +++ b/lib/rubocop/cop/rspec/not_to_not.rb @@ -1,8 +1,7 @@ module RuboCop module Cop module RSpec - # Enforces the usage of the same method on all negative message - # expectations. + # Checks for consistent method usage for negating expectations # # @example # # bad diff --git a/lib/rubocop/cop/rspec/subject_stub.rb b/lib/rubocop/cop/rspec/subject_stub.rb index f6341a140..98b263ddb 100644 --- a/lib/rubocop/cop/rspec/subject_stub.rb +++ b/lib/rubocop/cop/rspec/subject_stub.rb @@ -3,7 +3,7 @@ module RuboCop module Cop module RSpec - # Checks for stubs on test subjects + # Checks for stubbed test subjects # # @see https://robots.thoughtbot.com/don-t-stub-the-system-under-test # diff --git a/lib/rubocop/cop/rspec/verified_doubles.rb b/lib/rubocop/cop/rspec/verified_doubles.rb index fb41709a6..d06f815ee 100644 --- a/lib/rubocop/cop/rspec/verified_doubles.rb +++ b/lib/rubocop/cop/rspec/verified_doubles.rb @@ -4,7 +4,8 @@ module RuboCop module Cop module RSpec # Prefer using verifying doubles over normal doubles. - # see: https://relishapp.com/rspec/rspec-mocks/docs/verifying-doubles + # + # @see https://relishapp.com/rspec/rspec-mocks/docs/verifying-doubles # # @example # # bad From 8be54a6313f81f4d3f989b4ed6365b9788c4c240 Mon Sep 17 00:00:00 2001 From: John Backus Date: Tue, 9 Aug 2016 19:40:22 -0400 Subject: [PATCH 2/5] Add tool to extract cop descriptions from docs Fixes #173 --- bin/build_config | 20 +++++++ config/default.yml | 57 +++++++++---------- lib/rubocop/rspec/config_formatter.rb | 31 ++++++++++ lib/rubocop/rspec/description_extractor.rb | 35 ++++++++++++ rubocop-rspec.gemspec | 1 + spec/rubocop/rspec/config_formatter_spec.rb | 48 ++++++++++++++++ .../rspec/description_extractor_spec.rb | 35 ++++++++++++ 7 files changed, 198 insertions(+), 29 deletions(-) create mode 100755 bin/build_config create mode 100644 lib/rubocop/rspec/config_formatter.rb create mode 100644 lib/rubocop/rspec/description_extractor.rb create mode 100644 spec/rubocop/rspec/config_formatter_spec.rb create mode 100644 spec/rubocop/rspec/description_extractor_spec.rb diff --git a/bin/build_config b/bin/build_config new file mode 100755 index 000000000..41791a813 --- /dev/null +++ b/bin/build_config @@ -0,0 +1,20 @@ +#!/usr/bin/env ruby + +$LOAD_PATH.unshift(File.join(__dir__, '..', 'lib')) + +require 'yard' +require 'yaml' + +require 'rubocop/rspec/description_extractor' +require 'rubocop/rspec/config_formatter' + +glob = File.join(__dir__, '..', 'lib', 'rubocop', 'cop', 'rspec', '*.rb') +YARD.parse(glob, []) + +descriptions = RuboCop::RSpec::DescriptionExtractor.new(YARD::Registry.all).to_h +current_config = YAML.load_file('config/default.yml') + +File.write( + 'config/default.yml', + RuboCop::RSpec::ConfigFormatter.new(current_config, descriptions).dump +) diff --git a/config/default.yml b/config/default.yml index 6212862f9..3c861ee14 100644 --- a/config/default.yml +++ b/config/default.yml @@ -1,21 +1,20 @@ +--- AllCops: RSpec: Patterns: - # Match any file that contains `_spec.rb` so that FilePath can also flag - # and correct files with odd extensions like `spec/foo_spec.rb.html` - - '_spec.rb' - - '(?:^|/)spec/' + - _spec.rb + - "(?:^|/)spec/" RSpec/AnyInstance: - Description: 'Check that instances are not being stubbed globally' + Description: Check that instances are not being stubbed globally Enabled: true RSpec/BeEql: - Description: 'Check for expectations where `be(...)` can be used instead of `eql(...)`' + Description: Check for expectations where `be(...)` can be used instead of `eql(...)` Enabled: true RSpec/HookArgument: - Description: 'Checks the arguments passed to `before`, `around`, and `after`.' + Description: Checks the arguments passed to `before`, `around`, and `after`. Enabled: true EnforcedStyle: implicit SupportedStyles: @@ -24,20 +23,20 @@ RSpec/HookArgument: - example RSpec/DescribeClass: - Description: 'Check that the first argument to the top level describe is a constant' + Description: Check that the first argument to the top level describe is a constant Enabled: true RSpec/DescribedClass: - Description: 'Checks that tests use `described_class`' + Description: Checks that tests use `described_class` SkipBlocks: false Enabled: true RSpec/DescribeMethod: - Description: 'Checks that the second argument to `describe` specifies a method' + Description: Checks that the second argument to `describe` specifies a method Enabled: true RSpec/ExampleWording: - Description: 'Checks that example descriptions do not start with "should"' + Description: Checks that example descriptions do not start with "should" Enabled: true CustomTransform: be: is @@ -46,72 +45,72 @@ RSpec/ExampleWording: IgnoredWords: [] RSpec/EmptyExampleGroup: - Description: 'Checks if an example group does not include any tests' + Description: Checks if an example group does not include any tests Enabled: true CustomIncludeMethods: [] RSpec/ExpectActual: - Description: 'Checks for `expect(...)` calls containing literal values' + Description: Checks for `expect(...)` calls containing literal values Enabled: true RSpec/MultipleDescribes: - Description: 'Checks for multiple top level describes' + Description: Checks for multiple top level describes Enabled: true RSpec/MultipleExpectations: - Description: 'Checks if examples contain too many `expect` calls' + Description: Checks if examples contain too many `expect` calls Enabled: true Max: 1 RSpec/NestedGroups: - Description: 'Checks for nested example groups' + Description: Checks for nested example groups Enabled: true MaxNesting: 2 RSpec/InstanceVariable: - Description: 'Checks for instance variable usage in specs' + Description: Checks for instance variable usage in specs AssignmentOnly: false Enabled: true RSpec/LetSetup: - Description: 'Checks for `let!` being used for test setup.' + Description: Checks for `let!` being used for test setup. Enabled: true RSpec/LeadingSubject: - Description: 'Checks for `subject` definitions that come after `let` definitions' + Description: Checks for `subject` definitions that come after `let` definitions Enabled: true RSpec/FilePath: - Description: 'Checks that spec file paths are consistent with the test subject' + Description: Checks that spec file paths are consistent with the test subject Enabled: true CustomTransform: RuboCop: rubocop RSpec: rspec RSpec/VerifiedDoubles: - Description: 'Prefer using verifying doubles over normal doubles.' + Description: Prefer using verifying doubles over normal doubles. Enabled: true IgnoreSymbolicNames: false RSpec/NotToNot: - Description: 'Checks for consistent method usage for negating expectations' + Description: Checks for consistent method usage for negating expectations EnforcedStyle: not_to SupportedStyles: - - not_to - - to_not + - not_to + - to_not Enabled: true RSpec/Focus: - Description: 'Checks if examples are focused.' + Description: Checks if examples are focused. Enabled: true RSpec/ExampleLength: - Description: 'Checks for long examples' + Description: Checks for long examples Enabled: true Max: 5 RSpec/MessageExpectation: - Description: 'Checks for consistent message expectation style' + Description: Checks for consistent message expectation style Enabled: true EnforcedStyle: allow SupportedStyles: @@ -119,9 +118,9 @@ RSpec/MessageExpectation: - expect RSpec/NamedSubject: - Description: 'Checks for explicitly referenced test subjects' + Description: Checks for explicitly referenced test subjects Enabled: true RSpec/SubjectStub: - Description: 'Checks for stubbed test subjects' + Description: Checks for stubbed test subjects Enabled: true diff --git a/lib/rubocop/rspec/config_formatter.rb b/lib/rubocop/rspec/config_formatter.rb new file mode 100644 index 000000000..228408690 --- /dev/null +++ b/lib/rubocop/rspec/config_formatter.rb @@ -0,0 +1,31 @@ +module RuboCop + module RSpec + # Builds a YAML config file from two config hashes + class ConfigFormatter + NAMESPACE = 'RSpec'.freeze + + def initialize(config, descriptions) + @config = config + @descriptions = descriptions + end + + def dump + YAML.dump(unified_config).gsub(/^#{NAMESPACE}/, "\n#{NAMESPACE}") + end + + private + + def unified_config + cops.each_with_object(config.dup) do |cop, unified| + unified[cop] = descriptions.fetch(cop).merge(config.fetch(cop)) + end + end + + def cops + (descriptions.keys + config.keys).uniq.grep(/\A#{NAMESPACE}/) + end + + attr_reader :config, :descriptions + end + end +end diff --git a/lib/rubocop/rspec/description_extractor.rb b/lib/rubocop/rspec/description_extractor.rb new file mode 100644 index 000000000..7aec05cc8 --- /dev/null +++ b/lib/rubocop/rspec/description_extractor.rb @@ -0,0 +1,35 @@ +module RuboCop + module RSpec + # Extracts cop descriptions from YARD docstrings + class DescriptionExtractor + COP_NAMESPACE = 'RuboCop::Cop::RSpec'.freeze + COP_FORMAT = 'RSpec/%s'.freeze + + def initialize(yardocs) + @yardocs = yardocs + end + + def to_h + cop_documentation.each_with_object({}) do |(name, docstring), config| + config[format(COP_FORMAT, name)] = { + 'Description' => docstring.split("\n\n").first.to_s + } + end + end + + private + + def cop_documentation + yardocs + .select(&method(:cop?)) + .map { |doc| [doc.name, doc.docstring] } + end + + def cop?(doc) + doc.type.equal?(:class) && doc.to_s.start_with?(COP_NAMESPACE) + end + + attr_reader :yardocs + end + end +end diff --git a/rubocop-rspec.gemspec b/rubocop-rspec.gemspec index 9b7f75f2d..7082e7793 100644 --- a/rubocop-rspec.gemspec +++ b/rubocop-rspec.gemspec @@ -38,4 +38,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'anima' spec.add_development_dependency 'concord' spec.add_development_dependency 'adamantium' + spec.add_development_dependency 'yard' end diff --git a/spec/rubocop/rspec/config_formatter_spec.rb b/spec/rubocop/rspec/config_formatter_spec.rb new file mode 100644 index 000000000..7b01f1ab1 --- /dev/null +++ b/spec/rubocop/rspec/config_formatter_spec.rb @@ -0,0 +1,48 @@ +require 'rubocop/rspec/config_formatter' + +RSpec.describe RuboCop::RSpec::ConfigFormatter do + let(:config) do + { + 'AllCops' => { + 'Setting' => 'fourty two' + }, + 'RSpec/Foo' => { + 'Config' => 2, + 'Enabled' => true + }, + 'RSpec/Bar' => { + 'Enabled' => true + } + } + end + + let(:descriptions) do + { + 'RSpec/Foo' => { + 'Description' => 'Blah' + }, + 'RSpec/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 + | + |RSpec/Foo: + | Description: Blah + | Config: 2 + | Enabled: true + | + |RSpec/Bar: + | Description: Wow + | Enabled: true + YAML + end +end diff --git a/spec/rubocop/rspec/description_extractor_spec.rb b/spec/rubocop/rspec/description_extractor_spec.rb new file mode 100644 index 000000000..c4fb1cd14 --- /dev/null +++ b/spec/rubocop/rspec/description_extractor_spec.rb @@ -0,0 +1,35 @@ +require 'yard' + +require 'rubocop/rspec/description_extractor' + +RSpec.describe RuboCop::RSpec::DescriptionExtractor do + let(:yardocs) do + [ + instance_double( + YARD::CodeObjects::MethodObject, + docstring: "Checks foo\n\nLong description", + to_s: 'RuboCop::Cop::RSpec::Foo', + type: :class, + name: 'Foo' + ), + instance_double( + YARD::CodeObjects::MethodObject, + docstring: 'Hi', + to_s: 'RuboCop::Cop::RSpec::Foo#bar', + type: :method, + name: 'Foo#bar' + ), + instance_double( + YARD::CodeObjects::MethodObject, + docstring: 'This is not a cop', + to_s: 'RuboCop::Cop::Mixin::Sneaky', + type: :class + ) + ] + end + + it 'builds a hash of descriptions' do + expect(described_class.new(yardocs).to_h) + .to eql('RSpec/Foo' => { 'Description' => 'Checks foo' }) + end +end From 93d973f68e0cb77a7682aafc21c554084828454d Mon Sep 17 00:00:00 2001 From: John Backus Date: Tue, 9 Aug 2016 19:53:46 -0400 Subject: [PATCH 3/5] Add build task to confirm config is in sync --- Rakefile | 20 +++++++++++++++++++- bin/build_config | 3 +-- config/default.yml | 2 +- lib/rubocop/rspec/config_formatter.rb | 4 +++- spec/rubocop/rspec/config_formatter_spec.rb | 4 ++-- 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/Rakefile b/Rakefile index 5cc39337b..320a81889 100644 --- a/Rakefile +++ b/Rakefile @@ -1,5 +1,8 @@ +require 'open3' + require 'bundler' require 'bundler/gem_tasks' + begin Bundler.setup(:default, :development) rescue Bundler::BundlerError => e @@ -27,4 +30,19 @@ task :internal_investigation do abort('RuboCop failed!') unless result.zero? end -task default: [:spec, :internal_investigation] +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 + _, stdout, _, process = + Open3.popen3('git diff --exit-code config/default.yml') + + unless process.value.success? + raise "default.yml is out of sync:\n\n#{stdout.read}\nRun bin/build_config" + end +end + +task default: [:spec, :internal_investigation, :confirm_config] diff --git a/bin/build_config b/bin/build_config index 41791a813..6bcfeee2f 100755 --- a/bin/build_config +++ b/bin/build_config @@ -3,13 +3,12 @@ $LOAD_PATH.unshift(File.join(__dir__, '..', 'lib')) require 'yard' -require 'yaml' require 'rubocop/rspec/description_extractor' require 'rubocop/rspec/config_formatter' glob = File.join(__dir__, '..', 'lib', 'rubocop', 'cop', 'rspec', '*.rb') -YARD.parse(glob, []) +YARD.parse(Dir[glob], []) descriptions = RuboCop::RSpec::DescriptionExtractor.new(YARD::Registry.all).to_h current_config = YAML.load_file('config/default.yml') diff --git a/config/default.yml b/config/default.yml index 3c861ee14..4ff35d014 100644 --- a/config/default.yml +++ b/config/default.yml @@ -73,7 +73,7 @@ RSpec/InstanceVariable: Enabled: true RSpec/LetSetup: - Description: Checks for `let!` being used for test setup. + Description: Checks unreferenced `let!` calls being used for test setup. Enabled: true RSpec/LeadingSubject: diff --git a/lib/rubocop/rspec/config_formatter.rb b/lib/rubocop/rspec/config_formatter.rb index 228408690..44818dfea 100644 --- a/lib/rubocop/rspec/config_formatter.rb +++ b/lib/rubocop/rspec/config_formatter.rb @@ -1,3 +1,5 @@ +require 'yaml' + module RuboCop module RSpec # Builds a YAML config file from two config hashes @@ -17,7 +19,7 @@ def dump def unified_config cops.each_with_object(config.dup) do |cop, unified| - unified[cop] = descriptions.fetch(cop).merge(config.fetch(cop)) + unified[cop] = config.fetch(cop).merge(descriptions.fetch(cop)) end end diff --git a/spec/rubocop/rspec/config_formatter_spec.rb b/spec/rubocop/rspec/config_formatter_spec.rb index 7b01f1ab1..61c521608 100644 --- a/spec/rubocop/rspec/config_formatter_spec.rb +++ b/spec/rubocop/rspec/config_formatter_spec.rb @@ -36,13 +36,13 @@ | Setting: fourty two | |RSpec/Foo: - | Description: Blah | Config: 2 | Enabled: true + | Description: Blah | |RSpec/Bar: - | Description: Wow | Enabled: true + | Description: Wow YAML end end From 37905fa6e29f7c003d749b1838f7304f59f23c18 Mon Sep 17 00:00:00 2001 From: John Backus Date: Sat, 13 Aug 2016 18:28:54 -0700 Subject: [PATCH 4/5] Add spec asserting descriptions end with a period --- config/default.yml | 38 +++++++++---------- lib/rubocop/cop/rspec/any_instance.rb | 2 +- lib/rubocop/cop/rspec/be_eql.rb | 2 +- lib/rubocop/cop/rspec/describe_class.rb | 2 +- lib/rubocop/cop/rspec/describe_method.rb | 2 +- lib/rubocop/cop/rspec/described_class.rb | 2 +- lib/rubocop/cop/rspec/empty_example_group.rb | 2 +- lib/rubocop/cop/rspec/example_length.rb | 2 +- lib/rubocop/cop/rspec/example_wording.rb | 2 +- lib/rubocop/cop/rspec/expect_actual.rb | 2 +- lib/rubocop/cop/rspec/file_path.rb | 2 +- lib/rubocop/cop/rspec/instance_variable.rb | 2 +- lib/rubocop/cop/rspec/leading_subject.rb | 2 +- lib/rubocop/cop/rspec/message_expectation.rb | 2 +- lib/rubocop/cop/rspec/multiple_describes.rb | 2 +- .../cop/rspec/multiple_expectations.rb | 2 +- lib/rubocop/cop/rspec/named_subject.rb | 2 +- lib/rubocop/cop/rspec/nested_groups.rb | 2 +- lib/rubocop/cop/rspec/not_to_not.rb | 2 +- lib/rubocop/cop/rspec/subject_stub.rb | 2 +- spec/project/default_config_spec.rb | 4 ++ 21 files changed, 42 insertions(+), 38 deletions(-) diff --git a/config/default.yml b/config/default.yml index 4ff35d014..dd82d3c01 100644 --- a/config/default.yml +++ b/config/default.yml @@ -6,11 +6,11 @@ AllCops: - "(?:^|/)spec/" RSpec/AnyInstance: - Description: Check that instances are not being stubbed globally + Description: Check that instances are not being stubbed globally. Enabled: true RSpec/BeEql: - Description: Check for expectations where `be(...)` can be used instead of `eql(...)` + Description: Check for expectations where `be(...)` can replace `eql(...)`. Enabled: true RSpec/HookArgument: @@ -23,20 +23,20 @@ RSpec/HookArgument: - example RSpec/DescribeClass: - Description: Check that the first argument to the top level describe is a constant + Description: Check that the first argument to the top level describe is a constant. Enabled: true RSpec/DescribedClass: - Description: Checks that tests use `described_class` + Description: Checks that tests use `described_class`. SkipBlocks: false Enabled: true RSpec/DescribeMethod: - Description: Checks that the second argument to `describe` specifies a method + Description: Checks that the second argument to `describe` specifies a method. Enabled: true RSpec/ExampleWording: - Description: Checks that example descriptions do not start with "should" + Description: Checks that example descriptions do not start with "should". Enabled: true CustomTransform: be: is @@ -45,30 +45,30 @@ RSpec/ExampleWording: IgnoredWords: [] RSpec/EmptyExampleGroup: - Description: Checks if an example group does not include any tests + Description: Checks if an example group does not include any tests. Enabled: true CustomIncludeMethods: [] RSpec/ExpectActual: - Description: Checks for `expect(...)` calls containing literal values + Description: Checks for `expect(...)` calls containing literal values. Enabled: true RSpec/MultipleDescribes: - Description: Checks for multiple top level describes + Description: Checks for multiple top level describes. Enabled: true RSpec/MultipleExpectations: - Description: Checks if examples contain too many `expect` calls + Description: Checks if examples contain too many `expect` calls. Enabled: true Max: 1 RSpec/NestedGroups: - Description: Checks for nested example groups + Description: Checks for nested example groups. Enabled: true MaxNesting: 2 RSpec/InstanceVariable: - Description: Checks for instance variable usage in specs + Description: Checks for instance variable usage in specs. AssignmentOnly: false Enabled: true @@ -77,11 +77,11 @@ RSpec/LetSetup: Enabled: true RSpec/LeadingSubject: - Description: Checks for `subject` definitions that come after `let` definitions + Description: Checks for `subject` definitions that come after `let` definitions. Enabled: true RSpec/FilePath: - Description: Checks that spec file paths are consistent with the test subject + Description: Checks that spec file paths are consistent with the test subject. Enabled: true CustomTransform: RuboCop: rubocop @@ -93,7 +93,7 @@ RSpec/VerifiedDoubles: IgnoreSymbolicNames: false RSpec/NotToNot: - Description: Checks for consistent method usage for negating expectations + Description: Checks for consistent method usage for negating expectations. EnforcedStyle: not_to SupportedStyles: - not_to @@ -105,12 +105,12 @@ RSpec/Focus: Enabled: true RSpec/ExampleLength: - Description: Checks for long examples + Description: Checks for long examples. Enabled: true Max: 5 RSpec/MessageExpectation: - Description: Checks for consistent message expectation style + Description: Checks for consistent message expectation style. Enabled: true EnforcedStyle: allow SupportedStyles: @@ -118,9 +118,9 @@ RSpec/MessageExpectation: - expect RSpec/NamedSubject: - Description: Checks for explicitly referenced test subjects + Description: Checks for explicitly referenced test subjects. Enabled: true RSpec/SubjectStub: - Description: Checks for stubbed test subjects + Description: Checks for stubbed test subjects. Enabled: true diff --git a/lib/rubocop/cop/rspec/any_instance.rb b/lib/rubocop/cop/rspec/any_instance.rb index 9c30e3532..237a54371 100644 --- a/lib/rubocop/cop/rspec/any_instance.rb +++ b/lib/rubocop/cop/rspec/any_instance.rb @@ -1,7 +1,7 @@ module RuboCop module Cop module RSpec - # Check that instances are not being stubbed globally + # Check that instances are not being stubbed globally. # # Prefer instance doubles over stubbing any instance of a class # diff --git a/lib/rubocop/cop/rspec/be_eql.rb b/lib/rubocop/cop/rspec/be_eql.rb index 94bfd17f5..ce3a3d89b 100644 --- a/lib/rubocop/cop/rspec/be_eql.rb +++ b/lib/rubocop/cop/rspec/be_eql.rb @@ -1,7 +1,7 @@ module RuboCop module Cop module RSpec - # Check for expectations where `be(...)` can be used instead of `eql(...)` + # Check for expectations where `be(...)` can replace `eql(...)`. # # The `be` matcher compares by identity while the `eql` matcher # compares using `eql?`. Integers, floats, booleans, and symbols diff --git a/lib/rubocop/cop/rspec/describe_class.rb b/lib/rubocop/cop/rspec/describe_class.rb index b4ad85191..106f0daf9 100644 --- a/lib/rubocop/cop/rspec/describe_class.rb +++ b/lib/rubocop/cop/rspec/describe_class.rb @@ -3,7 +3,7 @@ module RuboCop module Cop module RSpec - # Check that the first argument to the top level describe is a constant + # Check that the first argument to the top level describe is a constant. # # @example # # bad diff --git a/lib/rubocop/cop/rspec/describe_method.rb b/lib/rubocop/cop/rspec/describe_method.rb index d51400d7f..a26ea9837 100644 --- a/lib/rubocop/cop/rspec/describe_method.rb +++ b/lib/rubocop/cop/rspec/describe_method.rb @@ -3,7 +3,7 @@ module RuboCop module Cop module RSpec - # Checks that the second argument to `describe` specifies a method + # Checks that the second argument to `describe` specifies a method. # # @example # # bad diff --git a/lib/rubocop/cop/rspec/described_class.rb b/lib/rubocop/cop/rspec/described_class.rb index 71f9bd7a3..24c9c8622 100644 --- a/lib/rubocop/cop/rspec/described_class.rb +++ b/lib/rubocop/cop/rspec/described_class.rb @@ -3,7 +3,7 @@ module RuboCop module Cop module RSpec - # Checks that tests use `described_class` + # Checks that tests use `described_class`. # # If the first argument of describe is a class, the class is exposed to # each example via described_class - this should be used instead of diff --git a/lib/rubocop/cop/rspec/empty_example_group.rb b/lib/rubocop/cop/rspec/empty_example_group.rb index 1ca7884d1..1c9279c69 100644 --- a/lib/rubocop/cop/rspec/empty_example_group.rb +++ b/lib/rubocop/cop/rspec/empty_example_group.rb @@ -3,7 +3,7 @@ module RuboCop module Cop module RSpec - # Checks if an example group does not include any tests + # Checks if an example group does not include any tests. # # This cop is configurable using the `CustomIncludeMethods` option # diff --git a/lib/rubocop/cop/rspec/example_length.rb b/lib/rubocop/cop/rspec/example_length.rb index fc073ddf9..2edf52c2d 100644 --- a/lib/rubocop/cop/rspec/example_length.rb +++ b/lib/rubocop/cop/rspec/example_length.rb @@ -3,7 +3,7 @@ module RuboCop module Cop module RSpec - # Checks for long examples + # Checks for long examples. # # A long example is usually more difficult to understand. Consider # extracting out some behaviour, e.g. with a `let` block, or a helper diff --git a/lib/rubocop/cop/rspec/example_wording.rb b/lib/rubocop/cop/rspec/example_wording.rb index 79d5e212d..167b202a4 100644 --- a/lib/rubocop/cop/rspec/example_wording.rb +++ b/lib/rubocop/cop/rspec/example_wording.rb @@ -3,7 +3,7 @@ module RuboCop module Cop module RSpec - # Checks that example descriptions do not start with "should" + # Checks that example descriptions do not start with "should". # # @see http://betterspecs.org/#should # diff --git a/lib/rubocop/cop/rspec/expect_actual.rb b/lib/rubocop/cop/rspec/expect_actual.rb index 820f86f29..a644d38fd 100644 --- a/lib/rubocop/cop/rspec/expect_actual.rb +++ b/lib/rubocop/cop/rspec/expect_actual.rb @@ -3,7 +3,7 @@ module RuboCop module Cop module RSpec - # Checks for `expect(...)` calls containing literal values + # Checks for `expect(...)` calls containing literal values. # # @example # # bad diff --git a/lib/rubocop/cop/rspec/file_path.rb b/lib/rubocop/cop/rspec/file_path.rb index 1115ba041..64e4cfa14 100644 --- a/lib/rubocop/cop/rspec/file_path.rb +++ b/lib/rubocop/cop/rspec/file_path.rb @@ -3,7 +3,7 @@ module RuboCop module Cop module RSpec - # Checks that spec file paths are consistent with the test subject + # Checks that spec file paths are consistent with the test subject. # # Checks the path of the spec file and enforces that it reflects the # described class/module and its optionally called out method. diff --git a/lib/rubocop/cop/rspec/instance_variable.rb b/lib/rubocop/cop/rspec/instance_variable.rb index 17e1dac2c..0396c2146 100644 --- a/lib/rubocop/cop/rspec/instance_variable.rb +++ b/lib/rubocop/cop/rspec/instance_variable.rb @@ -3,7 +3,7 @@ module RuboCop module Cop module RSpec - # Checks for instance variable usage in specs + # Checks for instance variable usage in specs. # # This cop can be configured with the option `AssignmentOnly` which # will configure the cop to only register offenses on instance diff --git a/lib/rubocop/cop/rspec/leading_subject.rb b/lib/rubocop/cop/rspec/leading_subject.rb index 4e76b697e..c9b813afe 100644 --- a/lib/rubocop/cop/rspec/leading_subject.rb +++ b/lib/rubocop/cop/rspec/leading_subject.rb @@ -3,7 +3,7 @@ module RuboCop module Cop module RSpec - # Checks for `subject` definitions that come after `let` definitions + # Checks for `subject` definitions that come after `let` definitions. # # @example # # bad diff --git a/lib/rubocop/cop/rspec/message_expectation.rb b/lib/rubocop/cop/rspec/message_expectation.rb index 2c2a5eaf0..6b0f0be4c 100644 --- a/lib/rubocop/cop/rspec/message_expectation.rb +++ b/lib/rubocop/cop/rspec/message_expectation.rb @@ -3,7 +3,7 @@ module RuboCop module Cop module RSpec - # Checks for consistent message expectation style + # Checks for consistent message expectation style. # # This cop can be configured in your configuration using the # `EnforcedStyle` option and supports `--auto-gen-config`. diff --git a/lib/rubocop/cop/rspec/multiple_describes.rb b/lib/rubocop/cop/rspec/multiple_describes.rb index f4bde99d9..a0b51a6a3 100644 --- a/lib/rubocop/cop/rspec/multiple_describes.rb +++ b/lib/rubocop/cop/rspec/multiple_describes.rb @@ -3,7 +3,7 @@ module RuboCop module Cop module RSpec - # Checks for multiple top level describes + # Checks for multiple top level describes. # # Multiple descriptions for the same class or module should either # be nested or separated into different test files. diff --git a/lib/rubocop/cop/rspec/multiple_expectations.rb b/lib/rubocop/cop/rspec/multiple_expectations.rb index 31936e6f5..9b12e15c8 100644 --- a/lib/rubocop/cop/rspec/multiple_expectations.rb +++ b/lib/rubocop/cop/rspec/multiple_expectations.rb @@ -3,7 +3,7 @@ module RuboCop module Cop module RSpec - # Checks if examples contain too many `expect` calls + # Checks if examples contain too many `expect` calls. # # @see http://betterspecs.org/#single Single expectation test # diff --git a/lib/rubocop/cop/rspec/named_subject.rb b/lib/rubocop/cop/rspec/named_subject.rb index 7aa59bf25..c1c3d2e99 100644 --- a/lib/rubocop/cop/rspec/named_subject.rb +++ b/lib/rubocop/cop/rspec/named_subject.rb @@ -3,7 +3,7 @@ module RuboCop module Cop module RSpec - # Checks for explicitly referenced test subjects + # Checks for explicitly referenced test subjects. # # RSpec lets you declare an "implicit subject" using `subject { ... }` # which allows for tests like `it { should be_valid }`. If you need to diff --git a/lib/rubocop/cop/rspec/nested_groups.rb b/lib/rubocop/cop/rspec/nested_groups.rb index 0357cf939..56ffd20f5 100644 --- a/lib/rubocop/cop/rspec/nested_groups.rb +++ b/lib/rubocop/cop/rspec/nested_groups.rb @@ -3,7 +3,7 @@ module RuboCop module Cop module RSpec - # Checks for nested example groups + # Checks for nested example groups. # # This cop is configurable using the `MaxNesting` option # diff --git a/lib/rubocop/cop/rspec/not_to_not.rb b/lib/rubocop/cop/rspec/not_to_not.rb index 83b7521d5..6af913799 100644 --- a/lib/rubocop/cop/rspec/not_to_not.rb +++ b/lib/rubocop/cop/rspec/not_to_not.rb @@ -1,7 +1,7 @@ module RuboCop module Cop module RSpec - # Checks for consistent method usage for negating expectations + # Checks for consistent method usage for negating expectations. # # @example # # bad diff --git a/lib/rubocop/cop/rspec/subject_stub.rb b/lib/rubocop/cop/rspec/subject_stub.rb index 98b263ddb..c06008846 100644 --- a/lib/rubocop/cop/rspec/subject_stub.rb +++ b/lib/rubocop/cop/rspec/subject_stub.rb @@ -3,7 +3,7 @@ module RuboCop module Cop module RSpec - # Checks for stubbed test subjects + # Checks for stubbed test subjects. # # @see https://robots.thoughtbot.com/don-t-stub-the-system-under-test # diff --git a/spec/project/default_config_spec.rb b/spec/project/default_config_spec.rb index 50ba00662..1aa7d5207 100644 --- a/spec/project/default_config_spec.rb +++ b/spec/project/default_config_spec.rb @@ -42,6 +42,10 @@ def cop_configuration(config_key) end end + it 'ends every description with a period' do + expect(cop_configuration('Description')).to all(end_with('.')) + end + it 'includes Enabled: true for every cop' do expect(cop_configuration('Enabled')).to all(be(true)) end From 65d6fbc133dc9530b6c8397fa7ee768d8e903081 Mon Sep 17 00:00:00 2001 From: John Backus Date: Sat, 13 Aug 2016 18:30:38 -0700 Subject: [PATCH 5/5] Change `rake` to build config first There are tests which assert that the configuration is correct. The config is built from documentation so it is useful to have the configuration be in sync before the specs are run. --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 320a81889..f7a6660fb 100644 --- a/Rakefile +++ b/Rakefile @@ -45,4 +45,4 @@ task confirm_config: :build_config do end end -task default: [:spec, :internal_investigation, :confirm_config] +task default: [:build_config, :spec, :internal_investigation, :confirm_config]