diff --git a/.reek.yml b/.reek.yml index 8fa5f167..933745e1 100644 --- a/.reek.yml +++ b/.reek.yml @@ -113,6 +113,7 @@ detectors: - RubyCritic::Reporter#self.report_generator_class - RubyCritic::SourceLocator#deduplicate_symlinks - RubyCritic::Command::Compare#compare_branches + - RubyCritic::Attributes::ClassMethods#coerce FeatureEnvy: exclude: - Parser::AST::Node#module_name @@ -163,6 +164,7 @@ detectors: - RubyCritic::Command::Compare#threshold_reached? - RubyCritic::Command::Compare#threshold_values_set? - RubyCritic::SourceLocator#ruby_file? + - RubyCritic::Attributes#default_for TooManyInstanceVariables: exclude: - RubyCritic::Generator::Html::CodeFile @@ -189,6 +191,13 @@ detectors: exclude: - RubyCritic::Config#self.method_missing - RubyCritic::Config#self.respond_to_missing? + - RubyCritic::Attributes::ClassMethods#attributes + ModuleInitialize: + exclude: + - RubyCritic::Attributes + NilCheck: + exclude: + - RubyCritic::Attributes::ClassMethods#coerce TooManyMethods: exclude: - RubyCritic::Cli::Options::File diff --git a/CHANGELOG.md b/CHANGELOG.md index bb057bd8..dbc13e10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * [CHORE] ... * [FEATURE] ... +* [CHANGE] Replace the unmaintained `virtus` dependency with a small internal attributes module, dropping its abandoned transitive dependencies (`axiom-types`, `coercible`, `descendants_tracker`, `ice_nine`, and the deprecated `thread_safe`). Also move `ostruct` to a development dependency since it is only used in tests. (by [@pnomolos][]) * [CHANGE] Replace all Cucumber features with Minitest/Spec specs (by [@faisal][]) * [BUGFIX] Add `lang="en"` to the report's `` element, give the menu-toggle anchor an `aria-label`, and make the per-rating summary IDs unique. Fixes 17 WCAG 2.1 AA structural errors on `overview.html`. (by [@MarcusAl][]) @@ -506,3 +507,4 @@ [@exoego]: https://github.com/exoego [@raff-s]: https://github.com/raff-s [@MarcusAl]: https://github.com/MarcusAl +[@pnomolos]: https://github.com/pnomolos diff --git a/lib/rubycritic/core/analysed_module.rb b/lib/rubycritic/core/analysed_module.rb index 441a091b..5091c549 100644 --- a/lib/rubycritic/core/analysed_module.rb +++ b/lib/rubycritic/core/analysed_module.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -require 'virtus' +require 'rubycritic/core/attributes' require 'rubycritic/core/rating' module RubyCritic class AnalysedModule - include Virtus.model + include Attributes # Complexity is reduced by a factor of 25 when calculating cost COMPLEXITY_FACTOR = 25.0 diff --git a/lib/rubycritic/core/attributes.rb b/lib/rubycritic/core/attributes.rb new file mode 100644 index 00000000..c6f050d4 --- /dev/null +++ b/lib/rubycritic/core/attributes.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +module RubyCritic + # Lightweight replacement for the subset of Virtus used by RubyCritic. + # Provides an `attribute` class macro that registers attributes (with + # optional type coercion and default value), generates reader/writer + # methods, and an initializer accepting a hash of attribute values. + module Attributes + def self.included(base) + base.extend(ClassMethods) + end + + # Class-level macros and attribute registry. + module ClassMethods + def attributes + @attributes ||= + if superclass.respond_to?(:attributes) + superclass.attributes.dup + else + {} + end + end + + def attribute(name, type = nil, default: nil) + attributes[name] = { type: type, default: default } + + define_method(name) { instance_variable_get("@#{name}") } + define_method("#{name}=") do |value| + instance_variable_set("@#{name}", self.class.coerce(type, value)) + end + end + + def coerce(type, value) + return value if value.nil? || type.nil? + + case type.name + when 'Float' then Float(value) + when 'Integer' then Integer(value) + when 'Symbol' then value.to_sym + when 'Array' then Array(value) + else value + end + end + end + + def initialize(attributes = {}) + self.class.attributes.each do |name, definition| + if attributes.key?(name) + public_send("#{name}=", attributes[name]) + else + instance_variable_set("@#{name}", default_for(definition)) + end + end + end + + private + + def default_for(definition) + default = definition[:default] + case default + when Array, Hash then default.dup + else default + end + end + end +end diff --git a/lib/rubycritic/core/smell.rb b/lib/rubycritic/core/smell.rb index 37dcb9f2..4adbf32b 100644 --- a/lib/rubycritic/core/smell.rb +++ b/lib/rubycritic/core/smell.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -require 'virtus' +require 'rubycritic/core/attributes' require 'rubycritic/core/location' module RubyCritic class Smell - include Virtus.model + include Attributes attribute :context attribute :cost diff --git a/rubycritic.gemspec b/rubycritic.gemspec index 88051dca..3333c24f 100644 --- a/rubycritic.gemspec +++ b/rubycritic.gemspec @@ -34,7 +34,6 @@ Gem::Specification.new do |spec| spec.add_dependency 'flay', '~> 2.13' spec.add_dependency 'flog', '~> 4.7' spec.add_dependency 'launchy', '>= 2.5.2' - spec.add_dependency 'ostruct' spec.add_dependency 'parser', '>= 3.3.0.5' spec.add_dependency 'prism', '>= 1.6.0' spec.add_dependency 'rainbow', '~> 3.1.1' @@ -43,7 +42,6 @@ Gem::Specification.new do |spec| spec.add_dependency 'ruby_parser', '~> 3.21' spec.add_dependency 'simplecov', '>= 0.22.0' spec.add_dependency 'tty-which', '~> 0.5.0' - spec.add_dependency 'virtus', '~> 2.0' spec.add_development_dependency 'aruba', '~> 2.3.1', '>= 2.3.1' spec.add_development_dependency 'bundler', '>= 2.0.0' @@ -60,6 +58,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'minitest-around', '~> 0.6.0' spec.add_development_dependency 'minitest-mock' spec.add_development_dependency 'mocha', '~> 3.0.0' + spec.add_development_dependency 'ostruct' spec.add_development_dependency 'rake', '~> 13.3.0', '>= 11.0.0' spec.add_development_dependency 'rdoc' spec.add_development_dependency 'rexml', '>= 3.2.0'