From ee4f69d6b1ce203f483e14035ba078fbfd98f2a2 Mon Sep 17 00:00:00 2001 From: Francois Buys Date: Fri, 27 Oct 2023 00:34:43 +0200 Subject: [PATCH] ISSUE-1734 Replace kwalify with dry-schema for schema validation The current validator (Kwalify) seems outdated and lacks good documentation. A recent issue showed that we could improve the schema validation to also check and warn against missing configurations (See issue: #1734) dry-schema provide good documentation and it looks like it also provides the features we require. Structural validation where key presence can be verified separately from values. This removes ambiguity related to "presence" validation where you don't know if value is indeed nil or if a key is missing in the input hash. Changes include: - Update changelog - Add a validation section to How-To-Write-New-Detectors - Add schema specs - Update feature specs See: https://github.com/troessner/reek/issues/1734 --- CHANGELOG.md | 4 + docs/How-To-Write-New-Detectors.md | 12 + .../directory_specific_directives.feature | 3 +- .../masking_smells.feature | 2 +- .../schema_validation.feature | 3 +- lib/reek/configuration/schema.rb | 182 +++++++++++++++ lib/reek/configuration/schema.yml | 210 ------------------ lib/reek/configuration/schema_validator.rb | 24 +- reek.gemspec | 2 +- spec/reek/configuration/schema_spec.rb | 150 +++++++++++++ .../configuration/schema_validator_spec.rb | 29 ++- 11 files changed, 386 insertions(+), 235 deletions(-) create mode 100644 lib/reek/configuration/schema.rb delete mode 100644 lib/reek/configuration/schema.yml create mode 100644 spec/reek/configuration/schema_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index d2e9830d9..8cd59a191 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change log +## [Unreleased] + +* (fbuys) Replace the config schema validator [Kwalify](https://github.com/sunaku/kwalify) with [dry-schema](https://github.com/dry-rb/dry-schema) + ## 6.1.4 (2023-01-13) * (mvz) Update parser dependency to the 3.2.x series diff --git a/docs/How-To-Write-New-Detectors.md b/docs/How-To-Write-New-Detectors.md index aaa91d9a9..0857af5d7 100644 --- a/docs/How-To-Write-New-Detectors.md +++ b/docs/How-To-Write-New-Detectors.md @@ -124,6 +124,18 @@ end The following examples should then cover the detector specific features. +### Schema validation + +We make use of [dry-schema](https://github.com/dry-rb/dry-schema) to validate the +the reek configuration file (usually named `.reek.yml` in the root of a project). +The validation will warn reek users of missing or incorrectly defined configuration. + +If you add or modify a detector you will need to make sure that you update the +[schema](lib/reek/configuration/schema.rb) file. For help with the schema syntax +you can refer to the [dry-schema documentation](https://dry-rb.org/gems/dry-schema). +You can also take a look at the existing schema rules for they all look fairly +similar. + ### Cucumber features We are trying to write as few Cucumber features as possible. diff --git a/features/configuration_files/directory_specific_directives.feature b/features/configuration_files/directory_specific_directives.feature index 77a1e5df2..95e0944ba 100644 --- a/features/configuration_files/directory_specific_directives.feature +++ b/features/configuration_files/directory_specific_directives.feature @@ -200,7 +200,8 @@ Feature: Directory directives Then the exit status indicates an error And stderr reports: """ - Error: We found some problems with your configuration file: [/directories/dummy_directory/IteratorsNested] key 'IteratorsNested:' is undefined. + Error: We found some problems with your configuration file: + [/directories/dummy_directory/IteratorsNested/enabled] is not allowed. """ Scenario: Abort on file as directory directive diff --git a/features/configuration_files/masking_smells.feature b/features/configuration_files/masking_smells.feature index 032840fd4..ec17e274d 100644 --- a/features/configuration_files/masking_smells.feature +++ b/features/configuration_files/masking_smells.feature @@ -9,7 +9,7 @@ Feature: Masking smells using config files When I run reek -c corrupt.reek smelly.rb And stderr reports: """ - Error: We found some problems with your configuration file: [/] 'Not a valid configuration file': not a mapping. + Error: Invalid configuration file at .reek.yml. """ And the exit status indicates an error And it reports nothing diff --git a/features/configuration_files/schema_validation.feature b/features/configuration_files/schema_validation.feature index fbacfec1f..22435951e 100644 --- a/features/configuration_files/schema_validation.feature +++ b/features/configuration_files/schema_validation.feature @@ -55,5 +55,6 @@ Feature: Validate schema Then the exit status indicates an error And stderr reports: """ - Error: We found some problems with your configuration file: [/detectors/DoesNotExist] key 'DoesNotExist:' is undefined. + Error: We found some problems with your configuration file: + [/detectors/DoesNotExist/enabled] is not allowed. """ diff --git a/lib/reek/configuration/schema.rb b/lib/reek/configuration/schema.rb new file mode 100644 index 000000000..57f91ad1a --- /dev/null +++ b/lib/reek/configuration/schema.rb @@ -0,0 +1,182 @@ +# frozen_string_literal: true + +require 'dry/schema' + +module Reek + module Configuration + # + # Configuration schema constants. + # + class Schema + # Enable the :info extension so we can introspect + # your keys and types + Dry::Schema.load_extensions(:info) + + # rubocop:disable Metrics/BlockLength + ALL_DETECTORS_SCHEMA = Dry::Schema.Params do + optional(:Attribute).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + end + optional(:BooleanParameter).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + end + optional(:ClassVariable).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + end + optional(:ControlParameter).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + end + optional(:DataClump).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + end + optional(:DataClump).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + optional(:max_copies).filled(:integer) + optional(:min_clump_size).filled(:integer) + end + optional(:DuplicateMethodCall).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + optional(:max_calls).filled(:integer) + optional(:allow_calls).array(:string) + end + optional(:FeatureEnvy).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + end + optional(:InstanceVariableAssumption).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + end + optional(:IrresponsibleModule).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + end + optional(:LongParameterList).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + optional(:max_params).filled(:integer) + optional(:overrides).filled(:hash) do + required(:initialize).filled(:hash) do + required(:max_params).filled(:integer) + end + end + end + optional(:LongYieldList).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + optional(:max_params).filled(:integer) + end + optional(:ManualDispatch).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + end + optional(:MissingSafeMethod).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + end + optional(:ModuleInitialize).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + end + optional(:NestedIterators).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + optional(:max_allowed_nesting).filled(:integer) + optional(:ignore_iterators) { array(:string) & filled? } + end + optional(:NilCheck).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + end + optional(:RepeatedConditional).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + optional(:max_ifs).filled(:integer) + end + optional(:SubclassedFromCoreClass).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + end + optional(:TooManyConstants).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + optional(:max_constants).filled(:integer) + end + optional(:TooManyInstanceVariables).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + optional(:max_instance_variables).filled(:integer) + end + optional(:TooManyMethods).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + optional(:max_methods).filled(:integer) + end + optional(:TooManyStatements).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + optional(:max_statements).filled(:integer) + end + optional(:UncommunicativeMethodName).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + optional(:reject).array(:string) + optional(:accept).array(:string) + end + optional(:UncommunicativeModuleName).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + optional(:reject).array(:string) + optional(:accept).array(:string) + end + optional(:UncommunicativeParameterName).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + optional(:reject).array(:string) + optional(:accept).array(:string) + end + optional(:UncommunicativeVariableName).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + optional(:reject).array(:string) + optional(:accept).array(:string) + end + optional(:UnusedParameters).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + end + optional(:UnusedPrivateMethod).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + end + optional(:UtilityFunction).filled(:hash) do + optional(:enabled).filled(:bool) + optional(:exclude).array(:string) + optional(:public_methods_only).filled(:bool) + end + end + # rubocop:enable Metrics/BlockLength + + # @quality :reek:TooManyStatements { max_statements: 7 } + def self.schema(directories = []) + Dry::Schema.Params do + config.validate_keys = true + # config.messages.load_paths << "lib/reek/configuration/schema_errors.yml" + + optional(:detectors).filled(ALL_DETECTORS_SCHEMA) + optional(:directories).filled(:hash) do + directories.each { |dir| optional(dir.to_sym).filled(ALL_DETECTORS_SCHEMA) } + end + optional(:exclude_paths).array(:string) + end + end + end + end +end diff --git a/lib/reek/configuration/schema.yml b/lib/reek/configuration/schema.yml deleted file mode 100644 index 9b28da121..000000000 --- a/lib/reek/configuration/schema.yml +++ /dev/null @@ -1,210 +0,0 @@ ---- -type: map -mapping: - "detectors": - type: map - mapping: &all_detectors - Attribute: - type: map - mapping: &detector_base - "enabled": - type: bool - "exclude": - type: seq - sequence: - - type: str - BooleanParameter: - type: map - mapping: - <<: *detector_base - ClassVariable: - type: map - mapping: - <<: *detector_base - ControlParameter: - type: map - mapping: - <<: *detector_base - DataClump: - type: map - mapping: - <<: *detector_base - max_copies: - type: number - min_clump_size: - type: number - DuplicateMethodCall: - type: map - mapping: - <<: *detector_base - max_calls: - type: number - allow_calls: - type: seq - sequence: - - type: str - FeatureEnvy: - type: map - mapping: - <<: *detector_base - InstanceVariableAssumption: - type: map - mapping: - <<: *detector_base - IrresponsibleModule: - type: map - mapping: - <<: *detector_base - LongParameterList: - type: map - mapping: - <<: *detector_base - max_params: - type: number - overrides: - type: map - mapping: - initialize: - type: map - mapping: - max_params: - type: number - LongYieldList: - type: map - mapping: - <<: *detector_base - max_params: - type: number - ManualDispatch: - type: map - mapping: - <<: *detector_base - MissingSafeMethod: - type: map - mapping: - <<: *detector_base - ModuleInitialize: - type: map - mapping: - <<: *detector_base - NestedIterators: - type: map - mapping: - <<: *detector_base - max_allowed_nesting: - type: number - ignore_iterators: - type: seq - sequence: - - type: str - NilCheck: - type: map - mapping: - <<: *detector_base - RepeatedConditional: - type: map - mapping: - <<: *detector_base - max_ifs: - type: number - SubclassedFromCoreClass: - type: map - mapping: - <<: *detector_base - Syntax: - type: map - mapping: - <<: *detector_base - TooManyConstants: - type: map - mapping: - <<: *detector_base - max_constants: - type: number - TooManyInstanceVariables: - type: map - mapping: - <<: *detector_base - max_instance_variables: - type: number - TooManyMethods: - type: map - mapping: - <<: *detector_base - max_methods: - type: number - TooManyStatements: - type: map - mapping: - <<: *detector_base - max_statements: - type: number - UncommunicativeMethodName: - type: map - mapping: - <<: *detector_base - reject: &reject_settings - type: seq - sequence: - - type: str - accept: &accept_settings - type: seq - sequence: - - type: str - UncommunicativeModuleName: - type: map - mapping: - <<: *detector_base - reject: *reject_settings - accept: *accept_settings - UncommunicativeParameterName: - type: map - mapping: - <<: *detector_base - reject: *reject_settings - accept: *accept_settings - UncommunicativeVariableName: - type: map - mapping: - <<: *detector_base - reject: *reject_settings - accept: *accept_settings - UnusedParameters: - type: map - mapping: - <<: *detector_base - UnusedPrivateMethod: - type: map - mapping: - <<: *detector_base - UtilityFunction: - type: map - mapping: - <<: *detector_base - public_methods_only: - type: bool - - "directories": - type: map - mapping: - # For any given key that is not matched somewhere else we'll apply the schema below. - # So this will just slurp in every directory we throw at it and then validate everything under - # it against all detectors - for instance: - # - # directories: - # "web_app/app/controllers": - # NestedIterators: - # enabled: false - # "web_app/app/helpers": - # UtilityFunction: - # enabled: false - # - # For details check out: http://www.kuwata-lab.com/kwalify/ruby/users-guide.02.html#tips-default - =: - type: map - mapping: *all_detectors - - "exclude_paths": - type: seq - sequence: - - type: str diff --git a/lib/reek/configuration/schema_validator.rb b/lib/reek/configuration/schema_validator.rb index 3f51585f8..c05659195 100644 --- a/lib/reek/configuration/schema_validator.rb +++ b/lib/reek/configuration/schema_validator.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true -require 'yaml' require_relative '../cli/silencer' -Reek::CLI::Silencer.without_warnings { require 'kwalify' } require_relative '../errors/config_file_error' +require_relative 'schema' module Reek module Configuration @@ -11,28 +10,29 @@ module Configuration # Schema validator module. # class SchemaValidator - SCHEMA_FILE_PATH = File.expand_path('./schema.yml', __dir__) - def initialize(configuration) @configuration = configuration - @validator = CLI::Silencer.without_warnings do - schema_file = Kwalify::Yaml.load_file(SCHEMA_FILE_PATH) - Kwalify::Validator.new(schema_file) - end + config_directories = configuration['directories']&.keys || [] + @validator = Reek::Configuration::Schema.schema(config_directories) end def validate - errors = CLI::Silencer.without_warnings { @validator.validate @configuration } - return if !errors || errors.empty? + result = CLI::Silencer.without_warnings { @validator.call(@configuration) } + return if result.success? - raise Errors::ConfigFileError, error_message(errors) + raise Errors::ConfigFileError, error_message(result.errors) + rescue NoMethodError + raise Errors::ConfigFileError, "Invalid configuration file at #{Reek::DEFAULT_CONFIGURATION_FILE_NAME}." end private # :reek:UtilityFunction def error_message(errors) - "We found some problems with your configuration file: #{CLI::Silencer.silently { errors.join(', ') }}" + messages = errors.map do |error| + "[/#{error.path.join('/')}] #{error.text}." + end.join("\n") + "We found some problems with your configuration file:\n#{messages}" end end end diff --git a/reek.gemspec b/reek.gemspec index 12477afa0..b2604dc69 100644 --- a/reek.gemspec +++ b/reek.gemspec @@ -31,7 +31,7 @@ Gem::Specification.new do |spec| 'rubygems_mfa_required' => 'true' } - spec.add_runtime_dependency 'kwalify', '~> 0.7.0' + spec.add_runtime_dependency 'dry-schema', '~> 1.13.0' spec.add_runtime_dependency 'parser', '~> 3.2.0' spec.add_runtime_dependency 'rainbow', '>= 2.0', '< 4.0' spec.add_runtime_dependency 'rexml', '~> 3.1' diff --git a/spec/reek/configuration/schema_spec.rb b/spec/reek/configuration/schema_spec.rb new file mode 100644 index 000000000..392d76126 --- /dev/null +++ b/spec/reek/configuration/schema_spec.rb @@ -0,0 +1,150 @@ +require_relative '../../spec_helper' +require_lib 'reek/configuration/schema' + +RSpec.shared_examples_for 'all detectors present' do |path| + it 'contains a rule for the Attribute detector' do + expect(schema.info.dig(*(path + [:Attribute]))).not_to be_nil + end + + it 'contains a rule for the BooleanParameter detector' do + expect(schema.info.dig(*(path + [:BooleanParameter]))).not_to be_nil + end + + it 'contains a rule for the ClassVariable detector' do + expect(schema.info.dig(*(path + [:ClassVariable]))).not_to be_nil + end + + it 'contains a rule for the ControlParameter detector' do + expect(schema.info.dig(*(path + [:ControlParameter]))).not_to be_nil + end + + it 'contains a rule for the DataClump detector' do + expect(schema.info.dig(*(path + [:DataClump]))).not_to be_nil + end + + it 'contains a rule for the DuplicateMethodCall detector' do + expect(schema.info.dig(*(path + [:DuplicateMethodCall]))).not_to be_nil + end + + it 'contains a rule for the FeatureEnvy detector' do + expect(schema.info.dig(*(path + [:FeatureEnvy]))).not_to be_nil + end + + it 'contains a rule for the InstanceVariableAssumption detector' do + expect(schema.info.dig(*(path + [:InstanceVariableAssumption]))).not_to be_nil + end + + it 'contains a rule for the IrresponsibleModule detector' do + expect(schema.info.dig(*(path + [:IrresponsibleModule]))).not_to be_nil + end + + it 'contains a rule for the LongParameterList detector' do + expect(schema.info.dig(*(path + [:LongParameterList]))).not_to be_nil + end + + it 'contains a rule for the LongYieldList detector' do + expect(schema.info.dig(*(path + [:LongYieldList]))).not_to be_nil + end + + it 'contains a rule for the ManualDispatch detector' do + expect(schema.info.dig(*(path + [:ManualDispatch]))).not_to be_nil + end + + it 'contains a rule for the MissingSafeMethod detector' do + expect(schema.info.dig(*(path + [:MissingSafeMethod]))).not_to be_nil + end + + it 'contains a rule for the ModuleInitialize detector' do + expect(schema.info.dig(*(path + [:ModuleInitialize]))).not_to be_nil + end + + it 'contains a rule for the NestedIterators detector' do + expect(schema.info.dig(*(path + [:NestedIterators]))).not_to be_nil + end + + it 'contains a rule for the NilCheck detector' do + expect(schema.info.dig(*(path + [:NilCheck]))).not_to be_nil + end + + it 'contains a rule for the RepeatedConditional detector' do + expect(schema.info.dig(*(path + [:RepeatedConditional]))).not_to be_nil + end + + it 'contains a rule for the SubclassedFromCoreClass detector' do + expect(schema.info.dig(*(path + [:SubclassedFromCoreClass]))).not_to be_nil + end + + it 'contains a rule for the TooManyConstants detector' do + expect(schema.info.dig(*(path + [:TooManyConstants]))).not_to be_nil + end + + it 'contains a rule for the TooManyInstanceVariables detector' do + expect(schema.info.dig(*(path + [:TooManyInstanceVariables]))).not_to be_nil + end + + it 'contains a rule for the TooManyMethods detector' do + expect(schema.info.dig(*(path + [:TooManyMethods]))).not_to be_nil + end + + it 'contains a rule for the TooManyStatements detector' do + expect(schema.info.dig(*(path + [:TooManyStatements]))).not_to be_nil + end + + it 'contains a rule for the UncommunicativeMethodName detector' do + expect(schema.info.dig(*(path + [:UncommunicativeMethodName]))).not_to be_nil + end + + it 'contains a rule for the UncommunicativeModuleName detector' do + expect(schema.info.dig(*(path + [:UncommunicativeModuleName]))).not_to be_nil + end + + it 'contains a rule for the UncommunicativeParameterName detector' do + expect(schema.info.dig(*(path + [:UncommunicativeParameterName]))).not_to be_nil + end + + it 'contains a rule for the UncommunicativeVariableName detector' do + expect(schema.info.dig(*(path + [:UncommunicativeVariableName]))).not_to be_nil + end + + it 'contains a rule for the UnusedParameters detector' do + expect(schema.info.dig(*(path + [:UnusedParameters]))).not_to be_nil + end + + it 'contains a rule for the UnusedPrivateMethod detector' do + expect(schema.info.dig(*(path + [:UnusedPrivateMethod]))).not_to be_nil + end + + it 'contains a rule for the UtilityFunction detector' do + expect(schema.info.dig(*(path + [:UtilityFunction]))).not_to be_nil + end +end + +RSpec.describe Reek::Configuration::Schema do + describe '#schema' do + let(:schema) { described_class.schema } + + it 'contains rules for detectors' do + expect(schema.info[:keys]).to include(Reek::DETECTORS_KEY.to_sym) + end + + it_behaves_like 'all detectors present', [:keys, Reek::DETECTORS_KEY.to_sym, :keys] + + it 'contains rules for directories' do + expect(schema.info[:keys]).to include(Reek::DIRECTORIES_KEY.to_sym) + end + + it 'contains rules for exclude_paths' do + expect(schema.info[:keys]).to include(Reek::EXCLUDE_PATHS_KEY.to_sym) + end + + it 'does not create unnecessary directories rules' do + expect(schema.info.dig(:keys, :directories, :keys)).to be_empty + end + + context 'with directories' do + let(:schema) { described_class.schema(['app/controllers']) } + + it_behaves_like 'all detectors present', [:keys, Reek::DIRECTORIES_KEY.to_sym, :keys, :'app/controllers', :keys] + end + end +end diff --git a/spec/reek/configuration/schema_validator_spec.rb b/spec/reek/configuration/schema_validator_spec.rb index 5fef753bb..7f055f06e 100644 --- a/spec/reek/configuration/schema_validator_spec.rb +++ b/spec/reek/configuration/schema_validator_spec.rb @@ -21,6 +21,15 @@ end end + context 'when configuration is invalid and unparsable' do + let(:configuration) { 'Not a valid configuration file' } + + it 'raises an error' do + message = "Invalid configuration file at #{Reek::DEFAULT_CONFIGURATION_FILE_NAME}." + expect { validator.validate }.to raise_error(Reek::Errors::ConfigFileError, message) + end + end + context 'when detector is invalid' do let(:configuration) do { @@ -31,7 +40,7 @@ end it 'raises an error' do - message = %r{\[/detectors/DoesNotExist\] key 'DoesNotExist:' is undefined} + message = %r{\[/detectors/DoesNotExist/enabled\] is not allowed.} expect { validator.validate }.to raise_error(Reek::Errors::ConfigFileError, message) end end @@ -46,7 +55,7 @@ end it 'raises an error' do - message = %r{\[/detectors/FeatureEnvy/enabled\] 'foo': not a boolean} + message = %r{\[/detectors/FeatureEnvy/enabled\] must be boolean.} expect { validator.validate }.to raise_error(Reek::Errors::ConfigFileError, message) end end @@ -61,7 +70,7 @@ end it 'raises an error' do - message = %r{\[/detectors/DataClump/does_not_exist\] key 'does_not_exist:' is undefined} + message = %r{\[/detectors/DataClump/does_not_exist\] is not allowed.} expect { validator.validate }.to raise_error(Reek::Errors::ConfigFileError, message) end end @@ -78,7 +87,8 @@ end it 'raises an error' do - message = %r{\[/detectors/UncommunicativeMethodName/#{attribute}\] '42': not a sequence} + message = %r{\n\[/detectors/UncommunicativeMethodName/#{attribute}\] must be an array.} + expect { validator.validate }.to raise_error(Reek::Errors::ConfigFileError, message) end end @@ -93,7 +103,8 @@ end it 'raises an error' do - message = %r{\[/detectors/UncommunicativeMethodName/#{attribute}/0\] '42': not a string} + message = %r{\n\[/detectors/UncommunicativeMethodName/#{attribute}/0\] must be a string.} + expect { validator.validate }.to raise_error(Reek::Errors::ConfigFileError, message) end end @@ -108,7 +119,7 @@ end it 'raises an error' do - message = %r{\[/exclude_paths\] '42': not a sequence} + message = %r{\[/exclude_paths\] must be an array.} expect { validator.validate }.to raise_error(Reek::Errors::ConfigFileError, message) end end @@ -121,7 +132,7 @@ end it 'raises an error' do - message = %r{\[/exclude_paths/0\] '42': not a string} + message = %r{\[/exclude_paths/0\] must be a string.} expect { validator.validate }.to raise_error(Reek::Errors::ConfigFileError, message) end end @@ -139,7 +150,7 @@ end it 'raises an error' do - message = %r{\[/directories/web_app/app/helpers/Bar\] key 'Bar:' is undefined} + message = %r{\[/directories/web_app/app/helpers/Bar/enabled\] is not allowed.} expect { validator.validate }.to raise_error(Reek::Errors::ConfigFileError, message) end end @@ -156,7 +167,7 @@ end it 'raises an error' do - message = %r{\[/directories/web_app/app/controllers/NestedIterators/foo\] key 'foo:' is undefined} + message = %r{\[/directories/web_app/app/controllers/NestedIterators/foo\] is not allowed.} expect { validator.validate }.to raise_error(Reek::Errors::ConfigFileError, message) end end