├── .dockerignore ├── .github ├── dependabot.yml └── workflows │ └── ruby.yml ├── .gitignore ├── .reek.yml ├── .rubocop.yml ├── .rubocop_todo.yml ├── .simplecov ├── .yardopts ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── Gemfile ├── License.txt ├── README.md ├── Rakefile ├── bin ├── code_climate_reek └── reek ├── docs ├── API.md ├── Attribute.md ├── Basic-Smell-Options.md ├── Boolean-Parameter.md ├── Class-Variable.md ├── Code-Smells.md ├── Command-Line-Options.md ├── Control-Couple.md ├── Control-Parameter.md ├── Data-Clump.md ├── Duplicate-Method-Call.md ├── Feature-Envy.md ├── How-To-Write-New-Detectors.md ├── How-reek-works-internally.md ├── Instance-Variable-Assumption.md ├── Irresponsible-Module.md ├── Large-Class.md ├── Long-Parameter-List.md ├── Long-Yield-List.md ├── Manual-Dispatch.md ├── Missing-Safe-Method.md ├── Module-Initialize.md ├── Nested-Iterators.md ├── Nil-Check.md ├── RSpec-matchers.md ├── Rake-Task.md ├── Reek-4-to-Reek-5-migration.md ├── Reek-Driven-Development.md ├── Repeated-Conditional.md ├── Simulated-Polymorphism.md ├── Smell-Suppression.md ├── Style-Guide.md ├── Subclassed-From-Core-Class.md ├── Too-Many-Constants.md ├── Too-Many-Instance-Variables.md ├── Too-Many-Methods.md ├── Too-Many-Statements.md ├── Uncommunicative-Method-Name.md ├── Uncommunicative-Module-Name.md ├── Uncommunicative-Name.md ├── Uncommunicative-Parameter-Name.md ├── Uncommunicative-Variable-Name.md ├── Unused-Parameters.md ├── Unused-Private-Method.md ├── Utility-Function.md ├── Versioning-Policy.md ├── YAML-Reports.md ├── defaults.reek.yml ├── templates │ └── default │ │ ├── docstring │ │ ├── html │ │ │ └── public_api_marker.erb │ │ └── setup.rb │ │ └── fulldoc │ │ └── html │ │ └── css │ │ └── common.css └── yard_plugin.rb ├── engine.json ├── features ├── command_line_interface │ ├── basic_usage.feature │ ├── options.feature │ ├── show_progress.feature │ ├── smell_selection.feature │ ├── smells_count.feature │ └── stdin.feature ├── configuration_files │ ├── accept_setting.feature │ ├── directory_specific_directives.feature │ ├── exclude_directives.feature │ ├── exclude_paths_directives.feature │ ├── masking_smells.feature │ ├── mix_accept_reject_setting.feature │ ├── reject_setting.feature │ ├── schema_validation.feature │ ├── show_configuration_file.feature │ └── unused_private_method.feature ├── configuration_loading.feature ├── configuration_via_source_comments │ ├── erroneous_source_comments.feature │ └── well_formed_source_comments.feature ├── locales.feature ├── programmatic_access.feature ├── rake_task │ └── rake_task.feature ├── reports │ ├── codeclimate.feature │ ├── json.feature │ ├── reports.feature │ └── yaml.feature ├── rspec_matcher.feature ├── samples.feature ├── step_definitions │ ├── .rubocop.yml │ ├── reek_steps.rb │ └── sample_file_steps.rb ├── support │ └── env.rb └── todo_list.feature ├── lib ├── reek.rb └── reek │ ├── ast │ ├── ast_node_class_map.rb │ ├── builder.rb │ ├── node.rb │ ├── object_refs.rb │ ├── reference_collector.rb │ ├── sexp_extensions.rb │ └── sexp_extensions │ │ ├── arguments.rb │ │ ├── begin.rb │ │ ├── block.rb │ │ ├── case.rb │ │ ├── constant.rb │ │ ├── if.rb │ │ ├── lambda.rb │ │ ├── logical_operators.rb │ │ ├── methods.rb │ │ ├── module.rb │ │ ├── nested_assignables.rb │ │ ├── self.rb │ │ ├── send.rb │ │ ├── super.rb │ │ ├── symbols.rb │ │ ├── variables.rb │ │ ├── when.rb │ │ └── yield.rb │ ├── cli │ ├── application.rb │ ├── command │ │ ├── base_command.rb │ │ ├── report_command.rb │ │ └── todo_list_command.rb │ ├── options.rb │ ├── silencer.rb │ └── status.rb │ ├── code_climate.rb │ ├── code_climate │ ├── code_climate_configuration.rb │ ├── code_climate_configuration.yml │ ├── code_climate_fingerprint.rb │ ├── code_climate_formatter.rb │ └── code_climate_report.rb │ ├── code_comment.rb │ ├── configuration │ ├── app_configuration.rb │ ├── configuration_converter.rb │ ├── configuration_file_finder.rb │ ├── configuration_validator.rb │ ├── default_directive.rb │ ├── directory_directives.rb │ ├── excluded_paths.rb │ ├── rake_task_converter.rb │ ├── schema.rb │ └── schema_validator.rb │ ├── context │ ├── attribute_context.rb │ ├── class_context.rb │ ├── code_context.rb │ ├── ghost_context.rb │ ├── method_context.rb │ ├── module_context.rb │ ├── refinement_context.rb │ ├── root_context.rb │ ├── send_context.rb │ ├── singleton_attribute_context.rb │ ├── singleton_method_context.rb │ ├── statement_counter.rb │ └── visibility_tracker.rb │ ├── context_builder.rb │ ├── detector_repository.rb │ ├── documentation_link.rb │ ├── errors │ ├── bad_detector_configuration_key_in_comment_error.rb │ ├── bad_detector_in_comment_error.rb │ ├── base_error.rb │ ├── config_file_error.rb │ ├── encoding_error.rb │ ├── garbage_detector_configuration_in_comment_error.rb │ ├── incomprehensible_source_error.rb │ ├── legacy_comment_separator_error.rb │ └── syntax_error.rb │ ├── examiner.rb │ ├── logging_error_handler.rb │ ├── rake │ └── task.rb │ ├── report.rb │ ├── report │ ├── base_report.rb │ ├── documentation_link_warning_formatter.rb │ ├── github_report.rb │ ├── heading_formatter.rb │ ├── html_report.rb │ ├── html_report │ │ └── html_report.html.erb │ ├── json_report.rb │ ├── location_formatter.rb │ ├── progress_formatter.rb │ ├── simple_warning_formatter.rb │ ├── text_report.rb │ ├── xml_report.rb │ └── yaml_report.rb │ ├── smell_configuration.rb │ ├── smell_detectors.rb │ ├── smell_detectors │ ├── attribute.rb │ ├── base_detector.rb │ ├── boolean_parameter.rb │ ├── class_variable.rb │ ├── control_parameter.rb │ ├── control_parameter_helpers │ │ ├── call_in_condition_finder.rb │ │ ├── candidate.rb │ │ └── control_parameter_finder.rb │ ├── data_clump.rb │ ├── duplicate_method_call.rb │ ├── feature_envy.rb │ ├── instance_variable_assumption.rb │ ├── irresponsible_module.rb │ ├── long_parameter_list.rb │ ├── long_yield_list.rb │ ├── manual_dispatch.rb │ ├── missing_safe_method.rb │ ├── module_initialize.rb │ ├── nested_iterators.rb │ ├── nil_check.rb │ ├── repeated_conditional.rb │ ├── subclassed_from_core_class.rb │ ├── too_many_constants.rb │ ├── too_many_instance_variables.rb │ ├── too_many_methods.rb │ ├── too_many_statements.rb │ ├── uncommunicative_method_name.rb │ ├── uncommunicative_module_name.rb │ ├── uncommunicative_parameter_name.rb │ ├── uncommunicative_variable_name.rb │ ├── unused_parameters.rb │ ├── unused_private_method.rb │ └── utility_function.rb │ ├── smell_warning.rb │ ├── source │ ├── source_code.rb │ └── source_locator.rb │ ├── spec.rb │ ├── spec │ ├── should_reek.rb │ ├── should_reek_of.rb │ ├── should_reek_only_of.rb │ └── smell_matcher.rb │ ├── tree_dresser.rb │ └── version.rb ├── logo ├── reek.bw.png ├── reek.bw.svg ├── reek.png ├── reek.svg ├── reek.text.png └── reek.text.svg ├── reek.gemspec ├── samples ├── checkstyle.xml ├── clean_source │ └── clean.rb ├── configuration │ ├── accepts_rejects_and_excludes_for_detectors.reek.yml │ ├── accepts_rejects_and_excludes_for_directory_directives.reek.yml │ ├── corrupt.reek │ ├── empty.reek │ ├── full_configuration.reek │ ├── full_mask.reek │ ├── home │ │ └── home.reek.yml │ ├── partial_mask.reek │ ├── regular_configuration │ │ ├── .reek.yml │ │ └── empty_sub_directory │ │ │ └── .gitignore │ └── with_excluded_paths.reek ├── no_config_file │ └── .keep ├── paths.rb ├── smelly_source │ ├── inline.rb │ ├── optparse.rb │ ├── redcloth.rb │ ├── ruby.rb │ └── smelly.rb ├── source_with_exclude_paths │ ├── ignore_me │ │ └── uncommunicative_method_name.rb │ └── nested │ │ ├── ignore_me_as_well │ │ └── irresponsible_module.rb │ │ ├── uncommunicative_parameter_name.rb │ │ └── uncommunicative_variable_name.rb ├── source_with_hidden_directories │ ├── .hidden │ │ └── hidden.rb │ └── not_hidden.rb └── source_with_non_ruby_files │ ├── gibberish │ ├── python_source.py │ └── ruby.rb ├── spec ├── performance │ └── reek │ │ └── smell_detectors │ │ └── runtime_speed_spec.rb ├── quality │ └── reek_source_spec.rb ├── reek │ ├── ast │ │ ├── node_spec.rb │ │ ├── object_refs_spec.rb │ │ ├── reference_collector_spec.rb │ │ └── sexp_extensions_spec.rb │ ├── cli │ │ ├── application_spec.rb │ │ ├── command │ │ │ ├── report_command_spec.rb │ │ │ └── todo_list_command_spec.rb │ │ ├── options_spec.rb │ │ └── silencer_spec.rb │ ├── code_climate │ │ ├── code_climate_configuration_spec.rb │ │ ├── code_climate_fingerprint_spec.rb │ │ ├── code_climate_formatter_spec.rb │ │ └── code_climate_report_spec.rb │ ├── code_comment_spec.rb │ ├── configuration │ │ ├── app_configuration_spec.rb │ │ ├── configuration_file_finder_spec.rb │ │ ├── default_directive_spec.rb │ │ ├── directory_directives_spec.rb │ │ ├── excluded_paths_spec.rb │ │ ├── rake_task_converter_spec.rb │ │ ├── schema_spec.rb │ │ └── schema_validator_spec.rb │ ├── context │ │ ├── code_context_spec.rb │ │ ├── ghost_context_spec.rb │ │ ├── method_context_spec.rb │ │ ├── module_context_spec.rb │ │ ├── root_context_spec.rb │ │ └── statement_counter_spec.rb │ ├── context_builder_spec.rb │ ├── detector_repository_spec.rb │ ├── documentation_link_spec.rb │ ├── errors │ │ └── base_error_spec.rb │ ├── examiner_spec.rb │ ├── logging_error_handler_spec.rb │ ├── rake │ │ └── task_spec.rb │ ├── report │ │ ├── github_report_spec.rb │ │ ├── html_report_spec.rb │ │ ├── json_report_spec.rb │ │ ├── location_formatter_spec.rb │ │ ├── progress_formatter_spec.rb │ │ ├── text_report_spec.rb │ │ ├── xml_report_spec.rb │ │ └── yaml_report_spec.rb │ ├── report_spec.rb │ ├── smell_configuration_spec.rb │ ├── smell_detectors │ │ ├── attribute_spec.rb │ │ ├── base_detector_spec.rb │ │ ├── boolean_parameter_spec.rb │ │ ├── class_variable_spec.rb │ │ ├── control_parameter_spec.rb │ │ ├── data_clump_spec.rb │ │ ├── duplicate_method_call_spec.rb │ │ ├── feature_envy_spec.rb │ │ ├── instance_variable_assumption_spec.rb │ │ ├── irresponsible_module_spec.rb │ │ ├── long_parameter_list_spec.rb │ │ ├── long_yield_list_spec.rb │ │ ├── manual_dispatch_spec.rb │ │ ├── missing_safe_method_spec.rb │ │ ├── module_initialize_spec.rb │ │ ├── nested_iterators_spec.rb │ │ ├── nil_check_spec.rb │ │ ├── repeated_conditional_spec.rb │ │ ├── subclassed_from_core_class_spec.rb │ │ ├── too_many_constants_spec.rb │ │ ├── too_many_instance_variables_spec.rb │ │ ├── too_many_methods_spec.rb │ │ ├── too_many_statements_spec.rb │ │ ├── uncommunicative_method_name_spec.rb │ │ ├── uncommunicative_module_name_spec.rb │ │ ├── uncommunicative_parameter_name_spec.rb │ │ ├── uncommunicative_variable_name_spec.rb │ │ ├── unused_parameters_spec.rb │ │ ├── unused_private_method_spec.rb │ │ └── utility_function_spec.rb │ ├── smell_warning_spec.rb │ ├── source │ │ ├── source_code_spec.rb │ │ └── source_locator_spec.rb │ ├── spec │ │ ├── should_reek_of_spec.rb │ │ ├── should_reek_only_of_spec.rb │ │ ├── should_reek_spec.rb │ │ └── smell_matcher_spec.rb │ └── tree_dresser_spec.rb └── spec_helper.rb └── tasks ├── configuration.rake ├── console.rake ├── reek.rake ├── rubocop.rake └── test.rake /.dockerignore: -------------------------------------------------------------------------------- 1 | # Folders 2 | 3 | .git 4 | .idea 5 | docs 6 | features 7 | logo 8 | pkg 9 | spec 10 | tasks 11 | tmp 12 | 13 | # Files 14 | 15 | .dockerignore 16 | .gitignore 17 | .rubocop.yml 18 | .travis.yml 19 | .yardopts 20 | ataru_setup.rb 21 | CHANGELOG.md 22 | CONTRIBUTING.md 23 | defaults.reek 24 | Dockerfile 25 | Rakefile 26 | README.md 27 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Documentation for all configuration options: 2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "bundler" 7 | directory: "/" 8 | schedule: 9 | interval: "daily" 10 | - package-ecosystem: "github-actions" 11 | directory: "/" 12 | schedule: 13 | interval: "monthly" 14 | -------------------------------------------------------------------------------- /.github/workflows/ruby.yml: -------------------------------------------------------------------------------- 1 | # This workflow will download a prebuilt Ruby version, install dependencies and 2 | # run tests with Rake 3 | # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby 4 | 5 | name: CI 6 | 7 | "on": 8 | push: 9 | branches: [master] 10 | pull_request: 11 | branches: [master] 12 | schedule: 13 | - cron: '16 4 12 * *' 14 | workflow_dispatch: 15 | 16 | env: 17 | CUCUMBER_PUBLISH_QUIET: true 18 | RUBYOPTS: "--disable-did-you-mean" 19 | jobs: 20 | test: 21 | 22 | runs-on: ubuntu-latest 23 | 24 | strategy: 25 | matrix: 26 | ruby: ["3.1", "3.2", "jruby-9.4", "3.3", "3.4"] 27 | 28 | steps: 29 | - uses: actions/checkout@v4 30 | - name: Set up Ruby 31 | uses: ruby/setup-ruby@v1 32 | with: 33 | ruby-version: ${{ matrix.ruby }} 34 | bundler-cache: true 35 | - name: Run specs 36 | run: bundle exec rake test:spec 37 | - name: Run performance tests 38 | run: bundle exec rake test:performance 39 | - name: Update default configuration 40 | run: bundle exec rake configuration:update_default_configuration 41 | - name: Run cucumber features 42 | run: bundle exec rake test:features 43 | - name: Run code quality specs 44 | run: bundle exec rake test:quality 45 | 46 | rubocop: 47 | 48 | runs-on: ubuntu-latest 49 | 50 | steps: 51 | - uses: actions/checkout@v4 52 | - name: Set up Ruby 53 | uses: ruby/setup-ruby@v1 54 | with: 55 | ruby-version: "3.3" 56 | bundler-cache: true 57 | - name: Run RuboCop 58 | run: bundle exec rubocop -P 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pkg 2 | spec/examples.txt 3 | spec/output 4 | tmp 5 | doc 6 | coverage 7 | .yardoc 8 | .bundle 9 | .rspec 10 | .rbx 11 | Gemfile.lock 12 | *.swp 13 | tags 14 | .tags* 15 | .DS_Store 16 | .idea/ 17 | .rbenv-gemsets 18 | -------------------------------------------------------------------------------- /.reek.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troessner/reek/a1d489431c54736c127a3d877d03ae4a5fb5ebcf/.reek.yml -------------------------------------------------------------------------------- /.simplecov: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | SimpleCov.start do 4 | track_files 'lib/**/*.rb' 5 | add_filter 'lib/reek/version.rb' # version.rb is loaded too early to test 6 | add_filter 'lib/reek/cli/options.rb' # tested mostly via integration tests 7 | add_filter 'spec/' 8 | add_filter 'samples/' 9 | coverage_dir 'tmp/coverage' 10 | enable_coverage :branch 11 | end 12 | 13 | SimpleCov.at_exit do 14 | SimpleCov.result.format! 15 | unless RUBY_ENGINE == 'jruby' 16 | SimpleCov.minimum_coverage 98.88 17 | SimpleCov.minimum_coverage_by_file 81.4 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --private 2 | --readme README.md 3 | --load ./docs/yard_plugin.rb 4 | - 5 | *.txt 6 | *.md 7 | docs/*.md 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # CodeClimate specification: https://github.com/codeclimate/spec/blob/master/SPEC.md 2 | # 3 | # Build and run via: 4 | # docker build -t codeclimate/codeclimate-reek . && docker run codeclimate/codeclimate-reek 5 | 6 | FROM ruby:2.6.0-alpine 7 | 8 | MAINTAINER The Reek core team 9 | 10 | ENV code_dir /code 11 | ENV app_dir /usr/src/app 12 | ENV user app 13 | 14 | RUN apk --update add git build-base 15 | ADD . ${app_dir} 16 | 17 | RUN adduser -u 9000 -D ${user} 18 | RUN chown -R ${user}:${user} ${app_dir} 19 | USER ${user} 20 | 21 | WORKDIR ${app_dir} 22 | 23 | RUN gem install rake 24 | RUN bundle install --without debugging development 25 | RUN gem install codeclimate-engine-rb 26 | 27 | VOLUME ${code_dir} 28 | WORKDIR ${code_dir} 29 | 30 | CMD [ "/usr/src/app/bin/code_climate_reek" ] 31 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gemspec 6 | 7 | gem 'aruba', '~> 2.1' 8 | gem 'codeclimate-engine-rb', '~> 0.4.0' 9 | gem 'cucumber', '~> 9.0' 10 | gem 'rake', '~> 13.0' 11 | gem 'rspec', '~> 3.0' 12 | gem 'rspec-benchmark', '~> 0.6.0' 13 | gem 'rubocop', '~> 1.75.0' 14 | gem 'rubocop-performance', '~> 1.25.0' 15 | gem 'rubocop-rspec', '~> 3.6.0' 16 | gem 'simplecov', '~> 0.22.0' 17 | gem 'yard', '~> 0.9.5' 18 | 19 | # Needed for YARD to properly parse GFM code blocks in the documentation 20 | gem 'redcarpet', '~> 3.4', platforms: [:mri] 21 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008,2009 Kevin Rutherford 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/gem_tasks' 4 | require 'rake/clean' 5 | 6 | Dir['tasks/**/*.rake'].each { |t| load t } 7 | 8 | task :ci do 9 | [ 10 | 'test:spec', 11 | 'test:performance', 12 | 'configuration:update_default_configuration', 13 | 'test:features', 14 | :rubocop, 15 | 'test:quality' 16 | ].each do |name| 17 | puts "\n=== Running #{name}...\n" 18 | Rake::Task[name].invoke 19 | puts "\n=== Running #{name} -> Done\n" 20 | end 21 | end 22 | 23 | task default: :ci 24 | -------------------------------------------------------------------------------- /bin/reek: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # Reek examines Ruby source code for smells. 6 | # Visit https://github.com/troessner/reek for docs etc. 7 | # 8 | 9 | require_relative '../lib/reek' 10 | require_relative '../lib/reek/cli/application' 11 | 12 | exit Reek::CLI::Application.new(ARGV).execute 13 | -------------------------------------------------------------------------------- /docs/Attribute.md: -------------------------------------------------------------------------------- 1 | # Attribute 2 | 3 | ## Introduction 4 | 5 | A class that publishes a setter for an instance variable invites 6 | client classes to become too intimate with its inner workings, and in 7 | particular with its representation of state. 8 | 9 | The same holds to a lesser extent for getters, but Reek doesn't flag those. 10 | 11 | ## Example 12 | 13 | Given: 14 | 15 | ```ruby 16 | class Klass 17 | attr_accessor :dummy 18 | end 19 | ``` 20 | 21 | Reek would emit the following warning: 22 | 23 | ``` 24 | reek test.rb 25 | 26 | test.rb -- 1 warning: 27 | [2]:Attribute: Klass#dummy is a writable attribute 28 | ``` 29 | 30 | ## Support in Reek 31 | 32 | This detector raises a warning for every public `attr_writer`, 33 | `attr_accessor`, and `attr` with the writable flag set to `true`. 34 | 35 | Reek does not raise warnings for read-only attributes. 36 | 37 | ## Configuration 38 | 39 | _Attribute_ supports only the [Basic Smell Options](Basic-Smell-Options.md). 40 | -------------------------------------------------------------------------------- /docs/Boolean-Parameter.md: -------------------------------------------------------------------------------- 1 | # Boolean Parameter 2 | 3 | ## Introduction 4 | 5 | _Boolean Parameter_ is a case of [Control Couple](Control-Couple.md), where a 6 | method parameter is defaulted to true or false. A _Boolean Parameter_ 7 | effectively permits a method's caller to decide which execution path to take. 8 | This is a case of bad cohesion. You're creating a dependency between methods 9 | that is not really necessary, thus increasing coupling. 10 | 11 | ## Example 12 | 13 | Given 14 | 15 | ```ruby 16 | class Dummy 17 | def hit_the_switch(switch = true) 18 | if switch 19 | puts 'Hitting the switch' 20 | # do other things... 21 | else 22 | puts 'Not hitting the switch' 23 | # do other things... 24 | end 25 | end 26 | end 27 | ``` 28 | 29 | Reek would emit the following warning: 30 | 31 | ``` 32 | test.rb -- 3 warnings: 33 | [1]:Dummy#hit_the_switch has boolean parameter 'switch' (BooleanParameter) 34 | [2]:Dummy#hit_the_switch is controlled by argument switch (ControlParameter) 35 | ``` 36 | 37 | Note that both smells are reported, _Boolean Parameter_ and _Control Parameter_. 38 | 39 | ## Getting rid of the smell 40 | 41 | This is highly dependent on your exact architecture, but looking at the example above what you could do is: 42 | 43 | * Move everything in the `if` branch into a separate method 44 | * Move everything in the `else` branch into a separate method 45 | * Get rid of the `hit_the_switch` method altogether 46 | * Make the decision what method to call in the initial caller of `hit_the_switch` 47 | 48 | ## Current support in Reek 49 | 50 | Reek can only detect a _Boolean Parameter_ when it has a default initializer like in the example above. 51 | 52 | ## Configuration 53 | 54 | _Boolean Parameter_ supports the [Basic Smell Options](Basic-Smell-Options.md). 55 | -------------------------------------------------------------------------------- /docs/Class-Variable.md: -------------------------------------------------------------------------------- 1 | # Class Variable 2 | 3 | ## Introduction 4 | 5 | Class variables form part of the global runtime state, and as such make it easy for one part of the system to accidentally or inadvertently depend on another part of the system. So the system becomes more prone to problems where changing something over here breaks something over there. In particular, class variables can make it hard to set up tests (because the context of the test includes all global state). 6 | 7 | For a detailed explanation, check out [this article](https://web.archive.org/web/20160714084532/http://4thmouse.com:80/index.php/2011/03/20/why-class-variables-in-ruby-are-a-bad-idea). and [Stackoverflow](https://stackoverflow.com/a/10594849/7798638) 8 | 9 | ## Example 10 | 11 | Given 12 | 13 | ```ruby 14 | class Dummy 15 | @@class_variable = :whatever 16 | end 17 | ``` 18 | 19 | Reek would emit the following warning: 20 | 21 | ``` 22 | reek test.rb 23 | 24 | test.rb -- 1 warning: 25 | [2]:Dummy declares the class variable @@class_variable (ClassVariable) 26 | ``` 27 | 28 | ## Getting rid of the smell 29 | 30 | You can use class-instance variable to mitigate the problem (as also suggested in the linked article above): 31 | 32 | ```ruby 33 | class Dummy 34 | @class_variable = :whatever 35 | end 36 | ``` 37 | 38 | ## Configuration 39 | 40 | _Class Variable_ supports the [Basic Smell Options](Basic-Smell-Options.md). 41 | -------------------------------------------------------------------------------- /docs/Control-Couple.md: -------------------------------------------------------------------------------- 1 | # Control Couple 2 | 3 | ## Introduction 4 | 5 | Control coupling occurs when a method or block checks the value of a parameter 6 | in order to decide which execution path to take. The offending parameter is 7 | often called a _Control Couple_. 8 | 9 | Control Coupling is a kind of duplication, because the calling method already knows which path should be taken. 10 | 11 | Control Coupling reduces the code's flexibility by creating a dependency 12 | between the caller and callee: any change to the possible values of the 13 | controlling parameter must be reflected on both sides of the call. A _Control 14 | Couple_ also reveals a loss of simplicity: the called method probably has more 15 | than one responsibility, because it includes at least two different code paths. 16 | 17 | You can find a good write-up regarding this problem [here](https://solnic.codes/2012/04/11/get-rid-of-that-code-smell-control-couple/). 18 | 19 | ## Current Support in Reek 20 | 21 | Reek performs the following checks that fall in this category: 22 | 23 | * [Control-Parameter](Control-Parameter.md) - a method parameter or block 24 | parameter is the tested value in a conditional statement 25 | * [Boolean-Parameter](Boolean-Parameter.md) - a method parameter is defaulted 26 | to `true` or `false`. 27 | -------------------------------------------------------------------------------- /docs/Control-Parameter.md: -------------------------------------------------------------------------------- 1 | # Control Parameter 2 | 3 | ## Introduction 4 | 5 | _Control Parameter_ is a case of [Control Couple](Control-Couple.md). 6 | 7 | ## Example 8 | 9 | A simple example would be the `quoted` parameter in the following method: 10 | 11 | ```ruby 12 | def write(quoted) 13 | if quoted 14 | write_quoted @value 15 | else 16 | write_unquoted @value 17 | end 18 | end 19 | ``` 20 | 21 | Fixing those problems is out of the scope of this document but an easy solution 22 | could be to remove the `write` method altogether and to move the calls to 23 | `write_quoted` and `write_unquoted` to the caller of `write`. 24 | 25 | ## Current Support in Reek 26 | 27 | Reek warns about _Control Parameter_ when a method parameter or block parameter is 28 | the tested value in a conditional statement. 29 | 30 | ## Configuration 31 | 32 | _Control Parameter_ supports the [Basic Smell Options](Basic-Smell-Options.md). 33 | -------------------------------------------------------------------------------- /docs/Data-Clump.md: -------------------------------------------------------------------------------- 1 | # Data Clump 2 | 3 | ## Introduction 4 | 5 | In general, a _Data Clump_ occurs when the same two or three items frequently 6 | appear together in classes and parameter lists, or when a group of instance 7 | variable names start or end with similar substrings. 8 | 9 | The recurrence of the items often means there is duplicate code spread around to handle them. There may be an abstraction missing from the code, making the system harder to understand. 10 | 11 | ## Example 12 | 13 | Given 14 | 15 | ```ruby 16 | class Dummy 17 | def x(y1,y2); end 18 | def y(y1,y2); end 19 | def z(y1,y2); end 20 | end 21 | ``` 22 | 23 | Reek would emit the following warning: 24 | 25 | ``` 26 | test.rb -- 1 warning: 27 | [2, 3, 4]:Dummy takes parameters [y1, y2] to 3 methods (DataClump) 28 | ``` 29 | 30 | A possible way to fix this problem (quoting from [Martin Fowler](http://martinfowler.com/bliki/DataClump.html)): 31 | 32 | > The first step is to replace data clumps with objects and use the objects whenever you see them. An immediate benefit is that you'll shrink some parameter lists. The interesting stuff happens as you begin to look for behavior to move into the new objects. 33 | 34 | ## Current Support in Reek 35 | 36 | Reek looks for a group of two or more parameters with the same names that are expected by three or more methods of a class. 37 | 38 | ## Configuration 39 | 40 | Reek's _Data Clump_ detector offers the [Basic Smell Options](Basic-Smell-Options.md), plus: 41 | 42 | | Option | Value | Effect | 43 | | -----------------|-------------|---------| 44 | | `max_copies` | integer | The maximum number of methods that are permitted to take the same group of parameters. Defaults to 2. | 45 | | `min_clump_size` | integer | The smallest number of parameters that can be reported as a clump. Defaults to 2. | 46 | 47 | -------------------------------------------------------------------------------- /docs/Irresponsible-Module.md: -------------------------------------------------------------------------------- 1 | # Irresponsible Module 2 | 3 | ## Introduction 4 | 5 | Classes and modules are the units of reuse and release. It is therefore 6 | considered good practice to annotate every class and module with a brief 7 | comment outlining its responsibilities. 8 | 9 | For further guideline on how to write good documentation in Ruby, see these 10 | links: 11 | - [Rails API documentation guidelines](http://edgeguides.rubyonrails.org/api_documentation_guidelines.html) 12 | - [Comments tell you why](https://blog.codinghorror.com/code-tells-you-how-comments-tell-you-why/) 13 | 14 | ## Example 15 | 16 | Given 17 | 18 | ```ruby 19 | class Dummy 20 | # Do things... 21 | end 22 | ``` 23 | 24 | Reek would emit the following warning: 25 | 26 | ``` 27 | test.rb -- 1 warning: 28 | [1]:IrresponsibleModule: Dummy has no descriptive comment 29 | ``` 30 | 31 | Fixing this is simple - just an explaining comment: 32 | 33 | ```ruby 34 | # The Dummy class is responsible for ... 35 | class Dummy 36 | # Do things... 37 | end 38 | ``` 39 | 40 | ## Current Support in Reek 41 | 42 | _Irresponsible Module_ checks classes and modules, including those 43 | created through `Struct.new` and `Class.new` and directly assigned to a constant. 44 | 45 | ## Configuration 46 | 47 | _Irresponsible Module_ supports only the [Basic Smell Options](Basic-Smell-Options.md). 48 | -------------------------------------------------------------------------------- /docs/Large-Class.md: -------------------------------------------------------------------------------- 1 | # Large Class 2 | 3 | ## Introduction 4 | 5 | A _Large Class_ is a class or module that has a large number of instance 6 | variables, methods or lines of code in any one piece of its specification. 7 | (That is, this smell relates to pieces of the class's specification, not to the 8 | size of the corresponding instance of `Class`.) 9 | 10 | ## Current Support in Reek 11 | 12 | Reek offers three checks in this category. 13 | 14 | * [Too Many Constants](Too-Many-Constants.md) 15 | * [Too Many Instance Variables](Too-Many-Instance-Variables.md) 16 | * [Too Many Methods](Too-Many-Methods.md) 17 | -------------------------------------------------------------------------------- /docs/Long-Parameter-List.md: -------------------------------------------------------------------------------- 1 | # Long Parameter List 2 | 3 | ## Introduction 4 | 5 | A _Long Parameter List_ occurs when a method has a lot of parameters. 6 | 7 | ## Example 8 | 9 | Given 10 | 11 | ```ruby 12 | class Dummy 13 | def long_list(foo,bar,baz,fling,flung) 14 | puts foo,bar,baz,fling,flung 15 | end 16 | end 17 | ``` 18 | 19 | Reek would report the following warning: 20 | 21 | ``` 22 | test.rb -- 1 warning: 23 | [2]:Dummy#long_list has 5 parameters (LongParameterList) 24 | ``` 25 | 26 | A common solution to this problem would be the introduction of parameter objects. 27 | 28 | ## Current Support in Reek 29 | 30 | _Long Parameter List_ reports any method or block with more than 3 parameters. 31 | 32 | ## Configuration 33 | 34 | Reek's _Long Parameter List_ detector supports the 35 | [Basic Smell Options](Basic-Smell-Options.md), plus: 36 | 37 | | Option | Value | Effect | 38 | | -------------|---------|---------| 39 | | `max_params` | integer | The maximum number of parameters allowed in a method or block before a warning is issued. Defaults to 3. | 40 | -------------------------------------------------------------------------------- /docs/Long-Yield-List.md: -------------------------------------------------------------------------------- 1 | # Long Yield List 2 | 3 | ## Introduction 4 | 5 | A _Long Yield List_ occurs when a method yields a lot of arguments to the block 6 | it gets passed. It is a special case of [Long Parameter List](Long-Parameter-List.md). 7 | 8 | ## Example 9 | 10 | ```ruby 11 | class Dummy 12 | def yields_a_lot(foo,bar,baz,fling,flung) 13 | yield foo,bar,baz,fling,flung 14 | end 15 | end 16 | ``` 17 | 18 | Reek would report the following warning: 19 | 20 | ``` 21 | test.rb -- 1 warning: 22 | [4]:Dummy#yields_a_lot yields 5 parameters (LongYieldList) 23 | ``` 24 | 25 | A common solution to this problem would be the introduction of parameter objects. 26 | 27 | ## Current Support in Reek 28 | 29 | Currently _Long Yield List_ reports any method or block with more than 3 parameters. 30 | 31 | ## Configuration 32 | 33 | Reek's _Long Yield List_ detector supports the [Basic Smell Options](Basic-Smell-Options.md), plus: 34 | 35 | | Option | Value | Effect | 36 | | -------------|---------|---------| 37 | | `max_params` | integer | The maximum number of parameters allowed in a method or block before a warning is issued. Defaults to 3. | 38 | -------------------------------------------------------------------------------- /docs/Manual-Dispatch.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | Reek reports a _Manual Dispatch_ smell if it finds source code that manually checks whether an object responds to a method before that method is called. Manual dispatch is a type of [Simulated Polymorphism](Simulated-Polymorphism.md) which leads to code that is harder to reason about, debug, and refactor. 4 | 5 | ## Example 6 | 7 | ```ruby 8 | class MyManualDispatcher 9 | attr_reader :foo 10 | 11 | def initialize(foo) 12 | @foo = foo 13 | end 14 | 15 | def call 16 | foo.bar if foo.respond_to?(:bar) 17 | end 18 | end 19 | ``` 20 | 21 | Reek would emit the following warning: 22 | 23 | ``` 24 | test.rb -- 1 warning: 25 | [9]: MyManualDispatcher manually dispatches method call (ManualDispatch) 26 | ``` 27 | 28 | ## Configuration 29 | 30 | _Manual Dispatch_ offers the [Basic Smell Options](Basic-Smell-Options.md). 31 | -------------------------------------------------------------------------------- /docs/Module-Initialize.md: -------------------------------------------------------------------------------- 1 | # Module Initialize 2 | 3 | ## Introduction 4 | 5 | A module is usually a mixin, so when an `#initialize` method is present it is 6 | hard to tell initialization order and parameters so having `#initialize` 7 | in a module is usually a bad idea. 8 | 9 | ## Example 10 | 11 | The `Foo` module below contains a method `initialize`. Although class `B` inherits from `A`, the inclusion of `Foo` stops `A#initialize` from being called. 12 | 13 | ```ruby 14 | class A 15 | def initialize(a) 16 | @a = a 17 | end 18 | end 19 | 20 | module Foo 21 | def initialize(foo) 22 | @foo = foo 23 | end 24 | end 25 | 26 | class B < A 27 | include Foo 28 | 29 | def initialize(b) 30 | super('bar') 31 | @b = b 32 | end 33 | end 34 | ``` 35 | 36 | A simple solution is to rename `Foo#initialize` and call that method by name: 37 | 38 | ```ruby 39 | module Foo 40 | def setup_foo_module(foo) 41 | @foo = foo 42 | end 43 | end 44 | 45 | class B < A 46 | include Foo 47 | 48 | def initialize(b) 49 | super 'bar' 50 | setup_foo_module('foo') 51 | @b = b 52 | end 53 | end 54 | ``` 55 | 56 | ## Current Support in Reek 57 | 58 | Reek warns about module initialize when an instance method named `initialize` is present in a module. 59 | 60 | ## Configuration 61 | 62 | Module Initialize supports the [Basic Smell Options](Basic-Smell-Options.md). 63 | -------------------------------------------------------------------------------- /docs/Nested-Iterators.md: -------------------------------------------------------------------------------- 1 | # Nested Iterators 2 | 3 | ## Introduction 4 | 5 | A _Nested Iterator_ occurs when a block contains another block. 6 | 7 | ## Example 8 | 9 | Given 10 | 11 | ```ruby 12 | class Duck 13 | class << self 14 | def duck_names 15 | %i!tick trick track!.each do |surname| 16 | %i!duck!.each do |last_name| 17 | puts "full name is #{surname} #{last_name}" 18 | end 19 | end 20 | end 21 | end 22 | end 23 | ``` 24 | 25 | Reek would report the following warning: 26 | 27 | ``` 28 | test.rb -- 1 warning: 29 | [5]:Duck#duck_names contains iterators nested 2 deep (NestedIterators) 30 | ``` 31 | 32 | ## Current Support in Reek 33 | 34 | _Nested Iterators_ reports failing methods only once. 35 | `Object#tap` is ignored by default and thus does not count as iterator. 36 | Furthermore iterators without block arguments are not counted, e.g.: 37 | 38 | 39 | ```ruby 40 | def foo 41 | before do 42 | item.each do |part| 43 | puts part 44 | end 45 | end 46 | end 47 | ``` 48 | 49 | would not smell of NestedIterators (given a maximum allowed nesting of 1) since the 50 | `before` would not be counted (because it doesn't pass any arguments to the block). 51 | 52 | ## Configuration 53 | 54 | _Nested Iterators_ offers the [Basic Smell Options](Basic-Smell-Options.md), plus: 55 | 56 | | Option | Value | Effect | 57 | | ----------------------|---------|---------| 58 | | `max_allowed_nesting` | integer | The maximum depth of nested iterators. Defaults to 1 | 59 | | `ignore_iterators` | Array | List of iterators to be excluded from the smell check. Includes only `tap` at the moment| 60 | -------------------------------------------------------------------------------- /docs/Nil-Check.md: -------------------------------------------------------------------------------- 1 | # Nil Check 2 | 3 | ## Introduction 4 | 5 | A _Nil Check_ is a type check. Failures of _Nil Check_ violate the 6 | ["tell, don't ask"](http://robots.thoughtbot.com/tell-dont-ask) principle. 7 | Additionally to that, type checks often mask bigger problems in your source 8 | code like not using OOP and / or polymorphism when you should. 9 | 10 | The _Nil Check_ code smell is a case of [Simulated Polymorphism](Simulated-Polymorphism.md). 11 | 12 | ## Example 13 | 14 | Given 15 | 16 | ```ruby 17 | class Klass 18 | def nil_checker(argument) 19 | if argument.nil? 20 | puts "argument is nil!" 21 | end 22 | end 23 | end 24 | ``` 25 | 26 | Reek would emit the following warning: 27 | 28 | ``` 29 | test.rb -- 1 warning: 30 | [3]:Klass#nil_checker performs a nil-check. (NilCheck) 31 | ``` 32 | 33 | ## Current Support in Reek 34 | 35 | _Nil Check_ reports use of 36 | 37 | * .nil? method 38 | * == and === operators when checking vs. nil 39 | * case statements that use syntax like when nil 40 | 41 | _Nil Check_ allows use of 42 | 43 | * the safe navigation operator like `foo&.bar` 44 | 45 | ## Configuration 46 | 47 | _Nil Check_ offers the [Basic Smell Options](Basic-Smell-Options.md). 48 | -------------------------------------------------------------------------------- /docs/Rake-Task.md: -------------------------------------------------------------------------------- 1 | # Rake Task 2 | 3 | ## Introduction 4 | 5 | Reek provides a Rake task that runs Reek on a set of source files. In its most simple form you just include something like that in your Rakefile: 6 | 7 | ```ruby 8 | require 'reek/rake/task' 9 | 10 | Reek::Rake::Task.new do |t| 11 | t.fail_on_error = false 12 | end 13 | ``` 14 | 15 | In its most simple form, that's it. 16 | 17 | When you now run: 18 | 19 | ```bash 20 | rake -T 21 | ``` 22 | 23 | you should see 24 | 25 | ```bash 26 | rake reek # Check for code smells 27 | ``` 28 | 29 | ## Configuration via task 30 | 31 | An more sophisticated rake task that would make use of all available configuration options could look like this: 32 | 33 | ```ruby 34 | Reek::Rake::Task.new do |t| 35 | t.name = 'custom_rake' # Whatever name you want. Defaults to "reek". 36 | t.config_file = 'config/.reek.yml' # Defaults to nothing. 37 | t.source_files = 'vendor/**/*.rb' # Glob pattern to match source files. Defaults to lib/**/*.rb 38 | t.reek_opts = '-U' # Defaults to ''. You can pass all the options here in that are shown by "reek -h" 39 | t.fail_on_error = false # Defaults to true 40 | t.verbose = true # Defaults to false 41 | end 42 | ``` 43 | 44 | Alternatively, you can create your own [Rake::FileList](http://rake.rubyforge.org/classes/Rake/FileList.html) and use that for `source_files`: 45 | 46 | ```ruby 47 | Reek::Rake::Task.new do |t| 48 | t.source_files = FileList['lib/**/*.rb'].exclude('lib/templates/**/*.rb') 49 | end 50 | ``` 51 | 52 | ## Configuration via environment variables 53 | 54 | You can overwrite the following attributes by environment variables: 55 | 56 | - "reek_opts" by using REEK_OPTS 57 | - "config_file" by using REEK_CFG 58 | - "source_files" by using REEK_SRC 59 | 60 | An example rake call using environment variables could look like this: 61 | 62 | ```bash 63 | REEK_CFG="config/custom.reek" REEK_OPTS="-s" rake reek 64 | ``` 65 | 66 | See also: [Reek-Driven-Development](Reek-Driven-Development.md) 67 | -------------------------------------------------------------------------------- /docs/Reek-Driven-Development.md: -------------------------------------------------------------------------------- 1 | # Reek-Driven Development 2 | 3 | One way to drive quality into your code from the very beginning of a project is to run Reek as a part of your testing process. 4 | 5 | ## Rake: `Reek::Rake::Task` 6 | 7 | You can add a [Rake Task] to your Rakefile, which will run Reek on all your source files. 8 | 9 | ```ruby 10 | require 'reek/rake/task' 11 | 12 | Reek::Rake::Task.new do |t| 13 | t.fail_on_error = true 14 | t.verbose = false 15 | t.source_files = 'lib/**/*.rb' 16 | end 17 | ``` 18 | 19 | Now, `rake reek` will run Reek on your source code. And, in this case, it fails if it finds any smells. 20 | 21 | For more detailed information about Reek's integration with Rake, see [Rake Task]. 22 | 23 | [Rake Task]: Rake-Task.md 24 | 25 | ## RSpec: `reek/spec` 26 | 27 | You can add Reek expectations directly into your RSpec specs. 28 | 29 | This example is from Reek's own source code: 30 | 31 | ```ruby 32 | require 'reek/spec' 33 | 34 | it 'contains no code smells' do 35 | Pathname.glob('lib/**/*.rb').each do |file| 36 | expect(file).not_to reek 37 | end 38 | end 39 | ``` 40 | 41 | By requiring [`reek/spec`] you gain access to the `reek` matcher. 42 | 43 | The `reek` matcher returns true if and only if Reek finds smells in your code. If the test fails, the matcher produces an error message that includes details of all the smells it found. 44 | 45 | [`reek/spec`]: ../lib/reek/spec.rb 46 | 47 | -------------------------------------------------------------------------------- /docs/Repeated-Conditional.md: -------------------------------------------------------------------------------- 1 | # Repeated Conditional 2 | 3 | ## Introduction 4 | 5 | _Repeated Conditional_ is a case of 6 | [Simulated Polymorphism](Simulated-Polymorphism.md). Basically it means you are 7 | checking the same value throughout a single class and take decisions based on 8 | this. 9 | 10 | ## Example 11 | 12 | Given 13 | 14 | ```ruby 15 | class RepeatedConditionals 16 | attr_accessor :switch 17 | 18 | def repeat_1 19 | puts "Repeat 1!" if switch 20 | end 21 | 22 | def repeat_2 23 | puts "Repeat 2!" if switch 24 | end 25 | 26 | def repeat_3 27 | puts "Repeat 3!" if switch 28 | end 29 | end 30 | ``` 31 | 32 | Reek would emit the following warning: 33 | 34 | ``` 35 | test.rb -- 4 warnings: 36 | [5, 9, 13]:RepeatedConditionals tests switch at least 3 times (RepeatedConditional) 37 | ``` 38 | 39 | If you get this warning then you are probably not using the right abstraction or even more probable, missing an additional abstraction. 40 | 41 | ## Configuration 42 | 43 | Reek's _Repeated Conditional_ detector offers the [Basic Smell Options](Basic-Smell-Options.md), plus: 44 | 45 | | Option | Value | Effect | 46 | | ---------------|-------------|---------| 47 | | `max_ifs` | integer | The maximum number of identical conditional tests permitted before Reek raises a warning. Defaults to 2. | 48 | -------------------------------------------------------------------------------- /docs/Simulated-Polymorphism.md: -------------------------------------------------------------------------------- 1 | # Simulated Polymorphism 2 | 3 | ## Introduction 4 | 5 | Simulated Polymorphism occurs when 6 | 7 | * code uses a case statement (especially on a type field); 8 | * or code has several if statements in a row (especially if they're comparing against the same value); 9 | * or code uses instance_of?, kind_of?, is_a?, or === to decide what type it's working with; 10 | * or multiple conditionals in different places test the same value. 11 | 12 | Conditional code is hard to read and understand, because the reader must hold more state in their head. When the same value is tested in multiple places throughout an application, any change to the set of possible values will require many methods and classes to change. Tests for the type of an object may indicate that the abstraction represented by that type is not completely defined (or understood). 13 | 14 | ## Current Support in Reek 15 | 16 | Reek checks for [Manual Dispatch](Manual-Dispatch.md), [Repeated Conditional](Repeated-Conditional.md) and for [Nil Check](Nil-Check.md). 17 | -------------------------------------------------------------------------------- /docs/Style-Guide.md: -------------------------------------------------------------------------------- 1 | # Style guide 2 | 3 | ## Instance variables and getters / setters 4 | 5 | We use instance vars only: 6 | 7 | - in the constructor 8 | - for [memoization](http://gavinmiller.io/2013/basics-of-ruby-memoization/) 9 | 10 | For everything else we use proper getters / setters. 11 | 12 | If possible those should be private. 13 | 14 | ## Data types 15 | 16 | - Class or module names that are carried around in hashes and configuration and what 17 | not should be designated by constants. So `DuplicateMethodCall`, not `:DuplicateMethodCall` or `"DuplicateMethodCall"` 18 | - Hash keys should be all symbols unless they designate classes / modules - see above. 19 | - Everything else like messages or parameters in smell warnings should be strings, nothing else. 20 | -------------------------------------------------------------------------------- /docs/Subclassed-From-Core-Class.md: -------------------------------------------------------------------------------- 1 | # Subclassed From Core Class 2 | 3 | ## Introduction 4 | 5 | Candidate classes for the _Subclassed From Core Class_ smell are classes which inherit from Core Classes like Hash, String and Array. 6 | 7 | Inheriting from Core Classes means that you are going to have a bad time debugging (the explanation below is taken from [here](http://words.steveklabnik.com/beware-subclassing-ruby-core-classes)): 8 | 9 | > What do you think this code should do? 10 | 11 | ```ruby 12 | List = Class.new(Array) 13 | 14 | l = List.new 15 | l << 1 16 | l << 2 17 | puts l.reverse.class # => Array 18 | ``` 19 | 20 | > If you said “it prints Array” you’d be right. 21 | > Let’s talk about a more pernicious issue: Strings. 22 | 23 | ```ruby 24 | class MyString < String 25 | def to_s 26 | "lol" 27 | end 28 | end 29 | 30 | s = MyString.new 31 | s.concat "Hey" 32 | 33 | puts s # => Hey 34 | puts s.to_s # => lol 35 | puts "#{s}" # => Hey 36 | ``` 37 | 38 | > That’s right! With Strings, Ruby doesn’t call #to_s: it puts the value in directly. 39 | > Generally speaking, subclassing isn’t the right idea here. 40 | 41 | ## Example 42 | 43 | Given 44 | 45 | ```ruby 46 | class Ary < Array 47 | end 48 | 49 | class Str < String 50 | end 51 | ``` 52 | 53 | Reek would report the _Subclassed From Core Class_ smell for both classes. Instead of subclassing them you want a data structure that uses one of these core classes internally, but isn’t exactly like one. For instance: 54 | 55 | ```ruby 56 | require 'forwardable' 57 | 58 | class List 59 | extend Forwardable 60 | def_delegators :@list, :<<, :length # and anything else 61 | 62 | def initialize(list = []) 63 | @list = list 64 | end 65 | 66 | def reverse 67 | List.new(@list.reverse) 68 | end 69 | end 70 | 71 | l = List.new 72 | l << 1 73 | l << 2 74 | puts l.reverse.class # => List 75 | ``` 76 | 77 | ## Configuration 78 | 79 | _Subclassed From Core Class_ offers the [Basic Smell Options](Basic-Smell-Options.md). 80 | -------------------------------------------------------------------------------- /docs/Too-Many-Constants.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | _Too Many Constants_ is a case of [Large Class](Large-Class.md). 4 | 5 | ## Example 6 | 7 | Given this configuration 8 | 9 | ```yaml 10 | TooManyConstants: 11 | max_constants: 3 12 | ``` 13 | 14 | and this code: 15 | 16 | ```ruby 17 | class Smelly 18 | CONST_1 = :dummy 19 | CONST_2 = :dummy 20 | CONST_3 = :dummy 21 | CONST_4 = :dummy 22 | end 23 | ``` 24 | 25 | Reek would emit the following warning: 26 | 27 | ``` 28 | test.rb -- 1 warning: 29 | [1]:TooManyConstants: Smelly has 4 constants 30 | ``` 31 | ## Configuration 32 | 33 | Reek's _Too Many Constants_ detector offers the [Basic Smell Options](Basic-Smell-Options.md), plus: 34 | 35 | | Option | Value | Effect | 36 | | -------------------------|---------|---------| 37 | | `max_constants` | integer | The maximum number of constants that are permitted. Defaults to 5 | 38 | -------------------------------------------------------------------------------- /docs/Too-Many-Instance-Variables.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | _Too Many Instance Variables_ is a case of [Large Class](Large-Class.md). 4 | 5 | ## Example 6 | 7 | Given this configuration 8 | 9 | ```yaml 10 | TooManyInstanceVariables: 11 | max_instance_variables: 3 12 | ``` 13 | 14 | and this code: 15 | 16 | ```ruby 17 | class Smelly 18 | def initialize 19 | @arg_1 = :dummy 20 | @arg_2 = :dummy 21 | @arg_3 = :dummy 22 | @arg_4 = :dummy 23 | end 24 | end 25 | ``` 26 | 27 | Reek would emit the following warning: 28 | 29 | ``` 30 | test.rb -- 5 warnings: 31 | [1]:TooManyInstanceVariables: Smelly has at least 4 instance variables 32 | ``` 33 | ## Current Support in Reek 34 | 35 | Reek only counts the instance variables you use explicitly like in the example above. Class macros like `attr_accessor` are disregarded. 36 | 37 | ## Configuration 38 | 39 | Reek's _Too Many Instance Variables_ detector offers the [Basic Smell Options](Basic-Smell-Options.md), plus: 40 | 41 | | Option | Value | Effect | 42 | | -------------------------|---------|---------| 43 | | `max_instance_variables` | integer | The maximum number of instance variables that are permitted. Defaults to 4 | 44 | -------------------------------------------------------------------------------- /docs/Too-Many-Methods.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | _Too Many Methods_ is a case of [Large Class](Large-Class.md). 4 | 5 | ## Example 6 | 7 | Given this configuration 8 | 9 | ```yaml 10 | TooManyMethods: 11 | max_methods: 3 12 | ``` 13 | 14 | and this code: 15 | 16 | ```ruby 17 | class Smelly 18 | def one; end 19 | def two; end 20 | def three; end 21 | def four; end 22 | end 23 | ``` 24 | 25 | Reek would emit the following warning: 26 | 27 | ``` 28 | test.rb -- 1 warning: 29 | [1]:TooManyMethods: Smelly has at least 4 methods 30 | ``` 31 | ## Current Support in Reek 32 | 33 | Reek counts all the methods it can find in a class — instance *and* class 34 | methods. So given `max_methods` from above is 4, this: 35 | 36 | ```ruby 37 | class Smelly 38 | class << self 39 | def one; end 40 | def two; end 41 | end 42 | 43 | def three; end 44 | def four; end 45 | end 46 | ``` 47 | 48 | would cause Reek to emit the same warning as in the example above. 49 | 50 | ## Configuration 51 | 52 | Reek's _Too Many Methods_ detector offers the [Basic Smell Options](Basic-Smell-Options.md), plus: 53 | 54 | | Option | Value | Effect | 55 | | --------------|---------|---------| 56 | | `max_methods` | integer | The maximum number of methods that are permitted. Defaults to 15 | 57 | -------------------------------------------------------------------------------- /docs/Uncommunicative-Name.md: -------------------------------------------------------------------------------- 1 | # Uncommunicative Name 2 | 3 | ## Introduction 4 | 5 | An _Uncommunicative Name_ is a name that doesn't communicate its intent well enough. 6 | 7 | Poor names make it hard for the reader to build a mental picture of what's 8 | going on in the code. They can also be mis-interpreted; and they hurt the flow 9 | of reading, because the reader must slow down to interpret the names. 10 | 11 | ## Current Support in Reek 12 | 13 | Reek offers four checks in this category: 14 | 15 | * [Uncommunicative Method Name](Uncommunicative-Method-Name.md) 16 | * [Uncommunicative Module Name](Uncommunicative-Module-Name.md) 17 | * [Uncommunicative Parameter Name](Uncommunicative-Parameter-Name.md) 18 | * [Uncommunicative Variable Name](Uncommunicative-Variable-Name.md) 19 | -------------------------------------------------------------------------------- /docs/Unused-Parameters.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | _Unused Parameter_ refers to methods with parameters that are unused in scope of the method. 4 | 5 | Having unused parameters in a method is code smell because leaving dead code in 6 | a method can never improve the method and it makes the code confusing to read. 7 | 8 | ## Example 9 | 10 | Given: 11 | 12 | ```ruby 13 | class Klass 14 | def unused_parameters(x,y,z) 15 | puts x,y # but not z 16 | end 17 | end 18 | ``` 19 | 20 | Reek would emit the following warning: 21 | 22 | ``` 23 | [2]:UnusedParameters: Klass#unused_parameters has unused parameter 'z' 24 | ``` 25 | 26 | ## Configuration 27 | 28 | _Unused Parameter_ offers the [Basic Smell Options](Basic-Smell-Options.md). 29 | -------------------------------------------------------------------------------- /docs/Utility-Function.md: -------------------------------------------------------------------------------- 1 | # Utility Function 2 | 3 | ## Introduction 4 | 5 | A _Utility Function_ is any instance method that has no dependency on the state of the instance. 6 | 7 | _Utility Function_ is heavily related to _[Feature Envy](Feature-Envy.md)_, please check out the explanation there why _Utility Function_ is something you should care about. 8 | 9 | ## Example 10 | 11 | Given 12 | 13 | ```ruby 14 | class UtilityFunction 15 | def showcase(argument) 16 | argument.to_s + argument.to_i 17 | end 18 | end 19 | ``` 20 | 21 | Reek would report: 22 | 23 | ``` 24 | test.rb -- 2 warnings: 25 | [2]:UtilityFunction#showcase doesn't depend on instance state (UtilityFunction) 26 | ``` 27 | 28 | ## Current Support in Reek 29 | 30 | _Utility Function_ will warn about any method that: 31 | 32 | * is non-empty 33 | * does not override an inherited method 34 | * calls at least one method on another object 35 | * doesn't use any of self's instance variables 36 | * doesn't use any of self's methods 37 | 38 | ## Differences to _Feature Envy_ 39 | 40 | _[Feature Envy](Feature-Envy.md)_ is only triggered if there are some references to self and _Utility Function_ is triggered if there are no references to self. 41 | 42 | ## Configuration 43 | 44 | Reek's _Utility Function_ detector supports the [Basic Smell Options](Basic-Smell-Options.md), plus: 45 | 46 | | Option | Value | Effect | 47 | | ----------------------|-------------|---------| 48 | | `public_methods_only` | Boolean | Disable this smell detector for non-public methods (which means "private" and "protected") | 49 | 50 | A sample configuration file would look like this: 51 | 52 | ```yaml 53 | --- 54 | detectors: 55 | UtilityFunction: 56 | public_methods_only: true 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/Versioning-Policy.md: -------------------------------------------------------------------------------- 1 | # Versioning Policy 2 | 3 | * CLI interface: Adding options is a non-breaking change, and would warrant an update of the minor version. Removing options is a breaking change and requires a major version update (we did this going to Reek 2). Adding a report format probably also warrants a minor version upgrade. 4 | * API: We haven't really defined a 'public' API for using Reek programmatically, and we've only just started testing it. So, this is basically a blank slate at the moment. We will work on this as a part of the Reek 3 release. 5 | * List of detected smells: Adding a smell warrants a minor release, removing a smell is a breaking change. This makes sense if you consider that the CLI allows running a single smell detector. 6 | * Consistency of detected smells: This is very hard to guarantee. If we fix a bug in one of the detectors, some fragrant code may become smelly, or vice versa. Right now we don't bother with this. 7 | * Smell configuration: The detectors are quite tolerant regarding configuration options that they don't recognize, so we regard any change here as only requiring a minor release. 8 | -------------------------------------------------------------------------------- /docs/templates/default/docstring/html/public_api_marker.erb: -------------------------------------------------------------------------------- 1 |
2 |

This <%= object.type %> is part of Reek's public API.

3 |
4 | -------------------------------------------------------------------------------- /docs/templates/default/docstring/setup.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | def init 4 | super 5 | return unless show_api_marker_section? 6 | 7 | if sections.first 8 | sections.first.place(:api_marker).before(:private) 9 | else 10 | sections :index, [:api_marker] 11 | end 12 | end 13 | 14 | def api_marker 15 | return if object.type == :root 16 | 17 | erb(:private) unless ['public', 'private'].include? api_text 18 | end 19 | 20 | private 21 | 22 | def api_text 23 | api_text = object.has_tag?(:api) && object.tag(:api).text 24 | api_text = 'public' if object.has_tag?(:public) 25 | api_text 26 | end 27 | 28 | def show_api_marker_section? 29 | return false if object.type == :root 30 | 31 | case api_text 32 | when 'public', 'private' 33 | false 34 | else 35 | true 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /docs/templates/default/fulldoc/html/css/common.css: -------------------------------------------------------------------------------- 1 | .note.public { background: #c5ffc5; border-color: #aaecaa; } 2 | -------------------------------------------------------------------------------- /docs/yard_plugin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'yard' 4 | 5 | # Template helper to modify processing of links in HTML generated from our 6 | # markdown files. 7 | module LocalLinkHelper 8 | # Rewrites links to (assumed local) markdown files so they're processed as 9 | # {file: } directives. 10 | def resolve_links(text) 11 | text = text.gsub(%r{([^<]*)}, '{file:\1 \2}') 12 | super 13 | end 14 | end 15 | 16 | YARD::Templates::Template.extra_includes << LocalLinkHelper 17 | YARD::Tags::Library.define_tag('Guaranteed public API', :public) 18 | YARD::Tags::Library.define_tag('Code quality configuration', :quality) 19 | YARD::Templates::Engine.register_template_path File.join(File.dirname(__FILE__), 'templates') 20 | -------------------------------------------------------------------------------- /engine.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Reek", 3 | "description": "Reek is a tool that examines Ruby classes, modules and methods and reports any code smells it finds.", 4 | "maintainer": { 5 | "name": "Matijs van Zuijlen, Piotr Szotkowski, Timo Rößner", 6 | "email": "timo.roessner@googlemail.com" 7 | }, 8 | "languages" : ["Ruby"], 9 | "version": "0.1", 10 | "spec_version": "0.0.1" 11 | } 12 | -------------------------------------------------------------------------------- /features/command_line_interface/basic_usage.feature: -------------------------------------------------------------------------------- 1 | Feature: The Reek CLI maintains backwards compatibility 2 | In order to use Reek without fuss 3 | As a developer 4 | I want to have a stable basic command line interface 5 | 6 | Scenario: the example from README reports as expected 7 | Given the smelly file 'smelly.rb' 8 | When I run reek smelly.rb 9 | Then the exit status indicates smells 10 | And it reports: 11 | """ 12 | smelly.rb -- 2 warnings: 13 | [4]:UncommunicativeMethodName: Smelly#x has the name 'x' 14 | [5]:UncommunicativeVariableName: Smelly#x has the variable name 'y' 15 | """ 16 | -------------------------------------------------------------------------------- /features/command_line_interface/show_progress.feature: -------------------------------------------------------------------------------- 1 | Feature: Show progress 2 | In order to see the progress of the examiners 3 | As a developer 4 | I want to be able to selectively activate progress reporting 5 | 6 | # Note that --progress is the default on TTYs, but needs to be explicitly 7 | # enabled here because output in the cucumber scenarios does not go to a TTY. 8 | Scenario: shows progress output on mixed files by default 9 | Given a directory called 'mixed_files' containing some clean and smelly files 10 | When I run reek --progress mixed_files 11 | Then the exit status indicates smells 12 | And it reports: 13 | """ 14 | Inspecting 2 file(s): 15 | .S 16 | 17 | mixed_files/dirty.rb -- 2 warnings: 18 | [4]:UncommunicativeMethodName: Smelly#x has the name 'x' 19 | [5]:UncommunicativeVariableName: Smelly#x has the variable name 'y' 20 | 2 total warnings 21 | """ 22 | 23 | Scenario: --no-progress disables progress output 24 | Given a directory called 'mixed_files' containing some clean and smelly files 25 | When I run reek --no-progress mixed_files 26 | Then the exit status indicates smells 27 | And it reports: 28 | """ 29 | mixed_files/dirty.rb -- 2 warnings: 30 | [4]:UncommunicativeMethodName: Smelly#x has the name 'x' 31 | [5]:UncommunicativeVariableName: Smelly#x has the variable name 'y' 32 | 2 total warnings 33 | """ 34 | -------------------------------------------------------------------------------- /features/command_line_interface/smell_selection.feature: -------------------------------------------------------------------------------- 1 | Feature: Smell selection 2 | In order to focus on particular code smells 3 | As a developer 4 | I want to be able to selectively activate smell detectors 5 | 6 | Scenario: --smell selects a smell to detect 7 | Given the smelly file 'smelly.rb' 8 | And a configuration file 'partial_mask.reek' 9 | When I run reek --no-line-numbers --smell UncommunicativeVariableName smelly.rb 10 | Then the exit status indicates smells 11 | And it reports: 12 | """ 13 | smelly.rb -- 1 warning: 14 | UncommunicativeVariableName: Smelly#x has the variable name 'y' 15 | """ 16 | -------------------------------------------------------------------------------- /features/command_line_interface/smells_count.feature: -------------------------------------------------------------------------------- 1 | Feature: Reports total number of code smells 2 | In order to monitor the total number of smells 3 | Reek outputs the total number of smells among all files inspected. 4 | 5 | Scenario: Does not output total number of smells when inspecting single file 6 | Given the smelly file 'smelly.rb' 7 | When I run reek smelly.rb 8 | Then the exit status indicates smells 9 | And it reports: 10 | """ 11 | smelly.rb -- 2 warnings: 12 | [4]:UncommunicativeMethodName: Smelly#x has the name 'x' 13 | [5]:UncommunicativeVariableName: Smelly#x has the variable name 'y' 14 | """ 15 | 16 | Scenario: Output total number of smells when inspecting multiple files 17 | Given a directory called 'smelly' containing two smelly files 18 | When I run reek smelly 19 | Then the exit status indicates smells 20 | And it reports: 21 | """ 22 | smelly/dirty_one.rb -- 2 warnings: 23 | [4]:UncommunicativeMethodName: Smelly#x has the name 'x' 24 | [5]:UncommunicativeVariableName: Smelly#x has the variable name 'y' 25 | smelly/dirty_two.rb -- 2 warnings: 26 | [4]:UncommunicativeMethodName: Smelly#x has the name 'x' 27 | [5]:UncommunicativeVariableName: Smelly#x has the variable name 'y' 28 | 4 total warnings 29 | """ 30 | 31 | Scenario: Output total number of smells even if total equals 0 32 | Given a directory called 'clean' containing two clean files 33 | When I run reek clean 34 | Then it succeeds 35 | And it reports: 36 | """ 37 | 0 total warnings 38 | """ 39 | -------------------------------------------------------------------------------- /features/configuration_files/exclude_directives.feature: -------------------------------------------------------------------------------- 1 | Feature: Exclude directives 2 | In order to have a more fine-grained control over what Reek reports 3 | As a user 4 | I want to be able to exclude specific contexts from reporting 5 | 6 | Scenario: Exclude multiple contexts 7 | Given a file named "config.reek" with: 8 | """ 9 | --- 10 | detectors: 11 | UncommunicativeMethodName: 12 | exclude: 13 | - "Smelly#x" 14 | UnusedPrivateMethod: 15 | enabled: true 16 | exclude: 17 | - "Smelly#foobar" 18 | """ 19 | And a file named "smelly.rb" with: 20 | """ 21 | class Smelly 22 | # Should report IrresponsibleModule 23 | def foo(arg); end # Should report UnusedParameter 24 | def x; end # Should not report UncommunicativeMethodName 25 | private 26 | def foobar; end # Should not report UnusedPrivateMethod 27 | end 28 | """ 29 | When I run reek -c config.reek smelly.rb 30 | Then it reports: 31 | """ 32 | smelly.rb -- 2 warnings: 33 | [1]:IrresponsibleModule: Smelly has no descriptive comment 34 | [3]:UnusedParameters: Smelly#foo has unused parameter 'arg' 35 | """ 36 | -------------------------------------------------------------------------------- /features/configuration_files/exclude_paths_directives.feature: -------------------------------------------------------------------------------- 1 | Feature: Exclude paths directives 2 | In order to avoid Reek wasting time on files that cannot be fixed 3 | As a user 4 | I want to be able to exclude specific paths from being checked 5 | 6 | Scenario: Exclude paths 7 | Given the smelly file "smelly.rb" in the directory "smelly_sources" 8 | And the smelly file "smelly.rb" in the directory "smelly_as_well" 9 | And the smelly file "smelly.rb" in the directory "smelly_as_well_2" 10 | When I run reek . 11 | Then the exit status indicates smells 12 | Given a file named "config.reek" with: 13 | """ 14 | --- 15 | exclude_paths: 16 | - smelly_sources/smelly.rb 17 | - smelly_as_well/ 18 | - smelly_as_well_2 19 | """ 20 | When I run reek -c config.reek . 21 | Then it succeeds 22 | And it reports nothing 23 | 24 | Scenario: Using a file name within an excluded directory 25 | Given a file named "bad_files_live_here/smelly.rb" with: 26 | """ 27 | # A smelly example class 28 | class Smelly 29 | def alfa(bravo); end 30 | end 31 | """ 32 | And a file named "config.reek" with: 33 | """ 34 | --- 35 | exclude_paths: 36 | - bad_files_live_here 37 | """ 38 | When I run reek -c config.reek bad_files_live_here/smelly.rb 39 | Then the exit status indicates smells 40 | When I run reek -c config.reek --force-exclusion bad_files_live_here/smelly.rb 41 | Then it succeeds 42 | And it reports nothing 43 | -------------------------------------------------------------------------------- /features/configuration_files/schema_validation.feature: -------------------------------------------------------------------------------- 1 | Feature: Validate schema 2 | In order to ensure that I am using the right configuration 3 | As a user 4 | I want to be notified when I am using a configuration that violates our schema 5 | 6 | Scenario: Our generated default configuration 7 | Given our default configuration file 8 | And the clean file "clean.rb" 9 | When I run reek -c defaults.reek.yml clean.rb 10 | Then it succeeds 11 | And it reports nothing 12 | 13 | Scenario: Detectors, directories and exclude paths all mixed 14 | Given a file named "config.reek" with: 15 | """ 16 | --- 17 | detectors: 18 | IrresponsibleModule: 19 | enabled: false 20 | NestedIterators: 21 | exclude: 22 | - "MyWorker#self.class_method" 23 | - "AnotherWorker#instance_method" 24 | DataClump: 25 | max_copies: 3 26 | min_clump_size: 3 27 | 28 | directories: 29 | "web_app/app/controllers": 30 | NestedIterators: 31 | enabled: false 32 | "web_app/app/helpers": 33 | UtilityFunction: 34 | enabled: false 35 | 36 | exclude_paths: 37 | - lib/legacy 38 | """ 39 | And a directory named "lib/legacy" 40 | And the clean file "clean.rb" 41 | When I run reek -c config.reek clean.rb 42 | Then it succeeds 43 | And it reports nothing 44 | 45 | Scenario: Invalid detector name 46 | Given a file named "config.reek" with: 47 | """ 48 | --- 49 | detectors: 50 | DoesNotExist: 51 | enabled: true 52 | """ 53 | And the clean file "clean.rb" 54 | When I run reek -c config.reek clean.rb 55 | Then the exit status indicates an error 56 | And stderr reports: 57 | """ 58 | Error: Invalid configuration file config.reek, error is 59 | [/detectors/DoesNotExist/enabled] is not allowed. 60 | """ 61 | -------------------------------------------------------------------------------- /features/configuration_files/show_configuration_file.feature: -------------------------------------------------------------------------------- 1 | Feature: Show configuration file 2 | With Reeks dynamic mechanism of finding a configuration file you might run into a situation where you are not 3 | 100% sure what configuration file Reek is using. E.g. you have a project specific configuration file in your 4 | project root and also another Reek configuration in your HOME directory that you use for all your other projects 5 | and for whatever reasons Reek seems to be using another configuration file than the one you assumed it would. 6 | In this case you can pass the flag `--show-configuration-path` to Reek which will cause Reek to output the path 7 | to the configuration file it is using. 8 | 9 | Scenario: Default configuration file present 10 | Given the clean file "clean.rb" 11 | And an empty file named ".reek.yml" 12 | When I run reek --show-configuration-path clean.rb 13 | Then it reports: 14 | """ 15 | Using '.reek.yml' as configuration file. 16 | """ 17 | 18 | Scenario: Non-default configuration file passed via CLI 19 | Given the clean file "clean.rb" 20 | And an empty file named "config.reek" 21 | When I run reek --show-configuration-path -c config.reek clean.rb 22 | Then it reports: 23 | """ 24 | Using 'config.reek' as configuration file. 25 | """ 26 | 27 | Scenario: Display the right configuration file even when there are multiple files present 28 | Given the clean file "clean.rb" 29 | And an empty file named ".reek.yml" 30 | And an empty file named "config.reek" 31 | When I run reek --show-configuration-path -c config.reek clean.rb 32 | Then it reports: 33 | """ 34 | Using 'config.reek' as configuration file. 35 | """ 36 | 37 | Scenario: Use configuration file we find when traversing up the directory tree 38 | Given the clean file "clean.rb" 39 | And with a configuration file that is further up in the directory tree 40 | When I run reek --show-configuration-path clean.rb 41 | Then it reports: 42 | """ 43 | Using '../../.reek.yml' as configuration file. 44 | """ 45 | -------------------------------------------------------------------------------- /features/locales.feature: -------------------------------------------------------------------------------- 1 | Feature: Handling different locales 2 | In order to work in a variety of environments 3 | As a developer 4 | I want Reek to work properly in any locale 5 | 6 | Scenario: Running Reek in an UTF-8 locale 7 | Given I set the environment variable "LANG" to "en_US.utf8" 8 | And a file "konnichiwa.rb" with: 9 | """ 10 | puts 'こんにちは世界' 11 | """ 12 | When I run reek -V konnichiwa.rb 13 | Then it succeeds 14 | And it reports no errors 15 | And it reports: 16 | """ 17 | konnichiwa.rb -- 0 warnings 18 | """ 19 | 20 | Scenario: Running Reek in the POSIX locale 21 | Given I set the environment variable "LANG" to "POSIX" 22 | And a file "konnichiwa.rb" with: 23 | """ 24 | puts 'こんにちは世界' 25 | """ 26 | When I run reek -V konnichiwa.rb 27 | Then it succeeds 28 | And it reports no errors 29 | And it reports: 30 | """ 31 | konnichiwa.rb -- 0 warnings 32 | """ 33 | -------------------------------------------------------------------------------- /features/programmatic_access.feature: -------------------------------------------------------------------------------- 1 | Feature: Using Reek programmatically 2 | In order to use Reek from inside my program 3 | As a developer 4 | I want to be able to use its classes 5 | 6 | Scenario: Accessing smells found by an examiner 7 | Given the smelly file 'smelly.rb' 8 | And a file named "examine.rb" with: 9 | """ 10 | require 'reek' 11 | examiner = Reek::Examiner.new(File.new('smelly.rb')) 12 | examiner.smells.each do |smell| 13 | puts smell.message 14 | end 15 | """ 16 | When I run `ruby examine.rb` 17 | Then it reports no errors 18 | And it reports: 19 | """ 20 | has the name 'x' 21 | has the variable name 'y' 22 | """ 23 | 24 | Scenario: Using Reek's built-in report classes 25 | Given the smelly file 'smelly.rb' 26 | And a file named "examine.rb" with: 27 | """ 28 | require 'reek' 29 | examiner = Reek::Examiner.new(File.new('smelly.rb')) 30 | report = Reek::Report::TextReport.new 31 | report.add_examiner examiner 32 | report.show 33 | """ 34 | When I run `ruby examine.rb` 35 | Then it reports no errors 36 | And it reports: 37 | """ 38 | smelly.rb -- 2 warnings: 39 | UncommunicativeMethodName: Smelly#x has the name 'x' 40 | UncommunicativeVariableName: Smelly#x has the variable name 'y' 41 | """ 42 | -------------------------------------------------------------------------------- /features/reports/yaml.feature: -------------------------------------------------------------------------------- 1 | Feature: Report smells using simple YAML layout 2 | In order to parse Reek's output simply and consistently, simply 3 | output a list of smells in Yaml. 4 | 5 | Scenario: output is empty when there are no smells 6 | Given a directory called 'clean' containing two clean files 7 | When I run reek --format yaml clean 8 | Then it succeeds 9 | And it reports this yaml: 10 | """ 11 | --- [] 12 | """ 13 | 14 | Scenario: Indicate smells and print them as yaml when using files 15 | Given the smelly file 'smelly.rb' 16 | When I run reek --format yaml smelly.rb 17 | Then the exit status indicates smells 18 | And it reports this yaml: 19 | """ 20 | --- 21 | - context: Smelly#x 22 | lines: 23 | - 4 24 | message: has the name 'x' 25 | smell_type: UncommunicativeMethodName 26 | source: smelly.rb 27 | name: x 28 | documentation_link: https://github.com/troessner/reek/blob/v6.5.0/docs/Uncommunicative-Method-Name.md 29 | - context: Smelly#x 30 | lines: 31 | - 5 32 | message: has the variable name 'y' 33 | smell_type: UncommunicativeVariableName 34 | source: smelly.rb 35 | name: y 36 | documentation_link: https://github.com/troessner/reek/blob/v6.5.0/docs/Uncommunicative-Variable-Name.md 37 | """ 38 | 39 | Scenario: Indicate smells and print them as yaml when using STDIN 40 | When I pass "class Turn; end" to reek --format yaml 41 | Then the exit status indicates smells 42 | And it reports this yaml: 43 | """ 44 | --- 45 | - smell_type: IrresponsibleModule 46 | source: "STDIN" 47 | context: Turn 48 | lines: 49 | - 1 50 | message: has no descriptive comment 51 | documentation_link: https://github.com/troessner/reek/blob/v6.5.0/docs/Irresponsible-Module.md 52 | """ 53 | -------------------------------------------------------------------------------- /features/rspec_matcher.feature: -------------------------------------------------------------------------------- 1 | Feature: Use reek_of matcher 2 | As a developer 3 | In order to check code quality as part of my spec suite 4 | I want to assert that my code doesn't smell 5 | 6 | Background: 7 | Given the smelly file 'smelly.rb' 8 | And a file "reek_spec.rb" with: 9 | """ 10 | require 'reek' 11 | require 'reek/spec' 12 | 13 | describe 'smelly.rb' do 14 | it 'is clean' do 15 | expect(Pathname.new('smelly.rb')).not_to reek 16 | end 17 | end 18 | """ 19 | 20 | Scenario: Failing on a smelly file 21 | When I run `rspec reek_spec.rb` 22 | Then stdout should contain: 23 | """ 24 | Failure/Error: expect(Pathname.new('smelly.rb')).not_to reek 25 | """ 26 | 27 | Scenario: Masking smells with a configuration file 28 | Given a file named ".reek.yml" with: 29 | """ 30 | --- 31 | detectors: 32 | UncommunicativeMethodName: 33 | enabled: false 34 | UncommunicativeVariableName: 35 | enabled: false 36 | """ 37 | When I run `rspec reek_spec.rb` 38 | Then stdout should contain: 39 | """ 40 | 1 example, 0 failures 41 | """ 42 | -------------------------------------------------------------------------------- /features/step_definitions/.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: ../../.rubocop.yml 2 | 3 | # Allow common When syntax 4 | Lint/AmbiguousRegexpLiteral: 5 | Enabled: false 6 | -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../lib/reek' 4 | require_relative '../../lib/reek/cli/application' 5 | require 'aruba/cucumber' 6 | 7 | # 8 | # Provides runner methods used in the cucumber steps. 9 | # 10 | class ReekWorld 11 | def reek(args) 12 | run_command_and_stop("reek --no-color --no-documentation #{args}", fail_on_error: false) 13 | end 14 | 15 | def reek_with_pipe(stdin, args) 16 | run_command "reek --no-color --no-documentation #{args}" 17 | type(stdin) 18 | close_input 19 | end 20 | end 21 | 22 | World do 23 | ReekWorld.new 24 | end 25 | 26 | Before do 27 | Aruba.configure do |config| 28 | config.exit_timeout = 30 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/reek.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Reek's core functionality 5 | # 6 | require_relative 'reek/version' 7 | require_relative 'reek/examiner' 8 | require_relative 'reek/report' 9 | 10 | module Reek 11 | DEFAULT_SMELL_CONFIGURATION = File.join(__dir__, '../docs/defaults.reek.yml').freeze 12 | DEFAULT_CONFIGURATION_FILE_NAME = '.reek.yml' 13 | DETECTORS_KEY = 'detectors' 14 | EXCLUDE_PATHS_KEY = 'exclude_paths' 15 | DIRECTORIES_KEY = 'directories' 16 | end 17 | -------------------------------------------------------------------------------- /lib/reek/ast/ast_node_class_map.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'node' 4 | require_relative 'sexp_extensions' 5 | 6 | module Reek 7 | module AST 8 | # Maps AST node types to sublasses of ASTNode extended with the relevant 9 | # utility modules. 10 | # 11 | class ASTNodeClassMap 12 | def initialize 13 | @klass_map = {} 14 | end 15 | 16 | def klass_for(type) 17 | klass_map[type] ||= Class.new(Node).tap do |klass| 18 | extension = extension_map[type] 19 | # TODO: map node type to constant directly. 20 | klass.send :include, extension if extension 21 | end 22 | end 23 | 24 | def extension_map 25 | @extension_map ||= 26 | begin 27 | assoc = SexpExtensions.constants.map do |const| 28 | [ 29 | const.to_s.sub(/Node$/, '').downcase.to_sym, 30 | SexpExtensions.const_get(const) 31 | ] 32 | end 33 | assoc.to_h 34 | end 35 | end 36 | 37 | private 38 | 39 | attr_reader :klass_map 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/reek/ast/builder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Reek 4 | module AST 5 | # 6 | # An AST Builder for ruby parser. 7 | # 8 | class Builder < ::Parser::Builders::Default 9 | # This is a work around for parsing ruby code that has a string with invalid sequence as UTF-8. 10 | # See. https://github.com/whitequark/parser/issues/283 11 | def string_value(token) 12 | value(token) 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/reek/ast/object_refs.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Reek 4 | # Represents functionality related to an Abstract Syntax Tree. 5 | module AST 6 | # 7 | # ObjectRefs is used in CodeContexts. 8 | # It manages and counts the references out of a method to other objects and to `self`. 9 | # 10 | # E.g. this code: 11 | # def foo(thing) 12 | # bar.call_me 13 | # bar.maybe(thing.wat) 14 | # end 15 | # 16 | # would make "@refs" below look like this after the TreeWalker has done his job: 17 | # { 18 | # :self=>[2, 3], # `bar.call_me` and `bar.maybe` count as refs to `self` in line 2 and 3 19 | # :thing=>[3] # `thing.wat` in `bar.maybe()` counts as one reference to `thing` 20 | # } 21 | # 22 | class ObjectRefs 23 | def initialize 24 | @refs = Hash.new { |refs, name| refs[name] = [] } 25 | end 26 | 27 | # Records the references a given method in a CodeContext has including 28 | # `self` (see the example at the beginning of this file). 29 | # 30 | # @param name [Symbol] The name of the object that the method references or `self`. 31 | # @param line [Int] The line number where this reference occurs. 32 | # 33 | # @return [Int|nil] The line number that was added (which might be nil). 34 | def record_reference(name:, line: nil) 35 | refs[name] << line 36 | end 37 | 38 | # @return [Hash] The most popular references. 39 | # E.g. for 40 | # { foo: [2], self: [2,3], bar: [3,4] } 41 | # this would return 42 | # { self: [2,3], bar: [3,4] } 43 | def most_popular 44 | max = refs.values.map(&:size).max 45 | refs.select { |_name, refs| refs.size == max } 46 | end 47 | 48 | def references_to(name) 49 | refs[name] 50 | end 51 | 52 | def self_is_max? 53 | refs.empty? || most_popular.key?(:self) 54 | end 55 | 56 | private 57 | 58 | attr_reader :refs 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/reek/ast/reference_collector.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Reek 4 | module AST 5 | # 6 | # Locates references to the current object within a portion 7 | # of an abstract syntax tree. 8 | # 9 | class ReferenceCollector 10 | def initialize(ast) 11 | @ast = ast 12 | end 13 | 14 | def num_refs_to_self 15 | (explicit_self_calls.to_a + implicit_self_calls.to_a).size 16 | end 17 | 18 | private 19 | 20 | attr_reader :ast 21 | 22 | def explicit_self_calls 23 | ast.each_node([:self, :super, :zsuper, :ivar, :ivasgn]) 24 | end 25 | 26 | def implicit_self_calls 27 | ast.each_node(:send).reject(&:receiver) 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/reek/ast/sexp_extensions.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'reference_collector' 4 | 5 | require_relative 'sexp_extensions/arguments' 6 | require_relative 'sexp_extensions/begin' 7 | require_relative 'sexp_extensions/block' 8 | require_relative 'sexp_extensions/case' 9 | require_relative 'sexp_extensions/constant' 10 | require_relative 'sexp_extensions/if' 11 | require_relative 'sexp_extensions/lambda' 12 | require_relative 'sexp_extensions/logical_operators' 13 | require_relative 'sexp_extensions/methods' 14 | require_relative 'sexp_extensions/module' 15 | require_relative 'sexp_extensions/nested_assignables' 16 | require_relative 'sexp_extensions/self' 17 | require_relative 'sexp_extensions/send' 18 | require_relative 'sexp_extensions/super' 19 | require_relative 'sexp_extensions/symbols' 20 | require_relative 'sexp_extensions/variables' 21 | require_relative 'sexp_extensions/when' 22 | require_relative 'sexp_extensions/yield' 23 | -------------------------------------------------------------------------------- /lib/reek/ast/sexp_extensions/begin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Reek 4 | module AST 5 | module SexpExtensions 6 | # Utility methods for :begin nodes. 7 | module BeginNode 8 | # The special type :begin exists primarily to contain more statements. 9 | # Therefore, this method overrides the default implementation to return 10 | # this node's children. 11 | def statements 12 | children 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/reek/ast/sexp_extensions/block.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Reek 4 | module AST 5 | module SexpExtensions 6 | # Utility methods for :block nodes. 7 | module BlockNode 8 | def call 9 | children.first 10 | end 11 | 12 | def args 13 | children[1] 14 | end 15 | 16 | def block 17 | children[2] 18 | end 19 | 20 | def parameters 21 | children[1] || [] 22 | end 23 | 24 | def parameter_names 25 | parameters.children 26 | end 27 | 28 | def simple_name 29 | :block 30 | end 31 | 32 | def without_block_arguments? 33 | args.components.empty? 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/reek/ast/sexp_extensions/case.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Reek 4 | module AST 5 | module SexpExtensions 6 | # Utility methods for :case nodes. 7 | module CaseNode 8 | def condition 9 | children.first 10 | end 11 | 12 | def body_nodes(type, ignoring = []) 13 | children[1..].compact.flat_map do |child| 14 | child.each_node(type, ignoring | type).to_a 15 | end 16 | end 17 | 18 | def else_body 19 | children.last 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/reek/ast/sexp_extensions/constant.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Reek 4 | module AST 5 | module SexpExtensions 6 | # Utility methods for :const nodes. 7 | module ConstNode 8 | # TODO: name -> full_name, simple_name -> name 9 | def name 10 | if namespace 11 | "#{namespace.format_to_ruby}::#{simple_name}" 12 | else 13 | simple_name.to_s 14 | end 15 | end 16 | 17 | def simple_name 18 | children.last 19 | end 20 | 21 | def namespace 22 | children.first 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/reek/ast/sexp_extensions/if.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Reek 4 | module AST 5 | module SexpExtensions 6 | # Utility methods for :if nodes. 7 | module IfNode 8 | # 9 | # @return [Reek::AST::Node] the condition that is associated with a conditional node. 10 | # For instance, this code 11 | # 12 | # if charlie(bravo) then delta end 13 | # 14 | # would be parsed into this AST: 15 | # 16 | # s(:if, 17 | # s(:send, nil, :charlie, 18 | # s(:lvar, :bravo)), 19 | # s(:send, nil, :delta), nil) 20 | # 21 | # so in this case we would return this 22 | # 23 | # s(:send, nil, :charlie, 24 | # s(:lvar, :bravo)) 25 | # 26 | # as condition. 27 | # 28 | def condition 29 | children.first 30 | end 31 | 32 | # @quality :reek:FeatureEnvy 33 | def body_nodes(type, ignoring = []) 34 | children[1..].compact.flat_map do |child| 35 | if ignoring.include? child.type 36 | [] 37 | else 38 | child.each_node(type, ignoring | type).to_a 39 | end 40 | end 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/reek/ast/sexp_extensions/lambda.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Reek 4 | module AST 5 | module SexpExtensions 6 | # Utility methods for :lambda nodes. 7 | module LambdaNode 8 | def name 9 | 'lambda' 10 | end 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/reek/ast/sexp_extensions/logical_operators.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Reek 4 | module AST 5 | module SexpExtensions 6 | # Base module for utility methods for :and and :or nodes. 7 | module LogicOperatorBase 8 | def condition 9 | children.first 10 | end 11 | 12 | def body_nodes(type, ignoring = []) 13 | children[1].each_node type, (ignoring | type) 14 | end 15 | end 16 | 17 | # Utility methods for :and nodes. 18 | module AndNode 19 | include LogicOperatorBase 20 | end 21 | 22 | # Utility methods for :or nodes. 23 | module OrNode 24 | include LogicOperatorBase 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/reek/ast/sexp_extensions/nested_assignables.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Reek 4 | module AST 5 | module SexpExtensions 6 | # Base module for utility methods for nodes that can contain argument 7 | # nodes nested through :mlhs nodes. 8 | module NestedAssignables 9 | def components 10 | children.flat_map(&:components) 11 | end 12 | end 13 | 14 | # Utility methods for :args nodes. 15 | module ArgsNode 16 | include NestedAssignables 17 | end 18 | 19 | # Utility methods for :mlhs nodes. 20 | module MlhsNode 21 | include NestedAssignables 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/reek/ast/sexp_extensions/self.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Reek 4 | module AST 5 | module SexpExtensions 6 | # Utility methods for :self nodes. 7 | module SelfNode 8 | def name 9 | :self 10 | end 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/reek/ast/sexp_extensions/send.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Reek 4 | module AST 5 | module SexpExtensions 6 | # Utility methods for :send nodes. 7 | module SendNode 8 | ATTR_DEFN_METHODS = [:attr_writer, :attr_accessor].freeze 9 | 10 | def receiver 11 | children.first 12 | end 13 | 14 | def name 15 | children[1] 16 | end 17 | 18 | def args 19 | children[2..] 20 | end 21 | 22 | def participants 23 | ([receiver] + args).compact 24 | end 25 | 26 | def module_creation_call? 27 | return true if object_creation_call? && module_creation_receiver? 28 | return true if data_definition_call? && data_definition_receiver? 29 | 30 | false 31 | end 32 | 33 | def object_creation_call? 34 | name == :new 35 | end 36 | 37 | def attribute_writer? 38 | ATTR_DEFN_METHODS.include?(name) || 39 | attr_with_writable_flag? 40 | end 41 | 42 | # Handles the case where we create an attribute writer via: 43 | # attr :foo, true 44 | def attr_with_writable_flag? 45 | name == :attr && args.any? && args.last.type == :true 46 | end 47 | 48 | private 49 | 50 | def module_creation_receiver? 51 | const_receiver? && [:Class, :Struct].include?(receiver.simple_name) 52 | end 53 | 54 | def data_definition_call? 55 | name == :define 56 | end 57 | 58 | def data_definition_receiver? 59 | const_receiver? && receiver.simple_name == :Data 60 | end 61 | 62 | def const_receiver? 63 | receiver && receiver.type == :const 64 | end 65 | end 66 | 67 | Op_AsgnNode = SendNode 68 | CSendNode = SendNode 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /lib/reek/ast/sexp_extensions/super.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Reek 4 | module AST 5 | module SexpExtensions 6 | # Utility methods for :super nodes. 7 | module SuperNode 8 | def name 9 | :super 10 | end 11 | end 12 | 13 | ZsuperNode = SuperNode 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/reek/ast/sexp_extensions/symbols.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Reek 4 | module AST 5 | module SexpExtensions 6 | # Utility methods for :sym nodes. 7 | module SymNode 8 | def name 9 | children.first 10 | end 11 | 12 | def full_name(outer) 13 | "#{outer}##{name}" 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/reek/ast/sexp_extensions/variables.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Reek 4 | module AST 5 | module SexpExtensions 6 | # Base module for utility methods for nodes representing variables. 7 | module VariableBase 8 | def name 9 | children.first 10 | end 11 | end 12 | 13 | # Utility methods for :cvar nodes. 14 | module CvarNode 15 | include VariableBase 16 | end 17 | 18 | # Utility methods for :ivar nodes. 19 | module IvarNode 20 | include VariableBase 21 | end 22 | 23 | # Utility methods for :ivasgn nodes. 24 | module IvasgnNode 25 | include VariableBase 26 | end 27 | 28 | # Utility methods for :lvar nodes. 29 | module LvarNode 30 | include VariableBase 31 | 32 | alias var_name name 33 | end 34 | 35 | # Utility methods for :gvar nodes. 36 | module GvarNode 37 | include VariableBase 38 | end 39 | 40 | LvasgnNode = LvarNode 41 | CvasgnNode = CvarNode 42 | CvdeclNode = CvarNode 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/reek/ast/sexp_extensions/when.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Reek 4 | module AST 5 | module SexpExtensions 6 | # Utility methods for :when nodes. 7 | module WhenNode 8 | def condition_list 9 | children[0..-2] 10 | end 11 | 12 | def body 13 | children.last 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/reek/ast/sexp_extensions/yield.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Reek 4 | module AST 5 | module SexpExtensions 6 | # Utility methods for :yield nodes. 7 | module YieldNode 8 | def args 9 | children 10 | end 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/reek/cli/command/base_command.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Reek 4 | module CLI 5 | module Command 6 | # 7 | # Base class for all commands 8 | # 9 | class BaseCommand 10 | def initialize(options:, sources:, configuration:) 11 | @options = options 12 | @sources = sources 13 | @configuration = configuration 14 | end 15 | 16 | private 17 | 18 | attr_reader :options, :sources, :configuration 19 | 20 | def smell_names 21 | @smell_names ||= options.smells_to_detect 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/reek/cli/silencer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'stringio' 4 | 5 | module Reek 6 | module CLI 7 | # CLI silencer 8 | module Silencer 9 | module_function 10 | 11 | # @quality :reek:TooManyStatements { max_statements: 9 } 12 | def silently 13 | old_verbose = $VERBOSE 14 | old_stderr = $stderr 15 | old_stdout = $stdout 16 | 17 | $VERBOSE = false 18 | $stderr = StringIO.new 19 | $stdout = StringIO.new 20 | yield 21 | ensure 22 | $VERBOSE = old_verbose 23 | $stderr = old_stderr 24 | $stdout = old_stdout 25 | end 26 | 27 | def without_warnings 28 | old_verbose = $VERBOSE 29 | $VERBOSE = false 30 | yield 31 | ensure 32 | $VERBOSE = old_verbose 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/reek/cli/status.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Reek 4 | module CLI 5 | module Status 6 | DEFAULT_SUCCESS_EXIT_CODE = 0 7 | DEFAULT_ERROR_EXIT_CODE = 1 8 | DEFAULT_FAILURE_EXIT_CODE = 2 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/reek/code_climate.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'code_climate/code_climate_fingerprint' 4 | require_relative 'code_climate/code_climate_formatter' 5 | require_relative 'code_climate/code_climate_report' 6 | -------------------------------------------------------------------------------- /lib/reek/code_climate/code_climate_configuration.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Reek 4 | module CodeClimate 5 | # loads the smell type metadata to present in Code Climate 6 | module CodeClimateConfiguration 7 | def self.load 8 | config_file = File.expand_path('code_climate_configuration.yml', __dir__) 9 | YAML.load_file config_file 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/reek/code_climate/code_climate_fingerprint.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'digest' 4 | 5 | module Reek 6 | module CodeClimate 7 | # Generates a string to uniquely identify a smell 8 | class CodeClimateFingerprint 9 | NON_IDENTIFYING_PARAMETERS = [:count, :depth].freeze 10 | 11 | def initialize(warning) 12 | @warning = warning 13 | end 14 | 15 | def compute 16 | return unless warning_uniquely_identifiable? 17 | 18 | identify_warning 19 | 20 | identifying_aspects.hexdigest.freeze 21 | end 22 | 23 | private 24 | 25 | attr_reader :warning 26 | 27 | def identify_warning 28 | identifying_aspects << warning.source 29 | identifying_aspects << warning.smell_type 30 | identifying_aspects << warning.context 31 | identifying_aspects << parameters 32 | end 33 | 34 | def identifying_aspects 35 | @identifying_aspects ||= Digest::MD5.new 36 | end 37 | 38 | def parameters 39 | warning.parameters.except(*NON_IDENTIFYING_PARAMETERS).sort.to_s 40 | end 41 | 42 | def warning_uniquely_identifiable? 43 | # These could be identifiable if they had parameters 44 | ![ 45 | 'ManualDispatch', 46 | 'NilCheck' 47 | ].include?(warning.smell_type) 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/reek/code_climate/code_climate_formatter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'codeclimate_engine' 4 | require_relative 'code_climate_configuration' 5 | 6 | module Reek 7 | module CodeClimate 8 | # Generates a hash in the structure specified by the Code Climate engine spec 9 | class CodeClimateFormatter 10 | def initialize(warning) 11 | @warning = warning 12 | end 13 | 14 | def render 15 | CCEngine::Issue.new(check_name: check_name, 16 | description: description, 17 | categories: categories, 18 | location: location, 19 | remediation_points: remediation_points, 20 | fingerprint: fingerprint, 21 | content: content).render 22 | end 23 | 24 | private 25 | 26 | attr_reader :warning 27 | 28 | def description 29 | [warning.context, warning.message].join(' ') 30 | end 31 | 32 | def check_name 33 | warning.smell_type 34 | end 35 | 36 | def categories 37 | ['Complexity'] 38 | end 39 | 40 | def location 41 | warning_lines = warning.lines 42 | CCEngine::Location::LineRange.new( 43 | path: warning.source, 44 | line_range: warning_lines.first..warning_lines.last) 45 | end 46 | 47 | def remediation_points 48 | configuration[warning.smell_type].fetch('remediation_points') 49 | end 50 | 51 | def fingerprint 52 | CodeClimateFingerprint.new(warning).compute 53 | end 54 | 55 | def content 56 | configuration[warning.smell_type].fetch('content') 57 | end 58 | 59 | def configuration 60 | @configuration ||= CodeClimateConfiguration.load 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/reek/code_climate/code_climate_report.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../report/base_report' 4 | require_relative 'code_climate_formatter' 5 | 6 | module Reek 7 | module CodeClimate 8 | # 9 | # Displays a list of smells in Code Climate engine format 10 | # (https://github.com/codeclimate/spec/blob/master/SPEC.md) 11 | # JSON with empty array for 0 smells 12 | # 13 | class CodeClimateReport < Report::BaseReport 14 | def show(out = $stdout) 15 | smells.map do |smell| 16 | out.print CodeClimateFormatter.new(smell).render 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/reek/configuration/configuration_validator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../errors/config_file_error' 4 | 5 | module Reek 6 | module Configuration 7 | # 8 | # Configuration validator module. 9 | # 10 | module ConfigurationValidator 11 | private 12 | 13 | # @quality :reek:UtilityFunction 14 | def smell_type?(key) 15 | Reek::SmellDetectors.const_defined? key 16 | rescue NameError 17 | false 18 | end 19 | 20 | # @quality :reek:UtilityFunction 21 | def key_to_smell_detector(key) 22 | Reek::SmellDetectors.const_get key 23 | end 24 | 25 | def with_valid_directory(path) 26 | directory = Pathname.new path.to_s.chomp('/') 27 | if directory.file? 28 | raise Errors::ConfigFileError, 29 | "`#{directory}` is supposed to be a directory but is a file" 30 | end 31 | yield directory if block_given? 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/reek/configuration/default_directive.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'configuration_validator' 4 | 5 | module Reek 6 | module Configuration 7 | # 8 | # Hash extension for the default directive. 9 | # 10 | module DefaultDirective 11 | include ConfigurationValidator 12 | 13 | # Adds the configuration for detectors as default directive. 14 | # 15 | # @param detectors_configuration [Hash] the configuration e.g.: 16 | # { 17 | # :IrresponsibleModule => {:enabled=>false}, 18 | # :Attribute => {:enabled=>true} 19 | # } 20 | # 21 | # @return [self] 22 | def add(detectors_configuration) 23 | detectors_configuration.each do |name, configuration| 24 | detector = key_to_smell_detector(name) 25 | self[detector] = (self[detector] || {}).merge configuration 26 | end 27 | self 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/reek/configuration/excluded_paths.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'configuration_validator' 4 | require_relative '../errors/config_file_error' 5 | 6 | module Reek 7 | module Configuration 8 | # 9 | # Array extension for excluded paths. 10 | # 11 | module ExcludedPaths 12 | include ConfigurationValidator 13 | 14 | # @param paths [String] 15 | # @return [undefined] 16 | def add(paths) 17 | paths.flat_map { |path| Dir[path] }. 18 | each { |path| self << Pathname(path) } 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/reek/configuration/rake_task_converter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Reek 4 | module Configuration 5 | # Responsible for converting configuration values coming from the outside world 6 | # to whatever we want to use internally. 7 | module RakeTaskConverter 8 | class << self 9 | REGEXABLE_ATTRIBUTES = %w(accept reject exclude).freeze 10 | 11 | # Converts marked strings like "/foobar/" into regexes. 12 | # 13 | # @param configuration [Hash] e.g. 14 | # {"enabled"=>true, "exclude"=>[], "reject"=>[/^[a-z]$/, /[0-9]$/, /[A-Z]/], "accept"=>[]} 15 | # @return [Hash] 16 | # 17 | # @quality :reek:NestedIterators { max_allowed_nesting: 2 } 18 | def convert(configuration) 19 | (configuration.keys & REGEXABLE_ATTRIBUTES).each do |attribute| 20 | configuration[attribute] = configuration[attribute].map do |item| 21 | item.is_a?(Regexp) ? item.inspect : item 22 | end 23 | end 24 | configuration 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/reek/configuration/schema_validator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../errors/config_file_error' 4 | require_relative 'schema' 5 | 6 | module Reek 7 | module Configuration 8 | # 9 | # Schema validator module. 10 | # 11 | class SchemaValidator 12 | def initialize(configuration) 13 | @configuration = configuration 14 | config_directories = configuration['directories']&.keys || [] 15 | @validator = Reek::Configuration::Schema.schema(config_directories) 16 | end 17 | 18 | def validate 19 | result = @validator.call(@configuration) 20 | return if result.success? 21 | 22 | raise Errors::ConfigFileError, error_message(result.errors) 23 | rescue NoMethodError 24 | raise Errors::ConfigFileError, 'unrecognized configuration data' 25 | end 26 | 27 | private 28 | 29 | # :reek:UtilityFunction 30 | def error_message(errors) 31 | messages = errors.map do |error| 32 | "[/#{error.path.join('/')}] #{error.text}." 33 | end.join("\n") 34 | "\n#{messages}" 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/reek/context/attribute_context.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'code_context' 4 | 5 | module Reek 6 | module Context 7 | # 8 | # A context wrapper for attribute definitions found in a syntax tree. 9 | # 10 | # @quality :reek:Attribute 11 | class AttributeContext < CodeContext 12 | attr_accessor :visibility 13 | 14 | def initialize(exp, send_expression) 15 | @visibility = :public 16 | @send_expression = send_expression 17 | super(exp) 18 | end 19 | 20 | def full_comment 21 | send_expression.full_comment || '' 22 | end 23 | 24 | def instance_method? 25 | true 26 | end 27 | 28 | def apply_current_visibility(current_visibility) 29 | self.visibility = current_visibility 30 | end 31 | 32 | private 33 | 34 | attr_reader :send_expression 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/reek/context/class_context.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'module_context' 4 | 5 | module Reek 6 | module Context 7 | # 8 | # A context wrapper for any class found in a syntax tree. 9 | # 10 | class ClassContext < ModuleContext 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/reek/context/ghost_context.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'code_context' 4 | require_relative 'singleton_method_context' 5 | 6 | module Reek 7 | module Context 8 | # Semi-transparent context to represent a metaclass while building the 9 | # context tree. This context will not be part of the resulting tree, but 10 | # will track context and visibility separately while building is in 11 | # progress. 12 | class GhostContext < ModuleContext 13 | attr_reader :children 14 | 15 | def register_with_parent(parent) 16 | @parent = parent 17 | end 18 | 19 | def append_child_context(child) 20 | real_parent = parent.append_child_context(child) 21 | super 22 | real_parent 23 | end 24 | 25 | # Return the correct class for child method contexts (representing nodes 26 | # of type `:def`). For GhostContext, this is the class that represents 27 | # singleton methods. 28 | def method_context_class 29 | SingletonMethodContext 30 | end 31 | 32 | # Return the correct class for child attribute contexts. For 33 | # GhostContext, this is the class that represents singleton attributes. 34 | def attribute_context_class 35 | SingletonAttributeContext 36 | end 37 | 38 | def track_visibility(visibility, names) 39 | visibility_tracker.track_visibility children: children, 40 | visibility: visibility, 41 | names: names 42 | end 43 | 44 | def record_use_of_self 45 | parent.record_use_of_self 46 | end 47 | 48 | def statement_counter 49 | parent.statement_counter 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/reek/context/refinement_context.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'module_context' 4 | 5 | module Reek 6 | module Context 7 | # 8 | # A context wrapper for any refinement blocks found in a syntax tree. 9 | # 10 | class RefinementContext < ModuleContext 11 | def full_name 12 | exp.call.args.first.name 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/reek/context/root_context.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'code_context' 4 | require_relative 'method_context' 5 | 6 | module Reek 7 | module Context 8 | # 9 | # A context wrapper representing the root of an abstract syntax tree. 10 | # 11 | class RootContext < CodeContext 12 | def type 13 | :root 14 | end 15 | 16 | def full_name 17 | '' 18 | end 19 | 20 | # Return the correct class for child method contexts (representing nodes 21 | # of type `:def`). For RootContext, this is the class that represents 22 | # instance methods. 23 | def method_context_class 24 | MethodContext 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/reek/context/send_context.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'code_context' 4 | 5 | module Reek 6 | module Context 7 | # 8 | # A context wrapper for method calls found in a syntax tree. 9 | # 10 | class SendContext < CodeContext 11 | attr_reader :name 12 | 13 | def initialize(exp, name) 14 | @name = name 15 | super(exp) 16 | end 17 | 18 | def method_name_called_to_call 19 | return unless @name == :method 20 | 21 | local_nodes(:sym).map(&:name) 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/reek/context/singleton_attribute_context.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'attribute_context' 4 | 5 | module Reek 6 | module Context 7 | # 8 | # A context wrapper for any singleton attribute definition found in a 9 | # syntax tree. 10 | # 11 | class SingletonAttributeContext < AttributeContext 12 | def instance_method? 13 | false 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/reek/context/singleton_method_context.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'method_context' 4 | 5 | module Reek 6 | module Context 7 | # 8 | # A context wrapper for any singleton method definition found in a syntax tree. 9 | # 10 | class SingletonMethodContext < MethodContext 11 | def singleton_method? 12 | true 13 | end 14 | 15 | def instance_method? 16 | false 17 | end 18 | 19 | def module_function? 20 | false 21 | end 22 | 23 | # Was this singleton method defined with an instance method-like syntax? 24 | def defined_as_instance_method? 25 | type == :def 26 | end 27 | 28 | def apply_current_visibility(current_visibility) 29 | super if defined_as_instance_method? 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/reek/context/statement_counter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../ast/node' 4 | 5 | module Reek 6 | module Context 7 | # Responsible for counting the statements in a `CodeContext`. 8 | class StatementCounter 9 | attr_reader :value 10 | 11 | def initialize 12 | @value = 0 13 | end 14 | 15 | def increase_by(sexp) 16 | self.value = value + sexp.length if sexp 17 | end 18 | 19 | def decrease_by(number) 20 | self.value = value - number 21 | end 22 | 23 | private 24 | 25 | attr_writer :value 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/reek/documentation_link.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Reek 4 | # Generate versioned links to our documentation 5 | module DocumentationLink 6 | HELP_LINK_TEMPLATE = 'https://github.com/troessner/reek/blob/v%s/docs/%s.md' 7 | 8 | module_function 9 | 10 | # Build link to the documentation about the given subject for the current 11 | # version of Reek. The subject can be either a smell type like 12 | # 'FeatureEnvy' or a general subject like 'Rake Task'. 13 | # 14 | # @param subject [String] 15 | # @return [String] the full URL for the relevant documentation 16 | def build(subject) 17 | Kernel.format(HELP_LINK_TEMPLATE, version: Version::STRING, item: name_to_param(subject)) 18 | end 19 | 20 | # Convert the given subject name to a form that is acceptable in a URL, by 21 | # dasherizeing it at the start of capitalized words. Spaces are discared. 22 | def name_to_param(name) 23 | name.split(/([A-Z][a-z][a-z]*)/).map(&:strip).reject(&:empty?).join('-') 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/reek/errors/bad_detector_configuration_key_in_comment_error.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'base_error' 4 | require_relative '../documentation_link' 5 | 6 | module Reek 7 | module Errors 8 | # Gets raised when trying to configure a detector with an option 9 | # which is unknown to it. 10 | class BadDetectorConfigurationKeyInCommentError < BaseError 11 | UNKNOWN_SMELL_DETECTOR_MESSAGE = <<-MESSAGE.freeze 12 | 13 | Error: You are trying to configure the smell detector '%s' 14 | in one of your source code comments with the unknown option %