diff --git a/config/default.yml b/config/default.yml new file mode 100644 index 0000000..7701aef --- /dev/null +++ b/config/default.yml @@ -0,0 +1,46 @@ +# This is the default configuration file. + +Sequel: + Enabled: true + DocumentationBaseURL: https://github.com/rubocop/rubocop-sequel/blob/master/README.md + +Sequel/ConcurrentIndex: + Description: Encourages the creation of indexes with the `NOT VALID` option to avoid locking tables. + Reference: https://www.rubydoc.info/gems/rubocop-sequel/RuboCop/Cop/Sequel/ConcurrentIndex + Enabled: true + VersionAdded: 0.0.1 + VersionChanged: 0.3.3 + +Sequel/JSONColumn: + Description: >- + Advocates the use of the `jsonb` column type over `json` or `hstore` for performance + benefits and additional functionality. + Reference: https://www.rubydoc.info/gems/rubocop-sequel/RuboCop/Cop/Sequel/JSONColumn + Enabled: true + Safe: false + VersionAdded: 0.0.1 + VersionChanged: 0.3.3 + +Sequel/MigrationName: + Description: >- + Helps to name migration files descriptively in order to avoid the use of default or generic names. + Reference: https://www.rubydoc.info/gems/rubocop-sequel/RuboCop/Cop/Sequel/MigrationName + Enabled: true + VersionAdded: '0.0.1' + VersionChanged: '0.3.3' + DefaultName: new_migration + +Sequel/PartialConstraint: + Description: Advises on the correct use of partial indexes by specifying the `where' argument. + Reference: https://www.rubydoc.info/gems/rubocop-sequel/RuboCop/Cop/Sequel/PartialConstraint + Enabled: true + VersionAdded: '0.3.0' + VersionChanged: '0.3.3' + +Sequel/SaveChanges: + Description: Ensures the use of save_changes instead of save to persist changes to models. + Reference: https://www.rubydoc.info/gems/rubocop-sequel/RuboCop/Cop/Sequel/SaveChanges + Enabled: true + SafeAutoCorrect: false + VersionAdded: '0.0.1' + VersionChanged: '0.3.3' diff --git a/config/obsoletion.yml b/config/obsoletion.yml new file mode 100644 index 0000000..bd3e13e --- /dev/null +++ b/config/obsoletion.yml @@ -0,0 +1,14 @@ +# +# Configuration for obsoletion. +# +# See: https://docs.rubocop.org/rubocop/extensions.html#config-obsoletions +# +extracted: + Sequel/*: ~ + +removed: + Sequel/ColumnDefault: + reason: >- + ColumnDefault cop since it's mostly outdated advice. + Details [#33](https://github.com/rubocop/rubocop-sequel/issues/32) + diff --git a/lib/rubocop-sequel.rb b/lib/rubocop-sequel.rb index 1d09ae9..0ca6e2e 100644 --- a/lib/rubocop-sequel.rb +++ b/lib/rubocop-sequel.rb @@ -1,8 +1,12 @@ # frozen_string_literal: true require 'rubocop' - +require 'rubocop/sequel' require 'rubocop/sequel/version' +require 'rubocop/sequel/inject' + +RuboCop::Sequel::Inject.defaults! + require 'rubocop/cop/sequel/concurrent_index' require 'rubocop/cop/sequel/json_column' require 'rubocop/cop/sequel/migration_name' diff --git a/lib/rubocop/sequel.rb b/lib/rubocop/sequel.rb index 7cc9e2f..91a5e78 100644 --- a/lib/rubocop/sequel.rb +++ b/lib/rubocop/sequel.rb @@ -3,5 +3,14 @@ module RuboCop # RuboCop Sequel project namespace module Sequel + PROJECT_ROOT = Pathname.new(__dir__).parent.parent.expand_path.freeze + CONFIG_DEFAULT = PROJECT_ROOT.join('config', 'default.yml').freeze + CONFIG = YAML.safe_load(CONFIG_DEFAULT.read).freeze + + private_constant(:CONFIG_DEFAULT, :PROJECT_ROOT) + + if ::RuboCop.const_defined?(:ConfigObsoletion) + ::RuboCop::ConfigObsoletion.files << PROJECT_ROOT.join('config', 'obsoletion.yml') + end end end diff --git a/lib/rubocop/sequel/inject.rb b/lib/rubocop/sequel/inject.rb new file mode 100644 index 0000000..68364e7 --- /dev/null +++ b/lib/rubocop/sequel/inject.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module RuboCop + module Sequel + # Because RuboCop doesn't yet support plugins, we have to monkey patch in a + # bit of our configuration. + module Inject + def self.defaults! + path = CONFIG_DEFAULT.to_s + hash = ConfigLoader.send(:load_yaml_configuration, path) + config = Config.new(hash, path).tap(&:make_excludes_absolute) + puts "configuration from #{path}" if ConfigLoader.debug? + config = ConfigLoader.merge_with_default(config, path, unset_nil: false) + ConfigLoader.instance_variable_set(:@default_configuration, config) + end + end + end +end diff --git a/spec/project_spec.rb b/spec/project_spec.rb new file mode 100644 index 0000000..66d2da6 --- /dev/null +++ b/spec/project_spec.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'config/default.yml', type: :feature do + subject(:config) { RuboCop::ConfigLoader.load_file('config/default.yml') } + + let(:configuration_keys) { config.tap { |c| c.delete('inherit_mode') }.keys } + let(:version_regexp) { /\A\d+\.\d+(?:\.\d+)?\z|\A<>\z/ } + + shared_examples 'has a nicely formatted description' do |cop_name| + it 'does not contain new lines' do + description = config.dig(cop_name, 'Description') + + expect(description.include?("\n")).to be(false) + end + + it 'stars from a verb' do # rubocop:disable RSpec/ExampleLength + description = config.dig(cop_name, 'Description') + start_with_subject = description.match(/\AThis cop (?.+?) .*/) + suggestion = start_with_subject[:verb]&.capitalize if start_with_subject + suggestion ||= 'a verb' + + expect(start_with_subject).to( + be_nil, "should be started with `#{suggestion}` instead of `This cop ...`." + ) + end + + it 'has a period at EOL of description' do + description = config.dig(cop_name, 'Description') + + expect(description).to match(/\.\z/) + end + end + + shared_examples 'has metadata' do |cop_name| + context 'with VersionAdded' do + it 'required' do + version = config.dig(cop_name, 'VersionAdded') + expect(version).not_to be_nil + end + + it 'nicely formatted' do + version = config.dig(cop_name, 'VersionAdded') + expect(version).to match(version_regexp), "should be format ('X.Y' or 'X.Y.Z' or '<>')" + end + end + + context 'with VersionChanged' do + it 'nicely formatted' do + version = config.dig(cop_name, 'VersionChanged') + next unless version + + expect(version).to match(version_regexp), "should be format ('X.Y' or 'X.Y.Z' or '<>')" + end + end + + context 'with VersionRemoved' do + it 'nicely formatted' do + version = config.dig(cop_name, 'VersionRemoved') + next unless version + + expect(version).to match(version_regexp), "should be format ('X.Y' or 'X.Y.Z' or '<>')" + end + end + + context 'with Safe' do + it 'does not include `true`' do + safe = config.dig(cop_name, 'Safe') + expect(safe).not_to be(true), 'has unnecessary `Safe: true` config.' + end + end + + context 'with SafeAutoCorrect' do + it 'does not include unnecessary `false`' do + next unless config.dig(cop_name, 'Safe') == false + + safe_autocorrect = config.dig(cop_name, 'SafeAutoCorrect') + + expect(safe_autocorrect).not_to be(false), 'has unnecessary `SafeAutoCorrect: false` config.' + end + end + end + + cop_names = RuboCop::Cop::Registry.global.with_department(:Sequel).cops.map(&:cop_name) + cop_names.each do |cop_name| + describe "Cop #{cop_name}" do + include_examples 'has a nicely formatted description', cop_name + include_examples 'has metadata', cop_name + end + end + + it 'sorts configuration keys alphabetically' do + expected = configuration_keys.sort + configuration_keys.each_with_index do |key, idx| + expect(key).to eq expected[idx] + end + end + + it 'sorts cop names alphabetically' do # rubocop:disable RSpec/ExampleLength + previous_key = '' + config_default = YAML.load_file('config/default.yml') + + config_default.each_key do |key| + next if %w[inherit_mode AllCops].include?(key) + + expect(previous_key <= key).to be(true), "Cops should be sorted alphabetically. Please sort #{key}." + previous_key = key + end + end +end