├── .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 %