[DRE-commits] [rubocop] 03/04: Imported Upstream version 0.20.1
Sebastien Badia
sbadia-guest at moszumanska.debian.org
Mon Apr 21 14:08:43 UTC 2014
This is an automated email from the git hooks/post-receive script.
sbadia-guest pushed a commit to branch master
in repository rubocop.
commit d22303ac6b5c7e1e6c76bb2066f6d62154267f0e
Author: Sebastien Badia <seb at sebian.fr>
Date: Mon Apr 21 16:06:48 2014 +0200
Imported Upstream version 0.20.1
---
.gitignore | 51 +
.rspec | 1 +
.rubocop.yml | 3 +
.travis.yml | 16 +
.yardopts | 2 +
CHANGELOG.md | 851 +++++++++
CONTRIBUTING.md | 68 +
Gemfile | 13 +
LICENSE.txt | 20 +
README.md | 725 ++++++++
Rakefile | 43 +
bin/rubocop | 22 +
config/default.yml | 451 +++++
config/disabled.yml | 13 +
config/enabled.yml | 729 ++++++++
lib/rubocop.rb | 262 +++
lib/rubocop/cli.rb | 109 ++
lib/rubocop/comment_config.rb | 99 ++
lib/rubocop/config.rb | 106 ++
lib/rubocop/config_loader.rb | 161 ++
lib/rubocop/config_store.rb | 45 +
lib/rubocop/cop/commissioner.rb | 105 ++
lib/rubocop/cop/cop.rb | 192 ++
lib/rubocop/cop/corrector.rb | 84 +
lib/rubocop/cop/ignored_node.rb | 31 +
lib/rubocop/cop/lint/ambiguous_operator.rb | 50 +
lib/rubocop/cop/lint/ambiguous_regexp_literal.rb | 36 +
lib/rubocop/cop/lint/assignment_in_condition.rb | 46 +
lib/rubocop/cop/lint/block_alignment.rb | 151 ++
lib/rubocop/cop/lint/condition_position.rb | 52 +
lib/rubocop/cop/lint/debugger.rb | 35 +
lib/rubocop/cop/lint/deprecated_class_methods.rb | 62 +
lib/rubocop/cop/lint/else_layout.rb | 57 +
lib/rubocop/cop/lint/empty_ensure.rb | 18 +
lib/rubocop/cop/lint/empty_interpolation.rb | 22 +
lib/rubocop/cop/lint/end_alignment.rb | 129 ++
lib/rubocop/cop/lint/end_in_method.rb | 22 +
lib/rubocop/cop/lint/ensure_return.rb | 22 +
lib/rubocop/cop/lint/eval.rb | 18 +
lib/rubocop/cop/lint/handle_exceptions.rb | 18 +
lib/rubocop/cop/lint/invalid_character_literal.rb | 37 +
lib/rubocop/cop/lint/literal_in_condition.rb | 138 ++
lib/rubocop/cop/lint/literal_in_interpolation.rb | 37 +
lib/rubocop/cop/lint/loop.rb | 27 +
.../cop/lint/parentheses_as_grouped_expression.rb | 52 +
lib/rubocop/cop/lint/require_parentheses.rb | 68 +
lib/rubocop/cop/lint/rescue_exception.rb | 24 +
.../cop/lint/shadowing_outer_local_variable.rb | 31 +
lib/rubocop/cop/lint/space_before_first_arg.rb | 37 +
.../cop/lint/string_conversion_in_interpolation.rb | 51 +
lib/rubocop/cop/lint/syntax.rb | 28 +
lib/rubocop/cop/lint/unreachable_code.rb | 33 +
lib/rubocop/cop/lint/useless_access_modifier.rb | 58 +
lib/rubocop/cop/lint/useless_assignment.rb | 88 +
lib/rubocop/cop/lint/useless_comparison.rb | 30 +
.../cop/lint/useless_else_without_rescue.rb | 25 +
lib/rubocop/cop/lint/useless_setter_call.rb | 150 ++
lib/rubocop/cop/lint/void.rb | 54 +
lib/rubocop/cop/mixin/annotation_comment.rb | 37 +
lib/rubocop/cop/mixin/array_syntax.rb | 24 +
lib/rubocop/cop/mixin/autocorrect_alignment.rb | 79 +
.../cop/mixin/autocorrect_unless_changing_ast.rb | 24 +
lib/rubocop/cop/mixin/check_assignment.rb | 26 +
lib/rubocop/cop/mixin/check_methods.rb | 18 +
lib/rubocop/cop/mixin/code_length.rb | 33 +
.../cop/mixin/configurable_enforced_style.rb | 53 +
lib/rubocop/cop/mixin/configurable_max.rb | 19 +
lib/rubocop/cop/mixin/configurable_naming.rb | 45 +
lib/rubocop/cop/mixin/if_node.rb | 25 +
lib/rubocop/cop/mixin/if_then_else.rb | 23 +
lib/rubocop/cop/mixin/negative_conditional.rb | 24 +
lib/rubocop/cop/mixin/parser_diagnostic.rb | 34 +
lib/rubocop/cop/mixin/safe_assignment.rb | 19 +
lib/rubocop/cop/mixin/space_after_punctuation.rb | 33 +
lib/rubocop/cop/mixin/space_inside.rb | 35 +
lib/rubocop/cop/mixin/statement_modifier.rb | 60 +
lib/rubocop/cop/mixin/string_help.rb | 32 +
lib/rubocop/cop/mixin/surrounding_space.rb | 42 +
lib/rubocop/cop/offense.rb | 119 ++
lib/rubocop/cop/rails/action_filter.rb | 73 +
lib/rubocop/cop/rails/default_scope.rb | 35 +
lib/rubocop/cop/rails/has_and_belongs_to_many.rb | 18 +
lib/rubocop/cop/rails/output.rb | 26 +
lib/rubocop/cop/rails/read_write_attribute.rb | 43 +
lib/rubocop/cop/rails/scope_args.rb | 33 +
lib/rubocop/cop/rails/validation.rb | 31 +
lib/rubocop/cop/severity.rb | 76 +
.../cop/style/access_modifier_indentation.rb | 94 +
lib/rubocop/cop/style/accessor_method_name.rb | 45 +
lib/rubocop/cop/style/alias.rb | 48 +
lib/rubocop/cop/style/align_array.rb | 20 +
lib/rubocop/cop/style/align_hash.rb | 257 +++
lib/rubocop/cop/style/align_parameters.rb | 39 +
lib/rubocop/cop/style/and_or.rb | 44 +
lib/rubocop/cop/style/array_join.rb | 25 +
lib/rubocop/cop/style/ascii_comments.rb | 19 +
lib/rubocop/cop/style/ascii_identifiers.rb | 20 +
lib/rubocop/cop/style/attr.rb | 22 +
lib/rubocop/cop/style/begin_block.rb | 16 +
lib/rubocop/cop/style/block_comments.rb | 20 +
lib/rubocop/cop/style/block_nesting.rb | 55 +
lib/rubocop/cop/style/blocks.rb | 80 +
.../cop/style/braces_around_hash_parameters.rb | 87 +
lib/rubocop/cop/style/case_equality.rb | 18 +
lib/rubocop/cop/style/case_indentation.rb | 62 +
lib/rubocop/cop/style/character_literal.rb | 42 +
.../cop/style/class_and_module_camel_case.rb | 29 +
lib/rubocop/cop/style/class_and_module_children.rb | 69 +
lib/rubocop/cop/style/class_length.rb | 49 +
lib/rubocop/cop/style/class_methods.rb | 56 +
lib/rubocop/cop/style/class_vars.rb | 23 +
lib/rubocop/cop/style/collection_methods.rb | 73 +
lib/rubocop/cop/style/colon_method_call.rb | 33 +
lib/rubocop/cop/style/comment_annotation.rb | 37 +
lib/rubocop/cop/style/constant_name.rb | 29 +
lib/rubocop/cop/style/cyclomatic_complexity.rb | 41 +
lib/rubocop/cop/style/def_with_parentheses.rb | 33 +
lib/rubocop/cop/style/deprecated_hash_methods.rb | 39 +
lib/rubocop/cop/style/documentation.rb | 74 +
lib/rubocop/cop/style/dot_position.rb | 57 +
lib/rubocop/cop/style/double_negation.rb | 40 +
lib/rubocop/cop/style/empty_line_between_defs.rb | 46 +
lib/rubocop/cop/style/empty_lines.rb | 47 +
.../style/empty_lines_around_access_modifier.rb | 48 +
lib/rubocop/cop/style/empty_lines_around_body.rb | 74 +
lib/rubocop/cop/style/empty_literal.rb | 60 +
lib/rubocop/cop/style/encoding.rb | 30 +
lib/rubocop/cop/style/end_block.rb | 16 +
lib/rubocop/cop/style/end_of_line.rb | 30 +
lib/rubocop/cop/style/even_odd.rb | 60 +
lib/rubocop/cop/style/file_name.rb | 29 +
lib/rubocop/cop/style/final_newline.rb | 29 +
lib/rubocop/cop/style/flip_flop.rb | 20 +
lib/rubocop/cop/style/for.rb | 47 +
lib/rubocop/cop/style/format_string.rb | 66 +
lib/rubocop/cop/style/global_vars.rb | 74 +
lib/rubocop/cop/style/guard_clause.rb | 69 +
lib/rubocop/cop/style/hash_syntax.rb | 89 +
lib/rubocop/cop/style/if_unless_modifier.rb | 35 +
lib/rubocop/cop/style/if_with_semicolon.rb | 20 +
lib/rubocop/cop/style/indent_array.rb | 41 +
lib/rubocop/cop/style/indent_hash.rb | 144 ++
lib/rubocop/cop/style/indentation_consistency.rb | 43 +
lib/rubocop/cop/style/indentation_width.rb | 177 ++
lib/rubocop/cop/style/lambda.rb | 45 +
lib/rubocop/cop/style/lambda_call.rb | 63 +
lib/rubocop/cop/style/leading_comment_space.rb | 34 +
lib/rubocop/cop/style/line_end_concatenation.rb | 66 +
lib/rubocop/cop/style/line_length.rb | 34 +
lib/rubocop/cop/style/method_call_parentheses.rb | 35 +
.../cop/style/method_called_on_do_end_block.rb | 41 +
lib/rubocop/cop/style/method_def_parentheses.rb | 72 +
lib/rubocop/cop/style/method_length.rb | 29 +
lib/rubocop/cop/style/method_name.rb | 42 +
lib/rubocop/cop/style/module_function.rb | 32 +
lib/rubocop/cop/style/multiline_block_chain.rb | 44 +
lib/rubocop/cop/style/multiline_if_then.rb | 50 +
.../cop/style/multiline_ternary_operator.rb | 22 +
lib/rubocop/cop/style/negated_if.rb | 46 +
lib/rubocop/cop/style/negated_while.rb | 45 +
lib/rubocop/cop/style/nested_ternary_operator.rb | 26 +
lib/rubocop/cop/style/nil_comparison.rb | 42 +
lib/rubocop/cop/style/non_nil_check.rb | 104 ++
lib/rubocop/cop/style/not.rb | 34 +
lib/rubocop/cop/style/numeric_literals.rb | 73 +
lib/rubocop/cop/style/one_line_conditional.rb | 21 +
lib/rubocop/cop/style/op_method.rb | 27 +
lib/rubocop/cop/style/parameter_lists.rb | 44 +
.../cop/style/parentheses_around_condition.rb | 62 +
.../cop/style/percent_literal_delimiters.rb | 153 ++
lib/rubocop/cop/style/perl_backrefs.rb | 26 +
lib/rubocop/cop/style/predicate_name.rb | 46 +
lib/rubocop/cop/style/proc.rb | 32 +
lib/rubocop/cop/style/raise_args.rb | 68 +
lib/rubocop/cop/style/redundant_begin.rb | 35 +
lib/rubocop/cop/style/redundant_exception.rb | 32 +
lib/rubocop/cop/style/redundant_return.rb | 66 +
lib/rubocop/cop/style/redundant_self.rb | 145 ++
lib/rubocop/cop/style/regexp_literal.rb | 81 +
lib/rubocop/cop/style/rescue_modifier.rb | 39 +
lib/rubocop/cop/style/self_assignment.rb | 73 +
lib/rubocop/cop/style/semicolon.rb | 68 +
lib/rubocop/cop/style/signal_exception.rb | 96 +
lib/rubocop/cop/style/single_line_block_params.rb | 62 +
lib/rubocop/cop/style/single_line_methods.rb | 70 +
.../cop/style/single_space_before_first_arg.rb | 41 +
lib/rubocop/cop/style/space_after_colon.rb | 36 +
lib/rubocop/cop/style/space_after_comma.rb | 16 +
.../cop/style/space_after_control_keyword.rb | 37 +
lib/rubocop/cop/style/space_after_method_name.rb | 40 +
lib/rubocop/cop/style/space_after_not.rb | 41 +
lib/rubocop/cop/style/space_after_semicolon.rb | 16 +
.../space_around_equals_in_parameter_default.rb | 68 +
lib/rubocop/cop/style/space_around_operators.rb | 82 +
lib/rubocop/cop/style/space_before_block_braces.rb | 63 +
.../cop/style/space_before_modifier_keyword.rb | 40 +
lib/rubocop/cop/style/space_inside_block_braces.rb | 151 ++
lib/rubocop/cop/style/space_inside_brackets.rb | 16 +
.../cop/style/space_inside_hash_literal_braces.rb | 111 ++
lib/rubocop/cop/style/space_inside_parens.rb | 16 +
lib/rubocop/cop/style/special_global_vars.rb | 84 +
lib/rubocop/cop/style/string_literals.rb | 46 +
lib/rubocop/cop/style/symbol_array.rb | 24 +
lib/rubocop/cop/style/tab.rb | 26 +
lib/rubocop/cop/style/trailing_blank_lines.rb | 44 +
lib/rubocop/cop/style/trailing_comma.rb | 102 ++
lib/rubocop/cop/style/trailing_whitespace.rb | 28 +
lib/rubocop/cop/style/trivial_accessors.rb | 78 +
lib/rubocop/cop/style/unless_else.rb | 24 +
lib/rubocop/cop/style/variable_interpolation.rb | 33 +
lib/rubocop/cop/style/variable_name.rb | 44 +
lib/rubocop/cop/style/when_then.rb | 24 +
lib/rubocop/cop/style/while_until_do.rb | 45 +
lib/rubocop/cop/style/while_until_modifier.rb | 33 +
lib/rubocop/cop/style/word_array.rb | 86 +
lib/rubocop/cop/team.rb | 113 ++
lib/rubocop/cop/util.rb | 166 ++
lib/rubocop/cop/variable_inspector.rb | 427 +++++
lib/rubocop/cop/variable_inspector/assignment.rb | 103 ++
lib/rubocop/cop/variable_inspector/locatable.rb | 162 ++
lib/rubocop/cop/variable_inspector/reference.rb | 31 +
lib/rubocop/cop/variable_inspector/scope.rb | 71 +
lib/rubocop/cop/variable_inspector/variable.rb | 87 +
.../cop/variable_inspector/variable_table.rb | 129 ++
lib/rubocop/file_inspector.rb | 149 ++
lib/rubocop/formatter/base_formatter.rb | 119 ++
lib/rubocop/formatter/clang_style_formatter.rb | 35 +
lib/rubocop/formatter/colorizable.rb | 37 +
lib/rubocop/formatter/disabled_config_formatter.rb | 65 +
lib/rubocop/formatter/disabled_lines_formatter.rb | 56 +
lib/rubocop/formatter/emacs_style_formatter.rb | 21 +
lib/rubocop/formatter/file_list_formatter.rb | 19 +
lib/rubocop/formatter/formatter_set.rb | 75 +
lib/rubocop/formatter/fuubar_style_formatter.rb | 74 +
lib/rubocop/formatter/json_formatter.rb | 74 +
lib/rubocop/formatter/offense_count_formatter.rb | 54 +
lib/rubocop/formatter/progress_formatter.rb | 55 +
lib/rubocop/formatter/simple_text_formatter.rb | 117 ++
lib/rubocop/options.rb | 189 ++
lib/rubocop/path_util.rb | 23 +
lib/rubocop/processed_source.rb | 61 +
lib/rubocop/rake_task.rb | 70 +
lib/rubocop/source_parser.rb | 47 +
lib/rubocop/target_finder.rb | 91 +
lib/rubocop/token.rb | 22 +
lib/rubocop/version.rb | 21 +
relnotes/v0.19.0.md | 94 +
relnotes/v0.19.1.md | 16 +
relnotes/v0.20.0.md | 69 +
relnotes/v0.20.1.md | 24 +
rubocop-todo.yml | 20 +
rubocop.gemspec | 39 +
spec/.rubocop.yml | 5 +
spec/isolated_environment_spec.rb | 24 +
spec/project_spec.rb | 118 ++
spec/rubocop/cli_spec.rb | 1830 ++++++++++++++++++++
spec/rubocop/comment_config_spec.rb | 103 ++
spec/rubocop/config_loader_spec.rb | 328 ++++
spec/rubocop/config_spec.rb | 179 ++
spec/rubocop/config_store_spec.rb | 53 +
spec/rubocop/cop/commissioner_spec.rb | 83 +
spec/rubocop/cop/cop_spec.rb | 114 ++
spec/rubocop/cop/corrector_spec.rb | 59 +
spec/rubocop/cop/lint/ambiguous_operator_spec.rb | 113 ++
.../cop/lint/ambiguous_regexp_literal_spec.rb | 35 +
.../cop/lint/assignment_in_condition_spec.rb | 107 ++
spec/rubocop/cop/lint/block_alignment_spec.rb | 411 +++++
spec/rubocop/cop/lint/condition_position_spec.rb | 49 +
spec/rubocop/cop/lint/debugger_spec.rb | 39 +
.../cop/lint/deprecated_class_methods_spec.rb | 38 +
spec/rubocop/cop/lint/else_layout_spec.rb | 65 +
spec/rubocop/cop/lint/empty_ensure_spec.rb | 27 +
spec/rubocop/cop/lint/empty_interpolation_spec.rb | 18 +
spec/rubocop/cop/lint/end_alignment_spec.rb | 135 ++
spec/rubocop/cop/lint/end_in_method_spec.rb | 29 +
spec/rubocop/cop/lint/ensure_return_spec.rb | 39 +
spec/rubocop/cop/lint/eval_spec.rb | 33 +
spec/rubocop/cop/lint/handle_exceptions_spec.rb | 30 +
.../cop/lint/invalid_character_literal_spec.rb | 33 +
spec/rubocop/cop/lint/literal_in_condition_spec.rb | 154 ++
.../cop/lint/literal_in_interpolation_spec.rb | 31 +
spec/rubocop/cop/lint/loop_spec.rb | 27 +
.../lint/parentheses_as_grouped_expression_spec.rb | 57 +
spec/rubocop/cop/lint/require_parentheses_spec.rb | 82 +
spec/rubocop/cop/lint/rescue_exception_spec.rb | 123 ++
.../lint/shadowing_outer_local_variable_spec.rb | 237 +++
.../cop/lint/space_before_first_arg_spec.rb | 58 +
.../string_conversion_in_interpolation_spec.rb | 51 +
spec/rubocop/cop/lint/syntax_spec.rb | 34 +
spec/rubocop/cop/lint/unreachable_code_spec.rb | 63 +
.../cop/lint/useless_access_modifier_spec.rb | 192 ++
spec/rubocop/cop/lint/useless_assignment_spec.rb | 1608 +++++++++++++++++
spec/rubocop/cop/lint/useless_comparison_spec.rb | 30 +
.../cop/lint/useless_else_without_rescue_spec.rb | 48 +
spec/rubocop/cop/lint/useless_setter_call_spec.rb | 149 ++
spec/rubocop/cop/lint/void_spec.rb | 57 +
spec/rubocop/cop/offense_spec.rb | 133 ++
spec/rubocop/cop/rails/action_filter_spec.rb | 69 +
spec/rubocop/cop/rails/default_scope_spec.rb | 37 +
.../cop/rails/has_and_belongs_to_many_spec.rb | 13 +
spec/rubocop/cop/rails/output_spec.rb | 31 +
.../rubocop/cop/rails/read_write_attribute_spec.rb | 19 +
spec/rubocop/cop/rails/scope_args_spec.rb | 25 +
spec/rubocop/cop/rails/validation_spec.rb | 21 +
spec/rubocop/cop/severity_spec.rb | 113 ++
.../cop/style/access_modifier_indentation_spec.rb | 361 ++++
.../rubocop/cop/style/accessor_method_name_spec.rb | 81 +
spec/rubocop/cop/style/alias_spec.rb | 59 +
spec/rubocop/cop/style/align_array_spec.rb | 91 +
spec/rubocop/cop/style/align_hash_spec.rb | 391 +++++
spec/rubocop/cop/style/align_parameters_spec.rb | 295 ++++
spec/rubocop/cop/style/and_or_spec.rb | 57 +
spec/rubocop/cop/style/array_join_spec.rb | 29 +
spec/rubocop/cop/style/ascii_comments_spec.rb | 22 +
spec/rubocop/cop/style/ascii_identifiers_spec.rb | 36 +
spec/rubocop/cop/style/attr_spec.rb | 19 +
spec/rubocop/cop/style/begin_block_spec.rb | 13 +
spec/rubocop/cop/style/block_comments_spec.rb | 21 +
spec/rubocop/cop/style/block_nesting_spec.rb | 156 ++
spec/rubocop/cop/style/blocks_spec.rb | 105 ++
.../style/braces_around_hash_parameters_spec.rb | 284 +++
spec/rubocop/cop/style/case_equality_spec.rb | 13 +
spec/rubocop/cop/style/case_indentation_spec.rb | 292 ++++
spec/rubocop/cop/style/character_literal_spec.rb | 37 +
.../cop/style/class_and_module_camel_case_spec.rb | 40 +
.../cop/style/class_and_module_children_spec.rb | 129 ++
spec/rubocop/cop/style/class_length_spec.rb | 131 ++
spec/rubocop/cop/style/class_methods_spec.rb | 68 +
spec/rubocop/cop/style/class_vars_spec.rb | 19 +
spec/rubocop/cop/style/collection_methods_spec.rb | 48 +
spec/rubocop/cop/style/colon_method_call_spec.rb | 60 +
spec/rubocop/cop/style/comment_annotation_spec.rb | 86 +
spec/rubocop/cop/style/constant_name_spec.rb | 65 +
.../cop/style/cyclomatic_complexity_spec.rb | 204 +++
.../rubocop/cop/style/def_with_parentheses_spec.rb | 39 +
.../cop/style/deprecated_hash_methods_spec.rb | 45 +
spec/rubocop/cop/style/documentation_spec.rb | 123 ++
spec/rubocop/cop/style/dot_position_spec.rb | 91 +
spec/rubocop/cop/style/double_negation_spec.rb | 22 +
.../cop/style/empty_line_between_defs_spec.rb | 135 ++
.../empty_lines_around_access_modifier_spec.rb | 56 +
.../cop/style/empty_lines_around_body_spec.rb | 131 ++
spec/rubocop/cop/style/empty_lines_spec.rb | 51 +
spec/rubocop/cop/style/empty_literal_spec.rb | 100 ++
spec/rubocop/cop/style/encoding_spec.rb | 56 +
spec/rubocop/cop/style/end_block_spec.rb | 13 +
spec/rubocop/cop/style/end_of_line_spec.rb | 47 +
spec/rubocop/cop/style/even_odd_spec.rb | 75 +
spec/rubocop/cop/style/file_name_spec.rb | 84 +
spec/rubocop/cop/style/final_newline_spec.rb | 30 +
spec/rubocop/cop/style/flip_flop_spec.rb | 23 +
spec/rubocop/cop/style/for_spec.rb | 105 ++
spec/rubocop/cop/style/format_string_spec.rb | 136 ++
spec/rubocop/cop/style/global_vars_spec.rb | 34 +
spec/rubocop/cop/style/guard_clause_spec.rb | 77 +
spec/rubocop/cop/style/hash_syntax_spec.rb | 133 ++
spec/rubocop/cop/style/if_unless_modifier_spec.rb | 146 ++
spec/rubocop/cop/style/if_with_semicolon_spec.rb | 19 +
spec/rubocop/cop/style/indent_array_spec.rb | 136 ++
spec/rubocop/cop/style/indent_hash_spec.rb | 310 ++++
.../cop/style/indentation_consistency_spec.rb | 510 ++++++
spec/rubocop/cop/style/indentation_width_spec.rb | 606 +++++++
spec/rubocop/cop/style/lambda_call_spec.rb | 65 +
spec/rubocop/cop/style/lambda_spec.rb | 41 +
.../cop/style/leading_comment_space_spec.rb | 64 +
.../cop/style/line_end_concatenation_spec.rb | 62 +
spec/rubocop/cop/style/line_length_spec.rb | 20 +
.../cop/style/method_call_parentheses_spec.rb | 59 +
.../style/method_called_on_do_end_block_spec.rb | 60 +
.../cop/style/method_def_parentheses_spec.rb | 106 ++
spec/rubocop/cop/style/method_length_spec.rb | 147 ++
spec/rubocop/cop/style/method_name_spec.rb | 125 ++
spec/rubocop/cop/style/module_function_spec.rb | 24 +
.../cop/style/multiline_block_chain_spec.rb | 78 +
spec/rubocop/cop/style/multiline_if_then_spec.rb | 107 ++
.../cop/style/multiline_ternary_operator_spec.rb | 18 +
spec/rubocop/cop/style/negated_if_spec.rb | 98 ++
spec/rubocop/cop/style/negated_while_spec.rb | 62 +
.../cop/style/nested_ternary_operator_spec.rb | 21 +
spec/rubocop/cop/style/nil_comparison_spec.rb | 29 +
spec/rubocop/cop/style/non_nil_check_spec.rb | 70 +
spec/rubocop/cop/style/not_spec.rb | 28 +
spec/rubocop/cop/style/numeric_literals_spec.rb | 70 +
.../rubocop/cop/style/one_line_conditional_spec.rb | 13 +
spec/rubocop/cop/style/op_method_spec.rb | 82 +
spec/rubocop/cop/style/parameter_lists_spec.rb | 44 +
.../cop/style/parentheses_around_condition_spec.rb | 123 ++
.../cop/style/percent_literal_delimiters_spec.rb | 262 +++
spec/rubocop/cop/style/perl_backrefs_spec.rb | 17 +
spec/rubocop/cop/style/predicate_name_spec.rb | 26 +
spec/rubocop/cop/style/proc_spec.rb | 27 +
spec/rubocop/cop/style/raise_args_spec.rb | 87 +
spec/rubocop/cop/style/redundant_begin_spec.rb | 57 +
spec/rubocop/cop/style/redundant_exception_spec.rb | 27 +
spec/rubocop/cop/style/redundant_return_spec.rb | 171 ++
spec/rubocop/cop/style/redundant_self_spec.rb | 142 ++
spec/rubocop/cop/style/regexp_literal_spec.rb | 104 ++
spec/rubocop/cop/style/rescue_modifier_spec.rb | 116 ++
spec/rubocop/cop/style/self_assignment_spec.rb | 43 +
spec/rubocop/cop/style/semicolon_spec.rb | 114 ++
spec/rubocop/cop/style/signal_exception_spec.rb | 290 ++++
.../cop/style/single_line_block_params_spec.rb | 70 +
spec/rubocop/cop/style/single_line_methods_spec.rb | 90 +
.../style/single_space_before_first_arg_spec.rb | 63 +
spec/rubocop/cop/style/space_after_colon_spec.rb | 38 +
spec/rubocop/cop/style/space_after_comma_spec.rb | 30 +
.../cop/style/space_after_control_keyword_spec.rb | 84 +
.../cop/style/space_after_method_name_spec.rb | 70 +
spec/rubocop/cop/style/space_after_not_spec.rb | 22 +
.../cop/style/space_after_semicolon_spec.rb | 23 +
...pace_around_equals_in_parameter_default_spec.rb | 75 +
.../cop/style/space_around_operators_spec.rb | 325 ++++
.../cop/style/space_before_block_braces_spec.rb | 72 +
.../style/space_before_modifier_keyword_spec.rb | 70 +
.../cop/style/space_inside_block_braces_spec.rb | 287 +++
.../cop/style/space_inside_brackets_spec.rb | 59 +
.../style/space_inside_hash_literal_braces_spec.rb | 147 ++
spec/rubocop/cop/style/space_inside_parens_spec.rb | 46 +
spec/rubocop/cop/style/special_global_vars_spec.rb | 57 +
spec/rubocop/cop/style/string_literals_spec.rb | 212 +++
spec/rubocop/cop/style/symbol_array_spec.rb | 37 +
spec/rubocop/cop/style/tab_spec.rb | 17 +
.../rubocop/cop/style/trailing_blank_lines_spec.rb | 43 +
spec/rubocop/cop/style/trailing_comma_spec.rb | 230 +++
spec/rubocop/cop/style/trailing_whitespace_spec.rb | 30 +
spec/rubocop/cop/style/trivial_accessors_spec.rb | 418 +++++
spec/rubocop/cop/style/unless_else_spec.rb | 23 +
.../cop/style/variable_interpolation_spec.rb | 56 +
spec/rubocop/cop/style/variable_name_spec.rb | 107 ++
spec/rubocop/cop/style/when_then_spec.rb | 40 +
spec/rubocop/cop/style/while_until_do_spec.rb | 53 +
.../rubocop/cop/style/while_until_modifier_spec.rb | 93 +
spec/rubocop/cop/style/word_array_spec.rb | 97 ++
spec/rubocop/cop/team_spec.rb | 142 ++
spec/rubocop/cop/util_spec.rb | 49 +
.../cop/variable_inspector/assignment_spec.rb | 213 +++
.../cop/variable_inspector/locatable_spec.rb | 734 ++++++++
spec/rubocop/cop/variable_inspector/scope_spec.rb | 184 ++
.../cop/variable_inspector/variable_spec.rb | 73 +
.../cop/variable_inspector/variable_table_spec.rb | 269 +++
spec/rubocop/cop/variable_inspector_spec.rb | 29 +
spec/rubocop/file_inspector_spec.rb | 84 +
spec/rubocop/formatter/base_formatter_spec.rb | 191 ++
.../formatter/clang_style_formatter_spec.rb | 114 ++
spec/rubocop/formatter/colorizable_spec.rb | 107 ++
.../formatter/disabled_config_formatter_spec.rb | 50 +
.../formatter/disabled_lines_formatter_spec.rb | 69 +
.../formatter/emacs_style_formatter_spec.rb | 62 +
spec/rubocop/formatter/file_list_formatter_spec.rb | 33 +
spec/rubocop/formatter/formatter_set_spec.rb | 132 ++
.../formatter/fuubar_style_formatter_spec.rb | 129 ++
spec/rubocop/formatter/json_formatter_spec.rb | 152 ++
.../formatter/offense_count_formatter_spec.rb | 77 +
spec/rubocop/formatter/progress_formatter_spec.rb | 182 ++
.../formatter/simple_text_formatter_spec.rb | 123 ++
spec/rubocop/options_spec.rb | 176 ++
spec/rubocop/path_util_spec.rb | 42 +
spec/rubocop/processed_source_spec.rb | 114 ++
spec/rubocop/source_parser_spec.rb | 85 +
spec/rubocop/target_finder_spec.rb | 211 +++
spec/rubocop/token_spec.rb | 25 +
spec/spec_helper.rb | 149 ++
spec/support/ast_helper.rb | 15 +
spec/support/file_helper.rb | 21 +
spec/support/isolated_environment.rb | 34 +
spec/support/mri_syntax_checker.rb | 73 +
spec/support/shared_context.rb | 22 +
spec/support/shared_examples.rb | 33 +
spec/support/statement_modifier_helper.rb | 41 +
469 files changed, 41307 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a0ac677
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,51 @@
+# rcov generated
+coverage
+coverage.data
+
+# rdoc generated
+rdoc
+
+# yard generated
+doc
+.yardoc
+
+# bundler
+.bundle
+Gemfile.lock
+Gemfile.local
+
+# jeweler generated
+pkg
+
+# Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
+#
+# * Create a file at ~/.gitignore
+# * Include files you want ignored
+# * Run: git config --global core.excludesfile ~/.gitignore
+#
+# After doing this, these files will be ignored in all your git projects,
+# saving you from having to 'pollute' every project you touch with them
+#
+# Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
+#
+# For MacOS:
+#
+#.DS_Store
+
+# For TextMate
+#*.tmproj
+#tmtags
+
+# For emacs:
+#*~
+#\#*
+#.\#*
+
+# For vim:
+#*.swp
+
+# For redcar:
+#.redcar
+
+# For rubinius:
+#*.rbc
diff --git a/.rspec b/.rspec
new file mode 100644
index 0000000..4e1e0d2
--- /dev/null
+++ b/.rspec
@@ -0,0 +1 @@
+--color
diff --git a/.rubocop.yml b/.rubocop.yml
new file mode 100644
index 0000000..f9dc215
--- /dev/null
+++ b/.rubocop.yml
@@ -0,0 +1,3 @@
+# This is the configuration used to check the rubocop source code.
+
+inherit_from: rubocop-todo.yml
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..9166ed7
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,16 @@
+language: ruby
+rvm:
+ - 1.9.2
+ - 1.9.3
+ - 2.0.0
+ - 2.1.0
+ - ruby-head
+ - jruby-19mode
+ - rbx-2
+matrix:
+ allow_failures:
+ - rvm: ruby-head
+before_install: gem update --remote bundler
+script:
+ - bundle exec rspec
+ - bundle exec rubocop
diff --git a/.yardopts b/.yardopts
new file mode 100644
index 0000000..d0ca888
--- /dev/null
+++ b/.yardopts
@@ -0,0 +1,2 @@
+--markup markdown
+--hide-void-return
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..1e99947
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,851 @@
+# Change log
+
+## master (unreleased)
+
+## 0.20.1 (05/04/2014)
+
+### Bugs fixed
+
+* [#940](https://github.com/bbatsov/rubocop/issues/940): Fixed `UselessAccessModifier` not handling `attr_*` correctly. ([@fshowalter][])
+* `NegatedIf` properly handles negated `unless` condition. ([@bbatsov][])
+* `NegatedWhile` properly handles negated `until` condition. ([@bbatsov][])
+* [#925](https://github.com/bbatsov/rubocop/issues/925): Do not disable the `Syntax` cop in output from `--auto-gen-config`. ([@jonas054][])
+* [#943](https://github.com/bbatsov/rubocop/issues/943): Fix auto-correction interference problem between `SpaceAfterComma` and other cops. ([@jonas054][])
+* [#954](https://github.com/bbatsov/rubocop/pull/954): Fix auto-correction bug in `NilComparison`. ([@bbatsov][])
+* [#953](https://github.com/bbatsov/rubocop/pull/953): Fix auto-correction bug in `NonNilCheck`. ([@bbatsov][])
+* [#952](https://github.com/bbatsov/rubocop/pull/952): Handle implicit receiver in `StringConversionInInterpolation`. ([@bbatsov][])
+* [#956](https://github.com/bbatsov/rubocop/pull/956): Apply `ClassMethods` check only on `class`/`module` bodies. ([@bbatsov][])
+* [#945](https://github.com/bbatsov/rubocop/issues/945): Fix SpaceBeforeFirstArg cop for multiline argument and exclude assignments. ([@cschramm][])
+* [#948](https://github.com/bbatsov/rubocop/issues/948): `Blocks` cop avoids auto-correction if it would introduce a semantic change. ([@jonas054][])
+* [#946](https://github.com/bbatsov/rubocop/issues/946): Allow non-nil checks that are the final expressions of predicate method definitions in `NonNilCheck`. ([@bbatsov][])
+* [#957](https://github.com/bbatsov/rubocop/issues/957): Allow space + comment inside parentheses, braces, and square brackets. ([@jonas054][])
+
+## 0.20.0 (02/04/2014)
+
+### New features
+
+* New cop `GuardClause` checks for conditionals that can be replaced by guard clauses. ([@bbatsov][])
+* New cop `EmptyInterpolation` checks for empty interpolation in double-quoted strings. ([@bbatsov][])
+* [#899](https://github.com/bbatsov/rubocop/issues/899): Make `LineEndConcatenation` cop `<<` aware. ([@mockdeep][])
+* [#896](https://github.com/bbatsov/rubocop/issues/896): New option `--fail-level` changes minimum severity for exit with error code. ([@hiroponz][])
+* [#893](https://github.com/bbatsov/rubocop/issues/893): New option `--force-exclusion` forces excluding files specified in the configuration `Exclude` even if they are explicitly passed as arguments. ([@yujinakayama][])
+* `VariableInterpolation` cop does auto-correction. ([@bbatsov][])
+* `Not` cop does auto-correction. ([@bbatsov][])
+* `ClassMethods` cop does auto-correction. ([@bbatsov][])
+* `StringConversionInInterpolation` cop does auto-correction. ([@bbatsov][])
+* `NilComparison` cop does auto-correction. ([@bbatsov][])
+* `NonNilComparison` cop does auto-correction. ([@bbatsov][])
+* `NegatedIf` cop does auto-correction. ([@bbatsov][])
+* `NegatedWhile` cop does auto-correction. ([@bbatsov][])
+* New lint cop `SpaceBeforeFirstArg` checks for space between the method name and the first argument in method calls without parentheses. ([@jonas054][])
+* New style cop `SingleSpaceBeforeFirstArg` checks that no more than one space is used between the method name and the first argument in method calls without parentheses. ([@jonas054][])
+* New formatter `disabled_lines` displays cops and line ranges disabled by inline comments. ([@fshowalter][])
+* New cop `UselessAccessModifiers` checks for access modifiers that have no effect. ([@fshowalter][])
+
+### Changes
+
+* [#913](https://github.com/bbatsov/rubocop/issues/913): `FileName` accepts multiple extensions. ([@tamird][])
+* `AllCops/Excludes` and `AllCops/Includes` were renamed to `AllCops/Exclude` and `AllCops/Include` for consistency with standard cop params. ([@bbatsov][])
+* Extract `NonNilCheck` cop from `NilComparison`. ([@bbatsov][])
+* Renamed `FavorJoin` to `ArrayJoin`. ([@bbatsov][])
+* Renamed `FavorUnlessOverNegatedIf` to `NegatedIf`. ([@bbatsov][])
+* Renamed `FavorUntilOverNegatedWhile`to `NegatedWhile`. ([@bbatsov][])
+* Renamed `HashMethods` to `DeprecatedHashMethods`. ([@bbatsov][])
+* Renamed `ReadAttribute` to `ReadWriteAttribute` and extended it to check for uses of `write_attribute`. ([@bbatsov][])
+* Add experimental support for Ruby 2.2 (development version) by falling back to Ruby 2.1 parser. ([@yujinakayama][])
+
+### Bugs fixed
+
+* [#926](https://github.com/bbatsov/rubocop/issues/926): Fixed `BlockNesting` not auto-generating correctly. ([@tmorris-fiksu][])
+* [#904](https://github.com/bbatsov/rubocop/issues/904): Fixed a NPE in `LiteralInInterpolation`. ([@bbatsov][])
+* [#904](https://github.com/bbatsov/rubocop/issues/904): Fixed a NPE in `StringConversionInInterpolation`. ([@bbatsov][])
+* [#892](https://github.com/bbatsov/rubocop/issues/892): Make sure `Include` and `Exclude` paths in a `.rubocop.yml` are interpreted as relative to the directory of that file. ([@jonas054][])
+* [#906](https://github.com/bbatsov/rubocop/issues/906): Fixed a false positive in `LiteralInInterpolation`. ([@bbatsov][])
+* [#909](https://github.com/bbatsov/rubocop/issues/909): Handle properly multiple `rescue` clauses in `SignalException`. ([@bbatsov][])
+* [#876](https://github.com/bbatsov/rubocop/issues/876): Do a deep merge of hashes when overriding default configuration in a `.rubocop.yml` file. ([@jonas054][])
+* [#912](https://github.com/bbatsov/rubocop/issues/912): Fix a false positive in `LineEndConcatenation` for `%` string literals. ([@bbatsov][])
+* [#912](https://github.com/bbatsov/rubocop/issues/912): Handle top-level constant resolution in `DeprecatedClassMethods` (e.g. `::File.exists?`). ([@bbatsov][])
+* [#914](https://github.com/bbatsov/rubocop/issues/914): Fixed rdoc error during gem installation. ([@bbatsov][])
+* The `--only` option now enables the given cop in case it is disabled in configuration. ([@jonas054][])
+* Fix path resolution so that the default exclusion of `vendor` directories works. ([@jonas054][])
+* [#908](https://github.com/bbatsov/rubocop/issues/908): Fixed hanging while auto correct for `SpaceAfterComma` and `SpaceInsideBrackets`. ([@hiroponz][])
+* [#919](https://github.com/bbatsov/rubocop/issues/919): Don't avoid auto-correction in `HashSyntax` when there is missing space around operator. ([@jonas054][])
+* Fixed handling of floats in `NumericLiterals`. ([@bbatsov][])
+* [#927](https://github.com/bbatsov/rubocop/issues/927): Let `--auto-gen-config` overwrite an existing `rubocop-todo.yml` file instead of asking the user to remove it. ([@jonas054][])
+* [#936](https://github.com/bbatsov/rubocop/issues/936): Allow `_other` as well as `other` in `OpMethod`. ([@bbatsov][])
+
+## 0.19.1 (17/03/2014)
+
+### Bugs fixed
+
+* [#884](https://github.com/bbatsov/rubocop/issues/884): Fix --auto-gen-config for `NumericLiterals` so MinDigits is correct. ([@tmorris-fiksu][])
+* [#879](https://github.com/bbatsov/rubocop/issues/879): Fix --auto-gen-config for `RegexpLiteral` so we don't generate illegal values for `MaxSlashes`. ([@jonas054][])
+* Fix the name of the `Include` param in the default config of the Rails cops. ([@bbatsov][])
+* [#878](https://github.com/bbatsov/rubocop/pull/878): Blacklist `Rakefile`, `Gemfile` and `Capfile` by default in the `FileName` cop. ([@bbatsov][])
+* [#875](https://github.com/bbatsov/rubocop/issues/875): Handle `separator` style hashes in `IndentHash`. ([@jonas054][])
+* Fix a bug where multiple cli options that result in exit can be specified at once (e.g. `-vV`, `-v --show-cops`). ([@jkogara][])
+* [#889](https://github.com/bbatsov/rubocop/issues/889): Fix a false positive for `LiteralInCondition` when the condition is non-primitive array. ([@bbatsov][])
+
+## 0.19.0 (13/03/2014)
+
+### New features
+
+* New cop `FileName` makes sure that source files have snake_case names. ([@bbatsov][])
+* New cop `DeprecatedClassMethods` checks for deprecated class methods. ([@bbatsov][])
+* New cop `StringConversionInInterpolation` checks for redundant `Object#to_s` in string interpolation. ([@bbatsov][])
+* New cop `LiteralInInterpolation` checks for interpolated string literals. ([@bbatsov][])
+* New cop `SelfAssignment` checks for places where the self-assignment shorthand should have been used. ([@bbatsov][])
+* New cop `DoubleNegation` checks for uses of `!!`. ([@bbatsov][])
+* New cop `PercentLiteralDelimiters` enforces consistent usage of `%`-literal delimiters. ([@hannestyden][])
+* New Rails cop `ActionFilter` enforces the use of `_filter` or `_action` action filter methods. ([@bbatsov][])
+* New Rails cop `ScopeArgs` makes sure you invoke the `scope` method properly. ([@bbatsov][])
+* Add `with_fixed_indentation` style to `AlignParameters` cop. ([@hannestyden][])
+* Add `IgnoreLastArgumentHash` option to `AlignHash` cop. ([@hannestyden][])
+* [#743](https://github.com/bbatsov/rubocop/issues/743): `SingleLineMethods` cop does auto-correction. ([@jonas054][])
+* [#743](https://github.com/bbatsov/rubocop/issues/743): `Semicolon` cop does auto-correction. ([@jonas054][])
+* [#743](https://github.com/bbatsov/rubocop/issues/743): `EmptyLineBetweenDefs` cop does auto-correction. ([@jonas054][])
+* [#743](https://github.com/bbatsov/rubocop/issues/743): `IndentationWidth` cop does auto-correction. ([@jonas054][])
+* [#743](https://github.com/bbatsov/rubocop/issues/743): `IndentationConsistency` cop does auto-correction. ([@jonas054][])
+* [#809](https://github.com/bbatsov/rubocop/issues/809): New formatter `fuubar` displays a progress bar and shows details of offenses as soon as they are detected. ([@yujinakayama][])
+* [#797](https://github.com/bbatsov/rubocop/issues/797): New cop `IndentHash` checks the indentation of the first key in multi-line hash literals. ([@jonas054][])
+* [#797](https://github.com/bbatsov/rubocop/issues/797): New cop `IndentArray` checks the indentation of the first element in multi-line array literals. ([@jonas054][])
+* [#806](https://github.com/bbatsov/rubocop/issues/806): Now excludes files in `vendor/**` by default. ([@jeremyolliver][])
+* [#795](https://github.com/bbatsov/rubocop/issues/795): `IfUnlessModifier` and `WhileUntilModifier` supports `MaxLineLength`, which is independent of `LineLength` parameter `Max`. ([@agrimm][])
+* [#868](https://github.com/bbatsov/rubocop/issues/868): New cop `ClassAndModuleChildren` checks the style of children definitions at classes and modules: nested / compact. ([@geniou][])
+
+### Changes
+
+* [#793](https://github.com/bbatsov/rubocop/issues/793): Add printing total count when `rubocop --format offences`. ([@ma2gedev][])
+* Remove `Ignore` param from the Rails `Output` cop. The standard `Exclude/Include` should be used instead. ([@bbatsov][])
+* Renamed `FavorSprintf` to `FormatString` and made it configurable. ([@bbatsov][])
+* Renamed `Offence` to `Offense`. ([@bbatsov][])
+* Use `offense` in all messages instead of `offence`. ([@bbatsov][])
+* For indentation of `if`/`unless`/`while`/`until` bodies when the result is assigned to a variable, instead of supporting two styles simultaneously, `IndentationWidth` now supports one style of indentation at a time, specified by `EndAlignment`/`AlignWith`. ([@jonas054][])
+* Renamed `Style` param of `DotPosition` cop to `EnforcedStyle`. ([@bbatsov][])
+* Add `length` value to locations of offense in JSON formatter. ([@yujinakayama][])
+* `SpaceAroundBlockBraces` cop replaced by `SpaceBeforeBlockBraces` and `SpaceInsideBlockBraces`. ([@jonas054][])
+* `SpaceAroundEqualsInParameterDefault` cop is now configurable with the `EnforcedStyle` option. ([@jonas054][])
+
+### Bugs fixed
+
+* [#790](https://github.com/bbatsov/rubocop/issues/790): Fix auto-correction interference problem between `MethodDefParentheses` and other cops. ([@jonas054][])
+* [#794](https://github.com/bbatsov/rubocop/issues/794): Fix handling of modifier keywords with required parentheses in `ParenthesesAroundCondition`. ([@bbatsov][])
+* [#804](https://github.com/bbatsov/rubocop/issues/804): Fix a false positive with operator assignments in a loop (including `begin..rescue..end` with `retry`) in `UselessAssignment`. ([@yujinakayama][])
+* [#815](https://github.com/bbatsov/rubocop/issues/815): Fix a false positive for heredocs with blank lines in them in `EmptyLines`. ([@bbatsov][])
+* Auto-correction is now more robust and less likely to die because of `RangeError` or "clobbering". ([@jonas054][])
+* Offenses always reported in order of position in file, also during `--auto-correct` runs. ([@jonas054][])
+* Fix problem with `[Corrected]` tag sometimes missing in output from `--auto-correct` runs. ([@jonas054][])
+* Fix message from `EndAlignment` cop when `AlignWith` is `keyword`. ([@jonas054][])
+* Handle `case` conditions in `LiteralInCondition`. ([@bbatsov][])
+* [#822](https://github.com/bbatsov/rubocop/issues/822): Fix a false positive in `DotPosition` when enforced style is set to `trailing`. ([@bbatsov][])
+* Handle properly dynamic strings in `LineEndConcatenation`. ([@bbatsov][])
+* [#832](https://github.com/bbatsov/rubocop/issues/832): Fix auto-correction interference problem between `BracesAroundHashParameters` and `SpaceInsideHashLiteralBraces`. ([@jonas054][])
+* Fix bug in auto-correction of alignment so that only space can be removed. ([@jonas054][])
+* Fix bug in `IndentationWidth` auto-correction so it doesn't correct things that `IndentationConsistency` should correct. ([@jonas054][])
+* [#847](https://github.com/bbatsov/rubocop/issues/847): Fix bug in `RegexpLiteral` concerning `--auto-gen-config`. ([@jonas054][])
+* [#848](https://github.com/bbatsov/rubocop/issues/848): Fix bug in `--show-cops` that made it print the default configuration rather than the current configuration. ([@jonas054][])
+* [#862](https://github.com/bbatsov/rubocop/issues/862): Fix a bug where single line `rubocop:disable` comments with indentations were treated as multiline cop disabling comments. ([@yujinakayama][])
+* Fix a bug where `rubocop:disable` comments with a cop name including `all` (e.g. `MethodCallParentheses`) were disabling all cops. ([@yujinakayama][])
+* Fix a bug where string and regexp literals including `# rubocop:disable` were confused with real comments. ([@yujinakayama][])
+* [#877](https://github.com/bbatsov/rubocop/issues/877): Fix bug in `PercentLiteralDelimiters` concerning auto-correct of regular expressions with interpolation. ([@hannestyden][])
+
+## 0.18.1 (02/02/2014)
+
+### Bugs fixed
+
+* Remove double reporting in `EmptyLinesAroundBody` of empty line inside otherwise empty class/module/method that caused crash in autocorrect. ([@jonas054][])
+* [#779](https://github.com/bbatsov/rubocop/issues/779): Fix a false positive in `LineEndConcatenation`. ([@bbatsov][])
+* [#751](https://github.com/bbatsov/rubocop/issues/751): Fix `Documentation` cop so that a comment followed by an empty line and then a class definition is not considered to be class documentation. ([@jonas054][])
+* [#783](https://github.com/bbatsov/rubocop/issues/783): Fix a false positive in `ParenthesesAroundCondition` when the parentheses are actually required. ([@bbatsov][])
+* [#781](https://github.com/bbatsov/rubocop/issues/781): Fix problem with back-and-forth auto-correction in `AccessModifierIndentation`. ([@jonas054][])
+* [#785](https://github.com/bbatsov/rubocop/issues/785): Fix false positive on `%w` arrays in `TrailingComma`. ([@jonas054][])
+* [#782](https://github.com/bbatsov/rubocop/issues/782): Fix false positive in `AlignHash` for single line hashes. ([@jonas054][])
+
+## 0.18.0 (30/01/2014)
+
+### New features
+
+* [#714](https://github.com/bbatsov/rubocop/issues/714): New cop `RequireParentheses` checks for method calls without parentheses together with a boolean operator indicating that a mistake about precedence may have been made. ([@jonas054][])
+* [#743](https://github.com/bbatsov/rubocop/issues/743): `WordArray` cop does auto-correction. ([@jonas054][])
+* [#743](https://github.com/bbatsov/rubocop/issues/743): `Proc` cop does auto-correction. ([@bbatsov][])
+* [#743](https://github.com/bbatsov/rubocop/issues/743): `AccessModifierIndentation` cop does auto-correction. ([@jonas054][])
+* [#768](https://github.com/bbatsov/rubocop/issues/768): Rake task now supports `requires` and `options`. ([@nevir][])
+* [#759](https://github.com/bbatsov/rubocop/issues/759): New cop `EndLineConcatenation` checks for string literal concatenation with `+` at line end. ([@bbatsov][])
+
+### Changes
+
+* [#762](https://github.com/bbatsov/rubocop/issues/762): Support Rainbow gem both 1.99.x and 2.x. ([@yujinakayama][])
+* [#761](https://github.com/bbatsov/rubocop/issues/761): Relax `json` requirement to `>= 1.7.7`. ([@bbatsov][])
+* [#757](https://github.com/bbatsov/rubocop/issues/757): `Include/Exclude` supports relative globbing to some extent. ([@nevir][])
+
+### Bugs fixed
+
+* [#764](https://github.com/bbatsov/rubocop/issues/764): Handle heredocs in `TrailingComma`. ([@jonas054][])
+* Guide for contributors now points to correct issues page. ([@scottmatthewman][])
+
+## 0.17.0 (25/01/2014)
+
+### New features
+
+* New cop `ConditionPosition` checks for misplaced conditions in expressions like `if/unless/when/until`. ([@bbatsov][])
+* New cop `ElseLayout` checks for odd arrangement of code in the `else` branch of a conditional expression. ([@bbatsov][])
+* [#694](https://github.com/bbatsov/rubocop/issues/694): Support Ruby 1.9.2 until June 2014. ([@yujinakayama][])
+* [#702](https://github.com/bbatsov/rubocop/issues/702): Improve `rubocop-todo.yml` with comments about offence count, configuration parameters, and auto-correction support. ([@jonas054][])
+* Add new command-line flag `-D/--display-cop-names` to trigger the display of cop names in offence messages. ([@bbatsov][])
+* [#733](https://github.com/bbatsov/rubocop/pull/733): `NumericLiterals` cop does auto-correction. ([@dblock][])
+* [#713](https://github.com/bbatsov/rubocop/issues/713): New cop `TrailingComma` checks for comma after the last item in a hash, array, or method call parameter list. ([@jonas054][])
+
+### Changes
+
+* [#581](https://github.com/bbatsov/rubocop/pull/581): Extracted a new cop `AmbiguousOperator` from `Syntax` cop. It checks for ambiguous operators in the first argument of a method invocation without parentheses. ([@yujinakayama][])
+* Extracted a new cop `AmbiguousRegexpLiteral` from `Syntax` cop. It checks for ambiguous regexp literals in the first argument of a method invocation without parentheses. ([@yujinakayama][])
+* Extracted a new cop `UselessElseWithoutRescue` from `Syntax` cop. It checks for useless `else` in `begin..end` without `rescue`. ([@yujinakayama][])
+* Extracted a new cop `InvalidCharacterLiteral` from `Syntax` cop. It checks for invalid character literals with a non-escaped whitespace character (e.g. `? `). ([@yujinakayama][])
+* Removed `Syntax` cop from the configuration. It no longer can be disabled and it reports only invalid syntax offences. ([@yujinakayama][])
+* [#688](https://github.com/bbatsov/rubocop/issues/688): Output from `rubocop --show-cops` now looks like a YAML configuration file. The `--show-cops` option takes a comma separated list of cops as optional argument. ([@jonas054][])
+* New cop `IndentationConsistency` extracted from `IndentationWidth`, which has checked two kinds of offences until now. ([@jonas054][])
+
+### Bugs fixed
+
+* [#698](https://github.com/bbatsov/rubocop/pull/698): Support Windows paths on command-line. ([@rifraf][])
+* [#498](https://github.com/bbatsov/rubocop/issues/498): Disable terminal ANSI escape sequences when a formatter's output is not a TTY. ([@yujinakayama][])
+* [#703](https://github.com/bbatsov/rubocop/issues/703): BracesAroundHashParameters auto-correction broken with trailing comma. ([@jonas054][])
+* [#709](https://github.com/bbatsov/rubocop/issues/709): When `EndAlignment` has configuration `AlignWith: variable`, it now handles `@@a = if ...` and `a, b = if ...`. ([@jonas054][])
+* `SpaceAroundOperators` now reports an offence for `@@a=0`. ([@jonas054][])
+* [#707](https://github.com/bbatsov/rubocop/issues/707): Fix error on operator assignments in top level scope in `UselessAssignment`. ([@yujinakayama][])
+* Fix a bug where some offences were discarded when any cop that has specific target file path (by `Include` or `Exclude` under each cop configuration) had run. ([@yujinakayama][])
+* [#724](https://github.com/bbatsov/rubocop/issues/724): Accept colons denoting required keyword argument (a new feature in Ruby 2.1) without trailing space in `SpaceAfterColon`. ([@jonas054][])
+* The `--no-color` option works again. ([@jonas054][])
+* [#716](https://github.com/bbatsov/rubocop/issues/716): Fixed a regression in the auto-correction logic of `MethodDefParentheses`. ([@bbatsov][])
+* Inspected projects that lack a `.rubocop.yml` file, and therefore get their configuration from RuboCop's `config/default.yml`, no longer get configuration from RuboCop's `.rubocop.yml` and `rubocop-todo.yml`. ([@jonas054][])
+* [#730](https://github.com/bbatsov/rubocop/issues/730): `EndAlignment` now handles for example `private def some_method`, which is allowed in Ruby 2.1. It requires `end` to be aligned with `private`, not `def`, in such cases. ([@jonas054][])
+* [#744](https://github.com/bbatsov/rubocop/issues/744): Any new offences created by `--auto-correct` are now handled immediately and corrected when possible, so running `--auto-correct` once is enough. ([@jonas054][])
+* [#748](https://github.com/bbatsov/rubocop/pull/748): Auto-correction conflict between `EmptyLinesAroundBody` and `TrailingWhitespace` resolved. ([@jonas054][])
+* `ParenthesesAroundCondition` no longer crashes on parentheses around the condition in a ternary if. ([@jonas054][])
+* [#738](https://github.com/bbatsov/rubocop/issues/738): Fix a false positive in `StringLiterals`. ([@bbatsov][])
+
+## 0.16.0 (25/12/2013)
+
+### New features
+
+* [#612](https://github.com/bbatsov/rubocop/pull/612): `BracesAroundHashParameters` cop does auto-correction. ([@dblock][])
+* [#614](https://github.com/bbatsov/rubocop/pull/614): `ParenthesesAroundCondition` cop does auto-correction. ([@dblock][])
+* [#624](https://github.com/bbatsov/rubocop/pull/624): `EmptyLines` cop does auto-correction. ([@dblock][])
+* New Rails cop `DefaultScope` ensures `default_scope` is called properly with a block argument. ([@bbatsov][])
+* All cops now support the `Include` param, which specifies the files on which they should operate. ([@bbatsov][])
+* All cops now support the `Exclude` param, which specifies the files on which they should not operate. ([@bbatsov][])
+* [#631](https://github.com/bbatsov/rubocop/issues/631): `IndentationWidth` cop now detects inconsistent indentation between lines that should have the same indentation. ([@jonas054][])
+* [#649](https://github.com/bbatsov/rubocop/pull/649): `EmptyLinesAroundBody` cop does auto-correction. ([@dblock][])
+* [#657](https://github.com/bbatsov/rubocop/pull/657): `Alias` cop does auto-correction. ([@dblock][])
+* Rake task now support setting formatters. ([@pmenglund][])
+* [#653](https://github.com/bbatsov/rubocop/issues/653): `CaseIndentation` cop is now configurable with parameters `IndentWhenRelativeTo` and `IndentOneStep`. ([@jonas054][])
+* [#654](https://github.com/bbatsov/rubocop/pull/654): `For` cop is now configurable to enforce either `each` (default) or `for`. ([@jonas054][])
+* [#661](https://github.com/bbatsov/rubocop/issues/661): `EndAlignment` cop is now configurable for alignment with `keyword` (default) or `variable`. ([@jonas054][])
+* Allow to overwrite the severity of a cop with the new `Severity` param. ([@codez][])
+* New cop `FlipFlop` checks for flip flops. ([@agrimm][])
+* [#577](https://github.com/bbatsov/rubocop/issues/577): Introduced `MethodDefParentheses` to allow for for requiring either parentheses or no parentheses in method definitions. Replaces `DefWithoutParentheses`. ([@skanev][])
+* [#693](https://github.com/bbatsov/rubocop/pull/693): Generation of parameter values (i.e., not only `Enabled: false`) in `rubocop-todo.yml` by the `--auto-gen-config` option is now supported for some cops. ([@jonas054][])
+* New cop `AccessorMethodName` checks accessor method names for non-idiomatic names like `get_attribute` and `set_attribute`. ([@bbatsov][])
+* New cop `PredicateName` checks the names of predicate methods for non-idiomatic names like `is_something`, `has_something`, etc. ([@bbatsov][])
+* Support Ruby 2.1 with Parser 2.1. ([@yujinakayama][])
+
+### Changes
+
+* Removed `SymbolNames` as it was generating way too many false positives. ([@bbatsov][])
+* Renamed `ReduceArguments` to `SingleLineBlockParams` and made it configurable. ([@bbatsov][])
+
+### Bugs fixed
+
+* Handle properly heredocs in `StringLiterals` cop. ([@bbatsov][])
+* Fix `SpaceAroundOperators` to not report missing space around operator for `def self.method *args`. ([@jonas054][])
+* Properly handle `['AllCops']['Includes']` and `['AllCops']['Excludes']` when passing config via `-c`. ([@fancyremarker][], [@codez][])
+* [#611](https://github.com/bbatsov/rubocop/pull/611): Fix crash when loading an empty config file. ([@sinisterchipmunk][])
+* Fix `DotPosition` cop with `trailing` style for method calls on same line. ([@vonTronje][])
+* [#627](https://github.com/bbatsov/rubocop/pull/627): Fix counting of slashes in complicated regexps in `RegexpLiteral` cop. ([@jonas054][])
+* [#638](https://github.com/bbatsov/rubocop/issues/638): Fix bug in auto-correct that changes `each{ |x|` to `each d o |x|`. ([@jonas054][])
+* [#418](https://github.com/bbatsov/rubocop/issues/418): Stop searching for configuration files above the work directory of the isolated environment when running specs. ([@jonas054][])
+* Fix error on implicit match conditionals (e.g. `if /pattern/; end`) in `MultilineIfThen`. ([@agrimm][])
+* [#651](https://github.com/bbatsov/rubocop/issues/651): Handle properly method arguments in `RedundantSelf`. ([@bbatsov][])
+* [#628](https://github.com/bbatsov/rubocop/issues/628): Allow `self.Foo` in `RedundantSelf` cop. ([@chulkilee][])
+* [#668](https://github.com/bbatsov/rubocop/issues/668): Fix crash in `EndOfLine` that occurs when default encoding is `US_ASCII` and an inspected file has non-ascii characters. ([@jonas054][])
+* [#664](https://github.com/bbatsov/rubocop/issues/664): Accept oneline while when condition has local variable assignment. ([@emou][])
+* Fix auto-correct for `MethodDefParentheses` when parentheses are required. ([@skanev][])
+
+## 0.15.0 (06/11/2013)
+
+### New features
+
+* New cop `Output` checks for calls to print, puts, etc. in Rails. ([@daviddavis][])
+* New cop `EmptyLinesAroundBody` checks for empty lines around the bodies of class, method and module definitions. ([@bbatsov][])
+* `LeadingCommentSpace` cop does auto-correction. ([@jonas054][])
+* `SpaceAfterControlKeyword` cop does auto-correction. ([@jonas054][])
+* `SpaceAfterColon` cop does auto-correction. ([@jonas054][])
+* `SpaceAfterComma` cop does auto-correction. ([@jonas054][])
+* `SpaceAfterSemicolon` cop does auto-correction. ([@jonas054][])
+* `SpaceAfterMethodName` cop does auto-correction. ([@jonas054][])
+* `SpaceAroundBlockBraces` cop does auto-correction. ([@jonas054][])
+* `SpaceAroundEqualsInParameterDefault` cop does auto-correction. ([@jonas054][])
+* `SpaceAroundOperators` cop does auto-correction. ([@jonas054][])
+* `SpaceBeforeModifierKeyword` cop does auto-correction. ([@jonas054][])
+* `SpaceInsideHashLiteralBraces` cop does auto-correction. ([@jonas054][])
+* `SpaceInsideBrackets` cop does auto-correction. ([@jonas054][])
+* `SpaceInsideParens` cop does auto-correction. ([@jonas054][])
+* `TrailingWhitespace` cop does auto-correction. ([@jonas054][])
+* `TrailingBlankLines` cop does auto-correction. ([@jonas054][])
+* `FinalNewline` cop does auto-correction. ([@jonas054][])
+* New cop `CyclomaticComplexity` checks the cyclomatic complexity of methods against a configurable max value. ([@jonas054][])
+* [#594](https://github.com/bbatsov/rubocop/pull/594): New parameter `EnforcedStyleForEmptyBraces` with values `space` and `no_space` (default) added to `SpaceAroundBlockBraces`. ([@jonas054][])
+* [#603](https://github.com/bbatsov/rubocop/pull/603): New parameter `MinSize` added to `WordArray` to allow small string arrays, retaining the default (0). ([@claco][])
+
+### Changes
+
+* [#557](https://github.com/bbatsov/rubocop/pull/557): Configuration files for excluded files are no longer loaded. ([@jonas054][])
+* [#571](https://github.com/bbatsov/rubocop/pull/571): The default rake task now runs RuboCop over itself! ([@nevir][])
+* Encoding errors are reported as fatal offences rather than printed with red text. ([@jonas054][])
+* `AccessControl` cop is now configurable with the `EnforcedStyle` option. ([@sds][])
+* Split `AccessControl` cop to `AccessModifierIndentation` and `EmptyLinesAroundAccessModifier`. ([@bbatsov][])
+* [#594](https://github.com/bbatsov/rubocop/pull/594): Add configuration parameter `EnforcedStyleForEmptyBraces` to `SpaceInsideHashLiteralBraces` cop, and change `EnforcedStyleIsWithSpaces` (values `true`, `false`) to `EnforcedStyle` (values `space`, `no_space`). ([@jonas054][])
+* Coverage builds linked from the README page are enabled again. ([@jonas054][])
+
+### Bugs fixed
+
+* [#561](https://github.com/bbatsov/rubocop/pull/561): Handle properly negative literals in `NumericLiterals` cop. ([@bbatsov][])
+* [#567](https://github.com/bbatsov/rubocop/pull/567): Register an offence when the last hash parameter has braces in `BracesAroundHashParameters` cop. ([@dblock][])
+* `StringLiterals` cop no longer reports errors for character literals such as ?/. That should be done only by the `CharacterLiterals` cop. ([@jonas054][])
+* Made auto-correct much less likely to crash due to conflicting corrections ("clobbering"). ([@jonas054][])
+* [#565](https://github.com/bbatsov/rubocop/pull/565): `$GLOBAL_VAR from English library` should no longer be inserted when autocorrecting short-form global variables like `$!`. ([@nevir][])
+* [#566](https://github.com/bbatsov/rubocop/pull/566): Methods that just assign a splat to an ivar are no longer considered trivial writers. ([@nevir][])
+* [#585](https://github.com/bbatsov/rubocop/pull/585): `MethodCallParentheses` should allow methods starting with uppercase letter. ([@bbatsov][])
+* [#574](https://github.com/bbatsov/rubocop/issues/574): Fix error on multiple-assignment with non-array right hand side in `UselessSetterCall`. ([@yujinakayama][])
+* [#576](https://github.com/bbatsov/rubocop/issues/576): Output config validation warning to STDERR so that it won't be mixed up with formatter's output. ([@yujinakayama][])
+* [#599](https://github.com/bbatsov/rubocop/pull/599): `EndOfLine` cop is operational again. ([@jonas054][])
+* [#604](https://github.com/bbatsov/rubocop/issues/604): Fix error on implicit match conditionals (e.g. `if /pattern/; end`) in `FavorModifier`. ([@yujinakayama][])
+* [#600](https://github.com/bbatsov/rubocop/pull/600): Don't require an empty line for access modifiers at the beginning of class/module body. ([@bbatsov][])
+* [#608](https://github.com/bbatsov/rubocop/pull/608): `RescueException` no longer crashes when the namespace of a rescued class is in a local variable. ([@jonas054][])
+* [#173](https://github.com/bbatsov/rubocop/issues/173): Allow the use of `alias` in the body of an `instance_exec`. ([@bbatsov][])
+* [#554](https://github.com/bbatsov/rubocop/issues/554): Handle properly multi-line arrays with comments in them in `WordArray`. ([@bbatsov][])
+
+## 0.14.1 (10/10/2013)
+
+### New features
+
+* [#551](https://github.com/bbatsov/rubocop/pull/551): New cop `BracesAroundHashParameters` checks for braces in function calls with hash parameters. ([@dblock][])
+* New cop `SpaceAfterNot` tracks redundant space after the `!` operator. ([@bbatsov][])
+
+### Bugs fixed
+
+* Fix bug concerning table and separator alignment of multi-line hash with multiple keys on the same line. ([@jonas054][])
+* [#550](https://github.com/bbatsov/rubocop/pull/550): Fix a bug where `ClassLength` counted lines of inner classes/modules. ([@yujinakayama][])
+* [#550](https://github.com/bbatsov/rubocop/pull/550): Fix a false positive for namespace class in `Documentation`. ([@yujinakayama][])
+* [#556](https://github.com/bbatsov/rubocop/pull/556): Fix "Parser::Source::Range spans more than one line" bug in clang formatter. ([@yujinakayama][])
+* [#552](https://github.com/bbatsov/rubocop/pull/552): `RaiseArgs` allows exception constructor calls with more than one 1 argument. ([@bbatsov][])
+
+## 0.14.0 (07/10/2013)
+
+### New features
+
+* [#491](https://github.com/bbatsov/rubocop/issues/491): New cop `MethodCalledOnDoEndBlock` keeps track of methods called on `do`...`end` blocks.
+* [#456](https://github.com/bbatsov/rubocop/issues/456): New configuration parameter `AllCops`/`RunRailsCops` can be set to `true` for a project, removing the need to give the `-R`/`--rails` option with every invocation of `rubocop`.
+* [#501](https://github.com/bbatsov/rubocop/issues/501): `simple`/`clang`/`progress`/`emacs` formatters now print `[Corrected]` along with offence message when the offence is automatically corrected.
+* [#501](https://github.com/bbatsov/rubocop/issues/501): `simple`/`clang`/`progress` formatters now print count of auto-corrected offences in the final summary.
+* [#501](https://github.com/bbatsov/rubocop/issues/501): `json` formatter now outputs `corrected` key with boolean value in offence objects whether the offence is automatically corrected.
+* New cop `ClassLength` checks for overly long class definitions.
+* New cop `Debugger` checks for forgotten calls to debugger or pry.
+* New cop `RedundantException` checks for code like `raise RuntimeError, message`.
+* [#526](https://github.com/bbatsov/rubocop/issues/526): New cop `RaiseArgs` checks the args passed to `raise/fail`.
+
+### Changes
+
+* Cop `MethodAndVariableSnakeCase` replaced by `MethodName` and `VariableName`, both having the configuration parameter `EnforcedStyle` with values `snake_case` (default) and `camelCase`.
+* [#519](https://github.com/bbatsov/rubocop/issues/519): `HashSyntax` cop is now configurable and can enforce the use of the classic hash rockets syntax.
+* [#520](https://github.com/bbatsov/rubocop/issues/520): `StringLiterals` cop is now configurable and can enforce either single-quoted or double-quoted strings.
+* [#528](https://github.com/bbatsov/rubocop/issues/528): Added a config option to `RedundantReturn` to allow a `return` with multiple values.
+* [#524](https://github.com/bbatsov/rubocop/issues/524): Added a config option to `Semicolon` to allow the use of `;` as an expression separator.
+* [#525](https://github.com/bbatsov/rubocop/issues/525): `SignalException` cop is now configurable and can enforce the semantic rule or an exclusive use of `raise` or `fail`.
+* `LambdaCall` is now configurable and enforce either `Proc#call` or `Proc#()`.
+* [#529](https://github.com/bbatsov/rubocop/issues/529): Added config option `EnforcedStyle` to `SpaceAroundBraces`.
+* [#529](https://github.com/bbatsov/rubocop/issues/529): Changed config option `NoSpaceBeforeBlockParameters` to `SpaceBeforeBlockParameters`.
+* Support Parser 2.0.0 (non-beta).
+
+### Bugs fixed
+
+* [#514](https://github.com/bbatsov/rubocop/issues/514): Fix alignment of the hash containing different key lengths in one line.
+* [#496](https://github.com/bbatsov/rubocop/issues/496): Fix corner case crash in `AlignHash` cop: single key/value pair when configuration is `table` for '=>' and `separator` for `:`.
+* [#502](https://github.com/bbatsov/rubocop/issues/502): Don't check non-decimal literals with `NumericLiterals`.
+* [#448](https://github.com/bbatsov/rubocop/issues/448): Fix auto-correction of parameters spanning more than one line in `AlignParameters` cop.
+* [#493](https://github.com/bbatsov/rubocop/issues/493): Support disabling `Syntax` offences with `warning` severity.
+* Fix bug appearing when there were different values for the `AllCops`/`RunRailsCops` configuration parameter in different directories.
+* [#512](https://github.com/bbatsov/rubocop/issues/512): Fix bug causing crash in `AndOr` auto-correction.
+* [#515](https://github.com/bbatsov/rubocop/issues/515): Fix bug causing `AlignParameters` and `AlignArray` auto-correction to destroy code.
+* [#516](https://github.com/bbatsov/rubocop/issues/516): Fix bug causing `RedundantReturn` auto-correction to produce invalid code.
+* [#527](https://github.com/bbatsov/rubocop/issues/527): Handle `!=` expressions in `EvenOdd` cop.
+* `SignalException` cop now finds `raise` calls anywhere, not only in `begin` sections.
+* [#538](https://github.com/bbatsov/rubocop/issues/538): Fix bug causing `Blocks` auto-correction to produce invalid code.
+
+## 0.13.1 (19/09/2013)
+
+### New features
+
+* `HashSyntax` cop does auto-correction.
+* [#484](https://github.com/bbatsov/rubocop/pull/484): Allow calls to self to fix name clash with argument.
+* Renamed `SpaceAroundBraces` to `SpaceAroundBlockBraces`.
+* `SpaceAroundBlockBraces` now has a `NoSpaceBeforeBlockParameters` config option to enforce a style for blocks with parameters like `{|foo| puts }`.
+* New cop `LambdaCall` tracks uses of the obscure `lambda.(...)` syntax.
+
+### Bugs fixed
+
+* Fix crash on empty input file in `FinalNewline`.
+* [#485](https://github.com/bbatsov/rubocop/issues/485): Fix crash on multiple-assignment and op-assignment in `UselessSetterCall`.
+* [#497](https://github.com/bbatsov/rubocop/issues/497): Fix crash in `UselessComparison` and `NilComparison`.
+
+## 0.13.0 (13/09/2013)
+
+### New features
+
+* New configuration parameter `AllowAdjacentOneLineDefs` for `EmptyLineBetweenDefs`.
+* New cop `MultilineBlockChain` keeps track of chained blocks spanning multiple lines.
+* `RedundantSelf` cop does auto-correction.
+* `AvoidPerlBackrefs` cop does auto-correction.
+* `AvoidPerlisms` cop does auto-correction.
+* `RedundantReturn` cop does auto-correction.
+* `Blocks` cop does auto-correction.
+* New cop `TrailingBlankLines` keeps track of extra blanks lines at the end of source file.
+* New cop `AlignHash` keeps track of bad alignment in multi-line hash literals.
+* New cop `AlignArray` keeps track of bad alignment in multi-line array literals.
+* New cop `SpaceBeforeModifierKeyword` keeps track of missing space before a modifier keyword (`if`, `unless`, `while`, `until`).
+* New cop `FinalNewline` keeps tracks of the required final newline in a source file.
+* Highlightling corrected in `SpaceInsideHashLiteralBraces` and `SpaceAroundBraces` cops.
+
+### Changes
+
+* [#447](https://github.com/bbatsov/rubocop/issues/447): `BlockAlignment` cop now allows `end` to be aligned with the start of the line containing `do`.
+* `SymbolName` now has an `AllowDots` config option to allow symbols like `:'whatever.submit_button'`.
+* [#469](https://github.com/bbatsov/rubocop/issues/469): Extracted useless setter call tracking part of `UselessAssignment` cop to `UselessSetterCall`.
+* [#469](https://github.com/bbatsov/rubocop/issues/469): Merged `UnusedLocalVariable` cop into `UselessAssignment`.
+* [#458](https://github.com/bbatsov/rubocop/issues/458): The merged `UselessAssignment` cop now has advanced logic that tracks not only assignment at the end of the method but also every assignment in every scope.
+* [#466](https://github.com/bbatsov/rubocop/issues/466): Allow built-in JRuby global vars in `AvoidGlobalVars`.
+* Added a config option `AllowedVariables` to `AvoidGlobalVars` to allow users to whitelist certain global variables.
+* Renamed `AvoidGlobalVars` to `GlobalVars`.
+* Renamed `AvoidPerlisms` to `SpecialGlobalVars`.
+* Renamed `AvoidFor` to `For`.
+* Renamed `AvoidClassVars` to `ClassVars`.
+* Renamed `AvoidPerlBackrefs` to `PerlBackrefs`.
+* `NumericLiterals` now accepts a config param `MinDigits` - the minimal number of digits in the integer portion of number for the cop to check it.
+
+### Bugs fixed
+
+* [#449](https://github.com/bbatsov/rubocop/issues/449): Remove whitespaces between condition and `do` with `WhileUntilDo` auto-correction.
+* Continue with file inspection after parser warnings. Give up only on syntax errors.
+* Don't trigger the HashSyntax cop on digit-starting keys.
+* Fix crashes while inspecting class definition subclassing another class stored in a local variable in `UselessAssignment` (formerly of `UnusedLocalVariable`) and `ShadowingOuterLocalVariable` (like `clazz = Array; class SomeClass < clazz; end`).
+* [#463](https://github.com/bbatsov/rubocop/issues/463): Do not warn if using destructuring in second `reduce` argument (`ReduceArguments`).
+
+## 0.12.0 (23/08/2013)
+
+### New features
+
+* [#439](https://github.com/bbatsov/rubocop/issues/439): Added formatter 'OffenceCount' which outputs a summary list of cops and their offence count.
+* [#395](https://github.com/bbatsov/rubocop/issues/395): Added `--show-cops` option to show available cops.
+* New cop `NilComparison` keeps track of comparisons like `== nil`.
+* New cop `EvenOdd` keeps track of occasions where `Fixnum#even?` or `Fixnum#odd?` should have been used (like `x % 2 == 0`).
+* New cop `IndentationWidth` checks for files using indentation that is not two spaces.
+* New cop `SpaceAfterMethodName` keeps track of method definitions with a space between the method name and the opening parenthesis.
+* New cop `ParenthesesAsGroupedExpression` keeps track of method calls with a space before the opening parenthesis.
+* New cop `HashMethods` keeps track of uses of deprecated `Hash` methods.
+* New Rails cop `HasAndBelongsToMany` checks for uses of `has_and_belongs_to_many`.
+* New Rails cop `ReadAttribute` tracks uses of `read_attribute`.
+* `Attr` cop does auto-correction.
+* `CollectionMethods` cop does auto-correction.
+* `SignalException` cop does auto-correction.
+* `EmptyLiteral` cop does auto-correction.
+* `MethodCallParentheses` cop does auto-correction.
+* `DefWithParentheses` cop does auto-correction.
+* `DefWithoutParentheses` cop does auto-correction.
+
+### Changes
+
+* Dropped `-s`/`--silent` option. Now `progress`/`simple`/`clang` formatters always report summary and `emacs`/`files` formatters no longer report.
+* Dropped the `LineContinuation` cop.
+
+### Bugs fixed
+
+* [#432](https://github.com/bbatsov/rubocop/issues/432): Fix false positive for constant assignments when rhs is a method call with block in `ConstantName`.
+* [#434](https://github.com/bbatsov/rubocop/issues/434): Support classes and modules defined with `Class.new`/`Module.new` in `AccessControl`.
+* Fix which ranges are highlighted in reports from IfUnlessModifier, WhileUntilModifier, and MethodAndVariableSnakeCase cop.
+* [#438](https://github.com/bbatsov/rubocop/issues/438): Accept setting attribute on method argument in `UselessAssignment`.
+
+## 0.11.1 (12/08/2013)
+
+### Changes
+
+* [#425](https://github.com/bbatsov/rubocop/issues/425): `ColonMethodCalls` now allows constructor methods (like `Nokogiri::HTML()` to be called with double colon.
+
+### Bugs fixed
+
+* [#427](https://github.com/bbatsov/rubocop/issues/427): FavorUnlessOverNegatedIf triggered when using elsifs.
+* [#429](https://github.com/bbatsov/rubocop/issues/429): Fix `LeadingCommentSpace` offence reporting.
+* Fixed `AsciiComments` offence reporting.
+* Fixed `BlockComments` offence reporting.
+
+## 0.11.0 (09/08/2013)
+
+### New features
+
+* [#421](https://github.com/bbatsov/rubocop/issues/421): `TrivialAccessors` now ignores methods on user-configurable whitelist (such as `to_s` and `to_hash`).
+* [#369](https://github.com/bbatsov/rubocop/issues/369): New option `--auto-gen-config` outputs RuboCop configuration that disables all cops that detect any offences.
+* The list of annotation keywords recognized by the `CommentAnnotation` cop is now configurable.
+* Configuration file names are printed as they are loaded in `--debug` mode.
+* Auto-correct support added in `AlignParameters` cop.
+* New cop `UselessComparison` checks for comparisons of the same arguments.
+* New cop `UselessAssignment` checks for useless assignments to local variables.
+* New cop `SignalException` checks for proper usage of `fail` and `raise`.
+* New cop `ModuleFunction` checks for usage of `extend self` in modules.
+
+### Bugs fixed
+
+* [#374](https://github.com/bbatsov/rubocop/issues/374): Fixed error at post condition loop (`begin-end-while`, `begin-end-until`) in `UnusedLocalVariable` and `ShadowingOuterLocalVariable`.
+* [#373](https://github.com/bbatsov/rubocop/issues/373) and [#376](https://github.com/bbatsov/rubocop/issues/376): Allow braces around multi-line blocks if `do`-`end` would change the meaning of the code.
+* `RedundantSelf` now allows `self.` followed by any ruby keyword.
+* [#391](https://github.com/bbatsov/rubocop/issues/391): Fix bug in counting slashes in a regexp.
+* [#394](https://github.com/bbatsov/rubocop/issues/394): `DotPosition` cop handles correctly code like `l.(1)`.
+* [#390](https://github.com/bbatsov/rubocop/issues/390): `CommentAnnotation` cop allows keywords (e.g. Review, Optimize) if they just begin a sentence.
+* [#400](https://github.com/bbatsov/rubocop/issues/400): Fix bug concerning nested defs in `EmptyLineBetweenDefs` cop.
+* [#399](https://github.com/bbatsov/rubocop/issues/399): Allow assignment inside blocks in `AssignmentInCondition` cop.
+* Fix bug in favor_modifier.rb regarding missed offences after else etc.
+* [#393](https://github.com/bbatsov/rubocop/issues/393): Retract support for multiline chaining of blocks (which fixed [#346](https://github.com/bbatsov/rubocop/issues/346)), thus rejecting issue 346.
+* [#389](https://github.com/bbatsov/rubocop/issues/389): Ignore symbols that are arguments to Module#private_constant in `SymbolName` cop.
+* [#387](https://github.com/bbatsov/rubocop/issues/387): Do autocorrect in `AndOr` cop only if it does not change the meaning of the code.
+* [#398](https://github.com/bbatsov/rubocop/issues/398): Don't display blank lines in the output of the clang formatter.
+* [#283](https://github.com/bbatsov/rubocop/issues/283): Refine `StringLiterals` string content check.
+
+## 0.10.0 (17/07/2013)
+
+### New features
+
+* New cop `RedundantReturn` tracks redundant `return`s in method bodies.
+* New cop `RedundantBegin` tracks redundant `begin` blocks in method definitions.
+* New cop `RedundantSelf` tracks redundant uses of `self`.
+* New cop `EmptyEnsure` tracks empty `ensure` blocks.
+* New cop `CommentAnnotation` tracks formatting of annotation comments such as TODO.
+* Added custom rake task.
+* New formatter `FileListFormatter` outputs just a list of files with offences in them (related to [#357](https://github.com/bbatsov/rubocop/issues/357)).
+
+### Changes
+
+* `TrivialAccessors` now has an `ExactNameMatch` config option (related to [#308](https://github.com/bbatsov/rubocop/issues/308)).
+* `TrivialAccessors` now has an `ExcludePredicates` config option (related to [#326](https://github.com/bbatsov/rubocop/issues/326)).
+* Cops don't inherit from `Parser::AST::Rewriter` anymore. All 3rd party Cops should remove the call to `super` in their callbacks. If you implement your own processing you need to define the `#investigate` method instead of `#inspect`. Refer to the documentation of `Cop::Commissioner` and `Cop::Cop` classes for more information.
+* `EndAlignment` cop split into `EndAlignment` and `BlockAlignment` cops.
+
+### Bugs fixed
+
+* [#288](https://github.com/bbatsov/rubocop/issues/288): Work with absolute Excludes paths internally (2nd fix for this issue).
+* `TrivialAccessors` now detects class attributes as well as instance attributes.
+* [#338](https://github.com/bbatsov/rubocop/issues/338): Fix end alignment of blocks in chained assignments.
+* [#345](https://github.com/bbatsov/rubocop/issues/345): Add `$SAFE` to the list of built-in global variables.
+* [#340](https://github.com/bbatsov/rubocop/issues/340): Override config parameters rather than merging them.
+* [#349](https://github.com/bbatsov/rubocop/issues/349): Fix false positive for `CharacterLiteral` (`%w(?)`).
+* [#346](https://github.com/bbatsov/rubocop/issues/346): Support method chains for block end alignment checks.
+* [#350](https://github.com/bbatsov/rubocop/issues/350): Support line breaks between variables on left hand side for block end alignment checks.
+* [#356](https://github.com/bbatsov/rubocop/issues/356): Allow safe assignment in `ParenthesesAroundCondition`.
+
+### Misc
+
+* Improved performance on Ruby 1.9 by about 20%.
+* Improved overall performance by about 35%.
+
+## 0.9.1 (05/07/2013)
+
+### New features
+
+* Added `-l/--lint` option to allow doing only linting with no style checks (similar to running `ruby -wc`).
+
+### Changes
+
+* Removed the `BlockAlignSchema` configuration option from `EndAlignment`. We now support only the default alignment schema - `StartOfAssignment`.
+* Made the preferred collection methods in `CollectionMethods` configurable.
+* Made the `DotPosition` cop configurable - now both `leading` and `trailing` styles are supported.
+
+### Bugs fixed
+
+* [#318](https://github.com/bbatsov/rubocop/issues/318): Correct some special cases of block end alignment.
+* [#317](https://github.com/bbatsov/rubocop/issues/317): Fix a false positive in `LiteralInCondition`.
+* [#321](https://github.com/bbatsov/rubocop/issues/321): Ignore variables whose name start with `_` in `ShadowingOuterLocalVariable`.
+* [#322](https://github.com/bbatsov/rubocop/issues/322): Fix exception of `UnusedLocalVariable` and `ShadowingOuterLocalVariable` when inspecting keyword splat argument.
+* [#316](https://github.com/bbatsov/rubocop/issues/316): Correct nested postfix unless in `MultilineIfThen`.
+* [#327](https://github.com/bbatsov/rubocop/issues/327): Fix false offences for block expression that span on two lines in `EndAlignment`.
+* [#332](https://github.com/bbatsov/rubocop/issues/332): Fix exception of `UnusedLocalVariable` and `ShadowingOuterLocalVariable` when inspecting named captures.
+* [#333](https://github.com/bbatsov/rubocop/issues/333): Fix a case that `EnsureReturn` throws an exception when ensure has no body.
+
+## 0.9.0 (01/07/2013)
+
+### New features
+
+* Introduced formatter feature, enables custom formatted output and multiple outputs.
+* Added progress formatter and now it's the default. (`--format progress`).
+* Added JSON formatter. (`--format json`).
+* Added clang style formatter showing the offending source. code. (`--format clang`). The `clang` formatter marks a whole range rather than just the starting position, to indicate more clearly where the problem is.
+* Added `-f`/`--format` option to specify formatter.
+* Added `-o`/`--out` option to specify output file for each formatter.
+* Added `-r/--require` option to inject external Ruby code into RuboCop.
+* Added `-V/--verbose-version` option that displays Parser version and Ruby version as well.
+* Added `-R/--rails` option that enables extra Rails-specific cops.
+* Added support for auto-correction of some offences with `-a`/`--auto-correct`.
+* New cop `CaseEquality` checks for explicit use of `===`.
+* New cop `AssignmentInCondition` checks for assignment in conditions.
+* New cop `EndAlignment` tracks misaligned `end` keywords.
+* New cop `Void` tracks uses of literals/variables/operators in possibly void context.
+* New cop `Documentation` checks for top level class/module doc comments.
+* New cop `UnreachableCode` tracks unreachable code segments.
+* New cop `MethodCallParentheses` tracks unwanted braces in method calls.
+* New cop `UnusedLocalVariable` tracks unused local variables for each scope.
+* New cop `ShadowingOuterLocalVariable` tracks use of the same name as outer local variables for block arguments or block local variables.
+* New cop `WhileUntilDo` tracks uses of `do` with multi-line `while/until`.
+* New cop `CharacterLiteral` tracks uses of character literals (`?x`).
+* New cop `EndInMethod` tracks uses of `END` in method definitions.
+* New cop `LiteralInCondition` tracks uses of literals in the conditions of `if/while/until`.
+* New cop `BeginBlock` tracks uses of `BEGIN` blocks.
+* New cop `EndBlock` tracks uses of `END` blocks.
+* New cop `DotPosition` tracks the dot position in multi-line method calls.
+* New cop `Attr` tracks uses of `Module#attr`.
+* Add support for auto-correction of some offences with `-a`/`--auto-correct`.
+
+### Changes
+
+* Deprecated `-e`/`--emacs` option. (Use `--format emacs` instead).
+* Made `progress` formatter the default.
+* Most formatters (`progress`, `simple` and `clang`) now print relative file paths if the paths are under the current working directory.
+* Migrate all cops to new namespaces. `Rubocop::Cop::Lint` is for cops that emit warnings. `Rubocop::Cop::Style` is for cops that do not belong in other namespaces.
+* Merge `FavorPercentR` and `PercentR` into one cop called `RegexpLiteral`, and add configuration parameter `MaxSlashes`.
+* Add `CountKeywordArgs` configuration option to `ParameterLists` cop.
+
+### Bugs fixed
+
+* [#239](https://github.com/bbatsov/rubocop/issues/239): Fixed double quotes false positives.
+* [#233](https://github.com/bbatsov/rubocop/issues/233): Report syntax cop offences.
+* Fix off-by-one error in favor_modifier.
+* [#229](https://github.com/bbatsov/rubocop/issues/229): Recognize a line with CR+LF as a blank line in AccessControl cop.
+* [#235](https://github.com/bbatsov/rubocop/issues/235): Handle multiple constant assignment in ConstantName cop.
+* [#246](https://github.com/bbatsov/rubocop/issues/246): Correct handling of unicode escapes within double quotes.
+* Fix crashes in Blocks, CaseEquality, CaseIndentation, ClassAndModuleCamelCase, ClassMethods, CollectionMethods, and ColonMethodCall.
+* [#263](https://github.com/bbatsov/rubocop/issues/263): Do not check for space around operators called with method syntax.
+* [#271](https://github.com/bbatsov/rubocop/issues/271): Always allow line breaks inside hash literal braces.
+* [#270](https://github.com/bbatsov/rubocop/issues/270): Fixed a false positive in ParenthesesAroundCondition.
+* [#288](https://github.com/bbatsov/rubocop/issues/288): Get config parameter AllCops/Excludes from highest config file in path.
+* [#276](https://github.com/bbatsov/rubocop/issues/276): Let columns start at 1 instead of 0 in all output of column numbers.
+* [#292](https://github.com/bbatsov/rubocop/issues/292): Don't check non-regular files (like sockets, etc).
+* Fix crashes in WordArray on arrays of character literals such as `[?\r, ?\n]`.
+* Fix crashes in Documentation on empty modules.
+
+## 0.8.3 (18/06/2013)
+
+### Bug fixes
+
+* Lock Parser dependency to version 2.0.0.beta5.
+
+## 0.8.2 (06/05/2013)
+
+### New features
+
+* New cop `BlockNesting` checks for excessive block nesting.
+
+### Bug fixes
+
+* Correct calculation of whether a modifier version of a conditional statement will fit.
+* Fix an error in `MultilineIfThen` cop that occurred in some special cases.
+* [#231](https://github.com/bbatsov/rubocop/issues/231): Fix a false positive for modifier if.
+
+## 0.8.1 (05/30/2013)
+
+### New features
+
+* New cop `Proc` tracks uses of `Proc.new`.
+
+### Changes
+
+* Renamed `NewLambdaLiteral` to `Lambda`.
+* Aligned the `Lambda` cop more closely to the style guide - it now allows the use of `lambda` for multi-line blocks.
+
+### Bugs fixed
+
+* [#210](https://github.com/bbatsov/rubocop/issues/210): Fix a false positive for double quotes in regexp literals.
+* [#211](https://github.com/bbatsov/rubocop/issues/211): Fix a false positive for `initialize` method looking like a trivial writer.
+* [#215](https://github.com/bbatsov/rubocop/issues/215): Fixed a lot of modifier `if/unless/while/until` issues.
+* [#213](https://github.com/bbatsov/rubocop/issues/213): Make sure even disabled cops get their configuration set.
+* [#214](https://github.com/bbatsov/rubocop/issues/214): Fix SpaceInsideHashLiteralBraces to handle string interpolation right.
+
+## 0.8.0 (05/28/2013)
+
+### Changes
+
+* Folded `ArrayLiteral` and `HashLiteral` into `EmptyLiteral` cop.
+* The maximum number of params `ParameterLists` accepts in now configurable.
+* Reworked `SymbolSnakeCase` into `SymbolName`, which has an option `AllowCamelCase` enabled by default.
+* Migrated from `Ripper` to the portable [Parser](https://github.com/whitequark/parser).
+
+### New features
+
+* New cop `ConstantName` checks for constant which are not using `SCREAMING_SNAKE_CASE`.
+* New cop `AccessControl` checks private/protected indentation and surrounding blank lines.
+* New cop `Loop` checks for `begin/end/while(until)` and suggests the use of `Kernel#loop`.
+
+## 0.7.2 (05/13/2013)
+
+### Bugs fixed
+
+* [#155](https://github.com/bbatsov/rubocop/issues/155): 'Do not use semicolons to terminate expressions.' is not implemented correctly.
+* `OpMethod` now handles definition of unary operators without crashing.
+* `SymbolSnakeCase` now handles aliasing of operators without crashing.
+* `RescueException` now handles the splat operator `*` in a `rescue` clause without crashing.
+* [#159](https://github.com/bbatsov/rubocop/issues/159): AvoidFor cop misses many violations.
+
+## 0.7.1 (05/11/2013)
+
+### Bugs fixed
+
+* Added missing files to the gemspec.
+
+## 0.7.0 (05/11/2013)
+
+### New features
+
+* Added ability to include or exclude files/directories through `.rubocop.yml`.
+* Added option --only for running a single cop.
+* Relax semicolon rule for one line methods, classes and modules.
+* Configuration files, such as `.rubocop.yml`, can now include configuration from other files through the `inherit_from` directive. All configuration files implicitly inherit from `config/default.yml`.
+* New cop `ClassMethods` checks for uses for class/module names in definitions of class/module methods.
+* New cop `SingleLineMethods` checks for methods implemented on a single line.
+* New cop `FavorJoin` checks for usages of `Array#*` with a string argument.
+* New cop `BlockComments` tracks uses of block comments(`=begin/=end` comments).
+* New cop `EmptyLines` tracks consecutive blank lines.
+* New cop `WordArray` tracks arrays of words.
+* [#108](https://github.com/bbatsov/rubocop/issues/108): New cop `SpaceInsideHashLiteralBraces` checks for spaces inside hash literal braces - style is configurable.
+* New cop `LineContinuation` tracks uses of the line continuation character (`\`).
+* New cop `SymbolArray` tracks arrays of symbols.
+* Print warnings for unrecognized names in configuration files.
+* New cop `TrivialAccessors` tracks method definitions that could be automatically generated with `attr_*` methods.
+* New cop `LeadingCommentSpace` checks for missing space after `#` in comments.
+* New cop `ColonMethodCall` tracks uses of `::` for method calls.
+* New cop `AvoidGlobalVars` tracks uses of non built-in global variables.
+* New cop `SpaceAfterControlKeyword` tracks missing spaces after `if/elsif/case/when/until/unless/while`.
+* New cop `Not` tracks uses of the `not` keyword.
+* New cop `Eval` tracks uses of the `eval` function.
+
+### Bugs fixed
+
+* [#101](https://github.com/bbatsov/rubocop/issues/101): `SpaceAroundEqualsInParameterDefault` doesn't work properly with empty string.
+* Fix `BraceAfterPercent` for `%W`, `%i` and `%I` and added more tests.
+* Fix a false positive in the `Alias` cop. `:alias` is no longer treated as keyword.
+* `ArrayLiteral` now properly detects `Array.new`.
+* `HashLiteral` now properly detects `Hash.new`.
+* `VariableInterpolation` now detects regexp back references and doesn't crash.
+* Don't generate pathnames like some/project//some.rb.
+* [#151](https://github.com/bbatsov/rubocop/issues/151): Don't print the unrecognized cop warning several times for the same `.rubocop.yml`.
+
+### Misc
+
+* Renamed `Indentation` cop to `CaseIndentation` to avoid confusion.
+* Renamed `EmptyLines` cop to `EmptyLineBetweenDefs` to avoid confusion.
+
+## 0.6.1 (04/28/2013)
+
+### New features
+
+* Split `AsciiIdentifiersAndComments` cop in two separate cops.
+
+### Bugs fixed
+
+* [#90](https://github.com/bbatsov/rubocop/issues/90): Two cops crash when scanning code using `super`.
+* [#93](https://github.com/bbatsov/rubocop/issues/93): Issue with `whitespace?': undefined method`.
+* [#97](https://github.com/bbatsov/rubocop/issues/97): Build fails.
+* [#100](https://github.com/bbatsov/rubocop/issues/100): `OpMethod` cop doesn't work if method arg is not in braces.
+* `SymbolSnakeCase` now tracks Ruby 1.9 hash labels as well as regular symbols.
+
+### Misc
+
+* [#88](https://github.com/bbatsov/rubocop/issues/88): Abort gracefully when interrupted with Ctrl-C.
+* No longer crashes on bugs within cops. Now problematic checks are skipped and a message is displayed.
+* Replaced `Term::ANSIColor` with `Rainbow`.
+* Add an option to disable colors in the output.
+* Cop names are now displayed alongside messages when `-d/--debug` is passed.
+
+## 0.6.0 (04/23/2013)
+
+### New features
+
+* New cop `ReduceArguments` tracks argument names in reduce calls.
+* New cop `MethodLength` tracks number of LOC (lines of code) in methods.
+* New cop `RescueModifier` tracks uses of `rescue` in modifier form.
+* New cop `PercentLiterals` tracks uses of `%q`, `%Q`, `%s` and `%x`.
+* New cop `BraceAfterPercent` tracks uses of % literals with delimiters other than ().
+* Support for disabling cops locally in a file with rubocop:disable comments.
+* New cop `EnsureReturn` tracks usages of `return` in `ensure` blocks.
+* New cop `HandleExceptions` tracks suppressed exceptions.
+* New cop `AsciiIdentifiersAndComments` tracks uses of non-ascii characters in identifiers and comments.
+* New cop `RescueException` tracks uses of rescuing the `Exception` class.
+* New cop `ArrayLiteral` tracks uses of Array.new.
+* New cop `HashLiteral` tracks uses of Hash.new.
+* New cop `OpMethod` tracks the argument name in operator methods.
+* New cop `PercentR` tracks uses of %r literals with zero or one slash in the regexp.
+* New cop `FavorPercentR` tracks uses of // literals with more than one slash in the regexp.
+
+### Bugs fixed
+
+* [#62](https://github.com/bbatsov/rubocop/issues/62): Config files in ancestor directories are ignored if another exists in home directory.
+* [#65](https://github.com/bbatsov/rubocop/issues/65): Suggests to convert symbols `:==`, `:<=>` and the like to snake_case.
+* [#66](https://github.com/bbatsov/rubocop/issues/66): Does not crash on unreadable or unparseable files.
+* [#70](https://github.com/bbatsov/rubocop/issues/70): Support `alias` with bareword arguments.
+* [#64](https://github.com/bbatsov/rubocop/issues/64): Performance issue with Bundler.
+* [#75](https://github.com/bbatsov/rubocop/issues/75): Make it clear that some global variables require the use of the English library.
+* [#79](https://github.com/bbatsov/rubocop/issues/79): Ternary operator missing whitespace detection.
+
+### Misc
+
+* Dropped Jeweler for gem release management since it's no longer actively maintained.
+* Handle pluralization properly in the final summary.
+
+## 0.5.0 (04/17/2013)
+
+### New features
+
+* New cop `FavorSprintf` that checks for usages of `String#%`.
+* New cop `Semicolon` that checks for usages of `;` as expression separator.
+* New cop `VariableInterpolation` that checks for variable interpolation in double quoted strings.
+* New cop `Alias` that checks for uses of the keyword `alias`.
+* Automatically detect extensionless Ruby files with shebangs when search for Ruby source files in a directory.
+
+### Bugs fixed
+
+* [#59](https://github.com/bbatsov/rubocop/issues/59): Interpolated variables not enclosed in braces are not noticed.
+* [#42](https://github.com/bbatsov/rubocop/issues/42): Received malformed format string ArgumentError from rubocop.
+
+[@bbatsov]: https://github.com/bbatsov
+[@jonas054]: https://github.com/jonas054
+[@yujinakayama]: https://github.com/yujinakayama
+[@dblock]: https://github.com/dblock
+[@nevir]: https://github.com/nevir
+[@daviddavis]: https://github.com/daviddavis
+[@sds]: https://github.com/sds
+[@fancyremarker]: https://github.com/fancyremarker
+[@sinisterchipmunk]: https://github.com/sinisterchipmunk
+[@vonTronje]: https://github.com/vonTronje
+[@agrimm]: https://github.com/agrimm
+[@pmenglund]: https://github.com/pmenglund
+[@chulkilee]: https://github.com/chulkilee
+[@codez]: https://github.com/codez
+[@emou]: https://github.com/emou
+[@skanev]: http://github.com/skanev
+[@claco]: http://github.com/claco
+[@rifraf]: http://github.com/rifraf
+[@scottmatthewman]: https://github.com/scottmatthewman
+[@ma2gedev]: http://github.com/ma2gedev
+[@jeremyolliver]: https://github.com/jeremyolliver
+[@hannestyden]: https://github.com/hannestyden
+[@geniou]: https://github.com/geniou
+[@jkogara]: https://github.com/jkogara
+[@tmorris-fiksu]: https://github.com/tmorris-fiksu
+[@mockdeep]: https://github.com/mockdeep
+[@hiroponz]: https://github.com/hiroponz
+[@tamird]: https://github.com/tamird
+[@fshowalter]: https://github.com/fshowalter
+[@cschramm]: https://github.com/cschramm
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..f2d0dab
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,68 @@
+# Contributing
+
+If you discover issues, have ideas for improvements or new features,
+please report them to the [issue tracker][1] of the repository or
+submit a pull request. Please, try to follow these guidelines when you
+do so.
+
+## Issue reporting
+
+* Check that the issue has not already been reported.
+* Check that the issue has not already been fixed in the latest code
+ (a.k.a. `master`).
+* Be clear, concise and precise in your description of the problem.
+* Open an issue with a descriptive title and a summary in grammatically correct,
+ complete sentences.
+* Include the output of `rubocop -V`:
+
+```
+$ rubocop -V
+0.16.0 (using Parser 2.1.2, running on ruby 2.0.0 x86_64-darwin12.4.0)
+```
+
+* Include any relevant code to the issue summary.
+
+## Pull requests
+
+* Read [how to properly contribute to open source projects on Github][2].
+* Fork the project.
+* Use a topic/feature branch to easily amend a pull request later, if necessary.
+* Write [good commit messages][3].
+* Use the same coding conventions as the rest of the project.
+* Commit and push until you are happy with your contribution.
+* Make sure to add tests for it. This is important so I don't break it
+ in a future version unintentionally.
+* Add an entry to the [Changelog](CHANGELOG.md) accordingly. See [changelog entry format](#changelog-entry-format).
+* Please try not to mess with the Rakefile, version, or history. If
+ you want to have your own version, or is otherwise necessary, that
+ is fine, but please isolate to its own commit so I can cherry-pick
+ around it.
+* Make sure the test suite is passing ([including rbx and jruby][7]) and the code you wrote doesn't produce
+ RuboCop offences.
+* [Squash related commits together][5].
+* Open a [pull request][4] that relates to *only* one subject with a clear title
+ and description in grammatically correct, complete sentences.
+
+### Changelog entry format
+
+Here are a few examples:
+
+```
+* [#716](https://github.com/bbatsov/rubocop/issues/716): Fixed a regression in the auto-correction logic of `MethodDefParentheses`. ([@bbatsov][])
+* New cop `ElseLayout` checks for odd arrangement of code in the `else` branch of a conditional expression. ([@bbatsov][])
+```
+
+* Mark it up in [Markdown syntax][6].
+* The entry line should start with `* ` (an asterisk and a space).
+* If the change has a related GitHub issue (e.g. a bug fix for a reported issue), put a link to the issue as `[#123](https://github.com/bbatsov/rubocop/issues/123): `.
+* Describe the brief of the change. The sentence should end with a punctuation.
+* At the end of the entry, add an implicit link to your GitHub user page as `([@username][])`.
+* If this is your first contribution to RuboCop project, add a link definition for the implicit link to the bottom of the changelog as `[@username]: https://github.com/username`.
+
+[1]: https://github.com/bbatsov/rubocop/issues
+[2]: http://gun.io/blog/how-to-github-fork-branch-and-pull-request
+[3]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
+[4]: https://help.github.com/articles/using-pull-requests
+[5]: http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html
+[6]: http://daringfireball.net/projects/markdown/syntax
+[7]: http://blog.stwrt.ca/2013/09/06/installing-rubinius-with-rbenv
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..5e145af
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,13 @@
+source 'http://rubygems.org'
+
+gemspec
+
+group :test do
+ gem 'coveralls', require: false
+end
+
+local_gemfile = 'Gemfile.local'
+
+if File.exist?(local_gemfile)
+ eval(File.read(local_gemfile)) # rubocop:disable Eval
+end
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..c07ee63
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,20 @@
+Copyright (c) 2012-14 Bozhidar Batsov
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0660b3d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,725 @@
+[](http://badge.fury.io/rb/rubocop)
+[](https://gemnasium.com/bbatsov/rubocop)
+[](https://travis-ci.org/bbatsov/rubocop)
+[](https://coveralls.io/r/bbatsov/rubocop)
+[](https://codeclimate.com/github/bbatsov/rubocop)
+[](http://inch-pages.github.io/github/bbatsov/rubocop)
+
+# RuboCop
+
+> Role models are important. <br/>
+> -- Officer Alex J. Murphy / RoboCop
+
+**RuboCop** is a Ruby static code analyzer. Out of the box it will
+enforce many of the guidelines outlined in the community
+[Ruby Style Guide](https://github.com/bbatsov/ruby-style-guide).
+
+Most aspects of its behavior can be tweaked via various
+[configuration options](https://github.com/bbatsov/rubocop/blob/master/config/default.yml).
+
+Apart from reporting problems in your code, RuboCop can also
+automatically fix some of the problems for you.
+
+- [Installation](#installation)
+- [Basic Usage](#basic-usage)
+ - [Cops](#cops)
+ - [Style](#style)
+ - [Lint](#lint)
+ - [Rails](#rails)
+- [Configuration](#configuration)
+ - [Inheritance](#inheritance)
+ - [Defaults](#defaults)
+ - [Including/Excluding files](#includingexcluding-files)
+ - [Automatically Generated Configuration](#automatically-generated-configuration)
+- [Disabling Cops within Source Code](#disabling-cops-within-source-code)
+- [Formatters](#formatters)
+ - [Progress Formatter (default)](#progress-formatter-default)
+ - [Clang Style Formatter](#clang-style-formatter)
+ - [Fuubar Style Formatter](#fuubar-style-formatter)
+ - [Emacs Style Formatter](#emacs-style-formatter)
+ - [Simple Formatter](#simple-formatter)
+ - [File List Formatter](#file-list-formatter)
+ - [JSON Formatter](#json-formatter)
+ - [Offense Count Formatter](#offense-count-formatter)
+ - [Custom Formatters](#custom-formatters)
+ - [Creating Custom Formatter](#creating-custom-formatter)
+ - [Using Custom Formatter in Command Line](#using-custom-formatter-in-command-line)
+- [Compatibility](#compatibility)
+- [Editor integration](#editor-integration)
+ - [Emacs](#emacs)
+ - [Vim](#vim)
+ - [Sublime Text](#sublime-text)
+ - [Brackets](#brackets)
+ - [TextMate2](#textmate2)
+ - [Atom](#atom)
+ - [LightTable](#lighttable)
+ - [Other Editors](#other-editors)
+- [Guard integration](#guard-integration)
+- [Rake integration](#rake-integration)
+- [Team](#team)
+- [Contributors](#contributors)
+- [Mailing List](#mailing-list)
+- [Changelog](#changelog)
+- [Copyright](#copyright)
+
+## Installation
+
+**RuboCop**'s installation is pretty standard:
+
+```
+$ gem install rubocop
+```
+
+If you'd rather install RuboCop using `bundler`, don't require it in your `Gemfile`:
+
+```
+gem 'rubocop', require: false
+```
+
+## Basic Usage
+
+Running `rubocop` with no arguments will check all Ruby source files
+in the current directory:
+
+```
+$ rubocop
+```
+
+Alternatively you can pass `rubocop` a list of files and directories to check:
+
+```
+$ rubocop app spec lib/something.rb
+```
+
+Here's RuboCop in action. Consider the following Ruby source code:
+
+```ruby
+def badName
+ if something
+ test
+ end
+end
+```
+
+Running RuboCop on it (assuming it's in a file named `test.rb`) would produce the following report:
+
+```
+Offenses:
+
+test.rb:1:5: C: Use snake_case for methods and variables.
+def badName
+ ^^^^^^^
+test.rb:2:3: C: Favor modifier if/unless usage when you have a single-line body. Another good alternative is the usage of control flow &&/||.
+ if something
+ ^^
+test.rb:4:5: W: end at 4, 4 is not aligned with if at 2, 2
+ end
+ ^^^
+
+1 file inspected, 3 offenses detected
+```
+
+For more details check the available command-line options:
+
+```
+$ rubocop -h
+```
+
+Command flag | Description
+--------------------------|------------------------------------------------------------
+`-v/--version` | Displays the current version and exits
+`-V/--verbose-version` | Displays the current version plus the version of Parser and Ruby
+`-d/--debug` | Displays some extra debug output
+`-D/--display-cop-names` | Displays cop names in offense messages.
+`-c/--config` | Run with specified config file
+`-f/--format` | Choose a formatter
+`-o/--out` | Write output to a file instead of STDOUT
+`-r/--require` | Require Ruby file
+`-R/--rails` | Run extra Rails cops
+`-l/--lint` | Run only lint cops
+`-a/--auto-correct` | Auto-correct certain offenses *Note:* Experimental - use with caution
+`--only` | Run only the specified cop
+`--auto-gen-config` | Generate a configuration file acting as a TODO list
+`--show-cops` | Shows available cops and their configuration
+`--fail-level` | Minimum severity for exit with error code
+
+### Cops
+
+In RuboCop lingo the various checks performed on the code are called cops. There are several cop departments.
+
+#### Style
+
+Most of the cops in RuboCop are so called style cops that check for
+stylistics problems in your code. Almost all of the them are based on
+the Ruby Style Guide. Many of the style cops have configurations
+options allowing them to support different popular coding
+conventions.
+
+#### Lint
+
+Lint cops check for possible errors and very bad practices in your
+code. RuboCop implements in a portable way all built-in MRI lint
+checks (`ruby -wc`) and adds a lot of extra lint checks of its
+own. You can run only the lint cops like this:
+
+```
+$ rubocop -l
+```
+
+Disabling any of the lint cops is generally a bad idea.
+
+#### Rails
+
+Rails cops are specific to the Ruby on Rails framework. Unlike style
+and lint cops they are not used by default and you have to request them
+specifically:
+
+```
+$ rubocop -R
+```
+
+## Configuration
+
+The behavior of RuboCop can be controlled via the
+[.rubocop.yml](https://github.com/bbatsov/rubocop/blob/master/.rubocop.yml)
+configuration file. It makes it possible to enable/disable certain cops
+(checks) and to alter their behavior if they accept any parameters. The file
+can be placed either in your home directory or in some project directory.
+
+RuboCop will start looking for the configuration file in the directory
+where the inspected file is and continue its way up to the root directory.
+
+The file has the following format:
+
+```yaml
+inherit_from: ../.rubocop.yml
+
+Encoding:
+ Enabled: false
+
+LineLength:
+ Max: 99
+```
+
+### Inheritance
+
+The optional `inherit_from` directive is used to include configuration
+from one or more files. This makes it possible to have the common
+project settings in the `.rubocop.yml` file at the project root, and
+then only the deviations from those rules in the subdirectories. The
+files can be given with absolute paths or paths relative to the file
+where they are referenced. The settings after an `inherit_from`
+directive override any settings in the file(s) inherited from. When
+multiple files are included, the first file in the list has the lowest
+precedence and the last one has the highest. The format for multiple
+inheritance is:
+
+```yaml
+inherit_from:
+ - ../.rubocop.yml
+ - ../conf/.rubocop.yml
+```
+
+### Defaults
+
+The file
+[config/default.yml](https://github.com/bbatsov/rubocop/blob/master/config/default.yml)
+under the RuboCop home directory contains the default settings that
+all configurations inherit from. Project and personal `.rubocop.yml`
+files need only make settings that are different from the default
+ones. If there is no `.rubocop.yml` file in the project or home
+directory, `config/default.yml` will be used.
+
+### Including/Excluding files
+
+RuboCop checks all files recursively within the directory it is run
+on. However, it only recognizes files ending with `.rb` or
+extensionless files with a `#!.*ruby` declaration as Ruby files. If
+you'd like it to check other files you'll need to manually pass them
+in, or to add entries for them under `AllCops`/`Include`. Files and
+directories can also be ignored through `AllCops`/`Exclude`.
+
+Here is an example that might be used for a Rails project:
+
+```yaml
+AllCops:
+ Include:
+ - Rakefile
+ - config.ru
+ Exclude:
+ - db/**
+ - config/**
+ - script/**
+ - !ruby/regexp /old_and_unused\.rb$/
+
+# other configuration
+# ...
+```
+
+Files and directories are specified relative to the `.rubocop.yml` file.
+
+**Note**: The `Exclude` parameter is special. It is valid for the
+directory tree starting where it is defined. It is not shadowed by the
+setting of `Exclude` in other `.rubocop.yml` files in
+subdirectories. This is different from all other parameters, who
+follow RuboCop's general principle that configuration for an inspected
+file is taken from the nearest `.rubocop.yml`, searching upwards.
+
+Cops can be run only on specific sets of files when that's needed (for
+instance you might want to run some Rails model checks only on files whose
+paths match `app/models/*.rb`). All cops support the
+`Include` param.
+
+```yaml
+DefaultScope:
+ Include:
+ - app/models/*.rb
+```
+
+Cops can also exclude only specific sets of files when that's needed (for
+instance you might want to run some cop only on a specific file). All cops support the
+`Exclude` param.
+
+```yaml
+DefaultScope:
+ Exclude:
+ - app/models/problematic.rb
+```
+
+Specific cops can be disabled by setting `Enabled` to `false` for that specific cop.
+
+```yaml
+LineLength:
+ Enabled: false
+```
+
+Cops can customize their severity level. All cops support the `Severity` param.
+Allowed params are `refactor`, `convention`, `warning`, `error` and `fatal`.
+
+```yaml
+CyclomaticComplexity:
+ Severity: warning
+```
+
+### Automatically Generated Configuration
+
+If you have a code base with an overwhelming amount of offenses, it can be a
+good idea to use `rubocop --auto-gen-config` and add an `inherit_from:
+rubocop-todo.yml` in your `.rubocop.yml`. The generated file `rubocop-todo.yml`
+contains configuration to disable all cops that currently detect an offense in
+the code. Then you can start removing the entries in the generated file one by
+one as you work through all the offenses in the code.
+
+## Disabling Cops within Source Code
+
+One or more individual cops can be disabled locally in a section of a
+file by adding a comment such as
+
+```ruby
+# rubocop:disable LineLength, StringLiterals
+[...]
+# rubocop:enable LineLength, StringLiterals
+```
+
+You can also disable *all* cops with
+
+```ruby
+# rubocop:disable all
+[...]
+# rubocop:enable all
+```
+
+One or more cops can be disabled on a single line with an end-of-line
+comment.
+
+```ruby
+for x in (0..19) # rubocop:disable AvoidFor
+```
+
+## Formatters
+
+You can change the output format of RuboCop by specifying formatters with the `-f/--format` option.
+RuboCop ships with several built-in formatters, and also you can create your custom formatter.
+
+Additionaly the output can be redirected to a file instead of `$stdout` with the `-o/--out` option.
+
+Some of the built-in formatters produce **machine-parsable** output
+and they are considered public APIs.
+The rest of the formatters are for humans, so parsing their outputs is discouraged.
+
+You can enable multiple formatters at the same time by specifying `-f/--format` multiple times.
+The `-o/--out` option applies to the previously specified `-f/--format`,
+or the default `progress` format if no `-f/--format` is specified before the `-o/--out` option.
+
+```bash
+# Simple format to $stdout.
+$ rubocop --format simple
+
+# Progress (default) format to the file result.txt.
+$ rubocop --out result.txt
+
+# Both progress and offense count formats to $stdout.
+# The offense count formatter outputs only the final summary,
+# so you'll mostly see the outputs from the progress formatter,
+# and at the end the offense count summary will be outputted.
+$ rubocop --format progress --format offenses
+
+# Progress format to $stdout, and JSON format to the file rubocop.json.
+$ rubocop --format progress --format json --out rubocop.json
+# ~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
+# | |_______________|
+# $stdout
+
+# Progress format to result.txt, and simple format to $stdout.
+$ rubocop --output result.txt --format simple
+# ~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
+# | |
+# default format $stdout
+```
+
+### Progress Formatter (default)
+
+The default `progress` formatter outputs a character for each inspected file,
+and at the end it displays all detected offenses in the `clang` format.
+A `.` represents a clean file, and each of the capital letters means
+the severest offense (convention, warning, error or fatal) found in a file.
+
+```
+$ rubocop
+Inspecting 26 files
+..W.C....C..CWCW.C...WC.CC
+
+Offenses:
+
+lib/foo.rb:6:5: C: Missing top-level class documentation comment.
+ class Foo
+ ^^^^^
+
+...
+
+26 files inspected, 46 offenses detected
+```
+
+### Clang Style Formatter
+
+The `clang` formatter displays the offenses in a manner similar to `clang`:
+
+```
+$ rubocop test.rb
+test.rb:1:1: C: Use snake_case for methods and variables.
+def badName
+ ^^^^^^^
+test.rb:2:3: C: Favor modifier if/unless usage when you have a single-line body. Another good alternative is the usage of control flow &&/||.
+ if something
+ ^^
+test.rb:4:5: W: end at 4, 4 is not aligned with if at 2, 2
+ end
+ ^^^
+
+1 file inspected, 3 offenses detected
+```
+
+### Fuubar Style Formatter
+
+The `fuubar` style formatter displays a progress bar
+and shows details of offenses in the `clang` format as soon as they are detected.
+This is inspired by the [Fuubar](https://github.com/jeffkreeftmeijer/fuubar) formatter for RSpec.
+
+```
+$ rubocop --format fuubar
+lib/foo.rb.rb:1:1: C: Use snake_case for methods and variables.
+def badName
+ ^^^^^^^
+lib/bar.rb:13:14: W: File.exists? is deprecated in favor of File.exist?.
+ File.exists?(path)
+ ^^^^^^^
+ 22/53 files |======== 43 ========> | ETA: 00:00:02
+```
+
+### Emacs Style Formatter
+
+**Machine-parsable**
+
+The `emacs` formatter displays the offenses in a format suitable for consumption by `Emacs` (and possibly other tools).
+
+```
+$ rubocop --format emacs test.rb
+/Users/bozhidar/projects/test.rb:1:1: C: Use snake_case for methods and variables.
+/Users/bozhidar/projects/test.rb:2:3: C: Favor modifier if/unless usage when you have a single-line body. Another good alternative is the usage of control flow &&/||.
+/Users/bozhidar/projects/test.rb:4:5: W: end at 4, 4 is not aligned with if at 2, 2
+```
+
+### Simple Formatter
+
+The name of the formatter says it all :-)
+
+```
+$ rubocop --format simple test.rb
+== test.rb ==
+C: 1: 1: Use snake_case for methods and variables.
+C: 2: 3: Favor modifier if/unless usage when you have a single-line body. Another good alternative is the usage of control flow &&/||.
+W: 4: 5: end at 4, 4 is not aligned with if at 2, 2
+
+1 file inspected, 3 offenses detected
+```
+
+### File List Formatter
+
+ **Machine-parsable**
+
+Sometimes you might want to just open all files with offenses in your
+favorite editor. This formatter outputs just the names of the files
+with offenses in them and makes it possible to do something like:
+
+```
+$ rubocop --format files | xargs vim
+```
+
+### JSON Formatter
+
+**Machine-parsable**
+
+You can get RuboCop's inspection result in JSON format by passing `--format json` option in command line.
+The JSON structure is like the following example:
+
+```javascript
+{
+ "metadata": {
+ "rubocop_version": "0.9.0",
+ "ruby_engine": "ruby",
+ "ruby_version": "2.0.0",
+ "ruby_patchlevel": "195",
+ "ruby_platform": "x86_64-darwin12.3.0"
+ },
+ "files": [{
+ "path": "lib/foo.rb",
+ "offenses": []
+ }, {
+ "path": "lib/bar.rb",
+ "offenses": [{
+ "severity": "convention",
+ "message": "Line is too long. [81/79]",
+ "cop_name": "LineLength",
+ "corrected": true,
+ "location": {
+ "line": 546,
+ "column": 80,
+ "length": 4
+ }
+ }, {
+ "severity": "warning",
+ "message": "Unreachable code detected.",
+ "cop_name": "UnreachableCode",
+ "corrected": false,
+ "location": {
+ "line": 15,
+ "column": 9,
+ "length": 10
+ }
+ }
+ ]
+ }
+ ],
+ "summary": {
+ "offense_count": 2,
+ "target_file_count": 2,
+ "inspected_file_count": 2
+ }
+}
+```
+
+### Offense Count Formatter
+
+Sometimes when first applying RuboCop to a codebase, it's nice to be able to
+see where most of your style cleanup is going to be spent.
+
+With this in mind, you can use the offense count formatter to outline the offended
+cops and the number of offenses found for each by running:
+
+```
+$ rubocop --format offenses
+
+87 Documentation
+12 DotPosition
+8 AvoidGlobalVars
+7 EmptyLines
+6 AssignmentInCondition
+4 Blocks
+4 CommentAnnotation
+3 BlockAlignment
+1 IndentationWidth
+1 AvoidPerlBackrefs
+1 ColonMethodCall
+--
+134 Total
+```
+
+### Custom Formatters
+
+You can customize RuboCop's output format with custom formatter.
+
+#### Creating Custom Formatter
+
+To implement a custom formatter, you need to subclass
+`Rubocop::Formatter::BaseFormatter` and override some methods,
+or implement all formatter API methods by duck typing.
+
+Please see the documents below for more formatter API details.
+
+* [Rubocop::Formatter::BaseFormatter](http://rubydoc.info/gems/rubocop/Rubocop/Formatter/BaseFormatter)
+* [Rubocop::Cop::Offense](http://rubydoc.info/gems/rubocop/Rubocop/Cop/Offense)
+* [Parser::Source::Range](http://rubydoc.info/github/whitequark/parser/Parser/Source/Range)
+
+#### Using Custom Formatter in Command Line
+
+You can tell RuboCop to use your custom formatter with a combination of
+`--format` and `--require` option.
+For example, when you have defined `MyCustomFormatter` in
+`./path/to/my_custom_formatter.rb`, you would type this command:
+
+```
+$ rubocop --require ./path/to/my_custom_formatter --format MyCustomFormatter
+```
+
+Note: The path passed to `--require` is directly passed to `Kernel.require`.
+If your custom formatter file is not in `$LOAD_PATH`,
+you need to specify the path as relative path prefixed with `./` explicitly,
+or absolute path.
+
+## Compatibility
+
+RuboCop supports the following Ruby implementations:
+
+* MRI 1.9.2 ([until June 2014](https://www.ruby-lang.org/en/news/2013/12/17/maintenance-of-1-8-7-and-1-9-2/))
+* MRI 1.9.3
+* MRI 2.0
+* MRI 2.1
+* JRuby in 1.9 mode
+* Rubinius 2.0+
+
+## Editor integration
+
+### Emacs
+
+[rubocop.el](https://github.com/bbatsov/rubocop-emacs) is a simple
+Emacs interface for RuboCop. It allows you to run RuboCop inside Emacs
+and quickly jump between problems in your code.
+
+[flycheck](https://github.com/lunaryorn/flycheck) > 0.9 also supports
+RuboCop and uses it by default when available.
+
+### Vim
+
+The [vim-rubocop](https://github.com/ngmy/vim-rubocop) plugin runs
+RuboCop and displays the results in Vim.
+
+There's also a RuboCop checker in
+[syntastic](https://github.com/scrooloose/syntastic).
+
+### Sublime Text
+
+If you're a ST user you might find the
+[Sublime RuboCop plugin](https://github.com/pderichs/sublime_rubocop)
+useful.
+
+### Brackets
+
+The [brackets-rubocop](https://github.com/smockle/brackets-rubocop)
+extension displays RuboCop results in Brackets.
+It can be installed via the extension manager in Brackets.
+
+### TextMate2
+
+The [textmate2-rubocop](https://github.com/mrdougal/textmate2-rubocop)
+bundle displays formatted RuboCop results in a new window.
+Installation instructions can be found [here](https://github.com/mrdougal/textmate2-rubocop#installation).
+
+### Atom
+
+The [atom-lint](https://github.com/yujinakayama/atom-lint) package
+runs RuboCop and highlights the offenses in Atom.
+
+### LightTable
+
+The [lt-rubocop](https://github.com/seancaffery/lt-rubocop) plugin
+provides LightTable integration.
+
+### Other Editors
+
+Here's one great opportunity to contribute to RuboCop - implement
+RuboCop integration for your favorite editor.
+
+## Guard integration
+
+If you're fond of [Guard](https://github.com/guard/guard) you might
+like
+[guard-rubocop](https://github.com/yujinakayama/guard-rubocop). It
+allows you to automatically check Ruby code style with RuboCop when
+files are modified.
+
+
+## Rake integration
+
+To use RuboCop in your `Rakefile` add the following:
+
+```ruby
+require 'rubocop/rake_task'
+
+Rubocop::RakeTask.new
+```
+
+The above will use default values
+
+```ruby
+require 'rubocop/rake_task'
+
+desc 'Run RuboCop on the lib directory'
+Rubocop::RakeTask.new(:rubocop) do |task|
+ task.patterns = ['lib/**/*.rb']
+ # only show the files with failures
+ task.formatters = ['files']
+ # don't abort rake on failure
+ task.fail_on_error = false
+end
+```
+
+## Team
+
+Here's a list of RuboCop's core developers:
+
+* [Bozhidar Batsov](https://github.com/bbatsov)
+* [Jonas Arvidsson](https://github.com/jonas054)
+* [Yuji Nakayama](https://github.com/yujinakayama)
+* [Evgeni Dzhelyov](https://github.com/edzhelyov)
+
+## Contributors
+
+Here's a [list](https://github.com/bbatsov/rubocop/contributors) of
+all the people who have contributed to the development of RuboCop.
+
+I'm extremely grateful to each and every one of them!
+
+If you'd like to contribute to RuboCop, please take the time to go
+through our short
+[contribution guidelines](CONTRIBUTING.md).
+
+Converting more of the Ruby Style Guide into RuboCop cops is our top
+priority right now. Writing a new cop is a great way to dive into RuboCop!
+
+Of course, bug reports and suggestions for improvements are always
+welcome. GitHub pull requests are even better! :-)
+
+## Mailing List
+
+If you're interested in everything regarding RuboCop's development,
+consider joining its
+[Google Group](https://groups.google.com/forum/?fromgroups#!forum/rubocop).
+
+## Changelog
+
+RuboCop's changelog is available [here](CHANGELOG.md).
+
+## Copyright
+
+Copyright (c) 2012-2014 Bozhidar Batsov. See [LICENSE.txt](LICENSE.txt) for
+further details.
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 0000000..5cfdabd
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,43 @@
+# encoding: utf-8
+
+require 'rubygems'
+require 'bundler'
+require 'bundler/gem_tasks'
+begin
+ Bundler.setup(:default, :development)
+rescue Bundler::BundlerError => e
+ $stderr.puts e.message
+ $stderr.puts 'Run `bundle install` to install missing gems'
+ exit e.status_code
+end
+require 'rake'
+require 'rspec/core'
+require 'rspec/core/rake_task'
+require 'rubocop/rake_task'
+RSpec::Core::RakeTask.new(:spec) do |spec|
+ spec.pattern = FileList['spec/**/*_spec.rb']
+end
+
+desc 'Run RSpec with code coverage'
+task :coverage do
+ ENV['COVERAGE'] = 'true'
+ Rake::Task['spec'].execute
+end
+
+desc 'Run RuboCop over itself'
+Rubocop::RakeTask.new(:internal_investigation)
+
+task default: [:spec, :internal_investigation]
+
+require 'yard'
+YARD::Rake::YardocTask.new
+
+Rubocop::RakeTask.new
+
+task :console do
+ require 'irb'
+ require 'irb/completion'
+ require 'rubocop'
+ ARGV.clear
+ IRB.start
+end
diff --git a/bin/rubocop b/bin/rubocop
new file mode 100755
index 0000000..d6291d2
--- /dev/null
+++ b/bin/rubocop
@@ -0,0 +1,22 @@
+#!/usr/bin/env ruby
+# encoding: utf-8
+
+if RUBY_VERSION >= '1.9.2'
+ $LOAD_PATH.unshift(File.dirname(File.realpath(__FILE__)) + '/../lib')
+
+ require 'rubocop'
+ require 'benchmark'
+
+ cli = Rubocop::CLI.new
+ result = 0
+
+ time = Benchmark.realtime do
+ result = cli.run
+ end
+
+ puts "Finished in #{time} seconds" if cli.options[:debug]
+ exit result
+else
+ puts 'RuboCop supports only Ruby 1.9.2+'
+ exit(-1)
+end
diff --git a/config/default.yml b/config/default.yml
new file mode 100644
index 0000000..57cba10
--- /dev/null
+++ b/config/default.yml
@@ -0,0 +1,451 @@
+# This is the default configuration file. Enabling and disabling is configured
+# in separate files. This file adds all other parameters apart from Enabled.
+
+inherit_from:
+ - enabled.yml
+ - disabled.yml
+
+# Common configuration.
+AllCops:
+ # Include gemspec and Rakefile
+ Include:
+ - '**/*.gemspec'
+ - '**/Rakefile'
+ Exclude:
+ - 'vendor/**'
+ # By default, the rails cops are not run. Override in project or home
+ # directory .rubocop.yml files, or by giving the -R/--rails option.
+ RunRailsCops: false
+
+# Indent private/protected/public as deep as method definitions
+AccessModifierIndentation:
+ EnforcedStyle: indent
+ SupportedStyles:
+ - outdent
+ - indent
+
+# Align the elements of a hash literal if they span more than one line.
+AlignHash:
+ # Alignment of entries using hash rocket as separator. Valid values are:
+ #
+ # key - left alignment of keys
+ # 'a' => 2
+ # 'bb' => 3
+ # separator - alignment of hash rockets, keys are right aligned
+ # 'a' => 2
+ # 'bb' => 3
+ # table - left alignment of keys, hash rockets, and values
+ # 'a' => 2
+ # 'bb' => 3
+ EnforcedHashRocketStyle: key
+ # Alignment of entries using colon as separator. Valid values are:
+ #
+ # key - left alignment of keys
+ # a: 0
+ # bb: 1
+ # separator - alignment of colons, keys are right aligned
+ # a: 0
+ # bb: 1
+ # table - left alignment of keys and values
+ # a: 0
+ # bb: 1
+ EnforcedColonStyle: key
+ # Select whether hashes that are the last argument in a method call should be
+ # inspected? Valid values are:
+ #
+ # always_inspect - Inspect both implicit and explicit hashes.
+ # Registers and offence for:
+ # function(a: 1,
+ # b: 2)
+ # Registers an offence for:
+ # function({a: 1,
+ # b: 2})
+ # always_ignore - Ignore both implicit and explicit hashes.
+ # Accepts:
+ # function(a: 1,
+ # b: 2)
+ # Accepts:
+ # function({a: 1,
+ # b: 2})
+ # ignore_implicit - Ingore only implicit hashes.
+ # Accepts:
+ # function(a: 1,
+ # b: 2)
+ # Registers an offence for:
+ # function({a: 1,
+ # b: 2})
+ # ignore_explicit - Ingore only explicit hashes.
+ # Accepts:
+ # function({a: 1,
+ # b: 2})
+ # Registers an offence for:
+ # function(a: 1,
+ # b: 2)
+ EnforcedLastArgumentHashStyle: always_inspect
+ SupportedLastArgumentHashStyles:
+ - always_inspect
+ - always_ignore
+ - ignore_implicit
+ - ignore_explicit
+
+AlignParameters:
+ # Alignment of parameters in multi-line method calls.
+ #
+ # The `with_first_parameter` style aligns the following lines along the same column
+ # as the first parameter.
+ #
+ # method_call(a,
+ # b)
+ #
+ # The `with_fixed_indentation` style alignes the following lines with one
+ # level of indenation relative to the start of the line with the method call.
+ #
+ # method_call(a,
+ # b)
+ EnforcedStyle: with_first_parameter
+ SupportedStyles:
+ - with_first_parameter
+ - with_fixed_indentation
+
+# Allow safe assignment in conditions.
+AssignmentInCondition:
+ AllowSafeAssignment: true
+
+BlockNesting:
+ Max: 3
+
+BracesAroundHashParameters:
+ EnforcedStyle: no_braces
+ SupportedStyles:
+ - braces
+ - no_braces
+
+# Indentation of `when`.
+CaseIndentation:
+ IndentWhenRelativeTo: case
+ SupportedStyles:
+ - case
+ - end
+ IndentOneStep: false
+
+ClassAndModuleChildren:
+ # Checks the style of children definitions at classes and modules.
+ #
+ # Basically there are two different styles:
+ #
+ # `nested` - have each child on a separat line
+ # class Foo
+ # class Bar
+ # end
+ # end
+ #
+ # `compact` - combine definitions as much as possible
+ # class Foo::Bar
+ # end
+ #
+ # The compact style is only forced, for classes / modules with one child.
+ EnforcedStyle: nested
+ SupportedStyles:
+ - nested
+ - compact
+
+ClassLength:
+ CountComments: false # count full line comments?
+ Max: 100
+
+# Align with the style guide.
+CollectionMethods:
+ PreferredMethods:
+ collect: 'map'
+ collect!: 'map!'
+ inject: 'reduce'
+ detect: 'find'
+ find_all: 'select'
+
+# Checks formatting of special comments
+CommentAnnotation:
+ Keywords:
+ - TODO
+ - FIXME
+ - OPTIMIZE
+ - HACK
+ - REVIEW
+
+# Avoid complex methods.
+CyclomaticComplexity:
+ Max: 6
+
+# Multi-line method chaining should be done with leading dots.
+DotPosition:
+ EnforcedStyle: leading
+ SupportedStyles:
+ - leading
+ - trailing
+
+# Use empty lines between defs.
+EmptyLineBetweenDefs:
+ # If true, this parameter means that single line method definitions don't
+ # need an empty line between them.
+ AllowAdjacentOneLineDefs: false
+
+# Align ends correctly.
+EndAlignment:
+ # The value `keyword` means that `end` should be aligned with the matching
+ # keyword (if, while, etc.).
+ # The value `variable` means that in assignments, `end` should be aligned
+ # with the start of the variable on the left hand side of `=`. In all other
+ # situations, `end` should still be aligned with the keyword.
+ AlignWith: keyword
+ SupportedStyles:
+ - keyword
+ - variable
+
+FileName:
+ Exclude:
+ - Rakefile
+ - Gemfile
+ - Capfile
+
+# Checks use of for or each in multiline loops.
+For:
+ EnforcedStyle: each
+ SupportedStyles:
+ - for
+ - each
+
+# Enforce the method used for string formatting.
+FormatString:
+ EnforcedStyle: format
+ SupportedStyles:
+ - format
+ - sprintf
+ - percent
+
+# Built-in global variables are allowed by default.
+GlobalVars:
+ AllowedVariables: []
+
+HashSyntax:
+ EnforcedStyle: ruby19
+ SupportedStyles:
+ - ruby19
+ - hash_rockets
+
+IfUnlessModifier:
+ MaxLineLength: 79
+
+# Checks the indentation of the first key in a hash literal.
+IndentHash:
+ # The value `special_inside_parentheses` means that hash literals with braces
+ # that have their opening brace on the same line as a surrounding opening
+ # round parenthesis, shall have their first key indented relative to the
+ # first position inside the parenthesis.
+ # The value `consistent` means that the indentation of the first key shall
+ # always be relative to the first position of the line where the opening
+ # brace is.
+ EnforcedStyle: special_inside_parentheses
+ SupportedStyles:
+ - special_inside_parentheses
+ - consistent
+
+LambdaCall:
+ EnforcedStyle: call
+ SupportedStyles:
+ - call
+ - braces
+
+LineLength:
+ Max: 79
+
+MethodDefParentheses:
+ EnforcedStyle: require_parentheses
+ SupportedStyles:
+ - require_parentheses
+ - require_no_parentheses
+
+MethodLength:
+ CountComments: false # count full line comments?
+ Max: 10
+
+MethodName:
+ EnforcedStyle: snake_case
+ SupportedStyles:
+ - snake_case
+ - camelCase
+
+NumericLiterals:
+ MinDigits: 5
+
+ParameterLists:
+ Max: 5
+ CountKeywordArgs: true
+
+# Allow safe assignment in conditions.
+ParenthesesAroundCondition:
+ AllowSafeAssignment: true
+
+PercentLiteralDelimiters:
+ PreferredDelimiters:
+ '%': ()
+ '%i': ()
+ '%q': ()
+ '%Q': ()
+ '%r': '{}'
+ '%s': ()
+ '%w': ()
+ '%W': ()
+ '%x': ()
+
+PredicateName:
+ NamePrefixBlacklist:
+ - is_
+ - has_
+ - have_
+
+RaiseArgs:
+ EnforcedStyle: exploded
+ SupportedStyles:
+ - compact # raise Exception.new(msg)
+ - exploded # raise Exception, msg
+
+
+RedundantReturn:
+ # When true allows code like `return x, y`.
+ AllowMultipleReturnValues: false
+
+RegexpLiteral:
+ # The maximum number of (escaped) slashes that a slash-delimited regexp is
+ # allowed to have. If there are more slashes, a %r regexp shall be used.
+ MaxSlashes: 1
+
+Semicolon:
+ # Allow ; to separate several expressions on the same line.
+ AllowAsExpressionSeparator: false
+
+SignalException:
+ EnforcedStyle: semantic
+ SupportedStyles:
+ - only_raise
+ - only_fail
+ - semantic
+
+
+SingleLineBlockParams:
+ Methods:
+ - reduce:
+ - a
+ - e
+ - inject:
+ - a
+ - e
+
+SingleLineMethods:
+ AllowIfMethodIsEmpty: true
+
+StringLiterals:
+ EnforcedStyle: single_quotes
+ SupportedStyles:
+ - single_quotes
+ - double_quotes
+
+SpaceAroundEqualsInParameterDefault:
+ EnforcedStyle: space
+ SupportedStyles:
+ - space
+ - no_space
+
+SpaceBeforeBlockBraces:
+ EnforcedStyle: space
+ SupportedStyles:
+ - space
+ - no_space
+
+SpaceInsideBlockBraces:
+ EnforcedStyle: space
+ SupportedStyles:
+ - space
+ - no_space
+ # Valid values are: space, no_space
+ EnforcedStyleForEmptyBraces: no_space
+ # Space between { and |. Overrides EnforcedStyle if there is a conflict.
+ SpaceBeforeBlockParameters: true
+
+SpaceInsideHashLiteralBraces:
+ EnforcedStyle: space
+ EnforcedStyleForEmptyBraces: no_space
+ SupportedStyles:
+ - space
+ - no_space
+
+TrailingComma:
+ EnforcedStyleForMultiline: no_comma
+ SupportedStyles:
+ - comma
+ - no_comma
+
+# TrivialAccessors doesn't require exact name matches and doesn't allow
+# predicated methods by default.
+TrivialAccessors:
+ ExactNameMatch: false
+ AllowPredicates: false
+ Whitelist:
+ - to_ary
+ - to_a
+ - to_c
+ - to_enum
+ - to_h
+ - to_hash
+ - to_i
+ - to_int
+ - to_io
+ - to_open
+ - to_path
+ - to_proc
+ - to_r
+ - to_regexp
+ - to_str
+ - to_s
+ - to_sym
+
+VariableName:
+ EnforcedStyle: snake_case
+ SupportedStyles:
+ - snake_case
+ - camelCase
+
+WhileUntilModifier:
+ MaxLineLength: 79
+
+WordArray:
+ MinSize: 0
+
+##################### Rails ##################################
+
+ActionFilter:
+ EnforcedStyle: action
+ SupportedStyles:
+ - action
+ - filter
+ Include:
+ - app/controllers/*.rb
+
+DefaultScope:
+ Include:
+ - app/models/*.rb
+
+HasAndBelongsToMany:
+ Include:
+ - app/models/*.rb
+
+ReadWriteAttribute:
+ Include:
+ - app/models/*.rb
+
+ScopeArgs:
+ Include:
+ - app/models/*.rb
+
+Validation:
+ Include:
+ - app/models/*.rb
+
diff --git a/config/disabled.yml b/config/disabled.yml
new file mode 100644
index 0000000..66fdb7d
--- /dev/null
+++ b/config/disabled.yml
@@ -0,0 +1,13 @@
+# These are all the cops that are disabled in the default configuration.
+
+GuardClause:
+ Description: 'Check for conditionals that can be replaced with guard clauses'
+ Enabled: false
+
+MethodCalledOnDoEndBlock:
+ Description: 'Avoid chaining a method call on a do...end block.'
+ Enabled: false
+
+SymbolArray:
+ Description: 'Use %i or %I for arrays of symbols.'
+ Enabled: false
diff --git a/config/enabled.yml b/config/enabled.yml
new file mode 100644
index 0000000..5cb6051
--- /dev/null
+++ b/config/enabled.yml
@@ -0,0 +1,729 @@
+# These are all the cops that are enabled in the default configuration.
+
+AccessModifierIndentation:
+ Description: Check indentation of private/protected visibility modifiers.
+ Enabled: true
+
+AccessorMethodName:
+ Description: Check the naming of accessor methods for get_/set_.
+ Enabled: true
+
+Alias:
+ Description: 'Use alias_method instead of alias.'
+ Enabled: true
+
+AlignArray:
+ Description: >-
+ Align the elements of an array literal if they span more than
+ one line.
+ Enabled: true
+
+AlignHash:
+ Description: >-
+ Align the elements of a hash literal if they span more than
+ one line.
+ Enabled: true
+
+AlignParameters:
+ Description: >-
+ Align the parameters of a method call if they span more
+ than one line.
+ Enabled: true
+
+AndOr:
+ Description: 'Use &&/|| instead of and/or.'
+ Enabled: true
+
+ArrayJoin:
+ Description: 'Use Array#join instead of Array#*.'
+ Enabled: true
+
+AsciiComments:
+ Description: 'Use only ascii symbols in comments.'
+ Enabled: true
+
+AsciiIdentifiers:
+ Description: 'Use only ascii symbols in identifiers.'
+ Enabled: true
+
+Attr:
+ Description: 'Checks for uses of Module#attr.'
+ Enabled: true
+
+BeginBlock:
+ Description: 'Avoid the use of BEGIN blocks.'
+ Enabled: true
+
+BlockComments:
+ Description: 'Do not use block comments.'
+ Enabled: true
+
+BlockNesting:
+ Description: 'Avoid excessive block nesting'
+ Enabled: true
+
+Blocks:
+ Description: >-
+ Avoid using {...} for multi-line blocks (multiline chaining is
+ always ugly).
+ Prefer {...} over do...end for single-line blocks.
+ Enabled: true
+
+BracesAroundHashParameters:
+ Description: 'Enforce braces style inside hash parameters.'
+ Enabled: true
+
+CaseEquality:
+ Description: 'Avoid explicit use of the case equality operator(===).'
+ Enabled: true
+
+CaseIndentation:
+ Description: 'Indentation of when in a case/when/[else/]end.'
+ Enabled: true
+
+CharacterLiteral:
+ Description: 'Checks for uses of character literals.'
+ Enabled: true
+
+ClassAndModuleCamelCase:
+ Description: 'Use CamelCase for classes and modules.'
+ Enabled: true
+
+ClassAndModuleChildren:
+ Description: 'Checks style of children classes and modules.'
+ Enabled: true
+
+ClassLength:
+ Description: 'Avoid classes longer than 100 lines of code.'
+ Enabled: true
+
+ClassMethods:
+ Description: 'Use self when defining module/class methods.'
+ Enabled: true
+
+ClassVars:
+ Description: 'Avoid the use of class variables.'
+ Enabled: true
+
+CollectionMethods:
+ Description: 'Preferred collection methods.'
+ Enabled: true
+
+ColonMethodCall:
+ Description: 'Do not use :: for method call.'
+ Enabled: true
+
+CommentAnnotation:
+ Description: >-
+ Checks formatting of special comments
+ (TODO, FIXME, OPTIMIZE, HACK, REVIEW).
+ Enabled: true
+
+ConstantName:
+ Description: 'Constants should use SCREAMING_SNAKE_CASE.'
+ Enabled: true
+
+CyclomaticComplexity:
+ Description: 'Avoid complex methods.'
+ Enabled: true
+
+DefWithParentheses:
+ Description: 'Use def with parentheses when there are arguments.'
+ Enabled: true
+
+DeprecatedHashMethods:
+ Description: 'Checks for use of deprecated Hash methods.'
+ Enabled: true
+
+Documentation:
+ Description: 'Document classes and non-namespace modules.'
+ Enabled: true
+
+DotPosition:
+ Description: 'Checks the position of the dot in multi-line method calls.'
+ Enabled: true
+
+DoubleNegation:
+ Description: 'Checks for uses of double negation (!!).'
+ Enabled: true
+
+EmptyLineBetweenDefs:
+ Description: 'Use empty lines between defs.'
+ Enabled: true
+
+EmptyLines:
+ Description: "Don't use several empty lines in a row."
+ Enabled: true
+
+EmptyLinesAroundAccessModifier:
+ Description: "Keep blank lines around access modifiers."
+ Enabled: true
+
+EmptyLinesAroundBody:
+ Description: "Keeps track of empty lines around expression bodies."
+ Enabled: true
+
+EmptyLiteral:
+ Description: 'Prefer literals to Array.new/Hash.new/String.new.'
+ Enabled: true
+
+Encoding:
+ Description: 'Use UTF-8 as the source file encoding.'
+ Enabled: true
+
+EndBlock:
+ Description: 'Avoid the use of END blocks.'
+ Enabled: true
+
+EndOfLine:
+ Description: 'Use Unix-style line endings.'
+ Enabled: true
+
+EvenOdd:
+ Description: 'Favor the use of Fixnum#even? && Fixnum#odd?'
+ Enabled: true
+
+FileName:
+ Description: 'Use snake_case for source file names.'
+ Enabled: true
+
+FinalNewline:
+ Description: 'Checks for a final newline in a source file.'
+ Enabled: true
+
+FlipFlop:
+ Description: 'Checks for flip flops'
+ Enabled: true
+
+For:
+ Description: 'Checks use of for or each in multiline loops.'
+ Enabled: true
+
+FormatString:
+ Description: 'Enforce the use of Kernel#sprintf, Kernel#format or String#%.'
+ Enabled: true
+
+GlobalVars:
+ Description: 'Do not introduce global variables.'
+ Enabled: true
+
+HashSyntax:
+ Description: >-
+ Prefer Ruby 1.9 hash syntax { a: 1, b: 2 } over 1.8 syntax
+ { :a => 1, :b => 2 }.
+ Enabled: true
+
+IfUnlessModifier:
+ Description: >-
+ Favor modifier if/unless usage when you have a
+ single-line body.
+ Enabled: true
+
+IfWithSemicolon:
+ Description: 'Never use if x; .... Use the ternary operator instead.'
+ Enabled: true
+
+IndentationConsistency:
+ Description: 'Keep indentation straight.'
+ Enabled: true
+
+IndentationWidth:
+ Description: 'Use 2 spaces for indentation.'
+ Enabled: true
+
+IndentArray:
+ Description: >-
+ Checks the indentation of the first element in an array
+ literal.
+ Enabled: true
+
+IndentHash:
+ Description: 'Checks the indentation of the first key in a hash literal.'
+ Enabled: true
+
+Lambda:
+ Description: 'Use the new lambda literal syntax for single-line blocks.'
+ Enabled: true
+
+LambdaCall:
+ Description: 'Use lambda.call(...) instead of lambda.(...).'
+ Enabled: true
+
+LeadingCommentSpace:
+ Description: 'Comments should start with a space.'
+ Enabled: true
+
+LineEndConcatenation:
+ Description: 'Use \\ instead of + to concatenate two string literals at line end.'
+ Enabled: true
+
+LineLength:
+ Description: 'Limit lines to 79 characters.'
+ Enabled: true
+
+MethodCallParentheses:
+ Description: 'Do not use parentheses for method calls with no arguments.'
+ Enabled: true
+
+MethodDefParentheses:
+ Description: >-
+ Checks if the method definitions have or don't have
+ parentheses.
+ Enabled: true
+
+MethodLength:
+ Description: 'Avoid methods longer than 10 lines of code.'
+ Enabled: true
+
+MethodName:
+ Description: 'Use the configured style when naming methods.'
+ Enabled: true
+
+ModuleFunction:
+ Description: 'Checks for usage of `extend self` in modules.'
+ Enabled: true
+
+MultilineBlockChain:
+ Description: 'Avoid multi-line chains of blocks.'
+ Enabled: true
+
+MultilineIfThen:
+ Description: 'Never use then for multi-line if/unless.'
+ Enabled: true
+
+MultilineTernaryOperator:
+ Description: >-
+ Avoid multi-line ?: (the ternary operator);
+ use if/unless instead.
+ Enabled: true
+
+NegatedIf:
+ Description: >-
+ Favor unless over if for negative conditions
+ (or control flow or).
+ Enabled: true
+
+NegatedWhile:
+ Description: 'Favor until over while for negative conditions.'
+ Enabled: true
+
+NestedTernaryOperator:
+ Description: 'Use one expression per branch in a ternary operator.'
+ Enabled: true
+
+NilComparison:
+ Description: 'Prefer x.nil? to x == nil.'
+ Enabled: true
+
+NonNilCheck:
+ Description: 'Checks for redundant nil checks.'
+ Enabled: true
+
+Not:
+ Description: 'Use ! instead of not.'
+ Enabled: true
+
+NumericLiterals:
+ Description: >-
+ Add underscores to large numeric literals to improve their
+ readability.
+ Enabled: true
+
+OneLineConditional:
+ Description: >-
+ Favor the ternary operator(?:) over
+ if/then/else/end constructs.
+ Enabled: true
+
+OpMethod:
+ Description: 'When defining binary operators, name the argument other.'
+ Enabled: true
+
+ParameterLists:
+ Description: 'Avoid parameter lists longer than three or four parameters.'
+ Enabled: true
+
+ParenthesesAroundCondition:
+ Description: >-
+ Don't use parentheses around the condition of an
+ if/unless/while.
+ Enabled: true
+
+PercentLiteralDelimiters:
+ Description: 'Use `%`-literal delimiters consistently'
+ Enabled: true
+
+PerlBackrefs:
+ Description: 'Avoid Perl-style regex back references.'
+ Enabled: true
+
+PredicateName:
+ Description: 'Check the names of predicate methods.'
+ Enabled: true
+
+Proc:
+ Description: 'Use proc instead of Proc.new.'
+ Enabled: true
+
+RaiseArgs:
+ Description: 'Checks the arguments passed to raise/fail.'
+ Enabled: true
+
+RedundantBegin:
+ Description: "Don't use begin blocks when they are not needed."
+ Enabled: true
+
+RedundantException:
+ Description: "Checks for an obsolete RuntimeException argument in raise/fail."
+ Enabled: true
+
+RedundantReturn:
+ Description: "Don't use return where it's not required."
+ Enabled: true
+
+RedundantSelf:
+ Description: "Don't use self where it's not needed."
+ Enabled: true
+
+RegexpLiteral:
+ Description: >-
+ Use %r for regular expressions matching more than
+ `MaxSlashes` '/' characters.
+ Use %r only for regular expressions matching more than
+ `MaxSlashes` '/' character.
+ Enabled: true
+
+RescueModifier:
+ Description: 'Avoid using rescue in its modifier form.'
+ Enabled: true
+
+SelfAssignment:
+ Description: 'Checks for places where self-assignment shorthand should have been used.'
+ Enabled: true
+
+Semicolon:
+ Description: "Don't use semicolons to terminate expressions."
+ Enabled: true
+
+SignalException:
+ Description: 'Checks for proper usage of fail and raise.'
+ Enabled: true
+
+SingleLineBlockParams:
+ Description: 'Enforces the names of some block params.'
+ Enabled: true
+
+SingleLineMethods:
+ Description: 'Avoid single-line methods.'
+ Enabled: true
+
+SingleSpaceBeforeFirstArg:
+ Description: >-
+ Checks that exactly one space is used between a method name
+ and the first argument for method calls without parentheses.
+ Enabled: true
+
+SpaceAfterColon:
+ Description: 'Use spaces after colons.'
+ Enabled: true
+
+SpaceAfterComma:
+ Description: 'Use spaces after commas.'
+ Enabled: true
+
+SpaceAfterControlKeyword:
+ Description: 'Use spaces after if/elsif/unless/while/until/case/when.'
+ Enabled: true
+
+SpaceAfterMethodName:
+ Description: >-
+ Never put a space between a method name and the opening
+ parenthesis in a method definition.
+ Enabled: true
+
+SpaceAfterNot:
+ Description: Tracks redundant space after the ! operator.
+ Enabled: true
+
+SpaceAfterSemicolon:
+ Description: 'Use spaces after semicolons.'
+ Enabled: true
+
+SpaceBeforeBlockBraces:
+ Description: >-
+ Checks that the left block brace has or doesn't have space
+ before it.
+ Enabled: true
+
+SpaceInsideBlockBraces:
+ Description: >-
+ Checks that block braces have or don't have surrounding space.
+ For blocks taking parameters, checks that the left brace has
+ or doesn't have trailing space.
+ Enabled: true
+
+SpaceAroundEqualsInParameterDefault:
+ Description: >-
+ Checks that the equals signs in parameter default assignments
+ have or don't have surrounding space depending on
+ configuration.
+ Enabled: true
+
+SpaceAroundOperators:
+ Description: 'Use spaces around operators.'
+ Enabled: true
+
+SpaceBeforeModifierKeyword:
+ Description: 'Put a space before the modifier keyword.'
+ Enabled: true
+
+SpaceInsideBrackets:
+ Description: 'No spaces after [ or before ].'
+ Enabled: true
+
+SpaceInsideHashLiteralBraces:
+ Description: "Use spaces inside hash literal braces - or don't."
+ Enabled: true
+
+SpaceInsideParens:
+ Description: 'No spaces after ( or before ).'
+ Enabled: true
+
+SpecialGlobalVars:
+ Description: 'Avoid Perl-style global variables.'
+ Enabled: true
+
+StringLiterals:
+ Description: 'Checks if uses of quotes match the configured preference.'
+ Enabled: true
+
+Tab:
+ Description: 'No hard tabs.'
+ Enabled: true
+
+TrailingBlankLines:
+ Description: 'Checks for superfluous trailing blank lines.'
+ Enabled: true
+
+TrailingComma:
+ Description: 'Checks for trailing comma in parameter lists and literals.'
+ Enabled: true
+
+TrailingWhitespace:
+ Description: 'Avoid trailing whitespace.'
+ Enabled: true
+
+TrivialAccessors:
+ Description: 'Prefer attr_* methods to trivial readers/writers.'
+ Enabled: true
+
+UnlessElse:
+ Description: >-
+ Never use unless with else. Rewrite these with the positive
+ case first.
+ Enabled: true
+
+VariableInterpolation:
+ Description: >-
+ Don't interpolate global, instance and class variables
+ directly in strings.
+ Enabled: true
+
+VariableName:
+ Description: 'Use the configured style when naming variables.'
+ Enabled: true
+
+WhenThen:
+ Description: 'Use when x then ... for one-line cases.'
+ Enabled: true
+
+WhileUntilDo:
+ Description: 'Checks for redundant do after while or until.'
+ Enabled: true
+
+WhileUntilModifier:
+ Description: >-
+ Favor modifier while/until usage when you have a
+ single-line body.
+ Enabled: true
+
+WordArray:
+ Description: 'Use %w or %W for arrays of words.'
+ Enabled: true
+
+#################### Lint ################################
+### Warnings
+
+AmbiguousOperator:
+ Description: >-
+ Checks for ambiguous operators in the first argument of a
+ method invocation without parentheses.
+ Enabled: true
+
+AmbiguousRegexpLiteral:
+ Description: >-
+ Checks for ambiguous regexp literals in the first argument of
+ a method invocation without parenthesis.
+ Enabled: true
+
+AssignmentInCondition:
+ Description: "Don't use assignment in conditions."
+ Enabled: true
+
+BlockAlignment:
+ Description: 'Align block ends correctly.'
+ Enabled: true
+
+ConditionPosition:
+ Description: 'Checks for condition placed in a confusing position relative to the keyword.'
+ Enabled: true
+
+Debugger:
+ Description: 'Check for debugger calls.'
+ Enabled: true
+
+DeprecatedClassMethods:
+ Description: 'Check for deprecated class method calls.'
+ Enabled: true
+
+ElseLayout:
+ Description: 'Check for odd code arrangement in an else block.'
+ Enabled: true
+
+EmptyEnsure:
+ Description: 'Checks for empty ensure block.'
+ Enabled: true
+
+EmptyInterpolation:
+ Description: 'Checks for empty string interpolation.'
+ Enabled: true
+
+EndAlignment:
+ Description: 'Align ends correctly.'
+ Enabled: true
+
+EndInMethod:
+ Description: 'END blocks should not be placed inside method definitions.'
+ Enabled: true
+
+EnsureReturn:
+ Description: 'Never use return in an ensure block.'
+ Enabled: true
+
+Eval:
+ Description: 'The use of eval represents a serious security risk.'
+ Enabled: true
+
+HandleExceptions:
+ Description: "Don't suppress exception."
+ Enabled: true
+
+InvalidCharacterLiteral:
+ Description: >-
+ Checks for invalid character literals with a non-escaped
+ whitespace character.
+ Enabled: true
+
+LiteralInCondition:
+ Description: 'Checks of literals used in conditions.'
+ Enabled: true
+
+LiteralInInterpolation:
+ Description: 'Checks for literals used in interpolation.'
+ Enabled: true
+
+Loop:
+ Description: >-
+ Use Kernel#loop with break rather than begin/end/until or
+ begin/end/while for post-loop tests.
+ Enabled: true
+
+ParenthesesAsGroupedExpression:
+ Description: >-
+ Checks for method calls with a space before the opening
+ parenthesis.
+ Enabled: true
+
+RequireParentheses:
+ Description: >-
+ Use parentheses in the method call to avoid confusion
+ about precedence.
+ Enabled: true
+
+RescueException:
+ Description: 'Avoid rescuing the Exception class.'
+ Enabled: true
+
+ShadowingOuterLocalVariable:
+ Description: >-
+ Do not use the same name as outer local variable
+ for block arguments or block local variables.
+ Enabled: true
+
+SpaceBeforeFirstArg:
+ Description: >-
+ Put a space between a method name and the first argument
+ in a method call wihtout parentheses.
+ Enabled: true
+
+StringConversionInInterpolation:
+ Description: 'Checks for Object#to_s usage in string interpolation.'
+ Enabled: true
+
+UnreachableCode:
+ Description: 'Unreachable code.'
+ Enabled: true
+
+UselessAccessModifier:
+ Description: 'Checks for useless access modifiers.'
+ Enabled: true
+
+UselessAssignment:
+ Description: 'Checks for useless assignment to a local variable.'
+ Enabled: true
+
+UselessComparison:
+ Description: 'Checks for comparison of something with itself.'
+ Enabled: true
+
+UselessElseWithoutRescue:
+ Description: 'Checks for useless `else` in `begin..end` without `rescue`.'
+ Enabled: true
+
+UselessSetterCall:
+ Description: 'Checks for useless setter call to a local variable.'
+ Enabled: true
+
+Void:
+ Description: 'Possible use of operator/literal/variable in void context.'
+ Enabled: true
+
+##################### Rails ##################################
+
+ActionFilter:
+ Description: 'Enforces consistent use of action filter methods.'
+ Enabled: true
+
+DefaultScope:
+ Description: 'Checks if the argument passed to default_scope is a block.'
+ Enabled: true
+
+HasAndBelongsToMany:
+ Description: 'Prefer has_many :through to has_and_belongs_to_many.'
+ Enabled: true
+
+Output:
+ Description: 'Checks for calls to puts, print, etc.'
+ Enabled: true
+
+ReadWriteAttribute:
+ Description: 'Checks for read_attribute(:attr) and write_attribute(:attr, val).'
+ Enabled: true
+
+ScopeArgs:
+ Description: 'Checks the arguments of ActiveRecord scopes.'
+ Enabled: true
+
+Validation:
+ Description: 'Use sexy validations.'
+ Enabled: true
diff --git a/lib/rubocop.rb b/lib/rubocop.rb
new file mode 100644
index 0000000..0caaf40
--- /dev/null
+++ b/lib/rubocop.rb
@@ -0,0 +1,262 @@
+# encoding: utf-8
+
+# Try to load the latest parser and fall back to the latest supported version,
+# if parser does not yet support the current Ruby version.
+# This should only be problem when using development Ruby builds.
+begin
+ require 'parser/current'
+rescue NotImplementedError => error
+ warn "Falling back to Ruby 2.1 parser: #{error.message}"
+ require 'parser/ruby21'
+ Parser::CurrentRuby = Parser::Ruby21
+end
+
+require 'rainbow'
+# Rainbow 2.0 does not load the monkey-patch for String by default.
+require 'rainbow/ext/string' unless String.method_defined?(:color)
+
+require 'English'
+require 'set'
+require 'ast/sexp'
+require 'powerpack/enumerable/drop_last'
+require 'powerpack/hash/symbolize_keys'
+require 'powerpack/string/blank'
+require 'powerpack/string/strip_indent'
+
+require 'rubocop/version'
+
+require 'rubocop/path_util'
+
+require 'rubocop/cop/util'
+require 'rubocop/cop/offense'
+require 'rubocop/cop/ignored_node'
+require 'rubocop/cop/cop'
+require 'rubocop/cop/commissioner'
+require 'rubocop/cop/corrector'
+require 'rubocop/cop/team'
+require 'rubocop/cop/severity'
+
+require 'rubocop/cop/variable_inspector'
+require 'rubocop/cop/variable_inspector/locatable'
+require 'rubocop/cop/variable_inspector/variable'
+require 'rubocop/cop/variable_inspector/assignment'
+require 'rubocop/cop/variable_inspector/reference'
+require 'rubocop/cop/variable_inspector/scope'
+require 'rubocop/cop/variable_inspector/variable_table'
+
+require 'rubocop/cop/mixin/annotation_comment'
+require 'rubocop/cop/mixin/array_syntax'
+require 'rubocop/cop/mixin/autocorrect_alignment'
+require 'rubocop/cop/mixin/autocorrect_unless_changing_ast'
+require 'rubocop/cop/mixin/check_assignment'
+require 'rubocop/cop/mixin/check_methods'
+require 'rubocop/cop/mixin/configurable_max'
+require 'rubocop/cop/mixin/code_length'
+require 'rubocop/cop/mixin/configurable_enforced_style'
+require 'rubocop/cop/mixin/configurable_naming'
+require 'rubocop/cop/mixin/if_node'
+require 'rubocop/cop/mixin/if_then_else'
+require 'rubocop/cop/mixin/negative_conditional'
+require 'rubocop/cop/mixin/parser_diagnostic'
+require 'rubocop/cop/mixin/safe_assignment'
+require 'rubocop/cop/mixin/surrounding_space'
+require 'rubocop/cop/mixin/space_inside'
+require 'rubocop/cop/mixin/space_after_punctuation'
+require 'rubocop/cop/mixin/statement_modifier'
+require 'rubocop/cop/mixin/string_help'
+
+require 'rubocop/cop/lint/ambiguous_operator'
+require 'rubocop/cop/lint/ambiguous_regexp_literal'
+require 'rubocop/cop/lint/assignment_in_condition'
+require 'rubocop/cop/lint/block_alignment'
+require 'rubocop/cop/lint/condition_position'
+require 'rubocop/cop/lint/debugger'
+require 'rubocop/cop/lint/deprecated_class_methods'
+require 'rubocop/cop/lint/else_layout'
+require 'rubocop/cop/lint/empty_ensure'
+require 'rubocop/cop/lint/empty_interpolation'
+require 'rubocop/cop/lint/end_alignment'
+require 'rubocop/cop/lint/end_in_method'
+require 'rubocop/cop/lint/ensure_return'
+require 'rubocop/cop/lint/eval'
+require 'rubocop/cop/lint/handle_exceptions'
+require 'rubocop/cop/lint/invalid_character_literal'
+require 'rubocop/cop/lint/literal_in_condition'
+require 'rubocop/cop/lint/literal_in_interpolation'
+require 'rubocop/cop/lint/loop'
+require 'rubocop/cop/lint/parentheses_as_grouped_expression'
+require 'rubocop/cop/lint/require_parentheses'
+require 'rubocop/cop/lint/rescue_exception'
+require 'rubocop/cop/lint/shadowing_outer_local_variable'
+require 'rubocop/cop/lint/space_before_first_arg'
+require 'rubocop/cop/lint/string_conversion_in_interpolation'
+require 'rubocop/cop/lint/syntax'
+require 'rubocop/cop/lint/unreachable_code'
+require 'rubocop/cop/lint/useless_access_modifier'
+require 'rubocop/cop/lint/useless_assignment'
+require 'rubocop/cop/lint/useless_comparison'
+require 'rubocop/cop/lint/useless_else_without_rescue'
+require 'rubocop/cop/lint/useless_setter_call'
+require 'rubocop/cop/lint/void'
+
+require 'rubocop/cop/style/access_modifier_indentation'
+require 'rubocop/cop/style/accessor_method_name'
+require 'rubocop/cop/style/alias'
+require 'rubocop/cop/style/align_array'
+require 'rubocop/cop/style/align_hash'
+require 'rubocop/cop/style/align_parameters'
+require 'rubocop/cop/style/and_or'
+require 'rubocop/cop/style/array_join'
+require 'rubocop/cop/style/ascii_comments'
+require 'rubocop/cop/style/ascii_identifiers'
+require 'rubocop/cop/style/attr'
+require 'rubocop/cop/style/begin_block'
+require 'rubocop/cop/style/block_comments'
+require 'rubocop/cop/style/block_nesting'
+require 'rubocop/cop/style/blocks'
+require 'rubocop/cop/style/braces_around_hash_parameters'
+require 'rubocop/cop/style/case_equality'
+require 'rubocop/cop/style/case_indentation'
+require 'rubocop/cop/style/character_literal'
+require 'rubocop/cop/style/class_and_module_camel_case'
+require 'rubocop/cop/style/class_and_module_children'
+require 'rubocop/cop/style/class_length'
+require 'rubocop/cop/style/class_methods'
+require 'rubocop/cop/style/class_vars'
+require 'rubocop/cop/style/collection_methods'
+require 'rubocop/cop/style/colon_method_call'
+require 'rubocop/cop/style/comment_annotation'
+require 'rubocop/cop/style/constant_name'
+require 'rubocop/cop/style/cyclomatic_complexity'
+require 'rubocop/cop/style/def_with_parentheses'
+require 'rubocop/cop/style/deprecated_hash_methods'
+require 'rubocop/cop/style/documentation'
+require 'rubocop/cop/style/dot_position'
+require 'rubocop/cop/style/double_negation'
+require 'rubocop/cop/style/empty_line_between_defs'
+require 'rubocop/cop/style/empty_lines'
+require 'rubocop/cop/style/empty_lines_around_access_modifier'
+require 'rubocop/cop/style/empty_lines_around_body'
+require 'rubocop/cop/style/empty_literal'
+require 'rubocop/cop/style/encoding'
+require 'rubocop/cop/style/end_block'
+require 'rubocop/cop/style/end_of_line'
+require 'rubocop/cop/style/even_odd'
+require 'rubocop/cop/style/file_name'
+require 'rubocop/cop/style/final_newline'
+require 'rubocop/cop/style/flip_flop'
+require 'rubocop/cop/style/for'
+require 'rubocop/cop/style/format_string'
+require 'rubocop/cop/style/global_vars'
+require 'rubocop/cop/style/guard_clause'
+require 'rubocop/cop/style/hash_syntax'
+require 'rubocop/cop/style/if_unless_modifier'
+require 'rubocop/cop/style/if_with_semicolon'
+require 'rubocop/cop/style/indent_array'
+require 'rubocop/cop/style/indent_hash'
+require 'rubocop/cop/style/indentation_consistency'
+require 'rubocop/cop/style/indentation_width'
+require 'rubocop/cop/style/lambda'
+require 'rubocop/cop/style/lambda_call'
+require 'rubocop/cop/style/leading_comment_space'
+require 'rubocop/cop/style/line_end_concatenation'
+require 'rubocop/cop/style/line_length'
+require 'rubocop/cop/style/method_call_parentheses'
+require 'rubocop/cop/style/method_called_on_do_end_block'
+require 'rubocop/cop/style/method_def_parentheses'
+require 'rubocop/cop/style/method_length'
+require 'rubocop/cop/style/method_name'
+require 'rubocop/cop/style/module_function'
+require 'rubocop/cop/style/multiline_block_chain'
+require 'rubocop/cop/style/multiline_if_then'
+require 'rubocop/cop/style/multiline_ternary_operator'
+require 'rubocop/cop/style/negated_if'
+require 'rubocop/cop/style/negated_while'
+require 'rubocop/cop/style/nested_ternary_operator'
+require 'rubocop/cop/style/nil_comparison'
+require 'rubocop/cop/style/non_nil_check'
+require 'rubocop/cop/style/not'
+require 'rubocop/cop/style/numeric_literals'
+require 'rubocop/cop/style/one_line_conditional'
+require 'rubocop/cop/style/op_method'
+require 'rubocop/cop/style/parameter_lists'
+require 'rubocop/cop/style/parentheses_around_condition'
+require 'rubocop/cop/style/percent_literal_delimiters'
+require 'rubocop/cop/style/perl_backrefs'
+require 'rubocop/cop/style/predicate_name'
+require 'rubocop/cop/style/proc'
+require 'rubocop/cop/style/raise_args'
+require 'rubocop/cop/style/redundant_begin'
+require 'rubocop/cop/style/redundant_exception'
+require 'rubocop/cop/style/redundant_return'
+require 'rubocop/cop/style/redundant_self'
+require 'rubocop/cop/style/regexp_literal'
+require 'rubocop/cop/style/rescue_modifier'
+require 'rubocop/cop/style/self_assignment'
+require 'rubocop/cop/style/semicolon'
+require 'rubocop/cop/style/signal_exception'
+require 'rubocop/cop/style/single_line_block_params'
+require 'rubocop/cop/style/single_line_methods'
+require 'rubocop/cop/style/single_space_before_first_arg'
+require 'rubocop/cop/style/space_after_colon'
+require 'rubocop/cop/style/space_after_comma'
+require 'rubocop/cop/style/space_after_control_keyword'
+require 'rubocop/cop/style/space_after_method_name'
+require 'rubocop/cop/style/space_after_not'
+require 'rubocop/cop/style/space_after_semicolon'
+require 'rubocop/cop/style/space_around_equals_in_parameter_default'
+require 'rubocop/cop/style/space_around_operators'
+require 'rubocop/cop/style/space_before_block_braces'
+require 'rubocop/cop/style/space_before_modifier_keyword'
+require 'rubocop/cop/style/space_inside_block_braces'
+require 'rubocop/cop/style/space_inside_brackets'
+require 'rubocop/cop/style/space_inside_hash_literal_braces'
+require 'rubocop/cop/style/space_inside_parens'
+require 'rubocop/cop/style/special_global_vars'
+require 'rubocop/cop/style/string_literals'
+require 'rubocop/cop/style/symbol_array'
+require 'rubocop/cop/style/tab'
+require 'rubocop/cop/style/trailing_blank_lines'
+require 'rubocop/cop/style/trailing_comma'
+require 'rubocop/cop/style/trailing_whitespace'
+require 'rubocop/cop/style/trivial_accessors'
+require 'rubocop/cop/style/unless_else'
+require 'rubocop/cop/style/variable_interpolation'
+require 'rubocop/cop/style/variable_name'
+require 'rubocop/cop/style/when_then'
+require 'rubocop/cop/style/while_until_do'
+require 'rubocop/cop/style/while_until_modifier'
+require 'rubocop/cop/style/word_array'
+
+require 'rubocop/cop/rails/action_filter'
+require 'rubocop/cop/rails/default_scope'
+require 'rubocop/cop/rails/has_and_belongs_to_many'
+require 'rubocop/cop/rails/output'
+require 'rubocop/cop/rails/read_write_attribute'
+require 'rubocop/cop/rails/scope_args'
+require 'rubocop/cop/rails/validation'
+
+require 'rubocop/formatter/base_formatter'
+require 'rubocop/formatter/simple_text_formatter'
+require 'rubocop/formatter/disabled_lines_formatter'
+require 'rubocop/formatter/disabled_config_formatter'
+require 'rubocop/formatter/emacs_style_formatter'
+require 'rubocop/formatter/clang_style_formatter'
+require 'rubocop/formatter/progress_formatter'
+require 'rubocop/formatter/fuubar_style_formatter'
+require 'rubocop/formatter/json_formatter'
+require 'rubocop/formatter/file_list_formatter'
+require 'rubocop/formatter/offense_count_formatter'
+require 'rubocop/formatter/formatter_set'
+
+require 'rubocop/config'
+require 'rubocop/config_loader'
+require 'rubocop/config_store'
+require 'rubocop/target_finder'
+require 'rubocop/token'
+require 'rubocop/comment_config'
+require 'rubocop/processed_source'
+require 'rubocop/source_parser'
+require 'rubocop/file_inspector'
+require 'rubocop/cli'
+require 'rubocop/options'
diff --git a/lib/rubocop/cli.rb b/lib/rubocop/cli.rb
new file mode 100644
index 0000000..dba81aa
--- /dev/null
+++ b/lib/rubocop/cli.rb
@@ -0,0 +1,109 @@
+# encoding: utf-8
+
+module Rubocop
+ # The CLI is a class responsible of handling all the command line interface
+ # logic.
+ class CLI
+ # If set true while running,
+ # RuboCop will abort processing and exit gracefully.
+ attr_accessor :wants_to_quit
+ attr_reader :options, :config_store
+
+ alias_method :wants_to_quit?, :wants_to_quit
+
+ def initialize
+ @options = {}
+ @config_store = ConfigStore.new
+ end
+
+ # Entry point for the application logic. Here we
+ # do the command line arguments processing and inspect
+ # the target files
+ # @return [Fixnum] UNIX exit code
+ def run(args = ARGV)
+ trap_interrupt
+
+ @options, remaining_args = Options.new.parse(args)
+ act_on_options(remaining_args)
+ target_files = target_finder.find(remaining_args)
+
+ inspector = FileInspector.new(@options)
+ any_failed = inspector.process_files(target_files, @config_store) do
+ wants_to_quit?
+ end
+ inspector.display_error_summary
+
+ !any_failed && !wants_to_quit ? 0 : 1
+ rescue => e
+ $stderr.puts e.message
+ $stderr.puts e.backtrace
+ return 1
+ end
+
+ def trap_interrupt
+ Signal.trap('INT') do
+ exit!(1) if wants_to_quit?
+ self.wants_to_quit = true
+ $stderr.puts
+ $stderr.puts 'Exiting... Interrupt again to exit immediately.'
+ end
+ end
+
+ private
+
+ def act_on_options(args)
+ handle_exiting_options
+
+ ConfigLoader.debug = @options[:debug]
+ ConfigLoader.auto_gen_config = @options[:auto_gen_config]
+
+ @config_store.options_config = @options[:config] if @options[:config]
+
+ Rainbow.enabled = false unless @options[:color]
+ end
+
+ def handle_exiting_options
+ return unless Options::EXITING_OPTIONS.any? { |o| @options.key? o }
+
+ puts Rubocop::Version.version(false) if @options[:version]
+ puts Rubocop::Version.version(true) if @options[:verbose_version]
+ print_available_cops if @options[:show_cops]
+ exit(0)
+ end
+
+ def print_available_cops
+ cops = Cop::Cop.all
+ show_all = @options[:show_cops].empty?
+
+ if show_all
+ puts "# Available cops (#{cops.length}) + config for #{Dir.pwd}: "
+ end
+
+ cops.types.sort!.each { |type| print_cops_of_type(cops, type, show_all) }
+ end
+
+ def print_cops_of_type(cops, type, show_all)
+ cops_of_this_type = cops.with_type(type).sort_by!(&:cop_name)
+
+ if show_all
+ puts "# Type '#{type.to_s.capitalize}' (#{cops_of_this_type.size}):"
+ end
+
+ selected_cops = cops_of_this_type.select do |cop|
+ show_all || @options[:show_cops].include?(cop.cop_name)
+ end
+
+ selected_cops.each do |cop|
+ puts '# Supports --auto-correct' if cop.new.support_autocorrect?
+ puts "#{cop.cop_name}:"
+ cnf = @config_store.for(Dir.pwd.to_s).for_cop(cop)
+ puts cnf.to_yaml.lines.to_a[1..-1].map { |line| ' ' + line }
+ puts
+ end
+ end
+
+ def target_finder
+ @target_finder ||= TargetFinder.new(@config_store, @options)
+ end
+ end
+end
diff --git a/lib/rubocop/comment_config.rb b/lib/rubocop/comment_config.rb
new file mode 100644
index 0000000..02bf7f7
--- /dev/null
+++ b/lib/rubocop/comment_config.rb
@@ -0,0 +1,99 @@
+# encoding: utf-8
+
+module Rubocop
+ # This class parses the special `rubocop:disable` comments in a source
+ # and provides a way to check if each cop is enabled at arbitrary line.
+ class CommentConfig
+ COMMENT_DIRECTIVE_REGEXP = Regexp.new(
+ '\A# rubocop : ((?:dis|en)able)\b ((?:\w+,? )+)'.gsub(' ', '\s*')
+ )
+
+ attr_reader :processed_source
+
+ def initialize(processed_source)
+ @processed_source = processed_source
+ end
+
+ def cop_enabled_at_line?(cop, line_number)
+ cop = cop.cop_name if cop.respond_to?(:cop_name)
+ disabled_line_ranges = cop_disabled_line_ranges[cop]
+ disabled_line_ranges.none? { |range| range.include?(line_number) }
+ end
+
+ def cop_disabled_line_ranges
+ @cop_disabled_line_ranges ||= analyze
+ end
+
+ private
+
+ def analyze
+ disabled_line_ranges = Hash.new { |hash, key| hash[key] = [] }
+ disablement_start_line_numbers = {}
+
+ each_mentioned_cop do |cop_name, disabled, line, single_line|
+ if single_line
+ next unless disabled
+ disabled_line_ranges[cop_name] << (line..line)
+ else
+ if disabled
+ disablement_start_line_numbers[cop_name] = line
+ else
+ start_line = disablement_start_line_numbers.delete(cop_name)
+ next unless start_line
+ disabled_line_ranges[cop_name] << (start_line..line)
+ end
+ end
+ end
+
+ disablement_start_line_numbers.each do |cop_name, start_line|
+ disabled_line_ranges[cop_name] << (start_line..Float::INFINITY)
+ end
+
+ disabled_line_ranges
+ end
+
+ def each_mentioned_cop
+ all_cop_names = nil # For performance improvement
+
+ return if processed_source.comments.nil?
+
+ processed_source.comments.each do |comment|
+ match = comment.text.match(COMMENT_DIRECTIVE_REGEXP)
+ next unless match
+
+ switch, cops_string = match.captures
+
+ if cops_string == 'all'
+ all_cop_names ||= Cop::Cop.all.map(&:cop_name)
+ cop_names = all_cop_names
+ else
+ cop_names = cops_string.split(/,\s*/)
+ end
+
+ disabled = (switch == 'disable')
+ comment_line_number = comment.loc.expression.line
+ single_line = !comment_only_line?(comment_line_number)
+
+ cop_names.each do |cop_name|
+ yield cop_name, disabled, comment_line_number, single_line
+ end
+ end
+ end
+
+ def comment_only_line?(line_number)
+ non_comment_token_line_numbers.none? do |non_comment_line_number|
+ non_comment_line_number == line_number
+ end
+ end
+
+ def non_comment_token_line_numbers
+ @non_comment_token_line_numbers ||= begin
+ non_comment_tokens = processed_source.tokens.reject do |token|
+ token.type == :tCOMMENT
+ end
+
+ non_comment_tokens.map { |token| token.pos.line }.uniq
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/config.rb b/lib/rubocop/config.rb
new file mode 100644
index 0000000..bfa9d33
--- /dev/null
+++ b/lib/rubocop/config.rb
@@ -0,0 +1,106 @@
+# encoding: utf-8
+
+require 'delegate'
+require 'pathname'
+
+module Rubocop
+ # This class represents the configuration of the RuboCop application
+ # and all its cops. A Config is associated with a YAML configuration
+ # file from which it was read. Several different Configs can be used
+ # during a run of the rubocop program, if files in several
+ # directories are inspected.
+ class Config < DelegateClass(Hash)
+ include PathUtil
+
+ class ValidationError < StandardError; end
+
+ COMMON_PARAMS = %w(Exclude Include Severity)
+
+ attr_reader :loaded_path
+
+ def initialize(hash = {}, loaded_path = nil)
+ @hash = hash
+ @loaded_path = loaded_path
+ super(@hash)
+ end
+
+ def for_cop(cop)
+ cop = cop.cop_name if cop.respond_to?(:cop_name)
+ self[cop]
+ end
+
+ def cop_enabled?(cop)
+ for_cop(cop).nil? || for_cop(cop)['Enabled']
+ end
+
+ def warn_unless_valid
+ validate
+ rescue Config::ValidationError => e
+ warn "Warning: #{e.message}".color(:red)
+ end
+
+ # TODO: This should be a private method
+ def validate
+ # Don't validate RuboCop's own files. Avoids inifinite recursion.
+ return if @loaded_path.start_with?(ConfigLoader::RUBOCOP_HOME)
+
+ default_config = ConfigLoader.default_configuration
+
+ valid_cop_names, invalid_cop_names = @hash.keys.partition do |key|
+ default_config.key?(key)
+ end
+
+ invalid_cop_names.each do |name|
+ fail ValidationError,
+ "unrecognized cop #{name} found in #{loaded_path || self}"
+ end
+
+ valid_cop_names.each do |name|
+ @hash[name].each_key do |param|
+ unless COMMON_PARAMS.include?(param) ||
+ default_config[name].key?(param)
+ fail ValidationError,
+ "unrecognized parameter #{name}:#{param} found " \
+ "in #{loaded_path || self}"
+ end
+ end
+ end
+ end
+
+ def file_to_include?(file)
+ relative_file_path = path_relative_to_config(file)
+ patterns_to_include.any? do |pattern|
+ match_path?(pattern, relative_file_path)
+ end
+ end
+
+ def file_to_exclude?(file)
+ file = File.join(Dir.pwd, file) unless file.start_with?('/')
+ patterns_to_exclude.any? { |pattern| match_path?(pattern, file) }
+ end
+
+ def patterns_to_include
+ @hash['AllCops']['Include']
+ end
+
+ def patterns_to_exclude
+ @hash['AllCops']['Exclude']
+ end
+
+ def path_relative_to_config(path)
+ relative_path(path, base_dir_for_path_parameters)
+ end
+
+ # Paths specified in .rubocop.yml files are relative to the directory where
+ # that file is. Paths in other config files are relative to the current
+ # directory. This is so that paths in config/default.yml, for example, are
+ # not relative to RuboCop's config directory since that wouldn't work.
+ def base_dir_for_path_parameters
+ if File.basename(loaded_path) == ConfigLoader::DOTFILE
+ File.dirname(loaded_path)
+ else
+ Dir.pwd
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/config_loader.rb b/lib/rubocop/config_loader.rb
new file mode 100644
index 0000000..90f3980
--- /dev/null
+++ b/lib/rubocop/config_loader.rb
@@ -0,0 +1,161 @@
+# encoding: utf-8
+
+require 'yaml'
+require 'pathname'
+
+module Rubocop
+ # This class represents the configuration of the RuboCop application
+ # and all its cops. A Config is associated with a YAML configuration
+ # file from which it was read. Several different Configs can be used
+ # during a run of the rubocop program, if files in several
+ # directories are inspected.
+ class ConfigLoader
+ DOTFILE = '.rubocop.yml'
+ RUBOCOP_HOME = File.realpath(File.join(File.dirname(__FILE__), '..', '..'))
+ DEFAULT_FILE = File.join(RUBOCOP_HOME, 'config', 'default.yml')
+ AUTO_GENERATED_FILE = 'rubocop-todo.yml'
+
+ class << self
+ attr_accessor :debug, :auto_gen_config
+ attr_writer :root_level # The upwards search is stopped at this level.
+
+ alias_method :debug?, :debug
+ alias_method :auto_gen_config?, :auto_gen_config
+
+ def load_file(path)
+ path = File.absolute_path(path)
+ hash = YAML.load_file(path) || {}
+ puts "configuration from #{path}" if debug?
+
+ resolve_inheritance(path, hash)
+
+ hash.delete('inherit_from')
+ config = Config.new(hash, path)
+ deprecation_check(config)
+ config.warn_unless_valid
+ make_excludes_absolute(config)
+ config
+ end
+
+ def deprecation_check(config)
+ return unless config['AllCops']
+ if config['AllCops']['Excludes']
+ warn('AllCops/Excludes was renamed to AllCops/Exclude')
+ exit(-1)
+ elsif config['AllCops']['Includes']
+ warn('AllCops/Includes was renamed to AllCops/Include')
+ exit(-1)
+ end
+ end
+
+ def make_excludes_absolute(config)
+ if config['AllCops'] && config['AllCops']['Exclude']
+ config['AllCops']['Exclude'].map! do |exclude_elem|
+ if exclude_elem.is_a?(String) && !exclude_elem.start_with?('/')
+ File.join(config.base_dir_for_path_parameters, exclude_elem)
+ else
+ exclude_elem
+ end
+ end
+ end
+ end
+
+ # Return a recursive merge of two hashes. That is, a normal hash merge,
+ # with the addition that any value that is a hash, and occurs in both
+ # arguments, will also be merged. And so on.
+ def merge(base_hash, derived_hash)
+ result = base_hash.merge(derived_hash)
+ keys_appearing_in_both = base_hash.keys & derived_hash.keys
+ keys_appearing_in_both.each do |key|
+ if base_hash[key].is_a?(Hash)
+ result[key] = merge(base_hash[key], derived_hash[key])
+ end
+ end
+ result
+ end
+
+ def base_configs(path, inherit_from)
+ configs = Array(inherit_from).map do |f|
+ f = File.join(File.dirname(path), f) unless f.start_with?('/')
+ unless auto_gen_config? && f.include?(AUTO_GENERATED_FILE)
+ print 'Inheriting ' if debug?
+ load_file(f)
+ end
+ end
+
+ configs.compact
+ end
+
+ # Returns the path of .rubocop.yml searching upwards in the
+ # directory structure starting at the given directory where the
+ # inspected file is. If no .rubocop.yml is found there, the
+ # user's home directory is checked. If there's no .rubocop.yml
+ # there either, the path to the default file is returned.
+ def configuration_file_for(target_dir)
+ config_files_in_path(target_dir).first || DEFAULT_FILE
+ end
+
+ def configuration_from_file(config_file)
+ config = load_file(config_file)
+ return config if config_file == DEFAULT_FILE
+
+ found_files = config_files_in_path(config_file)
+ if found_files.any? && found_files.last != config_file
+ print 'AllCops/Exclude ' if debug?
+ add_excludes_from_higher_level(config, load_file(found_files.last))
+ end
+ merge_with_default(config, config_file)
+ end
+
+ def add_excludes_from_higher_level(config, highest_config)
+ if highest_config['AllCops'] && highest_config['AllCops']['Exclude']
+ config['AllCops'] ||= {}
+ excludes = config['AllCops']['Exclude'] ||= []
+ highest_config['AllCops']['Exclude'].each do |path|
+ unless path.is_a?(Regexp) || path.start_with?('/')
+ path = File.join(File.dirname(highest_config.loaded_path), path)
+ end
+ excludes << path unless excludes.include?(path)
+ end
+ end
+ end
+
+ def default_configuration
+ @default_configuration ||= begin
+ print 'Default ' if debug?
+ load_file(DEFAULT_FILE)
+ end
+ end
+
+ def merge_with_default(config, config_file)
+ Config.new(merge(default_configuration, config), config_file)
+ end
+
+ private
+
+ def resolve_inheritance(path, hash)
+ base_configs(path, hash['inherit_from']).reverse_each do |base_config|
+ base_config.each do |k, v|
+ hash[k] = hash.key?(k) ? merge(v, hash[k]) : v if v.is_a?(Hash)
+ end
+ end
+ end
+
+ def config_files_in_path(target)
+ possible_config_files = dirs_to_search(target).map do |dir|
+ File.join(dir, DOTFILE)
+ end
+ possible_config_files.select { |config_file| File.exist?(config_file) }
+ end
+
+ def dirs_to_search(target_dir)
+ dirs_to_search = []
+ Pathname.new(File.expand_path(target_dir)).ascend do |dir_pathname|
+ break if dir_pathname.to_s == @root_level
+ dirs_to_search << dir_pathname.to_s
+ end
+ dirs_to_search << Dir.home
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/config_store.rb b/lib/rubocop/config_store.rb
new file mode 100644
index 0000000..faa25cb
--- /dev/null
+++ b/lib/rubocop/config_store.rb
@@ -0,0 +1,45 @@
+# encoding: utf-8
+
+module Rubocop
+ # Handles caching of configurations and association of inspected
+ # ruby files to configurations.
+ class ConfigStore
+ def initialize
+ # @options_config stores a config that is specified in the command line.
+ # This takes precedence over configs located in any directories
+ @options_config = nil
+
+ # @path_cache maps directories to configuration paths. We search
+ # for .rubocop.yml only if we haven't already found it for the
+ # given directory.
+ @path_cache = {}
+
+ # @object_cache maps configuration file paths to
+ # configuration objects so we only need to load them once.
+ @object_cache = {}
+ end
+
+ def options_config=(options_config)
+ loaded_config = ConfigLoader.load_file(options_config)
+ ConfigLoader.make_excludes_absolute(loaded_config)
+ @options_config = ConfigLoader.merge_with_default(loaded_config,
+ options_config)
+ end
+
+ def for(file_or_dir)
+ return @options_config if @options_config
+
+ dir = if File.directory?(file_or_dir)
+ file_or_dir
+ else
+ File.dirname(file_or_dir)
+ end
+ @path_cache[dir] ||= ConfigLoader.configuration_file_for(dir)
+ path = @path_cache[dir]
+ @object_cache[path] ||= begin
+ print "For #{dir}: " if ConfigLoader.debug?
+ ConfigLoader.configuration_from_file(path)
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/commissioner.rb b/lib/rubocop/cop/commissioner.rb
new file mode 100644
index 0000000..ffe0cbb
--- /dev/null
+++ b/lib/rubocop/cop/commissioner.rb
@@ -0,0 +1,105 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # Commissioner class is responsible for processing the AST and delegating
+ # work to the specified cops.
+ class Commissioner < Parser::AST::Processor
+ attr_reader :errors
+
+ METHODS_NOT_DEFINED_IN_PARSER_PROCESSOR = [
+ :on_sym, :on_str, :on_int, :on_float
+ ]
+
+ def self.callback_methods
+ Parser::AST::Processor.instance_methods.select do |method|
+ method.to_s =~ /^on_/
+ end + METHODS_NOT_DEFINED_IN_PARSER_PROCESSOR
+ end
+
+ # Methods that are not defined in Parser::AST::Processor
+ # won't have a `super` to call. So we should not attempt
+ # to invoke `super` when defining them.
+ def self.call_super(callback)
+ if METHODS_NOT_DEFINED_IN_PARSER_PROCESSOR.include?(callback)
+ ''
+ else
+ 'super'
+ end
+ end
+
+ def initialize(cops, options = {})
+ @cops = cops
+ @options = options
+ reset_errors
+ end
+
+ callback_methods.each do |callback|
+ class_eval <<-EOS
+ def #{callback}(node)
+ @cops.each do |cop|
+ next unless cop.respond_to?(:#{callback})
+ with_cop_error_handling(cop) do
+ cop.send(:#{callback}, node)
+ end
+ end
+
+ #{call_super(callback)}
+ end
+ EOS
+ end
+
+ def investigate(processed_source)
+ reset_errors
+ prepare(processed_source)
+ invoke_cops_callback(processed_source)
+ process(processed_source.ast) if processed_source.ast
+ @cops.each_with_object([]) do |cop, offenses|
+ filename = processed_source.buffer.name
+ # ignore files that are of no interest to the cop in question
+ offenses.concat(cop.offenses) if cop.relevant_file?(filename)
+ end
+ end
+
+ private
+
+ def reset_errors
+ @errors = Hash.new { |hash, k| hash[k] = [] }
+ end
+
+ # TODO: Bad design.
+ def prepare(processed_source)
+ @cops.each { |cop| cop.processed_source = processed_source }
+ end
+
+ # There are cops that require their own custom processing.
+ # If they define the #investigate method, all input parameters passed
+ # to the commissioner will be passed to the cop too in order to do
+ # its own processing.
+ def invoke_cops_callback(processed_source)
+ @cops.each do |cop|
+ next unless cop.respond_to?(:investigate)
+
+ filename = processed_source.buffer.name
+
+ # ignore files that are of no interest to the cop in question
+ next unless cop.relevant_file?(filename)
+
+ with_cop_error_handling(cop) do
+ cop.investigate(processed_source)
+ end
+ end
+ end
+
+ def with_cop_error_handling(cop)
+ yield
+ rescue => e
+ if @options[:raise_error]
+ raise e
+ else
+ @errors[cop] << e
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/cop.rb b/lib/rubocop/cop/cop.rb
new file mode 100644
index 0000000..603a01a
--- /dev/null
+++ b/lib/rubocop/cop/cop.rb
@@ -0,0 +1,192 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # Store for all cops with helper functions
+ class CopStore < ::Array
+ # @return [Array<String>] list of types for current cops.
+ def types
+ @types = map(&:cop_type).uniq! unless defined? @types
+ @types
+ end
+
+ # @return [Array<Cop>] Cops for that specific type.
+ def with_type(type)
+ select { |c| c.cop_type == type }
+ end
+
+ # @return [Array<Cop>] Cops not for a specific type.
+ def without_type(type)
+ reject { |c| c.cop_type == type }
+ end
+ end
+
+ # A scaffold for concrete cops.
+ #
+ # The Cop class is meant to be extended.
+ #
+ # Cops track offenses and can autocorrect them of the fly.
+ #
+ # A commissioner object is responsible for traversing the AST and invoking
+ # the specific callbacks on each cop.
+ # If a cop needs to do its own processing of the AST or depends on
+ # something else, it should define the `#investigate` method and do
+ # the processing there.
+ #
+ # @example
+ #
+ # class CustomCop < Cop
+ # def investigate(processed_source)
+ # # Do custom processing
+ # end
+ # end
+ class Cop
+ extend AST::Sexp
+ include Util
+ include IgnoredNode
+
+ attr_reader :config, :offenses, :corrections
+ attr_accessor :processed_source # TODO: Bad design.
+
+ @all = CopStore.new
+
+ def self.all
+ @all.clone
+ end
+
+ def self.non_rails
+ @all.without_type(:rails)
+ end
+
+ def self.inherited(subclass)
+ @all << subclass
+ end
+
+ def self.cop_name
+ name.to_s.split('::').last
+ end
+
+ def self.cop_type
+ name.to_s.split('::')[-2].downcase.to_sym
+ end
+
+ def self.lint?
+ cop_type == :lint
+ end
+
+ def self.rails?
+ cop_type == :rails
+ end
+
+ def initialize(config = nil, options = nil)
+ @config = config || Config.new
+ @options = options || { auto_correct: false, debug: false }
+
+ @offenses = []
+ @corrections = []
+ end
+
+ def cop_config
+ @config.for_cop(self)
+ end
+
+ def autocorrect?
+ @options[:auto_correct] && support_autocorrect?
+ end
+
+ def debug?
+ @options[:debug]
+ end
+
+ def display_cop_names?
+ debug? || @options[:display_cop_names]
+ end
+
+ def message(node = nil)
+ self.class::MSG
+ end
+
+ def support_autocorrect?
+ respond_to?(:autocorrect, true)
+ end
+
+ def add_offense(node, loc, message = nil, severity = nil)
+ location = loc.is_a?(Symbol) ? node.loc.send(loc) : loc
+
+ return unless enabled_line?(location.line)
+
+ # Don't include the same location twice for one cop.
+ return if @offenses.find { |o| o.location == location }
+
+ severity = custom_severity || severity || default_severity
+
+ message ||= message(node)
+ message = display_cop_names? ? "#{name}: #{message}" : message
+
+ autocorrect(node) if autocorrect?
+ @offenses << Offense.new(severity, location, message, name,
+ autocorrect?)
+ yield if block_given?
+ end
+
+ def config_to_allow_offenses
+ Formatter::DisabledConfigFormatter.config_to_allow_offenses[cop_name]
+ end
+
+ def config_to_allow_offenses=(hash)
+ Formatter::DisabledConfigFormatter.config_to_allow_offenses[cop_name] =
+ hash
+ end
+
+ def cop_name
+ self.class.cop_name
+ end
+
+ alias_method :name, :cop_name
+
+ def include_file?(file)
+ file_name_matches_any?(file, 'Include', true)
+ end
+
+ def exclude_file?(file)
+ file_name_matches_any?(file, 'Exclude', false)
+ end
+
+ def relevant_file?(file)
+ include_file?(file) && !exclude_file?(file)
+ end
+
+ private
+
+ def file_name_matches_any?(file, parameter, default_result)
+ patterns = cop_config && cop_config[parameter]
+ return default_result unless patterns
+ path = config.path_relative_to_config(file)
+ patterns.any? { |pattern| match_path?(pattern, path) }
+ end
+
+ def enabled_line?(line_number)
+ return true unless @processed_source
+ @processed_source.comment_config
+ .cop_enabled_at_line?(self, line_number)
+ end
+
+ def default_severity
+ self.class.lint? ? :warning : :convention
+ end
+
+ def custom_severity
+ severity = cop_config && cop_config['Severity']
+ if severity
+ if Severity::NAMES.include?(severity.to_sym)
+ severity.to_sym
+ else
+ warn "Warning: Invalid severity '#{severity}'. " +
+ "Valid severities are #{Severity::NAMES.join(', ')}."
+ .color(:red)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/corrector.rb b/lib/rubocop/cop/corrector.rb
new file mode 100644
index 0000000..4742661
--- /dev/null
+++ b/lib/rubocop/cop/corrector.rb
@@ -0,0 +1,84 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # This class takes a source buffer and rewrite its source
+ # based on the different correction rules supplied.
+ #
+ # Important!
+ # The nodes modified by the corrections should be part of the
+ # AST of the source_buffer.
+ class Corrector
+ #
+ # @param source_buffer [Parser::Source::Buffer]
+ # @param corrections [Array(#call)]
+ # Array of Objects that respond to #call. They will receive the
+ # corrector itself and should use its method to modify the source.
+ #
+ # @example
+ #
+ # class AndOrCorrector
+ # def initialize(node)
+ # @node = node
+ # end
+ #
+ # def call(corrector)
+ # replacement = (@node.type == :and ? '&&' : '||')
+ # corrector.replace(@node.loc.operator, replacement)
+ # end
+ # end
+ #
+ # corrections = [AndOrCorrector.new(node)]
+ # corrector = Corrector.new(source_buffer, corrections)
+ def initialize(source_buffer, corrections)
+ @source_buffer = source_buffer
+ @corrections = corrections
+ @source_rewriter = Parser::Source::Rewriter.new(source_buffer)
+ end
+
+ # Does the actual rewrite and returns string corresponding to
+ # the rewritten source.
+ #
+ # @return [String]
+ # TODO: Handle conflict exceptions raised from the Source::Rewriter
+ def rewrite
+ @corrections.each do |correction|
+ correction.call(self)
+ end
+
+ @source_rewriter.process
+ end
+
+ # Removes the source range.
+ #
+ # @param [Parser::Source::Range] range
+ def remove(range)
+ @source_rewriter.remove(range)
+ end
+
+ # Inserts new code before the given source range.
+ #
+ # @param [Parser::Source::Range] range
+ # @param [String] content
+ def insert_before(range, content)
+ @source_rewriter.insert_before(range, content)
+ end
+
+ # Inserts new code after the given source range.
+ #
+ # @param [Parser::Source::Range] range
+ # @param [String] content
+ def insert_after(range, content)
+ @source_rewriter.insert_after(range, content)
+ end
+
+ # Replaces the code of the source range `range` with `content`.
+ #
+ # @param [Parser::Source::Range] range
+ # @param [String] content
+ def replace(range, content)
+ @source_rewriter.replace(range, content)
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/ignored_node.rb b/lib/rubocop/cop/ignored_node.rb
new file mode 100644
index 0000000..a7b7664
--- /dev/null
+++ b/lib/rubocop/cop/ignored_node.rb
@@ -0,0 +1,31 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # Handles adding and checking ignored nodes.
+ module IgnoredNode
+ def ignore_node(node)
+ @ignored_nodes ||= []
+ @ignored_nodes << node
+ end
+
+ def part_of_ignored_node?(node)
+ return false unless @ignored_nodes
+ expression = node.loc.expression
+ @ignored_nodes.each do |ignored_node|
+ if ignored_node.loc.expression.begin_pos <= expression.begin_pos &&
+ ignored_node.loc.expression.end_pos >= expression.end_pos
+ return true
+ end
+ end
+
+ false
+ end
+
+ def ignored_node?(node)
+ # Same object found in array?
+ @ignored_nodes && @ignored_nodes.any? { |n| n.eql?(node) }
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/ambiguous_operator.rb b/lib/rubocop/cop/lint/ambiguous_operator.rb
new file mode 100644
index 0000000..954b61a
--- /dev/null
+++ b/lib/rubocop/cop/lint/ambiguous_operator.rb
@@ -0,0 +1,50 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for ambiguous operators in the first argument of a
+ # method invocation without parentheses.
+ #
+ # @example
+ # array = [1, 2, 3]
+ #
+ # # The `*` is interpreted as a splat operator but it could possibly be
+ # # a `*` method invocation (i.e. `do_something.*(array)`).
+ # do_something *array
+ #
+ # # With parentheses, there's no ambiguity.
+ # do_something(*array)
+ class AmbiguousOperator < Cop
+ include ParserDiagnostic
+
+ AMBIGUITIES = {
+ '+' => { actual: 'positive number', possible: 'addition' },
+ '-' => { actual: 'negative number', possible: 'subtraction' },
+ '*' => { actual: 'splat', possible: 'multiplication' },
+ '&' => { actual: 'block', possible: 'binary AND' },
+ '**' => { actual: 'keyword splat', possible: 'exponent' }
+ }.each do |key, hash|
+ hash[:operator] = key
+ end
+
+ MSG_FORMAT = 'Ambiguous %{actual} operator. Parenthesize the method ' \
+ "arguments if it's surely a %{actual} operator, or add " \
+ 'a whitespace to the right of the %{operator} if it ' \
+ 'should be a %{possible}.'
+
+ private
+
+ def relevant_diagnostic?(diagnostic)
+ diagnostic.reason == :ambiguous_prefix
+ end
+
+ def alternative_message(diagnostic)
+ operator = diagnostic.location.source
+ hash = AMBIGUITIES[operator]
+ format(MSG_FORMAT, hash)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb b/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb
new file mode 100644
index 0000000..845bf7b
--- /dev/null
+++ b/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb
@@ -0,0 +1,36 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for ambiguous regexp literals in the first argument of
+ # a method invocation without parentheses.
+ #
+ # @example
+ # # This is interpreted as a method invocation with a regexp literal,
+ # # but it could possibly be `/` method invocations.
+ # # (i.e. `do_something./(pattern)./(i)`)
+ # do_something /pattern/i
+ #
+ # # With parentheses, there's no ambiguity.
+ # do_something(/pattern/i)
+ class AmbiguousRegexpLiteral < Cop
+ include ParserDiagnostic
+
+ MSG = 'Ambiguous regexp literal. Parenthesize the method arguments ' \
+ "if it's surely a regexp literal, or add a whitespace to the " \
+ 'right of the / if it should be a division.'
+
+ private
+
+ def relevant_diagnostic?(diagnostic)
+ diagnostic.reason == :ambiguous_literal
+ end
+
+ def alternative_message(diagnostic)
+ MSG
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/assignment_in_condition.rb b/lib/rubocop/cop/lint/assignment_in_condition.rb
new file mode 100644
index 0000000..35f7cb8
--- /dev/null
+++ b/lib/rubocop/cop/lint/assignment_in_condition.rb
@@ -0,0 +1,46 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for assignments in the conditions of
+ # if/while/until.
+ class AssignmentInCondition < Cop
+ include SafeAssignment
+
+ MSG = 'Assignment in condition - you probably meant to use `==`.'
+
+ def on_if(node)
+ check(node)
+ end
+
+ def on_while(node)
+ check(node)
+ end
+
+ def on_until(node)
+ check(node)
+ end
+
+ private
+
+ def check(node)
+ condition, = *node
+
+ # assignments inside blocks are not what we're looking for
+ return if condition.type == :block
+
+ on_node([:begin, *EQUALS_ASGN_NODES], condition) do |asgn_node|
+ # skip safe assignment nodes if safe assignment is allowed
+ return if safe_assignment_allowed? && safe_assignment?(asgn_node)
+
+ # assignment nodes from shorthand ops like ||= don't have operator
+ if asgn_node.type != :begin && asgn_node.loc.operator
+ add_offense(asgn_node, :operator)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/block_alignment.rb b/lib/rubocop/cop/lint/block_alignment.rb
new file mode 100644
index 0000000..ad4b5af
--- /dev/null
+++ b/lib/rubocop/cop/lint/block_alignment.rb
@@ -0,0 +1,151 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks whether the end keywords are aligned properly for do
+ # end blocks.
+ #
+ # @example
+ #
+ # variable = lambda do |i|
+ # i
+ # end
+ class BlockAlignment < Cop
+ include CheckAssignment
+
+ MSG = 'end at %d, %d is not aligned with %s at %d, %d%s'
+
+ def initialize(config = nil, options = nil)
+ super
+ @inspected_blocks = []
+ end
+
+ def on_block(node)
+ return if already_processed_node?(node)
+ check_block_alignment(node, node)
+ end
+
+ def on_and(node)
+ return if already_processed_node?(node)
+
+ _left, right = *node
+ if right.type == :block
+ check_block_alignment(node, right)
+ @inspected_blocks << right
+ end
+ end
+
+ alias_method :on_or, :on_and
+
+ def on_op_asgn(node)
+ variable, _op, args = *node
+ check_assignment(variable, args)
+ end
+
+ def on_send(node)
+ _receiver, _method, *args = *node
+ check_assignment(node, args.last)
+ end
+
+ def on_masgn(node)
+ variables, args = *node
+ check_assignment(variables, args)
+ end
+
+ private
+
+ def check_assignment(begin_node, other_node)
+ return unless other_node
+
+ block_node = find_block_node(other_node)
+ return unless block_node.type == :block
+
+ # If the block is an argument in a function call, align end with
+ # the block itself, and not with the function.
+ if begin_node.type == :send
+ _receiver, method, *_args = *begin_node
+ begin_node = block_node if method.to_s =~ /^\w+$/
+ end
+
+ # Align with the expression that is on the same line
+ # where the block is defined
+ if begin_node.type != :mlhs && block_is_on_next_line?(begin_node,
+ block_node)
+ return
+ end
+ return if already_processed_node?(block_node)
+
+ @inspected_blocks << block_node
+ check_block_alignment(begin_node, block_node)
+ end
+
+ def find_block_node(node)
+ while [:send, :lvasgn].include?(node.type)
+ n = case node.type
+ when :send
+ find_block_or_send_node(node) || break
+ when :lvasgn
+ _variable, value = *node
+ value
+ end
+ node = n if n
+ end
+ node
+ end
+
+ def find_block_or_send_node(send_node)
+ receiver, _method, args = *send_node
+ [receiver, args].find do |subnode|
+ subnode && [:block, :send].include?(subnode.type)
+ end
+ end
+
+ def check_block_alignment(start_node, block_node)
+ start_loc = start_node.loc.expression
+ end_loc = block_node.loc.end
+ do_loc = block_node.loc.begin # Actually it's either do or {.
+ return if do_loc.line == end_loc.line # One-liner, not interesting.
+ if start_loc.column != end_loc.column
+ # We've found that "end" is not aligned with the start node (which
+ # can be a block, a variable assignment, etc). But we also allow
+ # the "end" to be aligned with the start of the line where the "do"
+ # is, which is a style some people use in multi-line chains of
+ # blocks.
+ match = /\S.*/.match(do_loc.source_line)
+ indentation_of_do_line = match.begin(0)
+ if end_loc.column != indentation_of_do_line
+ add_offense(nil,
+ end_loc,
+ format(MSG, end_loc.line, end_loc.column,
+ start_loc.source.lines.to_a.first.chomp,
+ start_loc.line, start_loc.column,
+ alt_start_msg(match, start_loc, do_loc,
+ indentation_of_do_line)))
+ end
+ end
+ end
+
+ def alt_start_msg(match, start_loc, do_loc, indentation_of_do_line)
+ if start_loc.line == do_loc.line &&
+ start_loc.column == indentation_of_do_line
+ ''
+ else
+ " or #{match[0]} at #{do_loc.line}, #{indentation_of_do_line}"
+ end
+ end
+
+ def message
+ end
+
+ def already_processed_node?(node)
+ @inspected_blocks.include?(node)
+ end
+
+ def block_is_on_next_line?(begin_node, block_node)
+ begin_node.loc.line != block_node.loc.line
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/condition_position.rb b/lib/rubocop/cop/lint/condition_position.rb
new file mode 100644
index 0000000..05e7b91
--- /dev/null
+++ b/lib/rubocop/cop/lint/condition_position.rb
@@ -0,0 +1,52 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for conditions that are not on the same line as
+ # if/while/until.
+ #
+ # @example
+ #
+ # if
+ # some_condition
+ # do_something
+ # end
+ class ConditionPosition < Cop
+ def on_if(node)
+ return if node.loc.respond_to?(:question)
+
+ check(node)
+ end
+
+ def on_while(node)
+ check(node)
+ end
+
+ def on_until(node)
+ check(node)
+ end
+
+ private
+
+ def check(node)
+ condition, = *node
+
+ if on_different_line?(node.loc.keyword.line,
+ condition.loc.expression.line)
+ add_offense(condition, :expression,
+ message(node.loc.keyword.source))
+ end
+ end
+
+ def message(keyword)
+ "Place the condition on the same line as `#{keyword}`."
+ end
+
+ def on_different_line?(keyword_line, cond_line)
+ keyword_line != cond_line
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/debugger.rb b/lib/rubocop/cop/lint/debugger.rb
new file mode 100644
index 0000000..0eacb1b
--- /dev/null
+++ b/lib/rubocop/cop/lint/debugger.rb
@@ -0,0 +1,35 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for calls to debugger or pry.
+ class Debugger < Cop
+ MSG = 'Remove calls to `debugger`.'
+
+ # debugger call node
+ #
+ # (send nil :debugger)
+ DEBUGGER_NODE = s(:send, nil, :debugger)
+
+ # binding.pry node
+ #
+ # (send
+ # (send nil :binding) :pry)
+ PRY_NODE = s(:send, s(:send, nil, :binding), :pry)
+
+ # binding.remote_pry node
+ #
+ # (send
+ # (send nil :binding) :remote_pry)
+ REMOTE_PRY_NODE = s(:send, s(:send, nil, :binding), :remote_pry)
+
+ DEBUGGER_NODES = [DEBUGGER_NODE, PRY_NODE, REMOTE_PRY_NODE]
+
+ def on_send(node)
+ add_offense(node, :selector) if DEBUGGER_NODES.include?(node)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/deprecated_class_methods.rb b/lib/rubocop/cop/lint/deprecated_class_methods.rb
new file mode 100644
index 0000000..2127452
--- /dev/null
+++ b/lib/rubocop/cop/lint/deprecated_class_methods.rb
@@ -0,0 +1,62 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for uses of the deprecated class method usages.
+ class DeprecatedClassMethods < Cop
+ include AST::Sexp
+
+ MSG = '`%s` is deprecated in favor of `%s`.'
+
+ DEPRECATED_METHODS = [
+ [:File, :exists?, :exist?],
+ [:Dir, :exists?, :exist?]
+ ]
+
+ def on_send(node)
+ receiver, method_name, *_args = *node
+
+ DEPRECATED_METHODS.each do |data|
+ next unless class_nodes(data).include?(receiver)
+ next unless method_name == data[1]
+
+ add_offense(node, :selector,
+ format(MSG,
+ deprecated_method(data),
+ replacement_method(data)))
+ end
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ receiver, method_name, *_args = *node
+
+ DEPRECATED_METHODS.each do |data|
+ next unless class_nodes(data).include?(receiver)
+ next unless method_name == data[1]
+
+ corrector.replace(node.loc.selector,
+ data[2].to_s)
+ end
+ end
+ end
+
+ private
+
+ def class_nodes(data)
+ [s(:const, nil, data[0]),
+ s(:const, s(:cbase), data[0])]
+ end
+
+ def deprecated_method(data)
+ format('%s.%s', data[0], data[1])
+ end
+
+ def replacement_method(data)
+ format('%s.%s', data[0], data[2])
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/else_layout.rb b/lib/rubocop/cop/lint/else_layout.rb
new file mode 100644
index 0000000..8deaf0c
--- /dev/null
+++ b/lib/rubocop/cop/lint/else_layout.rb
@@ -0,0 +1,57 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for odd else block layout - like
+ # having an expression on the same line as the else keyword,
+ # which is usually a mistake.
+ #
+ # @example
+ #
+ # if something
+ # ...
+ # else do_this
+ # do_that
+ # end
+ class ElseLayout < Cop
+ def on_if(node)
+ # ignore ternary ops
+ return if node.loc.respond_to?(:question)
+ # ignore modifier ops & elsif nodes
+ return unless node.loc.end
+
+ check(node)
+ end
+
+ private
+
+ def check(node)
+ return unless node
+
+ if node.loc.respond_to?(:else) &&
+ node.loc.else &&
+ node.loc.else.is?('else')
+ _cond, _if_branch, else_branch = *node
+
+ return unless else_branch && else_branch.type == :begin
+
+ first_else_expr = else_branch.children.first
+
+ if first_else_expr.loc.expression.line == node.loc.else.line
+ add_offense(first_else_expr, :expression, message)
+ end
+ elsif node.loc.respond_to?(:keyword) &&
+ %w(if elsif).include?(node.loc.keyword.source)
+ _cond, _if_branch, else_branch = *node
+ check(else_branch)
+ end
+ end
+
+ def message
+ 'Odd `else` layout detected. Did you mean to use `elsif`?'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/empty_ensure.rb b/lib/rubocop/cop/lint/empty_ensure.rb
new file mode 100644
index 0000000..b64276a
--- /dev/null
+++ b/lib/rubocop/cop/lint/empty_ensure.rb
@@ -0,0 +1,18 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for empty `ensure` blocks
+ class EmptyEnsure < Cop
+ MSG = 'Empty `ensure` block detected.'
+
+ def on_ensure(node)
+ _body, ensure_body = *node
+
+ add_offense(node, :keyword) unless ensure_body
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/empty_interpolation.rb b/lib/rubocop/cop/lint/empty_interpolation.rb
new file mode 100644
index 0000000..81cf4bb
--- /dev/null
+++ b/lib/rubocop/cop/lint/empty_interpolation.rb
@@ -0,0 +1,22 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for empty interpolation.
+ #
+ # @example
+ #
+ # "result is #{}"
+ class EmptyInterpolation < Cop
+ MSG = 'Empty interpolation detected.'
+
+ def on_dstr(node)
+ node.children.select { |n| n.type == :begin }.each do |begin_node|
+ add_offense(begin_node, :expression) if begin_node.children.empty?
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/end_alignment.rb b/lib/rubocop/cop/lint/end_alignment.rb
new file mode 100644
index 0000000..bf013f3
--- /dev/null
+++ b/lib/rubocop/cop/lint/end_alignment.rb
@@ -0,0 +1,129 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks whether the end keywords are aligned properly.
+ #
+ # Two modes are supported through the AlignWith configuration
+ # parameter. If it's set to `keyword` (which is the default), the `end`
+ # shall be aligned with the start of the keyword (if, def, etc.). If it's
+ # set to `variable` the `end` shall be aligned with the left-hand-side of
+ # the variable assignment, if there is one.
+ #
+ # @example
+ #
+ # variable = if true
+ # end
+ class EndAlignment < Cop
+ include CheckMethods
+ include CheckAssignment
+ include ConfigurableEnforcedStyle
+
+ MSG = 'end at %d, %d is not aligned with %s at %d, %d'
+
+ def on_class(node)
+ check(node)
+ end
+
+ def on_module(node)
+ check(node)
+ end
+
+ def on_if(node)
+ check(node) if node.loc.respond_to?(:end)
+ end
+
+ def on_while(node)
+ check(node)
+ end
+
+ def on_until(node)
+ check(node)
+ end
+
+ def on_send(node)
+ receiver, method_name, *args = *node
+ if visibility_and_def_on_same_line?(receiver, method_name, args)
+ expr = node.loc.expression
+ method_def = args.first
+ range = Parser::Source::Range.new(expr.source_buffer,
+ expr.begin_pos,
+ method_def.loc.keyword.end_pos)
+ check_offset(method_def, range.source,
+ method_def.loc.keyword.begin_pos - expr.begin_pos)
+ ignore_node(method_def) # Don't check the same `end` again.
+ end
+ end
+
+ private
+
+ # Returns true for constructs such as
+ # private def my_method
+ # which are allowed in Ruby 2.1 and later.
+ def visibility_and_def_on_same_line?(receiver, method_name, args)
+ !receiver &&
+ [:public, :protected, :private,
+ :module_function].include?(method_name) &&
+ args.size == 1 && [:def, :defs].include?(args.first.type)
+ end
+
+ def check_assignment(node, rhs)
+ # If there are method calls chained to the right hand side of the
+ # assignment, we let rhs be the receiver of those method calls before
+ # we check if it's an if/unless/while/until.
+ rhs = first_part_of_call_chain(rhs)
+
+ return unless rhs
+
+ case rhs.type
+ when :if, :while, :until
+ return if rhs.loc.respond_to?(:question) # ternary
+
+ if style == :variable
+ expr = node.loc.expression
+ range = Parser::Source::Range.new(expr.source_buffer,
+ expr.begin_pos,
+ rhs.loc.keyword.end_pos)
+ offset = rhs.loc.keyword.column - node.loc.expression.column
+ else
+ range = rhs.loc.keyword
+ offset = 0
+ end
+
+ check_offset(rhs, range.source, offset)
+ ignore_node(rhs) # Don't check again.
+ end
+ end
+
+ def check(node, *_)
+ check_offset(node, node.loc.keyword.source, 0)
+ end
+
+ def check_offset(node, alignment_base, offset)
+ return if ignored_node?(node)
+
+ end_loc = node.loc.end
+ return unless end_loc # Discard modifier forms of if/while/until.
+
+ kw_loc = node.loc.keyword
+
+ if kw_loc.line != end_loc.line &&
+ kw_loc.column != end_loc.column + offset
+ add_offense(nil, end_loc,
+ format(MSG, end_loc.line, end_loc.column,
+ alignment_base, kw_loc.line, kw_loc.column)) do
+ opposite_style_detected
+ end
+ else
+ correct_style_detected
+ end
+ end
+
+ def parameter_name
+ 'AlignWith'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/end_in_method.rb b/lib/rubocop/cop/lint/end_in_method.rb
new file mode 100644
index 0000000..de03a17
--- /dev/null
+++ b/lib/rubocop/cop/lint/end_in_method.rb
@@ -0,0 +1,22 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for END blocks in method definitions.
+ class EndInMethod < Cop
+ include CheckMethods
+
+ MSG = '`END` found in method definition. Use `at_exit` instead.'
+
+ private
+
+ def check(node, *_)
+ on_node(:postexe, node) do |end_node|
+ add_offense(end_node, :keyword)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/ensure_return.rb b/lib/rubocop/cop/lint/ensure_return.rb
new file mode 100644
index 0000000..8e79a0f
--- /dev/null
+++ b/lib/rubocop/cop/lint/ensure_return.rb
@@ -0,0 +1,22 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for *return* from an *ensure* block.
+ class EnsureReturn < Cop
+ MSG = 'Never return from an `ensure` block.'
+
+ def on_ensure(node)
+ _body, ensure_body = *node
+
+ return unless ensure_body
+
+ on_node(:return, ensure_body) do |e|
+ add_offense(e, :expression)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/eval.rb b/lib/rubocop/cop/lint/eval.rb
new file mode 100644
index 0000000..8d0efad
--- /dev/null
+++ b/lib/rubocop/cop/lint/eval.rb
@@ -0,0 +1,18 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for the use of *Kernel#eval*.
+ class Eval < Cop
+ MSG = 'The use of `eval` is a serious security risk.'
+
+ def on_send(node)
+ receiver, method_name, = *node
+
+ add_offense(node, :selector) if receiver.nil? && method_name == :eval
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/handle_exceptions.rb b/lib/rubocop/cop/lint/handle_exceptions.rb
new file mode 100644
index 0000000..f603ecc
--- /dev/null
+++ b/lib/rubocop/cop/lint/handle_exceptions.rb
@@ -0,0 +1,18 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for *rescue* blocks with no body.
+ class HandleExceptions < Cop
+ MSG = 'Do not suppress exceptions.'
+
+ def on_resbody(node)
+ _exc_list_node, _exc_var_node, body_node = *node
+
+ add_offense(node, :expression) unless body_node
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/invalid_character_literal.rb b/lib/rubocop/cop/lint/invalid_character_literal.rb
new file mode 100644
index 0000000..434ee8c
--- /dev/null
+++ b/lib/rubocop/cop/lint/invalid_character_literal.rb
@@ -0,0 +1,37 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for invalid character literals with a non-escaped
+ # whitespace character (e.g. `? `).
+ # However, currently it's unclear whether there's a way to emit this
+ # warning without syntax errors.
+ #
+ # $ ruby -w
+ # p(? )
+ # -:1: warning: invalid character syntax; use ?\s
+ # -:1: syntax error, unexpected '?', expecting ')'
+ # p(? )
+ # ^
+ #
+ # @example
+ # p(? )
+ class InvalidCharacterLiteral < Cop
+ include ParserDiagnostic
+
+ private
+
+ def relevant_diagnostic?(diagnostic)
+ diagnostic.reason == :invalid_escape_use
+ end
+
+ def alternative_message(diagnostic)
+ diagnostic.message
+ .capitalize
+ .gsub('character syntax', 'character literal')
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/literal_in_condition.rb b/lib/rubocop/cop/lint/literal_in_condition.rb
new file mode 100644
index 0000000..e1ffc50
--- /dev/null
+++ b/lib/rubocop/cop/lint/literal_in_condition.rb
@@ -0,0 +1,138 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for literals used as the conditions or as
+ # operands in and/or expressions serving as the conditions of
+ # if/while/until.
+ #
+ # @example
+ #
+ # if 20
+ # do_something
+ # end
+ #
+ # if some_var && true
+ # do_something
+ # end
+ #
+ class LiteralInCondition < Cop
+ MSG = 'Literal `%s` appeared in a condition.'
+
+ LITERALS = [:str, :dstr, :int, :float, :array,
+ :hash, :regexp, :nil, :true, :false]
+
+ BASIC_LITERALS = LITERALS - [:dstr, :array, :hash]
+
+ def on_if(node)
+ check_for_literal(node)
+ end
+
+ def on_while(node)
+ check_for_literal(node)
+ end
+
+ def on_while_post(node)
+ check_for_literal(node)
+ end
+
+ def on_until(node)
+ check_for_literal(node)
+ end
+
+ def on_until_post(node)
+ check_for_literal(node)
+ end
+
+ def on_case(node)
+ cond, *whens, _else = *node
+
+ if cond
+ check_case_cond(cond)
+ else
+ whens.each do |when_node|
+ check_for_literal(when_node)
+ end
+ end
+ end
+
+ def message(node)
+ format(MSG, node.loc.expression.source)
+ end
+
+ private
+
+ def check_for_literal(node)
+ cond, = *node
+
+ # if the cond node is literal we obviously have a problem
+ if literal?(cond)
+ add_offense(cond, :expression)
+ else
+ # alternatively we have to consider a logical node with a
+ # literal argument
+ check_node(cond)
+ end
+ end
+
+ def not?(node)
+ return false unless node && node.type == :send
+
+ _receiver, method_name, *_args = *node
+
+ method_name == :!
+ end
+
+ def literal?(node)
+ LITERALS.include?(node.type)
+ end
+
+ def basic_literal?(node)
+ if node && node.type == :array
+ primitive_array?(node)
+ else
+ BASIC_LITERALS.include?(node.type)
+ end
+ end
+
+ def primitive_array?(node)
+ node.children.all? { |n| basic_literal?(n) }
+ end
+
+ def check_node(node)
+ return unless node
+
+ if not?(node)
+ receiver, = *node
+
+ handle_node(receiver)
+ elsif [:and, :or].include?(node.type)
+ *operands = *node
+ operands.each do |op|
+ handle_node(op)
+ end
+ elsif node.type == :begin && node.children.size == 1
+ child_node = node.children.first
+ handle_node(child_node)
+ end
+ end
+
+ def handle_node(node)
+ if literal?(node)
+ add_offense(node, :expression)
+ elsif [:send, :and, :or, :begin].include?(node.type)
+ check_node(node)
+ end
+ end
+
+ def check_case_cond(node)
+ return if node.type == :array && !primitive_array?(node)
+ return if node.type == :dstr
+
+ handle_node(node)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/literal_in_interpolation.rb b/lib/rubocop/cop/lint/literal_in_interpolation.rb
new file mode 100644
index 0000000..8152981
--- /dev/null
+++ b/lib/rubocop/cop/lint/literal_in_interpolation.rb
@@ -0,0 +1,37 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for interpolated literals.
+ #
+ # @example
+ #
+ # "result is #{10}"
+ class LiteralInInterpolation < Cop
+ LITERALS = [:str, :dstr, :int, :float, :array,
+ :hash, :regexp, :nil, :true, :false]
+
+ MSG = 'Literal interpolation detected.'
+
+ def on_dstr(node)
+ node.children.select { |n| n.type == :begin }.each do |begin_node|
+ final_node = begin_node.children.last
+ next unless final_node
+ # handle strings like __FILE__
+ return if special_string?(final_node)
+ next unless LITERALS.include?(final_node.type)
+
+ add_offense(final_node, :expression)
+ end
+ end
+
+ private
+
+ def special_string?(node)
+ node.type == :str && !node.loc.respond_to?(:begin)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/loop.rb b/lib/rubocop/cop/lint/loop.rb
new file mode 100644
index 0000000..8ed8397
--- /dev/null
+++ b/lib/rubocop/cop/lint/loop.rb
@@ -0,0 +1,27 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for uses of *begin...end while/until something*.
+ class Loop < Cop
+ MSG = 'Use `Kernel#loop` with `break` rather than ' \
+ '`begin/end/until`(or `while`).'
+
+ def on_while_post(node)
+ register_offense(node)
+ end
+
+ def on_until_post(node)
+ register_offense(node)
+ end
+
+ private
+
+ def register_offense(node)
+ add_offense(node, :keyword)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb b/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb
new file mode 100644
index 0000000..91cba61
--- /dev/null
+++ b/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb
@@ -0,0 +1,52 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # Checks for space between a the name of a called method and a left
+ # parenthesis.
+ #
+ # @example
+ #
+ # puts (x + y)
+ class ParenthesesAsGroupedExpression < Cop
+ MSG = '(...) interpreted as grouped expression.'
+
+ def on_send(node)
+ _receiver, method_name, args = *node
+ return if operator?(method_name) || method_name.to_s.end_with?('=')
+
+ if args && args.loc.expression.source.start_with?('(')
+ space_length = spaces_before_left_parenthesis(node)
+ if space_length > 0
+ expr = args.loc.expression
+ space_range =
+ Parser::Source::Range.new(expr.source_buffer,
+ expr.begin_pos - space_length,
+ expr.begin_pos)
+ add_offense(nil, space_range)
+ end
+ end
+ end
+
+ private
+
+ def spaces_before_left_parenthesis(node)
+ receiver, method_name, _args = *node
+ receiver_length = if receiver
+ receiver.loc.expression.source.length
+ else
+ 0
+ end
+ without_receiver = node.loc.expression.source[receiver_length..-1]
+
+ # Escape question mark if any.
+ method_regexp = Regexp.escape(method_name)
+
+ match = without_receiver.match(/^\s*\.?\s*#{method_regexp}(\s+)\(/)
+ match ? match.captures[0].length : 0
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/require_parentheses.rb b/lib/rubocop/cop/lint/require_parentheses.rb
new file mode 100644
index 0000000..cface14
--- /dev/null
+++ b/lib/rubocop/cop/lint/require_parentheses.rb
@@ -0,0 +1,68 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for expressions where there is a call to a predicate
+ # method with at least one argument, where no parentheses are used around
+ # the parameter list, and a boolean operator, && or ||, is used in the
+ # last argument.
+ #
+ # The idea behind warning for these constructs is that the user might
+ # be under the impression that the return value from the method call is
+ # an operand of &&/||.
+ #
+ # @example
+ #
+ # if day.is? :tuesday && month == :jan
+ # ...
+ # end
+ class RequireParentheses < Cop
+ include IfNode
+
+ MSG = 'Use parentheses in the method call to avoid confusion about ' \
+ 'precedence.'
+
+ def on_send(node)
+ _receiver, method_name, *args = *node
+
+ return if parentheses?(node)
+ return if args.empty?
+
+ if ternary_op?(args.first)
+ check_ternary(args.first, node)
+ else
+ # We're only checking predicate methods. There would be false
+ # positives otherwise.
+ check_send(args.last, node) if predicate?(method_name)
+ end
+ end
+
+ private
+
+ def check_ternary(arg, node)
+ condition, _, _ = *arg
+ if offense?(condition)
+ expr = node.loc.expression
+ range = Parser::Source::Range.new(expr.source_buffer,
+ expr.begin_pos,
+ condition.loc.expression.end_pos)
+ add_offense(range, range)
+ end
+ end
+
+ def check_send(arg, node)
+ add_offense(node, :expression) if offense?(arg)
+ end
+
+ def predicate?(method_name)
+ method_name =~ /\w\?$/
+ end
+
+ def offense?(node)
+ [:and, :or].include?(node.type)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/rescue_exception.rb b/lib/rubocop/cop/lint/rescue_exception.rb
new file mode 100644
index 0000000..a66121a
--- /dev/null
+++ b/lib/rubocop/cop/lint/rescue_exception.rb
@@ -0,0 +1,24 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for *rescue* blocks targeting the Exception class.
+ class RescueException < Cop
+ MSG = 'Avoid rescuing the `Exception` class.'
+
+ def on_resbody(node)
+ return unless node.children.first
+ rescue_args = node.children.first.children
+ if rescue_args.any? { |a| targets_exception?(a) }
+ add_offense(node, :expression)
+ end
+ end
+
+ def targets_exception?(rescue_arg_node)
+ Util.const_name(rescue_arg_node) == 'Exception'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb b/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb
new file mode 100644
index 0000000..cd06f5a
--- /dev/null
+++ b/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb
@@ -0,0 +1,31 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop looks for use of the same name as outer local variables
+ # for block arguments or block local variables.
+ # This is a mimic of the warning
+ # "shadowing outer local variable - foo" from `ruby -cw`.
+ class ShadowingOuterLocalVariable < Cop
+ include VariableInspector
+
+ MSG = 'Shadowing outer local variable - `%s`'
+
+ def investigate(processed_source)
+ inspect_variables(processed_source.ast)
+ end
+
+ def before_declaring_variable(variable)
+ return if variable.name.to_s.start_with?('_')
+
+ outer_local_variable = variable_table.find_variable(variable.name)
+ return unless outer_local_variable
+
+ message = format(MSG, variable.name)
+ add_offense(variable.declaration_node, :expression, message)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/space_before_first_arg.rb b/lib/rubocop/cop/lint/space_before_first_arg.rb
new file mode 100644
index 0000000..8729d1d
--- /dev/null
+++ b/lib/rubocop/cop/lint/space_before_first_arg.rb
@@ -0,0 +1,37 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # Checks for space between a method name and the first argument for
+ # method calls without parentheses.
+ #
+ # @example
+ #
+ # something?x
+ # something!x
+ #
+ class SpaceBeforeFirstArg < Cop
+ MSG = 'Put space between the method name and the first argument.'
+
+ def on_send(node)
+ return if parentheses?(node)
+
+ _receiver, method_name, *args = *node
+ return if args.empty?
+ return if operator?(method_name)
+ return if method_name.to_s.end_with?('=')
+
+ # Setter calls with parentheses are parsed this way. The parentheses
+ # belong to the argument, not the send node.
+ return if args.first.type == :begin
+
+ arg1 = args.first.loc.expression
+ arg1_with_space = range_with_surrounding_space(arg1, :left)
+
+ add_offense(nil, arg1) if arg1_with_space.source =~ /\A\S/
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/string_conversion_in_interpolation.rb b/lib/rubocop/cop/lint/string_conversion_in_interpolation.rb
new file mode 100644
index 0000000..bd63b27
--- /dev/null
+++ b/lib/rubocop/cop/lint/string_conversion_in_interpolation.rb
@@ -0,0 +1,51 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for string conversion in string interpolation,
+ # which is redundant.
+ #
+ # @example
+ #
+ # "result is #{something.to_s}"
+ class StringConversionInInterpolation < Cop
+ MSG_DEFAULT = 'Redundant use of `Object#to_s` in interpolation.'
+ MSG_SELF = 'Use `self` instead of `Object#to_s` in interpolation.'
+
+ def on_dstr(node)
+ node.children.select { |n| n.type == :begin }.each do |begin_node|
+ final_node = begin_node.children.last
+ next unless final_node && final_node.type == :send
+
+ receiver, method_name, *args = *final_node
+
+ if method_name == :to_s && args.empty?
+ add_offense(
+ final_node,
+ :selector,
+ receiver ? MSG_DEFAULT : MSG_SELF
+ )
+ end
+ end
+ end
+
+ private
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ receiver, _method_name, *_args = *node
+ corrector.replace(
+ node.loc.expression,
+ if receiver
+ receiver.loc.expression.source
+ else
+ 'self'
+ end
+ )
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/syntax.rb b/lib/rubocop/cop/lint/syntax.rb
new file mode 100644
index 0000000..a090230
--- /dev/null
+++ b/lib/rubocop/cop/lint/syntax.rb
@@ -0,0 +1,28 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This is actually not a cop and inspects nothing. It just provides
+ # methods to repack Parser's diagnostics into RuboCop's offenses.
+ module Syntax
+ COP_NAME = 'Syntax'.freeze
+
+ def self.offenses_from_diagnostics(diagnostics)
+ diagnostics.map do |diagnostic|
+ offense_from_diagnostic(diagnostic)
+ end
+ end
+
+ def self.offense_from_diagnostic(diagnostic)
+ Offense.new(
+ diagnostic.level,
+ diagnostic.location,
+ diagnostic.message,
+ COP_NAME
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/unreachable_code.rb b/lib/rubocop/cop/lint/unreachable_code.rb
new file mode 100644
index 0000000..cae2bc2
--- /dev/null
+++ b/lib/rubocop/cop/lint/unreachable_code.rb
@@ -0,0 +1,33 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for unreachable code.
+ # The check are based on the presence of flow of control
+ # statement in non-final position in *begin*(implicit) blocks.
+ class UnreachableCode < Cop
+ MSG = 'Unreachable code detected.'
+
+ NODE_TYPES = [:return, :next, :break, :retry, :redo]
+ FLOW_COMMANDS = [:throw, :raise, :fail]
+
+ def on_begin(node)
+ expressions = *node
+
+ expressions.each_cons(2) do |e1, e2|
+ if NODE_TYPES.include?(e1.type) || flow_command?(e1)
+ add_offense(e2, :expression)
+ end
+ end
+ end
+
+ private
+
+ def flow_command?(node)
+ FLOW_COMMANDS.any? { |c| command?(c, node) }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/useless_access_modifier.rb b/lib/rubocop/cop/lint/useless_access_modifier.rb
new file mode 100644
index 0000000..843df7b
--- /dev/null
+++ b/lib/rubocop/cop/lint/useless_access_modifier.rb
@@ -0,0 +1,58 @@
+# encoding: utf-8
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for access modifiers without any code.
+ #
+ # @example
+ # class Foo
+ # private # This is useless
+ #
+ # def self.some_method
+ # end
+ # end
+ class UselessAccessModifier < Cop
+ MSG = 'Useless `%s` access modifier.'
+
+ def on_class(node)
+ _name, _base_class, body = *node
+ return unless body
+
+ body_nodes = body.type == :begin ? body.children : [body]
+
+ body_nodes.each do |child_node|
+ check_for_access_modifier(child_node) ||
+ check_for_instance_method(child_node)
+ end
+
+ add_offense_for_access_modifier
+ end
+
+ private
+
+ def add_offense_for_access_modifier
+ return unless @access_modifier_node
+
+ _, modifier = *@access_modifier_node
+ message = format(MSG, modifier)
+ add_offense(@access_modifier_node, :expression, message)
+ end
+
+ def check_for_instance_method(node)
+ return unless node.type == :def ||
+ node.type == :send
+
+ @access_modifier_node = nil
+ end
+
+ def check_for_access_modifier(node)
+ return unless Style::AccessModifierIndentation
+ .modifier_node?(node)
+
+ add_offense_for_access_modifier
+ @access_modifier_node = node
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/useless_assignment.rb b/lib/rubocop/cop/lint/useless_assignment.rb
new file mode 100644
index 0000000..412bd53
--- /dev/null
+++ b/lib/rubocop/cop/lint/useless_assignment.rb
@@ -0,0 +1,88 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for every useless assignment to local variable in every
+ # scope.
+ # The basic idea for this cop was from the warning of `ruby -cw`:
+ #
+ # assigned but unused variable - foo
+ #
+ # Currently this cop has advanced logic that detects unreferenced
+ # reassignments and properly handles varied cases such as branch, loop,
+ # rescue, ensure, etc.
+ class UselessAssignment < Cop
+ include VariableInspector
+
+ MSG = 'Useless assignment to variable - %s'
+
+ def investigate(processed_source)
+ inspect_variables(processed_source.ast)
+ end
+
+ def after_leaving_scope(scope)
+ scope.variables.each_value do |variable|
+ check_for_unused_assignments(variable)
+ check_for_unused_block_local_variable(variable)
+ end
+ end
+
+ def check_for_unused_assignments(variable)
+ return if variable.name.to_s.start_with?('_')
+
+ variable.assignments.each do |assignment|
+ next if assignment.used?
+
+ message = message_for_useless_assignment(assignment)
+
+ location = if assignment.regexp_named_capture?
+ assignment.node.children.first.loc.expression
+ else
+ assignment.node.loc.name
+ end
+
+ add_offense(nil, location, message)
+ end
+ end
+
+ def message_for_useless_assignment(assignment)
+ variable = assignment.variable
+
+ message = format(MSG, variable.name)
+
+ if assignment.multiple_assignment?
+ message << ". Use _ or _#{variable.name} as a variable name " \
+ "to indicate that it won't be used."
+ elsif assignment.operator_assignment?
+ return_value_node = return_value_node_of_scope(variable.scope)
+ if assignment.meta_assignment_node.equal?(return_value_node)
+ non_assignment_operator = assignment.operator.sub(/=$/, '')
+ message << ". Use just operator #{non_assignment_operator}."
+ end
+ end
+
+ message
+ end
+
+ # TODO: More precise handling (rescue, ensure, nested begin, etc.)
+ def return_value_node_of_scope(scope)
+ body_node = scope.body_node
+
+ if body_node.type == :begin
+ body_node.children.last
+ else
+ body_node
+ end
+ end
+
+ def check_for_unused_block_local_variable(variable)
+ return unless variable.block_local_variable?
+ return unless variable.assignments.empty?
+ message = format(MSG, variable.name)
+ add_offense(variable.declaration_node, :expression, message)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/useless_comparison.rb b/lib/rubocop/cop/lint/useless_comparison.rb
new file mode 100644
index 0000000..f400b35
--- /dev/null
+++ b/lib/rubocop/cop/lint/useless_comparison.rb
@@ -0,0 +1,30 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for comparison of something with itself.
+ #
+ # @example
+ #
+ # x.top >= x.top
+ class UselessComparison < Cop
+ MSG = 'Comparison of something with itself detected.'
+
+ OPS = %w(== === != < > <= >= <=>)
+
+ def on_send(node)
+ # lambda.() does not have a selector
+ return unless node.loc.selector
+ op = node.loc.selector.source
+
+ if OPS.include?(op)
+ receiver, _method, args = *node
+
+ add_offense(node, :selector) if receiver == args
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/useless_else_without_rescue.rb b/lib/rubocop/cop/lint/useless_else_without_rescue.rb
new file mode 100644
index 0000000..54291c3
--- /dev/null
+++ b/lib/rubocop/cop/lint/useless_else_without_rescue.rb
@@ -0,0 +1,25 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for useless `else` in `begin..end` without `rescue`.
+ #
+ # @example
+ # begin
+ # do_something
+ # else
+ # handle_errors # This will never be run.
+ # end
+ class UselessElseWithoutRescue < Cop
+ include ParserDiagnostic
+
+ private
+
+ def relevant_diagnostic?(diagnostic)
+ diagnostic.reason == :useless_else
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/useless_setter_call.rb b/lib/rubocop/cop/lint/useless_setter_call.rb
new file mode 100644
index 0000000..6348a73
--- /dev/null
+++ b/lib/rubocop/cop/lint/useless_setter_call.rb
@@ -0,0 +1,150 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for setter call to local variable as the final
+ # expression of a function definition.
+ #
+ # @example
+ #
+ # def something
+ # x = Something.new
+ # x.attr = 5
+ # end
+ class UselessSetterCall < Cop
+ include CheckMethods
+
+ MSG = 'Useless setter call to local variable %s.'
+ ASSIGNMENT_TYPES = [:lvasgn, :ivasgn, :cvasgn, :gvasgn].freeze
+
+ private
+
+ def check(_node, _method_name, args, body)
+ return unless body
+
+ if body.type == :begin
+ expression = body.children
+ else
+ expression = body
+ end
+
+ last_expr = expression.is_a?(Array) ? expression.last : expression
+
+ return unless setter_call_to_local_variable?(last_expr)
+
+ tracker = MethodVariableTracker.new(args, body)
+ receiver, = *last_expr
+ var_name, = *receiver
+ return if tracker.contain_object_passed_as_argument?(var_name)
+
+ add_offense(receiver,
+ :name,
+ format(MSG, receiver.loc.name.source))
+ end
+
+ def setter_call_to_local_variable?(node)
+ return unless node && node.type == :send
+ receiver, method, _args = *node
+ return unless receiver && receiver.type == :lvar
+ method =~ /\w=$/
+ end
+
+ # This class tracks variable assignments in a method body
+ # and if a variable contains object passed as argument at the end of
+ # the method.
+ class MethodVariableTracker
+ def initialize(args_node, body_node)
+ @args_node = args_node
+ @body_node = body_node
+ end
+
+ def contain_object_passed_as_argument?(variable_name)
+ return @table[variable_name] if @table
+
+ @table = {}
+
+ @args_node.children.each do |arg_node|
+ arg_name, = *arg_node
+ @table[arg_name] = true
+ end
+
+ scan(@body_node) do |node|
+ case node.type
+ when :masgn
+ process_multiple_assignment(node)
+ when :or_asgn, :and_asgn
+ process_logical_operator_assignment(node)
+ when :op_asgn
+ process_binary_operator_assignment(node)
+ when *ASSIGNMENT_TYPES
+ _, rhs_node = *node
+ process_assignment(node, rhs_node)
+ end
+ end
+
+ @table[variable_name]
+ end
+
+ def scan(node, &block)
+ catch(:skip_children) do
+ yield node
+
+ node.children.each do |child|
+ next unless child.is_a?(Parser::AST::Node)
+ scan(child, &block)
+ end
+ end
+ end
+
+ def process_multiple_assignment(masgn_node)
+ mlhs_node, mrhs_node = *masgn_node
+
+ mlhs_node.children.each_with_index do |lhs_node, index|
+ next unless ASSIGNMENT_TYPES.include?(lhs_node.type)
+
+ lhs_variable_name, = *lhs_node
+ rhs_node = mrhs_node.children[index]
+
+ if mrhs_node.type == :array && rhs_node
+ process_assignment(lhs_variable_name, rhs_node)
+ else
+ @table[lhs_variable_name] = false
+ end
+ end
+
+ throw :skip_children
+ end
+
+ def process_logical_operator_assignment(asgn_node)
+ lhs_node, rhs_node = *asgn_node
+ return unless ASSIGNMENT_TYPES.include?(lhs_node.type)
+ process_assignment(lhs_node, rhs_node)
+
+ throw :skip_children
+ end
+
+ def process_binary_operator_assignment(op_asgn_node)
+ lhs_node, = *op_asgn_node
+ return unless ASSIGNMENT_TYPES.include?(lhs_node.type)
+ lhs_variable_name, = *lhs_node
+ @table[lhs_variable_name] = false
+
+ throw :skip_children
+ end
+
+ def process_assignment(asgn_node, rhs_node)
+ lhs_variable_name, = *asgn_node
+
+ if [:lvar, :ivar, :cvar, :gvar].include?(rhs_node.type)
+ rhs_variable_name, = *rhs_node
+ @table[lhs_variable_name] = @table[rhs_variable_name]
+ else
+ @table[lhs_variable_name] = false
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/lint/void.rb b/lib/rubocop/cop/lint/void.rb
new file mode 100644
index 0000000..755ef61
--- /dev/null
+++ b/lib/rubocop/cop/lint/void.rb
@@ -0,0 +1,54 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Lint
+ # This cop checks for operators, variables and literals used
+ # in void context.
+ class Void < Cop
+ OP_MSG = 'Operator `%s` used in void context.'
+ VAR_MSG = 'Variable `%s` used in void context.'
+ LIT_MSG = 'Literal `%s` used in void context'
+
+ OPS = %w(* / % + - == === != < > <= >= <=>)
+ VARS = [:ivar, :lvar, :cvar, :const]
+ LITERALS = [:str, :dstr, :int, :float, :array,
+ :hash, :regexp, :nil, :true, :false]
+
+ def on_begin(node)
+ expressions = *node
+
+ expressions.drop_last(1).each do |expr|
+ check_for_void_op(expr)
+ check_for_literal(expr)
+ check_for_var(expr)
+ end
+ end
+
+ private
+
+ def check_for_void_op(node)
+ return unless node.type == :send
+
+ op = node.loc.selector.source
+
+ add_offense(node, :selector, format(OP_MSG, op)) if OPS.include?(op)
+ end
+
+ def check_for_var(node)
+ if VARS.include?(node.type)
+ add_offense(node, :name,
+ format(VAR_MSG, node.loc.name.source))
+ end
+ end
+
+ def check_for_literal(node)
+ if LITERALS.include?(node.type)
+ add_offense(node, :expression,
+ format(LIT_MSG, node.loc.expression.source))
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/mixin/annotation_comment.rb b/lib/rubocop/cop/mixin/annotation_comment.rb
new file mode 100644
index 0000000..6d41f54
--- /dev/null
+++ b/lib/rubocop/cop/mixin/annotation_comment.rb
@@ -0,0 +1,37 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Common functionality related to annotation comments.
+ module AnnotationComment
+ private
+
+ def annotation?(comment)
+ _margin, first_word, colon, space, note = split_comment(comment)
+ keyword_appearance?(first_word, colon, space) &&
+ !just_first_word_of_sentence?(first_word, colon, space, note)
+ end
+
+ def split_comment(comment)
+ match = comment.text.match(/^(# ?)([A-Za-z]+)(\s*:)?(\s+)?(\S+)?/)
+ return false unless match
+ margin, first_word, colon, space, note = *match.captures
+ [margin, first_word, colon, space, note]
+ end
+
+ def keyword_appearance?(first_word, colon, space)
+ first_word && keyword?(first_word.upcase) && (colon || space)
+ end
+
+ def just_first_word_of_sentence?(first_word, colon, space, note)
+ first_word == first_word.capitalize && !colon && space && note
+ end
+
+ def keyword?(word)
+ config.for_cop('CommentAnnotation')['Keywords'].include?(word)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/mixin/array_syntax.rb b/lib/rubocop/cop/mixin/array_syntax.rb
new file mode 100644
index 0000000..e94f3a3
--- /dev/null
+++ b/lib/rubocop/cop/mixin/array_syntax.rb
@@ -0,0 +1,24 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # Common code for ordinary arrays with [] that can be written with %
+ # syntax.
+ module ArraySyntax
+ def array_of?(element_type, node)
+ return false unless square_brackets?(node)
+
+ array_elems = node.children
+
+ # no need to check empty arrays
+ return false unless array_elems && array_elems.size > 1
+
+ array_elems.all? { |e| e.type == element_type }
+ end
+
+ def square_brackets?(node)
+ node.loc.begin && node.loc.begin.is?('[')
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/mixin/autocorrect_alignment.rb b/lib/rubocop/cop/mixin/autocorrect_alignment.rb
new file mode 100644
index 0000000..aacd0ba
--- /dev/null
+++ b/lib/rubocop/cop/mixin/autocorrect_alignment.rb
@@ -0,0 +1,79 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # This module does auto-correction of nodes that should just be moved to
+ # the left or to the right, amount being determined by the instance
+ # variable @column_delta.
+ module AutocorrectAlignment
+ def check_alignment(items, base_column = nil)
+ base_column ||= items.first.loc.column unless items.empty?
+ items.each_cons(2) do |prev, current|
+ if current.loc.line > prev.loc.line && start_of_line?(current.loc)
+ @column_delta = base_column - current.loc.column
+ add_offense(current, :expression) if @column_delta != 0
+ end
+ end
+ end
+
+ def start_of_line?(loc)
+ loc.expression.source_line[0...loc.column] =~ /^\s*$/
+ end
+
+ def autocorrect(node)
+ # We can't use the instance variable inside the lambda. That would
+ # just give each lambda the same reference and they would all get
+ # the last value of @column_delta. A local variable fixes the
+ # problem.
+ column_delta = @column_delta
+
+ @corrections << lambda do |corrector|
+ expr = node.loc.expression
+ each_line(expr) do |line_begin_pos, line|
+ range = calculate_range(expr, line_begin_pos, column_delta)
+ if column_delta > 0
+ unless range.source == "\n"
+ corrector.insert_before(range, ' ' * column_delta)
+ end
+ else
+ remove(range, corrector) if range.source =~ /^[ \t]+$/
+ end
+ end
+ end
+ end
+
+ def calculate_range(expr, line_begin_pos, column_delta)
+ starts_with_space = expr.source_buffer.source[line_begin_pos] =~ / /
+ pos_to_remove = if column_delta > 0 || starts_with_space
+ line_begin_pos
+ else
+ line_begin_pos - column_delta.abs
+ end
+ Parser::Source::Range.new(expr.source_buffer, pos_to_remove,
+ pos_to_remove + column_delta.abs)
+ end
+
+ def remove(range, corrector)
+ original_stderr = $stderr
+ $stderr = StringIO.new # Avoid error messages on console
+ corrector.remove(range)
+ rescue RuntimeError
+ range = Parser::Source::Range.new(range.source_buffer,
+ range.begin_pos + 1,
+ range.end_pos + 1)
+ retry if range.source =~ /^ +$/
+ ensure
+ $stderr = original_stderr
+ end
+
+ def each_line(expr)
+ offset = 0
+ expr.source.each_line do |line|
+ line_begin_pos = expr.begin_pos + offset
+ yield line_begin_pos, line
+ offset += line.length
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/mixin/autocorrect_unless_changing_ast.rb b/lib/rubocop/cop/mixin/autocorrect_unless_changing_ast.rb
new file mode 100644
index 0000000..1e4ffb9
--- /dev/null
+++ b/lib/rubocop/cop/mixin/autocorrect_unless_changing_ast.rb
@@ -0,0 +1,24 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # This module does auto-correction of nodes that could become grammatically
+ # different after the correction. If the code change would alter the
+ # abstract syntax tree, it is not done.
+ module AutocorrectUnlessChangingAST
+ def autocorrect(node)
+ c = correction(node)
+ new_source = rewrite_node(node, c)
+
+ # Make the correction only if it doesn't change the AST.
+ @corrections << c if node == SourceParser.parse(new_source).ast
+ end
+
+ def rewrite_node(node, correction)
+ processed_source = SourceParser.parse(node.loc.expression.source)
+ c = correction(processed_source.ast)
+ Corrector.new(processed_source.buffer, [c]).rewrite
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/mixin/check_assignment.rb b/lib/rubocop/cop/mixin/check_assignment.rb
new file mode 100644
index 0000000..65b99f3
--- /dev/null
+++ b/lib/rubocop/cop/mixin/check_assignment.rb
@@ -0,0 +1,26 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # Common functionality for checking assignment nodes.
+ module CheckAssignment
+ TYPES = Util::ASGN_NODES - [:casgn, :op_asgn]
+ TYPES.each do |type|
+ define_method("on_#{type}") do |node|
+ _lhs, rhs = *node
+ check_assignment(node, rhs)
+ end
+ end
+
+ def on_casgn(node)
+ _scope, _lhs, rhs = *node
+ check_assignment(node, rhs)
+ end
+
+ def on_op_asgn(node)
+ _lhs, _op, rhs = *node
+ check_assignment(node, rhs)
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/mixin/check_methods.rb b/lib/rubocop/cop/mixin/check_methods.rb
new file mode 100644
index 0000000..273ccb4
--- /dev/null
+++ b/lib/rubocop/cop/mixin/check_methods.rb
@@ -0,0 +1,18 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # Common functionality for checking instance methods and singeton methods.
+ module CheckMethods
+ def on_def(node)
+ method_name, args, body = *node
+ check(node, method_name, args, body)
+ end
+
+ def on_defs(node)
+ _scope, method_name, args, body = *node
+ check(node, method_name, args, body)
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/mixin/code_length.rb b/lib/rubocop/cop/mixin/code_length.rb
new file mode 100644
index 0000000..66d7b40
--- /dev/null
+++ b/lib/rubocop/cop/mixin/code_length.rb
@@ -0,0 +1,33 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # Common functionality for checking length of code segments.
+ module CodeLength
+ include ConfigurableMax
+
+ def max_length
+ cop_config['Max']
+ end
+
+ def count_comments?
+ cop_config['CountComments']
+ end
+
+ def check(node, *_)
+ length = code_length(node)
+ if length > max_length
+ add_offense(node, :keyword, format(message, length,
+ max_length)) do
+ self.max = length
+ end
+ end
+ end
+
+ # Returns true for lines that shall not be included in the count.
+ def irrelevant_line(source_line)
+ source_line.blank? || !count_comments? && comment_line?(source_line)
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/mixin/configurable_enforced_style.rb b/lib/rubocop/cop/mixin/configurable_enforced_style.rb
new file mode 100644
index 0000000..ffe8372
--- /dev/null
+++ b/lib/rubocop/cop/mixin/configurable_enforced_style.rb
@@ -0,0 +1,53 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # Handles `EnforcedStyle` configuration parameters.
+ module ConfigurableEnforcedStyle
+ def opposite_style_detected
+ self.config_to_allow_offenses ||=
+ { parameter_name => alternative_style.to_s }
+ both_styles_detected if config_to_allow_offenses['Enabled']
+ end
+
+ def correct_style_detected
+ # Enabled:true indicates, later when the opposite style is detected,
+ # that the correct style is used somewhere.
+ self.config_to_allow_offenses ||= { 'Enabled' => true }
+ both_styles_detected if config_to_allow_offenses[parameter_name]
+ end
+
+ def both_styles_detected
+ # Both correct and opposite styles exist.
+ self.config_to_allow_offenses = { 'Enabled' => false }
+ end
+
+ def unrecognized_style_detected
+ # All we can do is to disable.
+ self.config_to_allow_offenses = { 'Enabled' => false }
+ end
+
+ def style
+ s = cop_config[parameter_name]
+ if cop_config['SupportedStyles'].include?(s)
+ s.to_sym
+ else
+ fail "Unknown style #{s} selected!"
+ end
+ end
+
+ def alternative_style
+ a = cop_config['SupportedStyles'].map(&:to_sym)
+ if a.size != 2
+ fail 'alternative_style can only be used when there are exactly ' \
+ '2 SupportedStyles'
+ end
+ style == a.first ? a.last : a.first
+ end
+
+ def parameter_name
+ 'EnforcedStyle'
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/mixin/configurable_max.rb b/lib/rubocop/cop/mixin/configurable_max.rb
new file mode 100644
index 0000000..91b9ead
--- /dev/null
+++ b/lib/rubocop/cop/mixin/configurable_max.rb
@@ -0,0 +1,19 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # Handles `Max` configuration parameters, especially setting them to an
+ # appropriate value with --auto-gen-config.
+ module ConfigurableMax
+ def max=(value)
+ cfg = self.config_to_allow_offenses ||= {}
+ value = [cfg[parameter_name], value].max if cfg[parameter_name]
+ cfg[parameter_name] = value
+ end
+
+ def parameter_name
+ 'Max'
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/mixin/configurable_naming.rb b/lib/rubocop/cop/mixin/configurable_naming.rb
new file mode 100644
index 0000000..e7cfe9a
--- /dev/null
+++ b/lib/rubocop/cop/mixin/configurable_naming.rb
@@ -0,0 +1,45 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # This module provides functionality for checking if names match the
+ # configured EnforcedStyle.
+ module ConfigurableNaming
+ include ConfigurableEnforcedStyle
+
+ SNAKE_CASE = /^@?[\da-z_]+[!?=]?$/
+ CAMEL_CASE = /^@?[a-z][\da-zA-Z]+[!?=]?$/
+
+ def check(node, range)
+ return unless range
+
+ name = range.source.to_sym
+ return if operator?(name)
+
+ if matches_config?(name)
+ correct_style_detected
+ else
+ add_offense(node, range, message(style)) do
+ opposite_style_detected
+ end
+ end
+ end
+
+ def matches_config?(name)
+ name =~ (style == :snake_case ? SNAKE_CASE : CAMEL_CASE)
+ end
+
+ # Returns a range containing the method name after the given regexp and
+ # a dot.
+ def after_dot(node, method_name_length, regexp)
+ expr = node.loc.expression
+ match = /\A#{regexp}\s*\.\s*/.match(expr.source)
+ return unless match
+ offset = match[0].length
+ begin_pos = expr.begin_pos + offset
+ Parser::Source::Range.new(expr.source_buffer, begin_pos,
+ begin_pos + method_name_length)
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/mixin/if_node.rb b/lib/rubocop/cop/mixin/if_node.rb
new file mode 100644
index 0000000..8dba070
--- /dev/null
+++ b/lib/rubocop/cop/mixin/if_node.rb
@@ -0,0 +1,25 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # Common functionality for checking if nodes.
+ module IfNode
+ def modifier_if?(node)
+ node.loc.end.nil?
+ end
+
+ def ternary_op?(node)
+ node.loc.respond_to?(:question)
+ end
+
+ def elsif?(node)
+ node.loc.respond_to?(:keyword) && node.loc.keyword &&
+ node.loc.keyword.is?('elsif')
+ end
+
+ def if_else?(node)
+ node.loc.respond_to?(:else) && node.loc.else
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/mixin/if_then_else.rb b/lib/rubocop/cop/mixin/if_then_else.rb
new file mode 100644
index 0000000..3b27c12
--- /dev/null
+++ b/lib/rubocop/cop/mixin/if_then_else.rb
@@ -0,0 +1,23 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # Common functionality for cops checking if and unless statements.
+ module IfThenElse
+ def on_if(node)
+ check(node)
+ end
+
+ def on_unless(node)
+ check(node)
+ end
+
+ def check(node)
+ # We won't check modifier or ternary conditionals.
+ return unless node.loc.expression.source =~ /\A(if|unless)\b/
+ return unless offending_line(node)
+ add_offense(node, :expression, error_message(node))
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/mixin/negative_conditional.rb b/lib/rubocop/cop/mixin/negative_conditional.rb
new file mode 100644
index 0000000..d382247
--- /dev/null
+++ b/lib/rubocop/cop/mixin/negative_conditional.rb
@@ -0,0 +1,24 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # Some common code shared between FavorUnlessOverNegatedIf and
+ # FavorUntilOverNegatedWhile.
+ module NegativeConditional
+ def check(node)
+ condition, _body, _rest = *node
+
+ # Look at last expression of contents if there's a parenthesis
+ # around condition.
+ condition = condition.children.last while condition.type == :begin
+
+ if condition.type == :send
+ _object, method = *condition
+ if method == :! && !(node.loc.respond_to?(:else) && node.loc.else)
+ add_offense(node, :expression)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/mixin/parser_diagnostic.rb b/lib/rubocop/cop/mixin/parser_diagnostic.rb
new file mode 100644
index 0000000..abc37a5
--- /dev/null
+++ b/lib/rubocop/cop/mixin/parser_diagnostic.rb
@@ -0,0 +1,34 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # Common functionality for cops which processes Parser's diagnostics.
+ # This mixin requires its user class to define `#relevant_diagnostic?`.
+ #
+ # def relevant_diagnostic?(diagnostic)
+ # diagnostic.reason == :my_interested_diagnostic_type
+ # end
+ #
+ # If you want to use an alternative offense message rather than the one in
+ # Parser's diagnostic, define `#alternative_message`.
+ #
+ # def alternative_message(diagnostic)
+ # 'My custom message'
+ # end
+ module ParserDiagnostic
+ def investigate(processed_source)
+ processed_source.diagnostics.each do |d|
+ next unless relevant_diagnostic?(d)
+
+ message = if respond_to?(:alternative_message, true)
+ alternative_message(d)
+ else
+ d.message.capitalize
+ end
+
+ add_offense(nil, d.location, message, d.level)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/mixin/safe_assignment.rb b/lib/rubocop/cop/mixin/safe_assignment.rb
new file mode 100644
index 0000000..7e06a97
--- /dev/null
+++ b/lib/rubocop/cop/mixin/safe_assignment.rb
@@ -0,0 +1,19 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # Common functionality for safe assignment. By safe assignment we mean
+ # putting parentheses around an assignment to indicate "I know I'm using an
+ # assignment as a condition. It's not a mistake."
+ module SafeAssignment
+ def safe_assignment?(node)
+ node.type == :begin && node.children.size == 1 &&
+ Util::EQUALS_ASGN_NODES.include?(node.children[0].type)
+ end
+
+ def safe_assignment_allowed?
+ cop_config['AllowSafeAssignment']
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/mixin/space_after_punctuation.rb b/lib/rubocop/cop/mixin/space_after_punctuation.rb
new file mode 100644
index 0000000..03b6041
--- /dev/null
+++ b/lib/rubocop/cop/mixin/space_after_punctuation.rb
@@ -0,0 +1,33 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # Common functionality for cops checking for missing space after
+ # punctuation.
+ module SpaceAfterPunctuation
+ MSG = 'Space missing after %s.'
+
+ def investigate(processed_source)
+ processed_source.tokens.each_cons(2) do |t1, t2|
+ if kind(t1) && t1.pos.line == t2.pos.line &&
+ t2.pos.column == t1.pos.column + offset(t1) &&
+ ![:tRPAREN, :tRBRACK].include?(t2.type)
+ add_offense(t1, t1.pos, format(MSG, kind(t1)))
+ end
+ end
+ end
+
+ # The normal offset, i.e., the distance from the punctuation
+ # token where a space should be, is 1.
+ def offset(token)
+ 1
+ end
+
+ def autocorrect(token)
+ @corrections << lambda do |corrector|
+ corrector.replace(token.pos, token.pos.source + ' ')
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/mixin/space_inside.rb b/lib/rubocop/cop/mixin/space_inside.rb
new file mode 100644
index 0000000..2bcef82
--- /dev/null
+++ b/lib/rubocop/cop/mixin/space_inside.rb
@@ -0,0 +1,35 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # Common functionality for checking for spaces inside various
+ # kinds of parentheses.
+ module SpaceInside
+ include SurroundingSpace
+ MSG = 'Space inside %s detected.'
+
+ def investigate(processed_source)
+ @processed_source = processed_source
+ left, right, kind = specifics
+ processed_source.tokens.each_cons(2) do |t1, t2|
+ if t1.type == left || t2.type == right
+ # If the second token is a comment, that means that a line break
+ # follows, and that the rules for space inside don't apply.
+ next if t2.type == :tCOMMENT
+
+ if t2.pos.line == t1.pos.line && space_between?(t1, t2)
+ range = Parser::Source::Range.new(processed_source.buffer,
+ t1.pos.end_pos,
+ t2.pos.begin_pos)
+ add_offense(range, range, format(MSG, kind))
+ end
+ end
+ end
+ end
+
+ def autocorrect(range)
+ @corrections << ->(corrector) { corrector.remove(range) }
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/mixin/statement_modifier.rb b/lib/rubocop/cop/mixin/statement_modifier.rb
new file mode 100644
index 0000000..f1bba3e
--- /dev/null
+++ b/lib/rubocop/cop/mixin/statement_modifier.rb
@@ -0,0 +1,60 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # Common functionality for modifier cops.
+ module StatementModifier
+ include IfNode
+
+ # TODO: Extremely ugly solution that needs lots of polish.
+ def check(sexp, comments)
+ case sexp.loc.keyword.source
+ when 'if' then cond, body, _else = *sexp
+ when 'unless' then cond, _else, body = *sexp
+ else cond, body = *sexp
+ end
+
+ return false if length(sexp) > 3
+
+ body_length = body_length(body)
+
+ return false if body_length == 0
+
+ on_node(:lvasgn, cond) do
+ return false
+ end
+
+ indentation = sexp.loc.keyword.column
+ kw_length = sexp.loc.keyword.size
+ cond_length = cond.loc.expression.size
+ space = 1
+ total = indentation + body_length + space + kw_length + space +
+ cond_length
+ total <= max_line_length && !body_has_comment?(body, comments)
+ end
+
+ def max_line_length
+ cop_config && cop_config['MaxLineLength'] ||
+ config.for_cop('LineLength')['Max']
+ end
+
+ def length(sexp)
+ sexp.loc.expression.source.lines.to_a.size
+ end
+
+ def body_length(body)
+ if body && body.loc.expression
+ body.loc.expression.size
+ else
+ 0
+ end
+ end
+
+ def body_has_comment?(body, comments)
+ comment_lines = comments.map(&:location).map(&:line)
+ body_line = body.loc.expression.line
+ comment_lines.include?(body_line)
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/mixin/string_help.rb b/lib/rubocop/cop/mixin/string_help.rb
new file mode 100644
index 0000000..75ae1ef
--- /dev/null
+++ b/lib/rubocop/cop/mixin/string_help.rb
@@ -0,0 +1,32 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # Classes that include this module just implement functions to determine
+ # what is an offense and how to do auto-correction. They get help with
+ # adding offenses for the faulty string nodes, and with filtering out
+ # nodes.
+ module StringHelp
+ def on_str(node)
+ # Constants like __FILE__ are handled as strings,
+ # but don't respond to begin.
+ return unless node.loc.respond_to?(:begin) && node.loc.begin
+ return if part_of_ignored_node?(node)
+
+ if offense?(node)
+ add_offense(node, :expression) { opposite_style_detected }
+ else
+ correct_style_detected
+ end
+ end
+
+ def on_dstr(node)
+ ignore_node(node)
+ end
+
+ def on_regexp(node)
+ ignore_node(node)
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/mixin/surrounding_space.rb b/lib/rubocop/cop/mixin/surrounding_space.rb
new file mode 100644
index 0000000..11563c8
--- /dev/null
+++ b/lib/rubocop/cop/mixin/surrounding_space.rb
@@ -0,0 +1,42 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # Common functionality for checking surrounding space.
+ module SurroundingSpace
+ def space_between?(t1, t2)
+ char_preceding_2nd_token =
+ @processed_source[t2.pos.line - 1][t2.pos.column - 1]
+ if char_preceding_2nd_token == '+' && t1.type != :tPLUS
+ # Special case. A unary plus is not present in the tokens.
+ char_preceding_2nd_token =
+ @processed_source[t2.pos.line - 1][t2.pos.column - 2]
+ end
+ t2.pos.line > t1.pos.line || char_preceding_2nd_token =~ /[ \t]/
+ end
+
+ def index_of_first_token(node)
+ b = node.loc.expression.begin
+ token_table[[b.line, b.column]]
+ end
+
+ def index_of_last_token(node)
+ e = node.loc.expression.end
+ (0...e.column).to_a.reverse.find do |c|
+ ix = token_table[[e.line, c]]
+ return ix if ix
+ end
+ end
+
+ def token_table
+ @token_table ||= begin
+ table = {}
+ @processed_source.tokens.each_with_index do |t, ix|
+ table[[t.pos.line, t.pos.column]] = ix
+ end
+ table
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/offense.rb b/lib/rubocop/cop/offense.rb
new file mode 100644
index 0000000..ae619bf
--- /dev/null
+++ b/lib/rubocop/cop/offense.rb
@@ -0,0 +1,119 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # An offense represents a style violation detected by RuboCop.
+ class Offense
+ include Comparable
+
+ # @api public
+ #
+ # @!attribute [r] severity
+ #
+ # @return [Rubocop::Cop::Severity]
+ attr_reader :severity
+
+ # @api public
+ #
+ # @!attribute [r] location
+ #
+ # @return [Parser::Source::Range]
+ # the location where the violation is detected.
+ #
+ # @see http://rubydoc.info/github/whitequark/parser/Parser/Source/Range
+ # Parser::Source::Range
+ attr_reader :location
+
+ # @api public
+ #
+ # @!attribute [r] message
+ #
+ # @return [String]
+ # human-readable message
+ #
+ # @example
+ # 'Line is too long. [90/79]'
+ attr_reader :message
+
+ # @api public
+ #
+ # @!attribute [r] cop_name
+ #
+ # @return [String]
+ # a cop class name without namespace.
+ # i.e. type of the violation.
+ #
+ # @example
+ # 'LineLength'
+ attr_reader :cop_name
+
+ # @api public
+ #
+ # @!attribute [r] corrected
+ #
+ # @return [Boolean]
+ # whether this offense is automatically corrected.
+ attr_reader :corrected
+ alias_method :corrected?, :corrected
+
+ # @api private
+ attr_reader :line
+
+ # @api private
+ attr_reader :column
+
+ # @api private
+ def initialize(severity, location, message, cop_name, corrected = false)
+ @severity = Rubocop::Cop::Severity.new(severity)
+ @location = location.freeze
+ @line = location.line.freeze
+ @column = location.column.freeze
+ @message = message.freeze
+ @cop_name = cop_name.freeze
+ @corrected = corrected.freeze
+ freeze
+ end
+
+ # @api private
+ # This is just for debugging purpose.
+ def to_s
+ format('%s:%3d:%3d: %s',
+ severity.code, line, real_column, message)
+ end
+
+ # @api private
+ #
+ # Internally we use column number that start at 0, but when
+ # outputting column numbers, we want them to start at 1. One
+ # reason is that editors, such as Emacs, expect this.
+ def real_column
+ column + 1
+ end
+
+ # @api public
+ #
+ # @return [Boolean]
+ # returns `true` if two offenses contain same attributes
+ def ==(other)
+ severity == other.severity && line == other.line &&
+ column == other.column && message == other.message &&
+ cop_name == other.cop_name
+ end
+
+ # @api public
+ #
+ # Returns `-1`, `0` or `+1`
+ # if this offense is less than, equal to, or greater than `other`.
+ #
+ # @return [Integer]
+ # comparison result
+ def <=>(other)
+ [:line, :column, :cop_name, :message].each do |attribute|
+ result = send(attribute) <=> other.send(attribute)
+ return result unless result == 0
+ end
+ 0
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/rails/action_filter.rb b/lib/rubocop/cop/rails/action_filter.rb
new file mode 100644
index 0000000..f534f80
--- /dev/null
+++ b/lib/rubocop/cop/rails/action_filter.rb
@@ -0,0 +1,73 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Rails
+ # This cop enforces the consistent use of action filters methods.
+ #
+ # The cop is configurable and the enforce the use of older
+ # something_filter methods or the newer something_action methods.
+ class ActionFilter < Cop
+ include ConfigurableEnforcedStyle
+
+ MSG = 'Prefer `%s` over `%s`.'
+
+ FILTER_METHODS = [:before_filter, :skip_before_filter,
+ :after_filter, :around_filter]
+
+ ACTION_METHODS = [:before_action, :skip_before_action,
+ :after_action, :around_action]
+
+ def on_block(node)
+ method, _args, _body = *node
+
+ check_method_node(method)
+ end
+
+ def on_send(node)
+ receiver, _method_name, *_args = *node
+
+ check_method_node(node) if receiver.nil?
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ corrector.replace(node.loc.selector,
+ preferred_method(node.loc.selector.source).to_s)
+ end
+ end
+
+ private
+
+ def check_method_node(node)
+ _receiver, method_name, *_args = *node
+
+ if offending_method?(method_name)
+ add_offense(
+ node, :selector,
+ format(MSG,
+ preferred_method(method_name),
+ method_name)
+ )
+ end
+ end
+
+ def offending_method?(method_name)
+ bad_methods.include?(method_name)
+ end
+
+ def bad_methods
+ style == :action ? FILTER_METHODS : ACTION_METHODS
+ end
+
+ def good_methods
+ style == :action ? ACTION_METHODS : FILTER_METHODS
+ end
+
+ def preferred_method(method)
+ good_methods[bad_methods.index(method.to_sym)]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/rails/default_scope.rb b/lib/rubocop/cop/rails/default_scope.rb
new file mode 100644
index 0000000..2556db3
--- /dev/null
+++ b/lib/rubocop/cop/rails/default_scope.rb
@@ -0,0 +1,35 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Rails
+ # This cop checks for default_scope calls when it was passed
+ # a lambda or a proc instead of a block.
+ #
+ # @example
+ #
+ # # incorrect
+ # default_scope -> { something }
+ #
+ # # correct
+ # default_scope { something }
+ class DefaultScope < Cop
+ MSG = '`default_scope` expects a block as its sole argument.'
+
+ def on_send(node)
+ return unless command?(:default_scope, node)
+
+ _receiver, _method_name, *args = *node
+
+ return unless args.size == 1
+
+ first_arg = args[0]
+
+ if first_arg.type != :block || lambda_or_proc?(first_arg)
+ add_offense(first_arg, :expression)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/rails/has_and_belongs_to_many.rb b/lib/rubocop/cop/rails/has_and_belongs_to_many.rb
new file mode 100644
index 0000000..6dd1b5d
--- /dev/null
+++ b/lib/rubocop/cop/rails/has_and_belongs_to_many.rb
@@ -0,0 +1,18 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Rails
+ # This cop checks for the use of the has_and_belongs_to_many macro.
+ class HasAndBelongsToMany < Cop
+ MSG = 'Prefer `has_many :through` to `has_and_belongs_to_many`.'
+
+ def on_send(node)
+ if command?(:has_and_belongs_to_many, node)
+ add_offense(node, :selector)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/rails/output.rb b/lib/rubocop/cop/rails/output.rb
new file mode 100644
index 0000000..49d5a50
--- /dev/null
+++ b/lib/rubocop/cop/rails/output.rb
@@ -0,0 +1,26 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Rails
+ # This cop checks for the use of output calls like puts and print
+ class Output < Cop
+ MSG = 'Do not write to stdout. Use Rails\' logger if you want to log.'
+
+ BLACKLIST = [:puts,
+ :print,
+ :p,
+ :pp,
+ :pretty_print]
+
+ def on_send(node)
+ receiver, method_name, *_args = *node
+
+ if receiver.nil? && BLACKLIST.include?(method_name)
+ add_offense(node, :selector)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/rails/read_write_attribute.rb b/lib/rubocop/cop/rails/read_write_attribute.rb
new file mode 100644
index 0000000..34e8a9b
--- /dev/null
+++ b/lib/rubocop/cop/rails/read_write_attribute.rb
@@ -0,0 +1,43 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Rails
+ # This cop checks for the use of the read_attribute or
+ # write_attribute methods.
+ #
+ # @example
+ #
+ # # bad
+ # x = read_attributed(:attr)
+ # write_attribute(:attr, val)
+ #
+ # # good
+ # x = self[:attr]
+ # self[:attr] = val
+ class ReadWriteAttribute < Cop
+ MSG = 'Prefer `%s` over `%s`.'
+
+ def on_send(node)
+ receiver, method_name, *_args = *node
+
+ return if receiver
+
+ if [:read_attribute, :write_attribute].include?(method_name)
+ add_offense(node, :selector)
+ end
+ end
+
+ def message(node)
+ _receiver, method_name, *_args = *node
+
+ if method_name == :read_attribute
+ format(MSG, 'self[:attr]', 'read_attribute(:attr)')
+ else
+ format(MSG, 'self[:attr] = val', 'write_attribute(:attr, var)')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/rails/scope_args.rb b/lib/rubocop/cop/rails/scope_args.rb
new file mode 100644
index 0000000..3d44938
--- /dev/null
+++ b/lib/rubocop/cop/rails/scope_args.rb
@@ -0,0 +1,33 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Rails
+ # This cop checks for scope calls where it was passed
+ # a method (usually a scope) instead of a lambda/proc.
+ #
+ # @example
+ #
+ # # bad
+ # scope :something, where(something: true)
+ #
+ # # good
+ # scope :something, -> { where(something: true) }
+ class ScopeArgs < Cop
+ MSG = 'Use `lambda`/`proc` instead of a plain method call.'
+
+ def on_send(node)
+ return unless command?(:scope, node)
+
+ _receiver, _method_name, *args = *node
+
+ return unless args.size == 2
+
+ second_arg = args[1]
+
+ add_offense(second_arg, :expression) if second_arg.type == :send
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/rails/validation.rb b/lib/rubocop/cop/rails/validation.rb
new file mode 100644
index 0000000..c2c85a4
--- /dev/null
+++ b/lib/rubocop/cop/rails/validation.rb
@@ -0,0 +1,31 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Rails
+ # This cop checks for the use of old-style attribute validation macros.
+ class Validation < Cop
+ MSG = 'Use the new "sexy" validations (`validates` ...).'
+
+ BLACKLIST = [:validates_acceptance_of,
+ :validates_confirmation_of,
+ :validates_exclusion_of,
+ :validates_format_of,
+ :validates_inclusion_of,
+ :validates_length_of,
+ :validates_numericality_of,
+ :validates_presence_of,
+ :validates_size_of,
+ :validates_uniqueness_of]
+
+ def on_send(node)
+ receiver, method_name, *_args = *node
+
+ if receiver.nil? && BLACKLIST.include?(method_name)
+ add_offense(node, :selector)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/severity.rb b/lib/rubocop/cop/severity.rb
new file mode 100644
index 0000000..d991308
--- /dev/null
+++ b/lib/rubocop/cop/severity.rb
@@ -0,0 +1,76 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # Severity class is simple value object about severity
+ class Severity
+ include Comparable
+
+ # @api private
+ NAMES = [:refactor, :convention, :warning, :error, :fatal]
+
+ # @api private
+ CODE_TABLE = { R: :refactor, C: :convention,
+ W: :warning, E: :error, F: :fatal }
+
+ # @api public
+ #
+ # @!attribute [r] name
+ #
+ # @return [Symbol]
+ # severity.
+ # any of `:refactor`, `:convention`, `:warning`, `:error` or `:fatal`.
+ attr_reader :name
+
+ # @api private
+ def self.name_from_code(code)
+ name = code.to_sym
+ CODE_TABLE[name] || name
+ end
+
+ # @api private
+ def initialize(name_or_code)
+ name = Severity.name_from_code(name_or_code)
+ unless NAMES.include?(name)
+ fail ArgumentError, "Unknown severity: #{name}"
+ end
+ @name = name.freeze
+ freeze
+ end
+
+ # @api private
+ def to_s
+ @name.to_s
+ end
+
+ # @api private
+ def code
+ @name.to_s[0].upcase
+ end
+
+ # @api private
+ def level
+ NAMES.index(name) + 1
+ end
+
+ # @api private
+ def ==(other)
+ if other.is_a?(Symbol)
+ @name == other
+ else
+ @name == other.name
+ end
+ end
+
+ # @api private
+ def hash
+ @name.hash
+ end
+
+ # @api private
+ def <=>(other)
+ level <=> other.level
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/access_modifier_indentation.rb b/lib/rubocop/cop/style/access_modifier_indentation.rb
new file mode 100644
index 0000000..76d525a
--- /dev/null
+++ b/lib/rubocop/cop/style/access_modifier_indentation.rb
@@ -0,0 +1,94 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Modifiers should be indented as deep as method definitions, or as deep
+ # as the class/module keyword, depending on configuration.
+ class AccessModifierIndentation < Cop
+ include AutocorrectAlignment
+ include ConfigurableEnforcedStyle
+
+ MSG = '%s access modifiers like `%s`.'
+
+ PRIVATE_NODE = s(:send, nil, :private)
+ PROTECTED_NODE = s(:send, nil, :protected)
+ PUBLIC_NODE = s(:send, nil, :public)
+
+ def on_class(node)
+ _name, _base_class, body = *node
+ check_body(body, node)
+ end
+
+ def on_sclass(node)
+ _name, body = *node
+ check_body(body, node)
+ end
+
+ def on_module(node)
+ _name, body = *node
+ check_body(body, node)
+ end
+
+ def on_block(node)
+ _method, _args, body = *node
+ check_body(body, node) if class_constructor?(node)
+ end
+
+ def self.modifier_node?(node)
+ [PRIVATE_NODE, PROTECTED_NODE, PUBLIC_NODE].include?(node)
+ end
+
+ private
+
+ def check_body(body, node)
+ return if body.nil? # Empty class etc.
+
+ modifiers = body.children.select { |c| self.class.modifier_node?(c) }
+ class_column = node.loc.expression.column
+
+ modifiers.each { |modifier| check_modifier(modifier, class_column) }
+ end
+
+ def check_modifier(send_node, class_start_col)
+ access_modifier_start_col = send_node.loc.expression.column
+ offset = access_modifier_start_col - class_start_col
+
+ @column_delta = expected_indent_offset - offset
+ if @column_delta == 0
+ correct_style_detected
+ else
+ add_offense(send_node, :expression) do
+ if offset == unexpected_indent_offset
+ opposite_style_detected
+ else
+ unrecognized_style_detected
+ end
+ end
+ end
+ end
+
+ def message(node)
+ format(MSG, style.capitalize, node.loc.selector.source)
+ end
+
+ def class_constructor?(block_node)
+ send_node = block_node.children.first
+ receiver_node, method_name, *_ = *send_node
+ return false unless method_name == :new
+ %w(Class Module).include?(Util.const_name(receiver_node))
+ end
+
+ def expected_indent_offset
+ style == :outdent ? 0 : IndentationWidth::CORRECT_INDENTATION
+ end
+
+ # An offset that is not expected, but correct if the configuration is
+ # changed.
+ def unexpected_indent_offset
+ IndentationWidth::CORRECT_INDENTATION - expected_indent_offset
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/accessor_method_name.rb b/lib/rubocop/cop/style/accessor_method_name.rb
new file mode 100644
index 0000000..86e5882
--- /dev/null
+++ b/lib/rubocop/cop/style/accessor_method_name.rb
@@ -0,0 +1,45 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop makes sure that accessor methods are named properly.
+ #
+ # @example
+ # # bad
+ # def set_attribute(value) ...
+ #
+ # # good
+ # def attribute=(value)
+ #
+ # # bad
+ # def get_attribute ...
+ #
+ # # good
+ # def attribute ...
+ class AccessorMethodName < Cop
+ include CheckMethods
+
+ private
+
+ def check(node, method_name, args, _body)
+ if bad_reader_name?(method_name.to_s, args)
+ add_offense(node, :name,
+ 'Do not prefix reader method names with `get_`.')
+ elsif bad_writer_name?(method_name.to_s, args)
+ add_offense(node, :name,
+ 'Do not prefix writer method names with `set_`.')
+ end
+ end
+
+ def bad_reader_name?(method_name, args)
+ method_name.start_with?('get_') && args.to_a.empty?
+ end
+
+ def bad_writer_name?(method_name, args)
+ method_name.start_with?('set_') && args.to_a.one?
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/alias.rb b/lib/rubocop/cop/style/alias.rb
new file mode 100644
index 0000000..3ac7e48
--- /dev/null
+++ b/lib/rubocop/cop/style/alias.rb
@@ -0,0 +1,48 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # The purpose of the this cop is advise the use of
+ # alias_method over the alias keyword whenever possible.
+ class Alias < Cop
+ MSG = 'Use `alias_method` instead of `alias`.'
+
+ def on_block(node)
+ method, _args, body = *node
+ _receiver, method_name = *method
+
+ # using alias is the only option in certain scenarios
+ # in such scenarios we don't want to report an offense
+ if method_name == :instance_exec
+ on_node(:alias, body) { |n| ignore_node(n) }
+ end
+ end
+
+ def on_alias(node)
+ return if ignored_node?(node)
+
+ # alias_method can't be used with global variables
+ new, old = *node
+
+ return if new.type == :gvar && old.type == :gvar
+
+ add_offense(node, :keyword)
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ # replace alias with alias_method
+ corrector.replace(node.loc.keyword, 'alias_method')
+ # insert a comma
+ new, old = *node
+ corrector.insert_after(new.loc.expression, ',')
+ # convert bareword arguments to symbols
+ corrector.replace(new.loc.expression, ":#{new.children.first}")
+ corrector.replace(old.loc.expression, ":#{old.children.first}")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/align_array.rb b/lib/rubocop/cop/style/align_array.rb
new file mode 100644
index 0000000..bd117c0
--- /dev/null
+++ b/lib/rubocop/cop/style/align_array.rb
@@ -0,0 +1,20 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Here we check if the elements of a multi-line array literal are
+ # aligned.
+ class AlignArray < Cop
+ include AutocorrectAlignment
+
+ MSG = 'Align the elements of an array literal if they span more ' \
+ 'than one line.'
+
+ def on_array(node)
+ check_alignment(node.children)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/align_hash.rb b/lib/rubocop/cop/style/align_hash.rb
new file mode 100644
index 0000000..5649e26
--- /dev/null
+++ b/lib/rubocop/cop/style/align_hash.rb
@@ -0,0 +1,257 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Here we check if the keys, separators, and values of a multi-line hash
+ # literal are aligned.
+ class AlignHash < Cop
+ include IgnoredNode
+
+ # Handles calculation of deltas (deviations from correct alignment)
+ # when the enforced style is 'key'.
+ class KeyAlignment
+ def checkable_layout(_node)
+ true
+ end
+
+ def deltas_for_first_pair(*_)
+ {} # The first pair is always considered correct.
+ end
+
+ def deltas(first_pair, prev_pair, current_pair)
+ if current_pair.loc.line == prev_pair.loc.line
+ {}
+ else
+ { key: first_pair.loc.column - current_pair.loc.column }
+ end
+ end
+ end
+
+ # Common functionality for the styles where not only keys, but also
+ # values are aligned.
+ class AlignmentOfValues
+ def checkable_layout(node)
+ !any_pairs_on_the_same_line?(node) && all_have_same_sparator?(node)
+ end
+
+ def deltas(first_pair, prev_pair, current_pair)
+ key_delta = key_delta(first_pair, current_pair)
+ current_separator = current_pair.loc.operator
+ separator_delta = separator_delta(first_pair, current_separator,
+ key_delta)
+ value_delta = value_delta(first_pair, current_pair) -
+ key_delta - separator_delta
+
+ { key: key_delta, separator: separator_delta, value: value_delta }
+ end
+
+ private
+
+ def separator_delta(first_pair, current_separator, key_delta)
+ if current_separator.is?(':')
+ 0 # Colon follows directly after key
+ else
+ hash_rocket_delta(first_pair, current_separator) - key_delta
+ end
+ end
+
+ def any_pairs_on_the_same_line?(node)
+ lines_of_the_children = node.children.map do |pair|
+ key, _value = *pair
+ key.loc.line
+ end
+ lines_of_the_children.uniq.size < lines_of_the_children.size
+ end
+
+ def all_have_same_sparator?(node)
+ first_separator = node.children.first.loc.operator.source
+ node.children[1..-1].all? do |pair|
+ pair.loc.operator.is?(first_separator)
+ end
+ end
+ end
+
+ # Handles calculation of deltas when the enforced style is 'table'.
+ class TableAlignment < AlignmentOfValues
+ # The table style is the only one where the first key-value pair can
+ # be considered to have bad alignment.
+ def deltas_for_first_pair(first_pair, node)
+ key_widths = node.children.map do |pair|
+ key, _value = *pair
+ key.loc.expression.source.length
+ end
+ @max_key_width = key_widths.max
+
+ separator_delta = separator_delta(first_pair,
+ first_pair.loc.operator, 0)
+ {
+ separator: separator_delta,
+ value: value_delta(first_pair, first_pair) - separator_delta
+ }
+ end
+
+ private
+
+ def key_delta(first_pair, current_pair)
+ first_pair.loc.column - current_pair.loc.column
+ end
+
+ def hash_rocket_delta(first_pair, current_separator)
+ first_pair.loc.column + @max_key_width + 1 -
+ current_separator.column
+ end
+
+ def value_delta(first_pair, current_pair)
+ first_key, _ = *first_pair
+ _, current_value = *current_pair
+ correct_value_column = first_key.loc.column +
+ spaced_separator(current_pair).length + @max_key_width
+ correct_value_column - current_value.loc.column
+ end
+
+ def spaced_separator(node)
+ node.loc.operator.is?('=>') ? ' => ' : ': '
+ end
+ end
+
+ # Handles calculation of deltas when the enforced style is 'separator'.
+ class SeparatorAlignment < AlignmentOfValues
+ def deltas_for_first_pair(first_pair, node)
+ {} # The first pair is always considered correct.
+ end
+
+ private
+
+ def key_delta(first_pair, current_pair)
+ key_end_column(first_pair) - key_end_column(current_pair)
+ end
+
+ def key_end_column(pair)
+ key, _value = *pair
+ key.loc.column + key.loc.expression.source.length
+ end
+
+ def hash_rocket_delta(first_pair, current_separator)
+ first_pair.loc.operator.column - current_separator.column
+ end
+
+ def value_delta(first_pair, current_pair)
+ _, first_value = *first_pair
+ _, current_value = *current_pair
+ first_value.loc.column - current_value.loc.column
+ end
+ end
+
+ MSG = 'Align the elements of a hash literal if they span more than ' \
+ 'one line.'
+
+ def on_send(node)
+ if (last_child = node.children.last) && hash?(last_child) &&
+ ignore_last_argument_hash?(last_child)
+ ignore_node(last_child)
+ end
+ end
+
+ def on_hash(node)
+ return if ignored_node?(node)
+ return if node.children.empty?
+ return unless multiline?(node)
+
+ @alignment_for_hash_rockets ||=
+ new_alignment('EnforcedHashRocketStyle')
+ @alignment_for_colons ||= new_alignment('EnforcedColonStyle')
+
+ first_pair = node.children.first
+
+ unless @alignment_for_hash_rockets.checkable_layout(node) &&
+ @alignment_for_colons.checkable_layout(node)
+ return
+ end
+
+ @column_deltas = alignment_for(first_pair)
+ .deltas_for_first_pair(first_pair, node)
+ add_offense(first_pair, :expression) unless good_alignment?
+
+ node.children.each_cons(2) do |prev, current|
+ @column_deltas = alignment_for(current).deltas(first_pair, prev,
+ current)
+ add_offense(current, :expression) unless good_alignment?
+ end
+ end
+
+ private
+
+ def ignore_last_argument_hash?(node)
+ case cop_config['EnforcedLastArgumentHashStyle']
+ when 'always_inspect' then false
+ when 'always_ignore' then true
+ when 'ignore_explicit' then explicit_hash?(node)
+ when 'ignore_implicit' then !explicit_hash?(node)
+ end
+ end
+
+ def hash?(node)
+ node.respond_to?(:type) && node.type == :hash
+ end
+
+ def explicit_hash?(node)
+ node.loc.begin
+ end
+
+ def multiline?(node)
+ node.loc.expression.source.include?("\n")
+ end
+
+ def alignment_for(pair)
+ if pair.loc.operator.is?('=>')
+ @alignment_for_hash_rockets
+ else
+ @alignment_for_colons
+ end
+ end
+
+ def autocorrect(node)
+ # We can't use the instance variable inside the lambda. That would
+ # just give each lambda the same reference and they would all get the
+ # last value of each. Some local variables fix the problem.
+ key_delta = @column_deltas[:key] || 0
+ separator_delta = @column_deltas[:separator] || 0
+ value_delta = @column_deltas[:value] || 0
+
+ key, value = *node
+
+ @corrections << lambda do |corrector|
+ adjust(corrector, key_delta, key.loc.expression)
+ adjust(corrector, separator_delta, node.loc.operator)
+ adjust(corrector, value_delta, value.loc.expression)
+ end
+ end
+
+ def new_alignment(key)
+ case cop_config[key]
+ when 'key' then KeyAlignment.new
+ when 'table' then TableAlignment.new
+ when 'separator' then SeparatorAlignment.new
+ else fail "Unknown #{key}: #{cop_config[key]}"
+ end
+ end
+
+ def adjust(corrector, delta, range)
+ if delta > 0
+ corrector.insert_before(range, ' ' * delta)
+ elsif delta < 0
+ range = Parser::Source::Range.new(range.source_buffer,
+ range.begin_pos - delta.abs,
+ range.begin_pos)
+ corrector.remove(range)
+ end
+ end
+
+ def good_alignment?
+ @column_deltas.values.compact.none? { |v| v != 0 }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/align_parameters.rb b/lib/rubocop/cop/style/align_parameters.rb
new file mode 100644
index 0000000..37d7957
--- /dev/null
+++ b/lib/rubocop/cop/style/align_parameters.rb
@@ -0,0 +1,39 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Here we check if the parameters on a multi-line method call are
+ # aligned.
+ class AlignParameters < Cop
+ include AutocorrectAlignment
+
+ MSG = 'Align the parameters of a method call if they span ' \
+ 'more than one line.'
+
+ def on_send(node)
+ _receiver, method, *args = *node
+
+ return if method == :[]=
+ return if args.size <= 1
+
+ check_alignment(args, base_column(node, args))
+ end
+
+ private
+
+ def fixed_indentation?
+ cop_config['EnforcedStyle'] == 'with_fixed_indentation'
+ end
+
+ def base_column(node, args)
+ if fixed_indentation?
+ node.loc.column + 2
+ else
+ args.first.loc.column
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/and_or.rb b/lib/rubocop/cop/style/and_or.rb
new file mode 100644
index 0000000..5c6d615
--- /dev/null
+++ b/lib/rubocop/cop/style/and_or.rb
@@ -0,0 +1,44 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for uses of *and* and *or*.
+ class AndOr < Cop
+ include AutocorrectUnlessChangingAST
+
+ MSG = 'Use `%s` instead of `%s`.'
+
+ OPS = { 'and' => '&&', 'or' => '||' }
+
+ def on_and(node)
+ process_logical_op(node)
+ end
+
+ def on_or(node)
+ process_logical_op(node)
+ end
+
+ private
+
+ def process_logical_op(node)
+ op = node.loc.operator.source
+ op_type = node.type.to_s
+
+ if op == op_type
+ add_offense(node,
+ :operator,
+ format(MSG, OPS[op], op))
+ end
+ end
+
+ def correction(node)
+ lambda do |corrector|
+ replacement = (node.type == :and ? '&&' : '||')
+ corrector.replace(node.loc.operator, replacement)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/array_join.rb b/lib/rubocop/cop/style/array_join.rb
new file mode 100644
index 0000000..78bb487
--- /dev/null
+++ b/lib/rubocop/cop/style/array_join.rb
@@ -0,0 +1,25 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for uses of "*" as a substitute for *join*.
+ #
+ # Not all cases can reliably checked, due to Ruby's dynamic
+ # types, so we consider only cases when the first argument is an
+ # array literal or the second is a string literal.
+ class ArrayJoin < Cop
+ MSG = 'Favor `Array#join` over `Array#*`.'
+
+ def on_send(node)
+ receiver_node, method_name, *arg_nodes = *node
+
+ if receiver_node && receiver_node.type == :array &&
+ method_name == :* && arg_nodes[0].type == :str
+ add_offense(node, :selector)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/ascii_comments.rb b/lib/rubocop/cop/style/ascii_comments.rb
new file mode 100644
index 0000000..4dbe3e2
--- /dev/null
+++ b/lib/rubocop/cop/style/ascii_comments.rb
@@ -0,0 +1,19 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for non-ascii (non-English) characters
+ # in comments.
+ class AsciiComments < Cop
+ MSG = 'Use only ascii symbols in comments.'
+
+ def investigate(processed_source)
+ processed_source.comments.each do |comment|
+ add_offense(comment, :expression) unless comment.text.ascii_only?
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/ascii_identifiers.rb b/lib/rubocop/cop/style/ascii_identifiers.rb
new file mode 100644
index 0000000..b4337c3
--- /dev/null
+++ b/lib/rubocop/cop/style/ascii_identifiers.rb
@@ -0,0 +1,20 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for non-ascii characters in indentifier names.
+ class AsciiIdentifiers < Cop
+ MSG = 'Use only ascii symbols in identifiers.'
+
+ def investigate(processed_source)
+ processed_source.tokens.each do |t|
+ if t.type == :tIDENTIFIER && !t.text.ascii_only?
+ add_offense(nil, t.pos)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/attr.rb b/lib/rubocop/cop/style/attr.rb
new file mode 100644
index 0000000..0dd6f78
--- /dev/null
+++ b/lib/rubocop/cop/style/attr.rb
@@ -0,0 +1,22 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for uses of Module#attr.
+ class Attr < Cop
+ MSG = 'Do not use `attr`. Use `attr_reader` instead.'
+
+ def on_send(node)
+ add_offense(node, :selector) if command?(:attr, node)
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ corrector.replace(node.loc.selector, 'attr_reader')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/begin_block.rb b/lib/rubocop/cop/style/begin_block.rb
new file mode 100644
index 0000000..5106b13
--- /dev/null
+++ b/lib/rubocop/cop/style/begin_block.rb
@@ -0,0 +1,16 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for BEGIN blocks.
+ class BeginBlock < Cop
+ MSG = 'Avoid the use of `BEGIN` blocks.'
+
+ def on_preexe(node)
+ add_offense(node, :keyword)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/block_comments.rb b/lib/rubocop/cop/style/block_comments.rb
new file mode 100644
index 0000000..2eeaad7
--- /dev/null
+++ b/lib/rubocop/cop/style/block_comments.rb
@@ -0,0 +1,20 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop looks for uses of block comments (=begin...=end).
+ class BlockComments < Cop
+ MSG = 'Do not use block comments.'
+
+ def investigate(processed_source)
+ processed_source.comments.each do |comment|
+ if comment.text.start_with?('=begin')
+ add_offense(comment, :expression)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/block_nesting.rb b/lib/rubocop/cop/style/block_nesting.rb
new file mode 100644
index 0000000..da61733
--- /dev/null
+++ b/lib/rubocop/cop/style/block_nesting.rb
@@ -0,0 +1,55 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for excessive nesting of conditional and looping
+ # constructs. Despite the cop's name, blocks are not considered as an
+ # extra level of nesting.
+ #
+ # The maximum level of nesting allowed is configurable.
+ class BlockNesting < Cop
+ include ConfigurableMax
+
+ NESTING_BLOCKS = [
+ :case, :if, :while, :while_post,
+ :until, :until_post, :for, :resbody
+ ]
+
+ def investigate(processed_source)
+ return unless processed_source.ast
+ max = cop_config['Max']
+ check_nesting_level(processed_source.ast, max, 0)
+ end
+
+ private
+
+ def check_nesting_level(node, max, current_level)
+ if NESTING_BLOCKS.include?(node.type)
+ unless node.loc.respond_to?(:keyword) &&
+ node.loc.keyword.is?('elsif')
+ current_level += 1
+ end
+ if current_level > max
+ self.max = current_level
+ unless part_of_ignored_node?(node)
+ add_offense(node, :expression, message(max)) do
+ ignore_node(node)
+ end
+ end
+ end
+ end
+ node.children.each do |child|
+ if child.is_a?(Parser::AST::Node)
+ check_nesting_level(child, max, current_level)
+ end
+ end
+ end
+
+ def message(max)
+ "Avoid more than #{max} levels of block nesting."
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/blocks.rb b/lib/rubocop/cop/style/blocks.rb
new file mode 100644
index 0000000..0f795c6
--- /dev/null
+++ b/lib/rubocop/cop/style/blocks.rb
@@ -0,0 +1,80 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Check for uses of braces or do/end around single line or
+ # multi-line blocks.
+ class Blocks < Cop
+ include AutocorrectUnlessChangingAST
+
+ MULTI_LINE_MSG = 'Avoid using {...} for multi-line blocks.'
+ SINGLE_LINE_MSG = 'Prefer {...} over do...end for single-line blocks.'
+
+ def on_send(node)
+ _receiver, method_name, *args = *node
+ if args.any?
+ block = get_block(args.last)
+ if block && !parentheses?(node) && !operator?(method_name)
+ # If there are no parentheses around the arguments, then braces
+ # and do-end have different meaning due to how they bind, so we
+ # allow either.
+ ignore_node(block)
+ end
+ end
+ end
+
+ def on_block(node)
+ return if ignored_node?(node)
+
+ block_length = Util.block_length(node)
+ block_begin = node.loc.begin.source
+
+ if block_length > 0 && block_begin == '{'
+ add_offense(node, :begin, MULTI_LINE_MSG)
+ elsif block_length == 0 && block_begin != '{'
+ add_offense(node, :begin, SINGLE_LINE_MSG)
+ end
+ end
+
+ private
+
+ def correction(node)
+ lambda do |corrector|
+ b, e = node.loc.begin, node.loc.end
+ if b.is?('{')
+ # If the left brace is immediately preceded by a word character,
+ # then we need a space before `do` to get valid Ruby code.
+ if b.source_buffer.source[b.begin_pos - 1, 1] =~ /\w/
+ corrector.insert_before(b, ' ')
+ end
+ corrector.replace(b, 'do')
+ corrector.replace(e, 'end')
+ else
+ corrector.replace(b, '{')
+ corrector.replace(e, '}')
+ end
+ end
+ end
+
+ def get_block(node)
+ case node.type
+ when :block
+ node
+ when :send
+ receiver, _method_name, *_args = *node
+ get_block(receiver) if receiver
+ end
+ end
+
+ def parentheses?(send_node)
+ send_node.loc.begin
+ end
+
+ def operator?(method_name)
+ method_name =~ /^\W/
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/braces_around_hash_parameters.rb b/lib/rubocop/cop/style/braces_around_hash_parameters.rb
new file mode 100644
index 0000000..0af7b22
--- /dev/null
+++ b/lib/rubocop/cop/style/braces_around_hash_parameters.rb
@@ -0,0 +1,87 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for braces in method calls with hash parameters.
+ class BracesAroundHashParameters < Cop
+ include ConfigurableEnforcedStyle
+
+ def on_send(node)
+ _receiver, method_name, *args = *node
+
+ # Discard attr writer methods.
+ return if method_name.to_s.end_with?('=')
+ # Discard operator methods.
+ return if operator?(method_name)
+
+ # We care only for the last argument.
+ arg = args.last
+
+ check(arg, args) if non_empty_hash?(arg)
+ end
+
+ private
+
+ def check(arg, args)
+ if style == :no_braces
+ if !braces?(arg) || all_hashes?(args)
+ correct_style_detected
+ else
+ add_offense(arg, :expression,
+ 'Redundant curly braces around a hash parameter.') do
+ opposite_style_detected
+ end
+ end
+ elsif braces?(arg)
+ correct_style_detected
+ else
+ add_offense(arg, :expression,
+ 'Missing curly braces around a hash parameter.') do
+ opposite_style_detected
+ end
+ end
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ if style == :no_braces
+ corrector.remove(node.loc.begin)
+ corrector.remove(node.loc.end)
+ remove_trailing_comma(node, corrector)
+ elsif style == :braces
+ corrector.insert_before(node.loc.expression, '{')
+ corrector.insert_after(node.loc.expression, '}')
+ end
+ end
+ end
+
+ def remove_trailing_comma(node, corrector)
+ sb = node.loc.end.source_buffer
+ pos_after_last_pair = node.children.last.loc.expression.end_pos
+ range_after_last_pair =
+ Parser::Source::Range.new(sb, pos_after_last_pair,
+ node.loc.end.begin_pos)
+ trailing_comma_offset = range_after_last_pair.source =~ /,/
+ if trailing_comma_offset
+ comma_begin = pos_after_last_pair + trailing_comma_offset
+ corrector.remove(Parser::Source::Range.new(sb, comma_begin,
+ comma_begin + 1))
+ end
+ end
+
+ def non_empty_hash?(arg)
+ arg && arg.type == :hash && arg.children.any?
+ end
+
+ def braces?(arg)
+ arg.loc.begin
+ end
+
+ def all_hashes?(args)
+ args.length > 1 && args.all? { |a| a.type == :hash }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/case_equality.rb b/lib/rubocop/cop/style/case_equality.rb
new file mode 100644
index 0000000..99e1650
--- /dev/null
+++ b/lib/rubocop/cop/style/case_equality.rb
@@ -0,0 +1,18 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for uses of the case equality operator(===).
+ class CaseEquality < Cop
+ MSG = 'Avoid the use of the case equality operator `===`.'
+
+ def on_send(node)
+ _receiver, method_name, *_args = *node
+
+ add_offense(node, :selector) if method_name == :===
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/case_indentation.rb b/lib/rubocop/cop/style/case_indentation.rb
new file mode 100644
index 0000000..6720e6d
--- /dev/null
+++ b/lib/rubocop/cop/style/case_indentation.rb
@@ -0,0 +1,62 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks how the *when*s of a *case* expression
+ # are indented in relation to its *case* or *end* keyword.
+ #
+ # It will register a separate offense for each misaligned *when*.
+ class CaseIndentation < Cop
+ include ConfigurableEnforcedStyle
+
+ def on_case(case_node)
+ _condition, *whens, _else = *case_node
+
+ base = style
+ indent = cop_config['IndentOneStep']
+ base_column = base_column(case_node, base)
+
+ whens.each do |when_node|
+ check_when(when_node, case_node, base, indent, base_column)
+ end
+ end
+
+ private
+
+ def check_when(when_node, case_node, base, indent, base_column)
+ pos = when_node.loc.keyword
+ expected_column = base_column +
+ (indent ? IndentationWidth::CORRECT_INDENTATION : 0)
+ if pos.column == expected_column
+ correct_style_detected
+ else
+ msg = 'Indent `when` ' + if indent
+ "one step more than `#{base}`."
+ else
+ "as deep as `#{base}`."
+ end
+ add_offense(when_node, pos, msg) do
+ if pos.column == base_column(case_node, alternative_style)
+ opposite_style_detected
+ else
+ unrecognized_style_detected
+ end
+ end
+ end
+ end
+
+ def parameter_name
+ 'IndentWhenRelativeTo'
+ end
+
+ def base_column(case_node, base)
+ case base
+ when :case then case_node.location.keyword.column
+ when :end then case_node.location.end.column
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/character_literal.rb b/lib/rubocop/cop/style/character_literal.rb
new file mode 100644
index 0000000..1a02631
--- /dev/null
+++ b/lib/rubocop/cop/style/character_literal.rb
@@ -0,0 +1,42 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Checks for uses of the character literal ?x.
+ class CharacterLiteral < Cop
+ include StringHelp
+
+ MSG = 'Do not use the character literal - use string literal instead.'
+
+ def offense?(node)
+ # we don't register an offense for things like ?\C-\M-d
+ node.loc.begin.is?('?') &&
+ node.loc.expression.source.size.between?(2, 3)
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ string = node.loc.expression.source[1..-1]
+
+ if string.length == 1 # normal character
+ corrector.replace(node.loc.expression, "'#{string}'")
+ elsif string.length == 2 # special character like \n
+ corrector.replace(node.loc.expression, %Q("#{string}"))
+ end
+ end
+ end
+
+ # Dummy implementation of method in ConfigurableEnforcedStyle that is
+ # called from StringHelp.
+ def opposite_style_detected
+ end
+
+ # Dummy implementation of method in ConfigurableEnforcedStyle that is
+ # called from StringHelp.
+ def correct_style_detected
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/class_and_module_camel_case.rb b/lib/rubocop/cop/style/class_and_module_camel_case.rb
new file mode 100644
index 0000000..dcc3b15
--- /dev/null
+++ b/lib/rubocop/cop/style/class_and_module_camel_case.rb
@@ -0,0 +1,29 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cops checks for class and module names with
+ # an underscore in them.
+ class ClassAndModuleCamelCase < Cop
+ MSG = 'Use CamelCase for classes and modules.'
+
+ def on_class(node)
+ check_name(node)
+ end
+
+ def on_module(node)
+ check_name(node)
+ end
+
+ private
+
+ def check_name(node)
+ name = node.loc.name.source
+
+ add_offense(node, :name) if name =~ /_/
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/class_and_module_children.rb b/lib/rubocop/cop/style/class_and_module_children.rb
new file mode 100644
index 0000000..d90318d
--- /dev/null
+++ b/lib/rubocop/cop/style/class_and_module_children.rb
@@ -0,0 +1,69 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks the style of children definitions at classes and
+ # modules. Basically there are two different styles:
+ #
+ # nested - have each child on its own line
+ # class Foo
+ # class Bar
+ # end
+ # end
+ #
+ # compact - combine definitions as much as possible
+ # class Foo::Bar
+ # end
+ #
+ # The compact style is only forced, for classes / modules with one child.
+ class ClassAndModuleChildren < Cop
+ include ConfigurableEnforcedStyle
+
+ NESTED_MSG = 'Use nested module/class definitions instead of ' \
+ 'compact style.'
+
+ COMPACT_MSG = 'Use compact module/class definition instead of ' \
+ 'nested style.'
+
+ def on_class(node)
+ _name, _superclass, body = *node
+ check_style(node, body)
+ end
+
+ def on_module(node)
+ _name, body = *node
+ check_style(node, body)
+ end
+
+ private
+
+ def check_style(node, body)
+ if style == :nested
+ check_nested_style(node)
+ else
+ check_compact_style(node, body)
+ end
+ end
+
+ def check_nested_style(node)
+ return unless compact_node_name?(node)
+ add_offense(node, :name, NESTED_MSG)
+ end
+
+ def check_compact_style(node, body)
+ return unless one_child?(node, body) && !compact_node_name?(node)
+ add_offense(node, :name, COMPACT_MSG)
+ end
+
+ def one_child?(node, body)
+ body && body.type != :begin
+ end
+
+ def compact_node_name?(node)
+ node.loc.name.source =~ /::/
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/class_length.rb b/lib/rubocop/cop/style/class_length.rb
new file mode 100644
index 0000000..fc8e344
--- /dev/null
+++ b/lib/rubocop/cop/style/class_length.rb
@@ -0,0 +1,49 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks if the length a class exceeds some maximum value.
+ # Comment lines can optionally be ignored.
+ # The maximum allowed length is configurable.
+ class ClassLength < Cop
+ include CodeLength
+
+ def on_class(node)
+ check(node)
+ end
+
+ private
+
+ def message
+ 'Class definition is too long. [%d/%d]'
+ end
+
+ def code_length(node)
+ class_body_line_numbers = line_range(node).to_a[1...-1]
+
+ target_line_numbers = class_body_line_numbers -
+ line_numbers_of_inner_classes(node)
+
+ target_line_numbers.reduce(0) do |length, line_number|
+ source_line = processed_source[line_number]
+ next length if irrelevant_line(source_line)
+ length + 1
+ end
+ end
+
+ def line_numbers_of_inner_classes(node)
+ line_numbers = Set.new
+
+ on_node([:class, :module], node) do |inner_node|
+ next if inner_node.eql?(node)
+ line_range = line_range(inner_node)
+ line_numbers.merge(line_range)
+ end
+
+ line_numbers.to_a
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/class_methods.rb b/lib/rubocop/cop/style/class_methods.rb
new file mode 100644
index 0000000..7afd978
--- /dev/null
+++ b/lib/rubocop/cop/style/class_methods.rb
@@ -0,0 +1,56 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for uses of the class/module name instead of
+ # self, when defining class/module methods.
+ class ClassMethods < Cop
+ MSG = 'Use `self.%s` instead of `%s.%s`.'
+
+ def on_class(node)
+ _name, _superclass, body = *node
+ check(body)
+ end
+
+ def on_module(node)
+ _name, body = *node
+ check(body)
+ end
+
+ private
+
+ def check(node)
+ return unless node
+
+ if node.type == :defs
+ check_defs(node)
+ elsif node.type == :begin
+ defs_nodes = node.children.compact.select { |n| n.type == :defs }
+ defs_nodes.each { |n| check_defs(n) }
+ end
+ end
+
+ def check_defs(node)
+ definee, method_name, _args, _body = *node
+
+ if definee.type == :const
+ _, class_name = *definee
+ add_offense(definee, :name,
+ message(class_name, method_name))
+ end
+ end
+
+ def message(class_name, method_name)
+ format(MSG, method_name, class_name, method_name)
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ corrector.replace(node.loc.name, 'self')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/class_vars.rb b/lib/rubocop/cop/style/class_vars.rb
new file mode 100644
index 0000000..725b677
--- /dev/null
+++ b/lib/rubocop/cop/style/class_vars.rb
@@ -0,0 +1,23 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for uses of class variables. Offenses
+ # are signaled only on assignment to class variables to
+ # reduced the number of offenses that would be reported.
+ class ClassVars < Cop
+ MSG = 'Replace class var %s with a class instance var.'
+
+ def on_cvasgn(node)
+ add_offense(node, :name)
+ end
+
+ def message(node)
+ class_var, = *node
+ format(MSG, class_var)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/collection_methods.rb b/lib/rubocop/cop/style/collection_methods.rb
new file mode 100644
index 0000000..b86348d
--- /dev/null
+++ b/lib/rubocop/cop/style/collection_methods.rb
@@ -0,0 +1,73 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for uses of unidiomatic method names
+ # from the Enumerable module.
+ #
+ # The current definition of the check is flawed and should be
+ # enhanced by check for by blocks & procs as arguments of the
+ # methods.
+ class CollectionMethods < Cop
+ MSG = 'Prefer `%s` over `%s`.'
+
+ def on_block(node)
+ method, _args, _body = *node
+
+ check_method_node(method)
+ end
+
+ def on_send(node)
+ _receiver, _method_name, *args = *node
+
+ if args.size == 1 && args.first.type == :block_pass
+ check_method_node(node)
+ end
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ corrector.replace(node.loc.selector,
+ preferred_method(node.loc.selector.source))
+ end
+ end
+
+ private
+
+ def check_method_node(node)
+ _receiver, method_name, *_args = *node
+
+ if preferred_methods[method_name]
+ add_offense(
+ node, :selector,
+ format(MSG,
+ preferred_method(method_name),
+ method_name)
+ )
+ end
+ end
+
+ def preferred_method(method)
+ preferred_methods[method.to_sym]
+ end
+
+ def preferred_methods
+ @preferred_methods ||=
+ begin
+ # Make sure default configuration 'foo' => 'bar' is removed from
+ # the total configuration if there is a 'bar' => 'foo' override.
+ default = default_cop_config['PreferredMethods']
+ merged = cop_config['PreferredMethods']
+ overrides = merged.values - default.values
+ merged.reject { |key, _| overrides.include?(key) }.symbolize_keys
+ end
+ end
+
+ def default_cop_config
+ ConfigLoader.default_configuration[cop_name]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/colon_method_call.rb b/lib/rubocop/cop/style/colon_method_call.rb
new file mode 100644
index 0000000..7b3123c
--- /dev/null
+++ b/lib/rubocop/cop/style/colon_method_call.rb
@@ -0,0 +1,33 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for methods invoked via the :: operator instead
+ # of the . operator (like FileUtils::rmdir instead of FileUtils.rmdir).
+ class ColonMethodCall < Cop
+ MSG = 'Do not use `::` for method calls.'
+
+ def on_send(node)
+ receiver, _method_name, *_args = *node
+
+ # discard methods with nil receivers and op methods(like [])
+ return unless receiver && node.loc.dot && node.loc.dot.is?('::')
+ return if allowed_name(_method_name.to_s)
+
+ add_offense(node, :dot)
+ end
+
+ def allowed_name(method_name)
+ method_name.match(/^[A-Z]/)
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ corrector.replace(node.loc.dot, '.')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/comment_annotation.rb b/lib/rubocop/cop/style/comment_annotation.rb
new file mode 100644
index 0000000..49c0c88
--- /dev/null
+++ b/lib/rubocop/cop/style/comment_annotation.rb
@@ -0,0 +1,37 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks that comment annotation keywords are written according
+ # to guidelines.
+ class CommentAnnotation < Cop
+ include AnnotationComment
+
+ MSG = 'Annotation keywords should be all upper case, followed by a ' \
+ 'colon and a space, then a note describing the problem.'
+
+ def investigate(processed_source)
+ processed_source.comments.each do |comment|
+ margin, first_word, colon, space, note = split_comment(comment)
+ if annotation?(comment) && !correct_annotation?(first_word, colon,
+ space, note)
+ start = comment.loc.expression.begin_pos + margin.length
+ length = first_word.length + (colon || '').length
+ range = Parser::Source::Range.new(processed_source.buffer,
+ start,
+ start + length)
+ add_offense(nil, range)
+ end
+ end
+ end
+
+ private
+
+ def correct_annotation?(first_word, colon, space, note)
+ keyword?(first_word) && (colon && space && note || !colon && !note)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/constant_name.rb b/lib/rubocop/cop/style/constant_name.rb
new file mode 100644
index 0000000..96e6ca1
--- /dev/null
+++ b/lib/rubocop/cop/style/constant_name.rb
@@ -0,0 +1,29 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks whether constant names are written using
+ # SCREAMING_SNAKE_CASE.
+ #
+ # To avoid false positives, it ignores cases in which we cannot know
+ # for certain the type of value that would be assigned to a constant.
+ class ConstantName < Cop
+ MSG = 'Use SCREAMING_SNAKE_CASE for constants.'
+ SNAKE_CASE = /^[\dA-Z_]+$/
+
+ def on_casgn(node)
+ _scope, const_name, value = *node
+
+ # We cannot know the result of method calls like
+ # NewClass = something_that_returns_a_class
+ # It's also ok to assign a class constant another class constant
+ # SomeClass = SomeOtherClass
+ unless value && [:send, :block, :const].include?(value.type)
+ add_offense(node, :name) if const_name !~ SNAKE_CASE
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/cyclomatic_complexity.rb b/lib/rubocop/cop/style/cyclomatic_complexity.rb
new file mode 100644
index 0000000..0e029b0
--- /dev/null
+++ b/lib/rubocop/cop/style/cyclomatic_complexity.rb
@@ -0,0 +1,41 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks that the cyclomatic complexity of methods is not higher
+ # than the configured maximum. The cyclomatic complexity is the number of
+ # linearly independent paths through a method. The algorithm counts
+ # decision points and adds one.
+ #
+ # An if statement (or unless or ?:) increases the complexity by one. An
+ # else branch does not, since it doesn't add a decision point. The &&
+ # operator (or keyword and) can be converted to a nested if statement,
+ # and ||/or is shorthand for a sequence of ifs, so they also add one.
+ # Loops can be said to have an exit condition, so they add one.
+ class CyclomaticComplexity < Cop
+ include CheckMethods
+ include ConfigurableMax
+
+ MSG = 'Cyclomatic complexity for %s is too high. [%d/%d]'
+ DECISION_POINT_NODES = [:if, :while, :until, :for, :rescue, :when,
+ :and, :or]
+
+ private
+
+ def check(node, method_name, *_)
+ complexity = 1
+ on_node(DECISION_POINT_NODES, node) { complexity += 1 }
+
+ max = cop_config['Max']
+ if complexity > max
+ add_offense(node, :keyword,
+ format(MSG, method_name, complexity, max)) do
+ self.max = complexity
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/def_with_parentheses.rb b/lib/rubocop/cop/style/def_with_parentheses.rb
new file mode 100644
index 0000000..51215bd
--- /dev/null
+++ b/lib/rubocop/cop/style/def_with_parentheses.rb
@@ -0,0 +1,33 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for parentheses in the definition of a method,
+ # that does not take any arguments. Both instance and
+ # class/singleton methods are checked.
+ class DefWithParentheses < Cop
+ include CheckMethods
+
+ MSG = "Omit the parentheses in defs when the method doesn't accept " \
+ 'any arguments.'
+
+ def check(node, _method_name, args, _body)
+ start_line = node.loc.keyword.line
+ end_line = node.loc.end.line
+
+ return if start_line == end_line
+
+ add_offense(args, :begin) if args.children == [] && args.loc.begin
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ corrector.remove(node.loc.begin)
+ corrector.remove(node.loc.end)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/deprecated_hash_methods.rb b/lib/rubocop/cop/style/deprecated_hash_methods.rb
new file mode 100644
index 0000000..fd582a1
--- /dev/null
+++ b/lib/rubocop/cop/style/deprecated_hash_methods.rb
@@ -0,0 +1,39 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for uses of the deprecated methods Hash#has_key?
+ # and Hash#has_value?
+ class DeprecatedHashMethods < Cop
+ MSG = '`Hash#%s` is deprecated in favor of `Hash#%s`.'
+
+ DEPRECATED_METHODS = [:has_key?, :has_value?]
+
+ def on_send(node)
+ _receiver, method_name, *args = *node
+
+ if args.size == 1 && DEPRECATED_METHODS.include?(method_name)
+ add_offense(node, :selector,
+ format(MSG,
+ method_name,
+ proper_method_name(method_name)))
+ end
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ corrector.replace(node.loc.selector,
+ proper_method_name(node.loc.selector.source))
+ end
+ end
+
+ private
+
+ def proper_method_name(method_name)
+ method_name.to_s.sub(/has_/, '')
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/documentation.rb b/lib/rubocop/cop/style/documentation.rb
new file mode 100644
index 0000000..b8ad29d
--- /dev/null
+++ b/lib/rubocop/cop/style/documentation.rb
@@ -0,0 +1,74 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for missing top-level documentation of
+ # classes and modules. Classes with no body are exempt from the
+ # check and so are namespace modules - modules that have nothing in
+ # their bodies except classes or other other modules.
+ class Documentation < Cop
+ include AnnotationComment
+
+ MSG = 'Missing top-level %s documentation comment.'
+
+ def investigate(processed_source)
+ ast = processed_source.ast
+ return unless ast
+
+ ast_with_comments = Parser::Source::Comment.associate(
+ ast,
+ processed_source.comments
+ )
+
+ check(ast, ast_with_comments)
+ end
+
+ private
+
+ def check(ast, ast_with_comments)
+ on_node([:class, :module], ast) do |node|
+ case node.type
+ when :class
+ _name, _superclass, body = *node
+ when :module
+ _name, body = *node
+ end
+
+ next if node.type == :class && !body
+ next if namespace?(body)
+ next if associated_comment?(node, ast_with_comments)
+ add_offense(node, :keyword, format(MSG, node.type.to_s))
+ end
+ end
+
+ def namespace?(body_node)
+ return false unless body_node
+
+ case body_node.type
+ when :begin
+ body_node.children.all? do |node|
+ [:class, :module].include?(node.type)
+ end
+ when :class, :module
+ true
+ else
+ false
+ end
+ end
+
+ # Returns true if the node has a comment on the line above it that
+ # isn't an annotation.
+ def associated_comment?(node, ast_with_comments)
+ return false if ast_with_comments[node].empty?
+
+ preceding_comment = ast_with_comments[node].last
+ distance = node.loc.keyword.line - preceding_comment.loc.line
+ return false if distance > 1
+
+ !annotation?(preceding_comment)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/dot_position.rb b/lib/rubocop/cop/style/dot_position.rb
new file mode 100644
index 0000000..cca9ebd
--- /dev/null
+++ b/lib/rubocop/cop/style/dot_position.rb
@@ -0,0 +1,57 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks the . position in multi-line method calls.
+ class DotPosition < Cop
+ include ConfigurableEnforcedStyle
+
+ def on_send(node)
+ return unless node.loc.dot
+
+ if proper_dot_position?(node)
+ correct_style_detected
+ else
+ add_offense(node, :dot) { opposite_style_detected }
+ end
+ end
+
+ private
+
+ def message(node)
+ 'Place the . on the ' +
+ case style
+ when :leading
+ 'next line, together with the method name.'
+ when :trailing
+ 'previous line, together with the method call receiver.'
+ end
+ end
+
+ def proper_dot_position?(node)
+ receiver, _method_name, *_args = *node
+
+ receiver_line = receiver.loc.expression.end.line
+
+ if node.loc.selector
+ selector_line = node.loc.selector.line
+ else
+ # l.(1) has no selector, so we use the opening parenthesis instead
+ selector_line = node.loc.begin.line
+ end
+
+ # receiver and selector are on the same line
+ return true if selector_line == receiver_line
+
+ dot_line = node.loc.dot.line
+
+ case style
+ when :leading then dot_line == selector_line
+ when :trailing then dot_line != selector_line
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/double_negation.rb b/lib/rubocop/cop/style/double_negation.rb
new file mode 100644
index 0000000..e5416d0
--- /dev/null
+++ b/lib/rubocop/cop/style/double_negation.rb
@@ -0,0 +1,40 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for uses of double negation (!!) to convert something
+ # to a boolean value. As this is both cryptic and usually redundant it
+ # should be avoided.
+ #
+ # @example
+ #
+ # # bad
+ # !!something
+ #
+ # # good
+ # !something.nil?
+ class DoubleNegation < Cop
+ MSG = 'Avoid the use of double negation (`!!`).'
+
+ def on_send(node)
+ return unless not_node?(node)
+
+ receiver, _method_name, *_args = *node
+
+ add_offense(node, :selector) if not_node?(receiver)
+ end
+
+ private
+
+ def not_node?(node)
+ _receiver, method_name, *args = *node
+
+ # ! does not take any arguments
+ args.empty? && method_name == :! &&
+ node.loc.selector.is?('!')
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/empty_line_between_defs.rb b/lib/rubocop/cop/style/empty_line_between_defs.rb
new file mode 100644
index 0000000..d160987
--- /dev/null
+++ b/lib/rubocop/cop/style/empty_line_between_defs.rb
@@ -0,0 +1,46 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks whether method definitions are
+ # separated by empty lines.
+ class EmptyLineBetweenDefs < Cop
+ MSG = 'Use empty lines between defs.'
+
+ def on_def(node)
+ if @prev_def_end && (def_start(node) - @prev_def_end) == 1
+ unless @prev_was_single_line && singe_line_def?(node) &&
+ cop_config['AllowAdjacentOneLineDefs']
+ add_offense(node, :keyword)
+ end
+ end
+
+ @prev_def_end = def_end(node)
+ @prev_was_single_line = singe_line_def?(node)
+ end
+
+ private
+
+ def singe_line_def?(node)
+ def_start(node) == def_end(node)
+ end
+
+ def def_start(node)
+ node.loc.keyword.line
+ end
+
+ def def_end(node)
+ node.loc.end.line
+ end
+
+ def autocorrect(node)
+ range = range_with_surrounding_space(node.loc.expression, :left)
+ @corrections << lambda do |corrector|
+ corrector.insert_before(range, "\n")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/empty_lines.rb b/lib/rubocop/cop/style/empty_lines.rb
new file mode 100644
index 0000000..d3238f1
--- /dev/null
+++ b/lib/rubocop/cop/style/empty_lines.rb
@@ -0,0 +1,47 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cops checks for two or more consecutive blank lines.
+ class EmptyLines < Cop
+ MSG = 'Extra blank line detected.'
+ LINE_OFFSET = 2
+
+ def investigate(processed_source)
+ return if processed_source.tokens.empty?
+
+ prev_line = 1
+
+ processed_source.tokens.sort_by { |t| t.pos.line }.each do |token|
+ cur_line = token.pos.line
+
+ line_diff = cur_line - prev_line
+
+ if line_diff > LINE_OFFSET
+ # we need to be wary of comments since they
+ # don't show up in the tokens
+ ((prev_line + 1)...cur_line).each do |line|
+ # we check if the prev and current lines are empty
+ if processed_source[line - 2].empty? &&
+ processed_source[line - 1].empty?
+ range = source_range(processed_source.buffer,
+ processed_source[0...(line - 1)],
+ 0,
+ 1)
+ add_offense(range, range)
+ end
+ end
+ end
+
+ prev_line = cur_line
+ end
+ end
+
+ def autocorrect(range)
+ @corrections << ->(corrector) { corrector.remove(range) }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/empty_lines_around_access_modifier.rb b/lib/rubocop/cop/style/empty_lines_around_access_modifier.rb
new file mode 100644
index 0000000..ab689e8
--- /dev/null
+++ b/lib/rubocop/cop/style/empty_lines_around_access_modifier.rb
@@ -0,0 +1,48 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Access modifiers should be surrounded by blank lines.
+ class EmptyLinesAroundAccessModifier < Cop
+ MSG = 'Keep a blank line before and after `%s`.'
+
+ PRIVATE_NODE = s(:send, nil, :private)
+ PROTECTED_NODE = s(:send, nil, :protected)
+ PUBLIC_NODE = s(:send, nil, :public)
+
+ def on_send(node)
+ return unless modifier_node?(node)
+
+ return if empty_lines_around?(node)
+
+ add_offense(node, :expression)
+ end
+
+ private
+
+ def empty_lines_around?(node)
+ send_line = node.loc.line
+ previous_line = processed_source[send_line - 2]
+ next_line = processed_source[send_line]
+
+ (class_def?(previous_line.lstrip) ||
+ previous_line.blank?) &&
+ next_line.blank?
+ end
+
+ def class_def?(line)
+ %w(class module).any? { |keyword| line.start_with?(keyword) }
+ end
+
+ def message(node)
+ format(MSG, node.loc.selector.source)
+ end
+
+ def modifier_node?(node)
+ [PRIVATE_NODE, PROTECTED_NODE, PUBLIC_NODE].include?(node)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/empty_lines_around_body.rb b/lib/rubocop/cop/style/empty_lines_around_body.rb
new file mode 100644
index 0000000..72a51bd
--- /dev/null
+++ b/lib/rubocop/cop/style/empty_lines_around_body.rb
@@ -0,0 +1,74 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cops checks redundant empty lines around the bodies of classes,
+ # modules & methods.
+ #
+ # @example
+ #
+ # class Test
+ #
+ # def something
+ # ...
+ # end
+ #
+ # end
+ #
+ # def something(arg)
+ #
+ # ...
+ # end
+ #
+ class EmptyLinesAroundBody < Cop
+ include CheckMethods
+
+ MSG_BEG = 'Extra empty line detected at body beginning.'
+ MSG_END = 'Extra empty line detected at body end.'
+
+ def on_class(node)
+ check(node)
+ end
+
+ def on_module(node)
+ check(node)
+ end
+
+ def on_sclass(node)
+ check(node)
+ end
+
+ def autocorrect(range)
+ @corrections << ->(corrector) { corrector.remove(range) }
+ end
+
+ private
+
+ def check(node, *_)
+ start_line = node.loc.keyword.line
+ end_line = node.loc.end.line
+
+ return if start_line == end_line
+
+ check_source(start_line, end_line)
+ end
+
+ def check_source(start_line, end_line)
+ check_line(start_line, MSG_BEG)
+ check_line(end_line - 2, MSG_END) unless end_line - 2 == start_line
+ end
+
+ def check_line(line, msg)
+ if processed_source.lines[line].empty?
+ range = source_range(processed_source.buffer,
+ processed_source[0...line],
+ 0,
+ 1)
+ add_offense(range, range, msg)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/empty_literal.rb b/lib/rubocop/cop/style/empty_literal.rb
new file mode 100644
index 0000000..1108145
--- /dev/null
+++ b/lib/rubocop/cop/style/empty_literal.rb
@@ -0,0 +1,60 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for the use of a method, the result of which
+ # would be a literal, like an empty array, hash or string.
+ class EmptyLiteral < Cop
+ ARR_MSG = 'Use array literal [] instead of Array.new.'
+ HASH_MSG = 'Use hash literal {} instead of Hash.new.'
+ STR_MSG = "Use string literal '' instead of String.new."
+
+ # Empty array node
+ #
+ # (send
+ # (const nil :Array) :new)
+ ARRAY_NODE = s(:send, s(:const, nil, :Array), :new)
+
+ # Empty hash node
+ #
+ # (send
+ # (const nil :Hash) :new)
+ HASH_NODE = s(:send, s(:const, nil, :Hash), :new)
+
+ # Empty string node
+ #
+ # (send
+ # (const nil :String) :new)
+ STR_NODE = s(:send, s(:const, nil, :String), :new)
+
+ def on_send(node)
+ return if part_of_ignored_node?(node)
+
+ case node
+ when ARRAY_NODE
+ add_offense(node, :expression, ARR_MSG)
+ when HASH_NODE
+ add_offense(node, :expression, HASH_MSG)
+ when STR_NODE
+ add_offense(node, :expression, STR_MSG)
+ end
+ end
+
+ # TODO: Check block contents as well.
+ alias_method :on_block, :ignore_node
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ name = case node
+ when ARRAY_NODE then '[]'
+ when HASH_NODE then '{}'
+ when STR_NODE then "''"
+ end
+ corrector.replace(node.loc.expression, name)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/encoding.rb b/lib/rubocop/cop/style/encoding.rb
new file mode 100644
index 0000000..de95a5e
--- /dev/null
+++ b/lib/rubocop/cop/style/encoding.rb
@@ -0,0 +1,30 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks whether the source file has a
+ # utf-8 encoding comment. This check makes sense only
+ # in Ruby 1.9, since in 2.0+ utf-8 is the default source file
+ # encoding.
+ class Encoding < Cop
+ MSG = 'Missing utf-8 encoding comment.'
+
+ def investigate(processed_source)
+ unless RUBY_VERSION >= '2.0.0'
+ line_number = 0
+ line_number += 1 if processed_source[line_number] =~ /^#!/
+ line = processed_source[line_number]
+ unless line =~ /#.*coding\s?[:=]\s?(UTF|utf)-8/
+ add_offense(nil,
+ source_range(processed_source.buffer,
+ processed_source[0...line_number],
+ 0, 1),
+ MSG)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/end_block.rb b/lib/rubocop/cop/style/end_block.rb
new file mode 100644
index 0000000..e219224
--- /dev/null
+++ b/lib/rubocop/cop/style/end_block.rb
@@ -0,0 +1,16 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for END blocks.
+ class EndBlock < Cop
+ MSG = 'Avoid the use of `END` blocks. Use `Kernel#at_exit` instead.'
+
+ def on_postexe(node)
+ add_offense(node, :keyword)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/end_of_line.rb b/lib/rubocop/cop/style/end_of_line.rb
new file mode 100644
index 0000000..d3085a8
--- /dev/null
+++ b/lib/rubocop/cop/style/end_of_line.rb
@@ -0,0 +1,30 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for Windows-style line endings in the source code.
+ class EndOfLine < Cop
+ MSG = 'Carriage return character detected.'
+
+ def investigate(processed_source)
+ buffer = processed_source.buffer
+ original_source = IO.read(buffer.name,
+ encoding: buffer.source.encoding)
+ original_source.lines.each_with_index do |line, index|
+ if line =~ /\r$/
+ add_offense(nil,
+ source_range(buffer,
+ processed_source[0...index],
+ 0, line.length),
+ MSG)
+ # Usually there will be carriage return characters on all or none
+ # of the lines in a file, so we report only one offense.
+ break
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/even_odd.rb b/lib/rubocop/cop/style/even_odd.rb
new file mode 100644
index 0000000..357196c
--- /dev/null
+++ b/lib/rubocop/cop/style/even_odd.rb
@@ -0,0 +1,60 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for places where Fixnum#even? or Fixnum#odd?
+ # should have been used.
+ #
+ # @example
+ #
+ # # bad
+ # if x % 2 == 0
+ #
+ # # good
+ # if x.even?
+ class EvenOdd < Cop
+ MSG_EVEN = 'Replace with `Fixnum#even?`.'
+ MSG_ODD = 'Replace with `Fixnum#odd?`.'
+
+ ZERO = s(:int, 0)
+ ONE = s(:int, 1)
+ TWO = s(:int, 2)
+
+ def on_send(node)
+ receiver, method, args = *node
+
+ return unless [:==, :!=].include?(method)
+ return unless div_by_2?(receiver)
+
+ if args == ZERO
+ add_offense(node,
+ :expression,
+ method == :== ? MSG_EVEN : MSG_ODD)
+ elsif args == ONE
+ add_offense(node,
+ :expression,
+ method == :== ? MSG_ODD : MSG_EVEN)
+ end
+ end
+
+ private
+
+ def div_by_2?(node)
+ return unless node
+
+ # check for scenarios like (x % 2) == 0
+ if node.type == :begin && node.children.size == 1
+ node = node.children.first
+ end
+
+ return unless node.type == :send
+
+ _receiver, method, args = *node
+
+ method == :% && args == TWO
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/file_name.rb b/lib/rubocop/cop/style/file_name.rb
new file mode 100644
index 0000000..c37f76f
--- /dev/null
+++ b/lib/rubocop/cop/style/file_name.rb
@@ -0,0 +1,29 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop makes sure that Ruby source files have snake_case names.
+ class FileName < Cop
+ MSG = 'Use snake_case for source file names.'
+
+ SNAKE_CASE = /^[\da-z_]+$/
+
+ def investigate(processed_source)
+ file_path = processed_source.buffer.name
+
+ return if config.file_to_include?(file_path)
+
+ basename = File.basename(file_path).sub(/\.[^\.]+$/, '')
+
+ unless basename.split('.').all? { |fragment| fragment =~ SNAKE_CASE }
+ add_offense(nil,
+ source_range(processed_source.buffer,
+ processed_source[0..0],
+ 0, 1))
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/final_newline.rb b/lib/rubocop/cop/style/final_newline.rb
new file mode 100644
index 0000000..af28fa9
--- /dev/null
+++ b/lib/rubocop/cop/style/final_newline.rb
@@ -0,0 +1,29 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop enforces the presence of a final newline in each source file.
+ class FinalNewline < Cop
+ MSG = 'Source files should end with a newline (\n).'
+
+ def investigate(processed_source)
+ final_line = processed_source.raw_lines.to_a.last
+
+ unless final_line.nil? || final_line.end_with?("\n")
+ range = source_range(processed_source.buffer,
+ processed_source[0...-1],
+ final_line.length - 1, 1)
+ add_offense(range, range)
+ end
+ end
+
+ def autocorrect(range)
+ @corrections << lambda do |corrector|
+ corrector.insert_after(range, "\n")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/flip_flop.rb b/lib/rubocop/cop/style/flip_flop.rb
new file mode 100644
index 0000000..0520c91
--- /dev/null
+++ b/lib/rubocop/cop/style/flip_flop.rb
@@ -0,0 +1,20 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop looks for uses of flip flop operator
+ class FlipFlop < Cop
+ MSG = 'Avoid the use of flip flop operators.'
+
+ def on_iflipflop(node)
+ add_offense(node, :expression)
+ end
+
+ def on_eflipflop(node)
+ add_offense(node, :expression)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/for.rb b/lib/rubocop/cop/style/for.rb
new file mode 100644
index 0000000..2bea1b3
--- /dev/null
+++ b/lib/rubocop/cop/style/for.rb
@@ -0,0 +1,47 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop looks for uses of the *for* keyword, or *each* method. The
+ # preferred alternative is set in the EnforcedStyle configuration
+ # parameter. An *each* call with a block on a single line is always
+ # allowed, however.
+ class For < Cop
+ include ConfigurableEnforcedStyle
+
+ def on_for(node)
+ if style == :each
+ add_offense(node, :keyword, 'Prefer `each` over `for`.') do
+ opposite_style_detected
+ end
+ else
+ correct_style_detected
+ end
+ end
+
+ def on_block(node)
+ return if block_length(node) == 0
+
+ method, _args, _body = *node
+ return unless method.type == :send
+
+ _receiver, method_name, *args = *method
+ return unless method_name == :each && args.empty?
+
+ if style == :for
+ end_pos = method.loc.expression.end_pos
+ range = Parser::Source::Range.new(processed_source.buffer,
+ end_pos - 'each'.length,
+ end_pos)
+ add_offense(range, range, 'Prefer `for` over `each`.') do
+ opposite_style_detected
+ end
+ else
+ correct_style_detected
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/format_string.rb b/lib/rubocop/cop/style/format_string.rb
new file mode 100644
index 0000000..bab20c3
--- /dev/null
+++ b/lib/rubocop/cop/style/format_string.rb
@@ -0,0 +1,66 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop enforces the use of a single string formatting utility.
+ # Valid options include Kernel#format, Kernel#sprintf and String#%.
+ #
+ # The detection of String#% cannot be implemented in a reliable
+ # manner for all cases, so only two scenarios are considered -
+ # if the first argument is a string literal and if the second
+ # argument is an array literal.
+ class FormatString < Cop
+ include ConfigurableEnforcedStyle
+
+ def on_send(node)
+ add_offense(node, :selector) if offending_node?(node)
+ end
+
+ private
+
+ def offending_node?(node)
+ case style
+ when :format
+ sprintf?(node) || percent?(node)
+ when :sprintf
+ format?(node) || percent?(node)
+ when :percent
+ format?(node) || sprintf?(node)
+ end
+ end
+
+ def format?(node)
+ command?(:format, node)
+ end
+
+ def sprintf?(node)
+ command?(:sprintf, node)
+ end
+
+ def percent?(node)
+ receiver_node, method_name, *arg_nodes = *node
+
+ method_name == :% &&
+ ([:str, :dstr].include?(receiver_node.type) ||
+ arg_nodes[0].type == :array)
+ end
+
+ def message(node)
+ _receiver_node, method_name, *_arg_nodes = *node
+
+ preferred =
+ if style == :percent
+ 'String#%'
+ else
+ style
+ end
+
+ method_name = 'String#%' if method_name == :%
+
+ "Favor `#{preferred}` over `#{method_name}`."
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/global_vars.rb b/lib/rubocop/cop/style/global_vars.rb
new file mode 100644
index 0000000..9fffbd8
--- /dev/null
+++ b/lib/rubocop/cop/style/global_vars.rb
@@ -0,0 +1,74 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cops looks for uses of global variables.
+ # It does not report offenses for built-in global variables.
+ # Built-in global variables are allowed by default. Additionally
+ # users can allow additional variables via the AllowedVariables option.
+ #
+ # Note that backreferences like $1, $2, etc are not global variables.
+ class GlobalVars < Cop
+ MSG = 'Do not introduce global variables.'
+
+ # predefined global variables their English aliases
+ # http://www.zenspider.com/Languages/Ruby/QuickRef.html
+ BUILT_IN_VARS = %w(
+ $: $LOAD_PATH
+ $" $LOADED_FEATURES
+ $0 $PROGRAM_NAME
+ $! $ERROR_INFO
+ $@ $ERROR_POSITION
+ $; $FS $FIELD_SEPARATOR
+ $, $OFS $OUTPUT_FIELD_SEPARATOR
+ $/ $RS $INPUT_RECORD_SEPARATOR
+ $\\ $ORS $OUTPUT_RECORD_SEPARATOR
+ $. $NR $INPUT_LINE_NUMBER
+ $_ $LAST_READ_LINE
+ $> $DEFAULT_OUTPUT
+ $< $DEFAULT_INPUT
+ $$ $PID $PROCESS_ID
+ $? $CHILD_STATUS
+ $~ $LAST_MATCH_INFO
+ $= $IGNORECASE
+ $* $ARGV
+ $& $MATCH
+ $` $PREMATCH
+ $' $POSTMATCH
+ $+ $LAST_PAREN_MATCH
+ $stdin $stdout $stderr
+ $DEBUG $FILENAME $VERBOSE $SAFE
+ $-0 $-a $-d $-F $-i $-I $-l $-p $-v $-w
+ $CLASSPATH $JRUBY_VERSION $JRUBY_REVISION $ENV_JAVA
+ ).map(&:to_sym)
+
+ def user_vars
+ if cop_config['AllowedVariables']
+ cop_config['AllowedVariables'].map(&:to_sym)
+ else
+ []
+ end
+ end
+
+ def allowed_var?(global_var)
+ BUILT_IN_VARS.include?(global_var) || user_vars.include?(global_var)
+ end
+
+ def on_gvar(node)
+ check(node)
+ end
+
+ def on_gvasgn(node)
+ check(node)
+ end
+
+ def check(node)
+ global_var, = *node
+
+ add_offense(node, :name) unless allowed_var?(global_var)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/guard_clause.rb b/lib/rubocop/cop/style/guard_clause.rb
new file mode 100644
index 0000000..8e21756
--- /dev/null
+++ b/lib/rubocop/cop/style/guard_clause.rb
@@ -0,0 +1,69 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop if/unless expression that can be replace with a guard clause.
+ #
+ # @example
+ #
+ # # bad
+ # def test
+ # if something
+ # work
+ # work
+ # work
+ # end
+ # end
+ #
+ # # good
+ # def test
+ # return unless something
+ # work
+ # work
+ # work
+ # end
+ #
+ # It should be extended to handle methods whose body is if/else
+ # or a case expression with a default branch.
+ class GuardClause < Cop
+ include CheckMethods
+ include IfNode
+
+ MSG = 'Use a guard clause instead of wrapping ' \
+ 'the code inside a conditional expression.'
+
+ private
+
+ def check(_node, _method_name, _args, body)
+ return unless body
+
+ if body.type == :if
+ check_if_node(body)
+ elsif body.type == :begin
+ expressions = *body
+ last_expr = expressions.last
+
+ check_if_node(last_expr) if last_expr && last_expr.type == :if
+ end
+ end
+
+ def check_if_node(node)
+ _cond, _body, else_body = *node
+
+ return if else_body
+ # discard modifier ifs and ternary_ops
+ return if modifier_if?(node) || ternary_op?(node)
+ # discard short ifs
+ return unless if_length(node) > 3
+
+ add_offense(node, :keyword)
+ end
+
+ def if_length(node)
+ node.loc.end.line - node.loc.keyword.line + 1
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/hash_syntax.rb b/lib/rubocop/cop/style/hash_syntax.rb
new file mode 100644
index 0000000..60f3018
--- /dev/null
+++ b/lib/rubocop/cop/style/hash_syntax.rb
@@ -0,0 +1,89 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks hash literal syntax.
+ #
+ # It can enforce either the use of the class hash rocket syntax or
+ # the use of the newer Ruby 1.9 syntax (when applicable).
+ #
+ # A separate offense is registered for each problematic pair.
+ class HashSyntax < Cop
+ include ConfigurableEnforcedStyle
+
+ MSG_19 = 'Use the new Ruby 1.9 hash syntax.'
+ MSG_HASH_ROCKETS = 'Always use hash rockets in hashes.'
+
+ def on_hash(node)
+ style == :ruby19 ? ruby19_check(node) : hash_rockets_check(node)
+ end
+
+ def ruby19_check(node)
+ pairs = *node
+
+ sym_indices = pairs.all? { |p| word_symbol_pair?(p) }
+
+ check(pairs, '=>', MSG_19) if sym_indices
+ end
+
+ def hash_rockets_check(node)
+ pairs = *node
+
+ check(pairs, ':', MSG_HASH_ROCKETS)
+ end
+
+ def autocorrect(node)
+ key = node.children.first.loc.expression
+ op = node.loc.operator
+
+ @corrections << lambda do |corrector|
+ if style == :ruby19
+ range = Parser::Source::Range.new(key.source_buffer,
+ key.begin_pos, op.end_pos)
+ range = range_with_surrounding_space(range, :right)
+ corrector.replace(range,
+ range.source.sub(/^:(.*\S)\s*=>\s*$/, '\1: '))
+ else
+ corrector.insert_after(key, ' => ')
+ corrector.insert_before(key, ':')
+ corrector.remove(range_with_surrounding_space(op))
+ end
+ end
+ end
+
+ private
+
+ def space_before_operator?(op, key)
+ op.begin_pos - key.begin_pos - key.source.length > 0
+ end
+
+ def check(pairs, delim, msg)
+ pairs.each do |pair|
+ if pair.loc.operator && pair.loc.operator.is?(delim)
+ add_offense(pair,
+ pair.loc.expression.begin.join(pair.loc.operator),
+ msg) do
+ opposite_style_detected
+ end
+ else
+ correct_style_detected
+ end
+ end
+ end
+
+ def word_symbol_pair?(pair)
+ key, _value = *pair
+
+ if key.type == :sym
+ sym_name = key.to_a[0]
+
+ sym_name =~ /\A[A-Za-z_]\w*\z/
+ else
+ false
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/if_unless_modifier.rb b/lib/rubocop/cop/style/if_unless_modifier.rb
new file mode 100644
index 0000000..59812fd
--- /dev/null
+++ b/lib/rubocop/cop/style/if_unless_modifier.rb
@@ -0,0 +1,35 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Checks for if and unless statements that would fit on one line
+ # if written as a modifier if/unless.
+ # The maximum line length is configurable.
+ class IfUnlessModifier < Cop
+ include StatementModifier
+
+ def error_message(keyword)
+ "Favor modifier `#{keyword}` usage when having a single-line body." \
+ ' Another good alternative is the usage of control flow `&&`/`||`.'
+ end
+
+ def investigate(processed_source)
+ return unless processed_source.ast
+ on_node(:if, processed_source.ast) do |node|
+ # discard ternary ops, if/else and modifier if/unless nodes
+ next if ternary_op?(node)
+ next if modifier_if?(node)
+ next if elsif?(node)
+ next if if_else?(node)
+
+ if check(node, processed_source.comments)
+ add_offense(node, :keyword,
+ error_message(node.loc.keyword.source))
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/if_with_semicolon.rb b/lib/rubocop/cop/style/if_with_semicolon.rb
new file mode 100644
index 0000000..739da7d
--- /dev/null
+++ b/lib/rubocop/cop/style/if_with_semicolon.rb
@@ -0,0 +1,20 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Checks for uses of semicolon in if statements.
+ class IfWithSemicolon < Cop
+ include IfThenElse
+
+ def offending_line(node)
+ node.loc.begin.line if node.loc.begin && node.loc.begin.is?(';')
+ end
+
+ def error_message(_node)
+ 'Never use if x; Use the ternary operator instead.'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/indent_array.rb b/lib/rubocop/cop/style/indent_array.rb
new file mode 100644
index 0000000..d5019b5
--- /dev/null
+++ b/lib/rubocop/cop/style/indent_array.rb
@@ -0,0 +1,41 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cops checks the indentation of the first element in an array
+ # literal where the opening bracket and the first element are on separate
+ # lines. The other elements' indentations are handled by the AlignArray
+ # cop.
+ #
+ # Array literals shall have their first element indented one step (2
+ # spaces) more than the start of the line where the opening bracket is.
+ class IndentArray < Cop
+ include AutocorrectAlignment
+
+ def on_array(node)
+ first_pair = node.children.first
+ return if first_pair.nil?
+
+ left_bracket = node.loc.begin
+ return if left_bracket.nil?
+
+ return if first_pair.loc.expression.line == left_bracket.line
+
+ column = first_pair.loc.expression.column
+ base_column = left_bracket.source_line =~ /\S/
+ expected_column = base_column + IndentationWidth::CORRECT_INDENTATION
+ @column_delta = expected_column - column
+
+ add_offense(first_pair, :expression) if @column_delta != 0
+ end
+
+ def message(_)
+ format('Use %d spaces for indentation in an array, relative to ' \
+ 'the start of the line where the left bracket is.',
+ IndentationWidth::CORRECT_INDENTATION)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/indent_hash.rb b/lib/rubocop/cop/style/indent_hash.rb
new file mode 100644
index 0000000..8f0b2ab
--- /dev/null
+++ b/lib/rubocop/cop/style/indent_hash.rb
@@ -0,0 +1,144 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cops checks the indentation of the first key in a hash literal
+ # where the opening brace and the first key are on separate lines. The
+ # other keys' indentations are handled by the AlignHash cop.
+ #
+ # Hash literals that are arguments in a method call with parentheses, and
+ # where the opening curly brace of the hash is on the same line as the
+ # opening parenthesis of the method call, shall have their first key
+ # indented one step (two spaces) more than the position inside the
+ # opening parenthesis.
+ #
+ # Other hash literals shall have their first key indented one step more
+ # than the start of the line where the opening curly brace is.
+ class IndentHash < Cop
+ include AutocorrectAlignment
+ include ConfigurableEnforcedStyle
+
+ def on_hash(node)
+ left_brace = node.loc.begin
+ check(node, left_brace, nil) if left_brace
+ end
+
+ def on_send(node)
+ _receiver, _method_name, *args = *node
+ left_parenthesis = node.loc.begin
+ return unless left_parenthesis
+
+ args.each do |arg|
+ on_node(:hash, arg, :send) do |hash_node|
+ left_brace = hash_node.loc.begin
+ if left_brace && left_brace.line == left_parenthesis.line
+ check(hash_node, left_brace, left_parenthesis)
+ ignore_node(hash_node)
+ end
+ end
+ end
+ end
+
+ private
+
+ def check(hash_node, left_brace, left_parenthesis)
+ return if ignored_node?(hash_node)
+
+ first_pair = hash_node.children.first
+ return if first_pair.nil?
+
+ left_brace = hash_node.loc.begin
+ return if first_pair.loc.expression.line == left_brace.line
+
+ if separator_style?(first_pair)
+ check_based_on_longest_key(hash_node.children, left_brace,
+ left_parenthesis)
+ else
+ check_first_pair(first_pair, left_brace, left_parenthesis, 0)
+ end
+ end
+
+ def separator_style?(first_pair)
+ separator = first_pair.loc.operator
+ key = "Enforced#{separator.is?(':') ? 'Colon' : 'HashRocket'}Style"
+ config.for_cop('AlignHash')[key] == 'separator'
+ end
+
+ def check_based_on_longest_key(pairs, left_brace, left_parenthesis)
+ key_lengths = pairs.map do |pair|
+ pair.children.first.loc.expression.length
+ end
+ check_first_pair(pairs.first, left_brace, left_parenthesis,
+ key_lengths.max - key_lengths.first)
+ end
+
+ def check_first_pair(first_pair, left_brace, left_parenthesis, offset)
+ column = first_pair.loc.expression.column
+ @column_delta =
+ expected_column(left_brace, left_parenthesis, offset) - column
+
+ if @column_delta == 0
+ correct_style_detected
+ else
+ add_offense(first_pair, :expression,
+ message(base_description(left_parenthesis))) do
+ if column == unexpected_column(left_brace, left_parenthesis,
+ offset)
+ opposite_style_detected
+ else
+ unrecognized_style_detected
+ end
+ end
+ end
+ end
+
+ # Returns the expected, or correct indentation.
+ def expected_column(left_brace, left_parenthesis, offset)
+ base_column =
+ if left_parenthesis && style == :special_inside_parentheses
+ left_parenthesis.column + 1
+ else
+ left_brace.source_line =~ /\S/
+ end
+
+ base_column + IndentationWidth::CORRECT_INDENTATION + offset
+ end
+
+ # Returns the description of what the correct indentation is based on.
+ def base_description(left_parenthesis)
+ if left_parenthesis && style == :special_inside_parentheses
+ 'the first position after the preceding left parenthesis'
+ else
+ 'the start of the line where the left curly brace is'
+ end
+ end
+
+ # Returns the "unexpected column", which is the column that would be
+ # correct if the configuration was changed.
+ def unexpected_column(left_brace, left_parenthesis, offset)
+ # Set a crazy value by default, indicating that there's no other
+ # configuration that can be chosen to make the used indentation
+ # accepted.
+ unexpected_base_column = -1000
+
+ if left_parenthesis
+ unexpected_base_column = if style == :special_inside_parentheses
+ left_brace.source_line =~ /\S/
+ else
+ left_parenthesis.column + 1
+ end
+ end
+
+ unexpected_base_column + IndentationWidth::CORRECT_INDENTATION +
+ offset
+ end
+
+ def message(base_description)
+ format('Use %d spaces for indentation in a hash, relative to %s.',
+ IndentationWidth::CORRECT_INDENTATION, base_description)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/indentation_consistency.rb b/lib/rubocop/cop/style/indentation_consistency.rb
new file mode 100644
index 0000000..29d4297
--- /dev/null
+++ b/lib/rubocop/cop/style/indentation_consistency.rb
@@ -0,0 +1,43 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cops checks for inconsistent indentation.
+ #
+ # @example
+ #
+ # class A
+ # def test
+ # puts 'hello'
+ # puts 'world'
+ # end
+ # end
+ class IndentationConsistency < Cop
+ include AutocorrectAlignment
+
+ MSG = 'Inconsistent indentation detected.'
+
+ def on_begin(node)
+ check(node)
+ end
+
+ def on_kwbegin(node)
+ check(node)
+ end
+
+ private
+
+ def check(node)
+ children_to_check = node.children.reject do |child|
+ # Don't check nodes that have special indentation and will be
+ # checked by the AccessModifierIndentation cop.
+ AccessModifierIndentation.modifier_node?(child)
+ end
+
+ check_alignment(children_to_check)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/indentation_width.rb b/lib/rubocop/cop/style/indentation_width.rb
new file mode 100644
index 0000000..803072c
--- /dev/null
+++ b/lib/rubocop/cop/style/indentation_width.rb
@@ -0,0 +1,177 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cops checks for indentation that doesn't use two spaces.
+ #
+ # @example
+ #
+ # class A
+ # def test
+ # puts 'hello'
+ # end
+ # end
+ class IndentationWidth < Cop
+ include AutocorrectAlignment
+ include CheckMethods
+ include CheckAssignment
+ include IfNode
+
+ CORRECT_INDENTATION = 2
+
+ def on_kwbegin(node)
+ check_indentation(node.loc.end, node.children.first)
+ end
+
+ def on_block(node)
+ _method, _args, body = *node
+ # Check body against end/} indentation. Checking against variable
+ # assignments, etc, would be more difficult.
+ check_indentation(node.loc.end, body)
+ end
+
+ def on_module(node)
+ _module_name, *members = *node
+ members.each { |m| check_indentation(node.loc.keyword, m) }
+ end
+
+ def on_class(node)
+ _class_name, _base_class, *members = *node
+ members.each { |m| check_indentation(node.loc.keyword, m) }
+ end
+
+ def check(node, _method_name, _args, body)
+ check_indentation(node.loc.keyword, body)
+ end
+
+ def on_for(node)
+ _variable, _collection, body = *node
+ check_indentation(node.loc.keyword, body)
+ end
+
+ def on_while(node, base = node)
+ _condition, body = *node
+ if node.loc.keyword.begin_pos == node.loc.expression.begin_pos
+ check_indentation(base.loc, body)
+ end
+ end
+
+ alias_method :on_until, :on_while
+
+ def on_case(node)
+ _condition, *branches = *node
+ latest_when = nil
+ branches.compact.each do |b|
+ if b.type == :when
+ _condition, body = *b
+ # Check "when" body against "when" keyword indentation.
+ check_indentation(b.loc.keyword, body)
+ latest_when = b
+ else
+ # Since it's not easy to get the position of the "else" keyword,
+ # we check "else" body against latest "when" keyword indentation.
+ check_indentation(latest_when.loc.keyword, b)
+ end
+ end
+ end
+
+ def on_if(node, base = node)
+ return if ignored_node?(node)
+ return if ternary_op?(node)
+ return if modifier_if?(node)
+
+ case node.loc.keyword.source
+ when 'if' then _condition, body, else_clause = *node
+ when 'unless' then _condition, else_clause, body = *node
+ else _condition, body = *node
+ end
+
+ check_if(node, body, else_clause, base.loc) if body
+ end
+
+ private
+
+ def check_assignment(node, rhs)
+ # If there are method calls chained to the right hand side of the
+ # assignment, we let rhs be the receiver of those method calls before
+ # we check its indentation.
+ rhs = first_part_of_call_chain(rhs)
+
+ if rhs
+ end_alignment_config = config.for_cop('EndAlignment')
+ style = if end_alignment_config['Enabled']
+ end_alignment_config['AlignWith']
+ else
+ 'keyword'
+ end
+ base = style == 'variable' ? node : rhs
+
+ case rhs.type
+ when :if then on_if(rhs, base)
+ when :while, :until then on_while(rhs, base)
+ else return
+ end
+
+ ignore_node(rhs)
+ end
+ end
+
+ def check_if(node, body, else_clause, base_loc)
+ return if ternary_op?(node)
+
+ check_indentation(base_loc, body)
+
+ if else_clause
+ if elsif?(else_clause)
+ _condition, inner_body, inner_else_clause = *else_clause
+ check_if(else_clause, inner_body, inner_else_clause, base_loc)
+ else
+ check_indentation(node.loc.else, else_clause)
+ end
+ end
+ end
+
+ def check_indentation(base_loc, body_node)
+ return unless body_node
+
+ # Don't check if expression is on same line as "then" keyword, etc.
+ return if body_node.loc.line == base_loc.line
+
+ return if starts_with_access_modifier?(body_node)
+
+ # Don't check indentation if the line doesn't start with the body.
+ # For example lines like "else do_something".
+ first_char_pos_on_line = body_node.loc.expression.source_line =~ /\S/
+ return unless body_node.loc.column == first_char_pos_on_line
+
+ indentation = body_node.loc.column - base_loc.column
+ @column_delta = CORRECT_INDENTATION - indentation
+ return if @column_delta == 0
+
+ # This cop only auto-corrects the first statement in a def body, for
+ # example.
+ body_node = body_node.children.first if body_node.type == :begin
+
+ expr = body_node.loc.expression
+ pos = if indentation >= 0
+ (expr.begin_pos - indentation)..expr.begin_pos
+ else
+ expr.begin_pos..(expr.begin_pos - indentation)
+ end
+
+ add_offense(body_node,
+ Parser::Source::Range.new(expr.source_buffer,
+ pos.begin, pos.end),
+ format("Use #{CORRECT_INDENTATION} (not %d) spaces " \
+ 'for indentation.', indentation))
+ end
+
+ def starts_with_access_modifier?(body_node)
+ body_node.type == :begin &&
+ AccessModifierIndentation.modifier_node?(body_node.children.first)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/lambda.rb b/lib/rubocop/cop/style/lambda.rb
new file mode 100644
index 0000000..26c1c12
--- /dev/null
+++ b/lib/rubocop/cop/style/lambda.rb
@@ -0,0 +1,45 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for uses of the pre 1.9 lambda syntax for one-line
+ # anonymous functions and uses of the 1.9 lambda syntax for multi-line
+ # anonymous functions.
+ class Lambda < Cop
+ SINGLE_MSG = 'Use the new lambda literal syntax `->(params) {...}`.'
+ MULTI_MSG = 'Use the `lambda` method for multi-line lambdas.'
+
+ TARGET = s(:send, nil, :lambda)
+
+ def on_block(node)
+ # We're looking for
+ # (block
+ # (send nil :lambda)
+ # ...)
+ block_method, = *node
+
+ if block_method == TARGET
+ selector = block_method.loc.selector.source
+ lambda_length = lambda_length(node)
+
+ if selector != '->' && lambda_length == 0
+ add_offense(block_method, :expression, SINGLE_MSG)
+ elsif selector == '->' && lambda_length > 0
+ add_offense(block_method, :expression, MULTI_MSG)
+ end
+ end
+ end
+
+ private
+
+ def lambda_length(block_node)
+ start_line = block_node.loc.begin.line
+ end_line = block_node.loc.end.line
+
+ end_line - start_line
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/lambda_call.rb b/lib/rubocop/cop/style/lambda_call.rb
new file mode 100644
index 0000000..343c578
--- /dev/null
+++ b/lib/rubocop/cop/style/lambda_call.rb
@@ -0,0 +1,63 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for use of the lambda.(args) syntax.
+ #
+ # @example
+ #
+ # # bad
+ # lambda.(x, y)
+ #
+ # # good
+ # lambda.call(x, y)
+ class LambdaCall < Cop
+ include ConfigurableEnforcedStyle
+
+ def on_send(node)
+ _receiver, selector, = *node
+
+ # we care only about `call` methods
+ return unless selector == :call
+
+ if offense?(node)
+ add_offense(node, :expression) { opposite_style_detected }
+ else
+ correct_style_detected
+ end
+ end
+
+ private
+
+ def offense?(node)
+ # lambda.() does not have a selector
+ style == :call && node.loc.selector.nil? ||
+ style == :braces && node.loc.selector
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ if style == :call
+ receiver_node, = *node
+ expr = node.loc.expression
+ receiver = receiver_node.loc.expression.source
+ replacement = expr.source.sub("#{receiver}.", "#{receiver}.call")
+ corrector.replace(expr, replacement)
+ else
+ corrector.remove(node.loc.selector)
+ end
+ end
+ end
+
+ def message(node)
+ if style == :call
+ 'Prefer the use of `lambda.call(...)` over `lambda.(...)`.'
+ else
+ 'Prefer the use of `lambda.(...)` over `lambda.call(...)`.'
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/leading_comment_space.rb b/lib/rubocop/cop/style/leading_comment_space.rb
new file mode 100644
index 0000000..f6451d0
--- /dev/null
+++ b/lib/rubocop/cop/style/leading_comment_space.rb
@@ -0,0 +1,34 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks whether comments have a leading space
+ # after the # denoting the start of the comment. The
+ # leading space is not required for some RDoc special syntax,
+ # like #++, #--, #:nodoc, etc.
+ class LeadingCommentSpace < Cop
+ MSG = 'Missing space after #.'
+
+ def investigate(processed_source)
+ processed_source.comments.each do |comment|
+ if comment.text =~ /^#+[^#\s:+-]/
+ unless comment.text.start_with?('#!') && comment.loc.line == 1
+ add_offense(comment, :expression)
+ end
+ end
+ end
+ end
+
+ def autocorrect(comment)
+ expr = comment.loc.expression
+ b = expr.begin_pos
+ hash_mark = Parser::Source::Range.new(expr.source_buffer, b, b + 1)
+ @corrections << lambda do |corrector|
+ corrector.insert_after(hash_mark, ' ')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/line_end_concatenation.rb b/lib/rubocop/cop/style/line_end_concatenation.rb
new file mode 100644
index 0000000..cf14078
--- /dev/null
+++ b/lib/rubocop/cop/style/line_end_concatenation.rb
@@ -0,0 +1,66 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for string literal concatenation at
+ # the end of a line.
+ #
+ # @example
+ #
+ # # bad
+ # some_str = 'ala' +
+ # 'bala'
+ #
+ # some_str = 'ala' <<
+ # 'bala'
+ #
+ # # good
+ # some_str = 'ala' \
+ # 'bala'
+ #
+ class LineEndConcatenation < Cop
+ MSG = 'Use \\ instead of + to concatenate those strings.'
+
+ def on_send(node)
+ add_offense(node, :selector) if offending_node?(node)
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ # replace + with \
+ corrector.replace(node.loc.selector, '\\')
+ end
+ end
+
+ private
+
+ def offending_node?(node)
+ receiver, method, arg = *node
+
+ # TODO: Report Emacs bug.
+ return false unless [:+, :<<].include?(method)
+
+ return false unless string_type?(receiver)
+
+ return false unless string_type?(arg)
+
+ expression = node.loc.expression.source
+ concatenator_at_line_end?(expression)
+ end
+
+ def concatenator_at_line_end?(expression)
+ # check if the first line of the expression ends with a + or a <<
+ expression =~ /.+(\+|<<)\s*$/
+ end
+
+ def string_type?(node)
+ return false unless [:str, :dstr].include?(node.type)
+
+ # we care only about quotes-delimited literals
+ node.loc.begin && ["'", '"'].include?(node.loc.begin.source)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/line_length.rb b/lib/rubocop/cop/style/line_length.rb
new file mode 100644
index 0000000..6b82bdc
--- /dev/null
+++ b/lib/rubocop/cop/style/line_length.rb
@@ -0,0 +1,34 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks the length of lines in the source code.
+ # The maximum length is configurable.
+ class LineLength < Cop
+ include ConfigurableMax
+
+ MSG = 'Line is too long. [%d/%d]'
+
+ def investigate(processed_source)
+ processed_source.lines.each_with_index do |line, index|
+ if line.length > max
+ message = format(MSG, line.length, max)
+ add_offense(nil,
+ source_range(processed_source.buffer,
+ processed_source[0...index], max,
+ line.length - max),
+ message) do
+ self.max = line.length
+ end
+ end
+ end
+ end
+
+ def max
+ cop_config['Max']
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/method_call_parentheses.rb b/lib/rubocop/cop/style/method_call_parentheses.rb
new file mode 100644
index 0000000..9ecfc83
--- /dev/null
+++ b/lib/rubocop/cop/style/method_call_parentheses.rb
@@ -0,0 +1,35 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for unwanted parentheses in parameterless method calls.
+ class MethodCallParentheses < Cop
+ MSG = 'Do not use parentheses for method calls with no arguments.'
+
+ def on_send(node)
+ _receiver, method_name, *args = *node
+
+ # methods starting with a capital letter should be skipped
+ return if method_name =~ /\A[A-Z]/
+
+ add_offense(node, :begin) if args.empty? && node.loc.begin
+ end
+
+ def autocorrect(node)
+ # Bail out if the call is going to be auto-corrected by EmptyLiteral.
+ if config.for_cop('EmptyLiteral')['Enabled'] &&
+ [EmptyLiteral::HASH_NODE,
+ EmptyLiteral::ARRAY_NODE,
+ EmptyLiteral::STR_NODE].include?(node)
+ return
+ end
+ @corrections << lambda do |corrector|
+ corrector.remove(node.loc.begin)
+ corrector.remove(node.loc.end)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/method_called_on_do_end_block.rb b/lib/rubocop/cop/style/method_called_on_do_end_block.rb
new file mode 100644
index 0000000..96daf35
--- /dev/null
+++ b/lib/rubocop/cop/style/method_called_on_do_end_block.rb
@@ -0,0 +1,41 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for methods called on a do...end block. The point of
+ # this check is that it's easy to miss the call tacked on to the block
+ # when reading code.
+ #
+ # @example
+ #
+ # a do
+ # b
+ # end.c
+ class MethodCalledOnDoEndBlock < Cop
+ MSG = 'Avoid chaining a method call on a do...end block.'
+
+ def on_block(node)
+ method, _args, _body = *node
+ # If the method that is chained on the do...end block is itself a
+ # method with a block, we allow it. It's pretty safe to assume that
+ # these calls are not missed by anyone reading code. We also want to
+ # avoid double reporting of offenses checked by the
+ # MultilineBlockChain cop.
+ ignore_node(method)
+ end
+
+ def on_send(node)
+ return if ignored_node?(node)
+ receiver, _method_name, *_args = *node
+ if receiver && receiver.type == :block && receiver.loc.end.is?('end')
+ range = Parser::Source::Range.new(receiver.loc.end.source_buffer,
+ receiver.loc.end.begin_pos,
+ node.loc.expression.end_pos)
+ add_offense(nil, range)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/method_def_parentheses.rb b/lib/rubocop/cop/style/method_def_parentheses.rb
new file mode 100644
index 0000000..c3b0393
--- /dev/null
+++ b/lib/rubocop/cop/style/method_def_parentheses.rb
@@ -0,0 +1,72 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cops checks for parentheses around the arguments in method
+ # definitions. Both instance and class/singleton methods are checked.
+ class MethodDefParentheses < Cop
+ include CheckMethods
+ include ConfigurableEnforcedStyle
+
+ def check(node, _method_name, args, _body)
+ if style == :require_parentheses
+ if arguments?(args) && !parentheses?(args)
+ add_offense(node,
+ args.loc.expression,
+ 'Use def with parentheses when there are ' \
+ 'parameters.') do
+ opposite_style_detected
+ end
+ else
+ correct_style_detected
+ end
+ elsif parentheses?(args)
+ add_offense(args, :expression, 'Use def without parentheses.') do
+ opposite_style_detected
+ end
+ else
+ correct_style_detected
+ end
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ if style == :require_parentheses
+ args_expr = args_node(node).loc.expression
+ args_with_space = range_with_surrounding_space(args_expr, :left)
+ just_space = Parser::Source::Range.new(args_expr.source_buffer,
+ args_with_space.begin_pos,
+ args_expr.begin_pos)
+ corrector.replace(just_space, '(')
+ corrector.insert_after(args_expr, ')')
+ elsif style == :require_no_parentheses
+ corrector.replace(node.loc.begin, ' ')
+ corrector.remove(node.loc.end)
+ end
+ end
+ end
+
+ private
+
+ def args_node(def_node)
+ if def_node.type == :def
+ _method_name, args, _body = *def_node
+ args
+ else
+ _scope, _method_name, args, _body = *def_node
+ args
+ end
+ end
+
+ def arguments?(args)
+ args.children.size > 0
+ end
+
+ def parentheses?(args)
+ args.loc.begin
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/method_length.rb b/lib/rubocop/cop/style/method_length.rb
new file mode 100644
index 0000000..9846a90
--- /dev/null
+++ b/lib/rubocop/cop/style/method_length.rb
@@ -0,0 +1,29 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks if the length a method exceeds some maximum value.
+ # Comment lines can optionally be ignored.
+ # The maximum allowed length is configurable.
+ class MethodLength < Cop
+ include CheckMethods
+ include CodeLength
+
+ private
+
+ def message
+ 'Method has too many lines. [%d/%d]'
+ end
+
+ def code_length(node)
+ lines = node.loc.expression.source.lines.to_a[1..-2] || []
+
+ lines.reject! { |line| irrelevant_line(line) }
+
+ lines.size
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/method_name.rb b/lib/rubocop/cop/style/method_name.rb
new file mode 100644
index 0000000..44636bd
--- /dev/null
+++ b/lib/rubocop/cop/style/method_name.rb
@@ -0,0 +1,42 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop makes sure that all methods use the configured style,
+ # snake_case or camelCase, for their names. Some special arrangements
+ # have to be made for operator methods.
+ class MethodName < Cop
+ include ConfigurableNaming
+
+ def on_def(node)
+ check(node, name_of_instance_method(node))
+ end
+
+ def on_defs(node)
+ check(node, name_of_singleton_method(node))
+ end
+
+ def name_of_instance_method(def_node)
+ expr = def_node.loc.expression
+ match = /^def(\s+)([\w]+[!?=]?\b)/.match(expr.source)
+ return unless match
+ space, method_name = match.captures
+ begin_pos = expr.begin_pos + 'def'.length + space.length
+ Parser::Source::Range.new(expr.source_buffer, begin_pos,
+ begin_pos + method_name.length)
+ end
+
+ def name_of_singleton_method(defs_node)
+ scope, method_name, _args, _body = *defs_node
+ after_dot(defs_node, method_name.length,
+ "def\s+" + Regexp.escape(scope.loc.expression.source))
+ end
+
+ def message(style)
+ format('Use %s for methods.', style)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/module_function.rb b/lib/rubocop/cop/style/module_function.rb
new file mode 100644
index 0000000..0b47e21
--- /dev/null
+++ b/lib/rubocop/cop/style/module_function.rb
@@ -0,0 +1,32 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cops checks for use of `extend self` in a module.
+ #
+ # @example
+ #
+ # module Test
+ # extend self
+ #
+ # ...
+ # end
+ class ModuleFunction < Cop
+ MSG = 'Use `module_function` instead of `extend self`.'
+
+ TARGET_NODE = s(:send, nil, :extend, s(:self))
+
+ def on_module(node)
+ _name, body = *node
+
+ if body && body.type == :begin
+ body.children.each do |body_node|
+ add_offense(body_node, :expression) if body_node == TARGET_NODE
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/multiline_block_chain.rb b/lib/rubocop/cop/style/multiline_block_chain.rb
new file mode 100644
index 0000000..dd19391
--- /dev/null
+++ b/lib/rubocop/cop/style/multiline_block_chain.rb
@@ -0,0 +1,44 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for chaining of a block after another block that spans
+ # multiple lines.
+ #
+ # @example
+ #
+ # Thread.list.find_all do |t|
+ # t.alive?
+ # end.map do |t|
+ # t.object_id
+ # end
+ class MultilineBlockChain < Cop
+ MSG = 'Avoid multi-line chains of blocks.'
+
+ def on_block(node)
+ method, _args, _body = *node
+ on_node(:send, method) do |send_node|
+ receiver, _method_name, *_args = *send_node
+ if receiver && receiver.type == :block
+ # The begin and end could also be braces, but we call the
+ # variables do... and end...
+ do_kw_loc, end_kw_loc = receiver.loc.begin, receiver.loc.end
+
+ if do_kw_loc.line != end_kw_loc.line
+ range =
+ Parser::Source::Range.new(end_kw_loc.source_buffer,
+ end_kw_loc.begin_pos,
+ method.loc.expression.end_pos)
+ add_offense(nil, range)
+ # Done. If there are more blocks in the chain, they will be
+ # found by subsequent calls to on_block.
+ break
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/multiline_if_then.rb b/lib/rubocop/cop/style/multiline_if_then.rb
new file mode 100644
index 0000000..5b1fcbb
--- /dev/null
+++ b/lib/rubocop/cop/style/multiline_if_then.rb
@@ -0,0 +1,50 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Checks for uses of the `then` keyword in multi-line if statements.
+ #
+ # @example This is considered bad practice:
+ #
+ # if cond then
+ # end
+ #
+ # @example If statements can contain `then` on the same line:
+ #
+ # if cond then a
+ # elsif cond then b
+ # end
+ class MultilineIfThen < Cop
+ include IfNode
+ include IfThenElse
+
+ def offending_line(node)
+ condition, body, else_clause = *node
+ next_thing = if body && body.loc.expression
+ body.loc.expression.begin
+ elsif else_clause && else_clause.loc.expression
+ else_clause.loc.expression.begin
+ else
+ node.loc.end # No body, use "end".
+ end
+ right_after_cond =
+ Parser::Source::Range.new(next_thing.source_buffer,
+ end_position(condition),
+ next_thing.begin_pos)
+ if right_after_cond.source =~ /\A\s*then\s*(#.*)?\s*\n/
+ node.loc.expression.begin.line
+ end
+ end
+
+ def end_position(conditional_node)
+ conditional_node.loc.expression.end.end_pos
+ end
+
+ def error_message(node)
+ "Never use `then` for multi-line `#{node.loc.keyword.source}`."
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/multiline_ternary_operator.rb b/lib/rubocop/cop/style/multiline_ternary_operator.rb
new file mode 100644
index 0000000..0160b91
--- /dev/null
+++ b/lib/rubocop/cop/style/multiline_ternary_operator.rb
@@ -0,0 +1,22 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for multi-line ternary op expressions.
+ class MultilineTernaryOperator < Cop
+ MSG = 'Avoid multi-line ?: (the ternary operator);' \
+ ' use `if`/`unless` instead.'
+
+ def on_if(node)
+ loc = node.loc
+
+ # discard non-ternary ops
+ return unless loc.respond_to?(:question)
+
+ add_offense(node, :expression) if loc.line != loc.colon.line
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/negated_if.rb b/lib/rubocop/cop/style/negated_if.rb
new file mode 100644
index 0000000..a25e2e4
--- /dev/null
+++ b/lib/rubocop/cop/style/negated_if.rb
@@ -0,0 +1,46 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Checks for uses of if with a negated condition. Only ifs
+ # without else are considered.
+ class NegatedIf < Cop
+ include NegativeConditional
+
+ MSG = 'Favor `%s` over `%s` for negative conditions.'
+
+ def on_if(node)
+ return unless node.loc.respond_to?(:keyword)
+ return if node.loc.keyword.is?('elsif')
+
+ check(node)
+ end
+
+ def message(node)
+ if node.loc.keyword.is?('if')
+ format(MSG, 'unless', 'if')
+ else
+ format(MSG, 'if', 'unless')
+ end
+ end
+
+ private
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ condition, _body, _rest = *node
+ # unwrap the negated portion of the condition (a send node)
+ pos_condition, _method, = *condition
+ corrector.replace(
+ node.loc.keyword,
+ node.loc.keyword.is?('if') ? 'unless' : 'if'
+ )
+ corrector.replace(condition.loc.expression,
+ pos_condition.loc.expression.source)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/negated_while.rb b/lib/rubocop/cop/style/negated_while.rb
new file mode 100644
index 0000000..87ec820
--- /dev/null
+++ b/lib/rubocop/cop/style/negated_while.rb
@@ -0,0 +1,45 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Checks for uses of while with a negated condition.
+ class NegatedWhile < Cop
+ include NegativeConditional
+
+ MSG = 'Favor `%s` over `%s` for negative conditions.'
+
+ def on_while(node)
+ check(node)
+ end
+
+ def on_until(node)
+ check(node)
+ end
+
+ def message(node)
+ if node.type == :while
+ format(MSG, 'until', 'while')
+ else
+ format(MSG, 'while', 'until')
+ end
+ end
+
+ private
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ condition, _body, _rest = *node
+ # unwrap the negated portion of the condition (a send node)
+ pos_condition, _method, = *condition
+ corrector.replace(
+ node.loc.keyword,
+ node.type == :while ? 'until' : 'while')
+ corrector.replace(condition.loc.expression,
+ pos_condition.loc.expression.source)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/nested_ternary_operator.rb b/lib/rubocop/cop/style/nested_ternary_operator.rb
new file mode 100644
index 0000000..5db4e06
--- /dev/null
+++ b/lib/rubocop/cop/style/nested_ternary_operator.rb
@@ -0,0 +1,26 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for nested ternary op expressions.
+ class NestedTernaryOperator < Cop
+ MSG = 'Ternary operators must not be nested. Prefer `if`/`else` ' \
+ 'constructs instead.'
+
+ def on_if(node)
+ loc = node.loc
+
+ # discard non-ternary ops
+ return unless loc.respond_to?(:question)
+
+ node.children.each do |child|
+ on_node(:if, child) do |c|
+ add_offense(c, :expression) if c.loc.respond_to?(:question)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/nil_comparison.rb b/lib/rubocop/cop/style/nil_comparison.rb
new file mode 100644
index 0000000..efb8325
--- /dev/null
+++ b/lib/rubocop/cop/style/nil_comparison.rb
@@ -0,0 +1,42 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for comparison of something with nil using ==.
+ #
+ # @example
+ #
+ # # bad
+ # if x == nil
+ #
+ # # good
+ # if x.nil?
+ class NilComparison < Cop
+ MSG = 'Prefer the use of the `nil?` predicate.'
+
+ OPS = [:==, :===]
+
+ NIL_NODE = s(:nil)
+
+ def on_send(node)
+ _receiver, method, args = *node
+
+ if OPS.include?(method)
+ add_offense(node, :selector) if args == NIL_NODE
+ end
+ end
+
+ private
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ expr = node.loc.expression
+ new_code = expr.source.sub(/\s*={2,3}\s*nil/, '.nil?')
+ corrector.replace(expr, new_code)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/non_nil_check.rb b/lib/rubocop/cop/style/non_nil_check.rb
new file mode 100644
index 0000000..4a218d5
--- /dev/null
+++ b/lib/rubocop/cop/style/non_nil_check.rb
@@ -0,0 +1,104 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for non-nil checks, which are usually redundant.
+ #
+ # @example
+ #
+ # # bad
+ # if x != nil
+ # if !x.nil?
+ #
+ # # good
+ # if x
+ #
+ # Non-nil checks are allowed if they are the final nodes of predicate.
+ #
+ # # good
+ # def signed_in?
+ # !current_user.nil?
+ # end
+ class NonNilCheck < Cop
+ MSG = 'Explicit non-nil checks are usually redundant.'
+
+ NIL_NODE = s(:nil)
+
+ def on_def(node)
+ method_name, _args, body = *node
+ process_method(method_name, body)
+ end
+
+ def on_defs(node)
+ _scope, method_name, _args, body = *node
+ process_method(method_name, body)
+ end
+
+ def on_send(node)
+ return if ignored_node?(node)
+ receiver, method, args = *node
+
+ return unless [:!=, :!].include?(method)
+
+ if method == :!=
+ add_offense(node, :selector) if args == NIL_NODE
+ elsif method == :!
+ add_offense(node, :expression) if nil_check?(receiver)
+ end
+ end
+
+ private
+
+ def process_method(name, body)
+ # only predicate methods are handled differently
+ return unless name.to_s.end_with?('?')
+ return unless body
+
+ if body.type != :begin
+ ignore_node(body)
+ elsif body.type == :begin
+ ignore_node(body.children.last)
+ end
+ end
+
+ def nil_check?(node)
+ return false unless node && node.type == :send
+
+ _receiver, method, *_args = *node
+ method == :nil?
+ end
+
+ def autocorrect(node)
+ receiver, method, _args = *node
+
+ if method == :!=
+ autocorrect_comparison(node)
+ elsif method == :!
+ autocorrect_non_nil(node, receiver)
+ end
+ end
+
+ def autocorrect_comparison(node)
+ @corrections << lambda do |corrector|
+ expr = node.loc.expression
+ new_code = expr.source.sub(/\s*!=\s*nil/, '')
+ corrector.replace(expr, new_code)
+ end
+ end
+
+ def autocorrect_non_nil(node, inner_node)
+ @corrections << lambda do |corrector|
+ receiver, _method, _args = *inner_node
+ if receiver
+ corrector.replace(node.loc.expression,
+ receiver.loc.expression.source)
+ else
+ corrector.replace(node.loc.expression, 'self')
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/not.rb b/lib/rubocop/cop/style/not.rb
new file mode 100644
index 0000000..9b69013
--- /dev/null
+++ b/lib/rubocop/cop/style/not.rb
@@ -0,0 +1,34 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for uses if the keyword *not* instead of !.
+ class Not < Cop
+ include AutocorrectUnlessChangingAST
+
+ MSG = 'Use `!` instead of `not`.'
+
+ def on_send(node)
+ _receiver, method_name, *args = *node
+
+ # not does not take any arguments
+ if args.empty? && method_name == :! &&
+ node.loc.selector.is?('not')
+ add_offense(node, :selector)
+ end
+ end
+
+ private
+
+ def correction(node)
+ lambda do |corrector|
+ old_source = node.loc.expression.source
+ new_source = old_source.sub(/not\s+/, '!')
+ corrector.replace(node.loc.expression, new_source)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/numeric_literals.rb b/lib/rubocop/cop/style/numeric_literals.rb
new file mode 100644
index 0000000..185b9b0
--- /dev/null
+++ b/lib/rubocop/cop/style/numeric_literals.rb
@@ -0,0 +1,73 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for big numeric literals without _ between groups
+ # of digits in them.
+ class NumericLiterals < Cop
+ # The parameter is called MinDigits (meaning the minimum number of
+ # digits for which an offense can be registered), but essentially it's
+ # a Max parameter (the maximum number of something that's allowed).
+ include ConfigurableMax
+
+ MSG = 'Separate every 3 digits in the integer portion of a number ' \
+ 'with underscores(_).'
+
+ def on_int(node)
+ check(node)
+ end
+
+ def on_float(node)
+ check(node)
+ end
+
+ private
+
+ def parameter_name
+ 'MinDigits'
+ end
+
+ def check(node)
+ int = integer_part(node)
+
+ # TODO: handle non-decimal literals as well
+ return if int.start_with?('0')
+
+ if int.size >= min_digits
+ case int
+ when /^\d+$/
+ add_offense(node, :expression) { self.max = int.size + 1 }
+ when /\d{4}/, /_\d{1,2}_/
+ add_offense(node, :expression) do
+ self.config_to_allow_offenses = { 'Enabled' => false }
+ end
+ end
+ end
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ int = node.loc.expression.source.to_i
+ formatted_int = int
+ .abs
+ .to_s
+ .reverse
+ .gsub(/...(?=.)/, '\&_')
+ .reverse
+ formatted_int.insert(0, '-') if int < 0
+ corrector.replace(node.loc.expression, formatted_int)
+ end
+ end
+
+ def integer_part(node)
+ node.loc.expression.source.sub(/^[+-]/, '').split('.').first
+ end
+
+ def min_digits
+ cop_config['MinDigits']
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/one_line_conditional.rb b/lib/rubocop/cop/style/one_line_conditional.rb
new file mode 100644
index 0000000..8c6b805
--- /dev/null
+++ b/lib/rubocop/cop/style/one_line_conditional.rb
@@ -0,0 +1,21 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # TODO: Make configurable.
+ # Checks for uses of if/then/else/end on a single line.
+ class OneLineConditional < Cop
+ include IfThenElse
+
+ def offending_line(node)
+ node.loc.expression.line unless node.loc.expression.source =~ /\n/
+ end
+
+ def error_message(_node)
+ 'Favor the ternary operator (?:) over if/then/else/end constructs.'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/op_method.rb b/lib/rubocop/cop/style/op_method.rb
new file mode 100644
index 0000000..dbb951d
--- /dev/null
+++ b/lib/rubocop/cop/style/op_method.rb
@@ -0,0 +1,27 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop makes sure that certain operator methods have their sole
+ # parameter named `other`.
+ class OpMethod < Cop
+ MSG = 'When defining the `%s` operator, name its argument `other`.'
+
+ BLACKLISTED = [:+@, :-@, :[], :[]=, :<<]
+
+ TARGET_ARGS = [s(:args, s(:arg, :other)), s(:args, s(:arg, :_other))]
+
+ def on_def(node)
+ name, args, _body = *node
+
+ if name !~ /\A\w/ && !BLACKLISTED.include?(name) &&
+ args.children.size == 1 && !TARGET_ARGS.include?(args)
+ add_offense(args.children[0], :expression,
+ format(MSG, name))
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/parameter_lists.rb b/lib/rubocop/cop/style/parameter_lists.rb
new file mode 100644
index 0000000..641c914
--- /dev/null
+++ b/lib/rubocop/cop/style/parameter_lists.rb
@@ -0,0 +1,44 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for methods with too many parameters.
+ # The maximum number of parameters in configurable.
+ # On Ruby 2.0+ keyword arguments can optionally
+ # be excluded from the total count.
+ class ParameterLists < Cop
+ include ConfigurableMax
+
+ MSG = 'Avoid parameter lists longer than %d parameters.'
+
+ def on_args(node)
+ count = args_count(node)
+ if count > max_params
+ add_offense(node, :expression, format(MSG, max_params)) do
+ self.max = count
+ end
+ end
+ end
+
+ private
+
+ def args_count(node)
+ if count_keyword_args?
+ node.children.size
+ else
+ node.children.reject { |a| a.type == :kwoptarg }.size
+ end
+ end
+
+ def max_params
+ cop_config['Max']
+ end
+
+ def count_keyword_args?
+ cop_config['CountKeywordArgs']
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/parentheses_around_condition.rb b/lib/rubocop/cop/style/parentheses_around_condition.rb
new file mode 100644
index 0000000..f44e8de
--- /dev/null
+++ b/lib/rubocop/cop/style/parentheses_around_condition.rb
@@ -0,0 +1,62 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for the presence of superfluous parentheses around the
+ # condition of if/unless/while/until.
+ class ParenthesesAroundCondition < Cop
+ include IfNode
+ include SafeAssignment
+
+ def on_if(node)
+ return if ternary_op?(node)
+ process_control_op(node)
+ end
+
+ def on_while(node)
+ process_control_op(node)
+ end
+
+ def on_until(node)
+ process_control_op(node)
+ end
+
+ private
+
+ def process_control_op(node)
+ cond, _body = *node
+
+ if cond.type == :begin
+ return if parens_required?(node)
+ # allow safe assignment
+ return if safe_assignment?(cond) && safe_assignment_allowed?
+
+ add_offense(cond, :expression, message(node))
+ end
+ end
+
+ def parens_required?(node)
+ exp = node.loc.expression
+ kw = node.loc.keyword
+ kw_offset = kw.begin_pos - exp.begin_pos
+
+ exp.source[kw_offset..-1].start_with?(kw.source + '(')
+ end
+
+ def message(node)
+ kw = node.loc.keyword.source
+ article = kw == 'while' ? 'a' : 'an'
+ "Don't use parentheses around the condition of #{article} `#{kw}`."
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ corrector.remove(node.loc.begin)
+ corrector.remove(node.loc.end)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/percent_literal_delimiters.rb b/lib/rubocop/cop/style/percent_literal_delimiters.rb
new file mode 100644
index 0000000..ee598bf
--- /dev/null
+++ b/lib/rubocop/cop/style/percent_literal_delimiters.rb
@@ -0,0 +1,153 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop enforces the consitent useage of `%`-literal delimiters.
+ class PercentLiteralDelimiters < Cop
+ def on_array(node)
+ process(node, '%w', '%W', '%i')
+ end
+
+ def on_regexp(node)
+ process(node, '%r')
+ end
+
+ def on_str(node)
+ process(node, '%', '%Q', '%q')
+ end
+ alias_method :on_dstr, :on_str
+
+ def on_sym(node)
+ process(node, '%s')
+ end
+
+ def on_xstr(node)
+ process(node, '%x')
+ end
+
+ def message(node)
+ type = type(node)
+ delimiters = preferred_delimiters(type)
+
+ "`#{type}`-literals should be delimited by " \
+ "`#{delimiters[0]}` and `#{delimiters[1]}`"
+ end
+
+ private
+
+ def autocorrect(node)
+ type = type(node)
+
+ opening_delimiter, closing_delimiter = preferred_delimiters(type)
+ opening_newline = new_line(node.loc.begin, node.children.first)
+ closing_newline = new_line(node.loc.end, node.children.last)
+
+ expression, reg_opt = *contents(node)
+ corrected_source =
+ type + opening_delimiter + opening_newline +
+ expression +
+ closing_newline + closing_delimiter + reg_opt
+
+ @corrections << lambda do |corrector|
+ corrector.replace(
+ node.loc.expression,
+ corrected_source
+ )
+ end
+ end
+
+ def process(node, *types)
+ on_percent_literal(node, types) if percent_literal?(node)
+ end
+
+ def percent_literal?(node)
+ if (begin_source = begin_source(node))
+ begin_source.start_with?('%')
+ end
+ end
+
+ def on_percent_literal(node, types)
+ type = type(node)
+ if types.include?(type) &&
+ !uses_preferred_delimiter?(node, type) &&
+ !contains_preferred_delimiter?(node, type)
+ add_offense(node, :expression)
+ end
+ end
+
+ def type(node)
+ node.loc.begin.source[0..-2]
+ end
+
+ def preferred_delimiters(type)
+ cop_config['PreferredDelimiters'][type].split(//)
+ end
+
+ def contents(node)
+ first_child, *middle, last_child = *node
+ last_child ||= first_child
+ if node.type == :regexp
+ *_, next_to_last_child = *middle
+ next_to_last_child ||= first_child
+ [
+ source(node, first_child, next_to_last_child),
+ last_child.loc.expression.source
+ ]
+ else
+ [
+ if first_child.is_a?(Parser::AST::Node)
+ source(node, first_child, last_child)
+ else
+ first_child.to_s
+ end,
+ ''
+ ]
+ end
+ end
+
+ def source(node, begin_node, end_node)
+ Parser::Source::Range.new(
+ node.loc.expression.source_buffer,
+ begin_node.loc.expression.begin_pos,
+ end_node.loc.expression.end_pos
+ ).source
+ end
+
+ def uses_preferred_delimiter?(node, type)
+ preferred_delimiters(type)[0] == begin_source(node)[-1]
+ end
+
+ def contains_preferred_delimiter?(node, type)
+ preferred_delimiters = preferred_delimiters(type)
+ node
+ .children.map { |n| string_source(n) }.compact
+ .any? { |s| preferred_delimiters.any? { |d| s.include?(d) } }
+ end
+
+ def begin_source(node)
+ if node.loc.respond_to?(:begin) && node.loc.begin
+ node.loc.begin.source
+ end
+ end
+
+ def string_source(node)
+ if node.is_a?(String)
+ node
+ elsif node.respond_to?(:type) && node.type == :str
+ node.loc.expression.source
+ end
+ end
+
+ def new_line(range, child_node)
+ same_line?(range, child_node) ? '' : "\n"
+ end
+
+ def same_line?(range, child_node)
+ !child_node.is_a?(Parser::AST::Node) ||
+ range.begin.line == child_node.loc.line
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/perl_backrefs.rb b/lib/rubocop/cop/style/perl_backrefs.rb
new file mode 100644
index 0000000..508d448
--- /dev/null
+++ b/lib/rubocop/cop/style/perl_backrefs.rb
@@ -0,0 +1,26 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop looks for uses of Perl-style regexp match
+ # backreferences like $1, $2, etc.
+ class PerlBackrefs < Cop
+ MSG = 'Avoid the use of Perl-style backrefs.'
+
+ def on_nth_ref(node)
+ add_offense(node, :expression)
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ backref, = *node
+
+ corrector.replace(node.loc.expression,
+ "Regexp.last_match[#{backref}]")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/predicate_name.rb b/lib/rubocop/cop/style/predicate_name.rb
new file mode 100644
index 0000000..f8da241
--- /dev/null
+++ b/lib/rubocop/cop/style/predicate_name.rb
@@ -0,0 +1,46 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop makes sure that predicates are named properly.
+ #
+ # @example
+ # # bad
+ # def is_even?(value) ...
+ #
+ # # good
+ # def even?(value)
+ #
+ # # bad
+ # def has_value? ...
+ #
+ # # good
+ # def value? ...
+ class PredicateName < Cop
+ include CheckMethods
+
+ private
+
+ def check(node, method_name, args, _body)
+ prefix_blacklist.each do |prefix|
+ if method_name.to_s.start_with?(prefix)
+ add_offense(node, :name,
+ message(method_name.to_s, prefix))
+ end
+ end
+ end
+
+ def message(method_name, prefix)
+ new_name = method_name.sub(prefix, '')
+ new_name << '?' unless method_name.end_with?('?')
+ "Rename `#{method_name}` to `#{new_name}`."
+ end
+
+ def prefix_blacklist
+ cop_config['NamePrefixBlacklist']
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/proc.rb b/lib/rubocop/cop/style/proc.rb
new file mode 100644
index 0000000..fcc3c01
--- /dev/null
+++ b/lib/rubocop/cop/style/proc.rb
@@ -0,0 +1,32 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cops checks for uses of Proc.new where Kernel#proc
+ # would be more appropriate.
+ class Proc < Cop
+ MSG = 'Use `proc` instead of `Proc.new`.'
+
+ TARGET = s(:send, s(:const, nil, :Proc), :new)
+
+ def on_block(node)
+ # We're looking for
+ # (block
+ # (send
+ # (const nil :Proc) :new)
+ # ...)
+ block_method, = *node
+
+ add_offense(block_method, :expression) if block_method == TARGET
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ corrector.replace(node.loc.expression, 'proc')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/raise_args.rb b/lib/rubocop/cop/style/raise_args.rb
new file mode 100644
index 0000000..40d582a
--- /dev/null
+++ b/lib/rubocop/cop/style/raise_args.rb
@@ -0,0 +1,68 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks the args passed to `fail` and `raise`.
+ class RaiseArgs < Cop
+ include ConfigurableEnforcedStyle
+
+ def on_send(node)
+ return unless command?(:raise, node) || command?(:fail, node)
+
+ case style
+ when :compact
+ check_compact(node)
+ when :exploded
+ check_exploded(node)
+ end
+ end
+
+ private
+
+ def check_compact(node)
+ _receiver, selector, *args = *node
+
+ if args.size > 1
+ add_offense(node, :expression, message(selector)) do
+ opposite_style_detected
+ end
+ else
+ correct_style_detected
+ end
+ end
+
+ def check_exploded(node)
+ _receiver, selector, *args = *node
+
+ if args.size == 1
+ arg, = *args
+
+ if arg.type == :send && arg.loc.selector.is?('new')
+ _receiver, _selector, *constructor_args = *arg
+
+ # Allow code like `raise Ex.new(arg1, arg2)`.
+ if constructor_args.size <= 1
+ add_offense(node, :expression, message(selector)) do
+ opposite_style_detected
+ end
+ end
+ end
+ else
+ correct_style_detected
+ end
+ end
+
+ def message(method)
+ case style
+ when :compact
+ "Provide an exception object as an argument to `#{method}`."
+ when :exploded
+ "Provide an exception class and message as " \
+ "arguments to `#{method}`."
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/redundant_begin.rb b/lib/rubocop/cop/style/redundant_begin.rb
new file mode 100644
index 0000000..373beb2
--- /dev/null
+++ b/lib/rubocop/cop/style/redundant_begin.rb
@@ -0,0 +1,35 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for redundant `begin` blocks.
+ #
+ # Currently it checks for code like this:
+ #
+ # @example
+ #
+ # def test
+ # begin
+ # ala
+ # bala
+ # rescue StandardError => e
+ # something
+ # end
+ # end
+ class RedundantBegin < Cop
+ include CheckMethods
+
+ MSG = 'Redundant `begin` block detected.'
+
+ private
+
+ def check(_node, _method_name, _args, body)
+ return unless body && body.type == :kwbegin
+
+ add_offense(body, :begin)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/redundant_exception.rb b/lib/rubocop/cop/style/redundant_exception.rb
new file mode 100644
index 0000000..03308fb
--- /dev/null
+++ b/lib/rubocop/cop/style/redundant_exception.rb
@@ -0,0 +1,32 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for RuntimeError as the argument of raise/fail.
+ #
+ # Currently it checks for code like this:
+ #
+ # @example
+ #
+ # raise RuntimeError, 'message'
+ class RedundantException < Cop
+ MSG = 'Redundant `RuntimeError` argument can be removed.'
+
+ TARGET_NODE = s(:const, nil, :RuntimeError)
+
+ def on_send(node)
+ return unless command?(:raise, node) || command?(:fail, node)
+
+ _receiver, _selector, *args = *node
+
+ return unless args.size == 2
+
+ first_arg, = *args
+
+ add_offense(first_arg, :expression) if first_arg == TARGET_NODE
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/redundant_return.rb b/lib/rubocop/cop/style/redundant_return.rb
new file mode 100644
index 0000000..345751f
--- /dev/null
+++ b/lib/rubocop/cop/style/redundant_return.rb
@@ -0,0 +1,66 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for redundant `return` expressions.
+ #
+ # @example
+ #
+ # def test
+ # return something
+ # end
+ #
+ # def test
+ # one
+ # two
+ # three
+ # return something
+ # end
+ #
+ # It should be extended to handle methods whose body is if/else
+ # or a case expression with a default branch.
+ class RedundantReturn < Cop
+ include CheckMethods
+
+ MSG = 'Redundant `return` detected.'
+
+ private
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ if node.children.size > 1
+ kids = node.children.map { |child| child.loc.expression }
+ corrector.insert_before(kids.first, '[')
+ corrector.insert_after(kids.last, ']')
+ end
+ return_kw = range_with_surrounding_space(node.loc.keyword, :right)
+ corrector.remove(return_kw)
+ end
+ end
+
+ def check(_node, _method_name, _args, body)
+ return unless body
+
+ if body.type == :return
+ check_return_node(body)
+ elsif body.type == :begin
+ expressions = *body
+ last_expr = expressions.last
+
+ if last_expr && last_expr.type == :return
+ check_return_node(last_expr)
+ end
+ end
+ end
+
+ def check_return_node(node)
+ return if cop_config['AllowMultipleReturnValues'] &&
+ node.children.size > 1
+
+ add_offense(node, :keyword)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/redundant_self.rb b/lib/rubocop/cop/style/redundant_self.rb
new file mode 100644
index 0000000..74bba6c
--- /dev/null
+++ b/lib/rubocop/cop/style/redundant_self.rb
@@ -0,0 +1,145 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for redundant uses of `self`.
+ #
+ # `self` is only needed when:
+ #
+ # * Sending a message to same object with zero arguments in
+ # presence of a method name clash with an argument or a local
+ # variable.
+ #
+ # Note, with using explicit self you can only send messages
+ # with public or protected scope, you cannot send private
+ # messages this way.
+ #
+ # Example:
+ #
+ # def bar
+ # :baz
+ # end
+ #
+ # def foo(bar)
+ # self.bar # resolves name clash with argument
+ # end
+ #
+ # def foo2
+ # bar = 1
+ # self.bar # resolves name class with local variable
+ # end
+ #
+ # * Calling an attribute writer to prevent an local variable assignment
+ #
+ # attr_writer :bar
+ #
+ # def foo
+ # self.bar= 1 # Make sure above attr writer is called
+ # end
+ #
+ # Special cases:
+ #
+ # We allow uses of `self` with operators because it would be awkward
+ # otherwise.
+ class RedundantSelf < Cop
+ MSG = 'Redundant `self` detected.'
+
+ def initialize(config = nil, options = nil)
+ super
+ @allowed_send_nodes = []
+ @local_variables = []
+ end
+
+ # Assignment of self.x
+
+ def on_or_asgn(node)
+ lhs, _rhs = *node
+ allow_self(lhs)
+ end
+
+ alias_method :on_and_asgn, :on_or_asgn
+
+ def on_op_asgn(node)
+ lhs, _op, _rhs = *node
+ allow_self(lhs)
+ end
+
+ # Using self.x to distinguish from local variable x
+
+ def on_def(node)
+ @local_variables = []
+ end
+
+ def on_defs(node)
+ @local_variables = []
+ end
+
+ def on_args(node)
+ node.children.each { |arg| on_argument(arg) }
+ end
+
+ def on_blockarg(node)
+ on_argument(node)
+ end
+
+ def on_lvasgn(node)
+ lhs, _rhs = *node
+ @local_variables << lhs
+ end
+
+ # Detect offenses
+
+ def on_send(node)
+ receiver, method_name, *_args = *node
+ if receiver && receiver.type == :self
+ unless operator?(method_name) || keyword?(method_name) ||
+ constant_name?(method_name) ||
+ @allowed_send_nodes.include?(node) ||
+ @local_variables.include?(method_name)
+ add_offense(node, :expression)
+ end
+ end
+ end
+
+ def autocorrect(node)
+ receiver, _method_name, *_args = *node
+ @corrections << lambda do |corrector|
+ corrector.remove(receiver.loc.expression)
+ corrector.remove(node.loc.dot)
+ end
+ end
+
+ private
+
+ def on_argument(node)
+ name, _ = *node
+ @local_variables << name
+ end
+
+ def operator?(method_name)
+ method_name.to_s =~ /\W/
+ end
+
+ def keyword?(method_name)
+ [:alias, :and, :begin, :break, :case, :class, :def, :defined, :do,
+ :else, :elsif, :end, :ensure, :false, :for, :if, :in, :module,
+ :next, :nil, :not, :or, :redo, :rescue, :retry, :return, :self,
+ :super, :then, :true, :undef, :unless, :until, :when, :while,
+ :yield].include?(method_name)
+ end
+
+ def constant_name?(method_name)
+ method_name.match(/^[A-Z]/)
+ end
+
+ def allow_self(node)
+ if node.type == :send
+ receiver, _method_name, *_args = *node
+ @allowed_send_nodes << node if receiver && receiver.type == :self
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/regexp_literal.rb b/lib/rubocop/cop/style/regexp_literal.rb
new file mode 100644
index 0000000..c8168ce
--- /dev/null
+++ b/lib/rubocop/cop/style/regexp_literal.rb
@@ -0,0 +1,81 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for regexp literals and reports offenses based
+ # on how many escaped slashes there are in the regexp and on the
+ # value of the configuration parameter MaxSlashes.
+ class RegexpLiteral < Cop
+ def on_regexp(node)
+ string_parts = node.children.select { |child| child.type == :str }
+ total_string = string_parts.map { |s| s.loc.expression.source }.join
+ slashes = total_string.count('/')
+ if node.loc.begin.is?('/')
+ if slashes > max_slashes
+ add_offense(node, :expression, error_message('')) do
+ self.slash_count_in_slashes_regexp = slashes
+ end
+ end
+ elsif slashes <= max_slashes
+ add_offense(node, :expression, error_message('only ')) do
+ self.slash_count_in_percent_r_regexp = slashes
+ end
+ end
+ end
+
+ private
+
+ def max_slashes
+ m = cop_config['MaxSlashes']
+ unless m.is_a?(Fixnum) && m >= 0
+ fail "Illegal value for MaxSlashes: #{m}"
+ end
+ m
+ end
+
+ # MaxSlashes must be set equal to the highest number of slashes used
+ # within // to avoid reports.
+ def slash_count_in_slashes_regexp=(value)
+ configure_slashes(value) { |current| [current, value].max }
+ end
+
+ # MaxSlashes must be set one less than the highest number of slashes
+ # used within %r{} to avoid reports.
+ def slash_count_in_percent_r_regexp=(value)
+ configure_slashes(value - 1) { |current| [current, value - 1].min }
+ end
+
+ def configure_slashes(value)
+ cfg = self.config_to_allow_offenses ||= {}
+ return if cfg.key?('Enabled')
+
+ if cfg['MaxSlashes']
+ value = yield cfg['MaxSlashes']
+ if cfg['MaxSlashes'] > max_slashes && value < max_slashes ||
+ cfg['MaxSlashes'] < max_slashes && value > max_slashes
+ # We can't both increase and decrease MaxSlashes to avoid
+ # reports. This means that the only option is to disable the cop.
+ cfg = self.config_to_allow_offenses = { 'Enabled' => false }
+ return
+ end
+ end
+
+ if value >= 0
+ cfg['MaxSlashes'] = value
+ else
+ self.config_to_allow_offenses = { 'Enabled' => false }
+ end
+ end
+
+ def error_message(word)
+ format('Use %%r %sfor regular expressions matching more ' \
+ "than %d '/' character%s.",
+ word,
+ max_slashes,
+ max_slashes == 1 ? '' : 's')
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/rescue_modifier.rb b/lib/rubocop/cop/style/rescue_modifier.rb
new file mode 100644
index 0000000..29f1612
--- /dev/null
+++ b/lib/rubocop/cop/style/rescue_modifier.rb
@@ -0,0 +1,39 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for uses of rescue in its modifier form.
+ class RescueModifier < Cop
+ include CheckMethods
+
+ MSG = 'Avoid using `rescue` in its modifier form.'
+
+ def on_rescue(node)
+ return if ignored_node?(node)
+
+ add_offense(node, :expression)
+ end
+
+ def on_kwbegin(node)
+ body, *_ = *node
+ check(nil, nil, nil, body)
+ end
+
+ def check(_node, _method_name, _args, body)
+ return unless body
+
+ case body.type
+ when :rescue
+ ignore_node(body)
+ when :ensure
+ first_child = body.children.first
+ if first_child && first_child.type == :rescue
+ ignore_node(first_child)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/self_assignment.rb b/lib/rubocop/cop/style/self_assignment.rb
new file mode 100644
index 0000000..1e8b6d1
--- /dev/null
+++ b/lib/rubocop/cop/style/self_assignment.rb
@@ -0,0 +1,73 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop enforces the use the shorthand for self-assignment.
+ #
+ # @example
+ #
+ # # bad
+ # x = x + 1
+ #
+ # # good
+ # x += 1
+ class SelfAssignment < Cop
+ include AST::Sexp
+
+ OPS = [:+, :-, :*, :**, :/, :|, :&]
+
+ def on_lvasgn(node)
+ check(node, :lvar)
+ end
+
+ def on_ivasgn(node)
+ check(node, :ivar)
+ end
+
+ def on_cvasgn(node)
+ check(node, :cvar)
+ end
+
+ def check(node, var_type)
+ var_name, rhs = *node
+
+ return unless rhs
+
+ if rhs.type == :send
+ check_send_node(node, rhs, var_name, var_type)
+ elsif [:and, :or].include?(rhs.type)
+ check_boolean_node(node, rhs, var_name, var_type)
+ end
+ end
+
+ def check_send_node(node, rhs, var_name, var_type)
+ receiver, method_name, *_args = *rhs
+
+ return unless OPS.include?(method_name)
+
+ target_node = s(var_type, var_name)
+
+ if receiver == target_node
+ add_offense(node,
+ :expression,
+ "Use self-assignment shorthand `#{method_name}=`.")
+ end
+ end
+
+ def check_boolean_node(node, rhs, var_name, var_type)
+ first_operand, _second_operand = *rhs
+
+ target_node = s(var_type, var_name)
+
+ if first_operand == target_node
+ operator = rhs.loc.operator.source
+ add_offense(node,
+ :expression,
+ "Use self-assignment shorthand `#{operator}=`.")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/semicolon.rb b/lib/rubocop/cop/style/semicolon.rb
new file mode 100644
index 0000000..a47c0fb
--- /dev/null
+++ b/lib/rubocop/cop/style/semicolon.rb
@@ -0,0 +1,68 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for multiple expressions placed on the same line.
+ # It also checks for lines terminated with a semicolon.
+ class Semicolon < Cop
+ MSG = 'Do not use semicolons to terminate expressions.'
+
+ def investigate(processed_source)
+ return unless processed_source.ast
+ @processed_source = processed_source
+
+ check_for_line_terminator
+ end
+
+ def on_begin(node)
+ unless cop_config['AllowAsExpressionSeparator']
+ exprs = node.children
+
+ return if exprs.size < 2
+
+ # create a map matching lines to the number of expressions on them
+ exprs_lines = exprs.map { |e| e.loc.expression.line }
+ lines = exprs_lines.group_by { |i| i }
+
+ # every line with more than 1 expression on it is an offense
+ lines.each do |line, expr_on_line|
+ if expr_on_line.size > 1
+ # TODO: Find the correct position of the semicolon. We don't
+ # know if the first semicolon on the line is a separator of
+ # expressions. It's just a guess.
+ column = @processed_source[line - 1].index(';')
+ convention_on(line, column, !:last_on_line)
+ end
+ end
+ end
+ end
+
+ private
+
+ def check_for_line_terminator
+ tokens_for_lines = @processed_source.tokens.group_by do |token|
+ token.pos.line
+ end
+
+ tokens_for_lines.each do |line, tokens|
+ if tokens.last.type == :tSEMI
+ convention_on(line, tokens.last.pos.column, :last_on_line)
+ end
+ end
+ end
+
+ def convention_on(line, column, last_on_line)
+ range = source_range(@processed_source.buffer,
+ @processed_source[0...(line - 1)], column,
+ 1)
+ add_offense(last_on_line ? range : nil, range)
+ end
+
+ def autocorrect(range)
+ @corrections << ->(corrector) { corrector.remove(range) } if range
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/signal_exception.rb b/lib/rubocop/cop/style/signal_exception.rb
new file mode 100644
index 0000000..3160a15
--- /dev/null
+++ b/lib/rubocop/cop/style/signal_exception.rb
@@ -0,0 +1,96 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for uses of `fail` and `raise`.
+ class SignalException < Cop
+ include ConfigurableEnforcedStyle
+
+ FAIL_MSG = 'Use `fail` instead of `raise` to signal exceptions.'
+ RAISE_MSG = 'Use `raise` instead of `fail` to rethrow exceptions.'
+
+ def on_rescue(node)
+ if style == :semantic
+ begin_node, *rescue_nodes, _else_node = *node
+
+ check_for(:raise, begin_node)
+
+ rescue_nodes.each do |rescue_node|
+ check_for(:fail, rescue_node)
+ allow(:raise, rescue_node)
+ end
+ end
+ end
+
+ def on_send(node)
+ case style
+ when :semantic
+ check_for(:raise, node) unless ignored_node?(node)
+ when :only_raise
+ check_for(:raise, node)
+ when :only_fail
+ check_for(:fail, node)
+ end
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ name =
+ case style
+ when :semantic then command?(:raise, node) ? 'fail' : 'raise'
+ when :only_raise then 'raise'
+ when :only_fail then 'fail'
+ end
+
+ corrector.replace(node.loc.selector, name)
+ end
+ end
+
+ private
+
+ def message(method_name)
+ case style
+ when :semantic
+ method_name == :fail ? RAISE_MSG : FAIL_MSG
+ when :only_raise
+ 'Always use `raise` to signal exceptions.'
+ when :only_fail
+ 'Always use `fail` to signal exceptions.'
+ end
+ end
+
+ def check_for(method_name, node)
+ return unless node
+
+ if style == :semantic
+ each_command(method_name, node) do |send_node|
+ unless ignored_node?(send_node)
+ add_offense(send_node, :selector, message(method_name))
+ ignore_node(send_node)
+ end
+ end
+ else
+ _receiver, selector, _args = *node
+
+ if [:raise, :fail].include?(selector) && selector != method_name
+ add_offense(node, :selector, message(method_name))
+ end
+ end
+ end
+
+ def allow(method_name, node)
+ each_command(method_name, node) do |send_node|
+ ignore_node(send_node)
+ end
+ end
+
+ def each_command(method_name, node)
+ on_node(:send, node, :rescue) do |send_node|
+ yield send_node if command?(method_name, send_node)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/single_line_block_params.rb b/lib/rubocop/cop/style/single_line_block_params.rb
new file mode 100644
index 0000000..af741b8
--- /dev/null
+++ b/lib/rubocop/cop/style/single_line_block_params.rb
@@ -0,0 +1,62 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks whether the block parameters of a single-line
+ # method accepting a block match the names specified via configuration.
+ #
+ # For instance one can configure `reduce`(`inject`) to use |a, e| as
+ # parameters.
+ class SingleLineBlockParams < Cop
+ def on_block(node)
+ # we care only for single line blocks
+ return unless Util.block_length(node) == 0
+
+ method_node, args_node, _body_node = *node
+ receiver, method_name, _method_args = *method_node
+
+ # discard other scenarios
+ return unless receiver
+ return unless method_names.include?(method_name)
+
+ # discard cases with argument destructuring
+ args = *args_node
+
+ return true unless args.all? { |n| n.type == :arg }
+
+ unless args_match?(method_name, args)
+ add_offense(args_node, :expression, message(method_name))
+ end
+ end
+
+ private
+
+ def message(method_name)
+ args = target_args(method_name).join(', ')
+ "Name `#{method_name}` block params `|#{args}|`."
+ end
+
+ def methods
+ cop_config['Methods']
+ end
+
+ def method_names
+ methods.map { |e| e.keys.first.to_sym }
+ end
+
+ def target_args(method_name)
+ method_name = method_name.to_s
+ method_hash = methods.find { |m| m.keys.first == method_name }
+ method_hash[method_name]
+ end
+
+ def args_match?(method_name, args)
+ actual_args = args.flat_map { |a| a.to_a }
+
+ actual_args == target_args(method_name).map(&:to_sym)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/single_line_methods.rb b/lib/rubocop/cop/style/single_line_methods.rb
new file mode 100644
index 0000000..37475b5
--- /dev/null
+++ b/lib/rubocop/cop/style/single_line_methods.rb
@@ -0,0 +1,70 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for single-line method definitions.
+ # It can optionally accept single-line methods with no body.
+ class SingleLineMethods < Cop
+ include CheckMethods
+
+ MSG = 'Avoid single-line method definitions.'
+
+ def allow_empty?
+ cop_config['AllowIfMethodIsEmpty']
+ end
+
+ private
+
+ def check(node, _method_name, _args, body)
+ start_line = node.loc.keyword.line
+ end_line = node.loc.end.line
+
+ empty_body = body.nil?
+
+ if start_line == end_line && !(allow_empty? && empty_body)
+ @body = body
+ add_offense(node, :expression)
+ end
+ end
+
+ def autocorrect(node)
+ body = @body
+ eol_comment = processed_source.comments.find do |c|
+ c.loc.line == node.loc.expression.line
+ end
+ @corrections << lambda do |corrector|
+ if body.type == :begin
+ body.children.each do |part|
+ break_line_before(part.loc.expression, node, corrector, 1)
+ end
+ else
+ break_line_before(body.loc.expression, node, corrector, 1)
+ end
+
+ break_line_before(node.loc.end, node, corrector, 0)
+
+ move_comment(eol_comment, node, corrector) if eol_comment
+ end
+ end
+
+ def break_line_before(range, node, corrector, indent_steps)
+ corrector.insert_before(
+ range,
+ "\n" + ' ' * (node.loc.keyword.column +
+ indent_steps *
+ IndentationWidth::CORRECT_INDENTATION)
+ )
+ end
+
+ def move_comment(eol_comment, node, corrector)
+ text = eol_comment.loc.expression.source
+ corrector.insert_before(node.loc.expression,
+ text + "\n" +
+ ' ' * node.loc.keyword.column)
+ corrector.remove(eol_comment.loc.expression)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/single_space_before_first_arg.rb b/lib/rubocop/cop/style/single_space_before_first_arg.rb
new file mode 100644
index 0000000..de4c972
--- /dev/null
+++ b/lib/rubocop/cop/style/single_space_before_first_arg.rb
@@ -0,0 +1,41 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Checks that exactly one space is used between a method name and the
+ # first argument for method calls without parentheses.
+ #
+ # @example
+ #
+ # something x
+ # something y, z
+ #
+ class SingleSpaceBeforeFirstArg < Cop
+ MSG = 'Put one space between the method name and the first argument.'
+
+ def on_send(node)
+ return if parentheses?(node)
+
+ _receiver, method_name, *args = *node
+ return if args.empty?
+ return if operator?(method_name)
+ return if method_name.to_s.end_with?('=')
+
+ arg1 = args.first.loc.expression
+ return if arg1.line > node.loc.line
+
+ arg1_with_space = range_with_surrounding_space(arg1, :left)
+ space = Parser::Source::Range.new(arg1.source_buffer,
+ arg1_with_space.begin_pos,
+ arg1.begin_pos)
+ add_offense(space, space) if space.length > 1
+ end
+
+ def autocorrect(range)
+ @corrections << ->(corrector) { corrector.replace(range, ' ') }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/space_after_colon.rb b/lib/rubocop/cop/style/space_after_colon.rb
new file mode 100644
index 0000000..834ae8c
--- /dev/null
+++ b/lib/rubocop/cop/style/space_after_colon.rb
@@ -0,0 +1,36 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Checks for colon (:) not followed by some kind of space.
+ class SpaceAfterColon < Cop
+ include IfNode
+
+ MSG = 'Space missing after colon.'
+
+ def on_pair(node)
+ oper = node.loc.operator
+ if oper.is?(':') && oper.source_buffer.source[oper.end_pos] =~ /\S/
+ add_offense(oper, oper)
+ end
+ end
+
+ def on_if(node)
+ if ternary_op?(node)
+ colon = node.loc.colon
+ if colon.source_buffer.source[colon.end_pos] =~ /\S/
+ add_offense(colon, colon)
+ end
+ end
+ end
+
+ def autocorrect(range)
+ @corrections << lambda do |corrector|
+ corrector.insert_after(range, ' ')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/space_after_comma.rb b/lib/rubocop/cop/style/space_after_comma.rb
new file mode 100644
index 0000000..6cc2bdb
--- /dev/null
+++ b/lib/rubocop/cop/style/space_after_comma.rb
@@ -0,0 +1,16 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Checks for comma (,) not followed by some kind of space.
+ class SpaceAfterComma < Cop
+ include SpaceAfterPunctuation
+
+ def kind(token)
+ 'comma' if token.type == :tCOMMA
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/space_after_control_keyword.rb b/lib/rubocop/cop/style/space_after_control_keyword.rb
new file mode 100644
index 0000000..55877d7
--- /dev/null
+++ b/lib/rubocop/cop/style/space_after_control_keyword.rb
@@ -0,0 +1,37 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Checks for various control keywords missing a space after them.
+ class SpaceAfterControlKeyword < Cop
+ MSG = 'Use space after control keywords.'
+ # elsif and unless are handled by on_if.
+ KEYWORDS = %w(if case when while until)
+
+ def on_keyword(node)
+ return if node.loc.is_a?(Parser::Source::Map::Ternary)
+
+ exp = node.loc.expression
+ kw = node.loc.keyword
+ kw_offset = kw.begin_pos - exp.begin_pos
+ if exp.source[kw_offset..-1].start_with?(kw.source + '(')
+ add_offense(node, kw)
+ end
+ end
+
+ KEYWORDS.each do |keyword|
+ define_method(:"on_#{keyword}") do |node|
+ on_keyword(node)
+ end
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ corrector.insert_after(node.loc.keyword, ' ')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/space_after_method_name.rb b/lib/rubocop/cop/style/space_after_method_name.rb
new file mode 100644
index 0000000..3ddac59
--- /dev/null
+++ b/lib/rubocop/cop/style/space_after_method_name.rb
@@ -0,0 +1,40 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Checks for space between a method name and a left parenthesis in defs.
+ #
+ # @example
+ #
+ # # bad
+ # def func (x) ... end
+ #
+ # # good
+ # def func(x) ... end
+ class SpaceAfterMethodName < Cop
+ include CheckMethods
+
+ MSG = 'Never put a space between a method name and the opening ' \
+ 'parenthesis.'
+
+ def check(_node, _method_name, args, body)
+ return unless args.loc.begin && args.loc.begin.is?('(')
+ expr = args.loc.expression
+ pos_before_left_paren = Parser::Source::Range.new(expr.source_buffer,
+ expr.begin_pos - 1,
+ expr.begin_pos)
+ if pos_before_left_paren.source =~ /\s/
+ add_offense(pos_before_left_paren, pos_before_left_paren)
+ end
+ end
+
+ def autocorrect(pos_before_left_paren)
+ @corrections << lambda do |corrector|
+ corrector.remove(pos_before_left_paren)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/space_after_not.rb b/lib/rubocop/cop/style/space_after_not.rb
new file mode 100644
index 0000000..89ee460
--- /dev/null
+++ b/lib/rubocop/cop/style/space_after_not.rb
@@ -0,0 +1,41 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for space after `!`.
+ #
+ # @example
+ # # bad
+ # ! something
+ #
+ # # good
+ # !something
+ class SpaceAfterNot < Cop
+ MSG = 'Do not leave space between `!` and its argument.'
+
+ def on_send(node)
+ _receiver, method_name, *_args = *node
+
+ return unless method_name == :!
+
+ if node.loc.expression.source =~ /^!\s+\w+/
+ # TODO: Improve source range to highlight the redundant whitespace.
+ add_offense(node, :selector)
+ end
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ receiver, _method_name, *_args = *node
+ space_range =
+ Parser::Source::Range.new(node.loc.selector.source_buffer,
+ node.loc.selector.end_pos,
+ receiver.loc.expression.begin_pos)
+ corrector.remove(space_range)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/space_after_semicolon.rb b/lib/rubocop/cop/style/space_after_semicolon.rb
new file mode 100644
index 0000000..c89e431
--- /dev/null
+++ b/lib/rubocop/cop/style/space_after_semicolon.rb
@@ -0,0 +1,16 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Checks for semicolon (;) not followed by some kind of space.
+ class SpaceAfterSemicolon < Cop
+ include SpaceAfterPunctuation
+
+ def kind(token)
+ 'semicolon' if token.type == :tSEMI
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/space_around_equals_in_parameter_default.rb b/lib/rubocop/cop/style/space_around_equals_in_parameter_default.rb
new file mode 100644
index 0000000..0909fe9
--- /dev/null
+++ b/lib/rubocop/cop/style/space_around_equals_in_parameter_default.rb
@@ -0,0 +1,68 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Checks that the equals signs in parameter default assignments
+ # have or don't have surrounding space depending on configuration.
+ class SpaceAroundEqualsInParameterDefault < Cop
+ include SurroundingSpace
+ include ConfigurableEnforcedStyle
+
+ def investigate(processed_source)
+ return unless processed_source.ast
+ @processed_source = processed_source
+ on_node(:optarg, processed_source.ast) do |optarg|
+ index = index_of_first_token(optarg)
+ arg, equals, value = processed_source.tokens[index, 3]
+ check_optarg(arg, equals, value)
+ end
+ end
+
+ private
+
+ def check_optarg(arg, equals, value)
+ space_on_both_sides = space_on_both_sides?(arg, equals, value)
+ no_surrounding_space = no_surrounding_space?(arg, equals, value)
+
+ if style == :space && space_on_both_sides ||
+ style == :no_space && no_surrounding_space
+ correct_style_detected
+ else
+ range = Parser::Source::Range.new(processed_source.buffer,
+ arg.pos.end_pos,
+ value.pos.begin_pos)
+ add_offense(range, range) do
+ if style == :space && no_surrounding_space ||
+ style == :no_space && space_on_both_sides
+ opposite_style_detected
+ else
+ unrecognized_style_detected
+ end
+ end
+ end
+ end
+
+ def space_on_both_sides?(arg, equals, value)
+ space_between?(arg, equals) && space_between?(equals, value)
+ end
+
+ def no_surrounding_space?(arg, equals, value)
+ !space_between?(arg, equals) && !space_between?(equals, value)
+ end
+
+ def message(_)
+ format('Surrounding space %s in default value assignment.',
+ style == :space ? 'missing' : 'detected')
+ end
+
+ def autocorrect(range)
+ replacement = style == :space ? ' = ' : '='
+ @corrections << lambda do |corrector|
+ corrector.replace(range, replacement)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/space_around_operators.rb b/lib/rubocop/cop/style/space_around_operators.rb
new file mode 100644
index 0000000..2cfe900
--- /dev/null
+++ b/lib/rubocop/cop/style/space_around_operators.rb
@@ -0,0 +1,82 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Checks that operators have space around them, except for **
+ # which should not have surrounding space.
+ class SpaceAroundOperators < Cop
+ TYPES = %w(and or class) + ASGN_NODES
+
+ TYPES.each { |t| define_method(:"on_#{t}") { |node| check(node) } }
+
+ def on_pair(node)
+ check(node) if node.loc.operator.is?('=>')
+ end
+
+ def on_if(node)
+ if node.loc.respond_to?(:question)
+ check_operator(node.loc.question)
+ check_operator(node.loc.colon)
+ end
+ end
+
+ def on_resbody(node)
+ check_operator(node.loc.assoc) if node.loc.assoc
+ end
+
+ def on_send(node)
+ if node.loc.operator
+ check(node)
+ elsif !unary_operation?(node) && !called_with_dot?(node)
+ op = node.loc.selector
+ check_operator(op) if operator?(op)
+ end
+ end
+
+ private
+
+ def operator?(range)
+ range.source !~ /^\[|\w/
+ end
+
+ def unary_operation?(node)
+ whole, selector = node.loc.expression, node.loc.selector
+ operator?(selector) && whole.begin_pos == selector.begin_pos
+ end
+
+ def called_with_dot?(node)
+ node.loc.dot
+ end
+
+ def check(node)
+ check_operator(node.loc.operator) if node.loc.operator
+ end
+
+ def check_operator(op)
+ with_space = range_with_surrounding_space(op)
+ if op.is?('**')
+ unless with_space.is?('**')
+ add_offense(with_space, op, 'Space around operator ** detected.')
+ end
+ elsif with_space.source !~ /^\s.*\s$/
+ add_offense(with_space, op,
+ 'Surrounding space missing for operator' \
+ " '#{op.source}'.")
+ end
+ end
+
+ def autocorrect(range)
+ @corrections << lambda do |corrector|
+ case range.source
+ when /\*\*/
+ corrector.replace(range, '**')
+ else
+ corrector.replace(range, " #{range.source.strip} ")
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/space_before_block_braces.rb b/lib/rubocop/cop/style/space_before_block_braces.rb
new file mode 100644
index 0000000..a9f8248
--- /dev/null
+++ b/lib/rubocop/cop/style/space_before_block_braces.rb
@@ -0,0 +1,63 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Checks that block braces have or don't have a space before the opening
+ # brace depending on configuration.
+ class SpaceBeforeBlockBraces < Cop
+ include ConfigurableEnforcedStyle
+
+ def on_block(node)
+ return if node.loc.begin.is?('do') # No braces.
+
+ # If braces are on separate lines, and the Blocks cop is enabled,
+ # those braces will be changed to do..end by the user or by
+ # auto-correct, so reporting space issues is not useful, and it
+ # creates auto-correct conflicts.
+ if config.for_cop('Blocks')['Enabled'] && Util.block_length(node) > 0
+ return
+ end
+
+ left_brace = node.loc.begin
+ space_plus_brace = range_with_surrounding_space(left_brace)
+ used_style =
+ space_plus_brace.source.start_with?('{') ? :no_space : :space
+
+ case used_style
+ when style then correct_style_detected
+ when :space then space_detected(left_brace, space_plus_brace)
+ else space_missing(left_brace)
+ end
+ end
+
+ private
+
+ def space_missing(left_brace)
+ add_offense(left_brace, left_brace,
+ 'Space missing to the left of {.') do
+ opposite_style_detected
+ end
+ end
+
+ def space_detected(left_brace, space_plus_brace)
+ space = Parser::Source::Range.new(left_brace.source_buffer,
+ space_plus_brace.begin_pos,
+ left_brace.begin_pos)
+ add_offense(space, space, 'Space detected to the left of {.') do
+ opposite_style_detected
+ end
+ end
+
+ def autocorrect(range)
+ @corrections << lambda do |corrector|
+ case range.source
+ when /\s/ then corrector.remove(range)
+ else corrector.insert_before(range, ' ')
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/space_before_modifier_keyword.rb b/lib/rubocop/cop/style/space_before_modifier_keyword.rb
new file mode 100644
index 0000000..e990bc0
--- /dev/null
+++ b/lib/rubocop/cop/style/space_before_modifier_keyword.rb
@@ -0,0 +1,40 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Here we check if modifier keywords are preceded by a space.
+ class SpaceBeforeModifierKeyword < Cop
+ MSG = 'Put a space before the modifier keyword.'
+
+ def on_if(node)
+ if modifier?(node)
+ kw = node.loc.keyword
+ b = kw.begin_pos
+ left_of_kw = Parser::Source::Range.new(kw.source_buffer, b - 1, b)
+ add_offense(node, left_of_kw) unless left_of_kw.is?(' ')
+ end
+ end
+
+ alias_method :on_while, :on_if
+ alias_method :on_until, :on_if
+
+ private
+
+ def modifier?(node)
+ node.loc.respond_to?(:end) && node.loc.end.nil? && !elsif?(node)
+ end
+
+ def elsif?(node)
+ node.loc.keyword.is?('elsif')
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ corrector.insert_before(node.loc.keyword, ' ')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/space_inside_block_braces.rb b/lib/rubocop/cop/style/space_inside_block_braces.rb
new file mode 100644
index 0000000..30c867a
--- /dev/null
+++ b/lib/rubocop/cop/style/space_inside_block_braces.rb
@@ -0,0 +1,151 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Checks that block braces have or don't have surrounding space inside
+ # them on configuration. For blocks taking parameters, it checks that the
+ # left brace has or doesn't have trailing space depending on
+ # configuration.
+ class SpaceInsideBlockBraces < Cop
+ include ConfigurableEnforcedStyle
+ include SurroundingSpace
+
+ def on_block(node)
+ return if node.loc.begin.is?('do') # No braces.
+
+ # If braces are on separate lines, and the Blocks cop is enabled,
+ # those braces will be changed to do..end by the user or by
+ # auto-correct, so reporting space issues is not useful, and it
+ # creates auto-correct conflicts.
+ if config.for_cop('Blocks')['Enabled'] && Util.block_length(node) > 0
+ return
+ end
+
+ left_brace, right_brace = node.loc.begin, node.loc.end
+
+ check_inside(node, left_brace, right_brace)
+ end
+
+ private
+
+ def check_inside(node, left_brace, right_brace)
+ sb = node.loc.expression.source_buffer
+
+ if left_brace.end_pos == right_brace.begin_pos
+ if style_for_empty_braces == :space
+ offense(sb, left_brace.begin_pos, right_brace.end_pos,
+ 'Space missing inside empty braces.')
+ end
+ else
+ range = Parser::Source::Range.new(sb, left_brace.end_pos,
+ right_brace.begin_pos)
+ inner = range.source
+ unless inner =~ /\n/
+ if inner =~ /\S/
+ braces_with_contents_inside(node, inner, sb)
+ elsif style_for_empty_braces == :no_space
+ offense(sb, range.begin_pos, range.end_pos,
+ 'Space inside empty braces detected.')
+ end
+ end
+ end
+ end
+
+ def braces_with_contents_inside(node, inner, sb)
+ _method, args, _body = *node
+ left_brace, right_brace = node.loc.begin, node.loc.end
+ pipe = args.loc.begin
+
+ if inner =~ /^\S/
+ no_space_inside_left_brace(left_brace, pipe, sb)
+ else
+ space_inside_left_brace(left_brace, pipe, sb)
+ end
+
+ if inner =~ /\S$/
+ no_space(sb, right_brace.begin_pos, right_brace.end_pos,
+ 'Space missing inside }.')
+ else
+ space_inside_right_brace(right_brace, sb)
+ end
+ end
+
+ def no_space_inside_left_brace(left_brace, pipe, sb)
+ if pipe
+ if left_brace.end_pos == pipe.begin_pos &&
+ cop_config['SpaceBeforeBlockParameters']
+ offense(sb, left_brace.begin_pos, pipe.end_pos,
+ 'Space between { and | missing.')
+ end
+ else
+ # We indicate the position after the left brace. Otherwise it's
+ # difficult to distinguish between space missing to the left and to
+ # the right of the brace in autocorrect.
+ no_space(sb, left_brace.end_pos, left_brace.end_pos + 1,
+ 'Space missing inside {.')
+ end
+ end
+
+ def space_inside_left_brace(left_brace, pipe, sb)
+ if pipe
+ unless cop_config['SpaceBeforeBlockParameters']
+ offense(sb, left_brace.end_pos, pipe.begin_pos,
+ 'Space between { and | detected.')
+ end
+ else
+ brace_with_space = range_with_surrounding_space(left_brace, :right)
+ space(sb, brace_with_space.begin_pos + 1, brace_with_space.end_pos,
+ 'Space inside { detected.')
+ end
+ end
+
+ def space_inside_right_brace(right_brace, sb)
+ brace_with_space = range_with_surrounding_space(right_brace, :left)
+ space(sb, brace_with_space.begin_pos, brace_with_space.end_pos - 1,
+ 'Space inside } detected.')
+ end
+
+ def no_space(sb, begin_pos, end_pos, msg)
+ if style == :space
+ offense(sb, begin_pos, end_pos, msg) { opposite_style_detected }
+ else
+ correct_style_detected
+ end
+ end
+
+ def space(sb, begin_pos, end_pos, msg)
+ if style == :no_space
+ offense(sb, begin_pos, end_pos, msg) { opposite_style_detected }
+ else
+ correct_style_detected
+ end
+ end
+
+ def offense(sb, begin_pos, end_pos, msg)
+ range = Parser::Source::Range.new(sb, begin_pos, end_pos)
+ add_offense(range, range, msg) { yield if block_given? }
+ end
+
+ def style_for_empty_braces
+ case cop_config['EnforcedStyleForEmptyBraces']
+ when 'space' then :space
+ when 'no_space' then :no_space
+ else fail 'Unknown EnforcedStyleForEmptyBraces selected!'
+ end
+ end
+
+ def autocorrect(range)
+ @corrections << lambda do |corrector|
+ case range.source
+ when /\s/ then corrector.remove(range)
+ when '{}' then corrector.replace(range, '{ }')
+ when '{|' then corrector.replace(range, '{ |')
+ else corrector.insert_before(range, ' ')
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/space_inside_brackets.rb b/lib/rubocop/cop/style/space_inside_brackets.rb
new file mode 100644
index 0000000..2d98c88
--- /dev/null
+++ b/lib/rubocop/cop/style/space_inside_brackets.rb
@@ -0,0 +1,16 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Checks for spaces inside square brackets.
+ class SpaceInsideBrackets < Cop
+ include SpaceInside
+
+ def specifics
+ [:tLBRACK, :tRBRACK, 'square brackets']
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/space_inside_hash_literal_braces.rb b/lib/rubocop/cop/style/space_inside_hash_literal_braces.rb
new file mode 100644
index 0000000..dd87768
--- /dev/null
+++ b/lib/rubocop/cop/style/space_inside_hash_literal_braces.rb
@@ -0,0 +1,111 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Checks that braces used for hash literals have or don't have
+ # surrounding space depending on configuration.
+ class SpaceInsideHashLiteralBraces < Cop
+ include SurroundingSpace
+ include ConfigurableEnforcedStyle
+
+ MSG = 'Space inside %s.'
+
+ def investigate(processed_source)
+ return unless processed_source.ast
+ tokens = processed_source.tokens
+
+ on_node(:hash, processed_source.ast) do |hash|
+ b_ix = index_of_first_token(hash)
+ if tokens[b_ix].type == :tLBRACE # Hash literal with braces?
+ e_ix = index_of_last_token(hash)
+ check(tokens[b_ix], tokens[b_ix + 1])
+ check(tokens[e_ix - 1], tokens[e_ix]) unless b_ix == e_ix - 1
+ end
+ end
+ end
+
+ private
+
+ def check(t1, t2)
+ # No offense if line break inside.
+ return if t1.pos.line < t2.pos.line
+ return if t2.type == :tCOMMENT # Also indicates there's a line break.
+
+ is_empty_braces = t1.text == '{' && t2.text == '}'
+ expect_space = if is_empty_braces
+ cop_config['EnforcedStyleForEmptyBraces'] == 'space'
+ else
+ style == :space
+ end
+ if offense?(t1, t2, expect_space)
+ brace = (t1.text == '{' ? t1 : t2).pos
+ range = expect_space ? brace : space_range(brace)
+ add_offense(range, range, message(brace, is_empty_braces,
+ expect_space)) do
+ opposite_style_detected
+ end
+ else
+ correct_style_detected
+ end
+ end
+
+ def offense?(t1, t2, expect_space)
+ has_space = space_between?(t1, t2)
+ expect_space ? !has_space : has_space
+ end
+
+ def message(brace, is_empty_braces, expect_space)
+ inside_what = if is_empty_braces
+ 'empty hash literal braces'
+ else
+ brace.source
+ end
+ problem = expect_space ? 'missing' : 'detected'
+ format(MSG, "#{inside_what} #{problem}")
+ end
+
+ def autocorrect(range)
+ @corrections << lambda do |corrector|
+ # It is possible that BracesAroundHashParameters will remove the
+ # braces while this cop inserts spaces. This can lead to unwanted
+ # changes to the inspected code. If we replace the brace with a
+ # brace plus space (rather than just inserting a space), then any
+ # removal of the same brace will give us a clobbering error. This
+ # in turn will make RuboCop fall back on cop-by-cop
+ # auto-correction. Problem solved.
+ case range.source
+ when /\s/ then corrector.remove(range)
+ when '{' then corrector.replace(range, '{ ')
+ else corrector.replace(range, ' }')
+ end
+ end
+ end
+
+ def space_range(token_range)
+ if token_range.source == '{'
+ range_of_space_to_the_right(token_range)
+ else
+ range_of_space_to_the_left(token_range)
+ end
+ end
+
+ def range_of_space_to_the_right(range)
+ src = range.source_buffer.source
+ end_pos = range.end_pos
+ end_pos += 1 while src[end_pos] =~ /[ \t]/
+ Parser::Source::Range.new(range.source_buffer,
+ range.begin_pos + 1, end_pos)
+ end
+
+ def range_of_space_to_the_left(range)
+ src = range.source_buffer.source
+ begin_pos = range.begin_pos
+ begin_pos -= 1 while src[begin_pos - 1] =~ /[ \t]/
+ Parser::Source::Range.new(range.source_buffer, begin_pos,
+ range.end_pos - 1)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/space_inside_parens.rb b/lib/rubocop/cop/style/space_inside_parens.rb
new file mode 100644
index 0000000..f16e875
--- /dev/null
+++ b/lib/rubocop/cop/style/space_inside_parens.rb
@@ -0,0 +1,16 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Checks for spaces inside ordinary round parentheses.
+ class SpaceInsideParens < Cop
+ include SpaceInside
+
+ def specifics
+ [:tLPAREN2, :tRPAREN, 'parentheses']
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/special_global_vars.rb b/lib/rubocop/cop/style/special_global_vars.rb
new file mode 100644
index 0000000..354c2e4
--- /dev/null
+++ b/lib/rubocop/cop/style/special_global_vars.rb
@@ -0,0 +1,84 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop looks for uses of Perl-style global variables.
+ class SpecialGlobalVars < Cop
+ MSG_BOTH = 'Prefer `%s` from the English library, or `%s` over `%s`.'
+ MSG_ENGLISH = 'Prefer `%s` from the English library over `%s`.'
+ MSG_REGULAR = 'Prefer `%s` over `%s`.'
+
+ PREFERRED_VARS = {
+ '$:' => ['$LOAD_PATH'],
+ '$"' => ['$LOADED_FEATURES'],
+ '$0' => ['$PROGRAM_NAME'],
+ '$!' => ['$ERROR_INFO'],
+ '$@' => ['$ERROR_POSITION'],
+ '$;' => ['$FIELD_SEPARATOR', '$FS'],
+ '$,' => ['$OUTPUT_FIELD_SEPARATOR', '$OFS'],
+ '$/' => ['$INPUT_RECORD_SEPARATOR', '$RS'],
+ '$\\' => ['$OUTPUT_RECORD_SEPARATOR', '$ORS'],
+ '$.' => ['$INPUT_LINE_NUMBER', '$NR'],
+ '$_' => ['$LAST_READ_LINE'],
+ '$>' => ['$DEFAULT_OUTPUT'],
+ '$<' => ['$DEFAULT_INPUT'],
+ '$$' => ['$PROCESS_ID', '$PID'],
+ '$?' => ['$CHILD_STATUS'],
+ '$~' => ['$LAST_MATCH_INFO'],
+ '$=' => ['$IGNORECASE'],
+ '$*' => ['$ARGV', 'ARGV'],
+ '$&' => ['$MATCH'],
+ '$`' => ['$PREMATCH'],
+ '$\'' => ['$POSTMATCH'],
+ '$+' => ['$LAST_PAREN_MATCH']
+ }.symbolize_keys
+
+ # Anything *not* in this set is provided by the English library.
+ NON_ENGLISH_VARS = Set.new([
+ '$LOAD_PATH',
+ '$LOADED_FEATURES',
+ '$PROGRAM_NAME',
+ 'ARGV'
+ ])
+
+ def on_gvar(node)
+ global_var, = *node
+
+ add_offense(node, :expression) if PREFERRED_VARS[global_var]
+ end
+
+ def message(node)
+ global_var, = *node
+
+ regular, english = PREFERRED_VARS[global_var].partition do |var|
+ NON_ENGLISH_VARS.include? var
+ end
+
+ # For now, we assume that lists are 2 items or less. Easy grammar!
+ regular_msg = regular.join('` or `')
+ english_msg = english.join('` or `')
+
+ if regular.length > 0 && english.length > 0
+ format(MSG_BOTH, english_msg, regular_msg, global_var)
+ elsif regular.length > 0
+ format(MSG_REGULAR, regular_msg, global_var)
+ elsif english.length > 0
+ format(MSG_ENGLISH, english_msg, global_var)
+ else
+ fail 'Bug in SpecialGlobalVars - global var w/o preferred vars!'
+ end
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ global_var, = *node
+
+ corrector.replace(node.loc.expression,
+ PREFERRED_VARS[global_var].first)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/string_literals.rb b/lib/rubocop/cop/style/string_literals.rb
new file mode 100644
index 0000000..df47b81
--- /dev/null
+++ b/lib/rubocop/cop/style/string_literals.rb
@@ -0,0 +1,46 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Checks if uses of quotes match the configured preference.
+ class StringLiterals < Cop
+ include ConfigurableEnforcedStyle
+ include StringHelp
+
+ private
+
+ def message(node)
+ if style == :single_quotes
+ "Prefer single-quoted strings when you don't need string " \
+ 'interpolation or special symbols.'
+ else
+ 'Prefer double-quoted strings unless you need single quotes to ' \
+ 'avoid extra backslashes for escaping.'
+ end
+ end
+
+ def offense?(node)
+ src = node.loc.expression.source
+ return false if src =~ /^(%[qQ]?|\?|<<-)/i
+ src !~ if style == :single_quotes
+ # regex matches IF there is a ' or there is a \\ in the
+ # string that is not preceeded/followed by another \\
+ # (e.g. "\\x34") but not "\\\\"
+ /' | (?<! \\) \\{2}* \\ (?! \\)/x
+ else
+ /" | \\/x
+ end
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ replacement = node.loc.begin.is?('"') ? "'" : '"'
+ corrector.replace(node.loc.begin, replacement)
+ corrector.replace(node.loc.end, replacement)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/symbol_array.rb b/lib/rubocop/cop/style/symbol_array.rb
new file mode 100644
index 0000000..ae20de5
--- /dev/null
+++ b/lib/rubocop/cop/style/symbol_array.rb
@@ -0,0 +1,24 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for array literals made up of symbols
+ # that are not using the %i() syntax.
+ #
+ # This check makes sense only on Ruby 2.0+.
+ class SymbolArray < Cop
+ include ArraySyntax
+
+ MSG = 'Use %i or %I for array of symbols.'
+
+ def on_array(node)
+ # %i and %I were introduced in Ruby 2.0
+ unless RUBY_VERSION < '2.0.0'
+ add_offense(node, :expression) if array_of?(:sym, node)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/tab.rb b/lib/rubocop/cop/style/tab.rb
new file mode 100644
index 0000000..15cefed
--- /dev/null
+++ b/lib/rubocop/cop/style/tab.rb
@@ -0,0 +1,26 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for tabs inside the source code.
+ class Tab < Cop
+ MSG = 'Tab detected.'
+
+ def investigate(processed_source)
+ processed_source.lines.each_with_index do |line, index|
+ match = line.match(/^( *)\t/)
+ if match
+ spaces = match.captures[0]
+ add_offense(nil,
+ source_range(processed_source.buffer,
+ processed_source[0...index],
+ spaces.length, 1),
+ MSG)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/trailing_blank_lines.rb b/lib/rubocop/cop/style/trailing_blank_lines.rb
new file mode 100644
index 0000000..1c11c3e
--- /dev/null
+++ b/lib/rubocop/cop/style/trailing_blank_lines.rb
@@ -0,0 +1,44 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop looks for trailing blank lines in the source code.
+ class TrailingBlankLines < Cop
+ MSG = '%d trailing blank lines detected.'
+
+ def investigate(processed_source)
+ blank_lines = 0
+
+ processed_source.lines.reverse_each do |line|
+ if line.blank?
+ blank_lines += 1
+ else
+ break
+ end
+ end
+
+ if blank_lines > 0
+ range_size =
+ processed_source.raw_lines.to_a[-blank_lines..-1].join.length
+ range = source_range(processed_source.buffer,
+ processed_source[0...-blank_lines],
+ 0, range_size)
+ add_offense(range, range, format(MSG, blank_lines))
+ end
+ end
+
+ def autocorrect(range)
+ # Bail out if there's also trailing whitespace, because
+ # auto-correction in the two cops would result in clobbering.
+ if range.source =~ / / &&
+ config.for_cop('TrailingWhitespace')['Enabled']
+ return
+ end
+
+ @corrections << ->(corrector) { corrector.remove(range) }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/trailing_comma.rb b/lib/rubocop/cop/style/trailing_comma.rb
new file mode 100644
index 0000000..657e022
--- /dev/null
+++ b/lib/rubocop/cop/style/trailing_comma.rb
@@ -0,0 +1,102 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for trailing comma in parameter lists and literals.
+ class TrailingComma < Cop
+ include ArraySyntax
+ include ConfigurableEnforcedStyle
+
+ MSG = '%s comma after the last %s.'
+
+ def on_array(node)
+ check_literal(node, 'item of %s array') if square_brackets?(node)
+ end
+
+ def on_hash(node)
+ check_literal(node, 'item of %s hash')
+ end
+
+ def on_send(node)
+ _receiver, _method_name, *args = *node
+ return if args.empty?
+ # It's impossible for a method call without parentheses to have
+ # a trailing comma.
+ return unless brackets?(node)
+
+ check(node, args, 'parameter of %s method call',
+ args.last.loc.expression.end_pos, node.loc.expression.end_pos)
+ end
+
+ private
+
+ def parameter_name
+ 'EnforcedStyleForMultiline'
+ end
+
+ def check_literal(node, kind)
+ return if node.children.empty?
+ # A braceless hash is the last parameter of a method call and will be
+ # checked as such.
+ return unless brackets?(node)
+
+ check(node, node.children, kind,
+ node.children.last.loc.expression.end_pos,
+ node.loc.end.begin_pos)
+ end
+
+ def check(node, items, kind, begin_pos, end_pos)
+ sb = items.first.loc.expression.source_buffer
+ after_last_item = Parser::Source::Range.new(sb, begin_pos, end_pos)
+
+ return if heredoc?(after_last_item.source)
+
+ comma_offset = after_last_item.source =~ /,/
+ should_have_comma = style == :comma && multiline?(node)
+ if comma_offset
+ unless should_have_comma
+ avoid_comma(items, kind,
+ after_last_item.begin_pos + comma_offset, sb)
+ end
+ elsif should_have_comma
+ put_comma(items, kind, sb)
+ end
+ end
+
+ def heredoc?(source_after_last_item)
+ source_after_last_item =~ /\w/
+ end
+
+ # Returns true if the node has round/square/curly brackets.
+ def brackets?(node)
+ node.loc.end
+ end
+
+ # Returns true if the round/square/curly brackets of the given node are
+ # on different lines.
+ def multiline?(node)
+ [node.loc.begin, node.loc.end].map(&:line).uniq.size > 1
+ end
+
+ def avoid_comma(items, kind, comma_begin_pos, sb)
+ range = Parser::Source::Range.new(sb, comma_begin_pos,
+ comma_begin_pos + 1)
+ article = kind =~ /array/ ? 'an' : 'a'
+ add_offense(nil, range,
+ format(MSG, 'Avoid', format(kind, article)))
+ end
+
+ def put_comma(items, kind, sb)
+ last_expr = items.last.loc.expression
+ ix = last_expr.source.rindex("\n") || 0
+ ix += last_expr.source[ix..-1] =~ /\S/
+ range = Parser::Source::Range.new(sb, last_expr.begin_pos + ix,
+ last_expr.end_pos)
+ add_offense(nil, range,
+ format(MSG, 'Put a', format(kind, 'a multiline')))
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/trailing_whitespace.rb b/lib/rubocop/cop/style/trailing_whitespace.rb
new file mode 100644
index 0000000..5331a60
--- /dev/null
+++ b/lib/rubocop/cop/style/trailing_whitespace.rb
@@ -0,0 +1,28 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop looks for trailing whitespace in the source code.
+ class TrailingWhitespace < Cop
+ MSG = 'Trailing whitespace detected.'
+
+ def investigate(processed_source)
+ processed_source.lines.each_with_index do |line, index|
+ if line =~ /.*[ \t]+$/
+ range = source_range(processed_source.buffer,
+ processed_source[0...index],
+ line.rstrip.length,
+ line.length - line.rstrip.length)
+ add_offense(range, range)
+ end
+ end
+ end
+
+ def autocorrect(range)
+ @corrections << ->(corrector) { corrector.remove(range) }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/trivial_accessors.rb b/lib/rubocop/cop/style/trivial_accessors.rb
new file mode 100644
index 0000000..67a3877
--- /dev/null
+++ b/lib/rubocop/cop/style/trivial_accessors.rb
@@ -0,0 +1,78 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop looks for trivial reader/writer methods, that could
+ # have been created with the attr_* family of functions automatically.
+ class TrivialAccessors < Cop
+ include CheckMethods
+
+ MSG = 'Use `attr_%s` to define trivial %s methods.'
+
+ private
+
+ def check(node, method_name, args, body)
+ kind = if trivial_reader?(method_name, args, body)
+ 'reader'
+ elsif trivial_writer?(method_name, args, body)
+ 'writer'
+ end
+ if kind
+ add_offense(node, :keyword,
+ format(MSG, kind, kind))
+ end
+ end
+
+ def exact_name_match?
+ cop_config['ExactNameMatch']
+ end
+
+ def allow_predicates?
+ cop_config['AllowPredicates']
+ end
+
+ def whitelist
+ whitelist = cop_config['Whitelist']
+ Array(whitelist).map(&:to_sym) + [:initialize]
+ end
+
+ def predicate?(method_name)
+ method_name[-1] == '?'
+ end
+
+ def trivial_reader?(method_name, args, body)
+ looks_like_trivial_reader?(args, body) &&
+ !allowed_method?(method_name, body)
+ end
+
+ def looks_like_trivial_reader?(args, body)
+ args.children.size == 0 && body && body.type == :ivar
+ end
+
+ def trivial_writer?(method_name, args, body)
+ looks_like_trivial_writer?(args, body) &&
+ !allowed_method?(method_name, body)
+ end
+
+ def looks_like_trivial_writer?(args, body)
+ args.children.size == 1 && args.children[0].type != :restarg &&
+ body && body.type == :ivasgn &&
+ body.children[1] && body.children[1].type == :lvar
+ end
+
+ def allowed_method?(method_name, body)
+ allow_predicates? && predicate?(method_name) ||
+ whitelist.include?(method_name) ||
+ exact_name_match? && !names_match?(method_name, body)
+ end
+
+ def names_match?(method_name, body)
+ ivar_name, = *body
+
+ method_name.to_s.chomp('=') == ivar_name[1..-1]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/unless_else.rb b/lib/rubocop/cop/style/unless_else.rb
new file mode 100644
index 0000000..ff477d5
--- /dev/null
+++ b/lib/rubocop/cop/style/unless_else.rb
@@ -0,0 +1,24 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop looks for *unless* expressions with *else* clauses.
+ class UnlessElse < Cop
+ MSG = 'Never use `unless` with `else`. Rewrite these with the ' \
+ 'positive case first.'
+
+ def on_if(node)
+ loc = node.loc
+
+ # discard ternary ops and modifier if/unless nodes
+ return unless loc.respond_to?(:keyword) && loc.respond_to?(:else)
+
+ if loc.keyword.is?('unless') && loc.else
+ add_offense(node, :expression)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/variable_interpolation.rb b/lib/rubocop/cop/style/variable_interpolation.rb
new file mode 100644
index 0000000..fb5a374
--- /dev/null
+++ b/lib/rubocop/cop/style/variable_interpolation.rb
@@ -0,0 +1,33 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for variable interpolation (like "#@ivar").
+ class VariableInterpolation < Cop
+ MSG = 'Replace interpolated variable `%s` with expression `#{%s}`.'
+
+ def on_dstr(node)
+ var_nodes(node.children).each do |v|
+ var = v.loc.expression.source
+
+ add_offense(v, :expression, format(MSG, var, var))
+ end
+ end
+
+ private
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ expr = node.loc.expression
+ corrector.replace(expr, "{#{expr.source}}")
+ end
+ end
+
+ def var_nodes(nodes)
+ nodes.select { |n| [:ivar, :cvar, :gvar, :nth_ref].include?(n.type) }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/variable_name.rb b/lib/rubocop/cop/style/variable_name.rb
new file mode 100644
index 0000000..9201503
--- /dev/null
+++ b/lib/rubocop/cop/style/variable_name.rb
@@ -0,0 +1,44 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop makes sure that all variables use the configured style,
+ # snake_case or camelCase, for their names.
+ class VariableName < Cop
+ include ConfigurableNaming
+
+ def on_lvasgn(node)
+ check(node, name_of_variable(node))
+ end
+
+ def on_ivasgn(node)
+ check(node, name_of_variable(node))
+ end
+
+ def on_send(node)
+ check(node, name_of_setter(node))
+ end
+
+ def name_of_variable(vasgn_node)
+ expr = vasgn_node.loc.expression
+ name = vasgn_node.children.first
+ Parser::Source::Range.new(expr.source_buffer, expr.begin_pos,
+ expr.begin_pos + name.length)
+ end
+
+ def name_of_setter(send_node)
+ receiver, method_name = *send_node
+ return unless receiver && receiver.type == :self
+ return unless method_name.to_s.end_with?('=')
+ after_dot(send_node, method_name.length - '='.length,
+ Regexp.escape(receiver.loc.expression.source))
+ end
+
+ def message(style)
+ format('Use %s for variables.', style)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/when_then.rb b/lib/rubocop/cop/style/when_then.rb
new file mode 100644
index 0000000..d853a6b
--- /dev/null
+++ b/lib/rubocop/cop/style/when_then.rb
@@ -0,0 +1,24 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for *when;* uses in *case* expressions.
+ class WhenThen < Cop
+ MSG = 'Never use `when x;`. Use `when x then` instead.'
+
+ def on_when(node)
+ if node.loc.begin && node.loc.begin.is?(';')
+ add_offense(node, :begin)
+ end
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ corrector.replace(node.loc.begin, ' then')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/while_until_do.rb b/lib/rubocop/cop/style/while_until_do.rb
new file mode 100644
index 0000000..922e740
--- /dev/null
+++ b/lib/rubocop/cop/style/while_until_do.rb
@@ -0,0 +1,45 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Checks for uses of `do` in multi-line `while/until` statements.
+ class WhileUntilDo < Cop
+ def on_while(node)
+ handle(node)
+ end
+
+ def on_until(node)
+ handle(node)
+ end
+
+ def handle(node)
+ length = node.loc.expression.source.lines.to_a.size
+
+ if length > 1
+ if node.loc.begin && node.loc.begin.is?('do')
+ add_offense(node, :begin,
+ error_message(node.type))
+ end
+ end
+ end
+
+ private
+
+ def error_message(node_type)
+ format('Never use `do` with multi-line `%s`.', node_type)
+ end
+
+ def autocorrect(node)
+ @corrections << lambda do |corrector|
+ condition_node, = *node
+ end_of_condition_range = condition_node.loc.expression.end
+ do_range = node.loc.begin
+ whitespaces_and_do_range = end_of_condition_range.join(do_range)
+ corrector.remove(whitespaces_and_do_range)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/while_until_modifier.rb b/lib/rubocop/cop/style/while_until_modifier.rb
new file mode 100644
index 0000000..d6af351
--- /dev/null
+++ b/lib/rubocop/cop/style/while_until_modifier.rb
@@ -0,0 +1,33 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # Checks for while and until statements that would fit on one line
+ # if written as a modifier while/until.
+ # The maximum line length is configurable.
+ class WhileUntilModifier < Cop
+ include StatementModifier
+
+ def investigate(processed_source)
+ return unless processed_source.ast
+ on_node([:while, :until], processed_source.ast) do |node|
+ # discard modifier while/until
+ next unless node.loc.end
+
+ if check(node, processed_source.comments)
+ add_offense(node, :keyword,
+ message(node.loc.keyword.source))
+ end
+ end
+ end
+
+ private
+
+ def message(keyword)
+ "Favor modifier `#{keyword}` usage when having a single-line body."
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/style/word_array.rb b/lib/rubocop/cop/style/word_array.rb
new file mode 100644
index 0000000..2a335d7
--- /dev/null
+++ b/lib/rubocop/cop/style/word_array.rb
@@ -0,0 +1,86 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module Style
+ # This cop checks for array literals made up of word-like
+ # strings, that are not using the %w() syntax.
+ class WordArray < Cop
+ include ArraySyntax
+ # The parameter is called MinSize (meaning the minimum array size for
+ # which an offense can be registered), but essentially it's a Max
+ # parameter (the maximum number of something that's allowed).
+ include ConfigurableMax
+
+ MSG = 'Use `%w` or `%W` for array of words.'
+
+ def on_array(node)
+ array_elems = node.children
+ if array_of?(:str, node) && !complex_content?(array_elems) &&
+ array_elems.size > min_size && !comments_in_array?(node)
+ add_offense(node, :expression) { self.max = array_elems.size }
+ end
+ end
+
+ private
+
+ def parameter_name
+ 'MinSize'
+ end
+
+ def comments_in_array?(node)
+ comments = processed_source.comments
+
+ array_range = node.loc.expression.to_a
+
+ comments.any? do |comment|
+ !(comment.loc.expression.to_a & array_range).empty?
+ end
+ end
+
+ def complex_content?(arr_sexp)
+ arr_sexp.each do |s|
+ source = s.loc.expression.source
+ unless source.start_with?('?') # %W(\r \n) can replace [?\r, ?\n]
+ str_content = Util.strip_quotes(source)
+ return true unless str_content =~ /\A[\w-]+\z/
+ end
+ end
+
+ false
+ end
+
+ def min_size
+ cop_config['MinSize']
+ end
+
+ def autocorrect(node)
+ sb = node.loc.expression.source_buffer
+ interpolated = false
+
+ contents = node.children.map do |n|
+ if character_literal?(n)
+ interpolated = true
+ begin_pos = n.loc.expression.begin_pos + '?'.length
+ end_pos = n.loc.expression.end_pos
+ else
+ begin_pos = n.loc.begin.end_pos
+ end_pos = n.loc.end.begin_pos
+ end
+ Parser::Source::Range.new(sb, begin_pos, end_pos).source
+ end.join(' ')
+
+ char = interpolated ? 'W' : 'w'
+
+ @corrections << lambda do |corrector|
+ corrector.replace(node.loc.expression, "%#{char}(#{contents})")
+ end
+ end
+
+ def character_literal?(node)
+ node.loc.end.nil?
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/team.rb b/lib/rubocop/cop/team.rb
new file mode 100644
index 0000000..8cc43d6
--- /dev/null
+++ b/lib/rubocop/cop/team.rb
@@ -0,0 +1,113 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # FIXME
+ class Team
+ attr_reader :errors, :updated_source_file
+
+ alias_method :updated_source_file?, :updated_source_file
+
+ def initialize(cop_classes, config, options = nil)
+ @cop_classes = cop_classes
+ @config = config
+ @options = options || { auto_correct: false, debug: false }
+ @errors = []
+ end
+
+ def autocorrect?
+ @options[:auto_correct]
+ end
+
+ def debug?
+ @options[:debug]
+ end
+
+ def inspect_file(processed_source)
+ # If we got any syntax errors, return only the syntax offenses.
+ # Parser may return nil for AST even though there are no syntax errors.
+ # e.g. sources which contain only comments
+ unless processed_source.valid_syntax?
+ diagnostics = processed_source.diagnostics
+ return Lint::Syntax.offenses_from_diagnostics(diagnostics)
+ end
+
+ commissioner = Commissioner.new(cops)
+ offenses = commissioner.investigate(processed_source)
+ process_commissioner_errors(
+ processed_source.file_path, commissioner.errors)
+ autocorrect(processed_source.buffer, cops)
+ offenses
+ end
+
+ def cops
+ @cops ||= begin
+ @cop_classes.each_with_object([]) do |cop_class, instances|
+ if cop_enabled?(cop_class)
+ instances << cop_class.new(@config, @options)
+ end
+ end
+ end
+ end
+
+ private
+
+ def cop_enabled?(cop_class)
+ @config.cop_enabled?(cop_class) ||
+ cop_class.cop_name == @options[:only]
+ end
+
+ def autocorrect(buffer, cops)
+ @updated_source_file = false
+ return unless autocorrect?
+
+ corrections = cops.reduce([]) do |array, cop|
+ array.concat(cop.corrections)
+ array
+ end
+
+ corrector = Corrector.new(buffer, corrections)
+ new_source = begin
+ corrector.rewrite
+ rescue RangeError, RuntimeError
+ autocorrect_one_cop(buffer, cops)
+ end
+
+ unless new_source == buffer.source
+ filename = buffer.name
+ File.open(filename, 'w') { |f| f.write(new_source) }
+ @updated_source_file = true
+ end
+ end
+
+ # Does a slower but safer auto-correction run by correcting for just one
+ # cop. The re-running of auto-corrections will make sure that the full
+ # set of auto-corrections is tried again after this method has finished.
+ def autocorrect_one_cop(buffer, cops)
+ cop_with_corrections = cops.find { |cop| cop.corrections.any? }
+ corrector = Corrector.new(buffer, cop_with_corrections.corrections)
+ corrector.rewrite
+ end
+
+ def process_commissioner_errors(file, file_errors)
+ file_errors.each do |cop, errors|
+ errors.each do |e|
+ handle_error(e,
+ "An error occurred while #{cop.name}".color(:red) +
+ " cop was inspecting #{file}.".color(:red))
+ end
+ end
+ end
+
+ def handle_error(e, message)
+ @errors << message
+ warn message
+ if debug?
+ puts e.message, e.backtrace
+ else
+ warn 'To see the complete backtrace run rubocop -d.'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/util.rb b/lib/rubocop/cop/util.rb
new file mode 100644
index 0000000..a922a7c
--- /dev/null
+++ b/lib/rubocop/cop/util.rb
@@ -0,0 +1,166 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # This module contains a collection of useful utility methods.
+ module Util
+ include PathUtil
+ extend AST::Sexp
+
+ PROC_NEW_NODE = s(:send, s(:const, nil, :Proc), :new)
+ EQUALS_ASGN_NODES = [:lvasgn, :ivasgn, :cvasgn, :gvasgn, :casgn, :masgn]
+ SHORTHAND_ASGN_NODES = [:op_asgn, :or_asgn, :and_asgn]
+ ASGN_NODES = EQUALS_ASGN_NODES + SHORTHAND_ASGN_NODES
+
+ # http://phrogz.net/programmingruby/language.html#table_18.4
+ # Backtick is added last just to help editors parse this code.
+ OPERATOR_METHODS = %w(
+ | ^ & <=> == === =~ > >= < <= << >>
+ + - * / % ** ~ +@ -@ [] []= ! != !~
+ ).map(&:to_sym) + [:'`']
+
+ module_function
+
+ def operator?(symbol)
+ OPERATOR_METHODS.include?(symbol)
+ end
+
+ def strip_quotes(str)
+ if str[0] == '"' || str[0] == "'"
+ str[0] = ''
+ str[-1] = ''
+ else
+ # we're dealing with %q or %Q
+ str[0, 3] = ''
+ str[-1] = ''
+ end
+
+ str
+ end
+
+ def block_length(block_node)
+ block_node.loc.end.line - block_node.loc.begin.line
+ end
+
+ def comment_line?(line_source)
+ line_source =~ /^\s*#/
+ end
+
+ def line_range(arg)
+ source_range = case arg
+ when Parser::Source::Range
+ arg
+ when Parser::AST::Node
+ arg.loc.expression
+ else
+ fail ArgumentError, "Invalid argument #{arg}"
+ end
+
+ source_range.begin.line..source_range.end.line
+ end
+
+ def const_name(node)
+ return nil if node.nil? || node.type != :const
+
+ const_names = []
+ const_node = node
+
+ loop do
+ namespace_node, name = *const_node
+ const_names << name
+ break unless namespace_node
+ break unless namespace_node.is_a?(Parser::AST::Node)
+ break if namespace_node.type == :cbase
+ const_node = namespace_node
+ end
+
+ const_names.reverse.join('::')
+ end
+
+ def command?(name, node)
+ return unless node.type == :send
+
+ receiver, method_name, _args = *node
+
+ # commands have no explicit receiver
+ !receiver && method_name == name
+ end
+
+ def lambda?(node)
+ fail 'Not a block node' unless node.type == :block
+
+ send_node, _block_args, _block_body = *node
+
+ command?(:lambda, send_node)
+ end
+
+ def proc?(node)
+ fail 'Not a block node' unless node.type == :block
+
+ send_node, _block_args, _block_body = *node
+
+ command?(:proc, send_node) || send_node == PROC_NEW_NODE
+ end
+
+ def lambda_or_proc?(node)
+ lambda?(node) || proc?(node)
+ end
+
+ def parentheses?(node)
+ node.loc.respond_to?(:end) && node.loc.end
+ end
+
+ def on_node(syms, sexp, excludes = [])
+ yield sexp if Array(syms).include?(sexp.type)
+
+ return if Array(excludes).include?(sexp.type)
+
+ sexp.children.each do |elem|
+ if elem.is_a?(Parser::AST::Node)
+ on_node(syms, elem, excludes) { |s| yield s }
+ end
+ end
+ end
+
+ def source_range(source_buffer, preceding_lines, begin_column,
+ column_count)
+ newline_length = 1
+ begin_pos = preceding_lines.reduce(0) do |a, e|
+ a + e.length + newline_length
+ end + begin_column
+ Parser::Source::Range.new(source_buffer, begin_pos,
+ begin_pos + column_count)
+ end
+
+ def range_with_surrounding_space(range, side = :both)
+ src = @processed_source.buffer.source
+ go_left = side == :left || side == :both
+ go_right = side == :right || side == :both
+ begin_pos = range.begin_pos
+ begin_pos -= 1 while go_left && src[begin_pos - 1] =~ /[ \t]/
+ end_pos = range.end_pos
+ end_pos += 1 while go_right && src[end_pos] =~ /[ \t]/
+ end_pos += 1 if go_right && src[end_pos] == "\n"
+ Parser::Source::Range.new(@processed_source.buffer, begin_pos, end_pos)
+ end
+
+ # Returns for example a bare `if` node if the given node is an `if` whith
+ # calls chained to the end of it.
+ def first_part_of_call_chain(node)
+ while node
+ case node.type
+ when :send
+ receiver, _method_name, _args = *node
+ node = receiver
+ when :block
+ method, _args, _body = *node
+ node = method
+ else
+ break
+ end
+ end
+ node
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/variable_inspector.rb b/lib/rubocop/cop/variable_inspector.rb
new file mode 100644
index 0000000..eb9d689
--- /dev/null
+++ b/lib/rubocop/cop/variable_inspector.rb
@@ -0,0 +1,427 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ # This module provides a way to track local variables and scopes of Ruby.
+ # This is intended to be used as mix-in, and the user class may override
+ # some of the hook methods.
+ module VariableInspector
+ VARIABLE_ASSIGNMENT_TYPE = :lvasgn
+ REGEXP_NAMED_CAPTURE_TYPE = :match_with_lvasgn
+ VARIABLE_ASSIGNMENT_TYPES =
+ [VARIABLE_ASSIGNMENT_TYPE, REGEXP_NAMED_CAPTURE_TYPE].freeze
+
+ METHOD_ARGUMENT_DECLARATION_TYPES = [
+ :arg, :optarg, :restarg,
+ :kwarg, :kwoptarg, :kwrestarg
+ ].freeze
+ BLOCK_ARGUMENT_DECLARATION_TYPE = :blockarg
+ ARGUMENT_DECLARATION_TYPES = (
+ METHOD_ARGUMENT_DECLARATION_TYPES + [BLOCK_ARGUMENT_DECLARATION_TYPE]
+ ).freeze
+ BLOCK_LOCAL_VARIABLE_DECLARATION_TYPE = :shadowarg
+ DECLARATION_TYPES = (
+ ARGUMENT_DECLARATION_TYPES + [BLOCK_LOCAL_VARIABLE_DECLARATION_TYPE]
+ ).freeze
+
+ LOGICAL_OPERATOR_ASSIGNMENT_TYPES = [:or_asgn, :and_asgn].freeze
+ OPERATOR_ASSIGNMENT_TYPES =
+ (LOGICAL_OPERATOR_ASSIGNMENT_TYPES + [:op_asgn]).freeze
+
+ MULTIPLE_ASSIGNMENT_TYPE = :masgn
+
+ VARIABLE_REFERENCE_TYPE = :lvar
+
+ POST_CONDITION_LOOP_TYPES = [:while_post, :until_post].freeze
+ LOOP_TYPES = (POST_CONDITION_LOOP_TYPES + [:while, :until, :for]).freeze
+
+ RESCUE_TYPE = :rescue
+
+ ZERO_ARITY_SUPER_TYPE = :zsuper
+
+ TWISTED_SCOPE_TYPES = [:block, :class, :sclass, :defs].freeze
+ SCOPE_TYPES = (TWISTED_SCOPE_TYPES + [:top_level, :module, :def]).freeze
+
+ def variable_table
+ @variable_table ||= VariableTable.new(self)
+ end
+
+ # Starting point.
+ def inspect_variables(root_node)
+ return unless root_node
+
+ # Wrap the root node with :top_level scope node.
+ top_level_node = wrap_with_top_level_node(root_node)
+
+ inspect_variables_in_scope(top_level_node)
+ end
+
+ def wrap_with_top_level_node(node)
+ # This is a custom node type, not defined in Parser.
+ Parser::AST::Node.new(:top_level, [node])
+ end
+
+ module_function :wrap_with_top_level_node
+
+ # This is called for each scope recursively.
+ def inspect_variables_in_scope(scope_node)
+ variable_table.push_scope(scope_node)
+ process_children(scope_node)
+ variable_table.pop_scope
+ end
+
+ def process_children(origin_node)
+ origin_node.children.each do |child|
+ next unless child.is_a?(Parser::AST::Node)
+ next if scanned_node?(child)
+ process_node(child)
+ end
+ end
+
+ def process_node(node)
+ catch(:skip_children) do
+ dispatch_node(node)
+ process_children(node)
+ end
+ end
+
+ def skip_children!
+ throw :skip_children
+ end
+
+ # rubocop:disable MethodLength, CyclomaticComplexity
+ def dispatch_node(node)
+ case node.type
+ when *DECLARATION_TYPES
+ process_variable_declaration(node)
+ when VARIABLE_ASSIGNMENT_TYPE
+ process_variable_assignment(node)
+ when REGEXP_NAMED_CAPTURE_TYPE
+ process_regexp_named_captures(node)
+ when *OPERATOR_ASSIGNMENT_TYPES
+ process_variable_operator_assignment(node)
+ when MULTIPLE_ASSIGNMENT_TYPE
+ process_variable_multiple_assignment(node)
+ when VARIABLE_REFERENCE_TYPE
+ process_variable_referencing(node)
+ when *LOOP_TYPES
+ process_loop(node)
+ when RESCUE_TYPE
+ process_rescue(node)
+ when ZERO_ARITY_SUPER_TYPE
+ process_zero_arity_super(node)
+ when *SCOPE_TYPES
+ process_scope(node)
+ end
+ end
+ # rubocop:enable MethodLength, CyclomaticComplexity
+
+ def process_variable_declaration(node)
+ # restarg would have no name:
+ #
+ # def initialize(*)
+ # end
+ return if node.type == :restarg && node.children.empty?
+
+ variable_name = node.children.first
+ variable_table.declare_variable(variable_name, node)
+ end
+
+ def process_variable_assignment(node)
+ name = node.children.first
+
+ unless variable_table.variable_exist?(name)
+ variable_table.declare_variable(name, node)
+ end
+
+ # Need to scan rhs before assignment so that we can mark previous
+ # assignments as referenced if rhs has referencing to the variable
+ # itself like:
+ #
+ # foo = 1
+ # foo = foo + 1
+ process_children(node)
+
+ variable_table.assign_to_variable(name, node)
+
+ skip_children!
+ end
+
+ def process_regexp_named_captures(node)
+ regexp_node, rhs_node = *node
+
+ regexp_string = regexp_node.children[0].children[0]
+ regexp = Regexp.new(regexp_string)
+ variable_names = regexp.named_captures.keys
+
+ variable_names.each do |name|
+ unless variable_table.variable_exist?(name)
+ variable_table.declare_variable(name, node)
+ end
+ end
+
+ process_node(rhs_node)
+ process_node(regexp_node)
+
+ variable_names.each do |name|
+ variable_table.assign_to_variable(name, node)
+ end
+
+ skip_children!
+ end
+
+ def process_variable_operator_assignment(node)
+ if LOGICAL_OPERATOR_ASSIGNMENT_TYPES.include?(node.type)
+ asgn_node, rhs_node = *node
+ else
+ asgn_node, _operator, rhs_node = *node
+ end
+
+ return unless asgn_node.type == :lvasgn
+
+ name = asgn_node.children.first
+
+ unless variable_table.variable_exist?(name)
+ variable_table.declare_variable(name, asgn_node)
+ end
+
+ # The following statements:
+ #
+ # foo = 1
+ # foo += foo = 2
+ # # => 3
+ #
+ # are equivalent to:
+ #
+ # foo = 1
+ # foo = foo + (foo = 2)
+ # # => 3
+ #
+ # So, at operator assignment node, we need to reference the variable
+ # before processing rhs nodes.
+
+ variable_table.reference_variable(name, node)
+ process_node(rhs_node)
+ variable_table.assign_to_variable(name, asgn_node)
+
+ skip_children!
+ end
+
+ def process_variable_multiple_assignment(node)
+ lhs_node, rhs_node = *node
+ process_node(rhs_node)
+ process_node(lhs_node)
+ skip_children!
+ end
+
+ def process_variable_referencing(node)
+ name = node.children.first
+ variable_table.reference_variable(name, node)
+ end
+
+ def process_loop(node)
+ if POST_CONDITION_LOOP_TYPES.include?(node.type)
+ # See the comment at the end of file for this behavior.
+ condition_node, body_node = *node
+ process_node(body_node)
+ process_node(condition_node)
+ else
+ process_children(node)
+ end
+
+ mark_assignments_as_referenced_in_loop(node)
+
+ skip_children!
+ end
+
+ def process_rescue(node)
+ resbody_nodes = node.children.select do |child|
+ next false unless child.is_a?(Parser::AST::Node)
+ child.type == :resbody
+ end
+
+ contain_retry = resbody_nodes.any? do |resbody_node|
+ scan(resbody_node) do |node_in_resbody|
+ break true if node_in_resbody.type == :retry
+ end
+ end
+
+ # Treat begin..rescue..end with retry as a loop.
+ process_loop(node) if contain_retry
+ end
+
+ def process_zero_arity_super(node)
+ variable_table.accessible_variables.each do |variable|
+ next unless variable.method_argument?
+ variable.reference!(node)
+ end
+ end
+
+ def process_scope(node)
+ if TWISTED_SCOPE_TYPES.include?(node.type)
+ # See the comment at the end of file for this behavior.
+ twisted_nodes = [node.children[0]]
+ twisted_nodes << node.children[1] if node.type == :class
+ twisted_nodes.compact!
+
+ twisted_nodes.each do |twisted_node|
+ process_node(twisted_node)
+ scanned_nodes << twisted_node
+ end
+ end
+
+ inspect_variables_in_scope(node)
+ skip_children!
+ end
+
+ # Mark all assignments which are referenced in the same loop
+ # as referenced by ignoring AST order since they would be referenced
+ # in next iteration.
+ def mark_assignments_as_referenced_in_loop(node)
+ referenced_variable_names_in_loop, assignment_nodes_in_loop =
+ find_variables_in_loop(node)
+
+ referenced_variable_names_in_loop.each do |name|
+ variable = variable_table.find_variable(name)
+ # Non related references which are catched in the above scan
+ # would be skipped here.
+ next unless variable
+ variable.assignments.each do |assignment|
+ next if assignment_nodes_in_loop.none? do |assignment_node|
+ assignment_node.equal?(assignment.node)
+ end
+ assignment.reference!
+ end
+ end
+ end
+
+ def find_variables_in_loop(loop_node)
+ referenced_variable_names_in_loop = []
+ assignment_nodes_in_loop = []
+
+ # #scan does not consider scope,
+ # but we don't need to care about it here.
+ scan(loop_node) do |node|
+ case node.type
+ when :lvar
+ referenced_variable_names_in_loop << node.children.first
+ when *OPERATOR_ASSIGNMENT_TYPES
+ asgn_node = node.children.first
+ if asgn_node.type == :lvasgn
+ referenced_variable_names_in_loop << asgn_node.children.first
+ end
+ when :lvasgn
+ assignment_nodes_in_loop << node
+ end
+ end
+
+ [referenced_variable_names_in_loop, assignment_nodes_in_loop]
+ end
+
+ # Simple recursive scan
+ def scan(node, &block)
+ node.children.each do |child|
+ next unless child.is_a?(Parser::AST::Node)
+ yield child
+ scan(child, &block)
+ end
+ nil
+ end
+
+ # Use Node#equal? for accurate check.
+ def scanned_node?(node)
+ scanned_nodes.any? do |scanned_node|
+ scanned_node.equal?(node)
+ end
+ end
+
+ def scanned_nodes
+ @scanned_nodes ||= []
+ end
+
+ # Hooks
+
+ def before_entering_scope(scope)
+ end
+
+ def after_entering_scope(scope)
+ end
+
+ def before_leaving_scope(scope)
+ end
+
+ def after_leaving_scope(scope)
+ end
+
+ def before_declaring_variable(variable_variable)
+ end
+
+ def after_declaring_variable(variable_variable)
+ end
+
+ # Post condition loops
+ #
+ # Loop body nodes need to be scanned first.
+ #
+ # Ruby:
+ # begin
+ # foo = 1
+ # end while foo > 10
+ # puts foo
+ #
+ # AST:
+ # (begin
+ # (while-post
+ # (send
+ # (lvar :foo) :>
+ # (int 10))
+ # (kwbegin
+ # (lvasgn :foo
+ # (int 1))))
+ # (send nil :puts
+ # (lvar :foo)))
+
+ # Twisted scope types
+ #
+ # The variable foo belongs to the top level scope,
+ # but in AST, it's under the block node.
+ #
+ # Ruby:
+ # some_method(foo = 1) do
+ # end
+ # puts foo
+ #
+ # AST:
+ # (begin
+ # (block
+ # (send nil :some_method
+ # (lvasgn :foo
+ # (int 1)))
+ # (args) nil)
+ # (send nil :puts
+ # (lvar :foo)))
+ #
+ # So the the method argument nodes need to be processed
+ # in current scope.
+ #
+ # Same thing.
+ #
+ # Ruby:
+ # instance = Object.new
+ # class << instance
+ # foo = 1
+ # end
+ #
+ # AST:
+ # (begin
+ # (lvasgn :instance
+ # (send
+ # (const nil :Object) :new))
+ # (sclass
+ # (lvar :instance)
+ # (begin
+ # (lvasgn :foo
+ # (int 1))
+ end
+ end
+end
diff --git a/lib/rubocop/cop/variable_inspector/assignment.rb b/lib/rubocop/cop/variable_inspector/assignment.rb
new file mode 100644
index 0000000..8e73fca
--- /dev/null
+++ b/lib/rubocop/cop/variable_inspector/assignment.rb
@@ -0,0 +1,103 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module VariableInspector
+ # This class represents each assignment of a variable.
+ class Assignment
+ include Locatable
+
+ MULTIPLE_LEFT_HAND_SIDE_TYPE = :mlhs
+ REFERENCE_PENETRABLE_BRANCH_TYPES = %w(rescue_main ensure_main).freeze
+
+ attr_reader :node, :variable, :referenced
+ alias_method :referenced?, :referenced
+
+ def initialize(node, variable)
+ unless VARIABLE_ASSIGNMENT_TYPES.include?(node.type)
+ fail ArgumentError,
+ "Node type must be any of #{VARIABLE_ASSIGNMENT_TYPES}, " \
+ "passed #{node.type}"
+ end
+
+ @node = node
+ @variable = variable
+ @referenced = false
+ end
+
+ def name
+ @node.children.first
+ end
+
+ def scope
+ @variable.scope
+ end
+
+ def reference!
+ @referenced = true
+ end
+
+ def used?
+ @variable.captured_by_block? || @referenced
+ end
+
+ def reference_penetrable?
+ REFERENCE_PENETRABLE_BRANCH_TYPES.include?(branch_type)
+ end
+
+ def regexp_named_capture?
+ @node.type == REGEXP_NAMED_CAPTURE_TYPE
+ end
+
+ def operator_assignment?
+ return false unless meta_assignment_node
+ OPERATOR_ASSIGNMENT_TYPES.include?(meta_assignment_node.type)
+ end
+
+ def multiple_assignment?
+ return false unless meta_assignment_node
+ meta_assignment_node.type == MULTIPLE_ASSIGNMENT_TYPE
+ end
+
+ def operator
+ assignment_node = meta_assignment_node || @node
+ assignment_node.loc.operator.source
+ end
+
+ def meta_assignment_node
+ if instance_variable_defined?(:@meta_assignment_node)
+ return @meta_assignment_node
+ end
+
+ @meta_assignment_node = nil
+
+ return unless parent_node
+
+ if OPERATOR_ASSIGNMENT_TYPES.include?(parent_node.type) &&
+ parent_node.children.index(@node) == 0
+ return @meta_assignment_node = parent_node
+ end
+
+ return unless grantparent_node
+
+ if parent_node.type == MULTIPLE_LEFT_HAND_SIDE_TYPE &&
+ grantparent_node.type == MULTIPLE_ASSIGNMENT_TYPE
+ return @meta_assignment_node = grantparent_node
+ end
+
+ nil
+ end
+
+ private
+
+ def parent_node
+ ancestor_nodes_in_scope.last
+ end
+
+ def grantparent_node
+ ancestor_nodes_in_scope[-2]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/variable_inspector/locatable.rb b/lib/rubocop/cop/variable_inspector/locatable.rb
new file mode 100644
index 0000000..ef26666
--- /dev/null
+++ b/lib/rubocop/cop/variable_inspector/locatable.rb
@@ -0,0 +1,162 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module VariableInspector
+ # This module provides a way to locate the conditional branch the node is
+ # in. This is intended to be used as mix-in.
+ module Locatable
+ BRANCH_TYPES = [:if, :case].freeze
+ CONDITION_INDEX_OF_BRANCH_NODE = 0
+
+ LOGICAL_OPERATOR_TYPES = [:and, :or].freeze
+ LEFT_SIDE_INDEX_OF_LOGICAL_OPERATOR_NODE = 0
+
+ ENSURE_TYPE = :ensure
+ ENSURE_INDEX_OF_ENSURE_NODE = 1
+
+ def node
+ fail '#node must be declared!'
+ end
+
+ def scope
+ fail '#scope must be declared!'
+ end
+
+ def inside_of_branch?
+ branch_point_node
+ end
+
+ def branch_id
+ return nil unless inside_of_branch?
+ @branch_id ||= [branch_point_node.object_id, branch_type].join('_')
+ end
+
+ def branch_type
+ return nil unless inside_of_branch?
+ @branch_type ||= [branch_point_node.type, branch_body_name].join('_')
+ end
+
+ # Inner if, case, rescue, or ensure node.
+ def branch_point_node
+ if instance_variable_defined?(:@branch_point_node)
+ return @branch_point_node
+ end
+
+ set_branch_point_and_body_nodes!
+ @branch_point_node
+ end
+
+ # A child node of #branch_point_node this assignment belongs.
+ def branch_body_node
+ if instance_variable_defined?(:@branch_body_node)
+ return @branch_body_node
+ end
+
+ set_branch_point_and_body_nodes!
+ @branch_body_node
+ end
+
+ def ancestor_nodes_in_scope
+ @ancestor_nodes_in_scope ||= scope.ancestors_of_node(@node)
+ end
+
+ private
+
+ def branch_body_name
+ case branch_point_node.type
+ when :if
+ if_body_name
+ when :case
+ case_body_name
+ when *LOGICAL_OPERATOR_TYPES
+ logical_operator_body_name
+ when RESCUE_TYPE
+ rescue_body_name
+ when ENSURE_TYPE
+ ensure_body_name
+ else
+ fail InvalidBranchBodyError
+ end
+ rescue InvalidBranchBodyError
+ raise InvalidBranchBodyError,
+ "Invalid body index #{body_index} of #{branch_point_node.type}"
+ end
+
+ def if_body_name
+ case body_index
+ when 1 then 'true'
+ when 2 then 'false'
+ else fail InvalidBranchBodyError
+ end
+ end
+
+ def case_body_name
+ if branch_body_node.type == :when
+ "when#{body_index - 1}"
+ else
+ 'else'
+ end
+ end
+
+ def logical_operator_body_name
+ case body_index
+ when 1 then 'right'
+ else fail InvalidBranchBodyError
+ end
+ end
+
+ def rescue_body_name
+ if body_index == 0
+ 'main'
+ elsif branch_body_node.type == :resbody
+ "rescue#{body_index - 1}"
+ else
+ 'else'
+ end
+ end
+
+ def ensure_body_name
+ case body_index
+ when 0 then 'main'
+ else fail InvalidBranchBodyError
+ end
+ end
+
+ def body_index
+ branch_point_node.children.index(branch_body_node)
+ end
+
+ def set_branch_point_and_body_nodes!
+ ancestors_and_self_nodes = ancestor_nodes_in_scope + [@node]
+
+ ancestors_and_self_nodes.reverse.each_cons(2) do |child, parent|
+ next unless branch?(parent, child)
+ @branch_point_node = parent
+ @branch_body_node = child
+ break
+ end
+ end
+
+ def branch?(parent_node, child_node)
+ child_index = parent_node.children.index(child_node)
+
+ case parent_node.type
+ when *BRANCH_TYPES
+ child_index != CONDITION_INDEX_OF_BRANCH_NODE
+ when *LOGICAL_OPERATOR_TYPES
+ child_index != LEFT_SIDE_INDEX_OF_LOGICAL_OPERATOR_NODE
+ when RESCUE_TYPE
+ true
+ when ENSURE_TYPE
+ child_index != ENSURE_INDEX_OF_ENSURE_NODE
+ else
+ false
+ end
+ end
+
+ class InvalidBranchBodyError < StandardError; end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/variable_inspector/reference.rb b/lib/rubocop/cop/variable_inspector/reference.rb
new file mode 100644
index 0000000..0f5e6c9
--- /dev/null
+++ b/lib/rubocop/cop/variable_inspector/reference.rb
@@ -0,0 +1,31 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module VariableInspector
+ # This class represents each reference of a variable.
+ class Reference
+ include Locatable
+
+ VARIABLE_REFERENCE_TYPES = (
+ [VARIABLE_REFERENCE_TYPE] +
+ OPERATOR_ASSIGNMENT_TYPES +
+ [ZERO_ARITY_SUPER_TYPE]
+ ).freeze
+
+ attr_reader :node, :scope
+
+ def initialize(node, scope)
+ unless VARIABLE_REFERENCE_TYPES.include?(node.type)
+ fail ArgumentError,
+ "Node type must be any of #{VARIABLE_REFERENCE_TYPES}, " \
+ "passed #{node.type}"
+ end
+
+ @node = node
+ @scope = scope
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/variable_inspector/scope.rb b/lib/rubocop/cop/variable_inspector/scope.rb
new file mode 100644
index 0000000..6057bbb
--- /dev/null
+++ b/lib/rubocop/cop/variable_inspector/scope.rb
@@ -0,0 +1,71 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module VariableInspector
+ # A Scope represents a context of local variable visibility.
+ # This is a place where local variables belong to.
+ # A scope instance holds a scope node and variable entries.
+ class Scope
+ attr_reader :node, :variables
+
+ def initialize(node)
+ # Accept begin node for top level scope.
+ unless SCOPE_TYPES.include?(node.type) || node.type == :begin
+ fail ArgumentError,
+ "Node type must be any of #{SCOPE_TYPES}, " \
+ "passed #{node.type}"
+ end
+ @node = node
+ @variables = {}
+ end
+
+ def ==(other)
+ @node.equal?(other.node)
+ end
+
+ def body_node
+ child_index = case @node.type
+ when :top_level then 0
+ when :module, :sclass then 1
+ when :def, :class, :block then 2
+ when :defs then 3
+ end
+
+ @node.children[child_index]
+ end
+
+ def ancestors_of_node(target_node)
+ ASTScanner.scan(@node) do |scanning_node, ancestor_nodes|
+ return ancestor_nodes[1..-1] if scanning_node.equal?(target_node)
+ end
+
+ fail "Node #{target_node} is not found in scope #{@node}"
+ end
+
+ # This class provides a ways to scan AST with tracking ancestor nodes.
+ class ASTScanner
+ def self.scan(node, &block)
+ new.scan(node, &block)
+ end
+
+ def initialize
+ @ancestor_nodes = []
+ end
+
+ def scan(node, &block)
+ @ancestor_nodes.push(node)
+
+ node.children.each do |child|
+ next unless child.is_a?(Parser::AST::Node)
+ yield child, @ancestor_nodes
+ scan(child, &block)
+ end
+
+ @ancestor_nodes.pop
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/variable_inspector/variable.rb b/lib/rubocop/cop/variable_inspector/variable.rb
new file mode 100644
index 0000000..dd20518
--- /dev/null
+++ b/lib/rubocop/cop/variable_inspector/variable.rb
@@ -0,0 +1,87 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module VariableInspector
+ # A Variable represents existance of a local variable.
+ # This holds a variable declaration node,
+ # and some states of the variable.
+ class Variable
+ VARIABLE_DECLARATION_TYPES =
+ (VARIABLE_ASSIGNMENT_TYPES + DECLARATION_TYPES).freeze
+
+ attr_reader :name, :declaration_node, :scope,
+ :assignments, :captured_by_block
+ alias_method :captured_by_block?, :captured_by_block
+
+ def initialize(name, declaration_node, scope)
+ unless VARIABLE_DECLARATION_TYPES.include?(declaration_node.type)
+ fail ArgumentError,
+ "Node type must be any of #{VARIABLE_DECLARATION_TYPES}, " \
+ "passed #{declaration_node.type}"
+ end
+
+ @name = name.to_sym
+ @declaration_node = declaration_node
+ @scope = scope
+
+ @assignments = []
+ @captured_by_block = false
+ end
+
+ def assign(node)
+ @assignments << Assignment.new(node, self)
+ end
+
+ def referenced?
+ @assignments.any?(&:referenced?)
+ end
+
+ def reference!(node)
+ reference = Reference.new(node, @scope)
+ consumed_branch_ids = Set.new
+
+ @assignments.reverse_each do |assignment|
+ next if consumed_branch_ids.include?(assignment.branch_id)
+
+ assignment.reference!
+
+ if assignment.inside_of_branch?
+ break if assignment.branch_id == reference.branch_id
+
+ unless assignment.reference_penetrable?
+ consumed_branch_ids << assignment.branch_id
+ end
+ else
+ break
+ end
+ end
+ end
+
+ def capture_with_block!
+ @captured_by_block = true
+ end
+
+ # This is a convenient way to check whether the variable is used
+ # in its entire variable lifetime.
+ # For more precise usage check, refer Assignment#used?.
+ #
+ # Once the variable is captured by a block, we have no idea
+ # when, where and how many times the block would be invoked
+ # and it means we cannot track the usage of the variable.
+ # So we consider it's used to suppress false positive offenses.
+ def used?
+ @captured_by_block || referenced?
+ end
+
+ def method_argument?
+ METHOD_ARGUMENT_DECLARATION_TYPES.include?(@declaration_node.type)
+ end
+
+ def block_local_variable?
+ @declaration_node.type == BLOCK_LOCAL_VARIABLE_DECLARATION_TYPE
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/cop/variable_inspector/variable_table.rb b/lib/rubocop/cop/variable_inspector/variable_table.rb
new file mode 100644
index 0000000..5355ea6
--- /dev/null
+++ b/lib/rubocop/cop/variable_inspector/variable_table.rb
@@ -0,0 +1,129 @@
+# encoding: utf-8
+
+module Rubocop
+ module Cop
+ module VariableInspector
+ # A VariableTable manages the lifetime of all scopes and local variables
+ # in a program.
+ # This holds scopes as stack structure, and provides a way to add local
+ # variables to current scope and find local variables by considering
+ # variable visibility of the current scope.
+ class VariableTable
+ def initialize(hook_receiver = nil)
+ @hook_receiver = hook_receiver
+ end
+
+ def invoke_hook(hook_name, *args)
+ @hook_receiver.send(hook_name, *args) if @hook_receiver
+ end
+
+ def scope_stack
+ @scope_stack ||= []
+ end
+
+ def push_scope(scope_node)
+ scope = Scope.new(scope_node)
+ invoke_hook(:before_entering_scope, scope)
+ scope_stack.push(scope)
+ invoke_hook(:after_entering_scope, scope)
+ scope
+ end
+
+ def pop_scope
+ scope = current_scope
+ invoke_hook(:before_leaving_scope, scope)
+ scope_stack.pop
+ invoke_hook(:after_leaving_scope, scope)
+ scope
+ end
+
+ def current_scope
+ scope_stack.last
+ end
+
+ def current_scope_level
+ scope_stack.count
+ end
+
+ def declare_variable(name, node)
+ variable = Variable.new(name, node, current_scope)
+ invoke_hook(:before_declaring_variable, variable)
+ current_scope.variables[variable.name] = variable
+ invoke_hook(:after_declaring_variable, variable)
+ variable
+ end
+
+ def assign_to_variable(name, node)
+ variable = find_variable(name)
+
+ unless variable
+ fail "Assigning to undeclared local variable \"#{name}\" " \
+ "at #{node.loc.expression}, #{node.inspect}"
+ end
+
+ variable.assign(node)
+ mark_variable_as_captured_by_block_if_so(variable)
+ end
+
+ def reference_variable(name, node)
+ variable = find_variable(name)
+
+ # In this code:
+ #
+ # foo = 1 unless foo
+ #
+ # (if
+ # (lvar :foo) nil
+ # (lvasgn :foo
+ # (int 1)))
+ #
+ # Parser knows whether the foo is a variable or method invocation.
+ # This means that if a :lvar node is shown in AST, the variable is
+ # assumed to be already declared, even if we haven't seen any :lvasgn
+ # or :arg node before the :lvar node.
+ #
+ # We don't invoke #declare_variable here otherwise
+ # Variable#declaration_node will be :lvar node, that is actually not.
+ # So just skip.
+ return unless variable
+
+ variable.reference!(node)
+ mark_variable_as_captured_by_block_if_so(variable)
+ end
+
+ def find_variable(name)
+ name = name.to_sym
+
+ scope_stack.reverse_each do |scope|
+ variable = scope.variables[name]
+ return variable if variable
+ # Only block scope allows referencing outer scope variables.
+ return nil unless scope.node.type == :block
+ end
+
+ nil
+ end
+
+ def variable_exist?(name)
+ find_variable(name)
+ end
+
+ def accessible_variables
+ scope_stack.reverse_each.reduce([]) do |variables, scope|
+ variables.concat(scope.variables.values)
+ break variables unless scope.node.type == :block
+ variables
+ end
+ end
+
+ private
+
+ def mark_variable_as_captured_by_block_if_so(variable)
+ return unless current_scope.node.type == :block
+ return if variable.scope == current_scope
+ variable.capture_with_block!
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/file_inspector.rb b/lib/rubocop/file_inspector.rb
new file mode 100644
index 0000000..3985fca
--- /dev/null
+++ b/lib/rubocop/file_inspector.rb
@@ -0,0 +1,149 @@
+# encoding: utf-8
+
+module Rubocop
+ # This class handles the processing of files, which includes dealing with
+ # formatters and letting cops inspect the files.
+ class FileInspector
+ def initialize(options)
+ @options = options
+ @errors = []
+ end
+
+ # Takes a block which it calls once per inspected file. The block shall
+ # return true if the caller wants to break the loop early.
+ def process_files(target_files, config_store)
+ target_files.each(&:freeze).freeze
+ inspected_files = []
+ any_failed = false
+
+ formatter_set.started(target_files)
+
+ target_files.each do |file|
+ break if yield
+ offenses = process_file(file, config_store)
+
+ any_failed = true if offenses.any? do |o|
+ o.severity >= fail_level
+ end
+ inspected_files << file
+ end
+
+ formatter_set.finished(inspected_files.freeze)
+
+ formatter_set.close_output_files
+ any_failed
+ end
+
+ def display_error_summary
+ return if @errors.empty?
+ plural = @errors.count > 1 ? 's' : ''
+ warn "\n#{@errors.count} error#{plural} occurred:".color(:red)
+ @errors.each { |error| warn error }
+ warn 'Errors are usually caused by RuboCop bugs.'
+ warn 'Please, report your problems to RuboCop\'s issue tracker.'
+ warn 'Mention the following information in the issue report:'
+ warn Rubocop::Version.version(true)
+ end
+
+ private
+
+ def process_file(file, config_store)
+ puts "Scanning #{file}" if @options[:debug]
+ processed_source, offenses = process_source(file)
+
+ if offenses.any?
+ formatter_set.file_started(file, offenses)
+ formatter_set.file_finished(file, offenses.compact.sort.freeze)
+ return offenses
+ end
+
+ formatter_set.file_started(
+ file, cop_disabled_line_ranges: processed_source.disabled_line_ranges)
+
+ # When running with --auto-correct, we need to inspect the file (which
+ # includes writing a corrected version of it) until no more corrections
+ # are made. This is because automatic corrections can introduce new
+ # offenses. In the normal case the loop is only executed once.
+ loop do
+ new_offenses, updated_source_file =
+ inspect_file(processed_source, config_store)
+ offenses += new_offenses.reject { |n| offenses.include?(n) }
+ break unless updated_source_file
+
+ # We have to reprocess the source to pickup the changes. Since the
+ # change could (theoretically) introduce parsing errors, we break the
+ # loop if we find any.
+ processed_source, parse_offenses = process_source(file)
+ offenses += parse_offenses if parse_offenses.any?
+ end
+
+ formatter_set.file_finished(file, offenses.compact.sort.freeze)
+ offenses
+ end
+
+ def process_source(file)
+ begin
+ processed_source = SourceParser.parse_file(file)
+ rescue Encoding::UndefinedConversionError, ArgumentError => e
+ range = Struct.new(:line, :column, :source_line).new(1, 0, '')
+ return [
+ nil,
+ [Cop::Offense.new(:fatal, range, e.message.capitalize + '.',
+ 'Parser')]]
+ end
+
+ [processed_source, []]
+ end
+
+ def inspect_file(processed_source, config_store)
+ config = config_store.for(processed_source.file_path)
+ team = Cop::Team.new(mobilized_cop_classes(config), config, @options)
+ offenses = team.inspect_file(processed_source)
+ @errors.concat(team.errors)
+ [offenses, team.updated_source_file?]
+ end
+
+ def mobilized_cop_classes(config)
+ @mobilized_cop_classes ||= {}
+ @mobilized_cop_classes[config.object_id] ||= begin
+ cop_classes = Cop::Cop.all
+
+ if @options[:only]
+ cop_classes.select! { |c| c.cop_name == @options[:only] }
+ else
+ # filter out Rails cops unless requested
+ cop_classes.reject!(&:rails?) unless run_rails_cops?(config)
+
+ # filter out style cops when --lint is passed
+ cop_classes.select!(&:lint?) if @options[:lint]
+ end
+
+ cop_classes
+ end
+ end
+
+ def run_rails_cops?(config)
+ @options[:rails] || config['AllCops']['RunRailsCops']
+ end
+
+ def formatter_set
+ @formatter_set ||= begin
+ set = Formatter::FormatterSet.new
+ pairs = @options[:formatters] || [[Options::DEFAULT_FORMATTER]]
+ pairs.each do |formatter_key, output_path|
+ set.add_formatter(formatter_key, output_path)
+ end
+ set
+ rescue => error
+ warn error.message
+ $stderr.puts error.backtrace
+ exit(1)
+ end
+ end
+
+ def fail_level
+ @fail_level ||= Rubocop::Cop::Severity.new(
+ @options[:fail_level] || :refactor)
+ end
+ end
+end
diff --git a/lib/rubocop/formatter/base_formatter.rb b/lib/rubocop/formatter/base_formatter.rb
new file mode 100644
index 0000000..2a6f80d
--- /dev/null
+++ b/lib/rubocop/formatter/base_formatter.rb
@@ -0,0 +1,119 @@
+# encoding: utf-8
+
+# rubocop:disable LineLength
+
+module Rubocop
+ module Formatter
+ # Abstract base class for formatter, implements all public API methods.
+ #
+ # ## Creating Custom Formatter
+ #
+ # You can create a custom formatter by subclassing
+ # `Rubocop::Formatter::BaseFormatter` and overriding some methods,
+ # or by implementing all the methods by duck typing.
+ #
+ # ## Using Custom Formatter in Command Line
+ #
+ # You can tell RuboCop to use your custom formatter with a combination of
+ # `--format` and `--require` option.
+ # For example, when you have defined `MyCustomFormatter` in
+ # `./path/to/my_custom_formatter.rb`, you would type this command:
+ #
+ # rubocop --require ./path/to/my_custom_formatter --format MyCustomFormatter
+ #
+ # Note: The path passed to `--require` is directly passed to
+ # `Kernel.require`.
+ # If your custom formatter file is not in `$LOAD_PATH`,
+ # you need to specify the path as relative path prefixed with `./`
+ # explicitly, or absolute path.
+ #
+ # ## Method Invocation Order
+ #
+ # For example, when RuboCop inspects 2 files,
+ # the invocation order should be like this:
+ #
+ # * `#initialize`
+ # * `#started`
+ # * `#file_started`
+ # * `#file_finished`
+ # * `#file_started`
+ # * `#file_finished`
+ # * `#finished`
+ #
+ class BaseFormatter
+ # rubocop:enable LineLength
+
+ # @api public
+ #
+ # @!attribute [r] output
+ #
+ # @return [IO]
+ # the IO object passed to `#initialize`
+ #
+ # @see #initialize
+ attr_reader :output
+
+ # @api public
+ #
+ # @param output [IO]
+ # `$stdout` or opened file
+ def initialize(output)
+ @output = output
+ end
+
+ # @api public
+ #
+ # Invoked once before any files are inspected.
+ #
+ # @param target_files [Array(String)]
+ # all target file paths to be inspected
+ #
+ # @return [void]
+ def started(target_files)
+ end
+
+ # @api public
+ #
+ # Invoked at the beginning of inspecting each files.
+ #
+ # @param file [String]
+ # the file path
+ #
+ # @param options [Hash]
+ # file specific information, currently this is always empty.
+ #
+ # @return [void]
+ def file_started(file, options)
+ end
+
+ # @api public
+ #
+ # Invoked at the end of inspecting each files.
+ #
+ # @param file [String]
+ # the file path
+ #
+ # @param offenses [Array(Rubocop::Cop::Offense)]
+ # all detected offenses for the file
+ #
+ # @return [void]
+ #
+ # @see Rubocop::Cop::Offense
+ def file_finished(file, offenses)
+ end
+
+ # @api public
+ #
+ # Invoked after all files are inspected, or interrupted by user.
+ #
+ # @param inspected_files [Array(String)]
+ # the inspected file paths.
+ # This would be same as `target_files` passed to `#started`
+ # unless RuboCop is interrupted by user.
+ #
+ # @return [void]
+ def finished(inspected_files)
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/formatter/clang_style_formatter.rb b/lib/rubocop/formatter/clang_style_formatter.rb
new file mode 100644
index 0000000..2ff1c07
--- /dev/null
+++ b/lib/rubocop/formatter/clang_style_formatter.rb
@@ -0,0 +1,35 @@
+# encoding: utf-8
+
+module Rubocop
+ module Formatter
+ # This formatter formats report data in clang style.
+ # The precise location of the problem is shown together with the
+ # relevant source code.
+ class ClangStyleFormatter < SimpleTextFormatter
+ def report_file(file, offenses)
+ offenses.each do |o|
+ output.printf("%s:%d:%d: %s: %s\n",
+ cyan(smart_path(file)), o.line, o.real_column,
+ colored_severity_code(o), message(o))
+
+ source_line = o.location.source_line
+
+ unless source_line.blank?
+ output.puts(source_line)
+ output.puts(highlight_line(o.location))
+ end
+ end
+ end
+
+ def highlight_line(location)
+ column_length = if location.begin.line == location.end.line
+ location.column_range.count
+ else
+ location.source_line.length - location.column
+ end
+
+ ' ' * location.column + '^' * column_length
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/formatter/colorizable.rb b/lib/rubocop/formatter/colorizable.rb
new file mode 100644
index 0000000..7c0abbc
--- /dev/null
+++ b/lib/rubocop/formatter/colorizable.rb
@@ -0,0 +1,37 @@
+# encoding: utf-8
+
+module Rubocop
+ module Formatter
+ # This mix-in module provides string coloring methods for terminals.
+ # It automatically disables coloring if coloring is disabled in the process
+ # globally or the formatter's output is not a terminal.
+ module Colorizable
+ def rainbow
+ @rainbow ||= begin
+ rainbow = Rainbow.new
+ rainbow.enabled = false unless output.tty?
+ rainbow
+ end
+ end
+
+ def colorize(string, *args)
+ rainbow.wrap(string).color(*args)
+ end
+
+ [
+ :black,
+ :red,
+ :green,
+ :yellow,
+ :blue,
+ :magenta,
+ :cyan,
+ :white
+ ].each do |color|
+ define_method(color) do |string|
+ colorize(string, color)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/formatter/disabled_config_formatter.rb b/lib/rubocop/formatter/disabled_config_formatter.rb
new file mode 100644
index 0000000..1eacb63
--- /dev/null
+++ b/lib/rubocop/formatter/disabled_config_formatter.rb
@@ -0,0 +1,65 @@
+# encoding: utf-8
+
+module Rubocop
+ module Formatter
+ # This formatter displays a YAML configuration file where all cops that
+ # detected any offenses are configured to not detect the offense.
+ class DisabledConfigFormatter < BaseFormatter
+ HEADING =
+ ['# This configuration was generated by `rubocop --auto-gen-config`',
+ "# on #{Time.now} using RuboCop version #{Version.version}.",
+ '# The point is for the user to remove these configuration records',
+ '# one by one as the offenses are removed from the code base.',
+ '# Note that changes in the inspected code, or installation of new',
+ '# versions of RuboCop, may require this file to be generated again.']
+ .join("\n")
+
+ @config_to_allow_offenses = {}
+
+ COPS = Cop::Cop.all.group_by { |c| c.cop_name }
+
+ class << self
+ attr_accessor :config_to_allow_offenses
+ end
+
+ def file_finished(file, offenses)
+ @cops_with_offenses ||= Hash.new(0)
+ offenses.each { |o| @cops_with_offenses[o.cop_name] += 1 }
+ end
+
+ def finished(inspected_files)
+ output.puts HEADING
+
+ # Syntax isn't a real cop and it can't be disabled.
+ @cops_with_offenses.delete('Syntax')
+
+ @cops_with_offenses.sort.each do |cop_name, offense_count|
+ output.puts
+ cfg = self.class.config_to_allow_offenses[cop_name]
+ cfg ||= { 'Enabled' => false }
+ output_cop_comments(output, cfg, cop_name, offense_count)
+ output.puts "#{cop_name}:"
+ cfg.each { |key, value| output.puts " #{key}: #{value}" }
+ end
+ puts "Created #{output.path}."
+ puts "Run `rubocop --config #{output.path}`, or"
+ puts "add inherit_from: #{output.path} in a .rubocop.yml file."
+ end
+
+ def output_cop_comments(output, cfg, cop_name, offense_count)
+ output.puts "# Offense count: #{offense_count}"
+ if COPS[cop_name] && COPS[cop_name].first.new.support_autocorrect?
+ output.puts '# Cop supports --auto-correct.'
+ end
+
+ default_cfg = Rubocop::ConfigLoader.default_configuration[cop_name]
+ if default_cfg
+ params = default_cfg.keys - %w(Description Enabled) - cfg.keys
+ unless params.empty?
+ output.puts "# Configuration parameters: #{params.join(', ')}."
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/formatter/disabled_lines_formatter.rb b/lib/rubocop/formatter/disabled_lines_formatter.rb
new file mode 100644
index 0000000..c7779bb
--- /dev/null
+++ b/lib/rubocop/formatter/disabled_lines_formatter.rb
@@ -0,0 +1,56 @@
+# encoding: utf-8
+module Rubocop
+ module Formatter
+ # A basic formatter that displays the lines disabled
+ # inline comments.
+ class DisabledLinesFormatter < BaseFormatter
+ include PathUtil
+ include Colorizable
+
+ attr_reader :cop_disabled_line_ranges
+
+ def started(target_files)
+ @cop_disabled_line_ranges = {}
+ end
+
+ def file_started(file, options)
+ return unless options[:cop_disabled_line_ranges]
+
+ @cop_disabled_line_ranges[file] =
+ options[:cop_disabled_line_ranges]
+ end
+
+ def finished(inspected_files)
+ cops_disabled_in_comments_summary
+ end
+
+ private
+
+ def cops_disabled_in_comments_summary
+ summary = "\nCops disabled line ranges:\n\n"
+
+ @cop_disabled_line_ranges.each do |file, disabled_cops|
+ disabled_cops.each do |cop, line_ranges|
+ line_ranges.each do |line_range|
+ file = cyan(smart_path(file))
+ summary << "#{file}:#{line_range}: #{cop}\n"
+ end
+ end
+ end
+
+ output.puts summary
+ end
+
+ def smart_path(path)
+ # Ideally, we calculate this relative to the project root.
+ base_dir = Dir.pwd
+
+ if path.start_with? base_dir
+ relative_path(path, base_dir)
+ else
+ path
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/formatter/emacs_style_formatter.rb b/lib/rubocop/formatter/emacs_style_formatter.rb
new file mode 100644
index 0000000..69a48be
--- /dev/null
+++ b/lib/rubocop/formatter/emacs_style_formatter.rb
@@ -0,0 +1,21 @@
+# encoding: utf-8
+
+module Rubocop
+ module Formatter
+ # This formatter displays the report data in format that's
+ # easy to process in the Emacs text editor.
+ # The output is machine-parsable.
+ class EmacsStyleFormatter < BaseFormatter
+ def file_finished(file, offenses)
+ offenses.each do |o|
+ message = o.corrected? ? '[Corrected] ' : ''
+ message << o.message
+
+ output.printf("%s:%d:%d: %s: %s\n",
+ file, o.line, o.real_column, o.severity.code,
+ message)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/formatter/file_list_formatter.rb b/lib/rubocop/formatter/file_list_formatter.rb
new file mode 100644
index 0000000..5defceb
--- /dev/null
+++ b/lib/rubocop/formatter/file_list_formatter.rb
@@ -0,0 +1,19 @@
+# encoding: utf-8
+
+module Rubocop
+ module Formatter
+ # This formatter displays just a list of the files with offenses in them,
+ # separated by newlines. The output is machine-parsable.
+ #
+ # Here's the format:
+ #
+ # /some/file
+ # /some/other/file
+ class FileListFormatter < BaseFormatter
+ def file_finished(file, offenses)
+ return if offenses.empty?
+ output.printf("%s\n", file)
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/formatter/formatter_set.rb b/lib/rubocop/formatter/formatter_set.rb
new file mode 100644
index 0000000..a0ea76a
--- /dev/null
+++ b/lib/rubocop/formatter/formatter_set.rb
@@ -0,0 +1,75 @@
+# encoding: utf-8
+
+module Rubocop
+ module Formatter
+ # This is a collection of formatters. A FormatterSet can hold multiple
+ # formatter instances and provides transparent formatter API methods
+ # which invoke same method of each formatters.
+ class FormatterSet < Array
+ BUILTIN_FORMATTERS_FOR_KEYS = {
+ 'progress' => ProgressFormatter,
+ 'simple' => SimpleTextFormatter,
+ 'clang' => ClangStyleFormatter,
+ 'fuubar' => FuubarStyleFormatter,
+ 'emacs' => EmacsStyleFormatter,
+ 'json' => JSONFormatter,
+ 'files' => FileListFormatter,
+ 'offenses' => OffenseCountFormatter,
+ 'disabled' => DisabledLinesFormatter
+ }
+
+ FORMATTER_APIS = [:started, :file_started, :file_finished, :finished]
+
+ FORMATTER_APIS.each do |method_name|
+ define_method(method_name) do |*args|
+ each { |f| f.send(method_name, *args) }
+ end
+ end
+
+ def add_formatter(formatter_type, output_path = nil)
+ formatter_class = case formatter_type
+ when Class
+ formatter_type
+ when /\A[A-Z]/
+ custom_formatter_class(formatter_type)
+ else
+ builtin_formatter_class(formatter_type)
+ end
+
+ output = output_path ? File.open(output_path, 'w') : $stdout
+
+ self << formatter_class.new(output)
+ end
+
+ def close_output_files
+ each do |formatter|
+ formatter.output.close if formatter.output.is_a?(File)
+ end
+ end
+
+ private
+
+ def builtin_formatter_class(specified_key)
+ matching_keys = BUILTIN_FORMATTERS_FOR_KEYS.keys.select do |key|
+ key.start_with?(specified_key)
+ end
+
+ if matching_keys.empty?
+ fail %(No formatter for "#{specified_key}")
+ elsif matching_keys.size > 1
+ fail %(Cannot determine formatter for "#{specified_key}")
+ end
+
+ BUILTIN_FORMATTERS_FOR_KEYS[matching_keys.first]
+ end
+
+ def custom_formatter_class(specified_class_name)
+ constant_names = specified_class_name.split('::')
+ constant_names.shift if constant_names.first.empty?
+ constant_names.reduce(Object) do |namespace, constant_name|
+ namespace.const_get(constant_name, false)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/formatter/fuubar_style_formatter.rb b/lib/rubocop/formatter/fuubar_style_formatter.rb
new file mode 100644
index 0000000..d5e8a4b
--- /dev/null
+++ b/lib/rubocop/formatter/fuubar_style_formatter.rb
@@ -0,0 +1,74 @@
+# encoding: utf-8
+
+require 'ruby-progressbar'
+
+module Rubocop
+ module Formatter
+ # This formatter displays a progress bar and shows details of offenses as
+ # soon as they are detected.
+ # This is inspired by the Fuubar formatter for RSpec by Jeff Kreeftmeijer.
+ # https://github.com/jeffkreeftmeijer/fuubar
+ class FuubarStyleFormatter < ClangStyleFormatter
+ RESET_SEQUENCE = "\e[0m"
+
+ def started(target_files)
+ super
+
+ @severest_offense = nil
+
+ file_phrase = target_files.count == 1 ? 'file' : 'files'
+
+ # 185/407 files |====== 45 ======> | ETA: 00:00:04
+ # %c / %C | %w > %i | %e
+ bar_format = " %c/%C #{file_phrase} |%w>%i| %e "
+
+ @progressbar = ProgressBar.create(
+ output: output,
+ total: target_files.count,
+ format: bar_format,
+ autostart: false
+ )
+ with_color { @progressbar.start }
+ end
+
+ def file_finished(file, offenses)
+ count_stats(offenses)
+
+ unless offenses.empty?
+ @progressbar.clear
+ report_file(file, offenses)
+ end
+
+ with_color { @progressbar.increment }
+ end
+
+ def count_stats(offenses)
+ super
+
+ offenses = offenses.reject(&:corrected?)
+ return if offenses.empty?
+
+ offenses << @severest_offense if @severest_offense
+ @severest_offense = offenses.max_by { |offense| offense.severity }
+ end
+
+ def with_color
+ if rainbow.enabled
+ output.write colorize('', progressbar_color).chomp(RESET_SEQUENCE)
+ yield
+ output.write RESET_SEQUENCE
+ else
+ yield
+ end
+ end
+
+ def progressbar_color
+ if @severest_offense
+ COLOR_FOR_SEVERITY[@severest_offense.severity.name]
+ else
+ :green
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/formatter/json_formatter.rb b/lib/rubocop/formatter/json_formatter.rb
new file mode 100644
index 0000000..e928c2e
--- /dev/null
+++ b/lib/rubocop/formatter/json_formatter.rb
@@ -0,0 +1,74 @@
+# encoding: utf-8
+
+require 'json'
+require 'pathname'
+
+module Rubocop
+ module Formatter
+ # This formatter formats the report data in JSON format.
+ class JSONFormatter < BaseFormatter
+ include PathUtil
+
+ attr_reader :output_hash
+
+ def initialize(output)
+ super
+ @output_hash = {
+ metadata: metadata_hash,
+ files: [],
+ summary: { offense_count: 0 }
+ }
+ end
+
+ def started(target_files)
+ output_hash[:summary][:target_file_count] = target_files.count
+ end
+
+ def file_finished(file, offenses)
+ output_hash[:files] << hash_for_file(file, offenses)
+ output_hash[:summary][:offense_count] += offenses.count
+ end
+
+ def finished(inspected_files)
+ output_hash[:summary][:inspected_file_count] = inspected_files.count
+ output.write output_hash.to_json
+ end
+
+ def metadata_hash
+ {
+ rubocop_version: Rubocop::Version::STRING,
+ ruby_engine: RUBY_ENGINE,
+ ruby_version: RUBY_VERSION,
+ ruby_patchlevel: RUBY_PATCHLEVEL.to_s,
+ ruby_platform: RUBY_PLATFORM
+ }
+ end
+
+ def hash_for_file(file, offenses)
+ {
+ path: relative_path(file),
+ offenses: offenses.map { |o| hash_for_offense(o) }
+ }
+ end
+
+ def hash_for_offense(offense)
+ {
+ severity: offense.severity.name,
+ message: offense.message,
+ cop_name: offense.cop_name,
+ corrected: offense.corrected?,
+ location: hash_for_location(offense)
+ }
+ end
+
+ # TODO: Consider better solution for Offense#real_column.
+ def hash_for_location(offense)
+ {
+ line: offense.line,
+ column: offense.real_column,
+ length: offense.location.length
+ }
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/formatter/offense_count_formatter.rb b/lib/rubocop/formatter/offense_count_formatter.rb
new file mode 100644
index 0000000..7bcb14f
--- /dev/null
+++ b/lib/rubocop/formatter/offense_count_formatter.rb
@@ -0,0 +1,54 @@
+# encoding: utf-8
+
+module Rubocop
+ module Formatter
+ # This formatter displays the list of offended cops with a count of how
+ # many offenses of their kind were found. Ordered by desc offense count
+ #
+ # Here's the format:
+ #
+ # 26 LineLength
+ # 3 OneLineConditional
+ # --
+ # 29 Total
+ class OffenseCountFormatter < BaseFormatter
+ attr_reader :offense_counts
+
+ def started(target_files)
+ super
+ @offense_counts = Hash.new(0)
+ end
+
+ def file_finished(file, offenses)
+ offenses.each { |o| @offense_counts[o.cop_name] += 1 }
+ end
+
+ def finished(inspected_files)
+ report_summary(inspected_files.count,
+ ordered_offense_counts(@offense_counts))
+ end
+
+ def report_summary(file_count, offense_counts)
+ output.puts
+
+ offense_count = total_offense_count(offense_counts)
+ offense_counts.each do |cop_name, count|
+ output.puts "#{count.to_s.ljust(offense_count.to_s.length + 2)}" \
+ "#{cop_name}\n"
+ end
+ output.puts '--'
+ output.puts "#{offense_count} Total"
+
+ output.puts
+ end
+
+ def ordered_offense_counts(offense_counts)
+ Hash[offense_counts.sort_by { |k, v| [-v, k] }]
+ end
+
+ def total_offense_count(offense_counts = {})
+ offense_counts.values.inject(0, :+)
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/formatter/progress_formatter.rb b/lib/rubocop/formatter/progress_formatter.rb
new file mode 100644
index 0000000..168ca85
--- /dev/null
+++ b/lib/rubocop/formatter/progress_formatter.rb
@@ -0,0 +1,55 @@
+# encoding: utf-8
+
+module Rubocop
+ module Formatter
+ # This formatter display dots for files with no offenses and
+ # letters for files with problems in the them. In the end it
+ # appends the regular report data in the clang style format.
+ class ProgressFormatter < ClangStyleFormatter
+ def started(target_files)
+ super
+ @offenses_for_files = {}
+ file_phrase = target_files.count == 1 ? 'file' : 'files'
+ output.puts "Inspecting #{target_files.count} #{file_phrase}"
+ end
+
+ def file_finished(file, offenses)
+ unless offenses.empty?
+ count_stats(offenses)
+ @offenses_for_files[file] = offenses
+ end
+
+ report_file_as_mark(file, offenses)
+ end
+
+ def finished(inspected_files)
+ output.puts
+
+ unless @offenses_for_files.empty?
+ output.puts
+ output.puts 'Offenses:'
+ output.puts
+
+ @offenses_for_files.each do |file, offenses|
+ report_file(file, offenses)
+ end
+ end
+
+ report_summary(inspected_files.count,
+ @total_offense_count,
+ @total_correction_count)
+ end
+
+ def report_file_as_mark(file, offenses)
+ mark = if offenses.empty?
+ green('.')
+ else
+ highest_offense = offenses.max_by { |o| o.severity }
+ colored_severity_code(highest_offense)
+ end
+
+ output.write mark
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/formatter/simple_text_formatter.rb b/lib/rubocop/formatter/simple_text_formatter.rb
new file mode 100644
index 0000000..25242a9
--- /dev/null
+++ b/lib/rubocop/formatter/simple_text_formatter.rb
@@ -0,0 +1,117 @@
+# encoding: utf-8
+
+require 'rubocop/formatter/colorizable'
+
+module Rubocop
+ module Formatter
+ # A basic formatter that displays only files with offenses.
+ # Offenses are displayed at compact form - just the
+ # location of the problem and the associated message.
+ class SimpleTextFormatter < BaseFormatter
+ include Colorizable
+ include PathUtil
+
+ COLOR_FOR_SEVERITY = {
+ refactor: :yellow,
+ convention: :yellow,
+ warning: :magenta,
+ error: :red,
+ fatal: :red
+ }.freeze
+
+ def started(target_files)
+ @total_offense_count = 0
+ @total_correction_count = 0
+ end
+
+ def file_finished(file, offenses)
+ return if offenses.empty?
+ count_stats(offenses)
+ report_file(file, offenses)
+ end
+
+ def finished(inspected_files)
+ report_summary(inspected_files.count,
+ @total_offense_count,
+ @total_correction_count)
+ end
+
+ def report_file(file, offenses)
+ output.puts yellow("== #{smart_path(file)} ==")
+
+ offenses.each do |o|
+ output.printf("%s:%3d:%3d: %s\n",
+ colored_severity_code(o),
+ o.line, o.real_column, message(o))
+ end
+ end
+
+ def report_summary(file_count, offense_count, correction_count)
+ summary = pluralize(file_count, 'file')
+ summary << ' inspected, '
+
+ offenses_text = pluralize(offense_count, 'offense', no_for_zero: true)
+ offenses_text << ' detected'
+ summary << colorize(offenses_text, offense_count.zero? ? :green : :red)
+
+ if correction_count > 0
+ summary << ', '
+ correction_text = pluralize(correction_count, 'offense')
+ correction_text << ' corrected'
+ color = correction_count == offense_count ? :green : :cyan
+ summary << colorize(correction_text, color)
+ end
+
+ output.puts
+ output.puts summary
+ end
+
+ private
+
+ def count_stats(offenses)
+ @total_offense_count += offenses.count
+ @total_correction_count += offenses.select(&:corrected?).count
+ end
+
+ def smart_path(path)
+ # Ideally, we calculate this relative to the project root.
+ base_dir = Dir.pwd
+
+ if path.start_with? base_dir
+ relative_path(path, base_dir)
+ else
+ path
+ end
+ end
+
+ def colored_severity_code(offense)
+ color = COLOR_FOR_SEVERITY[offense.severity.name]
+ colorize(offense.severity.code, color)
+ end
+
+ def annotate_message(msg)
+ msg.gsub(/`(.*?)`/, Rainbow('\1').yellow)
+ end
+
+ def message(offense)
+ message = offense.corrected? ? green('[Corrected] ') : ''
+ message << annotate_message(offense.message)
+ end
+
+ def pluralize(number, thing, options = {})
+ text = ''
+
+ if number == 0 && options[:no_for_zero]
+ text = 'no'
+ else
+ text << number.to_s
+ end
+
+ text << " #{thing}"
+ text << 's' unless number == 1
+
+ text
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/options.rb b/lib/rubocop/options.rb
new file mode 100644
index 0000000..f9f9c43
--- /dev/null
+++ b/lib/rubocop/options.rb
@@ -0,0 +1,189 @@
+# encoding: utf-8
+
+require 'optparse'
+
+module Rubocop
+ # This class handles command line options.
+ class Options
+ DEFAULT_FORMATTER = 'progress'
+ EXITING_OPTIONS = [:version, :verbose_version, :show_cops]
+
+ def initialize
+ @options = {}
+ end
+
+ def parse(args)
+ ignore_dropped_options(args)
+ convert_deprecated_options(args)
+
+ OptionParser.new do |opts|
+ opts.banner = 'Usage: rubocop [options] [file1, file2, ...]'
+
+ option(opts, '--only COP', 'Run just one cop.') do
+ validate_only_option
+ end
+
+ add_configuration_options(opts, args)
+ add_formatting_options(opts, args)
+
+ option(opts, '-r', '--require FILE', 'Require Ruby file.') do |f|
+ require f
+ end
+
+ add_severity_option(opts)
+ add_flags_with_optional_args(opts)
+ add_boolean_flags(opts)
+ end.parse!(args)
+
+ if (incompat = @options.keys & EXITING_OPTIONS).size > 1
+ fail ArgumentError, "Incompatible cli options: #{incompat.inspect}"
+ end
+
+ [@options, args]
+ end
+
+ private
+
+ def add_configuration_options(opts, args)
+ option(opts, '-c', '--config FILE', 'Specify configuration file.')
+
+ option(opts, '--auto-gen-config',
+ 'Generate a configuration file acting as a', 'TODO list.') do
+ validate_auto_gen_config_option(args)
+ @options[:formatters] = [[DEFAULT_FORMATTER],
+ [Formatter::DisabledConfigFormatter,
+ ConfigLoader::AUTO_GENERATED_FILE]]
+ end
+
+ option(opts, '--force-exclusion',
+ 'Force excluding files specified in the',
+ 'configuration `Exclude` even if they are',
+ 'explicitly passed as arguments.')
+ end
+
+ FORMAT_HELP = ['Choose an output formatter. This option',
+ 'can be specified multiple times to enable',
+ 'multiple formatters at the same time.',
+ ' [p]rogress (default)',
+ ' [s]imple',
+ ' [c]lang',
+ ' [d]isabled cops via inline comments',
+ ' [fu]ubar',
+ ' [e]macs',
+ ' [j]son',
+ ' [fi]les',
+ ' [o]ffenses',
+ ' custom formatter class name']
+
+ def add_formatting_options(opts, args)
+ option(opts, '-f', '--format FORMATTER', *FORMAT_HELP) do |key|
+ @options[:formatters] ||= []
+ @options[:formatters] << [key]
+ end
+
+ option(opts, '-o', '--out FILE',
+ 'Write output to a file instead of STDOUT.',
+ 'This option applies to the previously',
+ 'specified --format, or the default format',
+ 'if no format is specified.') do |path|
+ @options[:formatters] ||= [[DEFAULT_FORMATTER]]
+ @options[:formatters].last << path
+ end
+ end
+
+ def add_severity_option(opts)
+ opts.on('--fail-level SEVERITY',
+ Rubocop::Cop::Severity::NAMES,
+ Rubocop::Cop::Severity::CODE_TABLE,
+ 'Minimum severity for exit with error code.') do |severity|
+ @options[:fail_level] = severity
+ end
+ end
+
+ def add_flags_with_optional_args(opts)
+ option(opts, '--show-cops [cop1,cop2,...]',
+ 'Shows the given cops, or all cops by',
+ 'default, and their configurations for the',
+ 'current directory.') do |list|
+ @options[:show_cops] = list.nil? ? [] : list.split(',')
+ end
+ end
+
+ def add_boolean_flags(opts)
+ option(opts, '-d', '--debug', 'Display debug info.')
+ option(opts,
+ '-D', '--display-cop-names',
+ 'Display cop names in offense messages.')
+ option(opts, '-R', '--rails', 'Run extra Rails cops.')
+ option(opts, '-l', '--lint', 'Run only lint cops.')
+ option(opts, '-a', '--auto-correct', 'Auto-correct offenses.')
+
+ @options[:color] = true
+ opts.on('-n', '--no-color', 'Disable color output.') do
+ @options[:color] = false
+ end
+
+ option(opts, '-v', '--version', 'Display version.')
+ option(opts, '-V', '--verbose-version', 'Display verbose version.')
+ end
+
+ # Sets a value in the @options hash, based on the given long option and its
+ # value, in addition to calling the block if a block is given.
+ def option(opts, *args)
+ opts.on(*args) do |arg|
+ @options[long_opt_symbol(args)] = arg
+ yield arg if block_given?
+ end
+ end
+
+ # Finds the option in `args` starting with -- and converts it to a symbol,
+ # e.g. [..., '--auto-correct', ...] to :auto_correct.
+ def long_opt_symbol(args)
+ long_opt = args.find { |arg| arg.start_with?('--') }
+ long_opt[2..-1].sub(/ .*/, '').gsub(/-/, '_').to_sym
+ end
+
+ def ignore_dropped_options(args)
+ # Currently we don't make -s/--silent option raise error
+ # since those are mostly used by external tools.
+ rejected = args.reject! { |a| %w(-s --silent).include?(a) }
+ if rejected
+ warn '-s/--silent options is dropped. ' \
+ '`emacs` and `files` formatters no longer display summary.'
+ end
+ end
+
+ def convert_deprecated_options(args)
+ args.map! do |arg|
+ case arg
+ when '-e', '--emacs'
+ deprecate("#{arg} option", '--format emacs', '1.0.0')
+ %w(--format emacs)
+ else
+ arg
+ end
+ end.flatten!
+ end
+
+ def deprecate(subject, alternative = nil, version = nil)
+ message = "#{subject} is deprecated"
+ message << " and will be removed in RuboCop #{version}" if version
+ message << '.'
+ message << " Please use #{alternative} instead." if alternative
+ warn message
+ end
+
+ def validate_only_option
+ if Cop::Cop.all.none? { |c| c.cop_name == @options[:only] }
+ fail ArgumentError, "Unrecognized cop name: #{@options[:only]}."
+ end
+ end
+
+ def validate_auto_gen_config_option(args)
+ if args.any?
+ warn '--auto-gen-config can not be combined with any other arguments.'
+ exit(1)
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/path_util.rb b/lib/rubocop/path_util.rb
new file mode 100644
index 0000000..97ce6cb
--- /dev/null
+++ b/lib/rubocop/path_util.rb
@@ -0,0 +1,23 @@
+# encoding: utf-8
+
+module Rubocop
+ # Common methods and behaviors for dealing with paths.
+ module PathUtil
+ module_function
+
+ def relative_path(path, base_dir = Dir.pwd)
+ path_name = Pathname.new(File.expand_path(path))
+ path_name.relative_path_from(Pathname.new(base_dir)).to_s
+ end
+
+ def match_path?(pattern, path)
+ case pattern
+ when String
+ basename = File.basename(path)
+ path == pattern || basename == pattern || File.fnmatch(pattern, path)
+ when Regexp
+ path =~ pattern
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/processed_source.rb b/lib/rubocop/processed_source.rb
new file mode 100644
index 0000000..ebe40e9
--- /dev/null
+++ b/lib/rubocop/processed_source.rb
@@ -0,0 +1,61 @@
+# encoding: utf-8
+
+module Rubocop
+ # ProcessedSource contains objects which are generated by Parser
+ # and other information such as disabled lines for cops.
+ # It also provides a convenient way to access source lines.
+ class ProcessedSource
+ attr_reader :buffer, :ast, :comments, :tokens, :diagnostics,
+ :comment_config
+
+ def initialize(buffer, ast, comments, tokens, diagnostics)
+ @buffer = buffer
+ @ast = ast
+ @comments = comments
+ @tokens = tokens
+ @diagnostics = diagnostics
+ @comment_config = CommentConfig.new(self)
+ end
+
+ def disabled_line_ranges
+ comment_config.cop_disabled_line_ranges
+ end
+
+ def lines
+ if @lines
+ @lines
+ else
+ init_lines
+ @lines
+ end
+ end
+
+ def raw_lines
+ if @raw_lines
+ @raw_lines
+ else
+ init_lines
+ @raw_lines
+ end
+ end
+
+ def [](*args)
+ lines[*args]
+ end
+
+ def valid_syntax?
+ @diagnostics.none? { |d| [:error, :fatal].include?(d.level) }
+ end
+
+ def file_path
+ @buffer.name
+ end
+
+ private
+
+ def init_lines
+ @raw_lines = @buffer.source.lines
+ @lines = @raw_lines.map(&:chomp)
+ end
+ end
+end
diff --git a/lib/rubocop/rake_task.rb b/lib/rubocop/rake_task.rb
new file mode 100644
index 0000000..49cb3da
--- /dev/null
+++ b/lib/rubocop/rake_task.rb
@@ -0,0 +1,70 @@
+# encoding: utf-8
+
+require 'rake'
+require 'rake/tasklib'
+
+module Rubocop
+ # Provides a custom rake task.
+ #
+ # require 'rubocop/rake_task'
+ # Rubocop::RakeTask.new
+ class RakeTask < Rake::TaskLib
+ attr_accessor :name
+ attr_accessor :verbose
+ attr_accessor :fail_on_error
+ attr_accessor :patterns
+ attr_accessor :formatters
+ attr_accessor :requires
+ attr_accessor :options
+
+ def initialize(*args, &task_block)
+ setup_ivars(args)
+
+ desc 'Run RuboCop' unless ::Rake.application.last_comment
+
+ task(name, *args) do |_, task_args|
+ RakeFileUtils.send(:verbose, verbose) do
+ if task_block
+ task_block.call(*[self, task_args].slice(0, task_block.arity))
+ end
+ run_task(verbose)
+ end
+ end
+ end
+
+ def run_task(verbose)
+ # We lazy-load rubocop so that the task doesn't dramatically impact the
+ # load time of your Rakefile.
+ require 'rubocop'
+
+ cli = CLI.new
+ puts 'Running RuboCop...' if verbose
+ result = cli.run(full_options)
+ abort('RuboCop failed!') if fail_on_error unless result == 0
+ end
+
+ private
+
+ def full_options
+ [].tap do |result|
+ result.concat(formatters.map { |f| ['--format', f] }.flatten)
+ result.concat(requires.map { |r| ['--require', r] }.flatten)
+ result.concat(options)
+ result.concat(patterns)
+ end
+ end
+
+ def setup_ivars(args)
+ # More lazy-loading to keep load time down.
+ require 'rubocop/options'
+
+ @name = args.shift || :rubocop
+ @verbose = true
+ @fail_on_error = true
+ @patterns = []
+ @requires = []
+ @options = []
+ @formatters = [Rubocop::Options::DEFAULT_FORMATTER]
+ end
+ end
+end
diff --git a/lib/rubocop/source_parser.rb b/lib/rubocop/source_parser.rb
new file mode 100644
index 0000000..c27dc33
--- /dev/null
+++ b/lib/rubocop/source_parser.rb
@@ -0,0 +1,47 @@
+# encoding: utf-8
+
+module Rubocop
+ # SourceParser provides a way to parse Ruby source with Parser gem
+ # and also parses comment directives which disable arbitrary cops.
+ module SourceParser
+ module_function
+
+ def parse_file(path)
+ parse(File.read(path), path)
+ end
+
+ def parse(string, name = '(string)')
+ source_buffer = Parser::Source::Buffer.new(name, 1)
+ source_buffer.source = string
+
+ parser = create_parser
+ diagnostics = []
+ parser.diagnostics.consumer = lambda do |diagnostic|
+ diagnostics << diagnostic
+ end
+
+ begin
+ ast, comments, tokens = parser.tokenize(source_buffer)
+ rescue Parser::SyntaxError # rubocop:disable HandleExceptions
+ # All errors are in diagnostics. No need to handle exception.
+ end
+
+ tokens = tokens.map { |t| Token.from_parser_token(t) } if tokens
+
+ ProcessedSource.new(source_buffer, ast, comments, tokens, diagnostics)
+ end
+
+ def create_parser
+ parser = Parser::CurrentRuby.new
+
+ # On JRuby and Rubinius, there's a risk that we hang in tokenize() if we
+ # don't set the all errors as fatal flag. The problem is caused by a bug
+ # in Racc that is discussed in issue #93 of the whitequark/parser project
+ # on GitHub.
+ parser.diagnostics.all_errors_are_fatal = RUBY_ENGINE != 'ruby'
+ parser.diagnostics.ignore_warnings = false
+
+ parser
+ end
+ end
+end
diff --git a/lib/rubocop/target_finder.rb b/lib/rubocop/target_finder.rb
new file mode 100644
index 0000000..46233cd
--- /dev/null
+++ b/lib/rubocop/target_finder.rb
@@ -0,0 +1,91 @@
+# encoding: utf-8
+
+module Rubocop
+ # This class finds target files to inspect by scanning the directory tree
+ # and picking ruby files.
+ class TargetFinder
+ def initialize(config_store, options = {})
+ @config_store = config_store
+ @options = { force_exclusion: false, debug: false }.merge(options)
+ end
+
+ def force_exclusion?
+ @options[:force_exclusion]
+ end
+
+ def debug?
+ @options[:debug]
+ end
+
+ # Generate a list of target files by expanding globbing patterns
+ # (if any). If args is empty, recursively find all Ruby source
+ # files under the current directory
+ # @return [Array] array of file paths
+ def find(args)
+ return target_files_in_dir if args.empty?
+
+ files = []
+
+ args.uniq.each do |arg|
+ files += if File.directory?(arg)
+ target_files_in_dir(arg.chomp(File::SEPARATOR))
+ else
+ process_explicit_path(arg)
+ end
+ end
+
+ files.map { |f| File.expand_path(f) }.uniq
+ end
+
+ # Finds all Ruby source files under the current or other supplied
+ # directory. A Ruby source file is defined as a file with the `.rb`
+ # extension or a file with no extension that has a ruby shebang line
+ # as its first line.
+ # It is possible to specify includes and excludes using the config file,
+ # so you can include other Ruby files like Rakefiles and gemspecs.
+ # @param base_dir Root directory under which to search for
+ # ruby source files
+ # @return [Array] Array of filenames
+ def target_files_in_dir(base_dir = Dir.pwd)
+ # Support Windows: Backslashes from command-line -> forward slashes
+ if File::ALT_SEPARATOR
+ base_dir.gsub!(File::ALT_SEPARATOR, File::SEPARATOR)
+ end
+ files = Dir["#{base_dir}/**/*"].select { |path| FileTest.file?(path) }
+ base_dir_config = @config_store.for(base_dir)
+
+ target_files = files.select do |file|
+ next false if base_dir_config.file_to_exclude?(file)
+ next true if File.extname(file) == '.rb'
+ next true if ruby_executable?(file)
+ @config_store.for(file).file_to_include?(file)
+ end
+
+ target_files
+ end
+
+ def ruby_executable?(file)
+ return false unless File.extname(file).empty?
+ first_line = File.open(file) { |f| f.readline }
+ first_line =~ /#!.*ruby/
+ rescue EOFError, ArgumentError => e
+ warn "Unprocessable file #{file}: #{e.class}, #{e.message}" if debug?
+ false
+ end
+
+ def process_explicit_path(path)
+ files = if path.include?('*')
+ Dir[path]
+ else
+ [path]
+ end
+
+ return files unless force_exclusion?
+
+ files.reject do |file|
+ config = @config_store.for(file)
+ config.file_to_exclude?(file)
+ end
+ end
+ end
+end
diff --git a/lib/rubocop/token.rb b/lib/rubocop/token.rb
new file mode 100644
index 0000000..2a1677c
--- /dev/null
+++ b/lib/rubocop/token.rb
@@ -0,0 +1,22 @@
+# encoding: utf-8
+
+module Rubocop
+ # A basic wrapper around Parser's tokens.
+ class Token
+ attr_reader :pos, :type, :text
+
+ def self.from_parser_token(parser_token)
+ type, details = *parser_token
+ text, range = *details
+ new(range, type, text)
+ end
+
+ def initialize(pos, type, text)
+ @pos, @type, @text = pos, type, text
+ end
+
+ def to_s
+ "[[#{@pos.line}, #{@pos.column}], #{@type}, #{@text.inspect}]"
+ end
+ end
+end
diff --git a/lib/rubocop/version.rb b/lib/rubocop/version.rb
new file mode 100644
index 0000000..075be93
--- /dev/null
+++ b/lib/rubocop/version.rb
@@ -0,0 +1,21 @@
+# encoding: utf-8
+
+module Rubocop
+ # This module holds the RuboCop version information.
+ module Version
+ STRING = '0.20.1'
+
+ MSG = '%s (using Parser %s, running on %s %s %s)'
+
+ module_function
+
+ def version(debug = false)
+ if debug
+ format(MSG, STRING, Parser::VERSION,
+ RUBY_ENGINE, RUBY_VERSION, RUBY_PLATFORM)
+ else
+ STRING
+ end
+ end
+ end
+end
diff --git a/relnotes/v0.19.0.md b/relnotes/v0.19.0.md
new file mode 100644
index 0000000..1b81f09
--- /dev/null
+++ b/relnotes/v0.19.0.md
@@ -0,0 +1,94 @@
+This is the biggest RuboCop release we've done in a while. The highlights include
+about a dozen new cops, more cop configuration options, improved auto-correct and so many bugfixes.
+
+You'll might note that we changed the use of `offence` with `offense`. This was done to keep the spelling
+in our code consistent. Hopefully this won't cause anyone problems, but we're obliged to mention it as
+the `Offence` class itself got renamed.
+
+Below is the list of all the gory details. Enjoy!
+
+## RuboCop 0.19.0
+
+### New features
+
+* New cop `FileName` makes sure that source files have snake_case names. ([@bbatsov][])
+* New cop `DeprecatedClassMethods` checks for deprecated class methods. ([@bbatsov][])
+* New cop `StringConversionInInterpolation` checks for redundant `Object#to_s` in string interpolation. ([@bbatsov][])
+* New cop `LiteralInInterpolation` checks for interpolated string literals. ([@bbatsov][])
+* New cop `SelfAssignment` checks for places where the self-assignment shorthand should have been used. ([@bbatsov][])
+* New cop `DoubleNegation` checks for uses of `!!`. ([@bbatsov][])
+* New cop `PercentLiteralDelimiters` enforces consistent usage of `%`-literal delimiters. ([@hannestyden][])
+* New Rails cop `ActionFilter` enforces the use of `_filter` or `_action` action filter methods. ([@bbatsov][])
+* New Rails cop `ScopeArgs` makes sure you invoke the `scope` method properly. ([@bbatsov][])
+* Add `with_fixed_indentation` style to `AlignParameters` cop. ([@hannestyden][])
+* Add `IgnoreLastArgumentHash` option to `AlignHash` cop. ([@hannestyden][])
+* [#743](https://github.com/bbatsov/rubocop/issues/743): `SingleLineMethods` cop does auto-correction. ([@jonas054][])
+* [#743](https://github.com/bbatsov/rubocop/issues/743): `Semicolon` cop does auto-correction. ([@jonas054][])
+* [#743](https://github.com/bbatsov/rubocop/issues/743): `EmptyLineBetweenDefs` cop does auto-correction. ([@jonas054][])
+* [#743](https://github.com/bbatsov/rubocop/issues/743): `IndentationWidth` cop does auto-correction. ([@jonas054][])
+* [#743](https://github.com/bbatsov/rubocop/issues/743): `IndentationConsistency` cop does auto-correction. ([@jonas054][])
+* [#809](https://github.com/bbatsov/rubocop/issues/809): New formatter `fuubar` displays a progress bar and shows details of offenses as soon as they are detected. ([@yujinakayama][])
+* [#797](https://github.com/bbatsov/rubocop/issues/797): New cop `IndentHash` checks the indentation of the first key in multi-line hash literals. ([@jonas054][])
+* [#797](https://github.com/bbatsov/rubocop/issues/797): New cop `IndentArray` checks the indentation of the first element in multi-line array literals. ([@jonas054][])
+* [#806](https://github.com/bbatsov/rubocop/issues/806): Now excludes files in `vendor/**` by default. ([@jeremyolliver][])
+* [#795](https://github.com/bbatsov/rubocop/issues/795): `IfUnlessModifier` and `WhileUntilModifier` supports `MaxLineLength`, which is independent of `LineLength` parameter `Max`. ([@agrimm][])
+* [#868](https://github.com/bbatsov/rubocop/issues/868): New cop `ClassAndModuleChildren` checks the style of children definitions at classes and modules: nested / compact. ([@geniou][])
+
+### Changes
+
+* [#793](https://github.com/bbatsov/rubocop/issues/793): Add printing total count when `rubocop --format offences`. ([@ma2gedev][])
+* Remove `Ignore` param from the Rails `Output` cop. The standard `Exclude/Include` should be used instead. ([@bbatsov][])
+* Renamed `FavorSprintf` to `FormatString` and made it configurable. ([@bbatsov][])
+* Renamed `Offence` to `Offense`. ([@bbatsov][])
+* Use `offense` in all messages instead of `offence`. ([@bbatsov][])
+* For indentation of `if`/`unless`/`while`/`until` bodies when the result is assigned to a variable, instead of supporting two styles simultaneously, `IndentationWidth` now supports one style of indentation at a time, specified by `EndAlignment`/`AlignWith`. ([@jonas054][])
+* Renamed `Style` param of `DotPosition` cop to `EnforcedStyle`. ([@bbatsov][])
+* Add `length` value to locations of offense in JSON formatter. ([@yujinakayama][])
+* `SpaceAroundBlockBraces` cop replaced by `SpaceBeforeBlockBraces` and `SpaceInsideBlockBraces`. ([@jonas054][])
+* `SpaceAroundEqualsInParameterDefault` cop is now configurable with the `EnforcedStyle` option. ([@jonas054][])
+
+### Bugs fixed
+
+* [#790](https://github.com/bbatsov/rubocop/issues/790): Fix auto-correction interference problem between `MethodDefParentheses` and other cops. ([@jonas054][])
+* [#794](https://github.com/bbatsov/rubocop/issues/794): Fix handling of modifier keywords with required parentheses in `ParenthesesAroundCondition`. ([@bbatsov][])
+* [#804](https://github.com/bbatsov/rubocop/issues/804): Fix a false positive with operator assignments in a loop (including `begin..rescue..end` with `retry`) in `UselessAssignment`. ([@yujinakayama][])
+* [#815](https://github.com/bbatsov/rubocop/issues/815): Fix a false positive for heredocs with blank lines in them in `EmptyLines`. ([@bbatsov][])
+* Auto-correction is now more robust and less likely to die because of `RangeError` or "clobbering". ([@jonas054][])
+* Offenses always reported in order of position in file, also during `--auto-correct` runs. ([@jonas054][])
+* Fix problem with `[Corrected]` tag sometimes missing in output from `--auto-correct` runs. ([@jonas054][])
+* Fix message from `EndAlignment` cop when `AlignWith` is `keyword`. ([@jonas054][])
+* Handle `case` conditions in `LiteralInCondition`. ([@bbatsov][])
+* [#822](https://github.com/bbatsov/rubocop/issues/822): Fix a false positive in `DotPosition` when enforced style is set to `trailing`. ([@bbatsov][])
+* Handle properly dynamic strings in `LineEndConcatenation`. ([@bbatsov][])
+* [#832](https://github.com/bbatsov/rubocop/issues/832): Fix auto-correction interference problem between `BracesAroundHashParameters` and `SpaceInsideHashLiteralBraces`. ([@jonas054][])
+* Fix bug in auto-correction of alignment so that only space can be removed. ([@jonas054][])
+* Fix bug in `IndentationWidth` auto-correction so it doesn't correct things that `IndentationConsistency` should correct. ([@jonas054][])
+* [#847](https://github.com/bbatsov/rubocop/issues/847): Fix bug in `RegexpLiteral` concerning `--auto-gen-config`. ([@jonas054][])
+* [#848](https://github.com/bbatsov/rubocop/issues/848): Fix bug in `--show-cops` that made it print the default configuration rather than the current configuration. ([@jonas054][])
+* [#862](https://github.com/bbatsov/rubocop/issues/862): Fix a bug where single line `rubocop:disable` comments with indentations were treated as multiline cop disabling comments. ([@yujinakayama][])
+* Fix a bug where `rubocop:disable` comments with a cop name including `all` (e.g. `MethodCallParentheses`) were disabling all cops. ([@yujinakayama][])
+* Fix a bug where string and regexp literals including `# rubocop:disable` were confused with real comments. ([@yujinakayama][])
+
+[@bbatsov]: https://github.com/bbatsov
+[@jonas054]: https://github.com/jonas054
+[@yujinakayama]: https://github.com/yujinakayama
+[@dblock]: https://github.com/dblock
+[@nevir]: https://github.com/nevir
+[@daviddavis]: https://github.com/daviddavis
+[@sds]: https://github.com/sds
+[@fancyremarker]: https://github.com/fancyremarker
+[@sinisterchipmunk]: https://github.com/sinisterchipmunk
+[@vonTronje]: https://github.com/vonTronje
+[@agrimm]: https://github.com/agrimm
+[@pmenglund]: https://github.com/pmenglund
+[@chulkilee]: https://github.com/chulkilee
+[@codez]: https://github.com/codez
+[@emou]: https://github.com/emou
+[@skanev]: http://github.com/skanev
+[@claco]: http://github.com/claco
+[@rifraf]: http://github.com/rifraf
+[@scottmatthewman]: https://github.com/scottmatthewman
+[@ma2gedev]: http://github.com/ma2gedev
+[@jeremyolliver]: https://github.com/jeremyolliver
+[@hannestyden]: https://github.com/hannestyden
+[@geniou]: https://github.com/geniou
diff --git a/relnotes/v0.19.1.md b/relnotes/v0.19.1.md
new file mode 100644
index 0000000..b5ca81d
--- /dev/null
+++ b/relnotes/v0.19.1.md
@@ -0,0 +1,16 @@
+RuboCop 0.19.1 is a bugfix-only release. Below is a list of the bugs we've fixed since 0.19.0:
+
+### Bugs fixed
+
+* [#884](https://github.com/bbatsov/rubocop/issues/884): Fix --auto-gen-config for `NumericLiterals` so MinDigits is correct. ([@tmorris-fiksu][])
+* [#879](https://github.com/bbatsov/rubocop/issues/879): Fix --auto-gen-config for `RegexpLiteral` so we don't generate illegal values for `MaxSlashes`. ([@jonas054][])
+* Fix the name of the `Include` param in the default config of the Rails cops. ([@bbatsov][])
+* [#878](https://github.com/bbatsov/rubocop/pull/878): Blacklist `Rakefile`, `Gemfile` and `Capfile` by default in the `FileName` cop. ([@bbatsov][])
+* [#875](https://github.com/bbatsov/rubocop/issues/875): Handle `separator` style hashes in `IndentHash`. ([@jonas054][])
+* Fix a bug where multiple cli options that result in exit can be specified at once (e.g. `-vV`, `-v --show-cops`). ([@jkogara][])
+* [#889](https://github.com/bbatsov/rubocop/issues/889): Fix a false positive for `LiteralInCondition` when the condition is non-primitive array. ([@bbatsov][])
+
+[@bbatsov]: https://github.com/bbatsov
+[@jonas054]: https://github.com/jonas054
+[@jkogara]: https://github.com/jkogara
+[@tmorris-fiksu]: https://github.com/tmorris-fiksu
diff --git a/relnotes/v0.20.0.md b/relnotes/v0.20.0.md
new file mode 100644
index 0000000..ab2d4c9
--- /dev/null
+++ b/relnotes/v0.20.0.md
@@ -0,0 +1,69 @@
+There aren't many exciting new features this time around - our focus in 0.20 has been fixing most of the problems we
+introduced in 0.19. Apart from that we've added a few new cops and implemented auto-correct for a bunch of the existing cops.
+You'll also notice that the messages produced by most formatters a bit more colorful (and hopefully more readable as well).
+
+You should also note that `AllCops/Includes` and `AllCops/Excludes` have been renamed, so you'll have to update your config files
+accordingly.
+
+Below is the list of all the gory details. Enjoy!
+
+### New features
+
+* New cop `GuardClause` checks for conditionals that can be replaced by guard clauses. ([@bbatsov][])
+* New cop `EmptyInterpolation` checks for empty interpolation in double-quoted strings. ([@bbatsov][])
+* [#899](https://github.com/bbatsov/rubocop/issues/899): Make `LineEndConcatenation` cop `<<` aware. ([@mockdeep][])
+* [#896](https://github.com/bbatsov/rubocop/issues/896): New option `--fail-level` changes minimum severity for exit with error code. ([@hiroponz][])
+* [#893](https://github.com/bbatsov/rubocop/issues/893): New option `--force-exclusion` forces excluding files specified in the configuration `Exclude` even if they are explicitly passed as arguments. ([@yujinakayama][])
+* `VariableInterpolation` cop does auto-correction. ([@bbatsov][])
+* `Not` cop does auto-correction. ([@bbatsov][])
+* `ClassMethods` cop does auto-correction. ([@bbatsov][])
+* `StringConversionInInterpolation` cop does auto-correction. ([@bbatsov][])
+* `NilComparison` cop does auto-correction. ([@bbatsov][])
+* `NonNilComparison` cop does auto-correction. ([@bbatsov][])
+* `NegatedIf` cop does auto-correction. ([@bbatsov][])
+* `NegatedWhile` cop does auto-correction. ([@bbatsov][])
+* New lint cop `SpaceBeforeFirstArg` checks for space between the method name and the first argument in method calls without parentheses. ([@jonas054][])
+* New style cop `SingleSpaceBeforeFirstArg` checks that no more than one space is used between the method name and the first argument in method calls without parentheses. ([@jonas054][])
+* New formatter `disabled_lines` displays cops and line ranges disabled by inline comments. ([@fshowalter][])
+* New cop `UselessAccessModifiers` checks for access modifiers that have no effect. ([@fshowalter][])
+
+### Changes
+
+* [#913](https://github.com/bbatsov/rubocop/issues/913): `FileName` accepts multiple extensions. ([@tamird][])
+* `AllCops/Excludes` and `AllCops/Includes` were renamed to `AllCops/Exclude` and `AllCops/Include` for consistency with standard cop params. ([@bbatsov][])
+* Extract `NonNilCheck` cop from `NilComparison`. ([@bbatsov][])
+* Renamed `FavorJoin` to `ArrayJoin`. ([@bbatsov][])
+* Renamed `FavorUnlessOverNegatedIf` to `NegatedIf`. ([@bbatsov][])
+* Renamed `FavorUntilOverNegatedWhile`to `NegatedWhile`. ([@bbatsov][])
+* Renamed `HashMethods` to `DeprecatedHashMethods`. ([@bbatsov][])
+* Renamed `ReadAttribute` to `ReadWriteAttribute` and extended it to check for uses of `write_attribute`. ([@bbatsov][])
+* Add experimental support for Ruby 2.2 (development version) by falling back to Ruby 2.1 parser. ([@yujinakayama][])
+
+### Bugs fixed
+
+* [#926](https://github.com/bbatsov/rubocop/issues/926): Fixed `BlockNesting` not auto-generating correctly. ([@tmorris-fiksu][])
+* [#904](https://github.com/bbatsov/rubocop/issues/904): Fixed a NPE in `LiteralInInterpolation`. ([@bbatsov][])
+* [#904](https://github.com/bbatsov/rubocop/issues/904): Fixed a NPE in `StringConversionInInterpolation`. ([@bbatsov][])
+* [#892](https://github.com/bbatsov/rubocop/issues/892): Make sure `Include` and `Exclude` paths in a `.rubocop.yml` are interpreted as relative to the directory of that file. ([@jonas054][])
+* [#906](https://github.com/bbatsov/rubocop/issues/906): Fixed a false positive in `LiteralInInterpolation`. ([@bbatsov][])
+* [#909](https://github.com/bbatsov/rubocop/issues/909): Handle properly multiple `rescue` clauses in `SignalException`. ([@bbatsov][])
+* [#876](https://github.com/bbatsov/rubocop/issues/876): Do a deep merge of hashes when overriding default configuration in a `.rubocop.yml` file. ([@jonas054][])
+* [#912](https://github.com/bbatsov/rubocop/issues/912): Fix a false positive in `LineEndConcatenation` for `%` string literals. ([@bbatsov][])
+* [#912](https://github.com/bbatsov/rubocop/issues/912): Handle top-level constant resolution in `DeprecatedClassMethods` (e.g. `::File.exists?`). ([@bbatsov][])
+* [#914](https://github.com/bbatsov/rubocop/issues/914): Fixed rdoc error during gem installation. ([@bbatsov][])
+* The `--only` option now enables the given cop in case it is disabled in configuration. ([@jonas054][])
+* Fix path resolution so that the default exclusion of `vendor` directories works. ([@jonas054][])
+* [#908](https://github.com/bbatsov/rubocop/issues/908): Fixed hanging while auto correct for `SpaceAfterComma` and `SpaceInsideBrackets`. ([@hiroponz][])
+* [#919](https://github.com/bbatsov/rubocop/issues/919): Don't avoid auto-correction in `HashSyntax` when there is missing space around operator. ([@jonas054][])
+* Fixed handling of floats in `NumericLiterals`. ([@bbatsov][])
+* [#927](https://github.com/bbatsov/rubocop/issues/927): Let `--auto-gen-config` overwrite an existing `rubocop-todo.yml` file instead of asking the user to remove it. ([@jonas054][])
+* [#936](https://github.com/bbatsov/rubocop/issues/936): Allow `_other` as well as `other` in `OpMethod`. ([@bbatsov][])
+
+[@bbatsov]: https://github.com/bbatsov
+[@jonas054]: https://github.com/jonas054
+[@yujinakayama]: https://github.com/yujinakayama
+[@tmorris-fiksu]: https://github.com/tmorris-fiksu
+[@mockdeep]: https://github.com/mockdeep
+[@hiroponz]: https://github.com/hiroponz
+[@tamird]: https://github.com/tamird
+[@fshowalter]: https://github.com/fshowalter
diff --git a/relnotes/v0.20.1.md b/relnotes/v0.20.1.md
new file mode 100644
index 0000000..c5d8115
--- /dev/null
+++ b/relnotes/v0.20.1.md
@@ -0,0 +1,24 @@
+RuboCop 0.20.1 is a bugfix-only release. Below is a list of the bugs we've fixed since 0.20.0:
+
+## 0.20.1 (05/04/2014)
+
+### Bugs fixed
+
+* [#940](https://github.com/bbatsov/rubocop/issues/940): Fixed `UselessAccessModifier` not handling `attr_*` correctly. ([@fshowalter][])
+* `NegatedIf` properly handles negated `unless` condition. ([@bbatsov][])
+* `NegatedWhile` properly handles negated `until` condition. ([@bbatsov][])
+* [#925](https://github.com/bbatsov/rubocop/issues/925): Do not disable the `Syntax` cop in output from `--auto-gen-config`. ([@jonas054][])
+* [#943](https://github.com/bbatsov/rubocop/issues/943): Fix auto-correction interference problem between `SpaceAfterComma` and other cops. ([@jonas054][])
+* [#954](https://github.com/bbatsov/rubocop/pull/954): Fix auto-correction bug in `NilComparison`. ([@bbatsov][])
+* [#953](https://github.com/bbatsov/rubocop/pull/953): Fix auto-correction bug in `NonNilCheck`. ([@bbatsov][])
+* [#952](https://github.com/bbatsov/rubocop/pull/952): Handle implicit receiver in `StringConversionInInterpolation`. ([@bbatsov][])
+* [#956](https://github.com/bbatsov/rubocop/pull/956): Apply `ClassMethods` check only on `class`/`module` bodies. ([@bbatsov][])
+* [#945](https://github.com/bbatsov/rubocop/issues/945): Fix SpaceBeforeFirstArg cop for multiline argument and exclude assignments. ([@cschramm][])
+* [#948](https://github.com/bbatsov/rubocop/issues/948): `Blocks` cop avoids auto-correction if it would introduce a semantic change. ([@jonas054][])
+* [#946](https://github.com/bbatsov/rubocop/issues/946): Allow non-nil checks that are the final expressions of predicate method definitions in `NonNilCheck`. ([@bbatsov][])
+* [#957](https://github.com/bbatsov/rubocop/issues/957): Allow space + comment inside parentheses, braces, and square brackets. ([@jonas054][])
+
+[@bbatsov]: https://github.com/bbatsov
+[@jonas054]: https://github.com/jonas054
+[@fshowalter]: https://github.com/fshowalter
+[@cschramm]: https://github.com/cschramm
diff --git a/rubocop-todo.yml b/rubocop-todo.yml
new file mode 100644
index 0000000..4728290
--- /dev/null
+++ b/rubocop-todo.yml
@@ -0,0 +1,20 @@
+# This configuration was generated by `rubocop --auto-gen-config`
+# on 2014-03-01 12:00:46 +1100 using RuboCop version 0.18.1.
+# The point is for the user to remove these configuration records
+# one by one as the offenses are removed from the code base.
+# Note that changes in the inspected code, or installation of new
+# versions of RuboCop, may require this file to be generated again.
+
+# Offense count: 6
+# Configuration parameters: CountComments.
+ClassLength:
+ Max: 146
+
+# Offense count: 16
+CyclomaticComplexity:
+ Max: 10
+
+# Offense count: 105
+# Configuration parameters: CountComments.
+MethodLength:
+ Max: 22
diff --git a/rubocop.gemspec b/rubocop.gemspec
new file mode 100644
index 0000000..cc5721d
--- /dev/null
+++ b/rubocop.gemspec
@@ -0,0 +1,39 @@
+# encoding: utf-8
+
+$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
+require 'rubocop/version'
+require 'English'
+
+Gem::Specification.new do |s|
+ s.name = 'rubocop'
+ s.version = Rubocop::Version::STRING
+ s.platform = Gem::Platform::RUBY
+ s.required_ruby_version = '>= 1.9.2'
+ s.authors = ['Bozhidar Batsov']
+ s.description = <<-EOF
+ Automatic Ruby code style checking tool.
+ Aims to enforce the community-driven Ruby Style Guide.
+ EOF
+
+ s.email = 'bozhidar at batsov.com'
+ s.files = `git ls-files`.split($RS)
+ s.test_files = s.files.grep(/^spec\//)
+ s.executables = s.files.grep(/^bin\//) { |f| File.basename(f) }
+ s.extra_rdoc_files = ['LICENSE.txt', 'README.md']
+ s.homepage = 'http://github.com/bbatsov/rubocop'
+ s.licenses = ['MIT']
+ s.require_paths = ['lib']
+ s.rubygems_version = '1.8.23'
+ s.summary = 'Automatic Ruby code style checking tool.'
+
+ s.add_runtime_dependency('rainbow', '>= 1.99.1', '< 3.0')
+ s.add_runtime_dependency('parser', '~> 2.1.7')
+ s.add_runtime_dependency('powerpack', '~> 0.0.6')
+ s.add_runtime_dependency('json', '>= 1.7.7', '< 2')
+ s.add_runtime_dependency('ruby-progressbar', '~> 1.4')
+ s.add_development_dependency('rake', '~> 10.1')
+ s.add_development_dependency('rspec', '~> 2.14')
+ s.add_development_dependency('yard', '~> 0.8')
+ s.add_development_dependency('bundler', '~> 1.3')
+ s.add_development_dependency('simplecov', '~> 0.7')
+end
diff --git a/spec/.rubocop.yml b/spec/.rubocop.yml
new file mode 100644
index 0000000..98fc057
--- /dev/null
+++ b/spec/.rubocop.yml
@@ -0,0 +1,5 @@
+inherit_from: ../.rubocop.yml
+
+# The documentation check doesn't make sense for test code
+Documentation:
+ Enabled: false
diff --git a/spec/isolated_environment_spec.rb b/spec/isolated_environment_spec.rb
new file mode 100644
index 0000000..8cf69a9
--- /dev/null
+++ b/spec/isolated_environment_spec.rb
@@ -0,0 +1,24 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe 'isolated environment', :isolated_environment do
+ include FileHelper
+
+ let(:cli) { Rubocop::CLI.new }
+
+ before(:each) { $stdout = StringIO.new }
+ after(:each) { $stdout = STDOUT }
+
+ # Configuration files above the work directory shall not disturb the
+ # tests. This is especially important on Windows where the temporary
+ # directory is under the user's home directory. On any platform we don't want
+ # a .rubocop.yml file in the temporary directory to affect the outcome of
+ # rspec.
+ it 'is not affected by a config file above the work directory' do
+ create_file('../.rubocop.yml', ['inherit_from: missing_file.yml'])
+ create_file('ex.rb', ['# encoding: utf-8'])
+ # A return value of 0 means that the erroneous config file was not read.
+ expect(cli.run([])).to eq(0)
+ end
+end
diff --git a/spec/project_spec.rb b/spec/project_spec.rb
new file mode 100644
index 0000000..41bb356
--- /dev/null
+++ b/spec/project_spec.rb
@@ -0,0 +1,118 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe 'RuboCop Project' do
+ describe 'default configuration file' do
+ let(:cop_names) { Rubocop::Cop::Cop.all.map(&:cop_name) }
+
+ subject(:default_config) do
+ Rubocop::ConfigLoader.load_file('config/default.yml')
+ end
+
+ it 'has configuration for all cops' do
+ expect(default_config.keys.sort).to eq((['AllCops'] + cop_names).sort)
+ end
+
+ it 'has a nicely formatted description for all cops' do
+ cop_names.each do |name|
+ description = default_config[name]['Description']
+ expect(description).not_to be_nil
+ expect(description).not_to include("\n")
+ end
+ end
+ end
+
+ describe 'changelog' do
+ subject(:changelog) do
+ path = File.join(File.dirname(__FILE__), '..', 'CHANGELOG.md')
+ File.read(path)
+ end
+
+ it 'has link definitions for all implicit links' do
+ implicit_link_names = changelog.scan(/\[([^\]]+)\]\[\]/).flatten.uniq
+ implicit_link_names.each do |name|
+ expect(changelog).to include("[#{name}]: http")
+ end
+ end
+
+ describe 'entry' do
+ subject(:entries) { lines.grep(/^\*/).map(&:chomp) }
+ let(:lines) { changelog.each_line }
+
+ it 'has a whitespace between the * and the body' do
+ entries.each do |entry|
+ expect(entry).to match(/^\* \S/)
+ end
+ end
+
+ context 'after version 0.14.0' do
+ let(:lines) do
+ changelog.each_line.take_while do |line|
+ !line.start_with?('## 0.14.0')
+ end
+ end
+
+ it 'has a link to the contributors at the end' do
+ entries.each do |entry|
+ expect(entry).to match(/\(\[@\S+\]\[\](?:, \[@\S+\]\[\])*\)$/)
+ end
+ end
+ end
+
+ describe 'link to related issue' do
+ let(:issues) do
+ entries.map do |entry|
+ entry.match(/\[(?<number>[#\d]+)\]\((?<url>[^\)]+)\)/)
+ end.compact
+ end
+
+ it 'has an issue number prefixed with #' do
+ issues.each do |issue|
+ expect(issue[:number]).to match(/^#\d+$/)
+ end
+ end
+
+ it 'has a valid URL' do
+ issues.each do |issue|
+ number = issue[:number].gsub(/\D/, '')
+ pattern = %r{^https://github\.com/bbatsov/rubocop/(?:issues|pull)/#{number}$} # rubocop:disable LineLength
+ expect(issue[:url]).to match(pattern)
+ end
+ end
+
+ it 'has a colon and a whitespace at the end' do
+ entries_including_issue_link = entries.select do |entry|
+ entry.match(/^\*\s*\[/)
+ end
+
+ entries_including_issue_link.each do |entry|
+ expect(entry).to include('): ')
+ end
+ end
+ end
+
+ describe 'body' do
+ let(:bodies) do
+ entries.map do |entry|
+ entry
+ .sub(/^\*\s*(?:\[.+?\):\s*)?/, '')
+ .sub(/\s*\([^\)]+\)$/, '')
+ end
+ end
+
+ it 'does not start with a lower case' do
+ bodies.each do |body|
+ expect(body).not_to match(/^[a-z]/)
+ end
+ end
+
+ it 'ends with a punctuation' do
+ bodies.each do |body|
+ expect(body).to match(/[\.\!]$/)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/cli_spec.rb b/spec/rubocop/cli_spec.rb
new file mode 100644
index 0000000..17cfa53
--- /dev/null
+++ b/spec/rubocop/cli_spec.rb
@@ -0,0 +1,1830 @@
+# encoding: utf-8
+
+require 'fileutils'
+require 'tmpdir'
+require 'spec_helper'
+require 'timeout'
+
+describe Rubocop::CLI, :isolated_environment do
+ include FileHelper
+
+ subject(:cli) { described_class.new }
+
+ before(:each) do
+ $stdout = StringIO.new
+ $stderr = StringIO.new
+ Rubocop::ConfigLoader.debug = false
+ end
+
+ after(:each) do
+ $stdout = STDOUT
+ $stderr = STDERR
+ end
+
+ def abs(path)
+ File.expand_path(path)
+ end
+
+ describe 'option' do
+ describe '--version' do
+ it 'exits cleanly' do
+ expect { cli.run ['-v'] }.to exit_with_code(0)
+ expect { cli.run ['--version'] }.to exit_with_code(0)
+ expect($stdout.string).to eq((Rubocop::Version::STRING + "\n") * 2)
+ end
+ end
+
+ describe '--auto-correct' do
+ it 'can correct two problems with blocks' do
+ # {} should be do..end and space is missing.
+ create_file('example.rb', ['# encoding: utf-8',
+ '(1..10).each{ |i|',
+ ' puts i',
+ '}'])
+ expect(cli.run(['--auto-correct'])).to eq(1)
+ expect(IO.read('example.rb'))
+ .to eq(['# encoding: utf-8',
+ '(1..10).each do |i|',
+ ' puts i',
+ 'end'].join("\n") + "\n")
+ end
+
+ it 'can handle spaces when removing braces' do
+ create_file('example.rb',
+ ['# encoding: utf-8',
+ "assert_post_status_code 400, 's', {:type => 'bad'}"])
+ expect(cli.run(%w(--auto-correct --format emacs))).to eq(1)
+ expect(IO.read('example.rb'))
+ .to eq(['# encoding: utf-8',
+ "assert_post_status_code 400, 's', type: 'bad'",
+ ''].join("\n"))
+ e = abs('example.rb')
+ expect($stdout.string)
+ .to eq(["#{e}:2:35: C: [Corrected] Redundant curly braces around " \
+ "a hash parameter.",
+ "#{e}:2:35: C: [Corrected] Use the new Ruby 1.9 hash " \
+ "syntax.",
+ # TODO: Don't report that a problem is corrected when it
+ # actually went away due to another correction.
+ "#{e}:2:35: C: [Corrected] Space inside { missing.",
+ # TODO: Don't report duplicates (HashSyntax in this case).
+ "#{e}:2:36: C: [Corrected] Use the new Ruby 1.9 hash " \
+ "syntax.",
+ "#{e}:2:50: C: [Corrected] Space inside } missing.",
+ ''].join("\n"))
+ end
+
+ # A case where two cops, EmptyLinesAroundBody and EmptyLines, try to
+ # remove the same line in autocorrect.
+ it 'can correct two empty lines at end of class body' do
+ create_file('example.rb', ['class Test',
+ ' def f',
+ ' end',
+ '',
+ '',
+ 'end'])
+ expect(cli.run(['--auto-correct'])).to eq(1)
+ expect($stderr.string).to eq('')
+ expect(IO.read('example.rb')).to eq(['class Test',
+ ' def f',
+ ' end',
+ 'end'].join("\n") + "\n")
+ end
+
+ # A case where WordArray's correction can be clobbered by
+ # AccessModifierIndentation's correction.
+ it 'can correct indentation and another thing' do
+ create_file('example.rb', ['# encoding: utf-8',
+ 'class Dsl',
+ 'private',
+ ' A = ["git", "path"]',
+ 'end'])
+ expect(cli.run(%w(--auto-correct --format emacs))).to eq(1)
+ expect(IO.read('example.rb')).to eq(['# encoding: utf-8',
+ 'class Dsl',
+ ' private',
+ ' A = %w(git path)',
+ 'end'].join("\n") + "\n")
+ e = abs('example.rb')
+ expect($stdout.string)
+ .to eq(["#{e}:2:1: C: Missing top-level class documentation " \
+ 'comment.',
+ "#{e}:3:1: C: [Corrected] Indent access modifiers like " \
+ '`private`.',
+ "#{e}:3:1: C: Keep a blank line before and after `private`.",
+ "#{e}:3:1: W: Useless `private` access modifier.",
+ # An offense that moves around during auto-correction will
+ # appear to be duplicated:
+ "#{e}:3:3: C: Keep a blank line before and after `private`.",
+ "#{e}:3:3: W: Useless `private` access modifier.",
+ "#{e}:4:7: C: [Corrected] Use `%w` or `%W` " \
+ 'for array of words.',
+ "#{e}:4:8: C: [Corrected] Prefer single-quoted strings " \
+ "when you don't need string interpolation or special " \
+ 'symbols.',
+ # Another instance of the same offense reported twice:
+ "#{e}:4:15: C: [Corrected] Prefer single-quoted strings " \
+ "when you don't need string interpolation or special " \
+ 'symbols.',
+ ''].join("\n"))
+ end
+
+ # A case where the same cop could try to correct an offense twice in one
+ # place.
+ it 'can correct empty line inside special form of nested modules' do
+ create_file('example.rb', ['module A module B',
+ '',
+ 'end end'])
+ expect(cli.run(['--auto-correct'])).to eq(1)
+ expect(IO.read('example.rb')).to eq(['module A module B',
+ 'end end'].join("\n") + "\n")
+ end
+
+ it 'can correct single line methods' do
+ create_file('example.rb', ['# encoding: utf-8',
+ 'def func1; do_something end # comment',
+ 'def func2() do_1; do_2; end'])
+ expect(cli.run(%w(--auto-correct --format offenses))).to eq(1)
+ expect(IO.read('example.rb')).to eq(['# encoding: utf-8',
+ '# comment',
+ 'def func1',
+ ' do_something',
+ 'end',
+ '',
+ 'def func2',
+ ' do_1',
+ ' do_2',
+ 'end',
+ ''].join("\n"))
+ expect($stdout.string).to eq(['',
+ '6 TrailingWhitespace',
+ '4 Semicolon',
+ '2 SingleLineMethods',
+ '1 DefWithParentheses',
+ '1 EmptyLineBetweenDefs',
+ '--',
+ '14 Total',
+ '',
+ ''].join("\n"))
+ end
+
+ # In this example, the auto-correction (changing "raise" to "fail")
+ # creates a new problem (alignment of parameters), which is also
+ # corrected automatically.
+ it 'can correct a problems and the problem it creates' do
+ create_file('example.rb',
+ ['# encoding: utf-8',
+ 'raise NotImplementedError,',
+ " 'Method should be overridden in child classes'"])
+ expect(cli.run(['--auto-correct'])).to eq(1)
+ expect(IO.read('example.rb'))
+ .to eq(['# encoding: utf-8',
+ 'fail NotImplementedError,',
+ " 'Method should be overridden in child classes'"]
+ .join("\n") + "\n")
+ expect($stdout.string)
+ .to eq(['Inspecting 1 file',
+ 'C',
+ '',
+ 'Offenses:',
+ '',
+ 'example.rb:2:1: C: [Corrected] Use fail instead of ' \
+ 'raise to signal exceptions.',
+ 'raise NotImplementedError,',
+ '^^^^^',
+ 'example.rb:3:7: C: [Corrected] Align the parameters of a ' \
+ 'method call if they span more than one line.',
+ " 'Method should be overridden in child classes'",
+ ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^',
+ '',
+ '1 file inspected, 2 offenses detected, 2 offenses ' \
+ 'corrected',
+ ''].join("\n"))
+ end
+
+ # Thanks to repeated auto-correction, we can get rid of the trailing
+ # spaces, and then the extra empty line.
+ it 'can correct two problems in the same place' do
+ create_file('example.rb',
+ ['# encoding: utf-8',
+ '# Example class.',
+ 'class Klass',
+ ' ',
+ ' def f',
+ ' end',
+ 'end'])
+ expect(cli.run(['--auto-correct'])).to eq(1)
+ expect(IO.read('example.rb'))
+ .to eq(['# encoding: utf-8',
+ '# Example class.',
+ 'class Klass',
+ ' def f',
+ ' end',
+ 'end'].join("\n") + "\n")
+ expect($stderr.string).to eq('')
+ expect($stdout.string)
+ .to eq(['Inspecting 1 file',
+ 'C',
+ '',
+ 'Offenses:',
+ '',
+ 'example.rb:4:1: C: [Corrected] Extra empty line detected ' \
+ 'at body beginning.',
+ 'example.rb:4:1: C: [Corrected] Trailing whitespace ' \
+ 'detected.',
+ '',
+ '1 file inspected, 2 offenses detected, 2 offenses ' \
+ 'corrected',
+ ''].join("\n"))
+ end
+
+ it 'can correct MethodDefParentheses and other offense' do
+ create_file('example.rb',
+ ['# encoding: utf-8',
+ 'def primes limit',
+ ' 1.upto(limit).find_all { |i| is_prime[i] }',
+ 'end'])
+ expect(cli.run(%w(-D --auto-correct))).to eq(1)
+ expect($stderr.string).to eq('')
+ expect(IO.read('example.rb'))
+ .to eq(['# encoding: utf-8',
+ 'def primes(limit)',
+ ' 1.upto(limit).select { |i| is_prime[i] }',
+ 'end'].join("\n") + "\n")
+ expect($stdout.string)
+ .to eq(['Inspecting 1 file',
+ 'C',
+ '',
+ 'Offenses:',
+ '',
+ 'example.rb:2:12: C: [Corrected] MethodDefParentheses: ' \
+ 'Use def with parentheses when there are parameters.',
+ 'def primes limit',
+ ' ^^^^^',
+ 'example.rb:3:17: C: [Corrected] CollectionMethods: ' \
+ 'Prefer select over find_all.',
+ ' 1.upto(limit).find_all { |i| is_prime[i] }',
+ ' ^^^^^^^^',
+ '',
+ '1 file inspected, 2 offenses detected, 2 offenses ' \
+ 'corrected',
+ ''].join("\n"))
+ end
+
+ it 'can correct WordArray and SpaceAfterComma offenses' do
+ create_file('example.rb',
+ ['# encoding: utf-8',
+ "f(type: ['offline','offline_payment'],",
+ " bar_colors: ['958c12','953579','ff5800','0085cc'])"])
+ expect(cli.run(%w(-D --auto-correct --format o))).to eq(1)
+ expect($stdout.string)
+ .to eq(['',
+ '4 SpaceAfterComma',
+ '2 WordArray',
+ '--',
+ '6 Total',
+ '',
+ ''].join("\n"))
+ expect(IO.read('example.rb'))
+ .to eq(['# encoding: utf-8',
+ 'f(type: %w(offline offline_payment),',
+ ' bar_colors: %w(958c12 953579 ff5800 0085cc))',
+ ''].join("\n"))
+ end
+
+ it 'can correct SpaceAfterComma and HashSyntax offenses' do
+ create_file('example.rb',
+ ['# encoding: utf-8',
+ "I18n.t('description',:property_name => property.name)"])
+ expect(cli.run(%w(-D --auto-correct --format emacs))).to eq(1)
+ expect($stdout.string)
+ .to eq(["#{abs('example.rb')}:2:21: C: [Corrected] " \
+ "SpaceAfterComma: Space missing after comma.",
+ "#{abs('example.rb')}:2:22: C: [Corrected] " \
+ "HashSyntax: Use the new Ruby 1.9 hash syntax.",
+ ''].join("\n"))
+ expect(IO.read('example.rb'))
+ .to eq(['# encoding: utf-8',
+ "I18n.t('description', property_name: property.name)",
+ ''].join("\n"))
+ end
+
+ it 'can correct HashSyntax and SpaceAroundOperators offenses' do
+ create_file('example.rb',
+ ['# encoding: utf-8',
+ '{ :b=>1 }'])
+ expect(cli.run(%w(-D --auto-correct --format emacs))).to eq(1)
+ expect(IO.read('example.rb')).to eq(['# encoding: utf-8',
+ '{ b: 1 }',
+ ''].join("\n"))
+ expect($stdout.string)
+ .to eq(["#{abs('example.rb')}:2:3: C: [Corrected] HashSyntax: Use " \
+ "the new Ruby 1.9 hash syntax.",
+ "#{abs('example.rb')}:2:5: C: [Corrected] " \
+ "SpaceAroundOperators: Surrounding space missing for " \
+ "operator '=>'.",
+ ''].join("\n"))
+ end
+
+ it 'can correct HashSyntax when --only is used' do
+ create_file('example.rb',
+ ['# encoding: utf-8',
+ '{ :b=>1 }'])
+ expect(cli.run(%w(--auto-correct -f emacs --only HashSyntax))).to eq(1)
+ expect($stderr.string).to eq('')
+ expect(IO.read('example.rb')).to eq(['# encoding: utf-8',
+ '{ b: 1 }',
+ ''].join("\n"))
+ expect($stdout.string)
+ .to eq(["#{abs('example.rb')}:2:3: C: [Corrected] Use the new " \
+ "Ruby 1.9 hash syntax.",
+ ''].join("\n"))
+ end
+
+ it 'can correct TrailingBlankLines and TrailingWhitespace offenses' do
+ create_file('example.rb',
+ ['# encoding: utf-8',
+ '',
+ ' ',
+ '',
+ ''])
+ expect(cli.run(%w(--auto-correct --format emacs))).to eq(1)
+ expect($stderr.string).to eq('')
+ expect(IO.read('example.rb')).to eq("# encoding: utf-8\n")
+ expect($stdout.string)
+ .to eq(["#{abs('example.rb')}:2:1: C: [Corrected] 3 trailing " \
+ "blank lines detected.",
+ "#{abs('example.rb')}:3:1: C: [Corrected] Trailing " \
+ "whitespace detected.",
+ ''].join("\n"))
+ end
+
+ it 'can correct MethodCallParentheses and EmptyLiteral offenses' do
+ create_file('example.rb',
+ ['# encoding: utf-8',
+ 'Hash.new()'])
+ expect(cli.run(%w(--auto-correct --format emacs))).to eq(1)
+ expect($stderr.string).to eq('')
+ expect(IO.read('example.rb')).to eq(['# encoding: utf-8',
+ '{}',
+ ''].join("\n"))
+ expect($stdout.string)
+ .to eq(["#{abs('example.rb')}:2:1: C: [Corrected] Use hash " \
+ "literal {} instead of Hash.new.",
+ "#{abs('example.rb')}:2:9: C: [Corrected] Do not use " \
+ "parentheses for method calls with no arguments.",
+ ''].join("\n"))
+ end
+
+ it 'can correct IndentHash offenses with separator style' do
+ create_file('example.rb',
+ ['# encoding: utf-8',
+ 'CONVERSION_CORRESPONDENCE = {',
+ ' match_for_should: :match,',
+ ' match_for_should_not: :match_when_negated,',
+ ' failure_message_for_should: :failure_message,',
+ 'failure_message_for_should_not: :failure_message_when',
+ '}'])
+ create_file('.rubocop.yml',
+ ['AlignHash:',
+ ' EnforcedColonStyle: separator'])
+ expect(cli.run(%w(--auto-correct))).to eq(1)
+ expect(IO.read('example.rb'))
+ .to eq(['# encoding: utf-8',
+ 'CONVERSION_CORRESPONDENCE = {',
+ ' match_for_should: :match,',
+ ' match_for_should_not: :match_when_negated,',
+ ' failure_message_for_should: :failure_message,',
+ ' failure_message_for_should_not: :failure_message_when',
+ '}',
+ ''].join("\n"))
+ end
+
+ it 'should not hang SpaceAfterPunctuation and SpaceInsideParens' do
+ create_file('example.rb',
+ ['# encoding: utf-8',
+ 'some_method(a, )'])
+ Timeout.timeout(10) do
+ expect(cli.run(%w(--auto-correct))).to eq(1)
+ end
+ expect($stderr.string).to eq('')
+ expect(IO.read('example.rb')).to eq(['# encoding: utf-8',
+ 'some_method(a,)',
+ ''].join("\n"))
+ end
+
+ it 'should not hang SpaceAfterPunctuation and SpaceInsideBrackets' do
+ create_file('example.rb',
+ ['# encoding: utf-8',
+ 'puts [1, ]'])
+ Timeout.timeout(10) do
+ expect(cli.run(%w(--auto-correct))).to eq(1)
+ end
+ expect($stderr.string).to eq('')
+ expect(IO.read('example.rb')).to eq(['# encoding: utf-8',
+ 'puts [1,]',
+ ''].join("\n"))
+ end
+ end
+
+ describe '--auto-gen-config' do
+ it 'overwrites an existing todo file' do
+ create_file('example1.rb', ['# encoding: utf-8',
+ 'x= 0 ',
+ '#' * 85,
+ 'y ',
+ 'puts x'])
+ create_file('rubocop-todo.yml', ['LineLength:',
+ ' Enabled: false'])
+ create_file('.rubocop.yml', ['inherit_from: rubocop-todo.yml'])
+ expect(cli.run(['--auto-gen-config'])).to eq(1)
+ expect(IO.readlines('rubocop-todo.yml')[7..-1].map(&:chomp))
+ .to eq(['# Offense count: 1',
+ 'LineLength:',
+ ' Max: 85',
+ '',
+ '# Offense count: 1',
+ '# Cop supports --auto-correct.',
+ 'SpaceAroundOperators:',
+ ' Enabled: false',
+ '',
+ '# Offense count: 2',
+ '# Cop supports --auto-correct.',
+ 'TrailingWhitespace:',
+ ' Enabled: false'])
+
+ # Create new CLI instance to avoid using cached configuration.
+ new_cli = described_class.new
+
+ expect(new_cli.run(['example1.rb'])).to eq(0)
+ end
+
+ it 'exits with error if file arguments are given' do
+ create_file('example1.rb', ['# encoding: utf-8',
+ 'x= 0 ',
+ '#' * 85,
+ 'y ',
+ 'puts x'])
+ expect { cli.run(['--auto-gen-config', 'example1.rb']) }
+ .to exit_with_code(1)
+ expect($stderr.string)
+ .to eq('--auto-gen-config can not be combined with any other ' \
+ "arguments.\n")
+ expect($stdout.string).to eq('')
+ end
+
+ it 'can generate a todo list' do
+ create_file('example1.rb', ['# encoding: utf-8',
+ '$x= 0 ',
+ '#' * 90,
+ '#' * 85,
+ 'y ',
+ 'puts x'])
+ create_file('example2.rb', ['# encoding: utf-8',
+ "\tx = 0",
+ 'puts x'])
+ expect(cli.run(['--auto-gen-config'])).to eq(1)
+ expect($stderr.string).to eq('')
+ expect($stdout.string)
+ .to include(['Created rubocop-todo.yml.',
+ 'Run `rubocop --config rubocop-todo.yml`, or',
+ 'add inherit_from: rubocop-todo.yml in a ' \
+ '.rubocop.yml file.',
+ ''].join("\n"))
+ expected =
+ ['# This configuration was generated by `rubocop --auto-gen-config`',
+ /# on .* using RuboCop version .*/,
+ '# The point is for the user to remove these configuration records',
+ '# one by one as the offenses are removed from the code base.',
+ '# Note that changes in the inspected code, or installation of new',
+ '# versions of RuboCop, may require this file to be generated ' \
+ 'again.',
+ '',
+ '# Offense count: 1',
+ '# Configuration parameters: AllowedVariables.',
+ 'GlobalVars:',
+ ' Enabled: false',
+ '',
+ '# Offense count: 1',
+ '# Cop supports --auto-correct.',
+ 'IndentationConsistency:',
+ ' Enabled: false',
+ '',
+ '# Offense count: 2',
+ 'LineLength:',
+ ' Max: 90',
+ '',
+ '# Offense count: 1',
+ '# Cop supports --auto-correct.',
+ 'SpaceAroundOperators:',
+ ' Enabled: false',
+ '',
+ '# Offense count: 1',
+ 'Tab:',
+ ' Enabled: false',
+ '',
+ '# Offense count: 2',
+ '# Cop supports --auto-correct.',
+ 'TrailingWhitespace:',
+ ' Enabled: false']
+ actual = IO.read('rubocop-todo.yml').split($RS)
+ expected.each_with_index do |line, ix|
+ if line.is_a?(String)
+ expect(actual[ix]).to eq(line)
+ else
+ expect(actual[ix]).to match(line)
+ end
+ end
+ end
+
+ it 'does not generate configuration for the Syntax cop' do
+ create_file('example1.rb', ['# encoding: utf-8',
+ 'x = < ', # Syntax error
+ 'puts x'])
+ create_file('example2.rb', ['# encoding: utf-8',
+ "\tx = 0",
+ 'puts x'])
+ expect(cli.run(['--auto-gen-config'])).to eq(1)
+ expect($stderr.string).to eq('')
+ expected =
+ ['# This configuration was generated by `rubocop --auto-gen-config`',
+ /# on .* using RuboCop version .*/,
+ '# The point is for the user to remove these configuration records',
+ '# one by one as the offenses are removed from the code base.',
+ '# Note that changes in the inspected code, or installation of new',
+ '# versions of RuboCop, may require this file to be generated ' \
+ 'again.',
+ '',
+ '# Offense count: 1',
+ '# Cop supports --auto-correct.',
+ 'IndentationConsistency:',
+ ' Enabled: false',
+ '',
+ '# Offense count: 1',
+ 'Tab:',
+ ' Enabled: false']
+ actual = IO.read('rubocop-todo.yml').split($RS)
+ expect(actual.length).to eq(expected.length)
+ expected.each_with_index do |line, ix|
+ if line.is_a?(String)
+ expect(actual[ix]).to eq(line)
+ else
+ expect(actual[ix]).to match(line)
+ end
+ end
+ end
+
+ it 'generates a todo list that removes the reports' do
+ create_file('example.rb', ['# encoding: utf-8',
+ 'y.gsub!(%r{abc/xyz}, "#{x}")'])
+ expect(cli.run(%w(--format emacs))).to eq(1)
+ expect($stdout.string)
+ .to eq(["#{abs('example.rb')}:2:9: C: Use %r only for regular " \
+ "expressions matching more than 1 '/' character.",
+ ''].join("\n"))
+ expect(cli.run(['--auto-gen-config'])).to eq(1)
+ expected =
+ ['# This configuration was generated by `rubocop --auto-gen-config`',
+ /# on .* using RuboCop version .*/,
+ '# The point is for the user to remove these configuration records',
+ '# one by one as the offenses are removed from the code base.',
+ '# Note that changes in the inspected code, or installation of new',
+ '# versions of RuboCop, may require this file to be generated ' \
+ 'again.',
+ '',
+ '# Offense count: 1',
+ 'RegexpLiteral:',
+ ' MaxSlashes: 0']
+ actual = IO.read('rubocop-todo.yml').split($RS)
+ expected.each_with_index do |line, ix|
+ if line.is_a?(String)
+ expect(actual[ix]).to eq(line)
+ else
+ expect(actual[ix]).to match(line)
+ end
+ end
+ $stdout = StringIO.new
+ result = cli.run(%w(--config rubocop-todo.yml --format emacs))
+ expect($stdout.string).to eq('')
+ expect(result).to eq(0)
+ end
+ end
+
+ describe '--only' do
+ it 'runs just one cop' do
+ create_file('example.rb', ['if x== 0 ',
+ "\ty",
+ 'end'])
+ # IfUnlessModifier depends on the configuration of LineLength.
+
+ expect(cli.run(['--format', 'simple',
+ '--only', 'IfUnlessModifier',
+ 'example.rb'])).to eq(1)
+ expect($stdout.string)
+ .to eq(['== example.rb ==',
+ 'C: 1: 1: Favor modifier if usage when ' \
+ 'having a single-line body. Another good alternative is ' \
+ 'the usage of control flow &&/||.',
+ '',
+ '1 file inspected, 1 offense detected',
+ ''].join("\n"))
+ end
+
+ it 'enables the given cop' do
+ create_file('example.rb', ['x = 0 ',
+ # Disabling comments still apply.
+ '# rubocop:disable TrailingWhitespace',
+ 'y = 1 '])
+
+ create_file('.rubocop.yml', ['TrailingWhitespace:',
+ ' Enabled: false'])
+
+ expect(cli.run(['--format', 'simple',
+ '--only', 'TrailingWhitespace',
+ 'example.rb'])).to eq(1)
+ expect($stderr.string).to eq('')
+ expect($stdout.string)
+ .to eq(['== example.rb ==',
+ 'C: 1: 6: Trailing whitespace detected.',
+ '',
+ '1 file inspected, 1 offense detected',
+ ''].join("\n"))
+ end
+ end
+
+ describe '--lint' do
+ it 'runs only lint cops' do
+ create_file('example.rb', ['if 0 ',
+ "\ty",
+ 'end'])
+ # IfUnlessModifier depends on the configuration of LineLength.
+
+ expect(cli.run(['--format', 'simple', '--lint',
+ 'example.rb'])).to eq(1)
+ expect($stdout.string)
+ .to eq(['== example.rb ==',
+ 'W: 1: 4: Literal 0 appeared in a condition.',
+ '',
+ '1 file inspected, 1 offense detected',
+ ''].join("\n"))
+ end
+ end
+
+ describe '-d/--debug' do
+ it 'shows config files' do
+ create_file('example1.rb', "\tputs 0")
+ expect(cli.run(['--debug', 'example1.rb'])).to eq(1)
+ home = File.dirname(File.dirname(File.dirname(__FILE__)))
+ expect($stdout.string.lines.grep(/configuration/).map(&:chomp))
+ .to eq(["For #{abs('')}:" \
+ " configuration from #{home}/config/default.yml",
+ "Inheriting configuration from #{home}/config/enabled.yml",
+ "Inheriting configuration from #{home}/config/disabled.yml"
+ ])
+ end
+
+ it 'shows cop names' do
+ create_file('example1.rb', "\tputs 0")
+ expect(cli.run(['--format',
+ 'emacs',
+ '--debug',
+ 'example1.rb'])).to eq(1)
+ expect($stdout.string.lines.to_a[-1])
+ .to eq(["#{abs('example1.rb')}:1:1: C: Tab: Tab detected.",
+ ''].join("\n"))
+ end
+ end
+
+ describe '-D/--display-cop-names' do
+ it 'shows cop names' do
+ create_file('example1.rb', "\tputs 0")
+ expect(cli.run(['--format',
+ 'emacs',
+ '--debug',
+ 'example1.rb'])).to eq(1)
+ expect($stdout.string.lines.to_a[-1])
+ .to eq(["#{abs('example1.rb')}:1:1: C: Tab: Tab detected.",
+ ''].join("\n"))
+ end
+ end
+
+ describe '--show-cops' do
+ shared_examples(:prints_config) do
+ it 'prints the current configuration' do
+ out = stdout.lines.to_a
+ printed_config = YAML.load(out.join)
+ cop_names = (cop_list[0] || '').split(',')
+ cop_names.each do |cop_name|
+ global_conf[cop_name].each do |key, value|
+ printed_value = printed_config[cop_name][key]
+ expect(printed_value).to eq(value)
+ end
+ end
+ end
+ end
+
+ let(:cops) { Rubocop::Cop::Cop.all }
+
+ let(:global_conf) do
+ config_path =
+ Rubocop::ConfigLoader.configuration_file_for(Dir.pwd.to_s)
+ Rubocop::ConfigLoader.configuration_from_file(config_path)
+ end
+
+ let(:stdout) { $stdout.string }
+
+ before do
+ create_file('.rubocop.yml', ['LineLength:',
+ ' Max: 110'])
+ expect { cli.run ['--show-cops'] + cop_list }.to exit_with_code(0)
+ end
+
+ context 'with no args' do
+ let(:cop_list) { [] }
+
+ # Extracts the first line out of the description
+ def short_description_of_cop(cop)
+ desc = full_description_of_cop(cop)
+ desc ? desc.lines.first.strip : ''
+ end
+
+ # Gets the full description of the cop or nil if no description is set.
+ def full_description_of_cop(cop)
+ cop_config = global_conf.for_cop(cop)
+ cop_config['Description']
+ end
+
+ it 'prints all available cops and their description' do
+ cops.each do |cop|
+ expect(stdout).to include cop.cop_name
+ # Because of line breaks, we will only find the beginning.
+ expect(stdout).to include short_description_of_cop(cop)[0..60]
+ end
+ end
+
+ it 'prints all types' do
+ cops
+ .types
+ .map(&:to_s)
+ .map(&:capitalize)
+ .each { |type| expect(stdout).to include(type) }
+ end
+
+ it 'prints all cops in their right type listing' do
+ lines = stdout.lines
+ lines.slice_before(/Type /).each do |slice|
+ types = cops.types.map(&:to_s).map(&:capitalize)
+ current = types.delete(slice.shift[/Type '(?<c>[^'']+)'/, 'c'])
+ # all cops in their type listing
+ cops.with_type(current).each do |cop|
+ expect(slice.any? { |l| l.include? cop.cop_name }).to be_true
+ end
+
+ # no cop in wrong type listing
+ types.each do |type|
+ cops.with_type(type).each do |cop|
+ expect(slice.any? { |l| l.include? cop.cop_name }).to be_false
+ end
+ end
+ end
+ end
+
+ include_examples :prints_config
+ end
+
+ context 'with one cop given' do
+ let(:cop_list) { ['Tab'] }
+
+ it 'prints that cop and nothing else' do
+ expect(stdout).to eq(['Tab:',
+ ' Description: No hard tabs.',
+ ' Enabled: true',
+ '',
+ ''].join("\n"))
+ end
+
+ include_examples :prints_config
+ end
+
+ context 'with two cops given' do
+ let(:cop_list) { ['Tab,LineLength'] }
+ include_examples :prints_config
+ end
+
+ context 'with one of the cops misspelled' do
+ let(:cop_list) { ['Tab,X123'] }
+
+ it 'skips the unknown cop' do
+ expect(stdout).to eq(['Tab:',
+ ' Description: No hard tabs.',
+ ' Enabled: true',
+ '',
+ ''].join("\n"))
+ end
+ end
+ end
+
+ describe '-f/--format' do
+ let(:target_file) { 'example.rb' }
+
+ before do
+ create_file(target_file, ['# encoding: utf-8',
+ '#' * 90])
+ end
+
+ describe 'builtin formatters' do
+ context 'when simple format is specified' do
+ it 'outputs with simple format' do
+ cli.run(['--format', 'simple', 'example.rb'])
+ expect($stdout.string)
+ .to include(["== #{target_file} ==",
+ 'C: 2: 80: Line is too long. [90/79]'].join("\n"))
+ end
+ end
+
+ context 'when clang format is specified' do
+ it 'outputs with clang format' do
+ create_file('example1.rb', ['# encoding: utf-8',
+ 'x= 0 ',
+ '#' * 85,
+ 'y ',
+ 'puts x'])
+ create_file('example2.rb', ['# encoding: utf-8',
+ "\tx",
+ 'def a',
+ ' puts',
+ 'end'])
+ create_file('example3.rb', ['# encoding: utf-8',
+ 'def badName',
+ ' if something',
+ ' test',
+ ' end',
+ 'end'])
+ expect(cli.run(['--format', 'clang', 'example1.rb',
+ 'example2.rb', 'example3.rb']))
+ .to eq(1)
+ expect($stdout.string)
+ .to eq(['example1.rb:2:2: C: Surrounding space missing for ' \
+ "operator '='.",
+ 'x= 0 ',
+ ' ^',
+ 'example1.rb:2:5: C: Trailing whitespace detected.',
+ 'x= 0 ',
+ ' ^',
+ 'example1.rb:3:80: C: Line is too long. [85/79]',
+ '###################################################' \
+ '##################################',
+ ' ' \
+ ' ^^^^^^',
+ 'example1.rb:4:2: C: Trailing whitespace detected.',
+ 'y ',
+ ' ^',
+ 'example2.rb:2:1: C: Tab detected.',
+ "\tx",
+ '^',
+ 'example2.rb:3:1: C: Inconsistent indentation ' \
+ 'detected.',
+ 'def a',
+ '^^^^^',
+ 'example2.rb:4:1: C: Use 2 (not 3) spaces for ' \
+ 'indentation.',
+ ' puts',
+ '^^^',
+ 'example3.rb:2:5: C: Use snake_case for methods.',
+ 'def badName',
+ ' ^^^^^^^',
+ 'example3.rb:3:3: C: Favor modifier if usage ' \
+ 'when having a single-line body. Another good ' \
+ 'alternative is the usage of control flow &&/||.',
+ ' if something',
+ ' ^^',
+ 'example3.rb:5:5: W: end at 5, 4 is not aligned ' \
+ 'with if at 3, 2',
+ ' end',
+ ' ^^^',
+ '',
+ '3 files inspected, 10 offenses detected',
+ ''].join("\n"))
+ end
+ end
+
+ context 'when emacs format is specified' do
+ it 'outputs with emacs format' do
+ create_file('example1.rb', ['# encoding: utf-8',
+ 'x= 0 ',
+ 'y ',
+ 'puts x'])
+ create_file('example2.rb', ['# encoding: utf-8',
+ "\tx = 0",
+ 'puts x'])
+ expect(cli.run(['--format', 'emacs', 'example1.rb',
+ 'example2.rb'])).to eq(1)
+ expected_output =
+ ["#{abs('example1.rb')}:2:2: C: Surrounding space missing" \
+ " for operator '='.",
+ "#{abs('example1.rb')}:2:5: C: Trailing whitespace detected.",
+ "#{abs('example1.rb')}:3:2: C: Trailing whitespace detected.",
+ "#{abs('example2.rb')}:2:1: C: Tab detected.",
+ "#{abs('example2.rb')}:3:1: C: Inconsistent indentation " \
+ 'detected.',
+ ''].join("\n")
+ expect($stdout.string).to eq(expected_output)
+ end
+ end
+
+ context 'when unknown format name is specified' do
+ it 'aborts with error message' do
+ expect { cli.run(['--format', 'unknown', 'example.rb']) }
+ .to exit_with_code(1)
+ expect($stderr.string)
+ .to include('No formatter for "unknown"')
+ end
+ end
+ end
+
+ describe 'custom formatter' do
+ let(:target_file) { abs('example.rb') }
+
+ context 'when a class name is specified' do
+ it 'uses the class as a formatter' do
+ module MyTool
+ class RubocopFormatter < Rubocop::Formatter::BaseFormatter
+ def started(all_files)
+ output.puts "started: #{all_files.join(',')}"
+ end
+
+ def file_started(file, options)
+ output.puts "file_started: #{file}"
+ end
+
+ def file_finished(file, offenses)
+ output.puts "file_finished: #{file}"
+ end
+
+ def finished(processed_files)
+ output.puts "finished: #{processed_files.join(',')}"
+ end
+ end
+ end
+
+ cli.run(['--format', 'MyTool::RubocopFormatter', 'example.rb'])
+ expect($stdout.string).to eq(["started: #{target_file}",
+ "file_started: #{target_file}",
+ "file_finished: #{target_file}",
+ "finished: #{target_file}",
+ ''].join("\n"))
+ end
+ end
+
+ context 'when unknown class name is specified' do
+ it 'aborts with error message' do
+ args = '--format UnknownFormatter example.rb'
+ expect { cli.run(args.split) }.to exit_with_code(1)
+ expect($stderr.string).to include('UnknownFormatter')
+ end
+ end
+ end
+
+ it 'can be used multiple times' do
+ cli.run(['--format', 'simple', '--format', 'emacs', 'example.rb'])
+ expect($stdout.string)
+ .to include(["== #{target_file} ==",
+ 'C: 2: 80: Line is too long. [90/79]',
+ "#{abs(target_file)}:2:80: C: Line is too long. " \
+ '[90/79]'].join("\n"))
+ end
+ end
+
+ describe '-o/--out option' do
+ let(:target_file) { 'example.rb' }
+
+ before do
+ create_file(target_file, ['# encoding: utf-8',
+ '#' * 90])
+ end
+
+ it 'redirects output to the specified file' do
+ cli.run(['--out', 'output.txt', target_file])
+ expect(File.read('output.txt')).to include('Line is too long.')
+ end
+
+ it 'is applied to the previously specified formatter' do
+ cli.run(['--format', 'simple',
+ '--format', 'emacs', '--out', 'emacs_output.txt',
+ target_file])
+
+ expect($stdout.string).to eq(["== #{target_file} ==",
+ 'C: 2: 80: Line is too long. [90/79]',
+ '',
+ '1 file inspected, 1 offense detected',
+ ''].join("\n"))
+
+ expect(File.read('emacs_output.txt'))
+ .to eq("#{abs(target_file)}:2:80: C: Line is too long. [90/79]\n")
+ end
+ end
+
+ describe '--fail-level option' do
+ let(:target_file) { 'example.rb' }
+
+ before do
+ create_file(target_file, ['# encoding: utf-8',
+ '#' * 90])
+ end
+
+ it 'should be failed when option is less than the severity level' do
+ expect(cli.run(['--fail-level', 'convention', target_file])).to eq(1)
+ end
+
+ it 'should be success when option is greater than the severity level' do
+ expect(cli.run(['--fail-level', 'warning', target_file])).to eq(0)
+ end
+ end
+
+ describe '--force-exclusion' do
+ let(:target_file) { 'example.rb' }
+
+ before do
+ create_file(target_file, ['# encoding: utf-8',
+ '#' * 90])
+
+ create_file('.rubocop.yml', ['AllCops:',
+ ' Exclude:',
+ " - #{target_file}"])
+
+ end
+
+ it 'excludes files specified in the configuration Exclude ' \
+ 'even if they are explicitly passed as arguments' do
+ expect(cli.run(['--force-exclusion', target_file])).to eq(0)
+ end
+ end
+ end
+
+ describe '#wants_to_quit?' do
+ it 'is initially false' do
+ expect(cli.wants_to_quit?).to be_false
+ end
+
+ context 'when true' do
+ it 'returns 1' do
+ create_file('example.rb', '# encoding: utf-8')
+ cli.wants_to_quit = true
+ expect(cli.run(['example.rb'])).to eq(1)
+ end
+ end
+ end
+
+ describe '#trap_interrupt' do
+ before do
+ @interrupt_handlers = []
+ allow(Signal).to receive(:trap).with('INT') do |&block|
+ @interrupt_handlers << block
+ end
+ end
+
+ def interrupt
+ @interrupt_handlers.each(&:call)
+ end
+
+ it 'adds a handler for SIGINT' do
+ expect(@interrupt_handlers).to be_empty
+ cli.trap_interrupt
+ expect(@interrupt_handlers.size).to eq(1)
+ end
+
+ context 'with SIGINT once' do
+ it 'sets #wants_to_quit? to true' do
+ cli.trap_interrupt
+ expect(cli.wants_to_quit?).to be_false
+ interrupt
+ expect(cli.wants_to_quit?).to be_true
+ end
+
+ it 'does not exit immediately' do
+ expect_any_instance_of(Object).not_to receive(:exit)
+ expect_any_instance_of(Object).not_to receive(:exit!)
+ cli.trap_interrupt
+ interrupt
+ end
+ end
+
+ context 'with SIGINT twice' do
+ it 'exits immediately' do
+ expect_any_instance_of(Object).to receive(:exit!).with(1)
+ cli.trap_interrupt
+ interrupt
+ interrupt
+ end
+ end
+ end
+
+ it 'checks a given correct file and returns 0' do
+ create_file('example.rb', ['# encoding: utf-8',
+ 'x = 0',
+ 'puts x'])
+ expect(cli.run(['--format', 'simple', 'example.rb'])).to eq(0)
+ expect($stdout.string)
+ .to eq("\n1 file inspected, no offenses detected\n")
+ end
+
+ it 'checks a given file with faults and returns 1' do
+ create_file('example.rb', ['# encoding: utf-8',
+ 'x = 0 ',
+ 'puts x'])
+ expect(cli.run(['--format', 'simple', 'example.rb'])).to eq(1)
+ expect($stdout.string)
+ .to eq ['== example.rb ==',
+ 'C: 2: 6: Trailing whitespace detected.',
+ '',
+ '1 file inspected, 1 offense detected',
+ ''].join("\n")
+ end
+
+ it 'registers an offense for a syntax error' do
+ create_file('example.rb', ['# encoding: utf-8',
+ 'class Test',
+ 'en'])
+ expect(cli.run(['--format', 'emacs', 'example.rb'])).to eq(1)
+ expect($stdout.string)
+ .to eq(["#{abs('example.rb')}:4:1: E: unexpected " \
+ 'token $end',
+ ''].join("\n"))
+ end
+
+ it 'registers an offense for Parser warnings' do
+ create_file('example.rb', ['# encoding: utf-8',
+ 'puts *test',
+ 'if a then b else c end'])
+ expect(cli.run(['--format', 'emacs', 'example.rb'])).to eq(1)
+ expect($stdout.string)
+ .to eq(["#{abs('example.rb')}:2:6: W: " \
+ 'Ambiguous splat operator. Parenthesize the method arguments ' \
+ "if it's surely a splat operator, or add a whitespace to the " \
+ 'right of the * if it should be a multiplication.',
+ "#{abs('example.rb')}:3:1: C: " \
+ 'Favor the ternary operator (?:) over if/then/else/end ' \
+ 'constructs.',
+ ''].join("\n"))
+ end
+
+ it 'can process a file with an invalid UTF-8 byte sequence' do
+ create_file('example.rb', ['# encoding: utf-8',
+ "# #{'f9'.hex.chr}#{'29'.hex.chr}"])
+ expect(cli.run(['--format', 'emacs', 'example.rb'])).to eq(1)
+ expect($stdout.string)
+ .to eq(["#{abs('example.rb')}:1:1: F: Invalid byte sequence in utf-8.",
+ ''].join("\n"))
+ end
+
+ describe 'rubocop:disable comment' do
+ it 'can disable all cops in a code section' do
+ create_file('example.rb',
+ ['# encoding: utf-8',
+ '# rubocop:disable all',
+ '#' * 90,
+ 'x(123456)',
+ 'y("123")',
+ 'def func',
+ ' # rubocop: enable LineLength, StringLiterals',
+ ' ' + '#' * 93,
+ ' x(123456)',
+ ' y("123")',
+ 'end'])
+ expect(cli.run(['--format', 'emacs', 'example.rb'])).to eq(1)
+ # all cops were disabled, then 2 were enabled again, so we
+ # should get 2 offenses reported.
+ expect($stdout.string)
+ .to eq(["#{abs('example.rb')}:8:80: C: Line is too long. [95/79]",
+ "#{abs('example.rb')}:10:5: C: Prefer single-quoted " \
+ "strings when you don't need string interpolation or " \
+ 'special symbols.',
+ ''].join("\n"))
+ end
+
+ it 'can disable selected cops in a code section' do
+ create_file('example.rb',
+ ['# encoding: utf-8',
+ '# rubocop:disable LineLength,NumericLiterals,' \
+ 'StringLiterals',
+ '#' * 90,
+ 'x(123456)',
+ 'y("123")',
+ 'def func',
+ ' # rubocop: enable LineLength, StringLiterals',
+ ' ' + '#' * 93,
+ ' x(123456)',
+ ' y("123")',
+ 'end'])
+ expect(cli.run(['--format', 'emacs', 'example.rb'])).to eq(1)
+ # 3 cops were disabled, then 2 were enabled again, so we
+ # should get 2 offenses reported.
+ expect($stdout.string)
+ .to eq(["#{abs('example.rb')}:8:80: C: Line is too long. [95/79]",
+ "#{abs('example.rb')}:10:5: C: Prefer single-quoted " \
+ "strings when you don't need string interpolation or " \
+ 'special symbols.',
+ ''].join("\n"))
+ end
+
+ it 'can disable all cops on a single line' do
+ create_file('example.rb', ['# encoding: utf-8',
+ 'y("123", 123456) # rubocop:disable all'
+ ])
+ expect(cli.run(['--format', 'emacs', 'example.rb'])).to eq(0)
+ expect($stdout.string).to be_empty
+ end
+
+ it 'can disable selected cops on a single line' do
+ create_file('example.rb',
+ ['# encoding: utf-8',
+ 'a' * 90 + ' # rubocop:disable LineLength',
+ '#' * 95,
+ 'y("123") # rubocop:disable LineLength,StringLiterals'
+ ])
+ expect(cli.run(['--format', 'emacs', 'example.rb'])).to eq(1)
+ expect($stdout.string)
+ .to eq(
+ ["#{abs('example.rb')}:3:80: C: Line is too long. [95/79]",
+ ''].join("\n"))
+ end
+ end
+
+ it 'finds a file with no .rb extension but has a shebang line' do
+ create_file('example', ['#!/usr/bin/env ruby',
+ '# encoding: utf-8',
+ 'x = 0',
+ 'puts x'
+ ])
+ expect(cli.run(%w(--format simple))).to eq(0)
+ expect($stdout.string)
+ .to eq(['', '1 file inspected, no offenses detected', ''].join("\n"))
+ end
+
+ describe 'rails cops' do
+ describe 'enabling/disabling' do
+ it 'by default does not run rails cops' do
+ create_file('app/models/example1.rb', ['# encoding: utf-8',
+ 'read_attribute(:test)'])
+ expect(cli.run(['--format', 'simple', 'app/models/example1.rb']))
+ .to eq(0)
+ end
+
+ it 'with -R given runs rails cops' do
+ create_file('app/models/example1.rb', ['# encoding: utf-8',
+ 'read_attribute(:test)'])
+ expect(cli.run(['--format', 'simple', '-R', 'app/models/example1.rb']))
+ .to eq(1)
+ expect($stdout.string).to include('Prefer self[:attr]')
+ end
+
+ it 'with configation option true in one dir runs rails cops there' do
+ source = ['# encoding: utf-8',
+ 'read_attribute(:test)']
+ create_file('dir1/app/models/example1.rb', source)
+ create_file('dir1/.rubocop.yml', ['AllCops:',
+ ' RunRailsCops: true',
+ '',
+ 'ReadAttribute:',
+ ' Include:',
+ ' - app/models/*.rb'])
+ create_file('dir2/app/models/example2.rb', source)
+ create_file('dir2/.rubocop.yml', ['AllCops:',
+ ' RunRailsCops: false',
+ '',
+ 'ReadAttribute:',
+ ' Include:',
+ ' - app/models/*.rb'])
+ expect(cli.run(%w(--format simple dir1 dir2))).to eq(1)
+ expect($stdout.string)
+ .to eq(['== dir1/app/models/example1.rb ==',
+ 'C: 2: 1: Prefer self[:attr] over read_attribute' \
+ '(:attr).',
+ '',
+ '2 files inspected, 1 offense detected',
+ ''].join("\n"))
+ end
+
+ it 'with configation option false but -R given runs rails cops' do
+ create_file('app/models/example1.rb', ['# encoding: utf-8',
+ 'read_attribute(:test)'])
+ create_file('.rubocop.yml', ['AllCops:',
+ ' RunRailsCops: false'])
+ expect(cli.run(['--format', 'simple', '-R', 'app/models/example1.rb']))
+ .to eq(1)
+ expect($stdout.string).to include('Prefer self[:attr]')
+ end
+ end
+
+ describe 'including/excluding' do
+ it 'includes some directories by default' do
+ source = ['# encoding: utf-8',
+ 'read_attribute(:test)',
+ "default_scope order: 'position'"]
+ # Several rails cops include app/models by default.
+ create_file('dir1/app/models/example1.rb', source)
+ create_file('dir1/app/models/example2.rb', source)
+ # No rails cops include app/views by default.
+ create_file('dir1/app/views/example3.rb', source)
+ # The .rubocop.yml file inherits from default.yml where the Include
+ # config parameter is set for the rails cops. The paths are interpreted
+ # as relative to dir1 because .rubocop.yml is placed there.
+ create_file('dir1/.rubocop.yml', ['AllCops:',
+ ' RunRailsCops: true',
+ '',
+ 'ReadWriteAttribute:',
+ ' Exclude:',
+ ' - example2.rb',
+ '',
+ 'DefaultScope:',
+ ' Exclude:',
+ ' - "**/example2.rb"'])
+ # No .rubocop.yml file in dir2 means that the paths from default.yml
+ # are interpreted as relative to the current directory, so they don't
+ # match.
+ create_file('dir2/app/models/example4.rb', source)
+
+ expect(cli.run(%w(--format simple dir1 dir2))).to eq(1)
+ expect($stdout.string)
+ .to eq(['== dir1/app/models/example1.rb ==',
+ 'C: 2: 1: Prefer self[:attr] over read_attribute' \
+ '(:attr).',
+ 'C: 3: 15: default_scope expects a block as its sole' \
+ ' argument.',
+ '',
+ '4 files inspected, 2 offenses detected',
+ ''].join("\n"))
+ end
+ end
+ end
+
+ describe 'cops can exclude files based on config' do
+ it 'ignores excluded files' do
+ create_file('example.rb', ['# encoding: utf-8',
+ 'x = 0'])
+ create_file('regexp.rb', ['# encoding: utf-8',
+ 'x = 0'])
+ create_file('exclude_glob.rb', ['#!/usr/bin/env ruby',
+ '# encoding: utf-8',
+ 'x = 0'])
+ create_file('dir/thing.rb', ['# encoding: utf-8',
+ 'x = 0'])
+ create_file('.rubocop.yml', ['UselessAssignment:',
+ ' Exclude:',
+ ' - example.rb',
+ ' - !ruby/regexp /regexp.rb\z/',
+ ' - "exclude_*"',
+ ' - "dir/*"'])
+ expect(cli.run(%w(--format simple))).to eq(0)
+ expect($stdout.string)
+ .to eq(['', '4 files inspected, no offenses detected',
+ ''].join("\n"))
+ end
+
+ end
+
+ describe 'configuration from file' do
+ it 'finds included files' do
+ create_file('example', ['# encoding: utf-8',
+ 'x = 0',
+ 'puts x'
+ ])
+ create_file('regexp', ['# encoding: utf-8',
+ 'x = 0',
+ 'puts x'
+ ])
+ create_file('.rubocop.yml', ['AllCops:',
+ ' Include:',
+ ' - example',
+ ' - !ruby/regexp /regexp$/'
+ ])
+ expect(cli.run(%w(--format simple))).to eq(0)
+ expect($stdout.string)
+ .to eq(['', '2 files inspected, no offenses detected',
+ ''].join("\n"))
+ end
+
+ it 'ignores excluded files' do
+ create_file('example.rb', ['# encoding: utf-8',
+ 'x = 0',
+ 'puts x'
+ ])
+ create_file('regexp.rb', ['# encoding: utf-8',
+ 'x = 0',
+ 'puts x'
+ ])
+ create_file('exclude_glob.rb', ['#!/usr/bin/env ruby',
+ '# encoding: utf-8',
+ 'x = 0',
+ 'puts x'
+ ])
+ create_file('.rubocop.yml', ['AllCops:',
+ ' Exclude:',
+ ' - example.rb',
+ ' - !ruby/regexp /regexp.rb$/',
+ ' - "exclude_*"'
+ ])
+ expect(cli.run(%w(--format simple))).to eq(0)
+ expect($stdout.string)
+ .to eq(['', '0 files inspected, no offenses detected',
+ ''].join("\n"))
+ end
+
+ # With rubinius 2.0.0.rc1 + rspec 2.13.1,
+ # File.stub(:open).and_call_original causes SystemStackError.
+ it 'does not read files in excluded list', broken: :rbx do
+ %w(rb.rb non-rb.ext without-ext).each do |filename|
+ create_file("example/ignored/#{filename}", ['# encoding: utf-8',
+ '#' * 90
+ ])
+ end
+
+ create_file('example/.rubocop.yml', ['AllCops:',
+ ' Exclude:',
+ ' - ignored/**'])
+ expect(File).not_to receive(:open).with(%r{/ignored/})
+ allow(File).to receive(:open).and_call_original
+ expect(cli.run(%w(--format simple example))).to eq(0)
+ expect($stdout.string)
+ .to eq(['', '0 files inspected, no offenses detected',
+ ''].join("\n"))
+ end
+
+ it 'can be configured with option to disable a certain error' do
+ create_file('example1.rb', 'puts 0 ')
+ create_file('rubocop.yml', ['Encoding:',
+ ' Enabled: false',
+ '',
+ 'CaseIndentation:',
+ ' Enabled: false'])
+ expect(cli.run(['--format', 'simple',
+ '-c', 'rubocop.yml', 'example1.rb'])).to eq(1)
+ expect($stdout.string)
+ .to eq(['== example1.rb ==',
+ 'C: 1: 7: Trailing whitespace detected.',
+ '',
+ '1 file inspected, 1 offense detected',
+ ''].join("\n"))
+ end
+
+ it 'can disable parser-derived offenses with warning severity' do
+ # `-' interpreted as argument prefix
+ create_file('example.rb', 'puts -1')
+ create_file('.rubocop.yml', ['Encoding:',
+ ' Enabled: false',
+ '',
+ 'AmbiguousOperator:',
+ ' Enabled: false'
+ ])
+ expect(cli.run(['--format', 'emacs', 'example.rb'])).to eq(0)
+ end
+
+ it 'cannot disable Syntax offenses with fatal/error severity' do
+ create_file('example.rb', 'class Test')
+ create_file('.rubocop.yml', ['Encoding:',
+ ' Enabled: false',
+ '',
+ 'Syntax:',
+ ' Enabled: false'
+ ])
+ expect(cli.run(['--format', 'emacs', 'example.rb'])).to eq(1)
+ expect($stdout.string).to include('unexpected token $end')
+ end
+
+ it 'can be configured to merge a parameter that is a hash' do
+ create_file('example1.rb',
+ ['# encoding: utf-8',
+ 'puts %w(a b c)',
+ 'puts %q|hi|'])
+ # We want to change the preferred delimiters for word arrays. The other
+ # settings from default.yml are unchanged.
+ create_file('rubocop.yml',
+ ['PercentLiteralDelimiters:',
+ ' PreferredDelimiters:',
+ " '%w': '[]'",
+ " '%W': '[]'"])
+ cli.run(['--format', 'simple', '-c', 'rubocop.yml', 'example1.rb'])
+ expect($stdout.string)
+ .to eq(['== example1.rb ==',
+ 'C: 2: 6: %w-literals should be delimited by [ and ]',
+ 'C: 3: 6: %q-literals should be delimited by ( and )',
+ '',
+ '1 file inspected, 2 offenses detected',
+ ''].join("\n"))
+ end
+
+ it 'can be configured to override a parameter that is a hash in a ' \
+ 'special case' do
+ create_file('example1.rb',
+ ['# encoding: utf-8',
+ 'arr.select { |e| e > 0 }.collect { |e| -e }',
+ 'a2.find_all { |e| e > 0 }'])
+ # We prefer find_all over select. This setting overrides the default
+ # select over find_all. Other preferred methods appearing in the default
+ # config (e.g., map over collect) are kept.
+ create_file('rubocop.yml',
+ ['CollectionMethods:',
+ ' PreferredMethods:',
+ ' select: find_all'])
+ cli.run(['--format', 'simple', '-c', 'rubocop.yml', 'example1.rb'])
+ expect($stdout.string)
+ .to eq(['== example1.rb ==',
+ 'C: 2: 5: Prefer find_all over select.',
+ 'C: 2: 26: Prefer map over collect.',
+ '',
+ '1 file inspected, 2 offenses detected',
+ ''].join("\n"))
+ end
+
+ it 'works when a cop that others depend on is disabled' do
+ create_file('example1.rb', ['if a',
+ ' b',
+ 'end'])
+ create_file('rubocop.yml', ['Encoding:',
+ ' Enabled: false',
+ '',
+ 'LineLength:',
+ ' Enabled: false'
+ ])
+ result = cli.run(['--format', 'simple',
+ '-c', 'rubocop.yml', 'example1.rb'])
+ expect($stdout.string)
+ .to eq(['== example1.rb ==',
+ 'C: 1: 1: Favor modifier if usage when having ' \
+ 'a single-line body. Another good alternative is the ' \
+ 'usage of control flow &&/||.',
+ '',
+ '1 file inspected, 1 offense detected',
+ ''].join("\n"))
+ expect(result).to eq(1)
+ end
+
+ it 'can be configured with project config to disable a certain error' do
+ create_file('example_src/example1.rb', 'puts 0 ')
+ create_file('example_src/.rubocop.yml', ['Encoding:',
+ ' Enabled: false',
+ '',
+ 'CaseIndentation:',
+ ' Enabled: false'
+ ])
+ expect(cli.run(['--format', 'simple',
+ 'example_src/example1.rb'])).to eq(1)
+ expect($stdout.string)
+ .to eq(['== example_src/example1.rb ==',
+ 'C: 1: 7: Trailing whitespace detected.',
+ '',
+ '1 file inspected, 1 offense detected',
+ ''].join("\n"))
+ end
+
+ it 'can use an alternative max line length from a config file' do
+ create_file('example_src/example1.rb', ['# encoding: utf-8',
+ '#' * 90
+ ])
+ create_file('example_src/.rubocop.yml', ['LineLength:',
+ ' Enabled: true',
+ ' Max: 100'
+ ])
+ expect(cli.run(['--format', 'simple',
+ 'example_src/example1.rb'])).to eq(0)
+ expect($stdout.string)
+ .to eq(['', '1 file inspected, no offenses detected', ''].join("\n"))
+ end
+
+ it 'can have different config files in different directories' do
+ %w(src lib).each do |dir|
+ create_file("example/#{dir}/example1.rb", ['# encoding: utf-8',
+ '#' * 90
+ ])
+ end
+ create_file('example/src/.rubocop.yml', ['LineLength:',
+ ' Enabled: true',
+ ' Max: 100'
+ ])
+ expect(cli.run(%w(--format simple example))).to eq(1)
+ expect($stdout.string).to eq(
+ ['== example/lib/example1.rb ==',
+ 'C: 2: 80: Line is too long. [90/79]',
+ '',
+ '2 files inspected, 1 offense detected',
+ ''].join("\n"))
+ end
+
+ it 'prefers a config file in ancestor directory to another in home' do
+ create_file('example_src/example1.rb', ['# encoding: utf-8',
+ '#' * 90
+ ])
+ create_file('example_src/.rubocop.yml', ['LineLength:',
+ ' Enabled: true',
+ ' Max: 100'
+ ])
+ create_file("#{Dir.home}/.rubocop.yml", ['LineLength:',
+ ' Enabled: true',
+ ' Max: 80'
+ ])
+ expect(cli.run(['--format', 'simple',
+ 'example_src/example1.rb'])).to eq(0)
+ expect($stdout.string)
+ .to eq(['', '1 file inspected, no offenses detected', ''].join("\n"))
+ end
+
+ it 'can exclude directories relative to .rubocop.yml' do
+ %w(src etc/test etc/spec tmp/test tmp/spec).each do |dir|
+ create_file("example/#{dir}/example1.rb", ['# encoding: utf-8',
+ '#' * 90
+ ])
+ end
+
+ create_file('example/.rubocop.yml', ['AllCops:',
+ ' Exclude:',
+ ' - src/**',
+ ' - etc/**',
+ ' - tmp/spec/**'])
+
+ expect(cli.run(%w(--format simple example))).to eq(1)
+ expect($stdout.string).to eq(
+ ['== example/tmp/test/example1.rb ==',
+ 'C: 2: 80: Line is too long. [90/79]',
+ '',
+ '1 file inspected, 1 offense detected',
+ ''].join("\n"))
+ end
+
+ it 'can exclude a typical vendor directory' do
+ create_file('vendor/bundle/ruby/1.9.1/gems/parser-2.0.0/.rubocop.yml',
+ ['AllCops:',
+ ' Exclude:',
+ ' - lib/parser/lexer.rb'])
+
+ create_file('vendor/bundle/ruby/1.9.1/gems/parser-2.0.0/lib/ex.rb',
+ ['# encoding: utf-8',
+ '#' * 90])
+
+ create_file('.rubocop.yml',
+ ['AllCops:',
+ ' Exclude:',
+ ' - vendor/**'])
+
+ cli.run(%w(--format simple))
+ expect($stdout.string)
+ .to eq(['', '0 files inspected, no offenses detected',
+ ''].join("\n"))
+ end
+
+ it 'excludes the vendor directory by default' do
+ create_file('vendor/ex.rb',
+ ['# encoding: utf-8',
+ '#' * 90])
+
+ cli.run(%w(--format simple))
+ expect($stdout.string)
+ .to eq(['', '0 files inspected, no offenses detected',
+ ''].join("\n"))
+ end
+
+ # Being immune to bad configuration files in excluded directories has
+ # become important due to a bug in rubygems
+ # (https://github.com/rubygems/rubygems/issues/680) that makes
+ # installations of, for example, rubocop lack their .rubocop.yml in the
+ # root directory.
+ it 'can exclude a vendor directory with an erroneous config file' do
+ create_file('vendor/bundle/ruby/1.9.1/gems/parser-2.0.0/.rubocop.yml',
+ ['inherit_from: non_existent.yml'])
+
+ create_file('vendor/bundle/ruby/1.9.1/gems/parser-2.0.0/lib/ex.rb',
+ ['# encoding: utf-8',
+ '#' * 90])
+
+ create_file('.rubocop.yml',
+ ['AllCops:',
+ ' Exclude:',
+ ' - vendor/**'])
+
+ cli.run(%w(--format simple))
+ expect($stderr.string).to eq('')
+ expect($stdout.string)
+ .to eq(['', '0 files inspected, no offenses detected',
+ ''].join("\n"))
+ end
+
+ # Relative exclude paths in .rubocop.yml files are relative to that file,
+ # but in configuration files with other names they will be relative to
+ # whatever file inherits from them.
+ it 'can exclude a vendor directory indirectly' do
+ create_file('vendor/bundle/ruby/1.9.1/gems/parser-2.0.0/.rubocop.yml',
+ ['AllCops:',
+ ' Exclude:',
+ ' - lib/parser/lexer.rb'])
+
+ create_file('vendor/bundle/ruby/1.9.1/gems/parser-2.0.0/lib/ex.rb',
+ ['# encoding: utf-8',
+ '#' * 90])
+
+ create_file('.rubocop.yml',
+ ['inherit_from: config/default.yml'])
+
+ create_file('config/default.yml',
+ ['AllCops:',
+ ' Exclude:',
+ ' - vendor/**'])
+
+ cli.run(%w(--format simple))
+ expect($stdout.string)
+ .to eq(['', '0 files inspected, no offenses detected',
+ ''].join("\n"))
+ end
+
+ it 'prints a warning for an unrecognized cop name in .rubocop.yml' do
+ create_file('example/example1.rb', ['# encoding: utf-8',
+ '#' * 90])
+
+ create_file('example/.rubocop.yml', ['LyneLenth:',
+ ' Enabled: true',
+ ' Max: 100'])
+
+ expect(cli.run(%w(--format simple example))).to eq(1)
+ expect($stderr.string)
+ .to eq(
+ ['Warning: unrecognized cop LyneLenth found in ' +
+ abs('example/.rubocop.yml'),
+ ''].join("\n"))
+ end
+
+ it 'prints a warning for an unrecognized configuration parameter' do
+ create_file('example/example1.rb', ['# encoding: utf-8',
+ '#' * 90])
+
+ create_file('example/.rubocop.yml', ['LineLength:',
+ ' Enabled: true',
+ ' Min: 10'])
+
+ expect(cli.run(%w(--format simple example))).to eq(1)
+ expect($stderr.string)
+ .to eq(
+ ['Warning: unrecognized parameter LineLength:Min found in ' +
+ abs('example/.rubocop.yml'),
+ ''].join("\n"))
+ end
+
+ it 'works when a configuration file passed by -c specifies Exclude ' \
+ 'with regexp' do
+ create_file('example/example1.rb', ['# encoding: utf-8',
+ '#' * 90])
+
+ create_file('rubocop.yml', ['AllCops:',
+ ' Exclude:',
+ ' - !ruby/regexp /example1\.rb$/'])
+
+ cli.run(%w(--format simple -c rubocop.yml))
+ expect($stdout.string)
+ .to eq(['', '0 files inspected, no offenses detected',
+ ''].join("\n"))
+ end
+
+ it 'works when a configuration file passed by -c specifies Exclude ' \
+ 'with strings' do
+ create_file('example/example1.rb', ['# encoding: utf-8',
+ '#' * 90])
+
+ create_file('rubocop.yml', ['AllCops:',
+ ' Exclude:',
+ ' - example/**'])
+
+ cli.run(%w(--format simple -c rubocop.yml))
+ expect($stdout.string)
+ .to eq(['', '0 files inspected, no offenses detected',
+ ''].join("\n"))
+ end
+
+ it 'works when a configuration file specifies a Severity' do
+ create_file('example/example1.rb', ['# encoding: utf-8',
+ '#' * 90])
+
+ create_file('rubocop.yml', ['LineLength:',
+ ' Severity: error'])
+
+ cli.run(%w(--format simple -c rubocop.yml))
+ expect($stdout.string)
+ .to eq(['== example/example1.rb ==',
+ 'E: 2: 80: Line is too long. [90/79]',
+ '',
+ '1 file inspected, 1 offense detected',
+ ''].join("\n"))
+ expect($stderr.string).to eq('')
+ end
+
+ it 'fails when a configuration file specifies an invalid Severity' do
+ create_file('example/example1.rb', ['# encoding: utf-8',
+ '#' * 90])
+
+ create_file('rubocop.yml', ['LineLength:',
+ ' Severity: superbad'])
+
+ cli.run(%w(--format simple -c rubocop.yml))
+ expect($stderr.string)
+ .to eq("Warning: Invalid severity 'superbad'. " \
+ 'Valid severities are refactor, convention, ' \
+ "warning, error, fatal.\n")
+ end
+ end
+end
diff --git a/spec/rubocop/comment_config_spec.rb b/spec/rubocop/comment_config_spec.rb
new file mode 100644
index 0000000..5b2ce0a
--- /dev/null
+++ b/spec/rubocop/comment_config_spec.rb
@@ -0,0 +1,103 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::CommentConfig do
+ subject(:comment_config) { Rubocop::CommentConfig.new(parse_source(source)) }
+
+ describe '#cop_enabled_at_line?' do
+ let(:source) do
+ [
+ '# encoding: utf-8',
+ '',
+ '# rubocop:disable MethodLength',
+ 'def some_method',
+ " puts 'foo'",
+ 'end',
+ '# rubocop:enable MethodLength',
+ '',
+ '# rubocop:disable all',
+ 'some_method',
+ '# rubocop:enable all',
+ '',
+ "code = 'This is evil.'",
+ 'eval(code) # rubocop:disable Eval',
+ "puts 'This is not evil.'",
+ '',
+ 'def some_method',
+ " puts 'Disabling indented single line' # rubocop:disable LineLength",
+ 'end',
+ '',
+ 'string = <<END',
+ 'This is a string not a real comment # rubocop:disable Loop',
+ 'END',
+ '',
+ 'foo # rubocop:disable MethodCallParentheses',
+ '',
+ '# rubocop:enable Void',
+ '',
+ '# rubocop:disable For',
+ 'foo'
+ ]
+ end
+
+ def disabled_lines_of_cop(cop)
+ (1..source.size).each_with_object([]) do |line_number, disabled_lines|
+ enabled = comment_config.cop_enabled_at_line?(cop, line_number)
+ disabled_lines << line_number unless enabled
+ end
+ end
+
+ it 'supports disabling multiple lines with a pair of directive' do
+ method_length_disabled_lines = disabled_lines_of_cop('MethodLength')
+ expected_part = (3..6).to_a
+ expect(method_length_disabled_lines & expected_part)
+ .to eq(expected_part)
+ end
+
+ it 'supports disabling all lines after a directive' do
+ for_disabled_lines = disabled_lines_of_cop('For')
+ expected_part = (29..source.size).to_a
+ expect(for_disabled_lines & expected_part)
+ .to eq(expected_part)
+ end
+
+ it 'just ignores unpaired enabling directives' do
+ void_disabled_lines = disabled_lines_of_cop('Void')
+ expected_part = (27..source.size).to_a
+ expect(void_disabled_lines & expected_part).to be_empty
+ end
+
+ it 'supports disabling single line with a direcive at end of line' do
+ eval_disabled_lines = disabled_lines_of_cop('Eval')
+ expect(eval_disabled_lines).to include(14)
+ expect(eval_disabled_lines).not_to include(15)
+ end
+
+ it 'handles indented single line' do
+ line_length_disabled_lines = disabled_lines_of_cop('LineLength')
+ expect(line_length_disabled_lines).to include(18)
+ expect(line_length_disabled_lines).not_to include(19)
+ end
+
+ it 'does not confuse a comment directive embedded in a string literal ' \
+ 'with a real comment' do
+ loop_disabled_lines = disabled_lines_of_cop('Loop')
+ expect(loop_disabled_lines).not_to include(22)
+ end
+
+ it 'supports disabling all cops with keyword all' do
+ expected_part = (9..10).to_a
+
+ Rubocop::Cop::Cop.all.each do |cop|
+ disabled_lines = disabled_lines_of_cop(cop)
+ expect(disabled_lines & expected_part).to eq(expected_part)
+ end
+ end
+
+ it 'does not confuse a cop name including "all" with all cops' do
+ alias_disabled_lines = disabled_lines_of_cop('Alias')
+ expect(alias_disabled_lines).not_to include(25)
+ end
+ end
+end
diff --git a/spec/rubocop/config_loader_spec.rb b/spec/rubocop/config_loader_spec.rb
new file mode 100644
index 0000000..1997f8b
--- /dev/null
+++ b/spec/rubocop/config_loader_spec.rb
@@ -0,0 +1,328 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::ConfigLoader do
+ include FileHelper
+
+ let(:default_config) { Rubocop::ConfigLoader.default_configuration }
+
+ describe '.configuration_file_for', :isolated_environment do
+ subject(:configuration_file_for) do
+ described_class.configuration_file_for(dir_path)
+ end
+
+ context 'when no config file exists in ancestor directories' do
+ let(:dir_path) { 'dir' }
+ before { create_file('dir/example.rb', '') }
+
+ context 'but a config file exists in home directory' do
+ before { create_file('~/.rubocop.yml', '') }
+
+ it 'returns the path to the file in home directory' do
+ expect(configuration_file_for).to end_with('home/.rubocop.yml')
+ end
+ end
+
+ context 'and no config file exists in home directory' do
+ it 'falls back to the provided default file' do
+ expect(configuration_file_for).to end_with('config/default.yml')
+ end
+ end
+ end
+
+ context 'when a config file exists in the parent directory' do
+ let(:dir_path) { 'dir' }
+
+ before do
+ create_file('dir/example.rb', '')
+ create_file('.rubocop.yml', '')
+ end
+
+ it 'returns the path to that configuration file' do
+ expect(configuration_file_for).to end_with('work/.rubocop.yml')
+ end
+ end
+
+ context 'when multiple config files exist in ancestor directories' do
+ let(:dir_path) { 'dir' }
+
+ before do
+ create_file('dir/example.rb', '')
+ create_file('dir/.rubocop.yml', '')
+ create_file('.rubocop.yml', '')
+ end
+
+ it 'prefers closer config file' do
+ expect(configuration_file_for).to end_with('dir/.rubocop.yml')
+ end
+ end
+ end
+
+ describe '.configuration_from_file', :isolated_environment do
+ subject(:configuration_from_file) do
+ described_class.configuration_from_file(file_path)
+ end
+
+ context 'with any config file' do
+ let(:file_path) { '.rubocop.yml' }
+
+ before do
+ create_file(file_path, ['Encoding:',
+ ' Enabled: false'])
+ end
+ it 'returns a configuration inheriting from default.yml' do
+ config = default_config['Encoding'].dup
+ config['Enabled'] = false
+ expect(configuration_from_file)
+ .to eql(default_config.merge('Encoding' => config))
+ end
+ end
+
+ context 'when multiple config files exist in ancestor directories' do
+ let(:file_path) { 'dir/.rubocop.yml' }
+
+ before do
+ create_file('.rubocop.yml',
+ ['AllCops:',
+ ' Exclude:',
+ ' - vendor/**'
+ ])
+
+ create_file(file_path,
+ ['AllCops:',
+ ' Exclude: []'
+ ])
+ end
+
+ it 'gets AllCops/Exclude from the highest directory level' do
+ excludes = configuration_from_file['AllCops']['Exclude']
+ expect(excludes).to eq([File.expand_path('vendor/**')])
+ end
+ end
+
+ context 'when a file inherits from a parent file' do
+ let(:file_path) { 'dir/.rubocop.yml' }
+
+ before do
+ create_file('.rubocop.yml',
+ ['AllCops:',
+ ' Exclude:',
+ ' - vendor/**',
+ ' - !ruby/regexp /[A-Z]/'
+ ])
+
+ create_file(file_path, ['inherit_from: ../.rubocop.yml'])
+ end
+
+ it 'gets an absolute AllCops/Exclude' do
+ excludes = configuration_from_file['AllCops']['Exclude']
+ expect(excludes).to eq([File.expand_path('vendor/**'), /[A-Z]/])
+ end
+ end
+
+ context 'when a file inherits from an empty parent file' do
+ let(:file_path) { 'dir/.rubocop.yml' }
+
+ before do
+ create_file('.rubocop.yml', [''])
+
+ create_file(file_path, ['inherit_from: ../.rubocop.yml'])
+ end
+
+ it 'does not fail to load' do
+ expect { configuration_from_file }.not_to raise_error
+ end
+ end
+
+ context 'when a file inherits from a sibling file' do
+ let(:file_path) { 'dir/.rubocop.yml' }
+
+ before do
+ create_file('src/.rubocop.yml',
+ ['AllCops:',
+ ' Exclude:',
+ ' - vendor/**'
+ ])
+
+ create_file(file_path, ['inherit_from: ../src/.rubocop.yml'])
+ end
+
+ it 'gets an absolute AllCops/Exclude' do
+ excludes = configuration_from_file['AllCops']['Exclude']
+ expect(excludes).to eq([File.expand_path('src/vendor/**')])
+ end
+ end
+
+ context 'when a file inherits from a parent and grandparent file' do
+ let(:file_path) { 'dir/subdir/.rubocop.yml' }
+
+ before do
+ create_file('dir/subdir/example.rb', '')
+
+ create_file('.rubocop.yml',
+ ['LineLength:',
+ ' Enabled: false',
+ ' Max: 77'])
+
+ create_file('dir/.rubocop.yml',
+ ['inherit_from: ../.rubocop.yml',
+ '',
+ 'MethodLength:',
+ ' Enabled: true',
+ ' CountComments: false',
+ ' Max: 10'
+ ])
+
+ create_file(file_path,
+ ['inherit_from: ../.rubocop.yml',
+ '',
+ 'LineLength:',
+ ' Enabled: true',
+ '',
+ 'MethodLength:',
+ ' Max: 5'
+ ])
+ end
+
+ it 'returns the ancestor configuration plus local overrides' do
+ config = default_config
+ .merge('LineLength' => {
+ 'Description' =>
+ default_config['LineLength']['Description'],
+ 'Enabled' => true,
+ 'Max' => 77
+ },
+ 'MethodLength' => {
+ 'Description' =>
+ default_config['MethodLength']['Description'],
+ 'Enabled' => true,
+ 'CountComments' => false,
+ 'Max' => 5
+ })
+ expect(configuration_from_file).to eq(config)
+ end
+ end
+
+ context 'when a file inherits from two configurations' do
+ let(:file_path) { '.rubocop.yml' }
+
+ before do
+ create_file('example.rb', '')
+
+ create_file('normal.yml',
+ ['MethodLength:',
+ ' Enabled: false',
+ ' CountComments: true',
+ ' Max: 79'])
+
+ create_file('special.yml',
+ ['MethodLength:',
+ ' Enabled: false',
+ ' Max: 200'])
+
+ create_file(file_path,
+ ['inherit_from:',
+ ' - normal.yml',
+ ' - special.yml',
+ '',
+ 'MethodLength:',
+ ' Enabled: true'
+ ])
+ end
+
+ it 'returns values from the last one when possible' do
+ expected = { 'Enabled' => true, # overridden in .rubocop.yml
+ 'CountComments' => true, # only defined in normal.yml
+ 'Max' => 200 } # special.yml takes precedence
+ expect(configuration_from_file['MethodLength'].to_set)
+ .to be_superset(expected.to_set)
+ end
+ end
+ end
+
+ describe '.load_file', :isolated_environment do
+ subject(:load_file) do
+ described_class.load_file(configuration_path)
+ end
+
+ let(:configuration_path) { '.rubocop.yml' }
+
+ it 'returns a configuration loaded from the passed path' do
+ create_file(configuration_path, [
+ 'Encoding:',
+ ' Enabled: true'
+ ])
+ configuration = load_file
+ expect(configuration['Encoding']).to eq(
+ 'Enabled' => true
+ )
+ end
+ end
+
+ describe '.merge' do
+ subject(:merge) { described_class.merge(base, derived) }
+
+ let(:base) do
+ {
+ 'AllCops' => {
+ 'Include' => ['**/*.gemspec', '**/Rakefile'],
+ 'Exclude' => []
+ }
+ }
+ end
+ let(:derived) do
+ { 'AllCops' => { 'Exclude' => ['example.rb', 'exclude_*'] } }
+ end
+
+ it 'returns a recursive merge of its two arguments' do
+ expect(merge).to eq('AllCops' => {
+ 'Include' => ['**/*.gemspec', '**/Rakefile'],
+ 'Exclude' => ['example.rb', 'exclude_*']
+ })
+ end
+ end
+
+ describe 'configuration for SymbolArray', :isolated_environment do
+ let(:config) do
+ config_path = described_class.configuration_file_for('.')
+ described_class.configuration_from_file(config_path)
+ end
+
+ context 'when no config file exists for the target file' do
+ it 'is disabled' do
+ expect(config.cop_enabled?('SymbolArray')).to be_false
+ end
+ end
+
+ context 'when a config file which does not mention SymbolArray exists' do
+ it 'is disabled' do
+ create_file('.rubocop.yml', [
+ 'LineLength:',
+ ' Max: 79'
+ ])
+ expect(config.cop_enabled?('SymbolArray')).to be_false
+ end
+ end
+
+ context 'when a config file which explicitly enables SymbolArray exists' do
+ it 'is enabled' do
+ create_file('.rubocop.yml', [
+ 'SymbolArray:',
+ ' Enabled: true'
+ ])
+ expect(config.cop_enabled?('SymbolArray')).to be_true
+ end
+ end
+ end
+
+ describe 'configuration for AssignmentInCondition' do
+ describe 'AllowSafeAssignment' do
+ it 'is enabled by default' do
+ default_config = described_class.default_configuration
+ symbol_name_config = default_config.for_cop('AssignmentInCondition')
+ expect(symbol_name_config['AllowSafeAssignment']).to be_true
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/config_spec.rb b/spec/rubocop/config_spec.rb
new file mode 100644
index 0000000..cef8044
--- /dev/null
+++ b/spec/rubocop/config_spec.rb
@@ -0,0 +1,179 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Config do
+ include FileHelper
+
+ subject(:configuration) { described_class.new(hash, loaded_path) }
+ let(:hash) { {} }
+ let(:loaded_path) { 'example/.rubocop.yml' }
+
+ describe '#validate', :isolated_environment do
+ # TODO: Because Config.load_file now outputs the validation warning,
+ # it is inserting text into the rspec test output here.
+ # The next 2 lines should be removed eventually.
+ before(:each) { $stderr = StringIO.new }
+ after(:each) { $stderr = STDERR }
+
+ subject(:configuration) do
+ Rubocop::ConfigLoader.load_file(configuration_path)
+ end
+
+ let(:configuration_path) { '.rubocop.yml' }
+
+ context 'when the configuration includes any unrecognized cop name' do
+ before do
+ create_file(configuration_path, [
+ 'LyneLenth:',
+ ' Enabled: true',
+ ' Max: 100'
+ ])
+ end
+
+ it 'raises validation error' do
+ expect { configuration.validate }
+ .to raise_error(described_class::ValidationError,
+ /^unrecognized cop LyneLenth/)
+ end
+ end
+
+ context 'when the configuration includes any unrecognized parameter' do
+ before do
+ create_file(configuration_path, [
+ 'LineLength:',
+ ' Enabled: true',
+ ' Min: 10'
+ ])
+ end
+
+ it 'raises validation error' do
+ expect { configuration.validate }
+ .to raise_error(described_class::ValidationError,
+ /^unrecognized parameter LineLength:Min/)
+ end
+ end
+
+ context 'when the configuration includes any common parameter' do
+ # Common parameters are parameters that are not in the default
+ # configuration, but are nonetheless allowed for any cop.
+ before do
+ create_file(configuration_path, [
+ 'LineLength:',
+ ' Exclude:',
+ ' - lib/file.rb',
+ ' Include:',
+ ' - lib/file.xyz',
+ ' Severity: warning'
+ ])
+ end
+
+ it 'does not raise validation error' do
+ expect { configuration.validate }.to_not raise_error
+ end
+ end
+ end
+
+ describe '#file_to_include?' do
+ let(:hash) do
+ {
+ 'AllCops' => {
+ 'Include' => ['Gemfile', 'config/unicorn.rb.example']
+ }
+ }
+ end
+
+ let(:loaded_path) { '/home/foo/project/.rubocop.yml' }
+
+ context 'when the passed path matches any of patterns to include' do
+ it 'returns true' do
+ file_path = '/home/foo/project/Gemfile'
+ expect(configuration.file_to_include?(file_path)).to be_true
+ end
+ end
+
+ context 'when the passed path does not match any of patterns to include' do
+ it 'returns false' do
+ file_path = '/home/foo/project/Gemfile.lock'
+ expect(configuration.file_to_include?(file_path)).to be_false
+ end
+ end
+ end
+
+ describe '#file_to_exclude?' do
+ let(:hash) do
+ {
+ 'AllCops' => {
+ 'Exclude' => ['/home/foo/project/log/*']
+ }
+ }
+ end
+
+ let(:loaded_path) { '/home/foo/project/.rubocop.yml' }
+
+ context 'when the passed path matches any of patterns to exclude' do
+ it 'returns true' do
+ file_path = '/home/foo/project/log/foo.rb'
+ expect(configuration.file_to_exclude?(file_path)).to be_true
+ end
+ end
+
+ context 'when the passed path does not match any of patterns to exclude' do
+ it 'returns false' do
+ file_path = '/home/foo/project/log_file.rb'
+ expect(configuration.file_to_exclude?(file_path)).to be_false
+ end
+ end
+ end
+
+ describe '#patterns_to_include' do
+ subject(:patterns_to_include) do
+ configuration = described_class.new(hash, loaded_path)
+ configuration.patterns_to_include
+ end
+
+ let(:hash) { {} }
+ let(:loaded_path) { 'example/.rubocop.yml' }
+
+ context 'when config file has AllCops => Include key' do
+ let(:hash) do
+ {
+ 'AllCops' => {
+ 'Include' => ['Gemfile', 'config/unicorn.rb.example']
+ }
+ }
+ end
+
+ it 'returns the Include value' do
+ expect(patterns_to_include).to eq([
+ 'Gemfile',
+ 'config/unicorn.rb.example'
+ ])
+ end
+ end
+ end
+
+ describe '#patterns_to_exclude' do
+ subject(:patterns_to_exclude) do
+ configuration = described_class.new(hash, loaded_path)
+ configuration.patterns_to_exclude
+ end
+
+ let(:hash) { {} }
+ let(:loaded_path) { 'example/.rubocop.yml' }
+
+ context 'when config file has AllCops => Exclude key' do
+ let(:hash) do
+ {
+ 'AllCops' => {
+ 'Exclude' => ['log/*']
+ }
+ }
+ end
+
+ it 'returns the Exclude value' do
+ expect(patterns_to_exclude).to eq(['log/*'])
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/config_store_spec.rb b/spec/rubocop/config_store_spec.rb
new file mode 100644
index 0000000..61ee4fc
--- /dev/null
+++ b/spec/rubocop/config_store_spec.rb
@@ -0,0 +1,53 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::ConfigStore do
+ subject(:config_store) { described_class.new }
+
+ before do
+ allow(Rubocop::ConfigLoader).to receive(:configuration_file_for) do |arg|
+ # File tree:
+ # file1
+ # dir/.rubocop.yml
+ # dir/file2
+ # dir/subdir/file3
+ (arg =~ /dir/ ? 'dir' : '.') + '/.rubocop.yml'
+ end
+ allow(Rubocop::ConfigLoader)
+ .to receive(:configuration_from_file) { |arg| arg }
+ allow(Rubocop::ConfigLoader)
+ .to receive(:load_file) { |arg| "#{arg} loaded" }
+ allow(Rubocop::ConfigLoader)
+ .to receive(:merge_with_default) { |config, file| "merged #{config}" }
+ end
+
+ describe '.for' do
+ it 'always uses config specified in command line' do
+ config_store.options_config = :options_config
+ expect(config_store.for('file1')).to eq('merged options_config loaded')
+ end
+
+ context 'when no config specified in command line' do
+ it 'gets config path and config from cache if available' do
+ expect(Rubocop::ConfigLoader).to receive(:configuration_file_for).once
+ .with('dir')
+ expect(Rubocop::ConfigLoader).to receive(:configuration_file_for).once
+ .with('dir/subdir')
+ # The stub returns the same config path for dir and dir/subdir.
+ expect(Rubocop::ConfigLoader).to receive(:configuration_from_file).once
+ .with('dir/.rubocop.yml')
+
+ config_store.for('dir/file2')
+ config_store.for('dir/file2')
+ config_store.for('dir/subdir/file3')
+ end
+
+ it 'searches for config path if not available in cache' do
+ expect(Rubocop::ConfigLoader).to receive(:configuration_file_for).once
+ expect(Rubocop::ConfigLoader).to receive(:configuration_from_file).once
+ config_store.for('file1')
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/cop/commissioner_spec.rb b/spec/rubocop/cop/commissioner_spec.rb
new file mode 100644
index 0000000..3a105e2
--- /dev/null
+++ b/spec/rubocop/cop/commissioner_spec.rb
@@ -0,0 +1,83 @@
+# encoding: utf-8
+# rubocop:disable LineLength
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Commissioner do
+ describe '#investigate' do
+ let(:cop) { double(Rubocop::Cop, offenses: []).as_null_object }
+
+ it 'returns all offenses found by the cops' do
+ allow(cop).to receive(:offenses).and_return([1])
+
+ commissioner = described_class.new([cop])
+ source = []
+ processed_source = parse_source(source)
+
+ expect(commissioner.investigate(processed_source)).to eq [1]
+ end
+
+ context 'when a cop has no interest in the file' do
+ it 'returns all offenses except the ones of the cop' do
+ cops = []
+ cops << double('cop A', offenses: %w(foo), relevant_file?: true)
+ cops << double('cop B', offenses: %w(bar), relevant_file?: false)
+ cops << double('cop C', offenses: %w(baz), relevant_file?: true)
+ cops.each(&:as_null_object)
+
+ commissioner = described_class.new(cops)
+ source = []
+ processed_source = parse_source(source)
+
+ expect(commissioner.investigate(processed_source)).to eq %w(foo baz)
+ end
+ end
+
+ it 'traverses the AST and invoke cops specific callbacks' do
+ expect(cop).to receive(:on_def)
+
+ commissioner = described_class.new([cop])
+ source = ['def method', '1', 'end']
+ processed_source = parse_source(source)
+
+ commissioner.investigate(processed_source)
+ end
+
+ it 'passes the input params to all cops that implement their own #investigate method' do
+ source = []
+ processed_source = parse_source(source)
+ expect(cop).to receive(:investigate).with(processed_source)
+
+ commissioner = described_class.new([cop])
+
+ commissioner.investigate(processed_source)
+ end
+
+ it 'stores all errors raised by the cops' do
+ allow(cop).to receive(:on_def) { fail RuntimeError }
+
+ commissioner = described_class.new([cop])
+ source = ['def method', '1', 'end']
+ processed_source = parse_source(source)
+
+ commissioner.investigate(processed_source)
+
+ expect(commissioner.errors[cop].size).to eq(1)
+ expect(commissioner.errors[cop][0]).to be_instance_of(RuntimeError)
+ end
+
+ context 'when passed :raise_error option' do
+ it 're-raises the exception received while processing' do
+ allow(cop).to receive(:on_def) { fail RuntimeError }
+
+ commissioner = described_class.new([cop], raise_error: true)
+ source = ['def method', '1', 'end']
+ processed_source = parse_source(source)
+
+ expect do
+ commissioner.investigate(processed_source)
+ end.to raise_error(RuntimeError)
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/cop/cop_spec.rb b/spec/rubocop/cop/cop_spec.rb
new file mode 100644
index 0000000..c4b21cd
--- /dev/null
+++ b/spec/rubocop/cop/cop_spec.rb
@@ -0,0 +1,114 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Cop do
+ subject(:cop) { described_class.new }
+ let(:location) do
+ source_buffer = Parser::Source::Buffer.new('test', 1)
+ source_buffer.source = "a\n"
+ Parser::Source::Range.new(source_buffer, 0, 1)
+ end
+
+ it 'initially has 0 offenses' do
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'keeps track of offenses' do
+ cop.add_offense(nil, location, 'message')
+
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'will report registered offenses' do
+ cop.add_offense(nil, location, 'message')
+
+ expect(cop.offenses).not_to be_empty
+ end
+
+ it 'will set default severity' do
+ cop.add_offense(nil, location, 'message')
+
+ expect(cop.offenses.first.severity).to eq(:convention)
+ end
+
+ it 'will set custom severity if present' do
+ cop.config[cop.name] = { 'Severity' => 'warning' }
+ cop.add_offense(nil, location, 'message')
+
+ expect(cop.offenses.first.severity).to eq(:warning)
+ end
+
+ it 'will warn if custom severity is invalid' do
+ cop.config[cop.name] = { 'Severity' => 'superbad' }
+ expect(cop).to receive(:warn)
+ cop.add_offense(nil, location, 'message')
+ end
+
+ it 'registers offense with its name' do
+ cop = Rubocop::Cop::Style::For.new
+ cop.add_offense(nil, location, 'message')
+ expect(cop.offenses.first.cop_name).to eq('For')
+ end
+
+ context 'with no submodule' do
+ subject(:cop) { described_class }
+ it('has right name') { expect(cop.cop_name).to eq('Cop') }
+ it('has right type') { expect(cop.cop_type).to eq(:cop) }
+ end
+
+ context 'with style cops' do
+ subject(:cop) { Rubocop::Cop::Style::For }
+ it('has right name') { expect(cop.cop_name).to eq('For') }
+ it('has right type') { expect(cop.cop_type).to eq(:style) }
+ end
+
+ context 'with lint cops' do
+ subject(:cop) { Rubocop::Cop::Lint::Loop }
+ it('has right name') { expect(cop.cop_name).to eq('Loop') }
+ it('has right type') { expect(cop.cop_type).to eq(:lint) }
+ end
+
+ context 'with rails cops' do
+ subject(:cop) { Rubocop::Cop::Rails::Validation }
+ it('has right name') { expect(cop.cop_name).to eq('Validation') }
+ it('has right type') { expect(cop.cop_type).to eq(:rails) }
+ end
+
+ describe 'CopStore' do
+ context '#types' do
+ subject { described_class.all.types }
+ it('has types') { expect(subject.length).not_to eq(0) }
+ it { should include :lint }
+ it do
+ pending 'Rails cops are usually removed after CLI start, ' \
+ 'so CLI spec impacts this one'
+ should include :rails
+ end
+ it { should include :style }
+ it 'contains every value only once' do
+ expect(subject.length).to eq(subject.uniq.length)
+ end
+ end
+ context '#with_type' do
+ let(:types) { described_class.all.types }
+ it 'has at least one cop per type' do
+ types.each do |c|
+ expect(described_class.all.with_type(c).length).to be > 0
+ end
+ end
+
+ it 'has each cop in exactly one type' do
+ sum = 0
+ types.each do |c|
+ sum += described_class.all.with_type(c).length
+ end
+ expect(sum).to be described_class.all.length
+ end
+
+ it 'returns 0 for an invalid type' do
+ expect(described_class.all.with_type('x').length).to be 0
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/cop/corrector_spec.rb b/spec/rubocop/cop/corrector_spec.rb
new file mode 100644
index 0000000..aca5bc7
--- /dev/null
+++ b/spec/rubocop/cop/corrector_spec.rb
@@ -0,0 +1,59 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Corrector do
+ describe '#rewrite' do
+ it 'allows removal of a range' do
+ source = 'true and false'
+ processed_source = parse_source(source)
+
+ correction = lambda do |corrector|
+ node = processed_source.ast
+ corrector.remove(node.loc.operator)
+ end
+
+ corrector = described_class.new(processed_source.buffer, [correction])
+ expect(corrector.rewrite).to eq 'true false'
+ end
+
+ it 'allows insertion before a source range' do
+ source = 'true and false'
+ processed_source = parse_source(source)
+
+ correction = lambda do |corrector|
+ node = processed_source.ast
+ corrector.insert_before(node.loc.operator, ';nil ')
+ end
+
+ corrector = described_class.new(processed_source.buffer, [correction])
+ expect(corrector.rewrite).to eq 'true ;nil and false'
+ end
+
+ it 'allows insertion after a source range' do
+ source = 'true and false'
+ processed_source = parse_source(source)
+
+ correction = lambda do |corrector|
+ node = processed_source.ast
+ corrector.insert_after(node.loc.operator, ' nil;')
+ end
+
+ corrector = described_class.new(processed_source.buffer, [correction])
+ expect(corrector.rewrite).to eq 'true and nil; false'
+ end
+
+ it 'allows replacement of a range' do
+ source = 'true and false'
+ processed_source = parse_source(source)
+
+ correction = lambda do |corrector|
+ node = processed_source.ast
+ corrector.replace(node.loc.operator, 'or')
+ end
+
+ corrector = described_class.new(processed_source.buffer, [correction])
+ expect(corrector.rewrite).to eq 'true or false'
+ end
+ end
+end
diff --git a/spec/rubocop/cop/lint/ambiguous_operator_spec.rb b/spec/rubocop/cop/lint/ambiguous_operator_spec.rb
new file mode 100644
index 0000000..54ad05a
--- /dev/null
+++ b/spec/rubocop/cop/lint/ambiguous_operator_spec.rb
@@ -0,0 +1,113 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::AmbiguousOperator do
+ subject(:cop) { described_class.new }
+
+ before do
+ inspect_source(cop, source)
+ end
+
+ context 'with a splat operator in the first argument' do
+ context 'without parentheses' do
+ context 'without whitespaces on the right of the operator' do
+ let(:source) do
+ [
+ 'array = [1, 2, 3]',
+ 'puts *array'
+ ]
+ end
+
+ it 'registers an offense' do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message).to eq(
+ 'Ambiguous splat operator. ' \
+ "Parenthesize the method arguments if it's surely a splat " \
+ 'operator, ' \
+ 'or add a whitespace to the right of the * if it should be a ' \
+ 'multiplication.'
+ )
+ expect(cop.highlights).to eq(['*'])
+ end
+ end
+
+ context 'with a whitespace on the right of the operator' do
+ let(:source) do
+ [
+ 'array = [1, 2, 3]',
+ 'puts * array'
+ ]
+ end
+
+ it 'accepts' do
+ expect(cop.offenses).to be_empty
+ end
+ end
+ end
+
+ context 'with parentheses' do
+ let(:source) do
+ [
+ 'array = [1, 2, 3]',
+ 'puts(*array)'
+ ]
+ end
+
+ it 'accepts' do
+ expect(cop.offenses).to be_empty
+ end
+ end
+ end
+
+ context 'with a block ampersand in the first argument' do
+ context 'without parentheses' do
+ context 'without whitespaces on the right of the operator' do
+ let(:source) do
+ [
+ 'process = proc { do_something }',
+ '2.times &process'
+ ]
+ end
+
+ it 'registers an offense' do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message).to eq(
+ 'Ambiguous block operator. ' \
+ "Parenthesize the method arguments if it's surely a block " \
+ 'operator, ' \
+ 'or add a whitespace to the right of the & if it should be a ' \
+ 'binary AND.'
+ )
+ expect(cop.highlights).to eq(['&'])
+ end
+ end
+
+ context 'with a whitespace on the right of the operator' do
+ let(:source) do
+ [
+ 'process = proc { do_something }',
+ '2.times & process'
+ ]
+ end
+
+ it 'accepts' do
+ expect(cop.offenses).to be_empty
+ end
+ end
+ end
+
+ context 'with parentheses' do
+ let(:source) do
+ [
+ 'process = proc { do_something }',
+ '2.times(&process)'
+ ]
+ end
+
+ it 'accepts' do
+ expect(cop.offenses).to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/cop/lint/ambiguous_regexp_literal_spec.rb b/spec/rubocop/cop/lint/ambiguous_regexp_literal_spec.rb
new file mode 100644
index 0000000..bad9858
--- /dev/null
+++ b/spec/rubocop/cop/lint/ambiguous_regexp_literal_spec.rb
@@ -0,0 +1,35 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::AmbiguousRegexpLiteral do
+ subject(:cop) { described_class.new }
+
+ before do
+ inspect_source(cop, source)
+ end
+
+ context 'with a regexp literal in the first argument' do
+ context 'without parentheses' do
+ let(:source) { 'p /pattern/' }
+
+ it 'registers an offense' do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message).to eq(
+ 'Ambiguous regexp literal. Parenthesize the method arguments ' \
+ "if it's surely a regexp literal, or add a whitespace to the " \
+ 'right of the / if it should be a division.'
+ )
+ expect(cop.highlights).to eq(['/'])
+ end
+ end
+
+ context 'with parentheses' do
+ let(:source) { 'p(/pattern/)' }
+
+ it 'accepts' do
+ expect(cop.offenses).to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/cop/lint/assignment_in_condition_spec.rb b/spec/rubocop/cop/lint/assignment_in_condition_spec.rb
new file mode 100644
index 0000000..bcf46de
--- /dev/null
+++ b/spec/rubocop/cop/lint/assignment_in_condition_spec.rb
@@ -0,0 +1,107 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::AssignmentInCondition, :config do
+ subject(:cop) { described_class.new(config) }
+ let(:cop_config) { { 'AllowSafeAssignment' => true } }
+
+ it 'registers an offense for lvar assignment in condition' do
+ inspect_source(cop,
+ ['if test = 10',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for lvar assignment in while condition' do
+ inspect_source(cop,
+ ['while test = 10',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for lvar assignment in until condition' do
+ inspect_source(cop,
+ ['until test = 10',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for ivar assignment in condition' do
+ inspect_source(cop,
+ ['if @test = 10',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for clvar assignment in condition' do
+ inspect_source(cop,
+ ['if @@test = 10',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for gvar assignment in condition' do
+ inspect_source(cop,
+ ['if $test = 10',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for constant assignment in condition' do
+ inspect_source(cop,
+ ['if TEST = 10',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts == in condition' do
+ inspect_source(cop,
+ ['if test == 10',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts = in a block that is called in a condition' do
+ inspect_source(cop,
+ ['return 1 if any_errors? { o = inspect(file) }'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts ||= in condition' do
+ inspect_source(cop,
+ ['raise StandardError unless foo ||= bar'])
+ expect(cop.offenses).to be_empty
+ end
+
+ context 'safe assignment is allowed' do
+ it 'accepts = in condition surrounded with braces' do
+ inspect_source(cop,
+ ['if (test = 10)',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ end
+
+ context 'safe assignment is not allowed' do
+ let(:cop_config) { { 'AllowSafeAssignment' => false } }
+
+ it 'does not accepts = in condition surrounded with braces' do
+ inspect_source(cop,
+ ['if (test = 10)',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+end
diff --git a/spec/rubocop/cop/lint/block_alignment_spec.rb b/spec/rubocop/cop/lint/block_alignment_spec.rb
new file mode 100644
index 0000000..48f7015
--- /dev/null
+++ b/spec/rubocop/cop/lint/block_alignment_spec.rb
@@ -0,0 +1,411 @@
+# encoding: utf-8
+# rubocop:disable LineLength
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::BlockAlignment do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for mismatched block end' do
+ inspect_source(cop,
+ ['test do |ala|',
+ ' end'
+ ])
+ expect(cop.messages)
+ .to eq(['end at 2, 2 is not aligned with test do |ala| at 1, 0'])
+ end
+
+ context 'when the block is a logical operand' do
+ it 'accepts a correctly aligned block end' do
+ inspect_source(cop,
+ ['(value.is_a? Array) && value.all? do |subvalue|',
+ ' type_check_value(subvalue, array_type)',
+ 'end',
+ 'a || b do',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ it 'accepts end aligned with a variable' do
+ inspect_source(cop,
+ ['variable = test do |ala|',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ context 'when there is an assignment chain' do
+ it 'registers an offense for an end aligned with the 2nd variable' do
+ inspect_source(cop,
+ ['a = b = c = test do |ala|',
+ ' end'
+ ])
+ expect(cop.messages)
+ .to eq(['end at 2, 4 is not aligned with a = b = c = test do |ala| at 1, 0'])
+ end
+
+ it 'accepts end aligned with the first variable' do
+ inspect_source(cop,
+ ['a = b = c = test do |ala|',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'and the block is an operand' do
+ it 'accepts end aligned with a variable' do
+ inspect_source(cop,
+ ['b = 1 + preceding_line.reduce(0) do |a, e|',
+ ' a + e.length + newline_length',
+ 'end + 1'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ it 'registers an offense for mismatched block end with a variable' do
+ inspect_source(cop,
+ ['variable = test do |ala|',
+ ' end'
+ ])
+ expect(cop.messages)
+ .to eq(['end at 2, 2 is not aligned with variable = test do |ala| at 1, 0'])
+ end
+
+ context 'when the block is defined on the next line' do
+ it 'accepts end aligned with the block expression' do
+ inspect_source(cop,
+ ['variable =',
+ ' a_long_method_that_dont_fit_on_the_line do |v|',
+ ' v.foo',
+ ' end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offenses for mismatched end alignment' do
+ inspect_source(cop,
+ ['variable =',
+ ' a_long_method_that_dont_fit_on_the_line do |v|',
+ ' v.foo',
+ 'end'
+ ])
+ expect(cop.messages)
+ .to eq(['end at 4, 0 is not aligned with a_long_method_that_dont_fit_on_the_line ' \
+ 'do |v| at 2, 2'])
+ end
+ end
+
+ context 'when the method part is a call chain that spans several lines' do
+ # Example from issue 346 of bbatsov/rubocop on github:
+ it 'accepts pretty alignment style' do
+ src = ['def foo(bar)',
+ ' bar.get_stuffs',
+ ' .reject do |stuff| ',
+ ' stuff.with_a_very_long_expression_that_doesnt_fit_the_line',
+ ' end.select do |stuff|',
+ ' stuff.another_very_long_expression_that_doesnt_fit_the_line',
+ ' end',
+ ' .select do |stuff|',
+ ' stuff.another_very_long_expression_that_doesnt_fit_the_line',
+ ' end',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers offenses for misaligned ends' do
+ src = ['def foo(bar)',
+ ' bar.get_stuffs',
+ ' .reject do |stuff|',
+ ' stuff.with_a_very_long_expression_that_doesnt_fit_the_line',
+ ' end.select do |stuff|',
+ ' stuff.another_very_long_expression_that_doesnt_fit_the_line',
+ ' end',
+ ' .select do |stuff|',
+ ' stuff.another_very_long_expression_that_doesnt_fit_the_line',
+ ' end',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.messages)
+ .to eq(['end at 5, 8 is not aligned with bar.get_stuffs at 2, 2 or' \
+ ' .reject do |stuff| at 3, 6',
+ 'end at 7, 4 is not aligned with bar.get_stuffs at 2, 2 or' \
+ ' end.select do |stuff| at 5, 8',
+ 'end at 10, 8 is not aligned with bar.get_stuffs at 2, 2 or' \
+ ' .select do |stuff| at 8, 6'])
+ end
+
+ # Example from issue 393 of bbatsov/rubocop on github:
+ it 'accepts end indented as the start of the block' do
+ src = ['my_object.chaining_this_very_long_method(with_a_parameter)',
+ ' .and_one_with_a_block do',
+ ' do_something',
+ 'end',
+ '', # Other variant:
+ 'my_object.chaining_this_very_long_method(',
+ ' with_a_parameter).and_one_with_a_block do',
+ ' do_something',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ # Example from issue 447 of bbatsov/rubocop on github:
+ it 'accepts two kinds of end alignment' do
+ src = [
+ # Aligned with start of line where do is:
+ 'params = default_options.merge(options)',
+ ' .delete_if { |k, v| v.nil? }',
+ ' .each_with_object({}) do |(k, v), new_hash|',
+ ' new_hash[k.to_s] = v.to_s',
+ ' end',
+ # Aligned with start of the whole expression:
+ 'params = default_options.merge(options)',
+ ' .delete_if { |k, v| v.nil? }',
+ ' .each_with_object({}) do |(k, v), new_hash|',
+ ' new_hash[k.to_s] = v.to_s',
+ 'end'
+ ]
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'when variables of a mass assignment spans several lines' do
+ it 'accepts end aligned with the variables' do
+ src = ['e,',
+ 'f = [5, 6].map do |i|',
+ ' i - 5',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for end aligned with the block' do
+ src = ['e,',
+ 'f = [5, 6].map do |i|',
+ ' i - 5',
+ ' end']
+ inspect_source(cop, src)
+ expect(cop.messages)
+ .to eq(['end at 4, 4 is not aligned with e, at 1, 0 or f = [5, 6].map do |i| at 2, 0'])
+ end
+ end
+
+ it 'accepts end aligned with an instance variable' do
+ inspect_source(cop,
+ ['@variable = test do |ala|',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for mismatched block end with an instance variable' do
+ inspect_source(cop,
+ ['@variable = test do |ala|',
+ ' end'
+ ])
+ expect(cop.messages)
+ .to eq(['end at 2, 2 is not aligned with @variable = test do |ala| at 1, 0'])
+ end
+
+ it 'accepts end aligned with a class variable' do
+ inspect_source(cop,
+ ['@@variable = test do |ala|',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for mismatched block end with a class variable' do
+ inspect_source(cop,
+ ['@@variable = test do |ala|',
+ ' end'
+ ])
+ expect(cop.messages)
+ .to eq(['end at 2, 2 is not aligned with @@variable = test do |ala| at 1, 0'])
+ end
+
+ it 'accepts end aligned with a global variable' do
+ inspect_source(cop,
+ ['$variable = test do |ala|',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for mismatched block end with a global variable' do
+ inspect_source(cop,
+ ['$variable = test do |ala|',
+ ' end'
+ ])
+ expect(cop.messages)
+ .to eq(['end at 2, 2 is not aligned with $variable = test do |ala| at 1, 0'])
+ end
+
+ it 'accepts end aligned with a constant' do
+ inspect_source(cop,
+ ['CONSTANT = test do |ala|',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for mismatched block end with a constant' do
+ inspect_source(cop,
+ ['Module::CONSTANT = test do |ala|',
+ ' end'
+ ])
+ expect(cop.messages)
+ .to eq(['end at 2, 2 is not aligned with Module::CONSTANT = test do |ala| at 1, 0'])
+ end
+
+ it 'accepts end aligned with a method call' do
+ inspect_source(cop,
+ ['parser.childs << lambda do |token|',
+ ' token << 1',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for mismatched block end with a method call' do
+ inspect_source(cop,
+ ['parser.childs << lambda do |token|',
+ ' token << 1',
+ ' end'
+ ])
+ expect(cop.messages)
+ .to eq(['end at 3, 2 is not aligned with parser.childs << lambda do |token| at 1, 0'])
+ end
+
+ it 'accepts end aligned with a method call with arguments' do
+ inspect_source(cop,
+ ['@h[:f] = f.each_pair.map do |f, v|',
+ ' v = 1',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for mismatched end with a method call with arguments' do
+ inspect_source(cop,
+ ['@h[:f] = f.each_pair.map do |f, v|',
+ ' v = 1',
+ ' end'
+ ])
+ expect(cop.messages)
+ .to eq(['end at 3, 2 is not aligned with @h[:f] = f.each_pair.map do |f, v| at 1, 0'])
+ end
+
+ it 'does not raise an error for nested block in a method call' do
+ inspect_source(cop,
+ ['expect(arr.all? { |o| o.valid? })'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts end aligned with the block when the block is a method argument' do
+ inspect_source(cop,
+ ['expect(arr.all? do |o|',
+ ' o.valid?',
+ ' end)'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for mismatched end not aligned with the block that is an argument' do
+ inspect_source(cop,
+ ['expect(arr.all? do |o|',
+ ' o.valid?',
+ ' end)'
+ ])
+ expect(cop.messages)
+ .to eq(['end at 3, 2 is not aligned with arr.all? do |o| at 1, 7 or ' \
+ 'expect(arr.all? do |o| at 1, 0'])
+ end
+
+ it 'accepts end aligned with an op-asgn (+=, -=)' do
+ inspect_source(cop,
+ ['rb += files.select do |file|',
+ ' file << something',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for mismatched block end with an op-asgn (+=, -=)' do
+ inspect_source(cop,
+ ['rb += files.select do |file|',
+ ' file << something',
+ ' end'
+ ])
+ expect(cop.messages)
+ .to eq(['end at 3, 2 is not aligned with rb at 1, 0'])
+ end
+
+ it 'accepts end aligned with an and-asgn (&&=)' do
+ inspect_source(cop,
+ ['variable &&= test do |ala|',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for mismatched block end with an and-asgn (&&=)' do
+ inspect_source(cop,
+ ['variable &&= test do |ala|',
+ ' end'
+ ])
+ expect(cop.messages)
+ .to eq(['end at 2, 2 is not aligned with variable &&= test do |ala| at 1, 0'])
+ end
+
+ it 'accepts end aligned with an or-asgn (||=)' do
+ inspect_source(cop,
+ ['variable ||= test do |ala|',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for mismatched block end with an or-asgn (||=)' do
+ inspect_source(cop,
+ ['variable ||= test do |ala|',
+ ' end'
+ ])
+ expect(cop.messages)
+ .to eq(['end at 2, 2 is not aligned with variable ||= test do |ala| at 1, 0'])
+ end
+
+ it 'accepts end aligned with a mass assignment' do
+ inspect_source(cop,
+ ['var1, var2 = lambda do |test|',
+ ' [1, 2]',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts end aligned with a call chain left hand side' do
+ inspect_source(cop,
+ ['parser.diagnostics.consumer = lambda do |diagnostic|',
+ ' diagnostics << diagnostic',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for mismatched block end with a mass assignment' do
+ inspect_source(cop,
+ ['var1, var2 = lambda do |test|',
+ ' [1, 2]',
+ ' end'
+ ])
+ expect(cop.messages)
+ .to eq(['end at 3, 2 is not aligned with var1, var2 at 1, 0'])
+ end
+end
diff --git a/spec/rubocop/cop/lint/condition_position_spec.rb b/spec/rubocop/cop/lint/condition_position_spec.rb
new file mode 100644
index 0000000..fa26861
--- /dev/null
+++ b/spec/rubocop/cop/lint/condition_position_spec.rb
@@ -0,0 +1,49 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::ConditionPosition do
+ subject(:cop) { described_class.new }
+
+ %w(if unless while until).each do |keyword|
+ it 'registers an offense for condition on the next line' do
+ inspect_source(cop,
+ ["#{keyword}",
+ 'x == 10',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts condition on the same line' do
+ inspect_source(cop,
+ ["#{keyword} x == 10",
+ ' bala',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ it 'registers an offense for elsif condition on the next line' do
+ inspect_source(cop,
+ ['if something',
+ ' test',
+ 'elsif',
+ ' something',
+ ' test',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'handles ternary ops' do
+ inspect_source(cop, ['x ? a : b'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'handles modifier forms' do
+ inspect_source(cop, ['x if something'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/lint/debugger_spec.rb b/spec/rubocop/cop/lint/debugger_spec.rb
new file mode 100644
index 0000000..22d058e
--- /dev/null
+++ b/spec/rubocop/cop/lint/debugger_spec.rb
@@ -0,0 +1,39 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::Debugger do
+ subject(:cop) { described_class.new }
+
+ it 'reports an offense for a debugger call' do
+ src = ['debugger']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'reports an offense for pry bindings' do
+ src = ['binding.pry',
+ 'binding.remote_pry']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(2)
+ end
+
+ it 'does not report an offense for non-pry binding' do
+ src = ['binding.pirate']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not report an offense for debugger in comments' do
+ src = ['# debugger']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not report an offense for a debugger or pry method' do
+ src = ['code.debugger',
+ 'door.pry']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/lint/deprecated_class_methods_spec.rb b/spec/rubocop/cop/lint/deprecated_class_methods_spec.rb
new file mode 100644
index 0000000..ffac3c4
--- /dev/null
+++ b/spec/rubocop/cop/lint/deprecated_class_methods_spec.rb
@@ -0,0 +1,38 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::DeprecatedClassMethods do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for File.exists?' do
+ inspect_source(cop, 'File.exists?(o)')
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['`File.exists?` is deprecated in favor of `File.exist?`.'])
+ end
+
+ it 'registers an offense for ::File.exists?' do
+ inspect_source(cop, '::File.exists?(o)')
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['`File.exists?` is deprecated in favor of `File.exist?`.'])
+ end
+
+ it 'registers an offense for Dir.exists?' do
+ inspect_source(cop, 'Dir.exists?(o)')
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['`Dir.exists?` is deprecated in favor of `Dir.exist?`.'])
+ end
+
+ it 'auto-corrects File.exists? with File.exist?' do
+ new_source = autocorrect_source(cop, 'File.exists?(something)')
+ expect(new_source).to eq('File.exist?(something)')
+ end
+
+ it 'auto-corrects Dir.exists? with Dir.exist?' do
+ new_source = autocorrect_source(cop, 'Dir.exists?(something)')
+ expect(new_source).to eq('Dir.exist?(something)')
+ end
+end
diff --git a/spec/rubocop/cop/lint/else_layout_spec.rb b/spec/rubocop/cop/lint/else_layout_spec.rb
new file mode 100644
index 0000000..8ea4aa2
--- /dev/null
+++ b/spec/rubocop/cop/lint/else_layout_spec.rb
@@ -0,0 +1,65 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::ElseLayout do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for expr on same line as else' do
+ inspect_source(cop,
+ ['if something',
+ ' test',
+ 'else ala',
+ ' something',
+ ' test',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts proper else' do
+ inspect_source(cop,
+ ['if something',
+ ' test',
+ 'else',
+ ' something',
+ ' test',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts single-expr else regardless of layout' do
+ inspect_source(cop,
+ ['if something',
+ ' test',
+ 'else bala',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'can handle elsifs' do
+ inspect_source(cop,
+ ['if something',
+ ' test',
+ 'elsif something',
+ ' bala',
+ 'else ala',
+ ' something',
+ ' test',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'handles ternary ops' do
+ inspect_source(cop, 'x ? a : b')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'handles modifier forms' do
+ inspect_source(cop, 'x if something')
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/lint/empty_ensure_spec.rb b/spec/rubocop/cop/lint/empty_ensure_spec.rb
new file mode 100644
index 0000000..08863e9
--- /dev/null
+++ b/spec/rubocop/cop/lint/empty_ensure_spec.rb
@@ -0,0 +1,27 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::EmptyEnsure do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for empty ensure' do
+ inspect_source(cop,
+ ['begin',
+ ' something',
+ 'ensure',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'does not register an offense for non-empty ensure' do
+ inspect_source(cop,
+ ['begin',
+ ' something',
+ ' return',
+ 'ensure',
+ ' file.close',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/lint/empty_interpolation_spec.rb b/spec/rubocop/cop/lint/empty_interpolation_spec.rb
new file mode 100644
index 0000000..630ae74
--- /dev/null
+++ b/spec/rubocop/cop/lint/empty_interpolation_spec.rb
@@ -0,0 +1,18 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::EmptyInterpolation do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for #{} in interpolation' do
+ inspect_source(cop, ['"this is the #{}"'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['#{}'])
+ end
+
+ it 'accepts non-empty interpolation' do
+ inspect_source(cop, '"this is #{top} silly"')
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/lint/end_alignment_spec.rb b/spec/rubocop/cop/lint/end_alignment_spec.rb
new file mode 100644
index 0000000..ff200fb
--- /dev/null
+++ b/spec/rubocop/cop/lint/end_alignment_spec.rb
@@ -0,0 +1,135 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::EndAlignment, :config do
+ subject(:cop) { described_class.new(config) }
+ let(:cop_config) { { 'AlignWith' => 'keyword' } }
+ let(:opposite) do
+ cop_config['AlignWith'] == 'keyword' ? 'variable' : 'keyword'
+ end
+
+ shared_examples 'misaligned' do |prefix, alignment_base, arg, end_kw, name|
+ name ||= alignment_base
+ it "registers an offense for mismatched #{name} ... end" do
+ inspect_source(cop, ["#{prefix}#{alignment_base} #{arg}",
+ end_kw])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages.first)
+ .to match(/end at 2, \d+ is not aligned with #{alignment_base} at 1,/)
+ expect(cop.highlights.first).to eq('end')
+ expect(cop.config_to_allow_offenses).to eq('AlignWith' => opposite)
+ end
+ end
+
+ shared_examples 'aligned' do |alignment_base, arg, end_kw, name|
+ name ||= alignment_base
+ it "accepts matching #{name} ... end" do
+ inspect_source(cop, ["#{alignment_base} #{arg}",
+ end_kw])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ include_examples 'misaligned', '', 'class', 'Test', ' end'
+ include_examples 'misaligned', '', 'module', 'Test', ' end'
+ include_examples 'misaligned', '', 'def', 'test', ' end'
+ include_examples 'misaligned', '', 'def', 'Test.test', ' end', 'defs'
+ include_examples 'misaligned', '', 'if', 'test', ' end'
+ include_examples 'misaligned', '', 'unless', 'test', ' end'
+ include_examples 'misaligned', '', 'while', 'test', ' end'
+ include_examples 'misaligned', '', 'until', 'test', ' end'
+
+ include_examples 'aligned', 'class', 'Test', 'end'
+ include_examples 'aligned', 'module', 'Test', 'end'
+ include_examples 'aligned', 'def', 'test', 'end'
+ include_examples 'aligned', 'def', 'Test.test', 'end', 'defs'
+ include_examples 'aligned', 'if', 'test', 'end'
+ include_examples 'aligned', 'unless', 'test', 'end'
+ include_examples 'aligned', 'while', 'test', 'end'
+ include_examples 'aligned', 'until', 'test', 'end'
+
+ context 'in ruby 2.1 or later' do
+ include_examples 'aligned', 'public def', 'test', 'end'
+ include_examples 'aligned', 'protected def', 'test', 'end'
+ include_examples 'aligned', 'private def', 'test', 'end'
+ include_examples 'aligned', 'module_function def', 'test', 'end'
+
+ include_examples('misaligned', '',
+ 'public def', 'test',
+ ' end')
+ include_examples('misaligned', '',
+ 'protected def', 'test',
+ ' end')
+ include_examples('misaligned', '',
+ 'private def', 'test',
+ ' end')
+ include_examples('misaligned', '',
+ 'module_function def', 'test',
+ ' end')
+ end
+
+ it 'can handle ternary if' do
+ inspect_source(cop, 'a = cond ? x : y')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'can handle modifier if' do
+ inspect_source(cop, 'a = x if cond')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for correct + opposite' do
+ inspect_source(cop, ['x = if a',
+ ' a1',
+ ' end',
+ 'y = if b',
+ ' b1',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages.first)
+ .to eq('end at 6, 0 is not aligned with if at 4, 4')
+ expect(cop.highlights.first).to eq('end')
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ context 'regarding assignment' do
+ context 'when AlignWith is keyword' do
+ include_examples 'misaligned', 'var = ', 'if', 'test', 'end'
+ include_examples 'misaligned', 'var = ', 'unless', 'test', 'end'
+ include_examples 'misaligned', 'var = ', 'while', 'test', 'end'
+ include_examples 'misaligned', 'var = ', 'until', 'test', 'end'
+
+ include_examples 'aligned', 'var = if', 'test', ' end'
+ include_examples 'aligned', 'var = unless', 'test', ' end'
+ include_examples 'aligned', 'var = while', 'test', ' end'
+ include_examples 'aligned', 'var = until', 'test', ' end'
+ end
+
+ context 'when AlignWith is variable' do
+ let(:cop_config) { { 'AlignWith' => 'variable' } }
+
+ include_examples 'aligned', 'var = if', 'test', 'end'
+ include_examples 'aligned', 'var = unless', 'test', 'end'
+ include_examples 'aligned', 'var = while', 'test', 'end'
+ include_examples 'aligned', 'var = until', 'test', 'end'
+ include_examples 'aligned', 'var = until', 'test', 'end.abc.join("")'
+ include_examples 'aligned', 'var = until', 'test', 'end.abc.tap {}'
+
+ include_examples 'misaligned', '', 'var = if', 'test', ' end'
+ include_examples 'misaligned', '', 'var = unless', 'test', ' end'
+ include_examples 'misaligned', '', 'var = while', 'test', ' end'
+ include_examples 'misaligned', '', 'var = until', 'test', ' end'
+ include_examples 'misaligned', '', 'var = until', 'test', ' end.j'
+
+ include_examples 'aligned', '@var = if', 'test', 'end'
+ include_examples 'aligned', '@@var = if', 'test', 'end'
+ include_examples 'aligned', '$var = if', 'test', 'end'
+ include_examples 'aligned', 'CNST = if', 'test', 'end'
+ include_examples 'aligned', 'a, b = if', 'test', 'end'
+ include_examples 'aligned', 'var ||= if', 'test', 'end'
+ include_examples 'aligned', 'var &&= if', 'test', 'end'
+ include_examples 'aligned', 'var += if', 'test', 'end'
+ end
+ end
+end
diff --git a/spec/rubocop/cop/lint/end_in_method_spec.rb b/spec/rubocop/cop/lint/end_in_method_spec.rb
new file mode 100644
index 0000000..aad28e0
--- /dev/null
+++ b/spec/rubocop/cop/lint/end_in_method_spec.rb
@@ -0,0 +1,29 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::EndInMethod do
+ subject(:cop) { described_class.new }
+
+ it 'reports an offense for def with an END inside' do
+ src = ['def test',
+ ' END { something }',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'reports an offense for defs with an END inside' do
+ src = ['def self.test',
+ ' END { something }',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts END outside of def(s)' do
+ src = ['END { something }']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/lint/ensure_return_spec.rb b/spec/rubocop/cop/lint/ensure_return_spec.rb
new file mode 100644
index 0000000..471c547
--- /dev/null
+++ b/spec/rubocop/cop/lint/ensure_return_spec.rb
@@ -0,0 +1,39 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::EnsureReturn do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for return in ensure' do
+ inspect_source(cop,
+ ['begin',
+ ' something',
+ 'ensure',
+ ' file.close',
+ ' return',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'does not register an offense for return outside ensure' do
+ inspect_source(cop,
+ ['begin',
+ ' something',
+ ' return',
+ 'ensure',
+ ' file.close',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not check when ensure block has no body' do
+ expect do
+ inspect_source(cop,
+ ['begin',
+ ' something',
+ 'ensure',
+ 'end'])
+ end.to_not raise_exception
+ end
+end
diff --git a/spec/rubocop/cop/lint/eval_spec.rb b/spec/rubocop/cop/lint/eval_spec.rb
new file mode 100644
index 0000000..aba9114
--- /dev/null
+++ b/spec/rubocop/cop/lint/eval_spec.rb
@@ -0,0 +1,33 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::Eval do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for eval as function' do
+ inspect_source(cop,
+ ['eval(something)'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights) .to eq(['eval'])
+ end
+
+ it 'registers an offense for eval as command' do
+ inspect_source(cop,
+ ['eval something'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights) .to eq(['eval'])
+ end
+
+ it 'does not register an offense for eval as variable' do
+ inspect_source(cop,
+ ['eval = something'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for eval as method' do
+ inspect_source(cop,
+ ['something.eval'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/lint/handle_exceptions_spec.rb b/spec/rubocop/cop/lint/handle_exceptions_spec.rb
new file mode 100644
index 0000000..adf3f51
--- /dev/null
+++ b/spec/rubocop/cop/lint/handle_exceptions_spec.rb
@@ -0,0 +1,30 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::HandleExceptions do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for empty rescue block' do
+ inspect_source(cop,
+ ['begin',
+ ' something',
+ 'rescue',
+ ' #do nothing',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Do not suppress exceptions.'])
+ end
+
+ it 'does not register an offense for rescue with body' do
+ inspect_source(cop,
+ ['begin',
+ ' something',
+ ' return',
+ 'rescue',
+ ' file.close',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/lint/invalid_character_literal_spec.rb b/spec/rubocop/cop/lint/invalid_character_literal_spec.rb
new file mode 100644
index 0000000..2624fb1
--- /dev/null
+++ b/spec/rubocop/cop/lint/invalid_character_literal_spec.rb
@@ -0,0 +1,33 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::InvalidCharacterLiteral do
+ subject(:cop) { described_class.new }
+
+ # Is there a way to emit this warning without syntax error?
+ #
+ # $ ruby -w
+ # p(? )
+ # -:1: warning: invalid character syntax; use ?\s
+ # -:1: syntax error, unexpected '?', expecting ')'
+ # p(? )
+ # ^
+ #
+ # https://github.com/ruby/ruby/blob/v2_1_0/parse.y#L7276
+ # https://github.com/whitequark/parser/blob/v2.1.2/lib/parser/lexer.rl#L1660
+ context 'with a non-escaped whitespace character literal ' do
+ let(:source) { 'p(? )' }
+
+ it 'registers an offense' do
+ pending 'Is there a way to emit this warning without syntax errors?'
+
+ inspect_source(cop, source)
+
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Invalid character literal; use ?\s')
+ expect(cop.highlights).to eq([' '])
+ end
+ end
+end
diff --git a/spec/rubocop/cop/lint/literal_in_condition_spec.rb b/spec/rubocop/cop/lint/literal_in_condition_spec.rb
new file mode 100644
index 0000000..03d8784
--- /dev/null
+++ b/spec/rubocop/cop/lint/literal_in_condition_spec.rb
@@ -0,0 +1,154 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::LiteralInCondition do
+ subject(:cop) { described_class.new }
+
+ %w(1 2.0 [1] {}).each do |lit|
+ it "registers an offense for literal #{lit} in if" do
+ inspect_source(cop,
+ ["if #{lit}",
+ ' top',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it "registers an offense for literal #{lit} in while" do
+ inspect_source(cop,
+ ["while #{lit}",
+ ' top',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it "registers an offense for literal #{lit} in until" do
+ inspect_source(cop,
+ ["until #{lit}",
+ ' top',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it "registers an offense for literal #{lit} in case" do
+ inspect_source(cop,
+ ["case #{lit}",
+ 'when x then top',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it "registers an offense for literal #{lit} in a when " \
+ 'of a case without anything after case keyword' do
+ inspect_source(cop,
+ ['case',
+ "when #{lit} then top",
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it "accepts literal #{lit} in a when of a case with " \
+ 'something after case keyword' do
+ inspect_source(cop,
+ ['case x',
+ "when #{lit} then top",
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it "registers an offense for literal #{lit} in &&" do
+ inspect_source(cop,
+ ["if x && #{lit}",
+ ' top',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it "registers an offense for literal #{lit} in complex cond" do
+ inspect_source(cop,
+ ["if x && !(a && #{lit}) && y && z",
+ ' top',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it "registers an offense for literal #{lit} in !" do
+ inspect_source(cop,
+ ["if !#{lit}",
+ ' top',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it "registers an offense for literal #{lit} in complex !" do
+ inspect_source(cop,
+ ["if !(x && (y && #{lit}))",
+ ' top',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it "accepts literal #{lit} if it's not an and/or operand" do
+ inspect_source(cop,
+ ["if test(#{lit})",
+ ' top',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it "accepts literal #{lit} in non-toplevel and/or" do
+ inspect_source(cop,
+ ["if (a || #{lit}).something",
+ ' top',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ it 'accepts array literal in case, if it has non-literal elements' do
+ inspect_source(cop,
+ ['case [1, 2, x]',
+ 'when [1, 2, 5] then top',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts array literal in case, if it has non-literal elements' do
+ inspect_source(cop,
+ ['case [1, 2, [x, 1]]',
+ 'when [1, 2, 5] then top',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for case with a primitive array condition' do
+ inspect_source(cop,
+ ['case [1, 2, [3, 4]]',
+ 'when [1, 2, 5] then top',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts dstr literal in case' do
+ inspect_source(cop,
+ ['case "#{x}"',
+ 'when [1, 2, 5] then top',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/lint/literal_in_interpolation_spec.rb b/spec/rubocop/cop/lint/literal_in_interpolation_spec.rb
new file mode 100644
index 0000000..0da5b16
--- /dev/null
+++ b/spec/rubocop/cop/lint/literal_in_interpolation_spec.rb
@@ -0,0 +1,31 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::LiteralInInterpolation do
+ subject(:cop) { described_class.new }
+
+ %w(1 2.0 [1] {}).each do |lit|
+ it "registers an offense for #{lit} in interpolation" do
+ inspect_source(cop,
+ ["\"this is the \#{#{lit}}\""])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it "registers an offense only for final #{lit} in interpolation" do
+ inspect_source(cop,
+ ["\"this is the \#{#{lit};#{lit}}\""])
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+
+ it 'accepts empty interpolation' do
+ inspect_source(cop, '"this is #{} silly"')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts strings like __FILE__' do
+ inspect_source(cop, '"this is #{__FILE__} silly"')
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/lint/loop_spec.rb b/spec/rubocop/cop/lint/loop_spec.rb
new file mode 100644
index 0000000..35bb7c6
--- /dev/null
+++ b/spec/rubocop/cop/lint/loop_spec.rb
@@ -0,0 +1,27 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::Loop do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for begin/end/while' do
+ inspect_source(cop, ['begin something; top; end while test'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for begin/end/until' do
+ inspect_source(cop, ['begin something; top; end until test'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts normal while' do
+ inspect_source(cop, ['while test; one; two; end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts normal until' do
+ inspect_source(cop, ['until test; one; two; end'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/lint/parentheses_as_grouped_expression_spec.rb b/spec/rubocop/cop/lint/parentheses_as_grouped_expression_spec.rb
new file mode 100644
index 0000000..da07e4b
--- /dev/null
+++ b/spec/rubocop/cop/lint/parentheses_as_grouped_expression_spec.rb
@@ -0,0 +1,57 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::ParenthesesAsGroupedExpression do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for method call with space before the ' \
+ 'parenthesis' do
+ inspect_source(cop, ['a.func (x)'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for predicate method call with space ' \
+ 'before the parenthesis' do
+ inspect_source(cop, ['is? (x)'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for math expression' do
+ inspect_source(cop, ['puts (2 + 3) * 4'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts a method call without arguments' do
+ inspect_source(cop, ['func'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a method call with arguments but no parentheses' do
+ inspect_source(cop, ['puts x'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a chain of method calls' do
+ inspect_source(cop, ['a.b',
+ 'a.b 1',
+ 'a.b(1)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts method with parens as arg to method without' do
+ inspect_source(cop, ['a b(c)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an operator call with argument in parentheses' do
+ inspect_source(cop, ['a % (b + c)',
+ 'a.b = (c == d)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a space inside opening paren followed by left paren' do
+ inspect_source(cop, ['a( (b) )'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/lint/require_parentheses_spec.rb b/spec/rubocop/cop/lint/require_parentheses_spec.rb
new file mode 100644
index 0000000..b156a1f
--- /dev/null
+++ b/spec/rubocop/cop/lint/require_parentheses_spec.rb
@@ -0,0 +1,82 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::RequireParentheses do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for missing parentheses around expression with ' \
+ '&& operator' do
+ inspect_source(cop, ["if day.is? 'monday' && month == :jan",
+ ' foo',
+ 'end'])
+ expect(cop.highlights).to eq(["day.is? 'monday' && month == :jan"])
+ expect(cop.messages)
+ .to eq(['Use parentheses in the method call to avoid confusion about ' \
+ 'precedence.'])
+ end
+
+ it 'registers an offense for missing parentheses around expression with ' \
+ '|| operator' do
+ inspect_source(cop, "day_is? 'tuesday' || true")
+ expect(cop.highlights).to eq(["day_is? 'tuesday' || true"])
+ end
+
+ it 'registers an offense for missing parentheses around expression in ' \
+ 'ternary' do
+ inspect_source(cop, "wd.include? 'tuesday' && true == true ? a : b")
+ expect(cop.highlights).to eq(["wd.include? 'tuesday' && true == true"])
+ end
+
+ it 'accepts missing parentheses around expression with + operator' do
+ inspect_source(cop, ["if day_is? 'tuesday' + rest",
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts method calls without parentheses followed by keyword and/or' do
+ inspect_source(cop, ["if day.is? 'tuesday' and month == :jan",
+ 'end',
+ "if day.is? 'tuesday' or month == :jan",
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts method calls that are all operations' do
+ inspect_source(cop, ['if current_level == max + 1',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts condition that is not a call' do
+ inspect_source(cop, ['if @debug',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts parentheses around expression with boolean operator' do
+ inspect_source(cop, ["if day.is?('tuesday' && true == true)",
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts method call with parentheses in ternary' do
+ inspect_source(cop, "wd.include?('tuesday' && true == true) ? a : b")
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts missing parentheses when method is not a predicate' do
+ inspect_source(cop, "weekdays.foo 'tuesday' && true == true")
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts calls to methods that are setters' do
+ inspect_source(cop, 's.version = @version || ">= 1.8.5"')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts calls to methods that are operators' do
+ inspect_source(cop, 'a[b || c]')
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/lint/rescue_exception_spec.rb b/spec/rubocop/cop/lint/rescue_exception_spec.rb
new file mode 100644
index 0000000..2f62410
--- /dev/null
+++ b/spec/rubocop/cop/lint/rescue_exception_spec.rb
@@ -0,0 +1,123 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::RescueException do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for rescue from Exception' do
+ inspect_source(cop,
+ ['begin',
+ ' something',
+ 'rescue Exception',
+ ' #do nothing',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for rescue with ::Exception' do
+ inspect_source(cop,
+ ['begin',
+ ' something',
+ 'rescue ::Exception',
+ ' #do nothing',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for rescue with StandardError, Exception' do
+ inspect_source(cop,
+ ['begin',
+ ' something',
+ 'rescue StandardError, Exception',
+ ' #do nothing',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for rescue with Exception => e' do
+ inspect_source(cop,
+ ['begin',
+ ' something',
+ 'rescue Exception => e',
+ ' #do nothing',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'does not register an offense for rescue with no class' do
+ inspect_source(cop,
+ ['begin',
+ ' something',
+ ' return',
+ 'rescue',
+ ' file.close',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for rescue with no class and => e' do
+ inspect_source(cop,
+ ['begin',
+ ' something',
+ ' return',
+ 'rescue => e',
+ ' file.close',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for rescue with other class' do
+ inspect_source(cop,
+ ['begin',
+ ' something',
+ ' return',
+ 'rescue ArgumentError => e',
+ ' file.close',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for rescue with other classes' do
+ inspect_source(cop,
+ ['begin',
+ ' something',
+ ' return',
+ 'rescue EOFError, ArgumentError => e',
+ ' file.close',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for rescue with a module prefix' do
+ inspect_source(cop,
+ ['begin',
+ ' something',
+ ' return',
+ 'rescue Test::Exception => e',
+ ' file.close',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not crash when the splat operator is used in a rescue' do
+ inspect_source(cop,
+ ['ERRORS = [Exception]',
+ 'begin',
+ ' a = 3 / 0',
+ 'rescue *ERRORS',
+ ' puts e',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not crash when the namespace of a rescued class is in a local ' \
+ 'variable' do
+ inspect_source(cop,
+ ['adapter = current_adapter',
+ 'begin',
+ 'rescue adapter::ParseError',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/lint/shadowing_outer_local_variable_spec.rb b/spec/rubocop/cop/lint/shadowing_outer_local_variable_spec.rb
new file mode 100644
index 0000000..3948343
--- /dev/null
+++ b/spec/rubocop/cop/lint/shadowing_outer_local_variable_spec.rb
@@ -0,0 +1,237 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::ShadowingOuterLocalVariable do
+ subject(:cop) { described_class.new }
+
+ context 'when a block argument has same name ' \
+ 'as an outer scope variable' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' foo = 1',
+ ' puts foo',
+ ' 1.times do |foo|',
+ ' end',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to include('Shadowing outer local variable - `foo`')
+ expect(cop.offenses.first.line).to eq(4)
+ end
+
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a splat block argument has same name ' \
+ 'as an outer scope variable' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' foo = 1',
+ ' puts foo',
+ ' 1.times do |*foo|',
+ ' end',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to include('Shadowing outer local variable - `foo`')
+ expect(cop.offenses.first.line).to eq(4)
+ end
+
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a block block argument has same name ' \
+ 'as an outer scope variable' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' foo = 1',
+ ' puts foo',
+ ' proc_taking_block = proc do |&foo|',
+ ' end',
+ ' proc_taking_block.call do',
+ ' end',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to include('Shadowing outer local variable - `foo`')
+ expect(cop.offenses.first.line).to eq(4)
+ end
+
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a block local variable has same name ' \
+ 'as an outer scope variable' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' foo = 1',
+ ' puts foo',
+ ' 1.times do |i; foo|',
+ ' puts foo',
+ ' end',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to include('Shadowing outer local variable - `foo`')
+ expect(cop.offenses.first.line).to eq(4)
+ end
+
+ include_examples 'mimics MRI 2.1', 'shadowing'
+ end
+
+ context 'when a block argument has different name ' \
+ 'with outer scope variables' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' foo = 1',
+ ' puts foo',
+ ' 1.times do |bar|',
+ ' end',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when an outer scope variable is reassigned in a block' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' foo = 1',
+ ' puts foo',
+ ' 1.times do',
+ ' foo = 2',
+ ' end',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when an outer scope variable is referenced in a block' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' foo = 1',
+ ' puts foo',
+ ' 1.times do',
+ ' puts foo',
+ ' end',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when multiple block arguments have same name "_"' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' 1.times do |_, foo, _|',
+ ' end',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when multiple block arguments have ' \
+ 'a same name starts with "_"' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' 1.times do |_foo, bar, _foo|',
+ ' end',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts' unless RUBY_VERSION < '2.0'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a block argument has same name "_" ' \
+ 'as outer scope variable "_"' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' _ = 1',
+ ' puts _',
+ ' 1.times do |_|',
+ ' end',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a block argument has a same name starts with "_" ' \
+ 'as an outer scope variable' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' _foo = 1',
+ ' puts _foo',
+ ' 1.times do |_foo|',
+ ' end',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a method argument has same name ' \
+ 'as an outer scope variable' do
+ let(:source) do
+ [
+ 'class SomeClass',
+ ' foo = 1',
+ ' puts foo',
+ ' def some_method(foo)',
+ ' end',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+end
diff --git a/spec/rubocop/cop/lint/space_before_first_arg_spec.rb b/spec/rubocop/cop/lint/space_before_first_arg_spec.rb
new file mode 100644
index 0000000..64ed798
--- /dev/null
+++ b/spec/rubocop/cop/lint/space_before_first_arg_spec.rb
@@ -0,0 +1,58 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::SpaceBeforeFirstArg do
+ subject(:cop) { described_class.new }
+
+ context 'for method calls without parentheses' do
+ it 'registers an offense for method call with no space before the ' \
+ 'first arg' do
+ inspect_source(cop, ['something?x',
+ 'a.something!y, z'])
+ expect(cop.messages)
+ .to eq(['Put space between the method name and the first ' \
+ 'argument.'] * 2)
+ expect(cop.highlights).to eq(%w(x y))
+ end
+
+ it 'accepts a method call with space before the first arg' do
+ inspect_source(cop, ['something? x',
+ 'a.something! y, z'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts square brackets operator' do
+ inspect_source(cop, ['something[:x]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a method call with space before a multiline arg' do
+ inspect_source(cop, "something [\n 'foo',\n 'bar'\n]")
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an assignment without space before first arg' do
+ inspect_source(cop, ['a.something=c', 'a.something,b=c,d'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'for method calls with parentheses' do
+ it 'accepts a method call without space' do
+ inspect_source(cop, ['something?(x)',
+ 'a.something(y, z)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a method call with space after the left parenthesis' do
+ inspect_source(cop, ['something?( x )'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts setter call' do
+ inspect_source(cop, ['self.class.controller_path=(path)'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+end
diff --git a/spec/rubocop/cop/lint/string_conversion_in_interpolation_spec.rb b/spec/rubocop/cop/lint/string_conversion_in_interpolation_spec.rb
new file mode 100644
index 0000000..c13b9c5
--- /dev/null
+++ b/spec/rubocop/cop/lint/string_conversion_in_interpolation_spec.rb
@@ -0,0 +1,51 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::StringConversionInInterpolation do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for #to_s in interpolation' do
+ inspect_source(cop, '"this is the #{result.to_s}"')
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Redundant use of `Object#to_s` in interpolation.'])
+ end
+
+ it 'detects #to_s in an interpolation with several expressions' do
+ inspect_source(cop, '"this is the #{top; result.to_s}"')
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts #to_s with arguments in an interpolation' do
+ inspect_source(cop, '"this is a #{result.to_s(8)}"')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts interpolation without #to_s' do
+ inspect_source(cop, '"this is the #{result}"')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not explode on implicit receiver' do
+ inspect_source(cop, '"#{to_s}"')
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Use `self` instead of `Object#to_s` in interpolation.'])
+ end
+
+ it 'does not explode on empty interpolation' do
+ inspect_source(cop, '"this is #{} silly"')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'autocorrects by removing the redundant to_s' do
+ corrected = autocorrect_source(cop, ['"some #{something.to_s}"'])
+ expect(corrected).to eq '"some #{something}"'
+ end
+
+ it 'autocorrects implicit receiver by replacing to_s with self' do
+ corrected = autocorrect_source(cop, ['"some #{to_s}"'])
+ expect(corrected).to eq '"some #{self}"'
+ end
+end
diff --git a/spec/rubocop/cop/lint/syntax_spec.rb b/spec/rubocop/cop/lint/syntax_spec.rb
new file mode 100644
index 0000000..8671411
--- /dev/null
+++ b/spec/rubocop/cop/lint/syntax_spec.rb
@@ -0,0 +1,34 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::Syntax do
+ describe '.offense_from_diagnostic' do
+ subject(:offense) { described_class.offense_from_diagnostic(diagnostic) }
+ let(:diagnostic) { Parser::Diagnostic.new(level, reason, args, location) }
+ let(:level) { :warning }
+ let(:reason) { :odd_hash }
+ let(:args) { [] }
+ let(:location) { double('location').as_null_object }
+
+ it 'returns an offense' do
+ expect(offense).to be_a(Rubocop::Cop::Offense)
+ end
+
+ it "sets diagnostic's level to offense's severity" do
+ expect(offense.severity).to eq(level)
+ end
+
+ it "sets diagnostic's message to offense's message" do
+ expect(offense.message).to eq('odd number of entries for a hash')
+ end
+
+ it "sets diagnostic's location to offense's location" do
+ expect(offense.location).to eq(location)
+ end
+
+ it 'sets Syntax as a cop name' do
+ expect(offense.cop_name).to eq('Syntax')
+ end
+ end
+end
diff --git a/spec/rubocop/cop/lint/unreachable_code_spec.rb b/spec/rubocop/cop/lint/unreachable_code_spec.rb
new file mode 100644
index 0000000..4ffefde
--- /dev/null
+++ b/spec/rubocop/cop/lint/unreachable_code_spec.rb
@@ -0,0 +1,63 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::UnreachableCode do
+ subject(:cop) { described_class.new }
+
+ described_class::NODE_TYPES.each do |t|
+ it "registers an offense for #{t} before other statements" do
+ inspect_source(cop,
+ ['foo = 5',
+ "#{t}",
+ 'bar'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it "accepts code with conditional #{t}" do
+ inspect_source(cop,
+ ['foo = 5',
+ "#{t} if test",
+ 'bar'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it "accepts #{t} as the final expression" do
+ inspect_source(cop,
+ ['foo = 5',
+ "#{t} if test"
+ ])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ described_class::FLOW_COMMANDS.each do |t|
+ it "registers an offense for #{t} before other statements" do
+ inspect_source(cop,
+ ['foo = 5',
+ "#{t} something",
+ 'bar'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it "accepts code with conditional #{t}" do
+ inspect_source(cop,
+ ['foo = 5',
+ "#{t} something if test",
+ 'bar'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it "accepts #{t} as the final expression" do
+ inspect_source(cop,
+ ['foo = 5',
+ "#{t} something if test"
+ ])
+ expect(cop.offenses).to be_empty
+ end
+ end
+end
diff --git a/spec/rubocop/cop/lint/useless_access_modifier_spec.rb b/spec/rubocop/cop/lint/useless_access_modifier_spec.rb
new file mode 100644
index 0000000..642dd19
--- /dev/null
+++ b/spec/rubocop/cop/lint/useless_access_modifier_spec.rb
@@ -0,0 +1,192 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::UselessAccessModifier do
+ subject(:cop) { described_class.new }
+
+ context 'when an access modifier has no effect' do
+ let(:source) do
+ [
+ 'class SomeClass',
+ ' def some_method',
+ ' puts 10',
+ ' end',
+ ' private',
+ ' def self.some_method',
+ ' puts 10',
+ ' end',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless `private` access modifier.')
+ expect(cop.offenses.first.line).to eq(5)
+ expect(cop.highlights).to eq(['private'])
+ end
+ end
+
+ context 'when an access modifier has no methods' do
+ let(:source) do
+ [
+ 'class SomeClass',
+ ' protected',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless `protected` access modifier.')
+ expect(cop.offenses.first.line).to eq(2)
+ expect(cop.highlights).to eq(['protected'])
+ end
+ end
+
+ context 'when an access modifier is followed by attr_*' do
+ let(:source) do
+ [
+ 'class SomeClass',
+ ' protected',
+ ' attr_accessor :some_property',
+ 'end'
+ ]
+ end
+
+ it 'does not register an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+
+ context 'when an access modifier is followed by a ' \
+ 'class method defined on constant' do
+ let(:source) do
+ [
+ 'class SomeClass',
+ ' protected',
+ ' def SomeClass.some_method',
+ ' end',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless `protected` access modifier.')
+ expect(cop.offenses.first.line).to eq(2)
+ expect(cop.highlights).to eq(['protected'])
+ end
+ end
+
+ context 'when consecutive access modifiers' do
+ let(:source) do
+ [
+ 'class SomeClass',
+ ' private',
+ ' private',
+ ' def some_method',
+ ' puts 10',
+ ' end',
+ ' def some_other_method',
+ ' puts 10',
+ ' end',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless `private` access modifier.')
+ expect(cop.offenses.first.line).to eq(2)
+ expect(cop.highlights).to eq(['private'])
+ end
+ end
+
+ context 'when passing method as symbol' do
+ let(:source) do
+ [
+ 'class SomeClass',
+ ' def some_method',
+ ' puts 10',
+ ' end',
+ ' private :some_method',
+ 'end'
+ ]
+ end
+
+ it 'does not register an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+
+ context 'when class is empty save modifier' do
+ let(:source) do
+ [
+ 'class SomeClass',
+ ' private',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless `private` access modifier.')
+ expect(cop.offenses.first.line).to eq(2)
+ expect(cop.highlights).to eq(['private'])
+ end
+ end
+
+ context 'when multiple class definitions in file but only one has offense' do
+ let(:source) do
+ [
+ 'class SomeClass',
+ ' private',
+ 'end',
+ 'class SomeOtherClass',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless `private` access modifier.')
+ expect(cop.offenses.first.line).to eq(2)
+ expect(cop.highlights).to eq(['private'])
+ end
+ end
+
+ if RUBY_ENGINE == 'ruby' && RUBY_VERSION.start_with?('2.1')
+ context 'ruby 2.1 style modifiers' do
+ let(:source) do
+ [
+ 'class SomeClass',
+ ' private def some_method',
+ ' puts 10',
+ ' end',
+ 'end'
+ ]
+ end
+
+ it 'does not register an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/cop/lint/useless_assignment_spec.rb b/spec/rubocop/cop/lint/useless_assignment_spec.rb
new file mode 100644
index 0000000..81e2fa5
--- /dev/null
+++ b/spec/rubocop/cop/lint/useless_assignment_spec.rb
@@ -0,0 +1,1608 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::UselessAssignment do
+ subject(:cop) { described_class.new }
+
+ context 'when a variable is assigned and unreferenced in a method' do
+ let(:source) do
+ [
+ 'class SomeClass',
+ ' foo = 1',
+ ' puts foo',
+ ' def some_method',
+ ' foo = 2',
+ ' bar = 3',
+ ' puts bar',
+ ' end',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(5)
+ expect(cop.highlights).to eq(['foo'])
+ end
+
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is assigned and unreferenced ' \
+ 'in a singleton method defined with self keyword' do
+ let(:source) do
+ [
+ 'class SomeClass',
+ ' foo = 1',
+ ' puts foo',
+ ' def self.some_method',
+ ' foo = 2',
+ ' bar = 3',
+ ' puts bar',
+ ' end',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(5)
+ expect(cop.highlights).to eq(['foo'])
+ end
+
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is assigned and unreferenced ' \
+ 'in a singleton method defined with variable name' do
+ let(:source) do
+ [
+ '1.times do',
+ ' foo = 1',
+ ' puts foo',
+ ' instance = Object.new',
+ ' def instance.some_method',
+ ' foo = 2',
+ ' bar = 3',
+ ' puts bar',
+ ' end',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(6)
+ expect(cop.highlights).to eq(['foo'])
+ end
+
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is assigned and unreferenced in a class' do
+ let(:source) do
+ [
+ '1.times do',
+ ' foo = 1',
+ ' puts foo',
+ ' class SomeClass',
+ ' foo = 2',
+ ' bar = 3',
+ ' puts bar',
+ ' end',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(5)
+ expect(cop.highlights).to eq(['foo'])
+ end
+
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is assigned and unreferenced in a class ' \
+ 'subclassing another class stored in local variable' do
+ let(:source) do
+ [
+ '1.times do',
+ ' foo = 1',
+ ' puts foo',
+ ' array_class = Array',
+ ' class SomeClass < array_class',
+ ' foo = 2',
+ ' bar = 3',
+ ' puts bar',
+ ' end',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(6)
+ expect(cop.highlights).to eq(['foo'])
+ end
+
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is assigned and unreferenced ' \
+ 'in a singleton class' do
+ let(:source) do
+ [
+ '1.times do',
+ ' foo = 1',
+ ' puts foo',
+ ' instance = Object.new',
+ ' class << instance',
+ ' foo = 2',
+ ' bar = 3',
+ ' puts bar',
+ ' end',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(6)
+ expect(cop.highlights).to eq(['foo'])
+ end
+
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is assigned and unreferenced in a module' do
+ let(:source) do
+ [
+ '1.times do',
+ ' foo = 1',
+ ' puts foo',
+ ' module SomeModule',
+ ' foo = 2',
+ ' bar = 3',
+ ' puts bar',
+ ' end',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(5)
+ expect(cop.highlights).to eq(['foo'])
+ end
+
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is assigned and unreferenced in top level' do
+ let(:source) do
+ [
+ 'foo = 1',
+ 'bar = 2',
+ 'puts bar'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(1)
+ expect(cop.highlights).to eq(['foo'])
+ end
+
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is assigned with operator assignment ' \
+ 'in top level' do
+ let(:source) do
+ [
+ 'foo ||= 1'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo. Use just operator ||.')
+ expect(cop.offenses.first.line).to eq(1)
+ expect(cop.highlights).to eq(['foo'])
+ end
+ end
+
+ context 'when a variable is assigned multiple times ' \
+ 'but unreferenced' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' foo = 1',
+ ' bar = 2',
+ ' foo = 3',
+ ' puts bar',
+ 'end'
+ ]
+ end
+
+ it 'registers offenses for each asignment' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(2)
+
+ expect(cop.offenses[0].message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses[0].line).to eq(2)
+
+ expect(cop.offenses[1].message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses[1].line).to eq(4)
+
+ expect(cop.highlights).to eq(%w(foo foo))
+ end
+ end
+
+ context 'when a referenced variable is reassigned ' \
+ 'but not re-referenced' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' foo = 1',
+ ' puts foo',
+ ' foo = 3',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense for the non-re-referenced assignment' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(4)
+ expect(cop.highlights).to eq(['foo'])
+ end
+ end
+
+ context 'when an unreferenced variable is reassigned ' \
+ 'and re-referenced' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' foo = 1',
+ ' foo = 3',
+ ' puts foo',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense for the unreferenced assignment' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(2)
+ expect(cop.highlights).to eq(['foo'])
+ end
+ end
+
+ context 'when an unreferenced variable is reassigned in a block' do
+ let(:source) do
+ [
+ 'def const_name(node)',
+ ' const_names = []',
+ ' const_node = node',
+ '',
+ ' loop do',
+ ' namespace_node, name = *const_node',
+ ' const_names << name',
+ ' break unless namespace_node',
+ ' break if namespace_node.type == :cbase',
+ ' const_node = namespace_node',
+ ' end',
+ '',
+ " const_names.reverse.join('::')",
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a referenced variable is reassigned in a block' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' foo = 1',
+ ' puts foo',
+ ' 1.times do',
+ ' foo = 2',
+ ' end',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a block local variable is declared but not assigned' do
+ let(:source) do
+ [
+ '1.times do |i; foo|',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense for the declaration' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(1)
+ expect(cop.highlights).to eq(['foo'])
+ end
+
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a block local variable is assigned and unreferenced' do
+ let(:source) do
+ [
+ '1.times do |i; foo|',
+ ' foo = 2',
+ 'end'
+ ]
+ end
+
+ it 'registers offenses for the assignment' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(2)
+ expect(cop.highlights).to eq(['foo'])
+ end
+ end
+
+ context 'when a variable is assigned in loop body and unreferenced' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' while true',
+ ' foo = 1',
+ ' end',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(3)
+ expect(cop.highlights).to eq(['foo'])
+ end
+
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is reassigned at the end of loop body ' \
+ 'and would be referenced in next iteration' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' total = 0',
+ ' foo = 0',
+ '',
+ ' while total < 100',
+ ' total += foo',
+ ' foo += 1',
+ ' end',
+ '',
+ ' total',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is reassigned at the end of loop body ' \
+ 'and would be referenced in loop condition' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' total = 0',
+ ' foo = 0',
+ '',
+ ' while foo < 100',
+ ' total += 1',
+ ' foo += 1',
+ ' end',
+ '',
+ ' total',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a setter is invoked with operator assignment in loop body' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' obj = {}',
+ '',
+ ' while obj[:count] < 100',
+ ' obj[:count] += 1',
+ ' end',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context "when a variable is reassigned in loop body but won't " \
+ 'be referenced either next iteration or loop condition' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' total = 0',
+ ' foo = 0',
+ '',
+ ' while total < 100',
+ ' total += 1',
+ ' foo += 1',
+ ' end',
+ '',
+ ' total',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ pending 'Requires an advanced logic that checks whether the return ' \
+ 'value of an operator assignment is used or not.'
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(7)
+ expect(cop.highlights).to eq(['foo'])
+ end
+ end
+
+ context 'when a referenced variable is reassigned ' \
+ 'but not re-referenced in a method defined in loop' do
+ let(:source) do
+ [
+ 'while true',
+ ' def some_method',
+ ' foo = 1',
+ ' puts foo',
+ ' foo = 3',
+ ' end',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(5)
+ expect(cop.highlights).to eq(['foo'])
+ end
+ end
+
+ context 'when a variable that has same name as outer scope variable ' \
+ 'is not referenced in a method defined in loop' do
+ let(:source) do
+ [
+ 'foo = 1',
+ '',
+ 'while foo < 100',
+ ' foo += 1',
+ ' def some_method',
+ ' foo = 1',
+ ' end',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(6)
+ expect(cop.highlights).to eq(['foo'])
+ end
+ end
+
+ context 'when a variable is assigned in single branch if ' \
+ 'and unreferenced' do
+ let(:source) do
+ [
+ 'def some_method(flag)',
+ ' if flag',
+ ' foo = 1',
+ ' end',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(3)
+ expect(cop.highlights).to eq(['foo'])
+ end
+
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a unreferenced variable is reassigned in same branch ' \
+ 'and referenced after the branching' do
+ let(:source) do
+ [
+ 'def some_method(flag)',
+ ' if flag',
+ ' foo = 1',
+ ' foo = 2',
+ ' end',
+ '',
+ ' foo',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense for the unreferenced assignment' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(3)
+ expect(cop.highlights).to eq(['foo'])
+ end
+ end
+
+ context 'when a variable is reassigned in single branch if ' \
+ 'and referenced after the branching' do
+ let(:source) do
+ [
+ 'def some_method(flag)',
+ ' foo = 1',
+ '',
+ ' if flag',
+ ' foo = 2',
+ ' end',
+ '',
+ ' foo',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is assigned in each branch of if ' \
+ 'and referenced after the branching' do
+ let(:source) do
+ [
+ 'def some_method(flag)',
+ ' if flag',
+ ' foo = 2',
+ ' else',
+ ' foo = 3',
+ ' end',
+ '',
+ ' foo',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is reassigned in single branch if ' \
+ 'and referenced in the branch' do
+ let(:source) do
+ [
+ 'def some_method(flag)',
+ ' foo = 1',
+ '',
+ ' if flag',
+ ' foo = 2',
+ ' puts foo',
+ ' end',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense for the unreferenced assignment' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(2)
+ expect(cop.highlights).to eq(['foo'])
+ end
+ end
+
+ context 'when a variable is assigned in each branch of if ' \
+ 'and referenced in the else branch' do
+ let(:source) do
+ [
+ 'def some_method(flag)',
+ ' if flag',
+ ' foo = 2',
+ ' else',
+ ' foo = 3',
+ ' puts foo',
+ ' end',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense for the assignment in the if branch' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(3)
+ expect(cop.highlights).to eq(['foo'])
+ end
+ end
+
+ context 'when a variable is assigned in branch of modifier if ' \
+ 'that references the variable in its conditional clause' \
+ 'and referenced after the branching' do
+ let(:source) do
+ [
+ 'def some_method(flag)',
+ ' foo = 1 unless foo',
+ ' puts foo',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is assigned in branch of modifier if ' \
+ 'that references the variable in its conditional clause' \
+ 'and unreferenced' do
+ let(:source) do
+ [
+ 'def some_method(flag)',
+ ' foo = 1 unless foo',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(2)
+ expect(cop.highlights).to eq(['foo'])
+ end
+ end
+
+ context 'when a variable is assigned on each side of && ' \
+ 'and referenced after the &&' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' (foo = do_something_returns_object_or_nil) && (foo = 1)',
+ ' foo',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a unreferenced variable is reassigned ' \
+ 'on the left side of && and referenced after the &&' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' foo = 1',
+ ' (foo = do_something_returns_object_or_nil) && do_something',
+ ' foo',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense for the unreferenced assignment' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(2)
+ expect(cop.highlights).to eq(['foo'])
+ end
+ end
+
+ context 'when a unreferenced variable is reassigned ' \
+ 'on the right side of && and referenced after the &&' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' foo = 1',
+ ' do_something_returns_object_or_nil && foo = 2',
+ ' foo',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is reassigned ' \
+ 'while referencing itself in rhs and referenced' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' foo = [1, 2]',
+ ' foo = foo.map { |i| i + 1 }',
+ ' puts foo',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is reassigned ' \
+ 'with binary operator assignment and referenced' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' foo = 1',
+ ' foo += 1',
+ ' foo',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is reassigned ' \
+ 'with logical operator assignment and referenced' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' foo = do_something_returns_object_or_nil',
+ ' foo ||= 1',
+ ' foo',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is reassigned with binary operator ' \
+ 'assignment while assigning to itself in rhs ' \
+ 'then referenced' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' foo = 1',
+ ' foo += foo = 2',
+ ' foo',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense for the assignment in rhs' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(3)
+ expect(cop.highlights).to eq(['foo'])
+ end
+ end
+
+ context 'when a variable is assigned first with ||= and referenced' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' foo ||= 1',
+ ' foo',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is assigned with ||= ' \
+ 'at the last expression of the scope' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' foo = do_something_returns_object_or_nil',
+ ' foo ||= 1',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message).to eq(
+ 'Useless assignment to variable - foo. Use just operator ||.'
+ )
+ expect(cop.offenses.first.line).to eq(3)
+ expect(cop.highlights).to eq(['foo'])
+ end
+ end
+
+ context 'when a variable is assigned with ||= ' \
+ 'before the last expression of the scope' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' foo = do_something_returns_object_or_nil',
+ ' foo ||= 1',
+ ' some_return_value',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(3)
+ expect(cop.highlights).to eq(['foo'])
+ end
+ end
+
+ context 'when a variable is assigned with multiple assignment ' \
+ 'and unreferenced' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' foo, bar = do_something',
+ ' puts foo',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message).to eq(
+ 'Useless assignment to variable - bar. ' \
+ 'Use _ or _bar as a variable name ' \
+ "to indicate that it won't be used."
+ )
+ expect(cop.offenses.first.line).to eq(2)
+ expect(cop.highlights).to eq(['bar'])
+ end
+
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is reassigned with multiple assignment ' \
+ 'while referencing itself in rhs and referenced' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' foo = 1',
+ ' foo, bar = do_something(foo)',
+ ' puts foo, bar',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is assigned in loop body ' \
+ 'and referenced in post while condition' do
+ let(:source) do
+ [
+ 'begin',
+ ' a = (a || 0) + 1',
+ ' puts a',
+ 'end while a <= 2'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is assigned in loop body ' \
+ 'and referenced in post until condition' do
+ let(:source) do
+ [
+ 'begin',
+ ' a = (a || 0) + 1',
+ ' puts a',
+ 'end until a > 2'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is assigned ' \
+ 'in main body of begin with rescue but unreferenced' do
+ let(:source) do
+ [
+ 'begin',
+ ' do_something',
+ ' foo = true',
+ 'rescue',
+ ' do_anything',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(3)
+ expect(cop.highlights).to eq(['foo'])
+ end
+
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is assigned in main body of begin, rescue ' \
+ 'and else then referenced after the begin' do
+ let(:source) do
+ [
+ 'begin',
+ ' do_something',
+ ' foo = :in_begin',
+ 'rescue FirstError',
+ ' foo = :in_first_rescue',
+ 'rescue SecondError',
+ ' foo = :in_second_rescue',
+ 'else',
+ ' foo = :in_else',
+ 'end',
+ '',
+ 'puts foo'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is reassigned multiple times ' \
+ 'in main body of begin then referenced after the begin' do
+ let(:source) do
+ [
+ 'begin',
+ ' status = :initial',
+ ' connect_sometimes_fails!',
+ ' status = :connected',
+ ' fetch_sometimes_fails!',
+ ' status = :fetched',
+ 'rescue',
+ ' do_something',
+ 'end',
+ '',
+ 'puts status'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is reassigned multiple times ' \
+ 'in main body of begin then referenced in rescue' do
+ let(:source) do
+ [
+ 'begin',
+ ' status = :initial',
+ ' connect_sometimes_fails!',
+ ' status = :connected',
+ ' fetch_sometimes_fails!',
+ ' status = :fetched',
+ 'rescue',
+ ' puts status',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is reassigned multiple times ' \
+ 'in main body of begin then referenced in ensure' do
+ let(:source) do
+ [
+ 'begin',
+ ' status = :initial',
+ ' connect_sometimes_fails!',
+ ' status = :connected',
+ ' fetch_sometimes_fails!',
+ ' status = :fetched',
+ 'ensure',
+ ' puts status',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is reassigned multiple times in rescue ' \
+ 'and referenced after the begin' do
+ let(:source) do
+ [
+ 'foo = false',
+ '',
+ 'begin',
+ ' do_something',
+ 'rescue',
+ ' foo = true',
+ ' foo = true',
+ 'end',
+ '',
+ 'puts foo'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(6)
+ expect(cop.highlights).to eq(['foo'])
+ end
+ end
+
+ context 'when a variable is reassigned multiple times ' \
+ 'in rescue with ensure then referenced after the begin' do
+ let(:source) do
+ [
+ 'foo = false',
+ '',
+ 'begin',
+ ' do_something',
+ 'rescue',
+ ' foo = true',
+ ' foo = true',
+ 'ensure',
+ ' do_anything',
+ 'end',
+ '',
+ 'puts foo'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(6)
+ expect(cop.highlights).to eq(['foo'])
+ end
+ end
+
+ context 'when a variable is reassigned multiple times ' \
+ 'in ensure with rescue then referenced after the begin' do
+ let(:source) do
+ [
+ 'begin',
+ ' do_something',
+ 'rescue',
+ ' do_anything',
+ 'ensure',
+ ' foo = true',
+ ' foo = true',
+ 'end',
+ '',
+ 'puts foo'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(6)
+ expect(cop.highlights).to eq(['foo'])
+ end
+ end
+
+ context 'when a variable is assigned at the end of rescue ' \
+ 'and would be referenced with retry' do
+ let(:source) do
+ [
+ 'retried = false',
+ '',
+ 'begin',
+ ' do_something',
+ 'rescue',
+ ' fail if retried',
+ ' retried = true',
+ ' retry',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is assigned with operator assignment ' \
+ 'in rescue and would be referenced with retry' do
+ let(:source) do
+ [
+ 'retry_count = 0',
+ '',
+ 'begin',
+ ' do_something',
+ 'rescue',
+ ' fail if (retry_count += 1) > 3',
+ ' retry',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is assigned ' \
+ 'in main body of begin, rescue and else ' \
+ 'and reassigned in ensure then referenced after the begin' do
+ let(:source) do
+ [
+ 'begin',
+ ' do_something',
+ ' foo = :in_begin',
+ 'rescue FirstError',
+ ' foo = :in_first_rescue',
+ 'rescue SecondError',
+ ' foo = :in_second_rescue',
+ 'else',
+ ' foo = :in_else',
+ 'ensure',
+ ' foo = :in_ensure',
+ 'end',
+ '',
+ 'puts foo'
+ ]
+ end
+
+ it 'registers offenses for each assignment before ensure' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(4)
+
+ expect(cop.offenses[0].line).to eq(3)
+ expect(cop.offenses[1].line).to eq(5)
+ expect(cop.offenses[2].line).to eq(7)
+ expect(cop.offenses[3].line).to eq(9)
+ end
+ end
+
+ context 'when a method argument is reassigned ' \
+ 'and zero arity super is called' do
+ let(:source) do
+ [
+ 'def some_method(foo)',
+ ' foo = 1',
+ ' super',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a local variable is unreferenced ' \
+ 'and zero arity super is called' do
+ let(:source) do
+ [
+ 'def some_method(bar)',
+ ' foo = 1',
+ ' super',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(2)
+ expect(cop.highlights).to eq(['foo'])
+ end
+
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a method argument is reassigned ' \
+ 'but not passed to super' do
+ let(:source) do
+ [
+ 'def some_method(foo, bar)',
+ ' foo = 1',
+ ' super(bar)',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(2)
+ expect(cop.highlights).to eq(['foo'])
+ end
+ end
+
+ context 'when a named capture is unreferenced in top level' do
+ let(:source) do
+ [
+ "/(?<foo>\w+)/ =~ 'FOO'"
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(1)
+ end
+
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a named capture is unreferenced ' \
+ 'in other than top level' do
+ let(:source) do
+ [
+ 'def some_method',
+ " /(?<foo>\w+)/ =~ 'FOO'",
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(2)
+ expect(cop.highlights).to eq(["/(?<foo>\w+)/"])
+ end
+
+ # MRI 2.0 accepts this case, but I have no idea why it does so
+ # and there's no convincing reason to conform to this behavior,
+ # so RuboCop does not mimic MRI in this case.
+ end
+
+ context 'when a named capture is referenced' do
+ let(:source) do
+ [
+ 'def some_method',
+ " /(?<foo>\w+)(?<bar>\s+)/ =~ 'FOO'",
+ ' puts foo',
+ ' puts bar',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is referenced ' \
+ 'in rhs of named capture expression' do
+ let(:source) do
+ [
+ 'def some_method',
+ " foo = 'some string'",
+ " /(?<foo>\w+)/ =~ foo",
+ ' puts foo',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ end
+
+ context 'when a variable is assigned in begin ' \
+ 'and referenced outside' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' begin',
+ ' foo = 1',
+ ' end',
+ ' puts foo',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is shadowed by a block argument ' \
+ 'and unreferenced' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' foo = 1',
+ ' 1.times do |foo|',
+ ' puts foo',
+ ' end',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(2)
+ expect(cop.highlights).to eq(['foo'])
+ end
+
+ include_examples 'mimics MRI 2.1', 'unused variable'
+ end
+
+ context 'when a variable is not used and the name starts with _' do
+ let(:source) do
+ [
+ 'def some_method',
+ ' _foo = 1',
+ ' bar = 2',
+ ' puts bar',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a method argument is not used' do
+ let(:source) do
+ [
+ 'def some_method(arg)',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when an optional method argument is not used' do
+ let(:source) do
+ [
+ 'def some_method(arg = nil)',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a block method argument is not used' do
+ let(:source) do
+ [
+ 'def some_method(&block)',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a splat method argument is not used' do
+ let(:source) do
+ [
+ 'def some_method(*args)',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a optional keyword method argument is not used' do
+ let(:source) do
+ [
+ 'def some_method(name: value)',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts' unless RUBY_VERSION < '2.0'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a keyword splat method argument is used' do
+ let(:source) do
+ [
+ 'def some_method(name: value, **rest_keywords)',
+ ' p rest_keywords',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts' unless RUBY_VERSION < '2.0'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a keyword splat method argument is not used' do
+ let(:source) do
+ [
+ 'def some_method(name: value, **rest_keywords)',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts' unless RUBY_VERSION < '2.0'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a block argument is not used' do
+ let(:source) do
+ [
+ '1.times do |i|',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when there is only one AST node and it is unused variable' do
+ let(:source) do
+ [
+ 'foo = 1'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(1)
+ expect(cop.highlights).to eq(['foo'])
+ end
+
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'when a variable is assigned ' \
+ 'while being passed to a method taking block' do
+
+ context 'and the variable is used' do
+ let(:source) do
+ [
+ 'some_method(foo = 1) do',
+ 'end',
+ 'puts foo'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+
+ context 'and the variable is not used' do
+ let(:source) do
+ [
+ 'some_method(foo = 1) do',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Useless assignment to variable - foo')
+ expect(cop.offenses.first.line).to eq(1)
+ expect(cop.highlights).to eq(['foo'])
+ end
+
+ include_examples 'mimics MRI 2.1'
+ end
+ end
+
+ context 'when a variabled is assigned ' \
+ 'and passed to a method followed by method taking block' do
+ let(:source) do
+ [
+ "pattern = '*.rb'",
+ 'Dir.glob(pattern).map do |path|',
+ 'end'
+ ]
+ end
+
+ include_examples 'accepts'
+ include_examples 'mimics MRI 2.1'
+ end
+end
diff --git a/spec/rubocop/cop/lint/useless_comparison_spec.rb b/spec/rubocop/cop/lint/useless_comparison_spec.rb
new file mode 100644
index 0000000..d7b5299
--- /dev/null
+++ b/spec/rubocop/cop/lint/useless_comparison_spec.rb
@@ -0,0 +1,30 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::UselessComparison do
+ subject(:cop) { described_class.new }
+
+ described_class::OPS.each do |op|
+ it "registers an offense for a simple comparison with #{op}" do
+ inspect_source(cop,
+ ["5 #{op} 5",
+ "a #{op} a"
+ ])
+ expect(cop.offenses.size).to eq(2)
+ end
+
+ it "registers an offense for a complex comparison with #{op}" do
+ inspect_source(cop,
+ ["5 + 10 * 30 #{op} 5 + 10 * 30",
+ "a.top(x) #{op} a.top(x)"
+ ])
+ expect(cop.offenses.size).to eq(2)
+ end
+ end
+
+ it 'works with lambda.()' do
+ inspect_source(cop, ['a.(x) > a.(x)'])
+ expect(cop.offenses.size).to eq(1)
+ end
+end
diff --git a/spec/rubocop/cop/lint/useless_else_without_rescue_spec.rb b/spec/rubocop/cop/lint/useless_else_without_rescue_spec.rb
new file mode 100644
index 0000000..f70a8bf
--- /dev/null
+++ b/spec/rubocop/cop/lint/useless_else_without_rescue_spec.rb
@@ -0,0 +1,48 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::UselessElseWithoutRescue do
+ subject(:cop) { described_class.new }
+
+ before do
+ inspect_source(cop, source)
+ end
+
+ context 'with `else` without `rescue`' do
+ let(:source) do
+ [
+ 'begin',
+ ' do_something',
+ 'else',
+ ' handle_unknown_errors',
+ 'end'
+ ]
+ end
+
+ it 'registers an offense' do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('Else without rescue is useless')
+ expect(cop.highlights).to eq(['else'])
+ end
+ end
+
+ context 'with `else` with `rescue`' do
+ let(:source) do
+ [
+ 'begin',
+ ' do_something',
+ 'rescue ArgumentError',
+ ' handle_argument_error',
+ 'else',
+ ' handle_unknown_errors',
+ 'end'
+ ]
+ end
+
+ it 'accepts' do
+ expect(cop.offenses).to be_empty
+ end
+ end
+end
diff --git a/spec/rubocop/cop/lint/useless_setter_call_spec.rb b/spec/rubocop/cop/lint/useless_setter_call_spec.rb
new file mode 100644
index 0000000..6a68d9c
--- /dev/null
+++ b/spec/rubocop/cop/lint/useless_setter_call_spec.rb
@@ -0,0 +1,149 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::UselessSetterCall do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for def ending with lvar attr assignment' do
+ inspect_source(cop,
+ ['def test',
+ ' top = Top.new',
+ ' top.attr = 5',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for defs ending with lvar attr assignment' do
+ inspect_source(cop,
+ ['def Top.test',
+ ' top = Top.new',
+ ' top.attr = 5',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts def ending with ivar assignment' do
+ inspect_source(cop,
+ ['def test',
+ ' something',
+ ' @top = 5',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts def ending ivar attr assignment' do
+ inspect_source(cop,
+ ['def test',
+ ' something',
+ ' @top.attr = 5',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts def ending with argument attr assignment' do
+ inspect_source(cop,
+ ['def test(some_arg)',
+ ' unrelated_local_variable = Top.new',
+ ' some_arg.attr = 5',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ context 'when a lvar has an object passed as argument ' \
+ 'at the end of the method' do
+ it 'accepts the lvar attr assignment' do
+ inspect_source(cop,
+ ['def test(some_arg)',
+ ' @some_ivar = some_arg',
+ ' @some_ivar.do_something',
+ ' some_lvar = @some_ivar',
+ ' some_lvar.do_something',
+ ' some_lvar.attr = 5',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'when a lvar has an object passed as argument ' \
+ 'by multiple-assignment at the end of the method' do
+ it 'accepts the lvar attr assignment' do
+ inspect_source(cop,
+ ['def test(some_arg)',
+ ' _first, some_lvar, _third = 1, some_arg, 3',
+ ' some_lvar.attr = 5',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'when a lvar does not have any object passed as argument ' \
+ 'with multiple-assignment at the end of the method' do
+ it 'registers an offense' do
+ inspect_source(cop,
+ ['def test(some_arg)',
+ ' _first, some_lvar, _third = do_something',
+ ' some_lvar.attr = 5',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+
+ context 'when a lvar possibly has an object passed as argument ' \
+ 'by logical-operator-assignment at the end of the method' do
+ it 'accepts the lvar attr assignment' do
+ inspect_source(cop,
+ ['def test(some_arg)',
+ ' some_lvar = nil',
+ ' some_lvar ||= some_arg',
+ ' some_lvar.attr = 5',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'when a lvar does not have any object passed as argument ' \
+ 'by binary-operator-assignment at the end of the method' do
+ it 'registers an offense' do
+ inspect_source(cop,
+ ['def test(some_arg)',
+ ' some_lvar = some_arg',
+ ' some_lvar += some_arg',
+ ' some_lvar.attr = 5',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+
+ context 'when a lvar declared as an argument ' \
+ 'is no longer the passed object at the end of the method' do
+ it 'registers an offense for the lvar attr assignment' do
+ inspect_source(cop,
+ ['def test(some_arg)',
+ ' some_arg = Top.new',
+ ' some_arg.attr = 5',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+
+ it 'is not confused by operators ending with =' do
+ inspect_source(cop,
+ ['def test',
+ ' top.attr == 5',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/lint/void_spec.rb b/spec/rubocop/cop/lint/void_spec.rb
new file mode 100644
index 0000000..d58bf12
--- /dev/null
+++ b/spec/rubocop/cop/lint/void_spec.rb
@@ -0,0 +1,57 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Lint::Void do
+ subject(:cop) { described_class.new }
+
+ described_class::OPS.each do |op|
+ it "registers an offense for void op #{op} if not on last line" do
+ inspect_source(cop,
+ ["a #{op} b",
+ "a #{op} b",
+ "a #{op} b"
+ ])
+ expect(cop.offenses.size).to eq(2)
+ end
+ end
+
+ described_class::OPS.each do |op|
+ it "accepts void op #{op} if on last line" do
+ inspect_source(cop,
+ ['something',
+ "a #{op} b"
+ ])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ described_class::OPS.each do |op|
+ it "accepts void op #{op} by itself without a begin block" do
+ inspect_source(cop, ["a #{op} b"])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ %w(var @var @@var VAR).each do |var|
+ it "registers an offense for void var #{var} if not on last line" do
+ inspect_source(cop,
+ ["#{var} = 5",
+ "#{var}",
+ 'top'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+
+ %w(1 2.0 /test/ [1] {}).each do |lit|
+ it "registers an offense for void lit #{lit} if not on last line" do
+ inspect_source(cop,
+ ["#{lit}",
+ 'top'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+
+end
diff --git a/spec/rubocop/cop/offense_spec.rb b/spec/rubocop/cop/offense_spec.rb
new file mode 100644
index 0000000..28d7028
--- /dev/null
+++ b/spec/rubocop/cop/offense_spec.rb
@@ -0,0 +1,133 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Offense do
+ let(:location) do
+ source_buffer = Parser::Source::Buffer.new('test', 1)
+ source_buffer.source = "a\n"
+ Parser::Source::Range.new(source_buffer, 0, 1)
+ end
+ subject(:offense) do
+ described_class.new(:convention, location, 'message', 'CopName', true)
+ end
+
+ it 'has a few required attributes' do
+ expect(offense.severity).to eq(:convention)
+ expect(offense.line).to eq(1)
+ expect(offense.message).to eq('message')
+ expect(offense.cop_name).to eq('CopName')
+ expect(offense.corrected?).to be_true
+ end
+
+ it 'overrides #to_s' do
+ expect(offense.to_s).to eq('C: 1: 1: message')
+ end
+
+ it 'does not blow up if a message contains %' do
+ offense = described_class.new(:convention, location, 'message % test',
+ 'CopName')
+
+ expect(offense.to_s).to eq('C: 1: 1: message % test')
+ end
+
+ it 'redefines == to compare offenses based on their contents' do
+ o1 = described_class.new(:convention, location, 'message', 'CopName')
+ o2 = described_class.new(:convention, location, 'message', 'CopName')
+
+ expect(o1 == o2).to be_true
+ end
+
+ it 'is frozen' do
+ expect(offense).to be_frozen
+ end
+
+ [:severity, :location, :line, :column, :message, :cop_name].each do |a|
+ describe "##{a}" do
+ it 'is frozen' do
+ expect(offense.send(a)).to be_frozen
+ end
+ end
+ end
+
+ context 'when unknown severity is passed' do
+ it 'raises error' do
+ expect do
+ described_class.new(:foobar, location, 'message', 'CopName')
+ end.to raise_error(ArgumentError)
+ end
+ end
+
+ describe '#severity_level' do
+ subject(:severity_level) do
+ described_class.new(severity, location, 'message', 'CopName')
+ .severity.level
+ end
+
+ context 'when severity is :refactor' do
+ let(:severity) { :refactor }
+ it 'is 1' do
+ expect(severity_level).to eq(1)
+ end
+ end
+
+ context 'when severity is :fatal' do
+ let(:severity) { :fatal }
+ it 'is 5' do
+ expect(severity_level).to eq(5)
+ end
+ end
+ end
+
+ describe '#<=>' do
+ def offense(hash = {})
+ attrs = {
+ sev: :convention,
+ line: 5,
+ col: 5,
+ mes: 'message',
+ cop: 'CopName'
+ }.merge(hash)
+
+ described_class.new(
+ attrs[:sev],
+ location(attrs[:line], attrs[:col],
+ %w(aaaaaa bbbbbb cccccc dddddd eeeeee ffffff)),
+ attrs[:mes],
+ attrs[:cop]
+ )
+ end
+
+ def location(line, column, source)
+ source_buffer = Parser::Source::Buffer.new('test', 1)
+ source_buffer.source = source.join("\n")
+ begin_pos = source[0...(line - 1)].reduce(0) do |a, e|
+ a + e.length + "\n".length
+ end + column
+ Parser::Source::Range.new(source_buffer, begin_pos, begin_pos + 1)
+ end
+
+ # We want a nice table layout, so we allow space inside empty hashes.
+ # rubocop:disable SpaceInsideHashLiteralBraces
+ [
+ [{ }, { }, 0],
+
+ [{ line: 6 }, { line: 5 }, 1],
+
+ [{ line: 5, col: 6 }, { line: 5, col: 5 }, 1],
+ [{ line: 6, col: 4 }, { line: 5, col: 5 }, 1],
+
+ [{ cop: 'B' }, { cop: 'A' }, 1],
+ [{ line: 6, cop: 'A' }, { line: 5, cop: 'B' }, 1],
+ [{ col: 6, cop: 'A' }, { col: 5, cop: 'B' }, 1]
+ ].each do |one, other, expectation|
+ context "when receiver has #{one} and other has #{other}" do
+ it "returns #{expectation}" do
+ an_offense = offense(one)
+ other_offense = offense(other)
+ expect(an_offense <=> other_offense).to eq(expectation)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/cop/rails/action_filter_spec.rb b/spec/rubocop/cop/rails/action_filter_spec.rb
new file mode 100644
index 0000000..bfc6ccf
--- /dev/null
+++ b/spec/rubocop/cop/rails/action_filter_spec.rb
@@ -0,0 +1,69 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Rails::ActionFilter, :config do
+ subject(:cop) { described_class.new(config) }
+
+ context 'when style is action' do
+ let(:cop_config) { { 'EnforcedStyle' => 'action' } }
+
+ described_class::FILTER_METHODS.each do |method|
+ it "registers an offense for #{method}" do
+ inspect_source_file(cop,
+ ["#{method} :name"])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it "registers an offense for #{method} with block" do
+ inspect_source_file(cop,
+ ["#{method} { |controller| something }"])
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+
+ described_class::ACTION_METHODS.each do |method|
+ it "accepts #{method}" do
+ inspect_source_file(cop,
+ ["#{method} :something"])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ it 'auto-corrects to preferred method' do
+ new_source = autocorrect_source_file(cop, 'before_filter :test')
+ expect(new_source).to eq('before_action :test')
+ end
+ end
+
+ context 'when style is filter' do
+ let(:cop_config) { { 'EnforcedStyle' => 'filter' } }
+
+ described_class::ACTION_METHODS.each do |method|
+ it "registers an offense for #{method}" do
+ inspect_source_file(cop,
+ ["#{method} :name"])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it "registers an offense for #{method} with block" do
+ inspect_source_file(cop,
+ ["#{method} { |controller| something }"])
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+
+ described_class::FILTER_METHODS.each do |method|
+ it "accepts #{method}" do
+ inspect_source_file(cop,
+ ["#{method} :something"])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ it 'auto-corrects to preferred method' do
+ new_source = autocorrect_source_file(cop, 'before_action :test')
+ expect(new_source).to eq('before_filter :test')
+ end
+ end
+end
diff --git a/spec/rubocop/cop/rails/default_scope_spec.rb b/spec/rubocop/cop/rails/default_scope_spec.rb
new file mode 100644
index 0000000..59b9849
--- /dev/null
+++ b/spec/rubocop/cop/rails/default_scope_spec.rb
@@ -0,0 +1,37 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Rails::DefaultScope do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for default scope with a lambda arg' do
+ inspect_source(cop,
+ ['default_scope -> { something }'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for default scope with a proc arg' do
+ inspect_source(cop,
+ ['default_scope proc { something }'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for default scope with a proc(Proc.new) arg' do
+ inspect_source(cop,
+ ['default_scope Proc.new { something }'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for non blocks' do
+ inspect_source(cop,
+ ['default_scope order: "position"'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts a block arg' do
+ inspect_source(cop,
+ ['default_scope { something }'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/rails/has_and_belongs_to_many_spec.rb b/spec/rubocop/cop/rails/has_and_belongs_to_many_spec.rb
new file mode 100644
index 0000000..c5ff430
--- /dev/null
+++ b/spec/rubocop/cop/rails/has_and_belongs_to_many_spec.rb
@@ -0,0 +1,13 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Rails::HasAndBelongsToMany do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for has_and_belongs_to_many' do
+ inspect_source(cop,
+ ['has_and_belongs_to_many :groups'])
+ expect(cop.offenses.size).to eq(1)
+ end
+end
diff --git a/spec/rubocop/cop/rails/output_spec.rb b/spec/rubocop/cop/rails/output_spec.rb
new file mode 100644
index 0000000..e433613
--- /dev/null
+++ b/spec/rubocop/cop/rails/output_spec.rb
@@ -0,0 +1,31 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Rails::Output do
+ subject(:cop) { described_class.new }
+
+ it 'should record an offense for puts statements' do
+ source = ['p "edmond dantes"',
+ 'puts "sinbad"',
+ 'print "abbe busoni"',
+ 'pp "monte cristo"']
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(4)
+ end
+
+ it 'should not record an offense for methods' do
+ source = ['obj.print',
+ 'something.p',
+ 'nothing.pp']
+ inspect_source(cop, source)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'should not record an offense for comments' do
+ source = ['# print "test"',
+ '# p']
+ inspect_source(cop, source)
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/rails/read_write_attribute_spec.rb b/spec/rubocop/cop/rails/read_write_attribute_spec.rb
new file mode 100644
index 0000000..533f32c
--- /dev/null
+++ b/spec/rubocop/cop/rails/read_write_attribute_spec.rb
@@ -0,0 +1,19 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Rails::ReadWriteAttribute do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for read_attribute' do
+ inspect_source(cop, 'res = read_attribute(:test)')
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['read_attribute'])
+ end
+
+ it 'registers an offense for write_attribute' do
+ inspect_source(cop, 'write_attribute(:test, val)')
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['write_attribute'])
+ end
+end
diff --git a/spec/rubocop/cop/rails/scope_args_spec.rb b/spec/rubocop/cop/rails/scope_args_spec.rb
new file mode 100644
index 0000000..cb7d372
--- /dev/null
+++ b/spec/rubocop/cop/rails/scope_args_spec.rb
@@ -0,0 +1,25 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Rails::ScopeArgs do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense a scope with a method arg' do
+ inspect_source(cop,
+ ['scope :active, where(active: true)'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts a lambda arg' do
+ inspect_source(cop,
+ ['scope :active, -> { where(active: true) }'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a proc arg' do
+ inspect_source(cop,
+ ['scope :active, proc { where(active: true) }'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/rails/validation_spec.rb b/spec/rubocop/cop/rails/validation_spec.rb
new file mode 100644
index 0000000..50f55ff
--- /dev/null
+++ b/spec/rubocop/cop/rails/validation_spec.rb
@@ -0,0 +1,21 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Rails::Validation do
+ subject(:cop) { described_class.new }
+
+ described_class::BLACKLIST.each do |validation|
+ it "registers an offense for #{validation}" do
+ inspect_source(cop,
+ ["#{validation} :name"])
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+
+ it 'accepts sexy validations' do
+ inspect_source(cop,
+ ['validates :name'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/severity_spec.rb b/spec/rubocop/cop/severity_spec.rb
new file mode 100644
index 0000000..19baa32
--- /dev/null
+++ b/spec/rubocop/cop/severity_spec.rb
@@ -0,0 +1,113 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Severity do
+ let(:refactor) { described_class.new(:refactor) }
+ let(:convention) { described_class.new(:convention) }
+ let(:warning) { described_class.new(:warning) }
+ let(:error) { described_class.new(:error) }
+ let(:fatal) { described_class.new(:fatal) }
+
+ it 'has a few required attributes' do
+ expect(convention.name).to eq(:convention)
+ end
+
+ it 'overrides #to_s' do
+ expect(convention.to_s).to eq('convention')
+ end
+
+ it 'redefines == to compare severities' do
+ expect(convention).to eq(:convention)
+ expect(convention).to eq(described_class.new(:convention))
+ expect(convention).not_to eq(:warning)
+ end
+
+ it 'is frozen' do
+ expect(convention).to be_frozen
+ end
+
+ describe '#code' do
+ describe 'refactor' do
+ it { expect(refactor.code).to eq('R') }
+ end
+
+ describe 'convention' do
+ it { expect(convention.code).to eq('C') }
+ end
+
+ describe 'warning' do
+ it { expect(warning.code).to eq('W') }
+ end
+
+ describe 'error' do
+ it { expect(error.code).to eq('E') }
+ end
+
+ describe 'fatal' do
+ it { expect(fatal.code).to eq('F') }
+ end
+ end
+
+ describe '#level' do
+ describe 'refactor' do
+ it { expect(refactor.level).to eq(1) }
+ end
+
+ describe 'convention' do
+ it { expect(convention.level).to eq(2) }
+ end
+
+ describe 'warning' do
+ it { expect(warning.level).to eq(3) }
+ end
+
+ describe 'error' do
+ it { expect(error.level).to eq(4) }
+ end
+
+ describe 'fatal' do
+ it { expect(fatal.level).to eq(5) }
+ end
+ end
+
+ describe 'constructs from code' do
+ describe 'R' do
+ it { expect(Rubocop::Cop::Severity.new('R')).to eq(refactor) }
+ end
+
+ describe 'C' do
+ it { expect(Rubocop::Cop::Severity.new('C')).to eq(convention) }
+ end
+
+ describe 'W' do
+ it { expect(Rubocop::Cop::Severity.new('W')).to eq(warning) }
+ end
+
+ describe 'E' do
+ it { expect(Rubocop::Cop::Severity.new('E')).to eq(error) }
+ end
+
+ describe 'F' do
+ it { expect(Rubocop::Cop::Severity.new('F')).to eq(fatal) }
+ end
+ end
+
+ describe 'Comparable' do
+ describe 'refactor' do
+ it { expect(refactor).to be < convention }
+ end
+
+ describe 'convention' do
+ it { expect(convention).to be < warning }
+ end
+
+ describe 'warning' do
+ it { expect(warning).to be < error }
+ end
+
+ describe 'error' do
+ it { expect(error).to be < fatal }
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/access_modifier_indentation_spec.rb b/spec/rubocop/cop/style/access_modifier_indentation_spec.rb
new file mode 100644
index 0000000..f32e39f
--- /dev/null
+++ b/spec/rubocop/cop/style/access_modifier_indentation_spec.rb
@@ -0,0 +1,361 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::AccessModifierIndentation, :config do
+ subject(:cop) { described_class.new(config) }
+
+ context 'when EnforcedStyle is set to indent' do
+ let(:cop_config) { { 'EnforcedStyle' => 'indent' } }
+
+ it 'registers an offense for misaligned private' do
+ inspect_source(cop,
+ ['class Test',
+ '',
+ 'private',
+ '',
+ ' def test; end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Indent access modifiers like `private`.'])
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' => 'outdent')
+ end
+
+ it 'registers an offense for misaligned private in module' do
+ inspect_source(cop,
+ ['module Test',
+ '',
+ ' private',
+ '',
+ ' def test; end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages).to eq(['Indent access modifiers like `private`.'])
+ # Not aligned according to `indent` or `outdent` style:
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'registers an offense for correct + opposite alignment' do
+ inspect_source(cop,
+ ['module Test',
+ '',
+ ' public',
+ '',
+ 'private',
+ '',
+ ' def test; end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages).to eq(['Indent access modifiers like `private`.'])
+ # No EnforcedStyle can allow both aligments:
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'registers an offense for opposite + correct alignment' do
+ inspect_source(cop,
+ ['module Test',
+ '',
+ 'public',
+ '',
+ ' private',
+ '',
+ ' def test; end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages).to eq(['Indent access modifiers like `public`.'])
+ # No EnforcedStyle can allow both aligments:
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'registers an offense for misaligned private in singleton class' do
+ inspect_source(cop,
+ ['class << self',
+ '',
+ 'private',
+ '',
+ ' def test; end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Indent access modifiers like `private`.'])
+ end
+
+ it 'registers an offense for misaligned private in class ' \
+ 'defined with Class.new' do
+ inspect_source(cop,
+ ['Test = Class.new do',
+ '',
+ 'private',
+ '',
+ ' def test; end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Indent access modifiers like `private`.'])
+ end
+
+ it 'registers an offense for misaligned private in module ' \
+ 'defined with Module.new' do
+ inspect_source(cop,
+ ['Test = Module.new do',
+ '',
+ 'private',
+ '',
+ ' def test; end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Indent access modifiers like `private`.'])
+ end
+
+ it 'registers an offense for misaligned protected' do
+ inspect_source(cop,
+ ['class Test',
+ '',
+ 'protected',
+ '',
+ ' def test; end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Indent access modifiers like `protected`.'])
+ end
+
+ it 'accepts properly indented private' do
+ inspect_source(cop,
+ ['class Test',
+ '',
+ ' private',
+ '',
+ ' def test; end',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts properly indented protected' do
+ inspect_source(cop,
+ ['class Test',
+ '',
+ ' protected',
+ '',
+ ' def test; end',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an empty class' do
+ inspect_source(cop,
+ ['class Test',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'handles properly nested classes' do
+ inspect_source(cop,
+ ['class Test',
+ '',
+ ' class Nested',
+ '',
+ ' private',
+ '',
+ ' def a; end',
+ ' end',
+ '',
+ ' protected',
+ '',
+ ' def test; end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Indent access modifiers like `private`.'])
+ end
+
+ it 'auto-corrects incorrectly indented access modifiers' do
+ corrected = autocorrect_source(cop, ['class Test',
+ '',
+ 'public',
+ ' private',
+ ' protected',
+ '',
+ ' def test; end',
+ 'end'])
+ expect(corrected).to eq(['class Test',
+ '',
+ ' public',
+ ' private',
+ ' protected',
+ '',
+ ' def test; end',
+ 'end'].join("\n"))
+ end
+ end
+
+ context 'when EnforcedStyle is set to outdent' do
+ let(:cop_config) { { 'EnforcedStyle' => 'outdent' } }
+ let(:indent_msg) { 'Outdent access modifiers like `private`.' }
+
+ it 'registers offense for private indented to method depth in a class' do
+ inspect_source(cop,
+ ['class Test',
+ '',
+ ' private',
+ '',
+ ' def test; end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages).to eq([indent_msg])
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' => 'indent')
+ end
+
+ it 'registers offense for private indented to method depth in a module' do
+ inspect_source(cop,
+ ['module Test',
+ '',
+ ' private',
+ '',
+ ' def test; end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages).to eq([indent_msg])
+ end
+
+ it 'registers offense for private indented to method depth in singleton' \
+ 'class' do
+ inspect_source(cop,
+ ['class << self',
+ '',
+ ' private',
+ '',
+ ' def test; end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages).to eq([indent_msg])
+ end
+
+ it 'registers offense for private indented to method depth in class ' \
+ 'defined with Class.new' do
+ inspect_source(cop,
+ ['Test = Class.new do',
+ '',
+ ' private',
+ '',
+ ' def test; end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages).to eq([indent_msg])
+ end
+
+ it 'registers offense for private indented to method depth in module ' \
+ 'defined with Module.new' do
+ inspect_source(cop,
+ ['Test = Module.new do',
+ '',
+ ' private',
+ '',
+ ' def test; end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages).to eq([indent_msg])
+ end
+
+ it 'accepts private indented to the containing class indent level' do
+ inspect_source(cop,
+ ['class Test',
+ '',
+ 'private',
+ '',
+ ' def test; end',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts protected indented to the containing class indent level' do
+ inspect_source(cop,
+ ['class Test',
+ '',
+ 'protected',
+ '',
+ ' def test; end',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'handles properly nested classes' do
+ inspect_source(cop,
+ ['class Test',
+ '',
+ ' class Nested',
+ '',
+ ' private',
+ '',
+ ' def a; end',
+ ' end',
+ '',
+ 'protected',
+ '',
+ ' def test; end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages).to eq([indent_msg])
+ end
+
+ it 'auto-corrects incorrectly indented access modifiers' do
+ corrected = autocorrect_source(cop, ['module M',
+ ' class Test',
+ '',
+ 'public',
+ ' private',
+ ' protected',
+ '',
+ ' def test; end',
+ ' end',
+ 'end'])
+ expect(corrected).to eq(['module M',
+ ' class Test',
+ '',
+ ' public',
+ ' private',
+ ' protected',
+ '',
+ ' def test; end',
+ ' end',
+ 'end'].join("\n"))
+ end
+
+ it 'auto-corrects private in complicated case' do
+ corrected = autocorrect_source(cop, ['class Hello',
+ ' def foo',
+ " 'hi'",
+ ' end',
+ '',
+ ' def bar',
+ ' Module.new do',
+ '',
+ ' private',
+ '',
+ ' def hi',
+ " 'bye'",
+ ' end',
+ ' end',
+ ' end',
+ 'end'])
+ expect(corrected).to eq(['class Hello',
+ ' def foo',
+ " 'hi'",
+ ' end',
+ '',
+ ' def bar',
+ ' Module.new do',
+ '',
+ ' private',
+ '',
+ ' def hi',
+ " 'bye'",
+ ' end',
+ ' end',
+ ' end',
+ 'end'].join("\n"))
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/accessor_method_name_spec.rb b/spec/rubocop/cop/style/accessor_method_name_spec.rb
new file mode 100644
index 0000000..e3c02e0
--- /dev/null
+++ b/spec/rubocop/cop/style/accessor_method_name_spec.rb
@@ -0,0 +1,81 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::AccessorMethodName do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for method get_... with no args' do
+ inspect_source(cop, ['def get_attr',
+ ' # ...',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['get_attr'])
+ end
+
+ it 'registers an offense for singleton method get_... with no args' do
+ inspect_source(cop, ['def self.get_attr',
+ ' # ...',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['get_attr'])
+ end
+
+ it 'accepts method get_something with args' do
+ inspect_source(cop, ['def get_something(arg)',
+ ' # ...',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts singleton method get_something with args' do
+ inspect_source(cop, ['def self.get_something(arg)',
+ ' # ...',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for method set_something with one arg' do
+ inspect_source(cop, ['def set_attr(arg)',
+ ' # ...',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['set_attr'])
+ end
+
+ it 'registers an offense for singleton method set_... with one args' do
+ inspect_source(cop, ['def self.set_attr(arg)',
+ ' # ...',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['set_attr'])
+ end
+
+ it 'accepts method set_something with no args' do
+ inspect_source(cop, ['def set_something',
+ ' # ...',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts singleton method set_something with no args' do
+ inspect_source(cop, ['def self.set_something',
+ ' # ...',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts method set_something with two args' do
+ inspect_source(cop, ['def set_something(arg1, arg2)',
+ ' # ...',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts singleton method set_something with two args' do
+ inspect_source(cop, ['def self.get_something(arg1, arg2)',
+ ' # ...',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/alias_spec.rb b/spec/rubocop/cop/style/alias_spec.rb
new file mode 100644
index 0000000..ca92d13
--- /dev/null
+++ b/spec/rubocop/cop/style/alias_spec.rb
@@ -0,0 +1,59 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::Alias do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for alias with symbol args' do
+ inspect_source(cop,
+ ['alias :ala :bala'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Use `alias_method` instead of `alias`.'])
+ end
+
+ it 'autocorrects alias with symbol args' do
+ corrected = autocorrect_source(cop, ['alias :ala :bala'])
+ expect(corrected).to eq 'alias_method :ala, :bala'
+ end
+
+ it 'registers an offense for alias with bareword args' do
+ inspect_source(cop,
+ ['alias ala bala'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Use `alias_method` instead of `alias`.'])
+ end
+
+ it 'autocorrects alias with bareword args' do
+ corrected = autocorrect_source(cop, ['alias ala bala'])
+ expect(corrected).to eq 'alias_method :ala, :bala'
+ end
+
+ it 'does not register an offense for alias_method' do
+ inspect_source(cop,
+ ['alias_method :ala, :bala'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for :alias' do
+ inspect_source(cop,
+ ['[:alias, :ala, :bala]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for alias with gvars' do
+ inspect_source(cop,
+ ['alias $ala $bala'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts alias in an instance_exec block' do
+ inspect_source(cop,
+ ['cli.instance_exec do',
+ ' alias :old_trap_interrupt :trap_interrupt',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/align_array_spec.rb b/spec/rubocop/cop/style/align_array_spec.rb
new file mode 100644
index 0000000..6d713c1
--- /dev/null
+++ b/spec/rubocop/cop/style/align_array_spec.rb
@@ -0,0 +1,91 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::AlignArray do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for misaligned array elements' do
+ inspect_source(cop, ['array = [',
+ ' a,',
+ ' b,',
+ ' c,',
+ ' d',
+ ']'])
+ expect(cop.messages).to eq(['Align the elements of an array ' \
+ 'literal if they span more than ' \
+ 'one line.'] * 2)
+ expect(cop.highlights).to eq(%w(b d))
+ end
+
+ it 'accepts aligned array keys' do
+ inspect_source(cop, ['array = [',
+ ' a,',
+ ' b,',
+ ' c,',
+ ' d',
+ ']'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts single line array' do
+ inspect_source(cop, 'array = [ a, b ]')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts several elements per line' do
+ inspect_source(cop, ['array = [ a, b,',
+ ' c, d ]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects alignment' do
+ new_source = autocorrect_source(cop, ['array = [',
+ ' a,',
+ ' b,',
+ ' c,',
+ ' d',
+ ']'])
+ expect(new_source).to eq(['array = [',
+ ' a,',
+ ' b,',
+ ' c,',
+ ' d',
+ ']'].join("\n"))
+ end
+
+ it 'auto-corrects array within array with too much indentation' do
+ original_source = ['[:l1,',
+ ' [:l2,',
+ '',
+ ' [:l3,',
+ ' [:l4]]]]']
+ new_source = autocorrect_source(cop, original_source)
+ expect(new_source).to eq(['[:l1,',
+ ' [:l2,',
+ '',
+ ' [:l3,',
+ ' [:l4]]]]'].join("\n"))
+ end
+
+ it 'auto-corrects array within array with too little indentation' do
+ original_source = ['[:l1,',
+ '[:l2,',
+ '',
+ ' [:l3,',
+ ' [:l4]]]]']
+ new_source = autocorrect_source(cop, original_source)
+ expect(new_source).to eq(['[:l1,',
+ ' [:l2,',
+ '',
+ ' [:l3,',
+ ' [:l4]]]]'].join("\n"))
+ end
+
+ it 'auto-corrects only elements that begin a line' do
+ original_source = ['array = [:bar, {',
+ ' whiz: 2, bang: 3 }, option: 3]']
+ new_source = autocorrect_source(cop, original_source)
+ expect(new_source).to eq(original_source.join("\n"))
+ end
+end
diff --git a/spec/rubocop/cop/style/align_hash_spec.rb b/spec/rubocop/cop/style/align_hash_spec.rb
new file mode 100644
index 0000000..e7c0966
--- /dev/null
+++ b/spec/rubocop/cop/style/align_hash_spec.rb
@@ -0,0 +1,391 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::AlignHash, :config do
+ subject(:cop) { described_class.new(config) }
+
+ shared_examples 'not on separate lines' do
+ it 'accepts single line hash' do
+ inspect_source(cop, 'func(a: 0, bb: 1)')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts several pairs per line' do
+ inspect_source(cop, ['func(a: 1, bb: 2,',
+ ' ccc: 3, dddd: 4)'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'always inspect last argument hash' do
+ let(:cop_config) do
+ {
+ 'EnforcedLastArgumentHashStyle' => 'always_inspect'
+ }
+ end
+
+ it 'registers offence for misaligned keys in implicit hash' do
+ inspect_source(cop, ['func(a: 0,',
+ ' b: 1)'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers offence for misaligned keys in explicit hash' do
+ inspect_source(cop, ['func({a: 0,',
+ ' b: 1})'])
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+
+ context 'always ignore last argument hash' do
+ let(:cop_config) do
+ {
+ 'EnforcedLastArgumentHashStyle' => 'always_ignore'
+ }
+ end
+
+ it 'accepts misaligned keys in implicit hash' do
+ inspect_source(cop, ['func(a: 0,',
+ ' b: 1)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts misaligned keys in explicit hash' do
+ inspect_source(cop, ['func({a: 0,',
+ ' b: 1})'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'ignore implicit last argument hash' do
+ let(:cop_config) do
+ {
+ 'EnforcedLastArgumentHashStyle' => 'ignore_implicit'
+ }
+ end
+
+ it 'accepts misaligned keys in implicit hash' do
+ inspect_source(cop, ['func(a: 0,',
+ ' b: 1)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers offence for misaligned keys in explicit hash' do
+ inspect_source(cop, ['func({a: 0,',
+ ' b: 1})'])
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+
+ context 'ignore explicit last argument hash' do
+ let(:cop_config) do
+ {
+ 'EnforcedLastArgumentHashStyle' => 'ignore_explicit'
+ }
+ end
+
+ it 'registers offence for misaligned keys in implicit hash' do
+ inspect_source(cop, ['func(a: 0,',
+ ' b: 1)'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts misaligned keys in explicit hash' do
+ inspect_source(cop, ['func({a: 0,',
+ ' b: 1})'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ let(:cop_config) do
+ {
+ 'EnforcedHashRocketStyle' => 'key',
+ 'EnforcedColonStyle' => 'key'
+ }
+ end
+
+ context 'with default configuration' do
+ it 'registers an offense for misaligned hash keys' do
+ inspect_source(cop, ['hash1 = {',
+ ' a: 0,',
+ ' bb: 1',
+ '}',
+ 'hash2 = {',
+ " 'ccc' => 2,",
+ " 'dddd' => 2",
+ '}'])
+ expect(cop.messages).to eq(['Align the elements of a hash ' \
+ 'literal if they span more than ' \
+ 'one line.'] * 2)
+ expect(cop.highlights).to eq(['bb: 1',
+ "'dddd' => 2"])
+ end
+
+ it 'accepts aligned hash keys' do
+ inspect_source(cop, ['hash1 = {',
+ ' a: 0,',
+ ' bb: 1,',
+ '}',
+ 'hash2 = {',
+ " 'ccc' => 2,",
+ " 'dddd' => 2",
+ '}'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for separator alignment' do
+ inspect_source(cop, ['hash = {',
+ " 'a' => 0,",
+ " 'bbb' => 1",
+ '}'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(["'bbb' => 1"])
+ end
+
+ context 'with implicit hash as last argument' do
+ it 'registers an offense for misaligned hash keys' do
+ inspect_source(cop, ['func(a: 0,',
+ ' b: 1)'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for right alignment of keys' do
+ inspect_source(cop, ['func(a: 0,',
+ ' bbb: 1)'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts aligned hash keys' do
+ inspect_source(cop, ['func(a: 0,',
+ ' b: 1)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an empty hash' do
+ inspect_source(cop, 'h = {}')
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ it 'auto-corrects alignment' do
+ new_source = autocorrect_source(cop, ['hash1 = { a: 0,',
+ ' bb: 1,',
+ ' ccc: 2 }',
+ 'hash2 = { :a => 0,',
+ ' :bb => 1,',
+ ' :ccc =>2 }'
+ ])
+ expect(new_source).to eq(['hash1 = { a: 0,',
+ ' bb: 1,',
+ ' ccc: 2 }',
+ 'hash2 = { :a => 0,',
+ ' :bb => 1,',
+ # Separator and value are not corrected
+ # in 'key' mode.
+ ' :ccc =>2 }'].join("\n"))
+ end
+ end
+
+ include_examples 'not on separate lines'
+
+ context 'with table alignment configuration' do
+ let(:cop_config) do
+ {
+ 'EnforcedHashRocketStyle' => 'table',
+ 'EnforcedColonStyle' => 'table'
+ }
+ end
+
+ include_examples 'not on separate lines'
+
+ it 'accepts aligned hash keys' do
+ inspect_source(cop, ['hash1 = {',
+ " 'a' => 0,",
+ " 'bbb' => 1",
+ '}',
+ 'hash2 = {',
+ ' a: 0,',
+ ' bbb: 1',
+ '}'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an empty hash' do
+ inspect_source(cop, 'h = {}')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a multiline array of single line hashes' do
+ inspect_source(cop, ['def self.scenarios_order',
+ ' [',
+ ' { before: %w( l k ) },',
+ ' { ending: %w( m l ) },',
+ ' { starting: %w( m n ) },',
+ ' { after: %w( n o ) }',
+ ' ]',
+ ' end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for misaligned hash values' do
+ inspect_source(cop, ['hash1 = {',
+ " 'a' => 0,",
+ " 'bbb' => 1",
+ '}',
+ 'hash2 = {',
+ ' a: 0,',
+ ' bbb:1',
+ '}'
+ ])
+ expect(cop.highlights).to eq(["'a' => 0",
+ 'bbb:1'])
+ end
+
+ it 'registers an offense for misaligned hash rockets' do
+ inspect_source(cop, ['hash = {',
+ " 'a' => 0,",
+ " 'bbb' => 1",
+ '}'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'auto-corrects alignment' do
+ new_source = autocorrect_source(cop, ['hash1 = { a: 0,',
+ ' bb: 1,',
+ ' ccc: 2 }',
+ "hash2 = { 'a' => 0,",
+ " 'bb' => 1,",
+ " 'ccc' =>2 }"])
+ expect(new_source).to eq(['hash1 = { a: 0,',
+ ' bb: 1,',
+ ' ccc: 2 }',
+ "hash2 = { 'a' => 0,",
+ " 'bb' => 1,",
+ " 'ccc' => 2 }"].join("\n"))
+ end
+ end
+
+ context 'with table+separator alignment configuration' do
+ let(:cop_config) do
+ {
+ 'EnforcedHashRocketStyle' => 'table',
+ 'EnforcedColonStyle' => 'separator'
+ }
+ end
+
+ it 'accepts a single method argument entry with colon' do
+ inspect_source(cop, ['merge(parent: nil)'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'with invalid configuration' do
+ let(:cop_config) do
+ {
+ 'EnforcedHashRocketStyle' => 'junk',
+ 'EnforcedColonStyle' => 'junk'
+ }
+ end
+ it 'fails' do
+ src = ['hash = {',
+ ' a: 0,',
+ ' bb: 1',
+ '}']
+ expect { inspect_source(cop, src) }.to raise_error(RuntimeError)
+ end
+ end
+
+ context 'with separator alignment configuration' do
+ let(:cop_config) do
+ {
+ 'EnforcedHashRocketStyle' => 'separator',
+ 'EnforcedColonStyle' => 'separator'
+ }
+ end
+
+ it 'accepts aligned hash keys' do
+ inspect_source(cop, ['hash1 = {',
+ ' a: 0,',
+ ' bbb: 1',
+ '}',
+ 'hash2 = {',
+ " 'a' => 0,",
+ " 'bbb' => 1",
+ '}'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an empty hash' do
+ inspect_source(cop, 'h = {}')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for misaligned hash values' do
+ inspect_source(cop, ['hash = {',
+ " 'a' => 0,",
+ " 'bbb' => 1",
+ '}'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for misaligned hash rockets' do
+ inspect_source(cop, ['hash = {',
+ " 'a' => 0,",
+ " 'bbb' => 1",
+ '}'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ include_examples 'not on separate lines'
+
+ it 'auto-corrects alignment' do
+ new_source = autocorrect_source(cop, ['hash1 = { a: 0,',
+ ' bb: 1,',
+ ' ccc: 2 }',
+ 'hash2 = { a => 0,',
+ ' bb => 1,',
+ ' ccc =>2 }'])
+ expect(new_source).to eq(['hash1 = { a: 0,',
+ ' bb: 1,',
+ ' ccc: 2 }',
+ 'hash2 = { a => 0,',
+ ' bb => 1,',
+ ' ccc => 2 }'].join("\n"))
+ end
+ end
+
+ context 'with different settings for => and :' do
+ let(:cop_config) do
+ {
+ 'EnforcedHashRocketStyle' => 'key',
+ 'EnforcedColonStyle' => 'separator'
+ }
+ end
+
+ it 'registers offenses for misaligned entries' do
+ inspect_source(cop, ['hash1 = {',
+ ' a: 0,',
+ ' bbb: 1',
+ '}',
+ 'hash2 = {',
+ " 'a' => 0,",
+ " 'bbb' => 1",
+ '}'])
+ expect(cop.highlights).to eq(['bbb: 1', "'bbb' => 1"])
+ end
+
+ it 'accepts aligned entries' do
+ inspect_source(cop, ['hash1 = {',
+ ' a: 0,',
+ ' bbb: 1',
+ '}',
+ 'hash2 = {',
+ " 'a' => 0,",
+ " 'bbb' => 1",
+ '}'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/align_parameters_spec.rb b/spec/rubocop/cop/style/align_parameters_spec.rb
new file mode 100644
index 0000000..f8859a9
--- /dev/null
+++ b/spec/rubocop/cop/style/align_parameters_spec.rb
@@ -0,0 +1,295 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::AlignParameters, :config do
+ subject(:cop) { described_class.new(config) }
+
+ context 'aligned with first parameter' do
+ let(:cop_config) do
+ {
+ 'EnforcedStyle' => 'with_first_parameter'
+ }
+ end
+
+ it 'registers an offense for parameters with single indent' do
+ inspect_source(cop, ['function(a,',
+ ' if b then c else d end)'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['if b then c else d end'])
+ end
+
+ it 'registers an offense for parameters with double indent' do
+ inspect_source(cop, ['function(a,',
+ ' if b then c else d end)'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts multiline []= method call' do
+ inspect_source(cop, ['Test.config["something"] =',
+ ' true'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts correctly aligned parameters' do
+ inspect_source(cop, ['function(a,',
+ ' 0, 1,',
+ ' (x + y),',
+ ' if b then c else d end)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts calls that only span one line' do
+ inspect_source(cop, ['find(path, s, @special[sexp[0]])'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't get confused by a symbol argument" do
+ inspect_source(cop, ['add_offense(index,',
+ ' MSG % kind)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't get confused by splat operator" do
+ inspect_source(cop, ['func1(*a,',
+ ' *b,',
+ ' c)',
+ 'func2(a,',
+ ' *b,',
+ ' c)',
+ 'func3(*a)'
+ ])
+ expect(cop.offenses.map(&:to_s))
+ .to eq(['C: 5: 6: Align the parameters of a method call if ' \
+ 'they span more than one line.'])
+ expect(cop.highlights).to eq(['*b'])
+ end
+
+ it "doesn't get confused by extra comma at the end" do
+ inspect_source(cop, ['func1(a,',
+ ' b,)'])
+ expect(cop.offenses.map(&:to_s))
+ .to eq(['C: 2: 6: Align the parameters of a method call if ' \
+ 'they span more than one line.'])
+ expect(cop.highlights).to eq(['b'])
+ end
+
+ it 'can handle a correctly aligned string literal as first argument' do
+ inspect_source(cop, ['add_offense(x,',
+ ' a)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'can handle a string literal as other argument' do
+ inspect_source(cop, ['add_offense(',
+ ' "", a)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't get confused by a line break inside a parameter" do
+ inspect_source(cop, ['read(path, { headers: true,',
+ ' converters: :numeric })'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't get confused by symbols with embedded expressions" do
+ inspect_source(cop, ['send(:"#{name}_comments_path")'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't get confused by regexen with embedded expressions" do
+ inspect_source(cop, ['a(/#{name}/)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts braceless hashes' do
+ inspect_source(cop, ['run(collection, :entry_name => label,',
+ ' :paginator => paginator)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts the first parameter being on a new row' do
+ inspect_source(cop, [' match(',
+ ' a,',
+ ' b',
+ ' )'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'can handle heredoc strings' do
+ inspect_source(cop, ['class_eval(<<-EOS, __FILE__, __LINE__ + 1)',
+ ' def run_#{name}_callbacks(*args)',
+ ' a = 1',
+ ' return value',
+ ' end',
+ ' EOS'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'can handle a method call within a method call' do
+ inspect_source(cop, ['a(a1,',
+ ' b(b1,',
+ ' b2),',
+ ' a2)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'can handle a call embedded in a string' do
+ inspect_source(cop, ['model("#{index(name)}", child)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'can handle do-end' do
+ inspect_source(cop, [' run(lambda do |e|',
+ " w = e['warden']",
+ ' end)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'can handle a call with a block inside another call' do
+ src = ['new(table_name,',
+ ' exec_query("info(\'#{row[\'name\']}\')").map { |col|',
+ " col['name']",
+ ' })']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'can handle a ternary condition with a block reference' do
+ inspect_source(cop, ['cond ? a : func(&b)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'can handle parentheses used with no parameters' do
+ inspect_source(cop, ['func()'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'can handle a multiline hash as second parameter' do
+ inspect_source(cop, ['tag(:input, {',
+ ' :value => value',
+ '})'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'can handle method calls without parentheses' do
+ inspect_source(cop, ['a(b c, d)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'can handle other method calls without parentheses' do
+ src = ['chars(Unicode.apply_mapping @wrapped_string, :uppercase)']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects alignment' do
+ new_source = autocorrect_source(cop, ['func(a,',
+ ' b,',
+ 'c)'])
+ expect(new_source).to eq(['func(a,',
+ ' b,',
+ ' c)'].join("\n"))
+ end
+
+ it 'auto-corrects each line of a multi-line parameter to the right' do
+ new_source =
+ autocorrect_source(cop,
+ ['create :transaction, :closed,',
+ ' account: account,',
+ ' open_price: 1.29,',
+ ' close_price: 1.30'])
+ expect(new_source)
+ .to eq(['create :transaction, :closed,',
+ ' account: account,',
+ ' open_price: 1.29,',
+ ' close_price: 1.30'].join("\n"))
+ end
+
+ it 'auto-corrects each line of a multi-line parameter to the left' do
+ new_source =
+ autocorrect_source(cop,
+ ['create :transaction, :closed,',
+ ' account: account,',
+ ' open_price: 1.29,',
+ ' close_price: 1.30'])
+ expect(new_source)
+ .to eq(['create :transaction, :closed,',
+ ' account: account,',
+ ' open_price: 1.29,',
+ ' close_price: 1.30'].join("\n"))
+ end
+
+ it 'auto-corrects only parameters that begin a line' do
+ original_source = ['foo(:bar, {',
+ ' whiz: 2, bang: 3 }, option: 3)']
+ new_source = autocorrect_source(cop, original_source)
+ expect(new_source).to eq(original_source.join("\n"))
+ end
+ end
+
+ context 'aligned with fixed indentation' do
+ let(:cop_config) do
+ {
+ 'EnforcedStyle' => 'with_fixed_indentation'
+ }
+ end
+
+ let(:correct_source) do
+ [
+ 'create :transaction, :closed,',
+ ' account: account,',
+ ' open_price: 1.29,',
+ ' close_price: 1.30'
+ ]
+ end
+
+ it 'does not autocorrect correct source' do
+ expect(autocorrect_source(cop, correct_source))
+ .to eq(correct_source.join("\n"))
+ end
+
+ it 'autocorrects by outdenting when indented too far' do
+ original_source = [
+ 'create :transaction, :closed,',
+ ' account: account,',
+ ' open_price: 1.29,',
+ ' close_price: 1.30'
+ ]
+
+ expect(autocorrect_source(cop, original_source))
+ .to eq(correct_source.join("\n"))
+ end
+
+ it 'autocorrects by indenting when not indented' do
+ original_source = [
+ 'create :transaction, :closed,',
+ 'account: account,',
+ 'open_price: 1.29,',
+ 'close_price: 1.30'
+ ]
+
+ expect(autocorrect_source(cop, original_source))
+ .to eq(correct_source.join("\n"))
+ end
+
+ it 'autocorrects when first line is indented' do
+ original_source = [
+ ' create :transaction, :closed,',
+ ' account: account,',
+ ' open_price: 1.29,',
+ ' close_price: 1.30'
+ ]
+
+ correct_source = [
+ ' create :transaction, :closed,',
+ ' account: account,',
+ ' open_price: 1.29,',
+ ' close_price: 1.30'
+ ]
+
+ expect(autocorrect_source(cop, original_source))
+ .to eq(correct_source.join("\n"))
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/and_or_spec.rb b/spec/rubocop/cop/style/and_or_spec.rb
new file mode 100644
index 0000000..c16b158
--- /dev/null
+++ b/spec/rubocop/cop/style/and_or_spec.rb
@@ -0,0 +1,57 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::AndOr do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for OR' do
+ inspect_source(cop,
+ ['test if a or b'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages).to eq(['Use `||` instead of `or`.'])
+ end
+
+ it 'registers an offense for AND' do
+ inspect_source(cop,
+ ['test if a and b'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages).to eq(['Use `&&` instead of `and`.'])
+ end
+
+ it 'accepts ||' do
+ inspect_source(cop,
+ ['test if a || b'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts &&' do
+ inspect_source(cop,
+ ['test if a && b'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects "and" with &&' do
+ new_source = autocorrect_source(cop, 'true and false')
+ expect(new_source).to eq('true && false')
+ end
+
+ it 'auto-corrects "or" with ||' do
+ new_source = autocorrect_source(cop, ['x = 12345',
+ 'true or false'])
+ expect(new_source).to eq(['x = 12345',
+ 'true || false'].join("\n"))
+ end
+
+ it 'leaves *or* as is if auto-correction changes the meaning' do
+ src = "teststring.include? 'a' or teststring.include? 'b'"
+ new_source = autocorrect_source(cop, src)
+ expect(new_source).to eq(src)
+ end
+
+ it 'leaves *and* as is if auto-correction changes the meaning' do
+ src = 'x = a + b and return x'
+ new_source = autocorrect_source(cop, src)
+ expect(new_source).to eq(src)
+ end
+end
diff --git a/spec/rubocop/cop/style/array_join_spec.rb b/spec/rubocop/cop/style/array_join_spec.rb
new file mode 100644
index 0000000..95e4671
--- /dev/null
+++ b/spec/rubocop/cop/style/array_join_spec.rb
@@ -0,0 +1,29 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::ArrayJoin do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for an array followed by string' do
+ inspect_source(cop,
+ ['%w(one two three) * ", "'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'does not register an offense for numbers' do
+ inspect_source(cop,
+ ['%w(one two three) * 4'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for ambiguous cases' do
+ inspect_source(cop,
+ ['test * ", "'])
+ expect(cop.offenses).to be_empty
+
+ inspect_source(cop,
+ ['%w(one two three) * test'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/ascii_comments_spec.rb b/spec/rubocop/cop/style/ascii_comments_spec.rb
new file mode 100644
index 0000000..83b7134
--- /dev/null
+++ b/spec/rubocop/cop/style/ascii_comments_spec.rb
@@ -0,0 +1,22 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::AsciiComments do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for a comment with non-ascii chars' do
+ inspect_source(cop,
+ ['# encoding: utf-8',
+ '# 这是什么?'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Use only ascii symbols in comments.'])
+ end
+
+ it 'accepts comments with only ascii chars' do
+ inspect_source(cop,
+ ['# AZaz1@$%~,;*_`|'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/ascii_identifiers_spec.rb b/spec/rubocop/cop/style/ascii_identifiers_spec.rb
new file mode 100644
index 0000000..9e51d34
--- /dev/null
+++ b/spec/rubocop/cop/style/ascii_identifiers_spec.rb
@@ -0,0 +1,36 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::AsciiIdentifiers do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for a variable name with non-ascii chars' do
+ inspect_source(cop,
+ ['# encoding: utf-8',
+ 'älg = 1'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Use only ascii symbols in identifiers.'])
+ end
+
+ it 'accepts identifiers with only ascii chars' do
+ inspect_source(cop,
+ ['x.empty?'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not get confused by a byte order mark' do
+ bom = "\xef\xbb\xbf"
+ inspect_source(cop,
+ [bom + '# encoding: utf-8',
+ "puts 'foo'"])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not get confused by an empty file' do
+ inspect_source(cop,
+ [''])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/attr_spec.rb b/spec/rubocop/cop/style/attr_spec.rb
new file mode 100644
index 0000000..584122e
--- /dev/null
+++ b/spec/rubocop/cop/style/attr_spec.rb
@@ -0,0 +1,19 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::Attr do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense attr' do
+ inspect_source(cop, ['class SomeClass',
+ ' attr :name',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'auto-corrects attr to attr_reader' do
+ new_source = autocorrect_source(cop, 'attr')
+ expect(new_source).to eq('attr_reader')
+ end
+end
diff --git a/spec/rubocop/cop/style/begin_block_spec.rb b/spec/rubocop/cop/style/begin_block_spec.rb
new file mode 100644
index 0000000..13dbba0
--- /dev/null
+++ b/spec/rubocop/cop/style/begin_block_spec.rb
@@ -0,0 +1,13 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::BeginBlock do
+ subject(:cop) { described_class.new }
+
+ it 'reports an offense for a BEGIN block' do
+ src = ['BEGIN { test }']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ end
+end
diff --git a/spec/rubocop/cop/style/block_comments_spec.rb b/spec/rubocop/cop/style/block_comments_spec.rb
new file mode 100644
index 0000000..3dac5b4
--- /dev/null
+++ b/spec/rubocop/cop/style/block_comments_spec.rb
@@ -0,0 +1,21 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::BlockComments do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for block comments' do
+ inspect_source(cop,
+ ['=begin',
+ 'comment',
+ '=end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts regular comments' do
+ inspect_source(cop,
+ ['# comment'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/block_nesting_spec.rb b/spec/rubocop/cop/style/block_nesting_spec.rb
new file mode 100644
index 0000000..900fbed
--- /dev/null
+++ b/spec/rubocop/cop/style/block_nesting_spec.rb
@@ -0,0 +1,156 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::BlockNesting, :config do
+ subject(:cop) { described_class.new(config) }
+ let(:cop_config) { { 'Max' => 2 } }
+
+ it 'accepts `Max` levels of nesting' do
+ source = ['if a',
+ ' if b',
+ ' puts b',
+ ' end',
+ 'end']
+ expect_nesting_offenses(source, [])
+ end
+
+ it 'registers an offense for `Max + 1` levels of `if` nesting' do
+ source = ['if a',
+ ' if b',
+ ' if c',
+ ' puts c',
+ ' end',
+ ' end',
+ 'end']
+ expect_nesting_offenses(source, [3])
+ end
+
+ it 'registers one offense for `Max + 2` levels of `if` nesting' do
+ source = ['if a',
+ ' if b',
+ ' if c',
+ ' if d',
+ ' puts d',
+ ' end',
+ ' end',
+ ' end',
+ 'end']
+ expect_nesting_offenses(source, [3], 4)
+ end
+
+ it 'registers 2 offenses' do
+ source = ['if a',
+ ' if b',
+ ' if c',
+ ' puts c',
+ ' end',
+ ' end',
+ ' if d',
+ ' if e',
+ ' puts e',
+ ' end',
+ ' end',
+ 'end']
+ expect_nesting_offenses(source, [3, 8])
+ end
+
+ it 'registers an offense for nested `case`' do
+ source = ['if a',
+ ' if b',
+ ' case c',
+ ' when C',
+ ' puts C',
+ ' end',
+ ' end',
+ 'end']
+ expect_nesting_offenses(source, [3])
+ end
+
+ it 'registers an offense for nested `while`' do
+ source = ['if a',
+ ' if b',
+ ' while c',
+ ' puts c',
+ ' end',
+ ' end',
+ 'end']
+ expect_nesting_offenses(source, [3])
+ end
+
+ it 'registers an offense for nested modifier `while`' do
+ source = ['if a',
+ ' if b',
+ ' begin',
+ ' puts c',
+ ' end while c',
+ ' end',
+ 'end']
+ expect_nesting_offenses(source, [3])
+ end
+
+ it 'registers an offense for nested `until`' do
+ source = ['if a',
+ ' if b',
+ ' until c',
+ ' puts c',
+ ' end',
+ ' end',
+ 'end']
+ expect_nesting_offenses(source, [3])
+ end
+
+ it 'registers an offense for nested modifier `until`' do
+ source = ['if a',
+ ' if b',
+ ' begin',
+ ' puts c',
+ ' end until c',
+ ' end',
+ 'end']
+ expect_nesting_offenses(source, [3])
+ end
+
+ it 'registers an offense for nested `for`' do
+ source = ['if a',
+ ' if b',
+ ' for c in [1,2] do',
+ ' puts c',
+ ' end',
+ ' end',
+ 'end']
+ expect_nesting_offenses(source, [3])
+ end
+
+ it 'registers an offense for nested `rescue`' do
+ source = ['if a',
+ ' if b',
+ ' begin',
+ ' puts c',
+ ' rescue',
+ ' puts x',
+ ' end',
+ ' end',
+ 'end']
+ expect_nesting_offenses(source, [5])
+ end
+
+ it 'accepts if/elsif' do
+ source = ['if a',
+ 'elsif b',
+ 'elsif c',
+ 'elsif d',
+ 'end']
+ expect_nesting_offenses(source, [])
+ end
+
+ def expect_nesting_offenses(source, lines, max_to_allow = 3)
+ inspect_source(cop, source)
+ expect(cop.offenses.map(&:line)).to eq(lines)
+ expect(cop.messages).to eq(
+ ['Avoid more than 2 levels of block nesting.'] * lines.length)
+ if cop.offenses.size > 0
+ expect(cop.config_to_allow_offenses['Max']).to eq(max_to_allow)
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/blocks_spec.rb b/spec/rubocop/cop/style/blocks_spec.rb
new file mode 100644
index 0000000..cd6f055
--- /dev/null
+++ b/spec/rubocop/cop/style/blocks_spec.rb
@@ -0,0 +1,105 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::Blocks do
+ subject(:cop) { described_class.new }
+
+ it 'accepts a multiline block with do-end' do
+ inspect_source(cop, ['each do |x|',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for a single line block with do-end' do
+ inspect_source(cop, ['each do |x| end'])
+ expect(cop.messages)
+ .to eq(['Prefer {...} over do...end for single-line blocks.'])
+ end
+
+ it 'accepts a single line block with braces' do
+ inspect_source(cop, ['each { |x| }'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects do and end for single line blocks to { and }' do
+ new_source = autocorrect_source(cop, 'block do |x| end')
+ expect(new_source).to eq('block { |x| }')
+ end
+
+ it 'does not auto-correct do-end if {} would change the meaning' do
+ src = "s.subspec 'Subspec' do |sp| end"
+ new_source = autocorrect_source(cop, src)
+ expect(new_source).to eq(src)
+ end
+
+ context 'when there are braces around a multi-line block' do
+ it 'registers an offense in the simple case' do
+ inspect_source(cop, ['each { |x|',
+ '}'])
+ expect(cop.messages)
+ .to eq(['Avoid using {...} for multi-line blocks.'])
+ end
+
+ it 'accepts braces if do-end would change the meaning' do
+ src = ['scope :foo, lambda { |f|',
+ ' where(condition: "value")',
+ '}',
+ '',
+ 'expect { something }.to raise_error(ErrorClass) { |error|',
+ ' # ...',
+ '}',
+ '',
+ 'expect { x }.to change {',
+ ' Counter.count',
+ '}.from(0).to(1)']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for braces if do-end would not change ' \
+ 'the meaning' do
+ src = ['scope :foo, (lambda { |f|',
+ ' where(condition: "value")',
+ '})',
+ '',
+ 'expect { something }.to(raise_error(ErrorClass) { |error|',
+ ' # ...',
+ '})']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(2)
+ end
+
+ it 'can handle special method names such as []= and done?' do
+ src = ['h2[k2] = Hash.new { |h3,k3|',
+ ' h3[k3] = 0',
+ '}',
+ '',
+ 'x = done? list.reject { |e|',
+ ' e.nil?',
+ '}']
+ inspect_source(cop, src)
+ expect(cop.messages)
+ .to eq(['Avoid using {...} for multi-line blocks.'])
+ end
+
+ it 'auto-corrects { and } to do and end' do
+ source = <<-END.strip_indent
+ each{ |x|
+ some_method
+ other_method
+ }
+ END
+
+ expected_source = <<-END.strip_indent
+ each do |x|
+ some_method
+ other_method
+ end
+ END
+
+ new_source = autocorrect_source(cop, source)
+ expect(new_source).to eq(expected_source)
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/braces_around_hash_parameters_spec.rb b/spec/rubocop/cop/style/braces_around_hash_parameters_spec.rb
new file mode 100644
index 0000000..fb1e182
--- /dev/null
+++ b/spec/rubocop/cop/style/braces_around_hash_parameters_spec.rb
@@ -0,0 +1,284 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::BracesAroundHashParameters, :config do
+ subject(:cop) { described_class.new(config) }
+
+ context 'no_braces' do
+ let(:cop_config) do
+ { 'EnforcedStyle' => 'no_braces' }
+ end
+
+ describe 'accepts' do
+ it 'one non-hash parameter' do
+ inspect_source(cop, ['where(2)'])
+ expect(cop.messages).to be_empty
+ expect(cop.highlights).to be_empty
+ end
+
+ it 'one empty hash parameter' do
+ inspect_source(cop, ['where({})'])
+ expect(cop.messages).to be_empty
+ expect(cop.highlights).to be_empty
+ end
+
+ it 'one hash parameter with separators' do
+ inspect_source(cop, ["where( { \n }\t ) "])
+ expect(cop.messages).to be_empty
+ expect(cop.highlights).to be_empty
+ end
+
+ it 'multiple non-hash parameters' do
+ inspect_source(cop, ['where(1, "2")'])
+ expect(cop.messages).to be_empty
+ expect(cop.highlights).to be_empty
+ end
+
+ it 'one hash parameter without braces' do
+ inspect_source(cop, ['where(x: "y")'])
+ expect(cop.messages).to be_empty
+ expect(cop.highlights).to be_empty
+ end
+
+ it 'one hash parameter without braces and multiple keys' do
+ inspect_source(cop, ['where(x: "y", foo: "bar")'])
+ expect(cop.messages).to be_empty
+ expect(cop.highlights).to be_empty
+ end
+
+ it 'one hash parameter without braces and one hash value' do
+ inspect_source(cop, ['where(x: { "y" => "z" })'])
+ expect(cop.messages).to be_empty
+ expect(cop.highlights).to be_empty
+ end
+
+ it 'multiple hash parameters with braces' do
+ inspect_source(cop, ['where({ x: 1 }, { y: 2 })'])
+ expect(cop.messages).to be_empty
+ expect(cop.highlights).to be_empty
+ end
+
+ it 'property assignment with braces' do
+ inspect_source(cop, ['x.z = { y: "z" }'])
+ expect(cop.messages).to be_empty
+ expect(cop.highlights).to be_empty
+ end
+
+ it 'operator with a hash parameter with braces' do
+ inspect_source(cop, ['x.z - { y: "z" }'])
+ expect(cop.messages).to be_empty
+ expect(cop.highlights).to be_empty
+ end
+
+ end
+
+ describe 'registers an offense for' do
+ it 'one non-hash parameter followed by a hash parameter with braces' do
+ inspect_source(cop, ['where(1, { y: 2 })'])
+ expect(cop.messages).to eq([
+ 'Redundant curly braces around a hash parameter.'
+ ])
+ expect(cop.highlights).to eq(['{ y: 2 }'])
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' => 'braces')
+ end
+
+ it 'correct + opposite style' do
+ inspect_source(cop, ['where(1, y: 2)',
+ 'where(1, { y: 2 })'])
+ expect(cop.messages).to eq([
+ 'Redundant curly braces around a hash parameter.'
+ ])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'opposite + correct style' do
+ inspect_source(cop, ['where(1, { y: 2 })',
+ 'where(1, y: 2)'])
+ expect(cop.messages).to eq([
+ 'Redundant curly braces around a hash parameter.'
+ ])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'one object method hash parameter with braces' do
+ inspect_source(cop, ['x.func({ y: "z" })'])
+ expect(cop.messages).to eq([
+ 'Redundant curly braces around a hash parameter.'
+ ])
+ expect(cop.highlights).to eq(['{ y: "z" }'])
+ end
+
+ it 'one hash parameter with braces' do
+ inspect_source(cop, ['where({ x: 1 })'])
+ expect(cop.messages).to eq([
+ 'Redundant curly braces around a hash parameter.'
+ ])
+ expect(cop.highlights).to eq(['{ x: 1 }'])
+ end
+
+ it 'one hash parameter with braces and separators' do
+ inspect_source(cop, ["where( \n { x: 1 } )"])
+ expect(cop.messages).to eq([
+ 'Redundant curly braces around a hash parameter.'
+ ])
+ expect(cop.highlights).to eq(['{ x: 1 }'])
+ end
+
+ it 'one hash parameter with braces and multiple keys' do
+ inspect_source(cop, ['where({ x: 1, foo: "bar" })'])
+ expect(cop.messages).to eq([
+ 'Redundant curly braces around a hash parameter.'
+ ])
+ expect(cop.highlights).to eq(['{ x: 1, foo: "bar" }'])
+ end
+ end
+
+ describe 'auto-corrects' do
+ it 'one non-hash parameter followed by a hash parameter with braces' do
+ corrected = autocorrect_source(cop, ['where(1, { y: 2 })'])
+ expect(corrected).to eq 'where(1, y: 2 )'
+ end
+
+ it 'one object method hash parameter with braces' do
+ corrected = autocorrect_source(cop, ['x.func({ y: "z" })'])
+ expect(corrected).to eq 'x.func( y: "z" )'
+ end
+
+ it 'one hash parameter with braces' do
+ corrected = autocorrect_source(cop, ['where({ x: 1 })'])
+ expect(corrected).to eq 'where( x: 1 )'
+ end
+
+ it 'one hash parameter with braces and separators' do
+ corrected = autocorrect_source(cop, ["where( \n { x: 1 } )"])
+ expect(corrected).to eq "where( \n x: 1 )"
+ end
+
+ it 'one hash parameter with braces and multiple keys' do
+ corrected = autocorrect_source(cop, ['where({ x: 1, foo: "bar" })'])
+ expect(corrected).to eq 'where( x: 1, foo: "bar" )'
+ end
+
+ it 'one hash parameter with braces and a trailing comma' do
+ corrected = autocorrect_source(cop, ['where({ x: 1, y: 2, })'])
+ expect(corrected).to eq 'where( x: 1, y: 2 )'
+ end
+ end
+ end
+
+ context 'braces' do
+ let(:cop_config) do
+ { 'EnforcedStyle' => 'braces' }
+ end
+
+ describe 'accepts' do
+ it 'an empty hash parameter' do
+ inspect_source(cop, ['where({})'])
+ expect(cop.messages).to be_empty
+ expect(cop.highlights).to be_empty
+ end
+
+ it 'one non-hash parameter' do
+ inspect_source(cop, ['where(2)'])
+ expect(cop.messages).to be_empty
+ expect(cop.highlights).to be_empty
+ end
+
+ it 'multiple non-hash parameters' do
+ inspect_source(cop, ['where(1, "2")'])
+ expect(cop.messages).to be_empty
+ expect(cop.highlights).to be_empty
+ end
+
+ it 'one hash parameter with braces' do
+ inspect_source(cop, ['where({ x: 1 })'])
+ expect(cop.messages).to be_empty
+ expect(cop.highlights).to be_empty
+ end
+
+ it 'multiple hash parameters with braces' do
+ inspect_source(cop, ['where({ x: 1 }, { y: 2 })'])
+ expect(cop.messages).to be_empty
+ expect(cop.highlights).to be_empty
+ end
+
+ it 'one hash parameter with braces and spaces around it' do
+ inspect_source(cop, [
+ 'where( { x: 1 } )'
+ ])
+ expect(cop.messages).to be_empty
+ expect(cop.highlights).to be_empty
+ end
+
+ it 'one hash parameter with braces and separators around it' do
+ inspect_source(cop, ["where( \t { x: 1 \n } )"])
+ expect(cop.messages).to be_empty
+ expect(cop.highlights).to be_empty
+ end
+ end
+
+ describe 'registers an offense for' do
+ it 'one hash parameter without braces' do
+ inspect_source(cop, ['where(x: "y")'])
+ expect(cop.messages).to eq([
+ 'Missing curly braces around a hash parameter.'
+ ])
+ expect(cop.highlights).to eq(['x: "y"'])
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' =>
+ 'no_braces')
+ end
+
+ it 'opposite + correct style' do
+ inspect_source(cop, ['where(y: 2)',
+ 'where({ y: 2 })'])
+ expect(cop.messages).to eq([
+ 'Missing curly braces around a hash parameter.'
+ ])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'correct + opposite style' do
+ inspect_source(cop, ['where({ y: 2 })',
+ 'where(y: 2)'])
+ expect(cop.messages).to eq([
+ 'Missing curly braces around a hash parameter.'
+ ])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'one hash parameter with multiple keys and without braces' do
+ inspect_source(cop, ['where(x: "y", foo: "bar")'])
+ expect(cop.messages).to eq([
+ 'Missing curly braces around a hash parameter.'
+ ])
+ expect(cop.highlights).to eq(['x: "y", foo: "bar"'])
+ end
+
+ it 'one hash parameter without braces with one hash value' do
+ inspect_source(cop, ['where(x: { "y" => "z" })'])
+ expect(cop.messages).to eq([
+ 'Missing curly braces around a hash parameter.'
+ ])
+ expect(cop.highlights).to eq(['x: { "y" => "z" }'])
+ end
+ end
+
+ describe 'auto-corrects' do
+ it 'one hash parameter without braces' do
+ corrected = autocorrect_source(cop, ['where(x: "y")'])
+ expect(corrected).to eq 'where({x: "y"})'
+ end
+
+ it 'one hash parameter with multiple keys and without braces' do
+ corrected = autocorrect_source(cop, ['where(x: "y", foo: "bar")'])
+ expect(corrected).to eq 'where({x: "y", foo: "bar"})'
+ end
+
+ it 'one hash parameter without braces with one hash value' do
+ corrected = autocorrect_source(cop, ['where(x: { "y" => "z" })'])
+ expect(corrected).to eq 'where({x: { "y" => "z" }})'
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/case_equality_spec.rb b/spec/rubocop/cop/style/case_equality_spec.rb
new file mode 100644
index 0000000..05ae395
--- /dev/null
+++ b/spec/rubocop/cop/style/case_equality_spec.rb
@@ -0,0 +1,13 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::CaseEquality do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for ===' do
+ inspect_source(cop, ['Array === var'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['==='])
+ end
+end
diff --git a/spec/rubocop/cop/style/case_indentation_spec.rb b/spec/rubocop/cop/style/case_indentation_spec.rb
new file mode 100644
index 0000000..cac6921
--- /dev/null
+++ b/spec/rubocop/cop/style/case_indentation_spec.rb
@@ -0,0 +1,292 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::CaseIndentation do
+ subject(:cop) { described_class.new(config) }
+ let(:config) do
+ merged = Rubocop::ConfigLoader
+ .default_configuration['CaseIndentation'].merge(cop_config)
+ Rubocop::Config.new('CaseIndentation' => merged)
+ end
+
+ context 'with IndentWhenRelativeTo: case' do
+ context 'with IndentOneStep: false' do
+ let(:cop_config) do
+ { 'IndentWhenRelativeTo' => 'case', 'IndentOneStep' => false }
+ end
+
+ context 'regarding assignment where the right hand side is a case' do
+ it 'accepts a correcty indented assignment' do
+ source = ['output = case variable',
+ " when 'value1'",
+ " 'output1'",
+ ' else',
+ " 'output2'",
+ ' end']
+ inspect_source(cop, source)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers on offense for an assignment indented as end' do
+ source = ['output = case variable',
+ "when 'value1'",
+ " 'output1'",
+ 'else',
+ " 'output2'",
+ 'end']
+ inspect_source(cop, source)
+ expect(cop.messages).to eq(['Indent `when` as deep as `case`.'])
+ expect(cop.config_to_allow_offenses).to eq('IndentWhenRelativeTo' =>
+ 'end')
+ end
+
+ it 'registers on offense for an assignment indented some other way' do
+ source = ['output = case variable',
+ " when 'value1'",
+ " 'output1'",
+ ' else',
+ " 'output2'",
+ 'end']
+ inspect_source(cop, source)
+ expect(cop.messages).to eq(['Indent `when` as deep as `case`.'])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'registers on offense for correct + opposite' do
+ source = ['output = case variable',
+ " when 'value1'",
+ " 'output1'",
+ ' else',
+ " 'output2'",
+ ' end',
+ 'output = case variable',
+ "when 'value1'",
+ " 'output1'",
+ 'else',
+ " 'output2'",
+ 'end']
+ inspect_source(cop, source)
+ expect(cop.messages).to eq(['Indent `when` as deep as `case`.'])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+ end
+
+ it "registers an offense for a when clause that's deeper than case" do
+ source = ['case a',
+ ' when 0 then return',
+ ' else',
+ ' case b',
+ ' when 1 then return',
+ ' end',
+ 'end']
+ inspect_source(cop, source)
+ expect(cop.messages).to eq(['Indent `when` as deep as `case`.'] * 2)
+ end
+
+ it "accepts a when clause that's equally indented with case" do
+ source = ['y = case a',
+ ' when 0 then break',
+ ' when 0 then return',
+ ' else',
+ ' z = case b',
+ ' when 1 then return',
+ ' when 1 then break',
+ ' end',
+ ' end',
+ 'case c',
+ 'when 2 then encoding',
+ 'end',
+ '']
+ inspect_source(cop, source)
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't get confused by strings with case in them" do
+ source = ['a = "case"',
+ 'case x',
+ 'when 0',
+ 'end',
+ '']
+ inspect_source(cop, source)
+ expect(cop.messages).to be_empty
+ end
+
+ it "doesn't get confused by symbols named case or when" do
+ source = ['KEYWORDS = { :case => true, :when => true }',
+ 'case type',
+ 'when 0',
+ ' ParameterNode',
+ 'when 1',
+ ' MethodCallNode',
+ 'end',
+ '']
+ inspect_source(cop, source)
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts correctly indented whens in complex combinations' do
+ source = ['each {',
+ ' case state',
+ ' when 0',
+ ' case name',
+ ' when :a',
+ ' end',
+ ' when 1',
+ ' loop {',
+ ' case name',
+ ' when :b',
+ ' end',
+ ' }',
+ ' end',
+ '}',
+ 'case s',
+ 'when Array',
+ 'end',
+ '']
+ inspect_source(cop, source)
+ expect(cop.messages).to be_empty
+ end
+ end
+
+ context 'with IndentOneStep: true' do
+ let(:cop_config) do
+ { 'IndentWhenRelativeTo' => 'case', 'IndentOneStep' => true }
+ end
+
+ context 'regarding assignment where the right hand side is a case' do
+ it 'accepts a correcty indented assignment' do
+ source = ['output = case variable',
+ " when 'value1'",
+ " 'output1'",
+ ' else',
+ " 'output2'",
+ ' end']
+ inspect_source(cop, source)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers on offense for an assignment indented some other way' do
+ source = ['output = case variable',
+ " when 'value1'",
+ " 'output1'",
+ ' else',
+ " 'output2'",
+ ' end']
+ inspect_source(cop, source)
+ expect(cop.messages)
+ .to eq(['Indent `when` one step more than `case`.'])
+ end
+ end
+
+ it "accepts a when clause that's 2 spaces deeper than case" do
+ source = ['case a',
+ ' when 0 then return',
+ ' else',
+ ' case b',
+ ' when 1 then return',
+ ' end',
+ 'end']
+ inspect_source(cop, source)
+ expect(cop.offenses).to be_empty
+ end
+
+ it "registers an offense for a when clause that's equally indented " \
+ 'with case' do
+ source = ['y = case a',
+ ' when 0 then break',
+ ' when 0 then return',
+ ' z = case b',
+ ' when 1 then return',
+ ' when 1 then break',
+ ' end',
+ ' end',
+ 'case c',
+ 'when 2 then encoding',
+ 'end',
+ '']
+ inspect_source(cop, source)
+ expect(cop.messages)
+ .to eq(['Indent `when` one step more than `case`.'] * 5)
+ end
+ end
+ end
+
+ context 'with IndentWhenRelativeTo: end' do
+ context 'with IndentOneStep: false' do
+ let(:cop_config) do
+ { 'IndentWhenRelativeTo' => 'end', 'IndentOneStep' => false }
+ end
+
+ context 'regarding assignment where the right hand side is a case' do
+ it 'accepts a correcty indented assignment' do
+ source = ['output = case variable',
+ "when 'value1'",
+ " 'output1'",
+ 'else',
+ " 'output2'",
+ 'end']
+ inspect_source(cop, source)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers on offense for an assignment indented some other way' do
+ source = ['output = case variable',
+ " when 'value1'",
+ " 'output1'",
+ ' else',
+ " 'output2'",
+ 'end']
+ inspect_source(cop, source)
+ expect(cop.messages).to eq(['Indent `when` as deep as `end`.'])
+ end
+ end
+ end
+
+ context 'with IndentOneStep: true' do
+ let(:cop_config) do
+ { 'IndentWhenRelativeTo' => 'end', 'IndentOneStep' => true }
+ end
+
+ context 'regarding assignment where the right hand side is a case' do
+ it 'accepts a correcty indented assignment' do
+ source = ['output = case variable',
+ " when 'value1'",
+ " 'output1'",
+ ' else',
+ " 'output2'",
+ 'end']
+ inspect_source(cop, source)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers on offense for an assignment indented as case' do
+ source = ['output = case variable',
+ " when 'value1'",
+ " 'output1'",
+ ' else',
+ " 'output2'",
+ ' end']
+ inspect_source(cop, source)
+ expect(cop.messages)
+ .to eq(['Indent `when` one step more than `end`.'])
+ expect(cop.config_to_allow_offenses).to eq('IndentWhenRelativeTo' =>
+ 'case')
+ end
+
+ it 'registers on offense for an assignment indented some other way' do
+ source = ['output = case variable',
+ " when 'value1'",
+ " 'output1'",
+ ' else',
+ " 'output2'",
+ ' end']
+ inspect_source(cop, source)
+ expect(cop.messages)
+ .to eq(['Indent `when` one step more than `end`.'])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/character_literal_spec.rb b/spec/rubocop/cop/style/character_literal_spec.rb
new file mode 100644
index 0000000..b063636
--- /dev/null
+++ b/spec/rubocop/cop/style/character_literal_spec.rb
@@ -0,0 +1,37 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::CharacterLiteral do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for character literals' do
+ inspect_source(cop, ['x = ?x'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for literals like \n' do
+ inspect_source(cop, ['x = ?\n'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts literals like ?\C-\M-d' do
+ inspect_source(cop, ['x = ?\C-\M-d'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts ? in a %w literal' do
+ inspect_source(cop, ['%w{? A}'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it "auto-corrects ?x to 'x'" do
+ new_source = autocorrect_source(cop, 'x = ?x')
+ expect(new_source).to eq("x = 'x'")
+ end
+
+ it 'auto-corrects ?\n to "\\n"' do
+ new_source = autocorrect_source(cop, 'x = ?\n')
+ expect(new_source).to eq('x = "\\n"')
+ end
+end
diff --git a/spec/rubocop/cop/style/class_and_module_camel_case_spec.rb b/spec/rubocop/cop/style/class_and_module_camel_case_spec.rb
new file mode 100644
index 0000000..4ed3f71
--- /dev/null
+++ b/spec/rubocop/cop/style/class_and_module_camel_case_spec.rb
@@ -0,0 +1,40 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::ClassAndModuleCamelCase do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for underscore in class and module name' do
+ inspect_source(cop,
+ ['class My_Class',
+ 'end',
+ '',
+ 'module My_Module',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(2)
+ end
+
+ it 'is not fooled by qualified names' do
+ inspect_source(cop,
+ ['class Top::My_Class',
+ 'end',
+ '',
+ 'module My_Module::Ala',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(2)
+ end
+
+ it 'accepts CamelCase names' do
+ inspect_source(cop,
+ ['class MyClass',
+ 'end',
+ '',
+ 'module Mine',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/class_and_module_children_spec.rb b/spec/rubocop/cop/style/class_and_module_children_spec.rb
new file mode 100644
index 0000000..5e2ed3c
--- /dev/null
+++ b/spec/rubocop/cop/style/class_and_module_children_spec.rb
@@ -0,0 +1,129 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::ClassAndModuleChildren, :config do
+ subject(:cop) { described_class.new(config) }
+
+ context 'nested style' do
+ let(:cop_config) do
+ {
+ 'EnforcedStyle' => 'nested'
+ }
+ end
+
+ it 'registers an offense for not nested classes' do
+ inspect_source(cop, ['class FooClass::BarClass', 'end'])
+
+ expect(cop.offenses.size).to eq 1
+ expect(cop.messages).to eq [
+ 'Use nested module/class definitions instead of compact style.'
+ ]
+ expect(cop.highlights).to eq ['FooClass::BarClass']
+ end
+
+ it 'registers an offense for not nested modules' do
+ inspect_source(cop, ['module FooModule::BarModule', 'end'])
+
+ expect(cop.offenses.size).to eq 1
+ expect(cop.messages).to eq [
+ 'Use nested module/class definitions instead of compact style.'
+ ]
+ expect(cop.highlights).to eq ['FooModule::BarModule']
+ end
+
+ it 'accepts nested children' do
+ inspect_source(cop,
+ ['class FooClass',
+ ' class BarClass',
+ ' end',
+ 'end',
+ '',
+ 'module FooModule',
+ ' module BarModule',
+ ' end',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts :: in parent class on inheritance' do
+ inspect_source(cop,
+ ['class FooClass',
+ ' class BarClass',
+ ' end',
+ 'end',
+ '',
+ 'class BazClass < FooClass::BarClass',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'compact style' do
+ let(:cop_config) do
+ {
+ 'EnforcedStyle' => 'compact'
+ }
+ end
+
+ it 'registers a offense for classes with nested children' do
+ inspect_source(cop,
+ ['class FooClass',
+ ' class BarClass',
+ ' end',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq 1
+ expect(cop.messages).to eq [
+ 'Use compact module/class definition instead of nested style.'
+ ]
+ expect(cop.highlights).to eq ['FooClass']
+ end
+
+ it 'registers a offense for modules with nested children' do
+ inspect_source(cop,
+ ['module FooModule',
+ ' module BarModule',
+ ' end',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq 1
+ expect(cop.messages).to eq [
+ 'Use compact module/class definition instead of nested style.'
+ ]
+ expect(cop.highlights).to eq ['FooModule']
+ end
+
+ it 'accepts compact style for classes / modules' do
+ inspect_source(cop,
+ ['class FooClass::BarClass',
+ 'end',
+ '',
+ 'module FooClass::BarModule',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts nesting for classes / modules with more than one child' do
+ inspect_source(cop,
+ ['class FooClass',
+ ' class BarClass',
+ ' end',
+ ' class BazClass',
+ ' end',
+ 'end',
+ '',
+ 'module FooModule',
+ ' module BarModule',
+ ' end',
+ ' class BazModule',
+ ' end',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/class_length_spec.rb b/spec/rubocop/cop/style/class_length_spec.rb
new file mode 100644
index 0000000..5d5e18b
--- /dev/null
+++ b/spec/rubocop/cop/style/class_length_spec.rb
@@ -0,0 +1,131 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::ClassLength, :config do
+ subject(:cop) { described_class.new(config) }
+ let(:cop_config) { { 'Max' => 5, 'CountComments' => false } }
+
+ it 'rejects a class with more than 5 lines' do
+ inspect_source(cop, ['class Test',
+ ' a = 1',
+ ' a = 2',
+ ' a = 3',
+ ' a = 4',
+ ' a = 5',
+ ' a = 6',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages).to eq(['Class definition is too long. [6/5]'])
+ expect(cop.config_to_allow_offenses).to eq('Max' => 6)
+ end
+
+ it 'accepts a class with 5 lines' do
+ inspect_source(cop, ['class Test',
+ ' a = 1',
+ ' a = 2',
+ ' a = 3',
+ ' a = 4',
+ ' a = 5',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a class with less than 5 lines' do
+ inspect_source(cop, ['class Test',
+ ' a = 1',
+ ' a = 2',
+ ' a = 3',
+ ' a = 4',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not count blank lines' do
+ inspect_source(cop, ['class Test',
+ ' a = 1',
+ ' a = 2',
+ ' a = 3',
+ ' a = 4',
+ '',
+ '',
+ ' a = 7',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts empty classes' do
+ inspect_source(cop, ['class Test',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ context 'when a class has inner classes' do
+ it 'does not count lines of inner classes' do
+ inspect_source(cop, ['class NamespaceClass',
+ ' class TestOne',
+ ' a = 1',
+ ' a = 2',
+ ' a = 3',
+ ' a = 4',
+ ' a = 5',
+ ' end',
+ ' class TestTwo',
+ ' a = 1',
+ ' a = 2',
+ ' a = 3',
+ ' a = 4',
+ ' a = 5',
+ ' end',
+ ' a = 1',
+ ' a = 2',
+ ' a = 3',
+ ' a = 4',
+ ' a = 5',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'rejects a class with 6 lines that belong to the class directly' do
+ inspect_source(cop, ['class NamespaceClass',
+ ' class TestOne',
+ ' a = 1',
+ ' a = 2',
+ ' a = 3',
+ ' a = 4',
+ ' a = 5',
+ ' end',
+ ' class TestTwo',
+ ' a = 1',
+ ' a = 2',
+ ' a = 3',
+ ' a = 4',
+ ' a = 5',
+ ' end',
+ ' a = 1',
+ ' a = 2',
+ ' a = 3',
+ ' a = 4',
+ ' a = 5',
+ ' a = 6',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+
+ context 'when CountComments is enabled' do
+ before { cop_config['CountComments'] = true }
+
+ it 'also counts commented lines' do
+ inspect_source(cop, ['class Test',
+ ' a = 1',
+ ' #a = 2',
+ ' a = 3',
+ ' #a = 4',
+ ' a = 5',
+ ' a = 6',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/class_methods_spec.rb b/spec/rubocop/cop/style/class_methods_spec.rb
new file mode 100644
index 0000000..8d5939b
--- /dev/null
+++ b/spec/rubocop/cop/style/class_methods_spec.rb
@@ -0,0 +1,68 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::ClassMethods do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for methods using a class name' do
+ inspect_source(cop,
+ ['class Test',
+ ' def Test.some_method',
+ ' do_something',
+ ' end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Use `self.some_method` instead of `Test.some_method`.'])
+ expect(cop.highlights).to eq(['Test'])
+ end
+
+ it 'registers an offense for methods using a module name' do
+ inspect_source(cop,
+ ['module Test',
+ ' def Test.some_method',
+ ' do_something',
+ ' end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Use `self.some_method` instead of `Test.some_method`.'])
+ expect(cop.highlights).to eq(['Test'])
+ end
+
+ it 'does not register an offense for methods using self' do
+ inspect_source(cop,
+ ['module Test',
+ ' def self.some_method',
+ ' do_something',
+ ' end',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense outside class/module bodies' do
+ inspect_source(cop,
+ ['def Test.some_method',
+ ' do_something',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'autocorrects class name to self' do
+ src = ['class Test',
+ ' def Test.some_method',
+ ' do_something',
+ ' end',
+ 'end']
+
+ correct_source = ['class Test',
+ ' def self.some_method',
+ ' do_something',
+ ' end',
+ 'end'].join("\n")
+
+ new_source = autocorrect_source(cop, src)
+ expect(new_source).to eq(correct_source)
+ end
+end
diff --git a/spec/rubocop/cop/style/class_vars_spec.rb b/spec/rubocop/cop/style/class_vars_spec.rb
new file mode 100644
index 0000000..0dd3511
--- /dev/null
+++ b/spec/rubocop/cop/style/class_vars_spec.rb
@@ -0,0 +1,19 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::ClassVars do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for class variable declaration' do
+ inspect_source(cop, ['class TestClass; @@test = 10; end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Replace class var @@test with a class instance var.'])
+ end
+
+ it 'does not register an offense for class variable usage' do
+ inspect_source(cop, ['@@test.test(20)'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/collection_methods_spec.rb b/spec/rubocop/cop/style/collection_methods_spec.rb
new file mode 100644
index 0000000..12cde23
--- /dev/null
+++ b/spec/rubocop/cop/style/collection_methods_spec.rb
@@ -0,0 +1,48 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::CollectionMethods, :config do
+ cop_config = {
+ 'PreferredMethods' => {
+ 'collect' => 'map',
+ 'inject' => 'reduce',
+ 'detect' => 'find',
+ 'find_all' => 'select'
+ }
+ }
+
+ subject(:cop) { described_class.new(config) }
+ let(:cop_config) { cop_config }
+
+ cop_config['PreferredMethods'].each do |method, preferred_method|
+ it "registers an offense for #{method} with block" do
+ inspect_source(cop, ["[1, 2, 3].#{method} { |e| e + 1 }"])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(["Prefer `#{preferred_method}` over `#{method}`."])
+ end
+
+ it "registers an offense for #{method} with proc param" do
+ inspect_source(cop, ["[1, 2, 3].#{method}(&:test)"])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(["Prefer `#{preferred_method}` over `#{method}`."])
+ end
+
+ it "accepts #{method} with more than 1 param" do
+ inspect_source(cop, ["[1, 2, 3].#{method}(other, &:test)"])
+ expect(cop.offenses).to be_empty
+ end
+
+ it "accepts #{method} without a block" do
+ inspect_source(cop, ["[1, 2, 3].#{method}"])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects to preferred method' do
+ new_source = autocorrect_source(cop, 'some.collect(&:test)')
+ expect(new_source).to eq('some.map(&:test)')
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/colon_method_call_spec.rb b/spec/rubocop/cop/style/colon_method_call_spec.rb
new file mode 100644
index 0000000..9016802
--- /dev/null
+++ b/spec/rubocop/cop/style/colon_method_call_spec.rb
@@ -0,0 +1,60 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::ColonMethodCall do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for instance method call' do
+ inspect_source(cop,
+ ['test::method_name'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for instance method call with arg' do
+ inspect_source(cop,
+ ['test::method_name(arg)'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for class method call' do
+ inspect_source(cop,
+ ['Class::method_name'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for class method call with arg' do
+ inspect_source(cop,
+ ['Class::method_name(arg, arg2)'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'does not register an offense for constant access' do
+ inspect_source(cop,
+ ['Tip::Top::SOME_CONST'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for nested class' do
+ inspect_source(cop,
+ ['Tip::Top.some_method'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for op methods' do
+ inspect_source(cop,
+ ['Tip::Top.some_method[3]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense when for constructor methods' do
+ inspect_source(cop,
+ ['Tip::Top(some_arg)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects "::" with "."' do
+ new_source = autocorrect_source(cop, 'test::method')
+ expect(new_source).to eq('test.method')
+ end
+end
diff --git a/spec/rubocop/cop/style/comment_annotation_spec.rb b/spec/rubocop/cop/style/comment_annotation_spec.rb
new file mode 100644
index 0000000..cf05169
--- /dev/null
+++ b/spec/rubocop/cop/style/comment_annotation_spec.rb
@@ -0,0 +1,86 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::CommentAnnotation, :config do
+ subject(:cop) { described_class.new(config) }
+ let(:cop_config) do
+ { 'Keywords' => %w(TODO FIXME OPTIMIZE HACK REVIEW) }
+ end
+
+ it 'registers an offense for a missing colon' do
+ inspect_source(cop, ['# TODO make better'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ context 'with configured keyword' do
+ let(:cop_config) { { 'Keywords' => %w(ISSUE) } }
+
+ it 'registers an offense for a missing colon after the word' do
+ inspect_source(cop, ['# ISSUE wrong order'])
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+
+ context 'when used with the clang formatter' do
+ let(:formatter) { Rubocop::Formatter::ClangStyleFormatter.new(output) }
+ let(:output) { StringIO.new }
+
+ it 'marks the annotation keyword' do
+ inspect_source(cop, ['# TODO:make better'])
+ formatter.report_file('t', cop.offenses)
+ expect(output.string).to eq(["t:1:3: C: #{described_class::MSG}",
+ '# TODO:make better',
+ ' ^^^^^',
+ ''].join("\n"))
+ end
+ end
+
+ it 'registers an offense for lower case' do
+ inspect_source(cop, ['# fixme: does not work'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for capitalized annotation keyword' do
+ inspect_source(cop, ['# Optimize: does not work'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for upper case with colon but no note' do
+ inspect_source(cop, ['# HACK:'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts upper case keyword with colon, space and note' do
+ inspect_source(cop, ['# REVIEW: not sure about this'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts upper case keyword alone' do
+ inspect_source(cop, ['# OPTIMIZE'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a comment that is obviously a code example' do
+ inspect_source(cop, ['# Todo.destroy(1)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a keyword that is just the beginning of a sentence' do
+ inspect_source(cop,
+ ["# Optimize if you want. I wouldn't recommend it.",
+ '# Hack is a fun game.'])
+ expect(cop.offenses).to be_empty
+ end
+
+ context 'when a keyword is not in the configuration' do
+ let(:cop_config) do
+ { 'Keywords' => %w(FIXME OPTIMIZE HACK REVIEW) }
+ end
+
+ it 'accepts the word without colon' do
+ inspect_source(cop, ['# TODO make better'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/constant_name_spec.rb b/spec/rubocop/cop/style/constant_name_spec.rb
new file mode 100644
index 0000000..3f38f26
--- /dev/null
+++ b/spec/rubocop/cop/style/constant_name_spec.rb
@@ -0,0 +1,65 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::ConstantName do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for camel case in const name' do
+ inspect_source(cop,
+ ['TopCase = 5'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers offenses for camel case in multiple const assignment' do
+ inspect_source(cop,
+ ['TopCase, Test2, TEST_3 = 5, 6, 7'])
+ expect(cop.offenses.size).to eq(2)
+ end
+
+ it 'registers an offense for snake case in const name' do
+ inspect_source(cop,
+ ['TOP_test = 5'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'allows screaming snake case in const name' do
+ inspect_source(cop,
+ ['TOP_TEST = 5'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'allows screaming snake case in multiple const assignment' do
+ inspect_source(cop,
+ ['TOP_TEST, TEST_2 = 5, 6'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not check names if rhs is a method call' do
+ inspect_source(cop,
+ ['AnythingGoes = test'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not check names if rhs is a method call with block' do
+ inspect_source(cop,
+ ['AnythingGoes = test do',
+ ' do_something',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not check if rhs is another constant' do
+ inspect_source(cop,
+ ['Parser::CurrentRuby = Parser::Ruby20'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'checks qualified const names' do
+ inspect_source(cop,
+ ['::AnythingGoes = 30',
+ 'a::Bar_foo = 10'])
+ expect(cop.offenses.size).to eq(2)
+ end
+end
diff --git a/spec/rubocop/cop/style/cyclomatic_complexity_spec.rb b/spec/rubocop/cop/style/cyclomatic_complexity_spec.rb
new file mode 100644
index 0000000..e41c3a5
--- /dev/null
+++ b/spec/rubocop/cop/style/cyclomatic_complexity_spec.rb
@@ -0,0 +1,204 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::CyclomaticComplexity, :config do
+ subject(:cop) { described_class.new(config) }
+
+ context 'when Max is 1' do
+ let(:cop_config) { { 'Max' => 1 } }
+
+ it 'accepts a method with no decision points' do
+ inspect_source(cop, ['def method_name',
+ ' call_foo',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts complex code outside of methods' do
+ inspect_source(cop,
+ ['def method_name',
+ ' call_foo',
+ 'end',
+ '',
+ 'if first_condition then',
+ ' call_foo if second_condition && third_condition',
+ ' call_bar if fourth_condition || fifth_condition',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for an if modifier' do
+ inspect_source(cop, ['def self.method_name',
+ ' call_foo if some_condition',
+ 'end'])
+ expect(cop.messages)
+ .to eq(['Cyclomatic complexity for method_name is too high. [2/1]'])
+ expect(cop.highlights).to eq(['def'])
+ expect(cop.config_to_allow_offenses).to eq('Max' => 2)
+ end
+
+ it 'registers an offense for an unless modifier' do
+ inspect_source(cop, ['def method_name',
+ ' call_foo unless some_condition',
+ 'end'])
+ expect(cop.messages)
+ .to eq(['Cyclomatic complexity for method_name is too high. [2/1]'])
+ end
+
+ it 'registers an offense for an elsif block' do
+ inspect_source(cop, ['def method_name',
+ ' if first_condition then',
+ ' call_foo',
+ ' elsif second_condition then',
+ ' call_bar',
+ ' else',
+ ' call_bam',
+ ' end',
+ 'end'])
+ expect(cop.messages)
+ .to eq(['Cyclomatic complexity for method_name is too high. [3/1]'])
+ end
+
+ it 'registers an offense for a ternary operator' do
+ inspect_source(cop, ['def method_name',
+ ' value = some_condition ? 1 : 2',
+ 'end'])
+ expect(cop.messages)
+ .to eq(['Cyclomatic complexity for method_name is too high. [2/1]'])
+ end
+
+ it 'registers an offense for a while block' do
+ inspect_source(cop, ['def method_name',
+ ' while some_condition do',
+ ' call_foo',
+ ' end',
+ 'end'])
+ expect(cop.messages)
+ .to eq(['Cyclomatic complexity for method_name is too high. [2/1]'])
+ end
+
+ it 'registers an offense for an until block' do
+ inspect_source(cop, ['def method_name',
+ ' until some_condition do',
+ ' call_foo',
+ ' end',
+ 'end'])
+ expect(cop.messages)
+ .to eq(['Cyclomatic complexity for method_name is too high. [2/1]'])
+ end
+
+ it 'registers an offense for a for block' do
+ inspect_source(cop, ['def method_name',
+ ' for i in 1..2 do',
+ ' call_method',
+ ' end',
+ 'end'])
+ expect(cop.messages)
+ .to eq(['Cyclomatic complexity for method_name is too high. [2/1]'])
+ end
+
+ it 'registers an offense for a rescue block' do
+ inspect_source(cop, ['def method_name',
+ ' begin',
+ ' call_foo',
+ ' rescue Exception',
+ ' call_bar',
+ ' end',
+ 'end'])
+ expect(cop.messages)
+ .to eq(['Cyclomatic complexity for method_name is too high. [2/1]'])
+ end
+
+ it 'registers an offense for a case/when block' do
+ inspect_source(cop, ['def method_name',
+ ' case value',
+ ' when 1',
+ ' call_foo',
+ ' when 2',
+ ' call_bar',
+ ' end',
+ 'end'])
+ expect(cop.messages)
+ .to eq(['Cyclomatic complexity for method_name is too high. [3/1]'])
+ end
+
+ it 'registers an offense for &&' do
+ inspect_source(cop, ['def method_name',
+ ' call_foo && call_bar',
+ 'end'])
+ expect(cop.messages)
+ .to eq(['Cyclomatic complexity for method_name is too high. [2/1]'])
+ end
+
+ it 'registers an offense for and' do
+ inspect_source(cop, ['def method_name',
+ ' call_foo and call_bar',
+ 'end'])
+ expect(cop.messages)
+ .to eq(['Cyclomatic complexity for method_name is too high. [2/1]'])
+ end
+
+ it 'registers an offense for ||' do
+ inspect_source(cop, ['def method_name',
+ ' call_foo || call_bar',
+ 'end'])
+ expect(cop.messages)
+ .to eq(['Cyclomatic complexity for method_name is too high. [2/1]'])
+ end
+
+ it 'registers an offense for or' do
+ inspect_source(cop, ['def method_name',
+ ' call_foo or call_bar',
+ 'end'])
+ expect(cop.messages)
+ .to eq(['Cyclomatic complexity for method_name is too high. [2/1]'])
+ end
+
+ it 'deals with nested if blocks containing && and ||' do
+ inspect_source(cop,
+ ['def method_name',
+ ' if first_condition then',
+ ' call_foo if second_condition && third_condition',
+ ' call_bar if fourth_condition || fifth_condition',
+ ' end',
+ 'end'])
+ expect(cop.messages)
+ .to eq(['Cyclomatic complexity for method_name is too high. [6/1]'])
+ end
+
+ it 'counts only a single method' do
+ inspect_source(cop, ['def method_name_1',
+ ' call_foo if some_condition',
+ 'end',
+ '',
+ 'def method_name_2',
+ ' call_foo if some_condition',
+ 'end'])
+ expect(cop.messages)
+ .to eq(['Cyclomatic complexity for method_name_1 is too high. [2/1]',
+ 'Cyclomatic complexity for method_name_2 is too high. [2/1]'])
+ end
+ end
+
+ context 'when Max is 2' do
+ let(:cop_config) { { 'Max' => 2 } }
+
+ it 'counts stupid nested if and else blocks' do
+ inspect_source(cop, ['def method_name',
+ ' if first_condition then',
+ ' call_foo',
+ ' else',
+ ' if second_condition then',
+ ' call_bar',
+ ' else',
+ ' call_bam if third_condition',
+ ' end',
+ ' call_baz if fourth_condition',
+ ' end',
+ 'end'])
+ expect(cop.messages)
+ .to eq(['Cyclomatic complexity for method_name is too high. [5/2]'])
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/def_with_parentheses_spec.rb b/spec/rubocop/cop/style/def_with_parentheses_spec.rb
new file mode 100644
index 0000000..4b1f27e
--- /dev/null
+++ b/spec/rubocop/cop/style/def_with_parentheses_spec.rb
@@ -0,0 +1,39 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::DefWithParentheses do
+ subject(:cop) { described_class.new }
+
+ it 'reports an offense for def with empty parens' do
+ src = ['def func()',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'reports an offense for class def with empty parens' do
+ src = ['def Test.func()',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts def with arg and parens' do
+ src = ['def func(a)',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts empty parentheses in one liners' do
+ src = ["def to_s() join '/' end"]
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-removes unneeded parens' do
+ new_source = autocorrect_source(cop, "def test();\nsomething\nend")
+ expect(new_source).to eq("def test;\nsomething\nend")
+ end
+end
diff --git a/spec/rubocop/cop/style/deprecated_hash_methods_spec.rb b/spec/rubocop/cop/style/deprecated_hash_methods_spec.rb
new file mode 100644
index 0000000..c32baa1
--- /dev/null
+++ b/spec/rubocop/cop/style/deprecated_hash_methods_spec.rb
@@ -0,0 +1,45 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::DeprecatedHashMethods do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for has_key? with one arg' do
+ inspect_source(cop,
+ ['o.has_key?(o)'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['`Hash#has_key?` is deprecated in favor of `Hash#key?`.'])
+ end
+
+ it 'accepts has_key? with no args' do
+ inspect_source(cop,
+ ['o.has_key?'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for has_value? with one arg' do
+ inspect_source(cop,
+ ['o.has_value?(o)'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['`Hash#has_value?` is deprecated in favor of `Hash#value?`.'])
+ end
+
+ it 'accepts has_value? with no args' do
+ inspect_source(cop,
+ ['o.has_value?'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects has_key? with key?' do
+ new_source = autocorrect_source(cop, 'hash.has_key?(:test)')
+ expect(new_source).to eq('hash.key?(:test)')
+ end
+
+ it 'auto-corrects has_value? with value?' do
+ new_source = autocorrect_source(cop, 'hash.has_value?(value)')
+ expect(new_source).to eq('hash.value?(value)')
+ end
+end
diff --git a/spec/rubocop/cop/style/documentation_spec.rb b/spec/rubocop/cop/style/documentation_spec.rb
new file mode 100644
index 0000000..e75119a
--- /dev/null
+++ b/spec/rubocop/cop/style/documentation_spec.rb
@@ -0,0 +1,123 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::Documentation do
+
+ subject(:cop) { described_class.new(config) }
+ let(:config) do
+ Rubocop::Config.new('CommentAnnotation' => {
+ 'Keywords' => %w(TODO FIXME OPTIMIZE HACK REVIEW)
+ })
+ end
+
+ it 'registers an offense for non-empty class' do
+ inspect_source(cop,
+ ['class My_Class',
+ ' TEST = 20',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'does not consider comment followed by empty line to be class ' \
+ 'documentation' do
+ inspect_source(cop,
+ ['# Copyright 2014',
+ '# Some company',
+ '',
+ 'class My_Class',
+ ' TEST = 20',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for non-namespace' do
+ inspect_source(cop,
+ ['module My_Class',
+ ' TEST = 20',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for empty module without documentation' do
+ # Because why would you have an empty module? It requires some
+ # explanation.
+ inspect_source(cop,
+ ['module Test',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts non-empty class with documentation' do
+ inspect_source(cop,
+ ['# class comment',
+ 'class My_Class',
+ ' TEST = 20',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for non-empty class with annotation comment' do
+ inspect_source(cop,
+ ['# OPTIMIZE: Make this faster.',
+ 'class My_Class',
+ ' TEST = 20',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts non-empty module with documentation' do
+ inspect_source(cop,
+ ['# class comment',
+ 'module My_Class',
+ ' TEST = 20',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts empty class without documentation' do
+ inspect_source(cop,
+ ['class My_Class',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts namespace module without documentation' do
+ inspect_source(cop,
+ ['module Test',
+ ' class A; end',
+ ' class B; end',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts namespace class without documentation' do
+ inspect_source(cop,
+ ['class Test',
+ ' class A; end',
+ ' class B; end',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not raise an error for an implicit match conditional' do
+ expect do
+ inspect_source(cop,
+ ['class Test',
+ ' if //',
+ ' end',
+ 'end'
+ ])
+ end.to_not raise_error
+ end
+end
diff --git a/spec/rubocop/cop/style/dot_position_spec.rb b/spec/rubocop/cop/style/dot_position_spec.rb
new file mode 100644
index 0000000..afa8a04
--- /dev/null
+++ b/spec/rubocop/cop/style/dot_position_spec.rb
@@ -0,0 +1,91 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::DotPosition, :config do
+ subject(:cop) { described_class.new(config) }
+
+ context 'Leading dots style' do
+ let(:cop_config) { { 'EnforcedStyle' => 'leading' } }
+
+ it 'registers an offense for trailing dot in multi-line call' do
+ inspect_source(cop, ['something.',
+ ' method_name'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['.'])
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' => 'trailing')
+ end
+
+ it 'registers an offense for correct + opposite' do
+ inspect_source(cop, ['something',
+ ' .method_name',
+ 'something.',
+ ' method_name'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'accepts leading do in multi-line method call' do
+ inspect_source(cop, ['something',
+ ' .method_name'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not err on method call with no dots' do
+ inspect_source(cop, ['puts something'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not err on method call without a method name' do
+ inspect_source(cop, ['l.', '(1)'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'does not err on method call on same line' do
+ inspect_source(cop, ['something.method_name'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'Trailing dots style' do
+ let(:cop_config) { { 'EnforcedStyle' => 'trailing' } }
+
+ it 'registers an offense for leading dot in multi-line call' do
+ inspect_source(cop, ['something',
+ ' .method_name'])
+ expect(cop.messages)
+ .to eq(['Place the . on the previous line, together with the method ' \
+ 'call receiver.'])
+ expect(cop.highlights).to eq(['.'])
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' => 'leading')
+ end
+
+ it 'accepts trailing dot in multi-line method call' do
+ inspect_source(cop, ['something.',
+ ' method_name'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not err on method call with no dots' do
+ inspect_source(cop, ['puts something'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not err on method call without a method name' do
+ inspect_source(cop, ['l', '.(1)'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'does not err on method call on same line' do
+ inspect_source(cop, ['something.method_name'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not get confused by several lines of chained methods' do
+ inspect_source(cop, ['File.new(something).',
+ 'readlines.map.',
+ 'compact.join("\n")'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/double_negation_spec.rb b/spec/rubocop/cop/style/double_negation_spec.rb
new file mode 100644
index 0000000..767cef9
--- /dev/null
+++ b/spec/rubocop/cop/style/double_negation_spec.rb
@@ -0,0 +1,22 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::DoubleNegation do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for !!' do
+ inspect_source(cop, '!!test.something')
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'does not register an offense for !' do
+ inspect_source(cop, '!test.something')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for not not' do
+ inspect_source(cop, 'not not test.something')
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/empty_line_between_defs_spec.rb b/spec/rubocop/cop/style/empty_line_between_defs_spec.rb
new file mode 100644
index 0000000..f345111
--- /dev/null
+++ b/spec/rubocop/cop/style/empty_line_between_defs_spec.rb
@@ -0,0 +1,135 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::EmptyLineBetweenDefs, :config do
+ subject(:cop) { described_class.new(config) }
+ let(:cop_config) { { 'AllowAdjacentOneLineDefs' => false } }
+
+ it 'finds offenses in inner classes' do
+ source = ['class K',
+ ' def m',
+ ' end',
+ ' class J',
+ ' def n',
+ ' end',
+ ' def o',
+ ' end',
+ ' end',
+ ' # checks something',
+ ' def p',
+ ' end',
+ 'end']
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line).sort).to eq([7])
+ end
+
+ # Only one def, so rule about empty line *between* defs does not
+ # apply.
+ it 'accepts a def that follows a line with code' do
+ source = ['x = 0',
+ 'def m',
+ 'end']
+ inspect_source(cop, source)
+ expect(cop.offenses).to be_empty
+ end
+
+ # Only one def, so rule about empty line *between* defs does not
+ # apply.
+ it 'accepts a def that follows code and a comment' do
+ source = [' x = 0',
+ ' # 123',
+ ' def m',
+ ' end']
+ inspect_source(cop, source)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts the first def without leading empty line in a class' do
+ source = ['class K',
+ ' def m',
+ ' end',
+ 'end']
+ inspect_source(cop, source)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a def that follows an empty line and then a comment' do
+ source = ['class A',
+ ' # calculates value',
+ ' def m',
+ ' end',
+ '',
+ ' private',
+ ' # calculates size',
+ ' def n',
+ ' end',
+ 'end'
+ ]
+ inspect_source(cop, source)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a def that is the first of a module' do
+ source = ['module Util',
+ ' public',
+ ' #',
+ ' def html_escape(s)',
+ ' end',
+ 'end'
+ ]
+ inspect_source(cop, source)
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts a nested def' do
+ source = ['def mock_model(*attributes)',
+ ' Class.new do',
+ ' def initialize(attrs)',
+ ' end',
+ ' end',
+ 'end'
+ ]
+ inspect_source(cop, source)
+ expect(cop.messages).to be_empty
+ end
+
+ it 'registers an offense for adjacent one-liners by default' do
+ source = ['def a; end',
+ 'def b; end']
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'auto-corrects adjacent one-liners by default' do
+ corrected = autocorrect_source(cop, [' def a; end',
+ ' def b; end'])
+ expect(corrected).to eq([' def a; end',
+ '',
+ ' def b; end'].join("\n"))
+ end
+
+ context 'when AllowAdjacentOneLineDefs is enabled' do
+ let(:cop_config) { { 'AllowAdjacentOneLineDefs' => true } }
+
+ it 'accepts adjacent one-liners' do
+ source = ['def a; end',
+ 'def b; end']
+ inspect_source(cop, source)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for adjacent defs if some are multi-line' do
+ source = ['def a; end',
+ 'def b; end',
+ 'def c', # Not a one-liner, so this is an offense.
+ 'end',
+ # Also an offense since previous was multi-line:
+ 'def d; end'
+ ]
+ inspect_source(cop, source)
+ expect(cop.offenses.map(&:line)).to eq([3, 5])
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/empty_lines_around_access_modifier_spec.rb b/spec/rubocop/cop/style/empty_lines_around_access_modifier_spec.rb
new file mode 100644
index 0000000..96ac8c3
--- /dev/null
+++ b/spec/rubocop/cop/style/empty_lines_around_access_modifier_spec.rb
@@ -0,0 +1,56 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::EmptyLinesAroundAccessModifier do
+ subject(:cop) { described_class.new }
+
+ %w(private protected public).each do |access_modifier|
+ it "requires blank line before #{access_modifier}" do
+ inspect_source(cop,
+ ['class Test',
+ ' something',
+ " #{access_modifier}",
+ '',
+ ' def test; end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(["Keep a blank line before and after `#{access_modifier}`."])
+ end
+
+ it 'requires blank line after #{access_modifier}' do
+ inspect_source(cop,
+ ['class Test',
+ ' something',
+ '',
+ " #{access_modifier}",
+ ' def test; end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(["Keep a blank line before and after `#{access_modifier}`."])
+ end
+
+ it 'accepts missing blank line when at the beginning of class/module' do
+ inspect_source(cop,
+ ['class Test',
+ " #{access_modifier}",
+ '',
+ ' def test; end',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'recognizes blank lines with DOS style line endings' do
+ inspect_source(cop,
+ ["class Test\r",
+ "\r",
+ " #{access_modifier}\r",
+ "\r",
+ " def test; end\r",
+ "end\r"])
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/empty_lines_around_body_spec.rb b/spec/rubocop/cop/style/empty_lines_around_body_spec.rb
new file mode 100644
index 0000000..bbad725
--- /dev/null
+++ b/spec/rubocop/cop/style/empty_lines_around_body_spec.rb
@@ -0,0 +1,131 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::EmptyLinesAroundBody do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for method body starting with a blank' do
+ inspect_source(cop,
+ ['def some_method',
+ '',
+ ' do_something',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ # The cop only registers an offense if the extra line is completely emtpy. If
+ # there is trailing whitespace, then that must be dealt with first. Having
+ # two cops registering offense for the line with only spaces would cause
+ # havoc in auto-correction.
+ it 'accepts method body starting with a line with spaces' do
+ inspect_source(cop,
+ ['def some_method',
+ ' ',
+ ' do_something',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'autocorrects method body starting with a blank' do
+ corrected = autocorrect_source(cop,
+ ['def some_method',
+ '',
+ ' do_something',
+ 'end'])
+ expect(corrected).to eq ['def some_method',
+ ' do_something',
+ 'end'].join("\n")
+ end
+
+ it 'registers an offense for class method body starting with a blank' do
+ inspect_source(cop,
+ ['def Test.some_method',
+ '',
+ ' do_something',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'autocorrects class method body starting with a blank' do
+ corrected = autocorrect_source(cop,
+ ['def Test.some_method',
+ '',
+ ' do_something',
+ 'end'])
+ expect(corrected).to eq ['def Test.some_method',
+ ' do_something',
+ 'end'].join("\n")
+ end
+
+ it 'registers an offense for method body ending with a blank' do
+ inspect_source(cop,
+ ['def some_method',
+ ' do_something',
+ '',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for class method body ending with a blank' do
+ inspect_source(cop,
+ ['def Test.some_method',
+ ' do_something',
+ '',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for class body starting with a blank' do
+ inspect_source(cop,
+ ['class SomeClass',
+ '',
+ ' do_something',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'autocorrects class body containing only a blank' do
+ corrected = autocorrect_source(cop,
+ ['class SomeClass',
+ '',
+ 'end'])
+ expect(corrected).to eq ['class SomeClass',
+ 'end'].join("\n")
+ end
+
+ it 'registers an offense for module body starting with a blank' do
+ inspect_source(cop,
+ ['module SomeModule',
+ '',
+ ' do_something',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for class body ending with a blank' do
+ inspect_source(cop,
+ ['class SomeClass',
+ ' do_something',
+ '',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for module body ending with a blank' do
+ inspect_source(cop,
+ ['module SomeModule',
+ ' do_something',
+ '',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'is not fooled by single line methods' do
+ inspect_source(cop,
+ ['def some_method; do_something; end',
+ '',
+ 'something_else'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/empty_lines_spec.rb b/spec/rubocop/cop/style/empty_lines_spec.rb
new file mode 100644
index 0000000..9198ad3
--- /dev/null
+++ b/spec/rubocop/cop/style/empty_lines_spec.rb
@@ -0,0 +1,51 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::EmptyLines do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for consecutive empty lines' do
+ inspect_source(cop,
+ ['test = 5', '', '', '', 'top'])
+ expect(cop.offenses.size).to eq(2)
+ end
+
+ it 'auto-corrects consecutive empty lines' do
+ corrected = autocorrect_source(cop,
+ ['test = 5', '', '', '', 'top'])
+ expect(corrected).to eq ['test = 5', '', 'top'].join("\n")
+ end
+
+ it 'works when there are no tokens' do
+ inspect_source(cop,
+ ['#comment'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'handles comments' do
+ inspect_source(cop,
+ ['test', '', '#comment', 'top'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for empty lines in a string' do
+ inspect_source(cop, ['result = "test
+
+
+
+ string"'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for heredocs with empty lines inside' do
+ inspect_source(cop, ['str = <<-TEXT',
+ 'line 1',
+ '',
+ '',
+ 'line 2',
+ 'TEXT',
+ 'puts str'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/empty_literal_spec.rb b/spec/rubocop/cop/style/empty_literal_spec.rb
new file mode 100644
index 0000000..8b28039
--- /dev/null
+++ b/spec/rubocop/cop/style/empty_literal_spec.rb
@@ -0,0 +1,100 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::EmptyLiteral do
+ subject(:cop) { described_class.new }
+
+ describe 'Empty Array' do
+ it 'registers an offense for Array.new()' do
+ inspect_source(cop,
+ ['test = Array.new()'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Use array literal [] instead of Array.new.'])
+ end
+
+ it 'registers an offense for Array.new' do
+ inspect_source(cop,
+ ['test = Array.new'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Use array literal [] instead of Array.new.'])
+ end
+
+ it 'does not register an offense for Array.new(3)' do
+ inspect_source(cop,
+ ['test = Array.new(3)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects Array.new to []' do
+ new_source = autocorrect_source(cop, 'test = Array.new')
+ expect(new_source).to eq('test = []')
+ end
+ end
+
+ describe 'Empty Hash' do
+ it 'registers an offense for Hash.new()' do
+ inspect_source(cop,
+ ['test = Hash.new()'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Use hash literal {} instead of Hash.new.'])
+ end
+
+ it 'registers an offense for Hash.new' do
+ inspect_source(cop,
+ ['test = Hash.new'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Use hash literal {} instead of Hash.new.'])
+ end
+
+ it 'does not register an offense for Hash.new(3)' do
+ inspect_source(cop,
+ ['test = Hash.new(3)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for Hash.new { block }' do
+ inspect_source(cop,
+ ['test = Hash.new { block }'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects Hash.new to {}' do
+ new_source = autocorrect_source(cop, 'test = Hash.new')
+ expect(new_source).to eq('test = {}')
+ end
+ end
+
+ describe 'Empty String' do
+ it 'registers an offense for String.new()' do
+ inspect_source(cop,
+ ['test = String.new()'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(["Use string literal '' instead of String.new."])
+ end
+
+ it 'registers an offense for String.new' do
+ inspect_source(cop,
+ ['test = String.new'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(["Use string literal '' instead of String.new."])
+ end
+
+ it 'does not register an offense for String.new("top")' do
+ inspect_source(cop,
+ ['test = String.new("top")'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects String.new to empty string literal' do
+ new_source = autocorrect_source(cop, 'test = String.new')
+ expect(new_source).to eq("test = ''")
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/encoding_spec.rb b/spec/rubocop/cop/style/encoding_spec.rb
new file mode 100644
index 0000000..8908db4
--- /dev/null
+++ b/spec/rubocop/cop/style/encoding_spec.rb
@@ -0,0 +1,56 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::Encoding do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense when no encoding present', ruby: 1.9 do
+ inspect_source(cop, ['def foo() end'])
+
+ expect(cop.messages).to eq(
+ ['Missing utf-8 encoding comment.'])
+ end
+
+ it 'accepts encoding on first line', ruby: 1.9 do
+ inspect_source(cop, ['# encoding: utf-8',
+ 'def foo() end'])
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts encoding on second line when shebang present', ruby: 1.9 do
+ inspect_source(cop, ['#!/usr/bin/env ruby',
+ '# encoding: utf-8',
+ 'def foo() end'])
+
+ expect(cop.messages).to be_empty
+ end
+
+ it 'books an offense when encoding is in the wrong place', ruby: 1.9 do
+ inspect_source(cop, ['def foo() end',
+ '# encoding: utf-8'])
+
+ expect(cop.messages).to eq(
+ ['Missing utf-8 encoding comment.'])
+ end
+
+ it 'does not register an offense on Ruby 2.0', ruby: 2.0 do
+ inspect_source(cop, ['def foo() end'])
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts encoding inserted by magic_encoding gem', ruby: 1.9 do
+ inspect_source(cop, ['# -*- encoding : utf-8 -*-',
+ 'def foo() end'])
+
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts vim-style encoding comments', ruby: 1.9 do
+ inspect_source(cop, ['# vim:fileencoding=utf-8',
+ 'def foo() end'])
+ expect(cop.messages).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/end_block_spec.rb b/spec/rubocop/cop/style/end_block_spec.rb
new file mode 100644
index 0000000..804a6dc
--- /dev/null
+++ b/spec/rubocop/cop/style/end_block_spec.rb
@@ -0,0 +1,13 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::EndBlock do
+ subject(:cop) { described_class.new }
+
+ it 'reports an offense for an END block' do
+ src = ['END { test }']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ end
+end
diff --git a/spec/rubocop/cop/style/end_of_line_spec.rb b/spec/rubocop/cop/style/end_of_line_spec.rb
new file mode 100644
index 0000000..48c2acd
--- /dev/null
+++ b/spec/rubocop/cop/style/end_of_line_spec.rb
@@ -0,0 +1,47 @@
+# encoding: utf-8
+
+require 'spec_helper'
+require 'tempfile'
+
+describe Rubocop::Cop::Style::EndOfLine do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for CR+LF' do
+ inspect_source_file(cop, ['x=0', '', "y=1\r"])
+ expect(cop.messages).to eq(['Carriage return character detected.'])
+ end
+
+ it 'highlights the whole offendng line' do
+ inspect_source_file(cop, ['x=0', '', "y=1\r"])
+ expect(cop.highlights).to eq(["y=1\r"])
+ end
+
+ it 'registers an offense for CR at end of file' do
+ inspect_source_file(cop, ["x=0\r"])
+ expect(cop.messages).to eq(['Carriage return character detected.'])
+ end
+
+ context 'when there are many lines ending with CR+LF' do
+ it 'registers only one offense' do
+ inspect_source_file(cop, ['x=0', '', 'y=1'].join("\r\n"))
+ expect(cop.messages.size).to eq(1)
+ end
+ end
+
+ context 'when the default external encoding is US_ASCII' do
+ before(:each) do
+ @orig_encoding = Encoding.default_external
+ Encoding.default_external = Encoding::US_ASCII
+ end
+ after(:each) { Encoding.default_external = @orig_encoding }
+
+ it 'does not crash on UTF-8 encoded non-ascii characters' do
+ inspect_source_file(cop,
+ ['# encoding: UTF-8',
+ 'class Epd::ReportsController < EpdAreaController',
+ " 'terecht bij uw ROM-coördinator.'",
+ 'end'].join("\n"))
+ expect(cop.offenses).to be_empty
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/even_odd_spec.rb b/spec/rubocop/cop/style/even_odd_spec.rb
new file mode 100644
index 0000000..fb04147
--- /dev/null
+++ b/spec/rubocop/cop/style/even_odd_spec.rb
@@ -0,0 +1,75 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::EvenOdd do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for x % 2 == 0' do
+ inspect_source(cop,
+ ['x % 2 == 0'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages).to eq(['Replace with `Fixnum#even?`.'])
+ end
+
+ it 'registers an offense for x % 2 != 0' do
+ inspect_source(cop,
+ ['x % 2 != 0'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages).to eq(['Replace with `Fixnum#odd?`.'])
+ end
+
+ it 'registers an offense for (x % 2) == 0' do
+ inspect_source(cop,
+ ['(x % 2) == 0'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages).to eq(['Replace with `Fixnum#even?`.'])
+ end
+
+ it 'registers an offense for (x % 2) != 0' do
+ inspect_source(cop,
+ ['(x % 2) != 0'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages).to eq(['Replace with `Fixnum#odd?`.'])
+ end
+
+ it 'registers an offense for x % 2 == 1' do
+ inspect_source(cop,
+ ['x % 2 == 1'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages).to eq(['Replace with `Fixnum#odd?`.'])
+ end
+
+ it 'registers an offense for x % 2 != 1' do
+ inspect_source(cop,
+ ['x % 2 != 1'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages).to eq(['Replace with `Fixnum#even?`.'])
+ end
+
+ it 'registers an offense for (x % 2) == 1' do
+ inspect_source(cop,
+ ['(x % 2) == 1'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages).to eq(['Replace with `Fixnum#odd?`.'])
+ end
+
+ it 'registers an offense for (x % 2) != 1' do
+ inspect_source(cop,
+ ['(x % 2) != 1'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages).to eq(['Replace with `Fixnum#even?`.'])
+ end
+
+ it 'accepts x % 3 == 0' do
+ inspect_source(cop,
+ ['x % 3 == 0'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts x % 3 != 0' do
+ inspect_source(cop,
+ ['x % 3 != 0'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/file_name_spec.rb b/spec/rubocop/cop/style/file_name_spec.rb
new file mode 100644
index 0000000..24abe0a
--- /dev/null
+++ b/spec/rubocop/cop/style/file_name_spec.rb
@@ -0,0 +1,84 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::FileName do
+ subject(:cop) { described_class.new(config) }
+
+ let(:config) do
+ Rubocop::Config.new(
+ { 'AllCops' => { 'Include' => includes } },
+ '/some/.rubocop.yml'
+ )
+ end
+
+ let(:includes) { [] }
+ let(:source) { ['print 1'] }
+ let(:processed_source) { parse_source(source) }
+
+ before do
+ allow(processed_source.buffer)
+ .to receive(:name).and_return(filename)
+ _investigate(cop, processed_source)
+ end
+
+ context 'with camelCase file names ending in .rb' do
+ let(:filename) { '/some/dir/testCase.rb' }
+
+ it 'reports an offense' do
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+
+ context 'with camelCase file names without file extension' do
+ let(:filename) { '/some/dir/testCase' }
+
+ it 'reports an offense' do
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+
+ context 'with snake_case file names ending in .rb' do
+ let(:filename) { '/some/dir/test_case.rb' }
+
+ it 'reports an offense' do
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'with snake_case file names without file extension' do
+ let(:filename) { '/some/dir/test_case' }
+
+ it 'does not report an offense' do
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'with snake_case file names with non-rb extension' do
+ let(:filename) { '/some/dir/some_task.rake' }
+
+ it 'does not report an offense' do
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'with snake_case file names with multiple extensions' do
+ let(:filename) { 'some/dir/some_view.html.slim_spec.rb' }
+
+ it 'does not report an offense' do
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'when the file is specified in AllCops/Include' do
+ let(:includes) { ['**/Gemfile'] }
+
+ context 'with a non-snake_case file name' do
+ let(:filename) { '/some/dir/Gemfile' }
+
+ it 'does not report an offense' do
+ expect(cop.offenses).to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/final_newline_spec.rb b/spec/rubocop/cop/style/final_newline_spec.rb
new file mode 100644
index 0000000..dbab305
--- /dev/null
+++ b/spec/rubocop/cop/style/final_newline_spec.rb
@@ -0,0 +1,30 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::FinalNewline do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for missing final newline' do
+ source = ['x = 0', 'top']
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts a final newline' do
+ source = ['x = 0', 'top', '']
+ inspect_source(cop, source)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an empty file' do
+ source = ['']
+ inspect_source(cop, source)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects missing newline' do
+ new_source = autocorrect_source(cop, ['x = 0'])
+ expect(new_source).to eq(['x = 0', ''].join("\n"))
+ end
+end
diff --git a/spec/rubocop/cop/style/flip_flop_spec.rb b/spec/rubocop/cop/style/flip_flop_spec.rb
new file mode 100644
index 0000000..f604719
--- /dev/null
+++ b/spec/rubocop/cop/style/flip_flop_spec.rb
@@ -0,0 +1,23 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::FlipFlop do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for inclusive flip flops' do
+ inspect_source(cop,
+ ['DATA.each_line do |line|',
+ 'print line if (line =~ /begin/)..(line =~ /end/)',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for exclusive flip flops' do
+ inspect_source(cop,
+ ['DATA.each_line do |line|',
+ 'print line if (line =~ /begin/)...(line =~ /end/)',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+end
diff --git a/spec/rubocop/cop/style/for_spec.rb b/spec/rubocop/cop/style/for_spec.rb
new file mode 100644
index 0000000..6ef5f96
--- /dev/null
+++ b/spec/rubocop/cop/style/for_spec.rb
@@ -0,0 +1,105 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::For, :config do
+ subject(:cop) { described_class.new(config) }
+
+ context 'when each is the enforced style' do
+ let(:cop_config) { { 'EnforcedStyle' => 'each' } }
+
+ it 'registers an offense for for' do
+ inspect_source(cop,
+ ['def func',
+ ' for n in [1, 2, 3] do',
+ ' puts n',
+ ' end',
+ 'end'])
+ expect(cop.messages).to eq(['Prefer `each` over `for`.'])
+ expect(cop.highlights).to eq(['for'])
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' => 'for')
+ end
+
+ it 'registers an offense for opposite + correct style' do
+ inspect_source(cop,
+ ['def func',
+ ' for n in [1, 2, 3] do',
+ ' puts n',
+ ' end',
+ ' [1, 2, 3].each do |n|',
+ ' puts n',
+ ' end',
+ 'end'])
+ expect(cop.messages).to eq(['Prefer `each` over `for`.'])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'accepts multiline each' do
+ inspect_source(cop,
+ ['def func',
+ ' [1, 2, 3].each do |n|',
+ ' puts n',
+ ' end',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts :for' do
+ inspect_source(cop, ['[:for, :ala, :bala]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts def for' do
+ inspect_source(cop, ['def for; end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'when for is the enforced style' do
+ let(:cop_config) { { 'EnforcedStyle' => 'for' } }
+
+ it 'accepts for' do
+ inspect_source(cop,
+ ['def func',
+ ' for n in [1, 2, 3] do',
+ ' puts n',
+ ' end',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for multiline each' do
+ inspect_source(cop,
+ ['def func',
+ ' [1, 2, 3].each do |n|',
+ ' puts n',
+ ' end',
+ 'end'])
+ expect(cop.messages).to eq(['Prefer `for` over `each`.'])
+ expect(cop.highlights).to eq(['each'])
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' => 'each')
+ end
+
+ it 'registers an offense for correct + opposite style' do
+ inspect_source(cop,
+ ['def func',
+ ' for n in [1, 2, 3] do',
+ ' puts n',
+ ' end',
+ ' [1, 2, 3].each do |n|',
+ ' puts n',
+ ' end',
+ 'end'])
+ expect(cop.messages).to eq(['Prefer `for` over `each`.'])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'accepts single line each' do
+ inspect_source(cop,
+ ['def func',
+ ' [1, 2, 3].each { |n| puts n }',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/format_string_spec.rb b/spec/rubocop/cop/style/format_string_spec.rb
new file mode 100644
index 0000000..3b44c78
--- /dev/null
+++ b/spec/rubocop/cop/style/format_string_spec.rb
@@ -0,0 +1,136 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::FormatString, :config do
+ subject(:cop) { described_class.new(config) }
+
+ context 'when enforced style is sprintf' do
+ let(:cop_config) { { 'EnforcedStyle' => 'sprintf' } }
+ it 'registers an offense for a string followed by something' do
+ inspect_source(cop,
+ ['puts "%d" % 10'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Favor `sprintf` over `String#%`.'])
+ end
+
+ it 'registers an offense for something followed by an array' do
+ inspect_source(cop,
+ ['puts x % [10, 11]'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Favor `sprintf` over `String#%`.'])
+ end
+
+ it 'does not register an offense for numbers' do
+ inspect_source(cop,
+ ['puts 10 % 4'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for ambiguous cases' do
+ inspect_source(cop,
+ ['puts x % 4'])
+ expect(cop.offenses).to be_empty
+
+ inspect_source(cop,
+ ['puts x % Y'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'works if the first operand contains embedded expressions' do
+ inspect_source(cop,
+ ['puts "#{x * 5} %d #{@test}" % 10'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Favor `sprintf` over `String#%`.'])
+ end
+
+ it 'registers an offense for format' do
+ inspect_source(cop,
+ ['format(something, a, b)'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Favor `sprintf` over `format`.'])
+ end
+ end
+
+ context 'when enforced style is format' do
+ let(:cop_config) { { 'EnforcedStyle' => 'format' } }
+
+ it 'registers an offense for a string followed by something' do
+ inspect_source(cop,
+ ['puts "%d" % 10'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Favor `format` over `String#%`.'])
+ end
+
+ it 'registers an offense for something followed by an array' do
+ inspect_source(cop,
+ ['puts x % [10, 11]'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Favor `format` over `String#%`.'])
+ end
+
+ it 'does not register an offense for numbers' do
+ inspect_source(cop,
+ ['puts 10 % 4'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for ambiguous cases' do
+ inspect_source(cop,
+ ['puts x % 4'])
+ expect(cop.offenses).to be_empty
+
+ inspect_source(cop,
+ ['puts x % Y'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'works if the first operand contains embedded expressions' do
+ inspect_source(cop,
+ ['puts "#{x * 5} %d #{@test}" % 10'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Favor `format` over `String#%`.'])
+ end
+
+ it 'registers an offense for sprintf' do
+ inspect_source(cop,
+ ['sprintf(something, a, b)'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Favor `format` over `sprintf`.'])
+ end
+ end
+
+ context 'when enforced style is percent' do
+ let(:cop_config) { { 'EnforcedStyle' => 'percent' } }
+
+ it 'registers an offense for format' do
+ inspect_source(cop,
+ ['format(something, a, b)'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Favor `String#%` over `format`.'])
+ end
+
+ it 'registers an offense for sprintf' do
+ inspect_source(cop,
+ ['sprintf(something, a, b)'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Favor `String#%` over `sprintf`.'])
+ end
+
+ it 'accepts String#%' do
+ inspect_source(cop,
+ ['puts "%d" % 10'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/global_vars_spec.rb b/spec/rubocop/cop/style/global_vars_spec.rb
new file mode 100644
index 0000000..e5dd8a0
--- /dev/null
+++ b/spec/rubocop/cop/style/global_vars_spec.rb
@@ -0,0 +1,34 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::GlobalVars, :config do
+ cop_config = {
+ 'AllowedVariables' => ['$allowed']
+ }
+
+ subject(:cop) { described_class.new(config) }
+ let(:cop_config) { cop_config }
+
+ it 'registers an offense for $custom' do
+ inspect_source(cop, ['puts $custom'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'allows user whitelisted variables' do
+ inspect_source(cop, ['puts $allowed'])
+ expect(cop.offenses).to be_empty
+ end
+
+ described_class::BUILT_IN_VARS.each do |var|
+ it "does not register an offense for built-in variable #{var}" do
+ inspect_source(cop, ["puts #{var}"])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ it 'does not register an offense for backrefs like $1' do
+ inspect_source(cop, ['puts $1'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/guard_clause_spec.rb b/spec/rubocop/cop/style/guard_clause_spec.rb
new file mode 100644
index 0000000..173a4ce
--- /dev/null
+++ b/spec/rubocop/cop/style/guard_clause_spec.rb
@@ -0,0 +1,77 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::GuardClause do
+ let(:cop) { described_class.new }
+
+ it 'reports an offense if method body is if without else' do
+ src = ['def func',
+ ' if something',
+ ' work',
+ ' work_more',
+ ' end',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'reports an offense if method body ends with if without else' do
+ src = ['def func',
+ ' test',
+ ' if something',
+ ' work',
+ ' work_more',
+ ' end',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts a method which body is if with else' do
+ src = ['def func',
+ ' if something',
+ ' work',
+ ' work_more',
+ ' else',
+ ' test',
+ ' end',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a method which body does not end with if' do
+ src = ['def func',
+ ' if something',
+ ' work',
+ ' work_more',
+ ' end',
+ ' test',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a method which body does not end with if' do
+ src = ['def func',
+ ' if something',
+ ' work',
+ ' work_more',
+ ' end',
+ ' test',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a method whose body is an if with a one-line body' do
+ src = ['def func',
+ ' if something',
+ ' work',
+ ' end',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/hash_syntax_spec.rb b/spec/rubocop/cop/style/hash_syntax_spec.rb
new file mode 100644
index 0000000..befb98d
--- /dev/null
+++ b/spec/rubocop/cop/style/hash_syntax_spec.rb
@@ -0,0 +1,133 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::HashSyntax, :config do
+ subject(:cop) { described_class.new(config) }
+
+ context 'configured to enforce ruby19 style' do
+ let(:config) do
+ Rubocop::Config.new('HashSyntax' => {
+ 'EnforcedStyle' => 'ruby19',
+ 'SupportedStyles' => %w(ruby19 hash_rockets)
+ },
+ 'SpaceAroundOperators' => { 'Enabled' => true })
+ end
+
+ it 'registers offense for hash rocket syntax when new is possible' do
+ inspect_source(cop, ['x = { :a => 0 }'])
+ expect(cop.messages).to eq(['Use the new Ruby 1.9 hash syntax.'])
+ expect(cop.config_to_allow_offenses)
+ .to eq('EnforcedStyle' => 'hash_rockets')
+ end
+
+ it 'registers an offense for mixed syntax when new is possible' do
+ inspect_source(cop, ['x = { :a => 0, b: 1 }'])
+ expect(cop.messages).to eq(['Use the new Ruby 1.9 hash syntax.'])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'registers an offense for hash rockets in method calls' do
+ inspect_source(cop, ['func(3, :a => 0)'])
+ expect(cop.messages).to eq(['Use the new Ruby 1.9 hash syntax.'])
+ end
+
+ it 'accepts hash rockets when keys have different types' do
+ inspect_source(cop, ['x = { :a => 0, "b" => 1 }'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts hash rockets when keys have whitespaces in them' do
+ inspect_source(cop, ['x = { :"t o" => 0 }'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts hash rockets when keys have special symbols in them' do
+ inspect_source(cop, ['x = { :"\tab" => 1 }'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts hash rockets when keys start with a digit' do
+ inspect_source(cop, ['x = { :"1" => 1 }'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'registers offense when keys start with an uppercase letter' do
+ inspect_source(cop, ['x = { :A => 0 }'])
+ expect(cop.messages).to eq(['Use the new Ruby 1.9 hash syntax.'])
+ end
+
+ it 'accepts new syntax in a hash literal' do
+ inspect_source(cop, ['x = { a: 0, b: 1 }'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts new syntax in method calls' do
+ inspect_source(cop, ['func(3, a: 0)'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'auto-corrects old to new style' do
+ new_source = autocorrect_source(cop, '{ :a => 1, :b => 2}')
+ expect(new_source).to eq('{ a: 1, b: 2}')
+ end
+
+ it 'auto-corrects even if it interferes with SpaceAroundOperators' do
+ # Clobbering caused by two cops changing in the same range is dealt with
+ # by the auto-correct loop, so there's no reason to avoid a change.
+ new_source = autocorrect_source(cop, '{ :a=>1, :b=>2 }')
+ expect(new_source).to eq('{ a: 1, b: 2 }')
+ end
+
+ context 'with SpaceAroundOperators disabled' do
+ let(:config) do
+ Rubocop::Config.new('HashSyntax' => {
+ 'EnforcedStyle' => 'ruby19',
+ 'SupportedStyles' => %w(ruby19 hash_rockets)
+ },
+ 'SpaceAroundOperators' => { 'Enabled' => false })
+ end
+
+ it 'auto-corrects even if there is no space around =>' do
+ new_source = autocorrect_source(cop, '{ :a=>1, :b=>2 }')
+ expect(new_source).to eq('{ a: 1, b: 2 }')
+ end
+ end
+ end
+
+ context 'configured to enforce hash rockets style' do
+ let(:cop_config) { { 'EnforcedStyle' => 'hash_rockets' } }
+
+ it 'registers offense for Ruby 1.9 style' do
+ inspect_source(cop, ['x = { a: 0 }'])
+ expect(cop.messages).to eq(['Always use hash rockets in hashes.'])
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' => 'ruby19')
+ end
+
+ it 'registers an offense for mixed syntax' do
+ inspect_source(cop, ['x = { :a => 0, b: 1 }'])
+ expect(cop.messages).to eq(['Always use hash rockets in hashes.'])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'registers an offense for 1.9 style in method calls' do
+ inspect_source(cop, ['func(3, a: 0)'])
+ expect(cop.messages).to eq(['Always use hash rockets in hashes.'])
+ end
+
+ it 'accepts hash rockets in a hash literal' do
+ inspect_source(cop, ['x = { :a => 0, :b => 1 }'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts hash rockets in method calls' do
+ inspect_source(cop, ['func(3, :a => 0)'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'auto-corrects new style to hash rockets' do
+ new_source = autocorrect_source(cop, '{ a: 1, b: 2}')
+ expect(new_source).to eq('{ :a => 1, :b => 2}')
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/if_unless_modifier_spec.rb b/spec/rubocop/cop/style/if_unless_modifier_spec.rb
new file mode 100644
index 0000000..fdcb866
--- /dev/null
+++ b/spec/rubocop/cop/style/if_unless_modifier_spec.rb
@@ -0,0 +1,146 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::IfUnlessModifier do
+ include StatementModifierHelper
+
+ subject(:cop) { described_class.new(config) }
+ let(:config) do
+ hash = { 'LineLength' => { 'Max' => 79 } }
+ Rubocop::Config.new(hash)
+ end
+
+ it 'registers an offense for multiline if that fits on one line' do
+ # This if statement fits exactly on one line if written as a
+ # modifier.
+ condition = 'a' * 38
+ body = 'b' * 35
+ expect(" #{body} if #{condition}".length).to eq(79)
+
+ inspect_source(cop,
+ [" if #{condition}",
+ " #{body}",
+ ' end'])
+ expect(cop.messages).to eq(
+ ['Favor modifier `if` usage when having a single-line' \
+ ' body. Another good alternative is the usage of control flow' \
+ ' `&&`/`||`.'])
+ end
+
+ it 'registers an offense for short multiline if near an else etc' do
+ inspect_source(cop,
+ ['if x',
+ ' y',
+ 'elsif x1',
+ ' y1',
+ 'else',
+ ' z',
+ 'end',
+ 'n = a ? 0 : 1',
+ 'm = 3 if m0',
+ '',
+ 'if a',
+ ' b',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it "accepts multiline if that doesn't fit on one line" do
+ check_too_long(cop, 'if')
+ end
+
+ it 'accepts multiline if whose body is more than one line' do
+ check_short_multiline(cop, 'if')
+ end
+
+ it 'registers an offense for multiline unless that fits on one line' do
+ inspect_source(cop, ['unless a',
+ ' b',
+ 'end'])
+ expect(cop.messages).to eq(
+ ['Favor modifier `unless` usage when having a single-line' \
+ ' body. Another good alternative is the usage of control flow' \
+ ' `&&`/`||`.'])
+ end
+
+ it 'accepts code with EOL comment since user might want to keep it' do
+ inspect_source(cop, ['unless a',
+ ' b # A comment',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts if-else-end' do
+ inspect_source(cop,
+ ['if args.last.is_a? Hash then args.pop else ' \
+ 'Hash.new end'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts an empty condition' do
+ check_empty(cop, 'if')
+ check_empty(cop, 'unless')
+ end
+
+ it 'accepts if/elsif' do
+ inspect_source(cop, ['if test',
+ ' something',
+ 'elsif test2',
+ ' something_else',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ context 'with implicit match conditional' do
+ let(:source) do
+ [
+ " if #{conditional}",
+ " #{body}",
+ ' end'
+ ]
+ end
+
+ let(:body) { 'b' * 35 }
+
+ context 'when a multiline if fits on one line' do
+ let(:conditional) { "/#{'a' * 36}/" }
+
+ it 'registers an offense' do
+ expect(" #{body} if #{conditional}".length).to eq(79)
+
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+
+ context "when a multiline if doesn't fit on one line" do
+ let(:conditional) { "/#{'a' * 37}/" }
+
+ it 'accepts' do
+ expect(" #{body} if #{conditional}".length).to eq(80)
+
+ inspect_source(cop, source)
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'when the maximum line length is specified by the cop itself' do
+ let(:config) do
+ hash = {
+ 'LineLength' => { 'Max' => 100 },
+ 'IfUnlessModifier' => { 'MaxLineLength' => 79 }
+ }
+ Rubocop::Config.new(hash)
+ end
+
+ it "accepts multiline if that doesn't fit on one line" do
+ check_too_long(cop, 'if')
+ end
+
+ it "accepts multiline unless that doesn't fit on one line" do
+ check_too_long(cop, 'unless')
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/if_with_semicolon_spec.rb b/spec/rubocop/cop/style/if_with_semicolon_spec.rb
new file mode 100644
index 0000000..4f93fa3
--- /dev/null
+++ b/spec/rubocop/cop/style/if_with_semicolon_spec.rb
@@ -0,0 +1,19 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::IfWithSemicolon do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for one line if/;/end' do
+ inspect_source(cop, ['if cond; run else dont end'])
+ expect(cop.messages).to eq(
+ ['Never use if x; Use the ternary operator instead.'])
+ end
+
+ it 'can handle modifier conditionals' do
+ inspect_source(cop, ['class Hash',
+ 'end if RUBY_VERSION < "1.8.7"'])
+ expect(cop.messages).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/indent_array_spec.rb b/spec/rubocop/cop/style/indent_array_spec.rb
new file mode 100644
index 0000000..472dd30
--- /dev/null
+++ b/spec/rubocop/cop/style/indent_array_spec.rb
@@ -0,0 +1,136 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::IndentArray do
+ subject(:cop) { described_class.new }
+
+ it 'accepts multi-assignments' do
+ inspect_source(cop, 'a, b = b, a')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts correctly indented only element' do
+ inspect_source(cop,
+ ['a << [',
+ ' 1',
+ ']'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for incorrectly indented only element' do
+ inspect_source(cop,
+ ['a << [',
+ ' 1',
+ ']'])
+ expect(cop.highlights).to eq(['1'])
+ end
+
+ it 'auto-corrects incorrectly indented only element' do
+ corrected = autocorrect_source(cop, ['a << [',
+ ' 1',
+ ']'])
+ expect(corrected).to eq ['a << [',
+ ' 1',
+ ']'].join("\n")
+ end
+
+ it 'accepts correctly indented first element' do
+ inspect_source(cop,
+ ['[',
+ ' x,',
+ ' y',
+ ']'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for incorrectly indented first element' do
+ inspect_source(cop,
+ ['[',
+ 'x,',
+ ' y',
+ ']'])
+ expect(cop.highlights).to eq(['x'])
+ end
+
+ it 'accepts several elements per line' do
+ inspect_source(cop,
+ ['a = [',
+ ' 1, 2',
+ ']'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a first element on the same line as the left bracket' do
+ inspect_source(cop,
+ ['a = ["a",',
+ ' "b"]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts single line array' do
+ inspect_source(cop,
+ ['a = [1, 2]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an empty array' do
+ inspect_source(cop,
+ ['a = []'])
+ expect(cop.offenses).to be_empty
+ end
+
+ context 'when array is method argument' do
+ context 'and arguments are surrounded by parentheses' do
+ it 'accepts normal indentation for first argument' do
+ inspect_source(cop,
+ ['func([',
+ ' 1',
+ '])'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for incorrect indentation' do
+ inspect_source(cop,
+ ['func([',
+ ' 1',
+ ' ])'])
+ expect(cop.messages)
+ .to eq(['Use 2 spaces for indentation in an array, relative to ' \
+ 'the start of the line where the left bracket is.'])
+ end
+
+ it 'accepts normal indentation for second argument' do
+ inspect_source(cop,
+ ['body.should have_tag("input", [',
+ ' :name])'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'and arguments are not surrounded by parentheses' do
+ it 'accepts single line array' do
+ inspect_source(cop,
+ ['func x, [1, 2]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a correctly indented multi-line array' do
+ inspect_source(cop,
+ ['func x, [',
+ ' 1, 2]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for incorrectly indented multi-line array' do
+ inspect_source(cop,
+ ['func x, [',
+ ' 1, 2]'])
+ expect(cop.messages)
+ .to eq(['Use 2 spaces for indentation in an array, relative to ' \
+ 'the start of the line where the left bracket is.'])
+ expect(cop.highlights).to eq(['1'])
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/indent_hash_spec.rb b/spec/rubocop/cop/style/indent_hash_spec.rb
new file mode 100644
index 0000000..3713dd9
--- /dev/null
+++ b/spec/rubocop/cop/style/indent_hash_spec.rb
@@ -0,0 +1,310 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::IndentHash do
+ subject(:cop) { described_class.new(config) }
+ let(:config) do
+ supported_styles = {
+ 'SupportedStyles' => %w(special_inside_parentheses consistent)
+ }
+ Rubocop::Config.new('AlignHash' => align_hash_config,
+ 'IndentHash' => cop_config.merge(supported_styles))
+ end
+ let(:align_hash_config) do
+ {
+ 'Enabled' => true,
+ 'EnforcedColonStyle' => 'key',
+ 'EnforcedHashRocketStyle' => 'key'
+ }
+ end
+ let(:cop_config) { { 'EnforcedStyle' => 'special_inside_parentheses' } }
+
+ context 'when the AlignHash style is separator for :' do
+ let(:align_hash_config) do
+ {
+ 'Enabled' => true,
+ 'EnforcedColonStyle' => 'separator',
+ 'EnforcedHashRocketStyle' => 'key'
+ }
+ end
+
+ it 'accepts correctly indented first pair' do
+ inspect_source(cop,
+ ['a << {',
+ ' a: 1,',
+ ' aaa: 222',
+ '}'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for incorrectly indented first pair with :' do
+ inspect_source(cop,
+ ['a << {',
+ ' a: 1,',
+ ' aaa: 222',
+ '}'])
+ expect(cop.highlights).to eq(['a: 1'])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+ end
+
+ context 'when the AlignHash style is separator for =>' do
+ let(:align_hash_config) do
+ {
+ 'Enabled' => true,
+ 'EnforcedColonStyle' => 'key',
+ 'EnforcedHashRocketStyle' => 'separator'
+ }
+ end
+
+ it 'accepts correctly indented first pair' do
+ inspect_source(cop,
+ ['a << {',
+ " 'a' => 1,",
+ " 'aaa' => 222",
+ '}'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for incorrectly indented first pair with =>' do
+ inspect_source(cop,
+ ['a << {',
+ " 'a' => 1,",
+ " 'aaa' => 222",
+ '}'])
+ expect(cop.highlights).to eq(["'a' => 1"])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+ end
+
+ context 'when hash is operand' do
+ it 'accepts correctly indented first pair' do
+ inspect_source(cop,
+ ['a << {',
+ ' a: 1',
+ '}'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for incorrectly indented first pair' do
+ inspect_source(cop,
+ ['a << {',
+ ' a: 1',
+ '}'])
+ expect(cop.highlights).to eq(['a: 1'])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'auto-corrects incorrectly indented first pair' do
+ corrected = autocorrect_source(cop, ['a << {',
+ ' a: 1',
+ '}'])
+ expect(corrected).to eq ['a << {',
+ ' a: 1',
+ '}'].join("\n")
+ end
+ end
+
+ context 'when hash is argument to setter' do
+ it 'accepts correctly indented first pair' do
+ inspect_source(cop,
+ [' config.rack_cache = {',
+ ' :metastore => "rails:/",',
+ ' :entitystore => "rails:/",',
+ ' :verbose => false',
+ ' }'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for incorrectly indented first pair' do
+ inspect_source(cop,
+ [' config.rack_cache = {',
+ ' :metastore => "rails:/",',
+ ' :entitystore => "rails:/",',
+ ' :verbose => false',
+ ' }'])
+ expect(cop.highlights).to eq([':metastore => "rails:/"'])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+ end
+
+ context 'when hash is right hand side in assignment' do
+ it 'registers an offense for incorrectly indented first pair' do
+ inspect_source(cop, ['a = {',
+ ' a: 1,',
+ ' b: 2,',
+ ' c: 3',
+ '}'])
+ expect(cop.messages)
+ .to eq(['Use 2 spaces for indentation in a hash, relative to the ' \
+ 'start of the line where the left curly brace is.'])
+ expect(cop.highlights).to eq(['a: 1'])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'auto-corrects incorrectly indented first pair' do
+ corrected = autocorrect_source(cop, ['a = {',
+ ' a: 1,',
+ ' b: 2,',
+ ' c: 3',
+ '}'])
+ expect(corrected).to eq ['a = {',
+ ' a: 1,',
+ ' b: 2,',
+ ' c: 3',
+ '}'].join("\n")
+ end
+
+ it 'accepts correctly indented first pair' do
+ inspect_source(cop,
+ ['a = {',
+ ' a: 1',
+ '}'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts several pairs per line' do
+ inspect_source(cop,
+ ['a = {',
+ ' a: 1, b: 2',
+ '}'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a first pair on the same line as the left brace' do
+ inspect_source(cop,
+ ['a = { "a" => 1,',
+ ' "b" => 2 }'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts single line hash' do
+ inspect_source(cop,
+ ['a = { a: 1, b: 2 }'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an empty hash' do
+ inspect_source(cop,
+ ['a = {}'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'when hash is method argument' do
+ context 'and arguments are surrounded by parentheses' do
+ context 'and EnforcedStyle is special_inside_parentheses' do
+ it 'accepts special indentation for first argument' do
+ inspect_source(cop,
+ ['func({',
+ ' a: 1',
+ ' })'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for incorrect indentation' do
+ inspect_source(cop,
+ ['func({',
+ ' a: 1',
+ '})'])
+ expect(cop.messages)
+ .to eq(['Use 2 spaces for indentation in a hash, relative to the' \
+ ' first position after the preceding left parenthesis.'])
+ expect(cop.config_to_allow_offenses)
+ .to eq('EnforcedStyle' => 'consistent')
+ end
+
+ it 'auto-corrects incorrectly indented first pair' do
+ corrected = autocorrect_source(cop, ['func({',
+ ' a: 1',
+ '})'])
+ expect(corrected).to eq ['func({',
+ ' a: 1',
+ '})'].join("\n")
+ end
+
+ it 'accepts special indentation for second argument' do
+ inspect_source(cop,
+ ['body.should have_tag("input", :attributes => {',
+ ' :name => /q\[(id_eq)\]/ })'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts normal indentation for hash within hash' do
+ inspect_source(cop,
+ ['scope = scope.where(',
+ ' klass.table_name => {',
+ ' reflection.type => model.base_class.sti_name',
+ ' }',
+ ')'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'and EnforcedStyle is consistent' do
+ let(:cop_config) { { 'EnforcedStyle' => 'consistent' } }
+
+ it 'accepts normal indentation for first argument' do
+ inspect_source(cop,
+ ['func({',
+ ' a: 1',
+ '})'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for incorrect indentation' do
+ inspect_source(cop,
+ ['func({',
+ ' a: 1',
+ ' })'])
+ expect(cop.messages)
+ .to eq(['Use 2 spaces for indentation in a hash, relative to the' \
+ ' start of the line where the left curly brace is.'])
+ expect(cop.config_to_allow_offenses)
+ .to eq('EnforcedStyle' => 'special_inside_parentheses')
+ end
+
+ it 'accepts normal indentation for second argument' do
+ inspect_source(cop,
+ ['body.should have_tag("input", :attributes => {',
+ ' :name => /q\[(id_eq)\]/ })'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+ end
+
+ context 'and argument are not surrounded by parentheses' do
+ it 'accepts braceless hash' do
+ inspect_source(cop,
+ ['func a: 1, b: 2'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts single line hash with braces' do
+ inspect_source(cop,
+ ['func x, { a: 1, b: 2 }'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a correctly indented multi-line hash with braces' do
+ inspect_source(cop,
+ ['func x, {',
+ ' a: 1, b: 2 }'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for incorrectly indented multi-line hash ' \
+ 'with braces' do
+ inspect_source(cop,
+ ['func x, {',
+ ' a: 1, b: 2 }'])
+ expect(cop.messages)
+ .to eq(['Use 2 spaces for indentation in a hash, relative to the ' \
+ 'start of the line where the left curly brace is.'])
+ expect(cop.highlights).to eq(['a: 1'])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/indentation_consistency_spec.rb b/spec/rubocop/cop/style/indentation_consistency_spec.rb
new file mode 100644
index 0000000..19aa092
--- /dev/null
+++ b/spec/rubocop/cop/style/indentation_consistency_spec.rb
@@ -0,0 +1,510 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::IndentationConsistency do
+ subject(:cop) { described_class.new }
+
+ context 'with if statement' do
+ it 'registers an offense for bad indentation in an if body' do
+ inspect_source(cop,
+ ['if cond',
+ ' func',
+ ' func',
+ 'end'])
+ expect(cop.messages).to eq(['Inconsistent indentation detected.'])
+ end
+
+ it 'registers an offense for bad indentation in an else body' do
+ inspect_source(cop,
+ ['if cond',
+ ' func1',
+ 'else',
+ ' func2',
+ ' func2',
+ 'end'])
+ expect(cop.messages).to eq(['Inconsistent indentation detected.'])
+ end
+
+ it 'registers an offense for bad indentation in an elsif body' do
+ inspect_source(cop,
+ ['if a1',
+ ' b1',
+ 'elsif a2',
+ ' b2',
+ 'b3',
+ 'else',
+ ' c',
+ 'end'])
+ expect(cop.messages).to eq(['Inconsistent indentation detected.'])
+ end
+
+ it 'autocorrects bad indentation' do
+ corrected = autocorrect_source(cop,
+ ['if a1',
+ ' b1',
+ 'elsif a2',
+ ' b2',
+ ' b3',
+ 'else',
+ ' c',
+ 'end'])
+ expect(corrected).to eq ['if a1',
+ ' b1',
+ 'elsif a2',
+ ' b2',
+ ' b3',
+ 'else',
+ ' c',
+ 'end'].join("\n")
+ end
+
+ it 'accepts a one line if statement' do
+ inspect_source(cop,
+ ['if cond then func1 else func2 end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a correctly aligned if/elsif/else/end' do
+ inspect_source(cop,
+ ['if a1',
+ ' b1',
+ 'elsif a2',
+ ' b2',
+ 'else',
+ ' c',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts if/elsif/else/end laid out as a table' do
+ inspect_source(cop,
+ ['if @io == $stdout then str << "$stdout"',
+ 'elsif @io == $stdin then str << "$stdin"',
+ 'elsif @io == $stderr then str << "$stderr"',
+ 'else str << @io.class.to_s',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts if/then/else/end laid out as another table' do
+ inspect_source(cop,
+ ["if File.exist?('config.save')",
+ 'then ConfigTable.load',
+ 'else ConfigTable.new',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an empty if' do
+ inspect_source(cop,
+ ['if a',
+ 'else',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an if in assignment with end aligned with variable' do
+ inspect_source(cop,
+ ['var = if a',
+ ' 0',
+ 'end',
+ '@var = if a',
+ ' 0',
+ 'end',
+ '$var = if a',
+ ' 0',
+ 'end',
+ 'var ||= if a',
+ ' 0',
+ 'end',
+ 'var &&= if a',
+ ' 0',
+ 'end',
+ 'var -= if a',
+ ' 0',
+ 'end',
+ 'VAR = if a',
+ ' 0',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an if/else in assignment with end aligned with variable' do
+ inspect_source(cop,
+ ['var = if a',
+ ' 0',
+ 'else',
+ ' 1',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an if/else in assignment with end aligned with variable ' \
+ 'and chaining after the end' do
+ inspect_source(cop,
+ ['var = if a',
+ ' 0',
+ 'else',
+ ' 1',
+ 'end.abc.join("")'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an if/else in assignment with end aligned with variable ' \
+ 'and chaining with a block after the end' do
+ inspect_source(cop,
+ ['var = if a',
+ ' 0',
+ 'else',
+ ' 1',
+ 'end.abc.tap {}'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an if in assignment with end aligned with if' do
+ inspect_source(cop,
+ ['var = if a',
+ ' 0',
+ ' end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an if/else in assignment with end aligned with if' do
+ inspect_source(cop,
+ ['var = if a',
+ ' 0',
+ ' else',
+ ' 1',
+ ' end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an if/else in assignment on next line with end aligned ' \
+ 'with if' do
+ inspect_source(cop,
+ ['var =',
+ ' if a',
+ ' 0',
+ ' else',
+ ' 1',
+ ' end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an if/else branches with rescue clauses' do
+ # Because of how the rescue clauses come out of Parser, these are
+ # special and need to be tested.
+ inspect_source(cop,
+ ['if a',
+ ' a rescue nil',
+ 'else',
+ ' a rescue nil',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'with unless' do
+ it 'registers an offense for bad indentation in an unless body' do
+ inspect_source(cop,
+ ['unless cond',
+ ' func',
+ ' func',
+ 'end'])
+ expect(cop.messages).to eq(['Inconsistent indentation detected.'])
+ end
+
+ it 'accepts an empty unless' do
+ inspect_source(cop,
+ ['unless a',
+ 'else',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'with case' do
+ it 'registers an offense for bad indentation in a case/when body' do
+ inspect_source(cop,
+ ['case a',
+ 'when b',
+ ' c',
+ ' d',
+ 'end'])
+ expect(cop.messages).to eq(['Inconsistent indentation detected.'])
+ end
+
+ it 'registers an offense for bad indentation in a case/else body' do
+ inspect_source(cop,
+ ['case a',
+ 'when b',
+ ' c',
+ 'when d',
+ ' e',
+ 'else',
+ ' f',
+ ' g',
+ 'end'])
+ expect(cop.messages).to eq(['Inconsistent indentation detected.'])
+ end
+
+ it 'accepts correctly indented case/when/else' do
+ inspect_source(cop,
+ ['case a',
+ 'when b',
+ ' c',
+ ' c',
+ 'when d',
+ 'else',
+ ' f',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts case/when/else laid out as a table' do
+ inspect_source(cop,
+ ['case sexp.loc.keyword.source',
+ "when 'if' then cond, body, _else = *sexp",
+ "when 'unless' then cond, _else, body = *sexp",
+ 'else cond, body = *sexp',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts case/when/else with then beginning a line' do
+ inspect_source(cop,
+ ['case sexp.loc.keyword.source',
+ "when 'if'",
+ 'then cond, body, _else = *sexp',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts indented when/else plus indented body' do
+ # "Indent when as deep as case" is the job of another cop.
+ inspect_source(cop,
+ ['case code_type',
+ " when 'ruby', 'sql', 'plain'",
+ ' code_type',
+ " when 'erb'",
+ " 'ruby; html-script: true'",
+ ' when "html"',
+ " 'xml'",
+ ' else',
+ " 'plain'",
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'with while/until' do
+ it 'registers an offense for bad indentation in a while body' do
+ inspect_source(cop,
+ ['while cond',
+ ' func',
+ ' func',
+ 'end'])
+ expect(cop.messages).to eq(['Inconsistent indentation detected.'])
+ end
+
+ it 'registers an offense for bad indentation in begin/end/while' do
+ inspect_source(cop,
+ ['something = begin',
+ ' func1',
+ ' func2',
+ 'end while cond'])
+ expect(cop.messages).to eq(['Inconsistent indentation detected.'])
+ end
+
+ it 'registers an offense for bad indentation in an until body' do
+ inspect_source(cop,
+ ['until cond',
+ ' func',
+ ' func',
+ 'end'])
+ expect(cop.messages).to eq(['Inconsistent indentation detected.'])
+ end
+
+ it 'accepts an empty while' do
+ inspect_source(cop,
+ ['while a',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'with for' do
+ it 'registers an offense for bad indentation in a for body' do
+ inspect_source(cop,
+ ['for var in 1..10',
+ ' func',
+ 'func',
+ 'end'])
+ expect(cop.messages).to eq(['Inconsistent indentation detected.'])
+ end
+
+ it 'accepts an empty for' do
+ inspect_source(cop,
+ ['for var in 1..10',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'with def/defs' do
+ it 'registers an offense for bad indentation in a def body' do
+ inspect_source(cop,
+ ['def test',
+ ' func1',
+ ' func2',
+ 'end'])
+ expect(cop.messages)
+ .to eq(['Inconsistent indentation detected.'])
+ end
+
+ it 'registers an offense for bad indentation in a defs body' do
+ inspect_source(cop,
+ ['def self.test',
+ ' func',
+ ' func',
+ 'end'])
+ expect(cop.messages).to eq(['Inconsistent indentation detected.'])
+ end
+
+ it 'accepts an empty def body' do
+ inspect_source(cop,
+ ['def test',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an empty defs body' do
+ inspect_source(cop,
+ ['def self.test',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'with class' do
+ it 'registers an offense for bad indentation in a class body' do
+ inspect_source(cop,
+ ['class Test',
+ ' def func1',
+ ' end',
+ ' def func2',
+ ' end',
+ 'end'])
+ expect(cop.messages)
+ .to eq(['Inconsistent indentation detected.'])
+ end
+
+ it 'accepts an empty class body' do
+ inspect_source(cop,
+ ['class Test',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts indented public, protected, and private' do
+ inspect_source(cop,
+ ['class Test',
+ ' public',
+ '',
+ ' def e',
+ ' end',
+ '',
+ ' protected',
+ '',
+ ' def f',
+ ' end',
+ '',
+ ' private',
+ '',
+ ' def g',
+ ' end',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for bad indentation in def but not for ' \
+ 'outdented public, protected, and private' do
+ inspect_source(cop,
+ ['class Test',
+ 'public',
+ '',
+ ' def e',
+ ' end',
+ '',
+ 'protected',
+ '',
+ ' def f',
+ ' end',
+ '',
+ 'private',
+ '',
+ ' def g',
+ ' end',
+ 'end'])
+ expect(cop.messages).to eq(['Inconsistent indentation detected.'])
+ expect(cop.highlights).to eq(["def g\n end"])
+ end
+ end
+
+ context 'with module' do
+ it 'registers an offense for bad indentation in a module body' do
+ inspect_source(cop,
+ ['module Test',
+ ' def func1',
+ ' end',
+ ' def func2',
+ ' end',
+ 'end'])
+ expect(cop.messages).to eq(['Inconsistent indentation detected.'])
+ end
+
+ it 'accepts an empty module body' do
+ inspect_source(cop,
+ ['module Test',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'with block' do
+ it 'registers an offense for bad indentation in a do/end body' do
+ inspect_source(cop,
+ ['a = func do',
+ ' b',
+ ' c',
+ 'end'])
+ expect(cop.messages).to eq(['Inconsistent indentation detected.'])
+ end
+
+ it 'registers an offense for bad indentation in a {} body' do
+ inspect_source(cop,
+ ['func {',
+ ' b',
+ ' c',
+ '}'])
+ expect(cop.messages).to eq(['Inconsistent indentation detected.'])
+ end
+
+ it 'accepts a correctly indented block body' do
+ inspect_source(cop,
+ ['a = func do',
+ ' b',
+ ' c',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an empty block body' do
+ inspect_source(cop,
+ ['a = func do',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/indentation_width_spec.rb b/spec/rubocop/cop/style/indentation_width_spec.rb
new file mode 100644
index 0000000..5f8c59f
--- /dev/null
+++ b/spec/rubocop/cop/style/indentation_width_spec.rb
@@ -0,0 +1,606 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::IndentationWidth do
+ subject(:cop) { described_class.new(config) }
+ let(:config) do
+ Rubocop::Config.new('EndAlignment' => end_alignment_config)
+ end
+ let(:end_alignment_config) do
+ { 'Enabled' => true, 'AlignWith' => 'variable' }
+ end
+
+ context 'with if statement' do
+ it 'registers an offense for bad indentation of an if body' do
+ inspect_source(cop,
+ ['if cond',
+ ' func',
+ 'end'])
+ expect(cop.messages).to eq(['Use 2 (not 1) spaces for indentation.'])
+ expect(cop.highlights).to eq([' '])
+ end
+
+ it 'registers an offense for bad indentation of an else body' do
+ inspect_source(cop,
+ ['if cond',
+ ' func1',
+ 'else',
+ ' func2',
+ 'end'])
+ expect(cop.messages).to eq(['Use 2 (not 1) spaces for indentation.'])
+ expect(cop.highlights).to eq([' '])
+ end
+
+ it 'registers an offense for bad indentation of an elsif body' do
+ inspect_source(cop,
+ ['if a1',
+ ' b1',
+ 'elsif a2',
+ ' b2',
+ 'else',
+ ' c',
+ 'end'])
+ expect(cop.messages).to eq(['Use 2 (not 1) spaces for indentation.'])
+ end
+
+ it 'registers offense for bad indentation of ternary inside else' do
+ inspect_source(cop,
+ ['if a',
+ ' b',
+ 'else',
+ ' x ? y : z',
+ 'end'])
+ expect(cop.messages)
+ .to eq(['Use 2 (not 5) spaces for indentation.'])
+ expect(cop.highlights).to eq([' '])
+ end
+
+ it 'registers offense for bad indentation of modifier if in else' do
+ inspect_source(cop,
+ ['if a',
+ ' b',
+ 'else',
+ ' x if y',
+ 'end'])
+ expect(cop.messages)
+ .to eq(['Use 2 (not 3) spaces for indentation.'])
+ end
+
+ it 'autocorrects bad indentation' do
+ corrected = autocorrect_source(cop,
+ ['if a1',
+ ' b1',
+ ' b1',
+ 'elsif a2',
+ ' b2',
+ 'else',
+ ' c',
+ 'end'])
+ expect(corrected)
+ .to eq ['if a1',
+ ' b1',
+ ' b1', # Will be corrected by IndentationConsistency.
+ 'elsif a2',
+ ' b2',
+ 'else',
+ ' c',
+ 'end'].join("\n")
+ end
+
+ it 'accepts a one line if statement' do
+ inspect_source(cop,
+ ['if cond then func1 else func2 end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a correctly aligned if/elsif/else/end' do
+ inspect_source(cop,
+ ['if a1',
+ ' b1',
+ 'elsif a2',
+ ' b2',
+ 'else',
+ ' c',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts if/elsif/else/end laid out as a table' do
+ inspect_source(cop,
+ ['if @io == $stdout then str << "$stdout"',
+ 'elsif @io == $stdin then str << "$stdin"',
+ 'elsif @io == $stderr then str << "$stderr"',
+ 'else str << @io.class.to_s',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts if/then/else/end laid out as another table' do
+ inspect_source(cop,
+ ["if File.exist?('config.save')",
+ 'then ConfigTable.load',
+ 'else ConfigTable.new',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an empty if' do
+ inspect_source(cop,
+ ['if a',
+ 'else',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ context 'with assignment' do
+ context 'when alignment style is variable' do
+ context 'and end is aligned with variable' do
+ it 'accepts an if with end aligned with variable' do
+ inspect_source(cop,
+ ['var = if a',
+ ' 0',
+ 'end',
+ '@var = if a',
+ ' 0',
+ 'end',
+ '$var = if a',
+ ' 0',
+ 'end',
+ 'var ||= if a',
+ ' 0',
+ 'end',
+ 'var &&= if a',
+ ' 0',
+ 'end',
+ 'var -= if a',
+ ' 0',
+ 'end',
+ 'VAR = if a',
+ ' 0',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an if/else' do
+ inspect_source(cop,
+ ['var = if a',
+ ' 0',
+ 'else',
+ ' 1',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an if/else with chaining after the end' do
+ inspect_source(cop,
+ ['var = if a',
+ ' 0',
+ 'else',
+ ' 1',
+ 'end.abc.join("")'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an if/else with chaining with a block after the end' do
+ inspect_source(cop,
+ ['var = if a',
+ ' 0',
+ 'else',
+ ' 1',
+ 'end.abc.tap {}'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'and end is aligned with keyword' do
+ it 'registers an offense for an if' do
+ inspect_source(cop,
+ ['var = if a',
+ ' 0',
+ ' end'])
+ expect(cop.messages)
+ .to eq(['Use 2 (not 8) spaces for indentation.'])
+ end
+
+ it 'registers an offense for a while' do
+ inspect_source(cop,
+ ['var = while a',
+ ' b',
+ ' end'])
+ expect(cop.messages)
+ .to eq(['Use 2 (not 8) spaces for indentation.'])
+ end
+
+ it 'registers an offense for an until' do
+ inspect_source(cop,
+ ['var = until a',
+ ' b',
+ ' end'])
+ expect(cop.messages)
+ .to eq(['Use 2 (not 8) spaces for indentation.'])
+ end
+ end
+ end
+
+ shared_examples 'assignment and if with keyword alignment' do
+ context 'and end is aligned with variable' do
+ it 'registers an offense for an if' do
+ inspect_source(cop,
+ ['var = if a',
+ ' 0',
+ 'end'])
+ expect(cop.messages)
+ .to eq(['Use 2 (not -4) spaces for indentation.'])
+ end
+
+ it 'registers an offense for a while' do
+ inspect_source(cop,
+ ['var = while a',
+ ' b',
+ 'end'])
+ expect(cop.messages)
+ .to eq(['Use 2 (not -4) spaces for indentation.'])
+ end
+
+ it 'autocorrects bad indentation' do
+ corrected = autocorrect_source(cop,
+ ['var = if a',
+ ' b',
+ 'end',
+ '',
+ 'var = while a',
+ ' b',
+ 'end'])
+ expect(corrected).to eq ['var = if a',
+ ' b',
+ 'end', # Not this cop's job to fix end.
+ '',
+ 'var = while a',
+ ' b',
+ 'end'].join("\n")
+ end
+ end
+
+ context 'and end is aligned with keyword' do
+ it 'accepts an if in assignment' do
+ inspect_source(cop,
+ ['var = if a',
+ ' 0',
+ ' end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an if/else in assignment' do
+ inspect_source(cop,
+ ['var = if a',
+ ' 0',
+ ' else',
+ ' 1',
+ ' end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an if/else in assignment on next line' do
+ inspect_source(cop,
+ ['var =',
+ ' if a',
+ ' 0',
+ ' else',
+ ' 1',
+ ' end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a while in assignment' do
+ inspect_source(cop,
+ ['var = while a',
+ ' b',
+ ' end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an until in assignment' do
+ inspect_source(cop,
+ ['var = until a',
+ ' b',
+ ' end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+ end
+
+ context 'when alignment style is keyword by choice' do
+ let(:end_alignment_config) do
+ { 'Enabled' => true, 'AlignWith' => 'keyword' }
+ end
+
+ include_examples 'assignment and if with keyword alignment'
+ end
+
+ context 'when alignment style is keyword by default' do
+ let(:end_alignment_config) do
+ { 'Enabled' => false, 'AlignWith' => 'variable' }
+ end
+
+ include_examples 'assignment and if with keyword alignment'
+ end
+ end
+
+ it 'accepts an if/else branches with rescue clauses' do
+ # Because of how the rescue clauses come out of Parser, these are
+ # special and need to be tested.
+ inspect_source(cop,
+ ['if a',
+ ' a rescue nil',
+ 'else',
+ ' a rescue nil',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'with unless' do
+ it 'registers an offense for bad indentation of an unless body' do
+ inspect_source(cop,
+ ['unless cond',
+ ' func',
+ 'end'])
+ expect(cop.messages).to eq(['Use 2 (not 1) spaces for indentation.'])
+ end
+
+ it 'accepts an empty unless' do
+ inspect_source(cop,
+ ['unless a',
+ 'else',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'with case' do
+ it 'registers an offense for bad indentation in a case/when body' do
+ inspect_source(cop,
+ ['case a',
+ 'when b',
+ ' c',
+ 'end'])
+ expect(cop.messages).to eq(['Use 2 (not 1) spaces for indentation.'])
+ end
+
+ it 'registers an offense for bad indentation in a case/else body' do
+ inspect_source(cop,
+ ['case a',
+ 'when b',
+ ' c',
+ 'when d',
+ ' e',
+ 'else',
+ ' f',
+ 'end'])
+ expect(cop.messages).to eq(['Use 2 (not 3) spaces for indentation.'])
+ end
+
+ it 'accepts correctly indented case/when/else' do
+ inspect_source(cop,
+ ['case a',
+ 'when b',
+ ' c',
+ ' c',
+ 'when d',
+ 'else',
+ ' f',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts case/when/else laid out as a table' do
+ inspect_source(cop,
+ ['case sexp.loc.keyword.source',
+ "when 'if' then cond, body, _else = *sexp",
+ "when 'unless' then cond, _else, body = *sexp",
+ 'else cond, body = *sexp',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts case/when/else with then beginning a line' do
+ inspect_source(cop,
+ ['case sexp.loc.keyword.source',
+ "when 'if'",
+ 'then cond, body, _else = *sexp',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts indented when/else plus indented body' do
+ # "Indent when as deep as case" is the job of another cop.
+ inspect_source(cop,
+ ['case code_type',
+ " when 'ruby', 'sql', 'plain'",
+ ' code_type',
+ " when 'erb'",
+ " 'ruby; html-script: true'",
+ ' when "html"',
+ " 'xml'",
+ ' else',
+ " 'plain'",
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'with while/until' do
+ it 'registers an offense for bad indentation of a while body' do
+ inspect_source(cop,
+ ['while cond',
+ ' func',
+ 'end'])
+ expect(cop.messages).to eq(['Use 2 (not 1) spaces for indentation.'])
+ end
+
+ it 'registers an offense for bad indentation of begin/end/while' do
+ inspect_source(cop,
+ ['something = begin',
+ ' func1',
+ ' func2',
+ 'end while cond'])
+ expect(cop.messages).to eq(['Use 2 (not 1) spaces for indentation.'])
+ end
+
+ it 'registers an offense for bad indentation of an until body' do
+ inspect_source(cop,
+ ['until cond',
+ ' func',
+ 'end'])
+ expect(cop.messages).to eq(['Use 2 (not 1) spaces for indentation.'])
+ end
+
+ it 'accepts an empty while' do
+ inspect_source(cop,
+ ['while a',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'with for' do
+ it 'registers an offense for bad indentation of a for body' do
+ inspect_source(cop,
+ ['for var in 1..10',
+ ' func',
+ 'end'])
+ expect(cop.messages).to eq(['Use 2 (not 1) spaces for indentation.'])
+ end
+
+ it 'accepts an empty for' do
+ inspect_source(cop,
+ ['for var in 1..10',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'with def/defs' do
+ it 'registers an offense for bad indentation of a def body' do
+ inspect_source(cop,
+ ['def test',
+ ' func1',
+ ' func2', # No offense registered for this.
+ 'end'])
+ expect(cop.messages).to eq(['Use 2 (not 4) spaces for indentation.'])
+ end
+
+ it 'registers an offense for bad indentation of a defs body' do
+ inspect_source(cop,
+ ['def self.test',
+ ' func',
+ 'end'])
+ expect(cop.messages).to eq(['Use 2 (not 3) spaces for indentation.'])
+ end
+
+ it 'accepts an empty def body' do
+ inspect_source(cop,
+ ['def test',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an empty defs body' do
+ inspect_source(cop,
+ ['def self.test',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'with class' do
+ it 'registers an offense for bad indentation of a class body' do
+ inspect_source(cop,
+ ['class Test',
+ ' def func',
+ ' end',
+ 'end'])
+ expect(cop.messages).to eq(['Use 2 (not 4) spaces for indentation.'])
+ end
+
+ it 'accepts an empty class body' do
+ inspect_source(cop,
+ ['class Test',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts indented public, protected, and private' do
+ inspect_source(cop,
+ ['class Test',
+ ' public',
+ '',
+ ' def e',
+ ' end',
+ '',
+ ' protected',
+ '',
+ ' def f',
+ ' end',
+ '',
+ ' private',
+ '',
+ ' def g',
+ ' end',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'with module' do
+ it 'registers an offense for bad indentation of a module body' do
+ inspect_source(cop,
+ ['module Test',
+ ' def func',
+ ' end',
+ 'end'])
+ expect(cop.messages).to eq(['Use 2 (not 4) spaces for indentation.'])
+ end
+
+ it 'accepts an empty module body' do
+ inspect_source(cop,
+ ['module Test',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'with block' do
+ it 'registers an offense for bad indentation of a do/end body' do
+ inspect_source(cop,
+ ['a = func do',
+ ' b',
+ 'end'])
+ expect(cop.messages).to eq(['Use 2 (not 1) spaces for indentation.'])
+ end
+
+ it 'registers an offense for bad indentation of a {} body' do
+ inspect_source(cop,
+ ['func {',
+ ' b',
+ '}'])
+ expect(cop.messages).to eq(['Use 2 (not 3) spaces for indentation.'])
+ end
+
+ it 'accepts a correctly indented block body' do
+ inspect_source(cop,
+ ['a = func do',
+ ' b',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an empty block body' do
+ inspect_source(cop,
+ ['a = func do',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/lambda_call_spec.rb b/spec/rubocop/cop/style/lambda_call_spec.rb
new file mode 100644
index 0000000..8220286
--- /dev/null
+++ b/spec/rubocop/cop/style/lambda_call_spec.rb
@@ -0,0 +1,65 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::LambdaCall, :config do
+ subject(:cop) { described_class.new(config) }
+
+ context 'when style is set to call' do
+ let(:cop_config) { { 'EnforcedStyle' => 'call' } }
+
+ it 'registers an offense for x.()' do
+ inspect_source(cop,
+ ['x.(a, b)'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' => 'braces')
+ end
+
+ it 'registers an offense for correct + opposite' do
+ inspect_source(cop,
+ ['x.call(a, b)',
+ 'x.(a, b)'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'accepts x.call()' do
+ inspect_source(cop, ['x.call(a, b)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects x.() to x.call()' do
+ new_source = autocorrect_source(cop, ['a.(x)'])
+ expect(new_source).to eq('a.call(x)')
+ end
+ end
+
+ context 'when style is set to braces' do
+ let(:cop_config) { { 'EnforcedStyle' => 'braces' } }
+
+ it 'registers an offense for x.call()' do
+ inspect_source(cop,
+ ['x.call(a, b)'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' => 'call')
+ end
+
+ it 'registers an offense for opposite + correct' do
+ inspect_source(cop,
+ ['x.call(a, b)',
+ 'x.(a, b)'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'accepts x.()' do
+ inspect_source(cop, ['x.(a, b)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects x.call() to x.()' do
+ new_source = autocorrect_source(cop, ['a.call(x)'])
+ expect(new_source).to eq('a.(x)')
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/lambda_spec.rb b/spec/rubocop/cop/style/lambda_spec.rb
new file mode 100644
index 0000000..65f6683
--- /dev/null
+++ b/spec/rubocop/cop/style/lambda_spec.rb
@@ -0,0 +1,41 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::Lambda do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for an old single-line lambda call' do
+ inspect_source(cop, ['f = lambda { |x| x }'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Use the new lambda literal syntax `->(params) {...}`.'])
+ end
+
+ it 'accepts the new lambda literal with single-line body' do
+ inspect_source(cop, ['lambda = ->(x) { x }',
+ 'lambda.(1)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for a new multi-line lambda call' do
+ inspect_source(cop, ['f = ->(x) do',
+ ' x',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Use the `lambda` method for multi-line lambdas.'])
+ end
+
+ it 'accepts the old lambda syntax with multi-line body' do
+ inspect_source(cop, ['l = lambda do |x|',
+ ' x',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts the lambda call outside of block' do
+ inspect_source(cop, ['l = lambda.test'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/leading_comment_space_spec.rb b/spec/rubocop/cop/style/leading_comment_space_spec.rb
new file mode 100644
index 0000000..29d35ae
--- /dev/null
+++ b/spec/rubocop/cop/style/leading_comment_space_spec.rb
@@ -0,0 +1,64 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::LeadingCommentSpace do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for comment without leading space' do
+ inspect_source(cop,
+ ['#missing space'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'does not register an offense for # followed by no text' do
+ inspect_source(cop,
+ ['#'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for more than one space' do
+ inspect_source(cop,
+ ['# heavily indented'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for more than one #' do
+ inspect_source(cop,
+ ['###### heavily indented'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for only #s' do
+ inspect_source(cop,
+ ['######'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for #! on first line' do
+ inspect_source(cop,
+ ['#!/usr/bin/ruby',
+ 'test'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for #! after the first line' do
+ inspect_source(cop,
+ ['test', '#!/usr/bin/ruby'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts rdoc syntax' do
+ inspect_source(cop,
+ ['#++',
+ '#--',
+ '#:nodoc:'])
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects missing space' do
+ new_source = autocorrect_source(cop, '#comment')
+ expect(new_source).to eq('# comment')
+ end
+end
diff --git a/spec/rubocop/cop/style/line_end_concatenation_spec.rb b/spec/rubocop/cop/style/line_end_concatenation_spec.rb
new file mode 100644
index 0000000..03ee6fe
--- /dev/null
+++ b/spec/rubocop/cop/style/line_end_concatenation_spec.rb
@@ -0,0 +1,62 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::LineEndConcatenation do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for string concat at line end' do
+ inspect_source(cop,
+ ['top = "test" +',
+ '"top"'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for string concat with << at line end' do
+ inspect_source(cop,
+ ['top = "test" <<',
+ '"top"'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for dynamic string concat at line end' do
+ inspect_source(cop,
+ ['top = "test#{x}" +',
+ '"top"'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for dynamic string concat with << at line end' do
+ inspect_source(cop,
+ ['top = "test#{x}" <<',
+ '"top"'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts string concat on the same line' do
+ inspect_source(cop,
+ ['top = "test" + "top"'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts string concat at line end when followed by comment' do
+ inspect_source(cop,
+ ['top = "test" + # something',
+ '"top"'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts string concat at line end when % literals are involved' do
+ inspect_source(cop,
+ ['top = %(test) +',
+ '"top"'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'autocorrects by replacing + with \\' do
+ corrected = autocorrect_source(cop,
+ ['top = "test" +',
+ '"top"'])
+ expect(corrected).to eq ['top = "test" \\', '"top"'].join("\n")
+ end
+end
diff --git a/spec/rubocop/cop/style/line_length_spec.rb b/spec/rubocop/cop/style/line_length_spec.rb
new file mode 100644
index 0000000..7d00d29
--- /dev/null
+++ b/spec/rubocop/cop/style/line_length_spec.rb
@@ -0,0 +1,20 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::LineLength, :config do
+ subject(:cop) { described_class.new(config) }
+ let(:cop_config) { { 'Max' => 79 } }
+
+ it "registers an offense for a line that's 80 characters wide" do
+ inspect_source(cop, ['#' * 80])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message).to eq('Line is too long. [80/79]')
+ expect(cop.config_to_allow_offenses).to eq('Max' => 80)
+ end
+
+ it "accepts a line that's 79 characters wide" do
+ inspect_source(cop, ['#' * 79])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/method_call_parentheses_spec.rb b/spec/rubocop/cop/style/method_call_parentheses_spec.rb
new file mode 100644
index 0000000..c451f18
--- /dev/null
+++ b/spec/rubocop/cop/style/method_call_parentheses_spec.rb
@@ -0,0 +1,59 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::MethodCallParentheses, :config do
+ subject(:cop) { described_class.new(config) }
+ let(:config) do
+ Rubocop::Config.new('EmptyLiteral' => { 'Enabled' => true })
+ end
+
+ it 'registers an offense for parens in method call without args' do
+ inspect_source(cop, ['top.test()'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts parentheses for methods starting with an upcase letter' do
+ inspect_source(cop, ['Test()'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts no parens in method call without args' do
+ inspect_source(cop, ['top.test'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts parens in method call with args' do
+ inspect_source(cop, ['top.test(a)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects by removing unneeded braces' do
+ new_source = autocorrect_source(cop, 'test()')
+ expect(new_source).to eq('test')
+ end
+
+ it 'does not auto-correct calls that will be changed to empty literals' do
+ original = ['Hash.new()',
+ 'Array.new()',
+ 'String.new()']
+ new_source = autocorrect_source(cop, original)
+ expect(new_source).to eq(original.join("\n"))
+ end
+
+ context 'when EmptyLiteral is disabled' do
+ let(:config) do
+ Rubocop::Config.new('EmptyLiteral' => { 'Enabled' => false })
+ end
+
+ it 'auto-corrects calls that could be empty literals' do
+ original = ['Hash.new()',
+ 'Array.new()',
+ 'String.new()']
+ new_source = autocorrect_source(cop, original)
+ expect(new_source).to eq(['Hash.new',
+ 'Array.new',
+ 'String.new'].join("\n"))
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/method_called_on_do_end_block_spec.rb b/spec/rubocop/cop/style/method_called_on_do_end_block_spec.rb
new file mode 100644
index 0000000..d9fa603
--- /dev/null
+++ b/spec/rubocop/cop/style/method_called_on_do_end_block_spec.rb
@@ -0,0 +1,60 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::MethodCalledOnDoEndBlock do
+ subject(:cop) { described_class.new }
+
+ context 'with a multi-line do..end block' do
+ it 'registers an offense for a chained call' do
+ inspect_source(cop, ['a do',
+ ' b',
+ 'end.c'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['end.c'])
+ end
+
+ it 'accepts it if there is no chained call' do
+ inspect_source(cop, ['a do',
+ ' b',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a chained block' do
+ inspect_source(cop, ['a do',
+ ' b',
+ 'end.c do',
+ ' d',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'with a single-line do..end block' do
+ it 'registers an offense for a chained call' do
+ inspect_source(cop, ['a do b end.c'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['end.c'])
+ end
+
+ it 'accepts a single-line do..end block with a chained block' do
+ inspect_source(cop, ['a do b end.c do d end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'with a {} block' do
+ it 'accepts a multi-line block with a chained call' do
+ inspect_source(cop, ['a {',
+ ' b',
+ '}.c'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a single-line block with a chained call' do
+ inspect_source(cop, ['a { b }.c'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/method_def_parentheses_spec.rb b/spec/rubocop/cop/style/method_def_parentheses_spec.rb
new file mode 100644
index 0000000..eabd515
--- /dev/null
+++ b/spec/rubocop/cop/style/method_def_parentheses_spec.rb
@@ -0,0 +1,106 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::MethodDefParentheses, :config do
+ subject(:cop) { described_class.new(config) }
+
+ context 'require_parentheses' do
+ let(:cop_config) { { 'EnforcedStyle' => 'require_parentheses' } }
+
+ it 'reports an offense for def with parameters but no parens' do
+ src = ['def func a, b',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' =>
+ 'require_no_parentheses')
+ end
+
+ it 'reports an offense for correct + opposite' do
+ src = ['def func(a, b)',
+ 'end',
+ 'def func a, b',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'reports an offense for class def with parameters but no parens' do
+ src = ['def Test.func a, b',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts def with no args and no parens' do
+ src = ['def func',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-adds required parens for a def' do
+ new_source = autocorrect_source(cop, 'def test param; end')
+ expect(new_source).to eq('def test(param); end')
+ end
+
+ it 'auto-adds required parens for a defs' do
+ new_source = autocorrect_source(cop, 'def self.test param; end')
+ expect(new_source).to eq('def self.test(param); end')
+ end
+
+ it 'auto-adds required parens to argument lists on multiple lines' do
+ new_source = autocorrect_source(cop, ['def test one,', 'two', 'end'])
+ expect(new_source).to eq("def test(one,\ntwo)\nend")
+ end
+ end
+
+ context 'require_no_parentheses' do
+ let(:cop_config) { { 'EnforcedStyle' => 'require_no_parentheses' } }
+
+ it 'reports an offense for def with parameters with parens' do
+ src = ['def func(a, b)',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' =>
+ 'require_parentheses')
+ end
+
+ it 'reports an offense for opposite + correct' do
+ src = ['def func(a, b)',
+ 'end',
+ 'def func a, b',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'reports an offense for class def with parameters with parens' do
+ src = ['def Test.func(a, b)',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'reports an offense for def with no args and parens' do
+ src = ['def func()',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'auto-removes the parens' do
+ new_source = autocorrect_source(cop, 'def test(param); end')
+ expect(new_source).to eq('def test param; end')
+ end
+
+ it 'auto-removes the parens for defs' do
+ new_source = autocorrect_source(cop, 'def self.test(param); end')
+ expect(new_source).to eq('def self.test param; end')
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/method_length_spec.rb b/spec/rubocop/cop/style/method_length_spec.rb
new file mode 100644
index 0000000..ddd9b64
--- /dev/null
+++ b/spec/rubocop/cop/style/method_length_spec.rb
@@ -0,0 +1,147 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::MethodLength, :config do
+ subject(:cop) { described_class.new(config) }
+ let(:cop_config) { { 'Max' => 5, 'CountComments' => false } }
+
+ it 'rejects a method with more than 5 lines' do
+ inspect_source(cop, ['def m()',
+ ' a = 1',
+ ' a = 2',
+ ' a = 3',
+ ' a = 4',
+ ' a = 5',
+ ' a = 6',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line).sort).to eq([1])
+ expect(cop.config_to_allow_offenses).to eq('Max' => 6)
+ end
+
+ it 'accepts a method with less than 5 lines' do
+ inspect_source(cop, ['def m()',
+ ' a = 1',
+ ' a = 2',
+ ' a = 3',
+ ' a = 4',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not count blank lines' do
+ inspect_source(cop, ['def m()',
+ ' a = 1',
+ ' a = 2',
+ ' a = 3',
+ ' a = 4',
+ '',
+ '',
+ ' a = 7',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts empty methods' do
+ inspect_source(cop, ['def m()',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'is not fooled by one-liner methods, syntax #1' do
+ inspect_source(cop, ['def one_line; 10 end',
+ 'def self.m()',
+ ' a = 1',
+ ' a = 2',
+ ' a = 4',
+ ' a = 5',
+ ' a = 6',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'is not fooled by one-liner methods, syntax #2' do
+ inspect_source(cop, ['def one_line(test) 10 end',
+ 'def self.m()',
+ ' a = 1',
+ ' a = 2',
+ ' a = 4',
+ ' a = 5',
+ ' a = 6',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'checks class methods, syntax #1' do
+ inspect_source(cop, ['def self.m()',
+ ' a = 1',
+ ' a = 2',
+ ' a = 3',
+ ' a = 4',
+ ' a = 5',
+ ' a = 6',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line).sort).to eq([1])
+ end
+
+ it 'checks class methods, syntax #2' do
+ inspect_source(cop, ['class K',
+ ' class << self',
+ ' def m()',
+ ' a = 1',
+ ' a = 2',
+ ' a = 3',
+ ' a = 4',
+ ' a = 5',
+ ' a = 6',
+ ' end',
+ ' end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line).sort).to eq([3])
+ end
+
+ it 'properly counts lines when method ends with block' do
+ inspect_source(cop, ['def m()',
+ ' something do',
+ ' a = 2',
+ ' a = 3',
+ ' a = 4',
+ ' a = 5',
+ ' end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line).sort).to eq([1])
+ end
+
+ it 'does not count commented lines by default' do
+ inspect_source(cop, ['def m()',
+ ' a = 1',
+ ' #a = 2',
+ ' a = 3',
+ ' #a = 4',
+ ' a = 5',
+ ' a = 6',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ context 'when CountComments is enabled' do
+ before { cop_config['CountComments'] = true }
+
+ it 'also counts commented lines' do
+ inspect_source(cop, ['def m()',
+ ' a = 1',
+ ' #a = 2',
+ ' a = 3',
+ ' #a = 4',
+ ' a = 5',
+ ' a = 6',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line).sort).to eq([1])
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/method_name_spec.rb b/spec/rubocop/cop/style/method_name_spec.rb
new file mode 100644
index 0000000..96a8faa
--- /dev/null
+++ b/spec/rubocop/cop/style/method_name_spec.rb
@@ -0,0 +1,125 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::MethodName, :config do
+ subject(:cop) { described_class.new(config) }
+
+ shared_examples 'never accepted' do
+ it 'registers an offense for mixed snake case and camel case' do
+ inspect_source(cop, ['def visit_Arel_Nodes_SelectStatement',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['visit_Arel_Nodes_SelectStatement'])
+ end
+
+ it 'registers an offense for capitalized camel case' do
+ inspect_source(cop, ['def MyMethod',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['MyMethod'])
+ end
+ end
+
+ shared_examples 'always accepted' do
+ it 'accepts one line methods' do
+ inspect_source(cop, "def body; '' end")
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts operator definitions' do
+ inspect_source(cop, ['def +(other)',
+ ' # ...',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'when configured for snake_case' do
+ let(:cop_config) { { 'EnforcedStyle' => 'snake_case' } }
+
+ it 'registers an offense for camel case in instance method name' do
+ inspect_source(cop, ['def myMethod',
+ ' # ...',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['myMethod'])
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' =>
+ 'camelCase')
+ end
+
+ it 'registers an offense for opposite + correct' do
+ inspect_source(cop, ['def my_method',
+ 'end',
+ 'def myMethod',
+ 'end'])
+ expect(cop.highlights).to eq(['myMethod'])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'registers an offense for camel case in singleton method name' do
+ inspect_source(cop, ['def self.myMethod',
+ ' # ...',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['myMethod'])
+ end
+
+ it 'accepts snake case in names' do
+ inspect_source(cop, ['def my_method',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ include_examples 'never accepted'
+ include_examples 'always accepted'
+ end
+
+ context 'when configured for camelCase' do
+ let(:cop_config) { { 'EnforcedStyle' => 'camelCase' } }
+
+ it 'accepts camel case in instance method name' do
+ inspect_source(cop, ['def myMethod',
+ ' # ...',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts camel case in singleton method name' do
+ inspect_source(cop, ['def self.myMethod',
+ ' # ...',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for snake case in names' do
+ inspect_source(cop, ['def my_method',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['my_method'])
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' =>
+ 'snake_case')
+ end
+
+ it 'registers an offense for correct + opposite' do
+ inspect_source(cop, ['def my_method',
+ 'end',
+ 'def myMethod',
+ 'end'])
+ expect(cop.highlights).to eq(['my_method'])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ include_examples 'always accepted'
+ include_examples 'never accepted'
+ end
+
+ context 'when configured with a bad value' do
+ let(:cop_config) { { 'EnforcedStyle' => 'other' } }
+
+ it 'fails' do
+ expect { inspect_source(cop, ['def a', 'end']) }
+ .to raise_error(RuntimeError)
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/module_function_spec.rb b/spec/rubocop/cop/style/module_function_spec.rb
new file mode 100644
index 0000000..2b8ab96
--- /dev/null
+++ b/spec/rubocop/cop/style/module_function_spec.rb
@@ -0,0 +1,24 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::ModuleFunction do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for extend self in module' do
+ inspect_source(cop,
+ ['module Test',
+ ' extend self',
+ ' def test; end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts extend self in class' do
+ inspect_source(cop,
+ ['class Test',
+ ' extend self',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/multiline_block_chain_spec.rb b/spec/rubocop/cop/style/multiline_block_chain_spec.rb
new file mode 100644
index 0000000..bedbc72
--- /dev/null
+++ b/spec/rubocop/cop/style/multiline_block_chain_spec.rb
@@ -0,0 +1,78 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::MultilineBlockChain do
+ subject(:cop) { described_class.new }
+
+ context 'with multi-line block chaining' do
+ it 'registers an offense for a simple case' do
+ inspect_source(cop, ['a do',
+ ' b',
+ 'end.c do',
+ ' d',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['end.c'])
+ end
+
+ it 'registers an offense for a slightly more complicated case' do
+ inspect_source(cop, ['a do',
+ ' b',
+ 'end.c1.c2 do',
+ ' d',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['end.c1.c2'])
+ end
+
+ it 'registers two offenses for a chain of three blocks' do
+ inspect_source(cop, ['a do',
+ ' b',
+ 'end.c do',
+ ' d',
+ 'end.e do',
+ ' f',
+ 'end'])
+ expect(cop.offenses.size).to eq(2)
+ expect(cop.highlights).to eq(['end.c', 'end.e'])
+ end
+
+ it 'registers an offense for a chain where the second block is ' \
+ 'single-line' do
+ inspect_source(cop, ['Thread.list.find_all { |t|',
+ ' t.alive?',
+ '}.map { |thread| thread.object_id }'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['}.map'])
+ end
+
+ it 'accepts a chain where the first block is single-line' do
+ inspect_source(cop,
+ ['Thread.list.find_all { |t| t.alive? }.map { |t| ',
+ ' t.object_id',
+ '}'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ it 'accepts a chain of blocks spanning one line' do
+ inspect_source(cop, ['a { b }.c { d }',
+ 'w do x end.y do z end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a multi-line block chained with calls on one line' do
+ inspect_source(cop, ['a do',
+ ' b',
+ 'end.c.d'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a chain of calls followed by a multi-line block' do
+ inspect_source(cop, ['a1.a2.a3 do',
+ ' b',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/multiline_if_then_spec.rb b/spec/rubocop/cop/style/multiline_if_then_spec.rb
new file mode 100644
index 0000000..a09cb28
--- /dev/null
+++ b/spec/rubocop/cop/style/multiline_if_then_spec.rb
@@ -0,0 +1,107 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::MultilineIfThen do
+ subject(:cop) { described_class.new }
+
+ # if
+
+ it 'registers an offense for then in multiline if' do
+ inspect_source(cop, ['if cond then',
+ 'end',
+ "if cond then\t",
+ 'end',
+ 'if cond then ',
+ 'end',
+ 'if cond',
+ 'then',
+ 'end',
+ 'if cond then # bad',
+ 'end'])
+ expect(cop.offenses.map(&:line)).to eq([1, 3, 5, 7, 10])
+ end
+
+ it 'accepts multiline if without then' do
+ inspect_source(cop, ['if cond',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts table style if/then/elsif/ends' do
+ inspect_source(cop,
+ ['if @io == $stdout then str << "$stdout"',
+ 'elsif @io == $stdin then str << "$stdin"',
+ 'elsif @io == $stderr then str << "$stderr"',
+ 'else str << @io.class.to_s',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not get confused by a then in a when' do
+ inspect_source(cop,
+ ['if a',
+ ' case b',
+ ' when c then',
+ ' end',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not get confused by a commented-out then' do
+ inspect_source(cop,
+ ['if a # then',
+ ' b',
+ 'end',
+ 'if c # then',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not raise an error for an implicit match if' do
+ expect do
+ inspect_source(cop,
+ ['if //',
+ 'end'])
+ end.not_to raise_error
+ end
+
+ # unless
+
+ it 'registers an offense for then in multiline unless' do
+ inspect_source(cop, ['unless cond then',
+ 'end'])
+ expect(cop.messages).to eq(
+ ['Never use `then` for multi-line `unless`.'])
+ end
+
+ it 'accepts multiline unless without then' do
+ inspect_source(cop, ['unless cond',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not get confused by a postfix unless' do
+ inspect_source(cop,
+ ['two unless one'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not get confused by a nested postfix unless' do
+ inspect_source(cop,
+ ['if two',
+ ' puts 1',
+ 'end unless two'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not raise an error for an implicit match unless' do
+ expect do
+ inspect_source(cop,
+ ['unless //',
+ 'end'])
+ end.not_to raise_error
+ end
+end
diff --git a/spec/rubocop/cop/style/multiline_ternary_operator_spec.rb b/spec/rubocop/cop/style/multiline_ternary_operator_spec.rb
new file mode 100644
index 0000000..62ac8d5
--- /dev/null
+++ b/spec/rubocop/cop/style/multiline_ternary_operator_spec.rb
@@ -0,0 +1,18 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::MultilineTernaryOperator do
+ subject(:cop) { described_class.new }
+
+ it 'registers offense for a multiline ternary operator expression' do
+ inspect_source(cop, ['a = cond ?',
+ ' b : c'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts a single line ternary operator expression' do
+ inspect_source(cop, ['a = cond ? b : c'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/negated_if_spec.rb b/spec/rubocop/cop/style/negated_if_spec.rb
new file mode 100644
index 0000000..dfeb39c
--- /dev/null
+++ b/spec/rubocop/cop/style/negated_if_spec.rb
@@ -0,0 +1,98 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::NegatedIf do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for if with exclamation point condition' do
+ inspect_source(cop,
+ ['if !a_condition',
+ ' some_method',
+ 'end',
+ 'some_method if !a_condition'
+ ])
+ expect(cop.messages).to eq(
+ ['Favor `unless` over `if` for negative ' \
+ 'conditions.'] * 2)
+ end
+
+ it 'registers an offense for unless with exclamation point condition' do
+ inspect_source(cop,
+ ['unless !a_condition',
+ ' some_method',
+ 'end',
+ 'some_method unless !a_condition'
+ ])
+ expect(cop.messages).to eq(['Favor `if` over `unless` for negative ' \
+ 'conditions.'] * 2)
+ end
+
+ it 'registers an offense for if with "not" condition' do
+ inspect_source(cop,
+ ['if not a_condition',
+ ' some_method',
+ 'end',
+ 'some_method if not a_condition'])
+ expect(cop.messages).to eq(
+ ['Favor `unless` over `if` for negative ' \
+ 'conditions.'] * 2)
+ expect(cop.offenses.map(&:line)).to eq([1, 4])
+ end
+
+ it 'accepts an if/else with negative condition' do
+ inspect_source(cop,
+ ['if !a_condition',
+ ' some_method',
+ 'else',
+ ' something_else',
+ 'end',
+ 'if not a_condition',
+ ' some_method',
+ 'elsif other_condition',
+ ' something_else',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an if where only part of the contition is negated' do
+ inspect_source(cop,
+ ['if !condition && another_condition',
+ ' some_method',
+ 'end',
+ 'if not condition or another_condition',
+ ' some_method',
+ 'end',
+ 'some_method if not condition or another_condition'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'is not confused by negated elsif' do
+ inspect_source(cop,
+ ['if test.is_a?(String)',
+ ' 3',
+ 'elsif test.is_a?(Array)',
+ ' 2',
+ 'elsif !test.nil?',
+ ' 1',
+ 'end'])
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not blow up for ternary ops' do
+ inspect_source(cop, 'a ? b : c')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'autocorrects by replacing if not with unless' do
+ corrected = autocorrect_source(cop, 'something if !x.even?')
+ expect(corrected).to eq 'something unless x.even?'
+ end
+
+ it 'autocorrects by replacing unless not with if' do
+ corrected = autocorrect_source(cop, 'something unless !x.even?')
+ expect(corrected).to eq 'something if x.even?'
+ end
+
+end
diff --git a/spec/rubocop/cop/style/negated_while_spec.rb b/spec/rubocop/cop/style/negated_while_spec.rb
new file mode 100644
index 0000000..5442363
--- /dev/null
+++ b/spec/rubocop/cop/style/negated_while_spec.rb
@@ -0,0 +1,62 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::NegatedWhile do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for while with exclamation point condition' do
+ inspect_source(cop,
+ ['while !a_condition',
+ ' some_method',
+ 'end',
+ 'some_method while !a_condition'
+ ])
+ expect(cop.messages).to eq(
+ ['Favor `until` over `while` for negative conditions.'] * 2)
+ end
+
+ it 'registers an offense for until with exclamation point condition' do
+ inspect_source(cop,
+ ['until !a_condition',
+ ' some_method',
+ 'end',
+ 'some_method until !a_condition'
+ ])
+ expect(cop.messages)
+ .to eq(['Favor `while` over `until` for negative conditions.'] * 2)
+ end
+
+ it 'registers an offense for while with "not" condition' do
+ inspect_source(cop,
+ ['while (not a_condition)',
+ ' some_method',
+ 'end',
+ 'some_method while not a_condition'])
+ expect(cop.messages).to eq(
+ ['Favor `until` over `while` for negative conditions.'] * 2)
+ expect(cop.offenses.map(&:line)).to eq([1, 4])
+ end
+
+ it 'accepts an while where only part of the contition is negated' do
+ inspect_source(cop,
+ ['while !a_condition && another_condition',
+ ' some_method',
+ 'end',
+ 'while not a_condition or another_condition',
+ ' some_method',
+ 'end',
+ 'some_method while not a_condition or other_cond'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'autocorrects by replacing while not with until' do
+ corrected = autocorrect_source(cop, 'something while !x.even?')
+ expect(corrected).to eq 'something until x.even?'
+ end
+
+ it 'autocorrects by replacing until not with while' do
+ corrected = autocorrect_source(cop, 'something until !x.even?')
+ expect(corrected).to eq 'something while x.even?'
+ end
+end
diff --git a/spec/rubocop/cop/style/nested_ternary_operator_spec.rb b/spec/rubocop/cop/style/nested_ternary_operator_spec.rb
new file mode 100644
index 0000000..8c7f063
--- /dev/null
+++ b/spec/rubocop/cop/style/nested_ternary_operator_spec.rb
@@ -0,0 +1,21 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::NestedTernaryOperator do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for a nested ternary operator expression' do
+ inspect_source(cop, ['a ? (b ? b1 : b2) : a2'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts a non-nested ternary operator within an if' do
+ inspect_source(cop, ['a = if x',
+ ' cond ? b : c',
+ 'else',
+ ' d',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/nil_comparison_spec.rb b/spec/rubocop/cop/style/nil_comparison_spec.rb
new file mode 100644
index 0000000..1f4b938
--- /dev/null
+++ b/spec/rubocop/cop/style/nil_comparison_spec.rb
@@ -0,0 +1,29 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::NilComparison do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for == nil' do
+ inspect_source(cop, 'x == nil')
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['=='])
+ end
+
+ it 'registers an offense for === nil' do
+ inspect_source(cop, 'x === nil')
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['==='])
+ end
+
+ it 'autocorrects by replacing == nil with .nil?' do
+ corrected = autocorrect_source(cop, 'x == nil')
+ expect(corrected).to eq 'x.nil?'
+ end
+
+ it 'autocorrects by replacing === nil with .nil?' do
+ corrected = autocorrect_source(cop, 'x === nil')
+ expect(corrected).to eq 'x.nil?'
+ end
+end
diff --git a/spec/rubocop/cop/style/non_nil_check_spec.rb b/spec/rubocop/cop/style/non_nil_check_spec.rb
new file mode 100644
index 0000000..7bee833
--- /dev/null
+++ b/spec/rubocop/cop/style/non_nil_check_spec.rb
@@ -0,0 +1,70 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::NonNilCheck do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for != nil' do
+ inspect_source(cop, 'x != nil')
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['!='])
+ end
+
+ it 'registers an offense for !x.nil?' do
+ inspect_source(cop, '!x.nil?')
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['!x.nil?'])
+ end
+
+ it 'registers an offense for not x.nil?' do
+ inspect_source(cop, 'not x.nil?')
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['not x.nil?'])
+ end
+
+ it 'does not register an offense if only expression in predicate' do
+ inspect_source(cop, ['def signed_in?',
+ ' !current_user.nil?',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense if only expression in class predicate' do
+ inspect_source(cop, ['def Test.signed_in?',
+ ' !current_user.nil?',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense if last expression in predicate' do
+ inspect_source(cop, ['def signed_in?',
+ ' something',
+ ' !current_user.nil?',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense if last expression in class predicate' do
+ inspect_source(cop, ['def Test.signed_in?',
+ ' something',
+ ' !current_user.nil?',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'autocorrects by removing != nil' do
+ corrected = autocorrect_source(cop, 'x != nil')
+ expect(corrected).to eq 'x'
+ end
+
+ it 'autocorrects by removing non-nil (!x.nil?) check' do
+ corrected = autocorrect_source(cop, '!x.nil?')
+ expect(corrected).to eq 'x'
+ end
+
+ it 'does not blow up when autocorrecting implicit receiver' do
+ corrected = autocorrect_source(cop, '!nil?')
+ expect(corrected).to eq 'self'
+ end
+end
diff --git a/spec/rubocop/cop/style/not_spec.rb b/spec/rubocop/cop/style/not_spec.rb
new file mode 100644
index 0000000..348157a
--- /dev/null
+++ b/spec/rubocop/cop/style/not_spec.rb
@@ -0,0 +1,28 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::Not do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for not' do
+ inspect_source(cop, 'not test')
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'does not register an offense for !' do
+ inspect_source(cop, '!test')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects "not" with !' do
+ new_source = autocorrect_source(cop, 'x = 10 if not y')
+ expect(new_source).to eq('x = 10 if !y')
+ end
+
+ it 'leaves "not" as is if auto-correction changes the meaning' do
+ src = 'not x < y'
+ new_source = autocorrect_source(cop, src)
+ expect(new_source).to eq(src)
+ end
+end
diff --git a/spec/rubocop/cop/style/numeric_literals_spec.rb b/spec/rubocop/cop/style/numeric_literals_spec.rb
new file mode 100644
index 0000000..6622957
--- /dev/null
+++ b/spec/rubocop/cop/style/numeric_literals_spec.rb
@@ -0,0 +1,70 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::NumericLiterals, :config do
+ subject(:cop) { described_class.new(config) }
+ let(:cop_config) { { 'MinDigits' => 5 } }
+
+ it 'registers an offense for a long undelimited integer' do
+ inspect_source(cop, ['a = 12345'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.config_to_allow_offenses).to eq('MinDigits' => 6)
+ end
+
+ it 'registers an offense for a float with a long undelimited integer part' do
+ inspect_source(cop, ['a = 123456.789'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.config_to_allow_offenses).to eq('MinDigits' => 7)
+ end
+
+ it 'registers an offense for an integer with misplaced underscore' do
+ inspect_source(cop, ['a = 123_456_78_90_00'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'accepts long numbers with underscore' do
+ inspect_source(cop, ['a = 123_456',
+ 'b = 123_456.55'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts a short integer without underscore' do
+ inspect_source(cop, ['a = 123'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'does not count a leading minus sign as a digit' do
+ inspect_source(cop, ['a = -1230'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts short numbers without underscore' do
+ inspect_source(cop, ['a = 123',
+ 'b = 123.456'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'ignores non-decimal literals' do
+ inspect_source(cop, ['a = 0b1010101010101',
+ 'b = 01717171717171',
+ 'c = 0xab11111111bb'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'autocorrects a long integer offense' do
+ corrected = autocorrect_source(cop, ['a = 123456'])
+ expect(corrected).to eq 'a = 123_456'
+ end
+
+ it 'autocorrects an integer with misplaced underscore' do
+ corrected = autocorrect_source(cop, ['a = 123_456_78_90_00'])
+ expect(corrected).to eq 'a = 123_456_789_000'
+ end
+
+ it 'autocorrects negative numbers' do
+ corrected = autocorrect_source(cop, ['a = -123456'])
+ expect(corrected).to eq 'a = -123_456'
+ end
+end
diff --git a/spec/rubocop/cop/style/one_line_conditional_spec.rb b/spec/rubocop/cop/style/one_line_conditional_spec.rb
new file mode 100644
index 0000000..b5f2918
--- /dev/null
+++ b/spec/rubocop/cop/style/one_line_conditional_spec.rb
@@ -0,0 +1,13 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::OneLineConditional do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for one line if/then/end' do
+ inspect_source(cop, ['if cond then run else dont end'])
+ expect(cop.messages).to eq(['Favor the ternary operator (?:)' \
+ ' over if/then/else/end constructs.'])
+ end
+end
diff --git a/spec/rubocop/cop/style/op_method_spec.rb b/spec/rubocop/cop/style/op_method_spec.rb
new file mode 100644
index 0000000..6734b87
--- /dev/null
+++ b/spec/rubocop/cop/style/op_method_spec.rb
@@ -0,0 +1,82 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::OpMethod do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for arg not named other' do
+ inspect_source(cop,
+ ['def +(another)',
+ ' another',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['When defining the `+` operator, name its argument `other`.'])
+ end
+
+ it 'works properly even if the argument not surrounded with braces' do
+ inspect_source(cop,
+ ['def + another',
+ ' another',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['When defining the `+` operator, name its argument `other`.'])
+ end
+
+ it 'does not register an offense for arg named other' do
+ inspect_source(cop,
+ ['def +(other)',
+ ' other',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for arg named _other' do
+ inspect_source(cop,
+ ['def <=>(_other)',
+ ' 0',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for []' do
+ inspect_source(cop,
+ ['def [](index)',
+ ' other',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for []=' do
+ inspect_source(cop,
+ ['def []=(index, value)',
+ ' other',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for <<' do
+ inspect_source(cop,
+ ['def <<(cop)',
+ ' other',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for non binary operators' do
+ inspect_source(cop,
+ ['def -@', # Unary minus
+ 'end',
+ '',
+ # This + is not a unary operator. It can only be
+ # called with dot notation.
+ 'def +',
+ 'end',
+ '',
+ 'def *(a, b)', # Quite strange, but legal ruby.
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/parameter_lists_spec.rb b/spec/rubocop/cop/style/parameter_lists_spec.rb
new file mode 100644
index 0000000..cc21d5c
--- /dev/null
+++ b/spec/rubocop/cop/style/parameter_lists_spec.rb
@@ -0,0 +1,44 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::ParameterLists, :config do
+ subject(:cop) { described_class.new(config) }
+ let(:cop_config) do
+ {
+ 'Max' => 4,
+ 'CountKeywordArgs' => true
+ }
+ end
+
+ it 'registers an offense for a method def with 5 parameters' do
+ inspect_source(cop, ['def meth(a, b, c, d, e)',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.config_to_allow_offenses).to eq('Max' => 5)
+ end
+
+ it 'accepts a method def with 4 parameters' do
+ inspect_source(cop, ['def meth(a, b, c, d)',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ context 'When CountKeywordArgs is true' do
+ it 'counts keyword arguments as well', ruby: 2.0 do
+ inspect_source(cop, ['def meth(a, b, c, d: 1, e: 2)',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+
+ context 'When CountKeywordArgs is false' do
+ before { cop_config['CountKeywordArgs'] = false }
+
+ it 'it does not count keyword arguments', ruby: 2.0 do
+ inspect_source(cop, ['def meth(a, b, c, d: 1, e: 2)',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/parentheses_around_condition_spec.rb b/spec/rubocop/cop/style/parentheses_around_condition_spec.rb
new file mode 100644
index 0000000..80b59e1
--- /dev/null
+++ b/spec/rubocop/cop/style/parentheses_around_condition_spec.rb
@@ -0,0 +1,123 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::ParenthesesAroundCondition, :config do
+ subject(:cop) { described_class.new(config) }
+ let(:cop_config) { { 'AllowSafeAssignment' => true } }
+
+ it 'registers an offense for parentheses around condition' do
+ inspect_source(cop, ['if (x > 10)',
+ 'elsif (x < 3)',
+ 'end',
+ 'unless (x > 10)',
+ 'end',
+ 'while (x > 10)',
+ 'end',
+ 'until (x > 10)',
+ 'end',
+ 'x += 1 if (x < 10)',
+ 'x += 1 unless (x < 10)',
+ 'x += 1 until (x < 10)',
+ 'x += 1 while (x < 10)'
+ ])
+ expect(cop.offenses.size).to eq(9)
+ expect(cop.messages.first)
+ .to eq("Don't use parentheses around the condition of an `if`.")
+ expect(cop.messages.last)
+ .to eq("Don't use parentheses around the condition of a `while`.")
+ end
+
+ it 'accepts parentheses if there is no space between the keyword and (.' do
+ inspect_source(cop, ['if(x > 5) then something end',
+ 'do_something until(x > 5)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects parentheses around condition' do
+ corrected = autocorrect_source(cop, ['if (x > 10)',
+ 'elsif (x < 3)',
+ 'end',
+ 'unless (x > 10)',
+ 'end',
+ 'while (x > 10)',
+ 'end',
+ 'until (x > 10)',
+ 'end',
+ 'x += 1 if (x < 10)',
+ 'x += 1 unless (x < 10)',
+ 'x += 1 while (x < 10)',
+ 'x += 1 until (x < 10)'
+ ])
+ expect(corrected).to eq ['if x > 10',
+ 'elsif x < 3',
+ 'end',
+ 'unless x > 10',
+ 'end',
+ 'while x > 10',
+ 'end',
+ 'until x > 10',
+ 'end',
+ 'x += 1 if x < 10',
+ 'x += 1 unless x < 10',
+ 'x += 1 while x < 10',
+ 'x += 1 until x < 10'
+ ].join("\n")
+ end
+
+ it 'accepts condition without parentheses' do
+ inspect_source(cop, ['if x > 10',
+ 'end',
+ 'unless x > 10',
+ 'end',
+ 'while x > 10',
+ 'end',
+ 'until x > 10',
+ 'end',
+ 'x += 1 if x < 10',
+ 'x += 1 unless x < 10',
+ 'x += 1 while x < 10',
+ 'x += 1 until x < 10'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts parentheses around condition in a ternary' do
+ inspect_source(cop, '(a == 0) ? b : a')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'is not confused by leading parenthesis in subexpression' do
+ inspect_source(cop, ['(a > b) && other ? one : two'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'is not confused by unbalanced parentheses' do
+ inspect_source(cop, ['if (a + b).c()',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ context 'safe assignment is allowed' do
+ it 'accepts = in condition surrounded with parentheses' do
+ inspect_source(cop,
+ ['if (test = 10)',
+ 'end'
+ ])
+ expect(cop.offenses).to be_empty
+ end
+
+ end
+
+ context 'safe assignment is not allowed' do
+ let(:cop_config) { { 'AllowSafeAssignment' => false } }
+
+ it 'does not accept = in condition surrounded with parentheses' do
+ inspect_source(cop,
+ ['if (test = 10)',
+ 'end'
+ ])
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/percent_literal_delimiters_spec.rb b/spec/rubocop/cop/style/percent_literal_delimiters_spec.rb
new file mode 100644
index 0000000..370a21b
--- /dev/null
+++ b/spec/rubocop/cop/style/percent_literal_delimiters_spec.rb
@@ -0,0 +1,262 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::PercentLiteralDelimiters, :config do
+ subject(:cop) { described_class.new(config) }
+
+ let(:cop_config) do
+ {
+ 'PreferredDelimiters' => {
+ '%' => '[]',
+ '%i' => '[]',
+ '%q' => '[]',
+ '%Q' => '[]',
+ '%r' => '[]',
+ '%s' => '[]',
+ '%w' => '[]',
+ '%W' => '[]',
+ '%x' => '[]'
+ }
+ }
+ end
+
+ context '`%` interpolated string' do
+ it 'does not register an offense for preferred delimiters' do
+ inspect_source(cop, ['%[string]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for other delimiters' do
+ inspect_source(cop, ['%(string)'])
+ expect(cop.messages).to eq([
+ '`%`-literals should be delimited by `[` and `]`'
+ ])
+ end
+
+ it 'does not register an offense for other delimiters ' \
+ 'when containing preferred delimiter characters' do
+ inspect_source(cop, ['%([string])'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for other delimiters ' \
+ 'when containing preferred delimiter characters in interpolation' do
+ inspect_source(cop, ['%(#{[1].first})'])
+ expect(cop.messages.size).to eq(1)
+ end
+ end
+
+ context '`%q` string' do
+ it 'does not register an offense for preferred delimiters' do
+ inspect_source(cop, ['%q[string]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for other delimiters' do
+ inspect_source(cop, ['%q(string)'])
+ expect(cop.messages).to eq([
+ '`%q`-literals should be delimited by `[` and `]`'
+ ])
+ end
+
+ it 'does not register an offense for other delimiters ' \
+ 'when containing preferred delimiter characters' do
+ inspect_source(cop, ['%q([string])'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context '`%Q` interpolated string' do
+ it 'does not register an offense for preferred delimiters' do
+ inspect_source(cop, ['%Q[string]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for other delimiters' do
+ inspect_source(cop, ['%Q(string)'])
+ expect(cop.messages).to eq([
+ '`%Q`-literals should be delimited by `[` and `]`'
+ ])
+ end
+
+ it 'does not register an offense for other delimiters ' \
+ 'when containing preferred delimiter characters' do
+ inspect_source(cop, ['%Q([string])'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for other delimiters ' \
+ 'when containing preferred delimiter characters in interpolation' do
+ inspect_source(cop, ['%Q(#{[1].first})'])
+ expect(cop.messages.size).to eq(1)
+ end
+ end
+
+ context '`%w` string array' do
+ it 'does not register an offense for preferred delimiters' do
+ inspect_source(cop, ['%w[some words]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for other delimiters' do
+ inspect_source(cop, ['%w(some words)'])
+ expect(cop.messages).to eq([
+ '`%w`-literals should be delimited by `[` and `]`'
+ ])
+ end
+
+ it 'does not register an offense for other delimiters ' \
+ 'when containing preferred delimiter characters' do
+ inspect_source(cop, ['%w([some] [words])'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context '`%W` interpolated string array' do
+ it 'does not register an offense for preferred delimiters' do
+ inspect_source(cop, ['%W[some words]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for other delimiters' do
+ inspect_source(cop, ['%W(some words)'])
+ expect(cop.messages).to eq([
+ '`%W`-literals should be delimited by `[` and `]`'
+ ])
+ end
+
+ it 'does not register an offense for other delimiters ' \
+ 'when containing preferred delimiter characters' do
+ inspect_source(cop, ['%W([some] [words])'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for other delimiters ' \
+ 'when containing preferred delimiter characters in interpolation' do
+ inspect_source(cop, ['%W(#{[1].first})'])
+ expect(cop.messages.size).to eq(1)
+ end
+ end
+
+ context '`%r` interpolated regular expression' do
+ it 'does not register an offense for preferred delimiters' do
+ inspect_source(cop, ['%r[regexp]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for other delimiters' do
+ inspect_source(cop, ['%r(regexp)'])
+ expect(cop.messages).to eq([
+ '`%r`-literals should be delimited by `[` and `]`'
+ ])
+ end
+
+ it 'does not register an offense for other delimiters ' \
+ 'when containing preferred delimiter characters' do
+ inspect_source(cop, ['%r([regexp])'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for other delimiters ' \
+ 'when containing preferred delimiter characters in interpolation' do
+ inspect_source(cop, ['%r(#{[1].first})'])
+ expect(cop.messages.size).to eq(1)
+ end
+ end
+
+ context '`%i` symbol array', ruby: 2.0 do
+ it 'does not register an offense for preferred delimiters' do
+ inspect_source(cop, ['%i[some symbols]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for other delimiters' do
+ inspect_source(cop, ['%i(some symbols)'])
+ expect(cop.messages).to eq([
+ '`%i`-literals should be delimited by `[` and `]`'
+ ])
+ end
+ end
+
+ context '`%s` symbol' do
+ it 'does not register an offense for preferred delimiters' do
+ inspect_source(cop, ['%s[symbol]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for other delimiters' do
+ inspect_source(cop, ['%s(symbol)'])
+ expect(cop.messages).to eq([
+ '`%s`-literals should be delimited by `[` and `]`'
+ ])
+ end
+ end
+
+ context '`%x` interpolated system call' do
+ it 'does not register an offense for preferred delimiters' do
+ inspect_source(cop, ['%x[command]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for other delimiters' do
+ inspect_source(cop, ['%x(command)'])
+ expect(cop.messages).to eq([
+ '`%x`-literals should be delimited by `[` and `]`'
+ ])
+ end
+
+ it 'does not register an offense for other delimiters ' \
+ 'when containing preferred delimiter characters' do
+ inspect_source(cop, ['%x([command])'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for other delimiters ' \
+ 'when containing preferred delimiter characters in interpolation' do
+ inspect_source(cop, ['%x(#{[1].first})'])
+ expect(cop.messages.size).to eq(1)
+ end
+ end
+
+ context 'auto-correct' do
+ it 'fixes a string' do
+ new_source = autocorrect_source(cop, '%(string)')
+ expect(new_source).to eq('%[string]')
+ end
+
+ it 'fixes a string array' do
+ new_source = autocorrect_source(cop, '%w(some words)')
+ expect(new_source).to eq('%w[some words]')
+ end
+
+ it 'fixes a regular expression' do
+ original_source = '%r(.*)'
+ new_source = autocorrect_source(cop, original_source)
+ expect(new_source).to eq('%r[.*]')
+ end
+
+ it 'fixes a string with interpolation' do
+ original_source = '%Q|#{with_interpolation}|'
+ new_source = autocorrect_source(cop, original_source)
+ expect(new_source).to eq('%Q[#{with_interpolation}]')
+ end
+
+ it 'fixes a regular expression with interpolation' do
+ original_source = '%r|#{with_interpolation}|'
+ new_source = autocorrect_source(cop, original_source)
+ expect(new_source).to eq('%r[#{with_interpolation}]')
+ end
+
+ it 'fixes a regular expression with option' do
+ original_source = '%r(.*)i'
+ new_source = autocorrect_source(cop, original_source)
+ expect(new_source).to eq('%r[.*]i')
+ end
+
+ it 'preserves line breaks when fixing a multiline array' do
+ new_source = autocorrect_source(cop, ['%w(', 'some', 'words', ')'])
+ expect(new_source).to eq("%w[\nsome\nwords\n]")
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/perl_backrefs_spec.rb b/spec/rubocop/cop/style/perl_backrefs_spec.rb
new file mode 100644
index 0000000..cde4c4a
--- /dev/null
+++ b/spec/rubocop/cop/style/perl_backrefs_spec.rb
@@ -0,0 +1,17 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::PerlBackrefs do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for $1' do
+ inspect_source(cop, ['puts $1'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'auto-corrects $1 to Regexp.last_match[1]' do
+ new_source = autocorrect_source(cop, '$1')
+ expect(new_source).to eq('Regexp.last_match[1]')
+ end
+end
diff --git a/spec/rubocop/cop/style/predicate_name_spec.rb b/spec/rubocop/cop/style/predicate_name_spec.rb
new file mode 100644
index 0000000..d64c10f
--- /dev/null
+++ b/spec/rubocop/cop/style/predicate_name_spec.rb
@@ -0,0 +1,26 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::PredicateName, :config do
+ subject(:cop) { described_class.new(config) }
+ let(:cop_config) { { 'NamePrefixBlacklist' => %w(has_ is_) } }
+
+ %w(has is).each do |prefix|
+ it 'registers an offense for blacklisted method_name' do
+ inspect_source(cop, ["def #{prefix}_attr",
+ ' # ...',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages).to eq(["Rename `#{prefix}_attr` to `attr?`."])
+ expect(cop.highlights).to eq(["#{prefix}_attr"])
+ end
+ end
+
+ it 'accepts non-blacklisted method name' do
+ inspect_source(cop, ['def have_attr',
+ ' # ...',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/proc_spec.rb b/spec/rubocop/cop/style/proc_spec.rb
new file mode 100644
index 0000000..8935f73
--- /dev/null
+++ b/spec/rubocop/cop/style/proc_spec.rb
@@ -0,0 +1,27 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::Proc do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for a Proc.new call' do
+ inspect_source(cop, ['f = Proc.new { |x| puts x }'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts the proc method' do
+ inspect_source(cop, ['f = proc { |x| puts x }'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts the Proc.new call outside of block' do
+ inspect_source(cop, ['p = Proc.new'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects Proc.new to proc' do
+ corrected = autocorrect_source(cop, ['Proc.new { test }'])
+ expect(corrected).to eq 'proc { test }'
+ end
+end
diff --git a/spec/rubocop/cop/style/raise_args_spec.rb b/spec/rubocop/cop/style/raise_args_spec.rb
new file mode 100644
index 0000000..43bf271
--- /dev/null
+++ b/spec/rubocop/cop/style/raise_args_spec.rb
@@ -0,0 +1,87 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::RaiseArgs, :config do
+ subject(:cop) { described_class.new(config) }
+
+ context 'when enforced style is compact' do
+ let(:cop_config) { { 'EnforcedStyle' => 'compact' } }
+
+ it 'reports an offense for a raise with 2 args' do
+ inspect_source(cop, ['raise RuntimeError, msg'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' => 'exploded')
+ end
+
+ it 'reports an offense for correct + opposite' do
+ inspect_source(cop, ['if a',
+ ' raise RuntimeError, msg',
+ 'else',
+ ' raise Ex.new(msg)',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Provide an exception object as an argument to `raise`.'])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'reports an offense for a raise with 3 args' do
+ inspect_source(cop, ['raise RuntimeError, msg, caller'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts a raise with msg argument' do
+ inspect_source(cop, ['raise msg'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a raise with an exception argument' do
+ inspect_source(cop, ['raise Ex.new(msg)'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'when enforced style is exploded' do
+ let(:cop_config) { { 'EnforcedStyle' => 'exploded' } }
+
+ it 'reports an offense for a raise with exception object' do
+ inspect_source(cop, ['raise Ex.new(msg)'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Provide an exception class and message ' \
+ 'as arguments to `raise`.'])
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' => 'compact')
+ end
+
+ it 'reports an offense for opposite + correct' do
+ inspect_source(cop, ['if a',
+ ' raise RuntimeError, msg',
+ 'else',
+ ' raise Ex.new(msg)',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'accepts exception constructor with more than 1 argument' do
+ inspect_source(cop, ['raise RuntimeError.new(a1, a2, a3)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a raise with 3 args' do
+ inspect_source(cop, ['raise RuntimeError, msg, caller'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a raise with 2 args' do
+ inspect_source(cop, ['raise RuntimeError, msg'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a raise with msg argument' do
+ inspect_source(cop, ['raise msg'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/redundant_begin_spec.rb b/spec/rubocop/cop/style/redundant_begin_spec.rb
new file mode 100644
index 0000000..9f9b18b
--- /dev/null
+++ b/spec/rubocop/cop/style/redundant_begin_spec.rb
@@ -0,0 +1,57 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::RedundantBegin do
+ subject(:cop) { described_class.new }
+
+ it 'reports an offense for def with redundant begin block' do
+ src = ['def func',
+ ' begin',
+ ' ala',
+ ' rescue => e',
+ ' bala',
+ ' end',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'reports an offense for defs with redundant begin block' do
+ src = ['def Test.func',
+ ' begin',
+ ' ala',
+ ' rescue => e',
+ ' bala',
+ ' end',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts a def with required begin block' do
+ src = ['def func',
+ ' begin',
+ ' ala',
+ ' rescue => e',
+ ' bala',
+ ' end',
+ ' something',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a defs with required begin block' do
+ src = ['def Test.func',
+ ' begin',
+ ' ala',
+ ' rescue => e',
+ ' bala',
+ ' end',
+ ' something',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/redundant_exception_spec.rb b/spec/rubocop/cop/style/redundant_exception_spec.rb
new file mode 100644
index 0000000..6d775b7
--- /dev/null
+++ b/spec/rubocop/cop/style/redundant_exception_spec.rb
@@ -0,0 +1,27 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::RedundantException do
+ subject(:cop) { described_class.new }
+
+ it 'reports an offense for a raise with RuntimeError' do
+ inspect_source(cop, ['raise RuntimeError, msg'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'reports an offense for a fail with RuntimeError' do
+ inspect_source(cop, ['fail RuntimeError, msg'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts a raise with RuntimeError if it does not have 2 args' do
+ inspect_source(cop, ['raise RuntimeError, msg, caller'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a fail with RuntimeError if it does not have 2 args' do
+ inspect_source(cop, ['fail RuntimeError, msg, caller'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/redundant_return_spec.rb b/spec/rubocop/cop/style/redundant_return_spec.rb
new file mode 100644
index 0000000..d9eb34d
--- /dev/null
+++ b/spec/rubocop/cop/style/redundant_return_spec.rb
@@ -0,0 +1,171 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::RedundantReturn, :config do
+ subject(:cop) { described_class.new(config) }
+ let(:cop_config) { { 'AllowMultipleReturnValues' => false } }
+
+ it 'reports an offense for def with only a return' do
+ src = ['def func',
+ ' return something',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'reports an offense for defs with only a return' do
+ src = ['def Test.func',
+ ' return something',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'reports an offense for def ending with return' do
+ src = ['def func',
+ ' one',
+ ' two',
+ ' return something',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'reports an offense for defs ending with return' do
+ src = ['def func',
+ ' one',
+ ' two',
+ ' return something',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts return in a non-final position' do
+ src = ['def func',
+ ' return something if something_else',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not blow up on empty method body' do
+ src = ['def func',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects by removing redundant returns' do
+ src = ['def func',
+ ' one',
+ ' two',
+ ' return something',
+ 'end'].join("\n")
+ result_src = ['def func',
+ ' one',
+ ' two',
+ ' something',
+ 'end'].join("\n")
+ new_source = autocorrect_source(cop, src)
+ expect(new_source).to eq(result_src)
+ end
+
+ context 'when multi-value returns are not allowed' do
+ it 'reports an offense for def with only a return' do
+ src = ['def func',
+ ' return something, test',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'reports an offense for defs with only a return' do
+ src = ['def Test.func',
+ ' return something, test',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'reports an offense for def ending with return' do
+ src = ['def func',
+ ' one',
+ ' two',
+ ' return something, test',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'reports an offense for defs ending with return' do
+ src = ['def func',
+ ' one',
+ ' two',
+ ' return something, test',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'auto-corrects by making implicit arrays explicit' do
+ src = ['def func',
+ ' return 1, 2',
+ 'end'].join("\n")
+ result_src = ['def func',
+ ' [1, 2]', # Just 1, 2 is not valid Ruby.
+ 'end'].join("\n")
+ new_source = autocorrect_source(cop, src)
+ expect(new_source).to eq(result_src)
+ end
+ end
+
+ context 'when multi-value returns are allowed' do
+ let(:cop_config) { { 'AllowMultipleReturnValues' => true } }
+
+ it 'accepts def with only a return' do
+ src = ['def func',
+ ' return something, test',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts defs with only a return' do
+ src = ['def Test.func',
+ ' return something, test',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts def ending with return' do
+ src = ['def func',
+ ' one',
+ ' two',
+ ' return something, test',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts defs ending with return' do
+ src = ['def func',
+ ' one',
+ ' two',
+ ' return something, test',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not auto-correct' do
+ src = ['def func',
+ ' return 1, 2',
+ 'end'].join("\n")
+ new_source = autocorrect_source(cop, src)
+ expect(new_source).to eq(src)
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/redundant_self_spec.rb b/spec/rubocop/cop/style/redundant_self_spec.rb
new file mode 100644
index 0000000..00fb4f3
--- /dev/null
+++ b/spec/rubocop/cop/style/redundant_self_spec.rb
@@ -0,0 +1,142 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::RedundantSelf do
+ subject(:cop) { described_class.new }
+
+ it 'reports an offense a self receiver on an rvalue' do
+ src = ['a = self.b']
+ inspect_source(cop, src)
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts a self receiver on an lvalue of an assignment' do
+ src = ['self.a = b']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a self receiver on an lvalue of an or-assignment' do
+ src = ['self.logger ||= Rails.logger']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a self receiver on an lvalue of an and-assignment' do
+ src = ['self.flag &&= value']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a self receiver on an lvalue of a plus-assignment' do
+ src = ['self.sum += 10']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a self receiver with the square bracket operator' do
+ src = ['self[a]']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a self receiver with the double less-than operator' do
+ src = ['self << a']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a self receiver for methods named like ruby keywords' do
+ src = ['a = self.class',
+ 'self.for(deps, [], true)',
+ 'self.and(other)',
+ 'self.or(other)',
+ 'self.alias',
+ 'self.begin',
+ 'self.break',
+ 'self.case',
+ 'self.def',
+ 'self.defined',
+ 'self.do',
+ 'self.else',
+ 'self.elsif',
+ 'self.end',
+ 'self.ensure',
+ 'self.false',
+ 'self.if',
+ 'self.in',
+ 'self.module',
+ 'self.next',
+ 'self.nil',
+ 'self.not',
+ 'self.redo',
+ 'self.rescue',
+ 'self.retry',
+ 'self.return',
+ 'self.self',
+ 'self.super',
+ 'self.then',
+ 'self.true',
+ 'self.undef',
+ 'self.unless',
+ 'self.until',
+ 'self.when',
+ 'self.while',
+ 'self.yield'
+ ]
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a self receiver used to distinguish from blockarg' do
+ src = ['def requested_specs(&groups)',
+ ' some_method(self.groups)',
+ 'end'
+ ]
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a self receiver used to distinguish from argument' do
+ src = ['def requested_specs(groups)',
+ ' some_method(self.groups)',
+ 'end'
+ ]
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a self receiver used to distinguish from argument' do
+ src = ['def requested_specs(final = true)',
+ ' something if self.final != final',
+ 'end'
+ ]
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a self receiver used to distinguish from local variable' do
+ src = ['def requested_specs',
+ ' @requested_specs ||= begin',
+ ' groups = self.groups - Bundler.settings.without',
+ ' groups.map! { |g| g.to_sym }',
+ ' specs_for(groups)',
+ ' end',
+ 'end'
+ ]
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a self receiver used to distinguish from constant' do
+ src = ['self.Foo']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects by removing redundant self' do
+ new_source = autocorrect_source(cop, ['self.x'])
+ expect(new_source).to eq('x')
+ end
+end
diff --git a/spec/rubocop/cop/style/regexp_literal_spec.rb b/spec/rubocop/cop/style/regexp_literal_spec.rb
new file mode 100644
index 0000000..74b5d3e
--- /dev/null
+++ b/spec/rubocop/cop/style/regexp_literal_spec.rb
@@ -0,0 +1,104 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::RegexpLiteral, :config do
+ subject(:cop) { described_class.new(config) }
+
+ context 'when MaxSlashes is -1' do
+ let(:cop_config) { { 'MaxSlashes' => -1 } }
+
+ it 'fails' do
+ expect { inspect_source(cop, ['x =~ /home/']) }
+ .to raise_error(RuntimeError)
+ end
+ end
+
+ context 'when MaxSlashes is 0' do
+ let(:cop_config) { { 'MaxSlashes' => 0 } }
+
+ it 'registers an offense for one slash in // regexp' do
+ inspect_source(cop, ['x =~ /home\//'])
+ expect(cop.messages)
+ .to eq(['Use %r for regular expressions matching more ' \
+ "than 0 '/' characters."])
+ expect(cop.config_to_allow_offenses).to eq('MaxSlashes' => 1)
+ end
+
+ it 'accepts zero slashes in // regexp' do
+ inspect_source(cop, ['z =~ /a/'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for zero slashes in %r regexp' do
+ inspect_source(cop, ['y =~ %r(etc)'])
+ expect(cop.messages)
+ .to eq(['Use %r only for regular expressions matching more ' \
+ "than 0 '/' characters."])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'accepts %r regexp with one slash' do
+ inspect_source(cop, ['x =~ %r(/home)'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'when MaxSlashes is 1' do
+ let(:cop_config) { { 'MaxSlashes' => 1 } }
+
+ it 'registers an offense for two slashes in // regexp' do
+ inspect_source(cop, ['x =~ /home\/\//',
+ 'y =~ /etc\/top\//'])
+ expect(cop.messages)
+ .to eq(['Use %r for regular expressions matching more ' \
+ "than 1 '/' character."] * 2)
+ expect(cop.config_to_allow_offenses).to eq('MaxSlashes' => 2)
+ end
+
+ it 'registers offenses for slashes with too many and %r with too few' do
+ inspect_source(cop, ['x =~ /home\/\//',
+ 'y =~ %r{home}'])
+ expect(cop.messages)
+ .to eq(['Use %r for regular expressions matching more ' \
+ "than 1 '/' character.",
+ 'Use %r only for regular expressions matching more ' \
+ "than 1 '/' character."])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'registers offenses for %r with too few and slashes with too many' do
+ inspect_source(cop, ['y =~ %r{home}',
+ 'x =~ /home\/\//'])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'accepts zero or one slash in // regexp' do
+ inspect_source(cop, ['x =~ /\/home/',
+ 'y =~ /\//',
+ 'w =~ /\//m',
+ 'z =~ /a/'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'ignores slashes do not belong // regexp' do
+ inspect_source(cop, ['x =~ /\s{#{x[/\s+/].length}}/'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for zero or one slash in %r regexp' do
+ inspect_source(cop, ['x =~ %r(/home)',
+ 'y =~ %r(etc)'])
+ expect(cop.messages)
+ .to eq(['Use %r only for regular expressions matching more ' \
+ "than 1 '/' character."] * 2)
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'accepts %r regexp with two or more slashes' do
+ inspect_source(cop, ['x =~ %r(/home/)',
+ 'y =~ %r(/////)'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/rescue_modifier_spec.rb b/spec/rubocop/cop/style/rescue_modifier_spec.rb
new file mode 100644
index 0000000..477cf48
--- /dev/null
+++ b/spec/rubocop/cop/style/rescue_modifier_spec.rb
@@ -0,0 +1,116 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::RescueModifier do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for modifier rescue' do
+ inspect_source(cop,
+ ['method rescue handle'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Avoid using `rescue` in its modifier form.'])
+ end
+
+ it 'handles more complex expression with modifier rescue' do
+ inspect_source(cop,
+ ['method1 or method2 rescue handle'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Avoid using `rescue` in its modifier form.'])
+ end
+
+ it 'handles modifier rescue in normal rescue' do
+ inspect_source(cop,
+ ['begin',
+ ' test rescue modifier_handle',
+ 'rescue',
+ ' normal_handle',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.line).to eq(2)
+ end
+
+ it 'does not register an offense for normal rescue' do
+ inspect_source(cop,
+ ['begin',
+ ' test',
+ 'rescue',
+ ' handle',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for normal rescue with ensure' do
+ inspect_source(cop,
+ ['begin',
+ ' test',
+ 'rescue',
+ ' handle',
+ 'ensure',
+ ' cleanup',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for nested normal rescue' do
+ inspect_source(cop,
+ ['begin',
+ ' begin',
+ ' test',
+ ' rescue',
+ ' handle_inner',
+ ' end',
+ 'rescue',
+ ' handle_outer',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ context 'when an instance method has implicit begin' do
+ it 'accepts normal rescue' do
+ inspect_source(cop,
+ ['def some_method',
+ ' test',
+ 'rescue',
+ ' handle',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'handles modifier rescue in body of implicit begin' do
+ inspect_source(cop,
+ ['def some_method',
+ ' test rescue modifier_handle',
+ 'rescue',
+ ' normal_handle',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.line).to eq(2)
+ end
+ end
+
+ context 'when a singleton method has implicit begin' do
+ it 'accepts normal rescue' do
+ inspect_source(cop,
+ ['def self.some_method',
+ ' test',
+ 'rescue',
+ ' handle',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'handles modifier rescue in body of implicit begin' do
+ inspect_source(cop,
+ ['def self.some_method',
+ ' test rescue modifier_handle',
+ 'rescue',
+ ' normal_handle',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.line).to eq(2)
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/self_assignment_spec.rb b/spec/rubocop/cop/style/self_assignment_spec.rb
new file mode 100644
index 0000000..27af01a
--- /dev/null
+++ b/spec/rubocop/cop/style/self_assignment_spec.rb
@@ -0,0 +1,43 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::SelfAssignment do
+ subject(:cop) { described_class.new }
+
+ described_class::OPS.product(['x', '@x', '@@x']).each do |op, var|
+ it "registers an offense for non-shorthand assignment #{op} and #{var}" do
+ inspect_source(cop,
+ ["#{var} = #{var} #{op} y"])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(["Use self-assignment shorthand `#{op}=`."])
+ end
+
+ it "accepts shorthand assignment for #{op} and #{var}" do
+ inspect_source(cop,
+ ["#{var} = #{var} #{op} y"])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(["Use self-assignment shorthand `#{op}=`."])
+ end
+ end
+
+ ['||', '&&'].product(['x', '@x', '@@x']).each do |op, var|
+ it "registers an offense for non-shorthand assignment #{op} and #{var}" do
+ inspect_source(cop,
+ ["#{var} = #{var} #{op} y"])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(["Use self-assignment shorthand `#{op}=`."])
+ end
+
+ it "accepts shorthand assignment for #{op} and #{var}" do
+ inspect_source(cop,
+ ["#{var} = #{var} #{op} y"])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(["Use self-assignment shorthand `#{op}=`."])
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/semicolon_spec.rb b/spec/rubocop/cop/style/semicolon_spec.rb
new file mode 100644
index 0000000..c26dd9f
--- /dev/null
+++ b/spec/rubocop/cop/style/semicolon_spec.rb
@@ -0,0 +1,114 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::Semicolon, :config do
+ subject(:cop) { described_class.new(config) }
+ let(:cop_config) { { 'AllowAsExpressionSeparator' => false } }
+
+ it 'registers an offense for a single expression' do
+ inspect_source(cop,
+ ['puts "this is a test";'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for several expressions' do
+ inspect_source(cop,
+ ['puts "this is a test"; puts "So is this"'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for one line method with two statements' do
+ inspect_source(cop,
+ ['def foo(a) x(1); y(2); z(3); end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts semicolon before end if so configured' do
+ inspect_source(cop,
+ ['def foo(a) z(3); end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts semicolon after params if so configured' do
+ inspect_source(cop,
+ ['def foo(a); z(3) end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts one line method definitions' do
+ inspect_source(cop,
+ ['def foo1; x(3) end',
+ 'def initialize(*_); end',
+ 'def foo2() x(3); end',
+ 'def foo3; x(3); end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts one line empty class definitions' do
+ inspect_source(cop,
+ ['# Prefer a single-line format for class ...',
+ 'class Foo < Exception; end',
+ '',
+ 'class Bar; end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts one line empty method definitions' do
+ inspect_source(cop,
+ ['# One exception to the rule are empty-body methods',
+ 'def no_op; end',
+ '',
+ 'def foo; end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts one line empty module definitions' do
+ inspect_source(cop,
+ ['module Foo; end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for semicolon at the end no matter what' do
+ inspect_source(cop,
+ ['module Foo; end;'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accept semicolons inside strings' do
+ inspect_source(cop,
+ ['string = ";',
+ 'multi-line string"'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects semicolons when syntactically possible' do
+ corrected =
+ autocorrect_source(cop,
+ ['module Foo; end;',
+ 'puts "this is a test";',
+ 'puts "this is a test"; puts "So is this"',
+ 'def foo(a) x(1); y(2); z(3); end'])
+ expect(corrected)
+ .to eq(['module Foo; end',
+ 'puts "this is a test"',
+ 'puts "this is a test"; puts "So is this"',
+ 'def foo(a) x(1); y(2); z(3); end'].join("\n"))
+ end
+
+ context 'when AllowAsExpressionSeparator is true' do
+ let(:cop_config) { { 'AllowAsExpressionSeparator' => true } }
+
+ it 'accepts several expressions' do
+ inspect_source(cop,
+ ['puts "this is a test"; puts "So is this"'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts one line method with two statements' do
+ inspect_source(cop,
+ ['def foo(a) x(1); y(2); z(3); end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/signal_exception_spec.rb b/spec/rubocop/cop/style/signal_exception_spec.rb
new file mode 100644
index 0000000..313bb68
--- /dev/null
+++ b/spec/rubocop/cop/style/signal_exception_spec.rb
@@ -0,0 +1,290 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::SignalException, :config do
+ subject(:cop) { described_class.new(config) }
+
+ context 'when enforced style is `semantic`' do
+ let(:cop_config) { { 'EnforcedStyle' => 'semantic' } }
+
+ it 'registers an offense for raise in begin section' do
+ inspect_source(cop,
+ ['begin',
+ ' raise',
+ 'rescue Exception',
+ ' #do nothing',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Use `fail` instead of `raise` to signal exceptions.'])
+ end
+
+ it 'registers an offense for raise in def body' do
+ inspect_source(cop,
+ ['def test',
+ ' raise',
+ 'rescue Exception',
+ ' #do nothing',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Use `fail` instead of `raise` to signal exceptions.'])
+ end
+
+ it 'registers an offense for fail in rescue section' do
+ inspect_source(cop,
+ ['begin',
+ ' fail',
+ 'rescue Exception',
+ ' fail',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Use `raise` instead of `fail` to rethrow exceptions.'])
+ end
+
+ it 'accepts raise in rescue section' do
+ inspect_source(cop,
+ ['begin',
+ ' fail',
+ 'rescue Exception',
+ ' raise RuntimeError',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts raise in def with multiple rescues' do
+ inspect_source(cop,
+ ['def test',
+ ' fail',
+ 'rescue StandardError',
+ ' # handle error',
+ 'rescue Exception',
+ ' raise',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for fail in def rescue section' do
+ inspect_source(cop,
+ ['def test',
+ ' fail',
+ 'rescue Exception',
+ ' fail',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Use `raise` instead of `fail` to rethrow exceptions.'])
+ end
+
+ it 'registers an offense for fail in second rescue' do
+ inspect_source(cop,
+ ['def test',
+ ' fail',
+ 'rescue StandardError',
+ ' # handle error',
+ 'rescue Exception',
+ ' fail',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers only offense for one raise that should be fail' do
+ # This is a special case that has caused double reporting.
+ inspect_source(cop,
+ ['map do',
+ " raise 'I'",
+ 'end.flatten.compact'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Use `fail` instead of `raise` to signal exceptions.'])
+ end
+
+ it 'accepts raise in def rescue section' do
+ inspect_source(cop,
+ ['def test',
+ ' fail',
+ 'rescue Exception',
+ ' raise',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for raise not in a begin/rescue/end' do
+ inspect_source(cop,
+ ["case cop_config['EnforcedStyle']",
+ "when 'single_quotes' then true",
+ "when 'double_quotes' then false",
+ "else raise 'Unknown StringLiterals style'",
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Use `fail` instead of `raise` to signal exceptions.'])
+ end
+
+ it 'registers one offense for each raise' do
+ inspect_source(cop,
+ ['cop.stub(:on_def) { raise RuntimeError }',
+ 'cop.stub(:on_def) { raise RuntimeError }'])
+ expect(cop.offenses.size).to eq(2)
+ expect(cop.messages)
+ .to eq(['Use `fail` instead of `raise` to signal exceptions.'] * 2)
+ end
+
+ it 'is not confused by nested begin/rescue' do
+ inspect_source(cop,
+ ['begin',
+ ' raise',
+ ' begin',
+ ' raise',
+ ' rescue',
+ ' fail',
+ ' end',
+ 'rescue Exception',
+ ' #do nothing',
+ 'end'])
+ expect(cop.offenses.size).to eq(3)
+ expect(cop.messages)
+ .to eq(['Use `fail` instead of `raise` to signal exceptions.'] * 2 +
+ ['Use `raise` instead of `fail` to rethrow exceptions.'])
+ end
+
+ it 'auto-corrects raise to fail when appropriate' do
+ new_source = autocorrect_source(cop,
+ ['begin',
+ ' raise',
+ 'rescue Exception',
+ ' raise',
+ 'end'])
+ expect(new_source).to eq(['begin',
+ ' fail',
+ 'rescue Exception',
+ ' raise',
+ 'end'].join("\n"))
+ end
+
+ it 'auto-corrects fail to raise when appropriate' do
+ new_source = autocorrect_source(cop,
+ ['begin',
+ ' fail',
+ 'rescue Exception',
+ ' fail',
+ 'end'])
+ expect(new_source).to eq(['begin',
+ ' fail',
+ 'rescue Exception',
+ ' raise',
+ 'end'].join("\n"))
+ end
+ end
+
+ context 'when enforced style is `raise`' do
+ let(:cop_config) { { 'EnforcedStyle' => 'only_raise' } }
+
+ it 'registers an offense for fail in begin section' do
+ inspect_source(cop,
+ ['begin',
+ ' fail',
+ 'rescue Exception',
+ ' #do nothing',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Always use `raise` to signal exceptions.'])
+ end
+
+ it 'registers an offense for fail in def body' do
+ inspect_source(cop,
+ ['def test',
+ ' fail',
+ 'rescue Exception',
+ ' #do nothing',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Always use `raise` to signal exceptions.'])
+ end
+
+ it 'registers an offense for fail in rescue section' do
+ inspect_source(cop,
+ ['begin',
+ ' raise',
+ 'rescue Exception',
+ ' fail',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Always use `raise` to signal exceptions.'])
+ end
+
+ it 'auto-corrects fail to raise always' do
+ new_source = autocorrect_source(cop,
+ ['begin',
+ ' fail',
+ 'rescue Exception',
+ ' fail',
+ 'end'])
+ expect(new_source).to eq(['begin',
+ ' raise',
+ 'rescue Exception',
+ ' raise',
+ 'end'].join("\n"))
+ end
+
+ end
+
+ context 'when enforced style is `fail`' do
+ let(:cop_config) { { 'EnforcedStyle' => 'only_fail' } }
+
+ it 'registers an offense for raise in begin section' do
+ inspect_source(cop,
+ ['begin',
+ ' raise',
+ 'rescue Exception',
+ ' #do nothing',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Always use `fail` to signal exceptions.'])
+ end
+
+ it 'registers an offense for raise in def body' do
+ inspect_source(cop,
+ ['def test',
+ ' raise',
+ 'rescue Exception',
+ ' #do nothing',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Always use `fail` to signal exceptions.'])
+ end
+
+ it 'registers an offense for raise in rescue section' do
+ inspect_source(cop,
+ ['begin',
+ ' fail',
+ 'rescue Exception',
+ ' raise',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Always use `fail` to signal exceptions.'])
+ end
+
+ it 'auto-corrects raise to fail always' do
+ new_source = autocorrect_source(cop,
+ ['begin',
+ ' raise',
+ 'rescue Exception',
+ ' raise',
+ 'end'])
+ expect(new_source).to eq(['begin',
+ ' fail',
+ 'rescue Exception',
+ ' fail',
+ 'end'].join("\n"))
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/single_line_block_params_spec.rb b/spec/rubocop/cop/style/single_line_block_params_spec.rb
new file mode 100644
index 0000000..ab0a384
--- /dev/null
+++ b/spec/rubocop/cop/style/single_line_block_params_spec.rb
@@ -0,0 +1,70 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::SingleLineBlockParams, :config do
+ subject(:cop) { described_class.new(config) }
+ let(:cop_config) do
+ { 'Methods' =>
+ [{ 'reduce' => %w(a e) },
+ { 'test' => %w(x y) }]
+ }
+ end
+
+ it 'find wrong argument names in calls with different syntax' do
+ inspect_source(cop,
+ ['def m',
+ ' [0, 1].reduce { |c, d| c + d }',
+ ' [0, 1].reduce{ |c, d| c + d }',
+ ' [0, 1].reduce(5) { |c, d| c + d }',
+ ' [0, 1].reduce(5){ |c, d| c + d }',
+ ' [0, 1].reduce (5) { |c, d| c + d }',
+ ' [0, 1].reduce(5) { |c, d| c + d }',
+ ' ala.test { |x, z| bala }',
+ 'end'])
+ expect(cop.offenses.size).to eq(7)
+ expect(cop.offenses.map(&:line).sort).to eq((2..8).to_a)
+ expect(cop.messages.first)
+ .to eq('Name `reduce` block params `|a, e|`.')
+ end
+
+ it 'allows calls with proper argument names' do
+ inspect_source(cop,
+ ['def m',
+ ' [0, 1].reduce { |a, e| a + e }',
+ ' [0, 1].reduce{ |a, e| a + e }',
+ ' [0, 1].reduce(5) { |a, e| a + e }',
+ ' [0, 1].reduce(5){ |a, e| a + e }',
+ ' [0, 1].reduce (5) { |a, e| a + e }',
+ ' [0, 1].reduce(5) { |a, e| a + e }',
+ ' ala.test { |x, y| bala }',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'ignores do..end blocks' do
+ inspect_source(cop,
+ ['def m',
+ ' [0, 1].reduce do |c, d|',
+ ' c + d',
+ ' end',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'ignores :reduce symbols' do
+ inspect_source(cop,
+ ['def m',
+ ' call_method(:reduce) { |a, b| a + b}',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not report when destructuring is used' do
+ inspect_source(cop,
+ ['def m',
+ ' test.reduce { |a, (id, _)| a + id}',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/single_line_methods_spec.rb b/spec/rubocop/cop/style/single_line_methods_spec.rb
new file mode 100644
index 0000000..50748a8
--- /dev/null
+++ b/spec/rubocop/cop/style/single_line_methods_spec.rb
@@ -0,0 +1,90 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::SingleLineMethods, :config do
+ subject(:cop) { described_class.new(config) }
+ let(:cop_config) { { 'AllowIfMethodIsEmpty' => true } }
+
+ it 'registers an offense for a single-line method' do
+ inspect_source(cop,
+ ['def some_method; body end',
+ 'def link_to(name, url); {:name => name}; end',
+ 'def @table.columns; super; end'])
+ expect(cop.messages).to eq(
+ ['Avoid single-line method definitions.'] * 3)
+ end
+
+ context 'when AllowIfMethodIsEmpty is disabled' do
+ let(:cop_config) { { 'AllowIfMethodIsEmpty' => false } }
+
+ it 'registers an offense for an empty method' do
+ inspect_source(cop, ['def no_op; end',
+ 'def self.resource_class=(klass); end',
+ 'def @table.columns; end'])
+ expect(cop.offenses.size).to eq(3)
+ end
+ end
+
+ context 'when AllowIfMethodIsEmpty is enabled' do
+ let(:cop_config) { { 'AllowIfMethodIsEmpty' => true } }
+
+ it 'accepts a single-line empty method' do
+ inspect_source(cop, ['def no_op; end',
+ 'def self.resource_class=(klass); end',
+ 'def @table.columns; end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ it 'accepts a multi-line method' do
+ inspect_source(cop, ['def some_method',
+ ' body',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not crash on an method with a capitalized name' do
+ inspect_source(cop, ['def NoSnakeCase',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects def with semicolon after method name' do
+ corrected = autocorrect_source(cop,
+ [' def some_method; body end # Cmnt'])
+ expect(corrected).to eq [' # Cmnt',
+ ' def some_method; ',
+ ' body ',
+ ' end '].join("\n")
+ end
+
+ it 'auto-corrects defs with parentheses after method name' do
+ corrected = autocorrect_source(cop, [' def self.some_method() body end'])
+ expect(corrected).to eq [' def self.some_method() ',
+ ' body ',
+ ' end'].join("\n")
+ end
+
+ it 'auto-corrects def with argument in parentheses' do
+ corrected = autocorrect_source(cop, [' def some_method(arg) body end'])
+ expect(corrected).to eq [' def some_method(arg) ',
+ ' body ',
+ ' end'].join("\n")
+ end
+
+ it 'auto-corrects def with argument and no parentheses' do
+ corrected = autocorrect_source(cop, [' def some_method arg; body end'])
+ expect(corrected).to eq [' def some_method arg; ',
+ ' body ',
+ ' end'].join("\n")
+ end
+
+ it 'auto-corrects def with semicolon before end' do
+ corrected = autocorrect_source(cop, [' def some_method; b1; b2; end'])
+ expect(corrected).to eq [' def some_method; ',
+ ' b1; ',
+ ' b2; ',
+ ' end'].join("\n")
+ end
+end
diff --git a/spec/rubocop/cop/style/single_space_before_first_arg_spec.rb b/spec/rubocop/cop/style/single_space_before_first_arg_spec.rb
new file mode 100644
index 0000000..6ba5db7
--- /dev/null
+++ b/spec/rubocop/cop/style/single_space_before_first_arg_spec.rb
@@ -0,0 +1,63 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::SingleSpaceBeforeFirstArg do
+ subject(:cop) { described_class.new }
+
+ context 'for method calls without parentheses' do
+ it 'registers an offense for method call with two spaces before the ' \
+ 'first arg' do
+ inspect_source(cop, ['something x',
+ 'a.something y, z'])
+ expect(cop.messages)
+ .to eq(['Put one space between the method name and the first ' \
+ 'argument.'] * 2)
+ expect(cop.highlights).to eq([' ', ' '])
+ end
+
+ it 'auto-corrects extra space' do
+ new_source = autocorrect_source(cop, ['something x',
+ 'a.something y, z'])
+ expect(new_source).to eq(['something x',
+ 'a.something y, z'].join("\n"))
+ end
+
+ it 'accepts a method call with one space before the first arg' do
+ inspect_source(cop, ['something x',
+ 'a.something y, z'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts + operator' do
+ inspect_source(cop, ['something +',
+ ' x'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts setter call' do
+ inspect_source(cop, ['something.x =',
+ ' y'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts multiple space containing line break' do
+ inspect_source(cop, ['something \\',
+ ' x'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'for method calls with parentheses' do
+ it 'accepts a method call without space' do
+ inspect_source(cop, ['something(x)',
+ 'a.something(y, z)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a method call with space after the left parenthesis' do
+ inspect_source(cop, ['something( x )'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/space_after_colon_spec.rb b/spec/rubocop/cop/style/space_after_colon_spec.rb
new file mode 100644
index 0000000..926be61
--- /dev/null
+++ b/spec/rubocop/cop/style/space_after_colon_spec.rb
@@ -0,0 +1,38 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::SpaceAfterColon do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for colon without space after it' do
+ # TODO: There is double reporting of the last colon (also from
+ # SpaceAroundOperators).
+ inspect_source(cop, ['x = w ? {a:3}:4'])
+ expect(cop.messages).to eq(['Space missing after colon.'] * 2)
+ expect(cop.highlights).to eq([':'] * 2)
+ end
+
+ it 'accepts colons in symbols' do
+ inspect_source(cop, ['x = :a'])
+ expect(cop.messages).to be_empty
+ end
+
+ if RUBY_VERSION >= '2.1'
+ it 'accepts colons denoting required keyword argument' do
+ inspect_source(cop, ['def initialize(table:, nodes:)',
+ 'end'])
+ expect(cop.messages).to be_empty
+ end
+ end
+
+ it 'accepts colons in strings' do
+ inspect_source(cop, ["str << ':'"])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'auto-corrects missing space' do
+ new_source = autocorrect_source(cop, 'x = w ? {a:3}:4')
+ expect(new_source).to eq('x = w ? {a: 3}: 4')
+ end
+end
diff --git a/spec/rubocop/cop/style/space_after_comma_spec.rb b/spec/rubocop/cop/style/space_after_comma_spec.rb
new file mode 100644
index 0000000..28af035
--- /dev/null
+++ b/spec/rubocop/cop/style/space_after_comma_spec.rb
@@ -0,0 +1,30 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::SpaceAfterComma do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for block argument commas without space' do
+ inspect_source(cop, ['each { |s,t| }'])
+ expect(cop.messages).to eq(
+ ['Space missing after comma.'])
+ end
+
+ it 'registers an offense for array index commas without space' do
+ inspect_source(cop, ['formats[0,1]'])
+ expect(cop.messages).to eq(
+ ['Space missing after comma.'])
+ end
+
+ it 'registers an offense for method call arg commas without space' do
+ inspect_source(cop, ['a(1,2)'])
+ expect(cop.messages).to eq(
+ ['Space missing after comma.'])
+ end
+
+ it 'auto-corrects missing space' do
+ new_source = autocorrect_source(cop, 'each { |s,t| a(1,formats[0,1])}')
+ expect(new_source).to eq('each { |s, t| a(1, formats[0, 1])}')
+ end
+end
diff --git a/spec/rubocop/cop/style/space_after_control_keyword_spec.rb b/spec/rubocop/cop/style/space_after_control_keyword_spec.rb
new file mode 100644
index 0000000..3115d35
--- /dev/null
+++ b/spec/rubocop/cop/style/space_after_control_keyword_spec.rb
@@ -0,0 +1,84 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::SpaceAfterControlKeyword do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for normal if' do
+ inspect_source(cop,
+ ['if(test) then result end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for modifier unless' do
+ inspect_source(cop, ['action unless(test)'])
+
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'does not get confused by keywords' do
+ inspect_source(cop, ['[:if, :unless].action'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not get confused by the ternary operator' do
+ inspect_source(cop, ['a ? b : c'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for if, elsif, and unless' do
+ inspect_source(cop,
+ ['if(a)',
+ 'elsif(b)',
+ ' unless(c)',
+ ' end',
+ 'end'])
+ expect(cop.offenses.map(&:line)).to eq([1, 2, 3])
+ end
+
+ it 'registers an offense for case and when' do
+ inspect_source(cop,
+ ['case(a)',
+ 'when(0) then 1',
+ 'end'])
+ expect(cop.offenses.map(&:line)).to eq([1, 2])
+ end
+
+ it 'registers an offense for while and until' do
+ inspect_source(cop,
+ ['while(a)',
+ ' b until(c)',
+ 'end'])
+ expect(cop.offenses.map(&:line)).to eq([1, 2])
+ end
+
+ it 'auto-corrects missing space' do
+ new_source = autocorrect_source(cop, ['if(test) then result end',
+ 'action unless(test)',
+ 'if(a)',
+ 'elsif(b)',
+ ' unless(c)',
+ ' end',
+ 'end',
+ 'case(a)',
+ 'when(0) then 1',
+ 'end',
+ 'while(a)',
+ ' b until(c)',
+ 'end'])
+ expect(new_source).to eq(['if (test) then result end',
+ 'action unless (test)',
+ 'if (a)',
+ 'elsif (b)',
+ ' unless (c)',
+ ' end',
+ 'end',
+ 'case (a)',
+ 'when (0) then 1',
+ 'end',
+ 'while (a)',
+ ' b until (c)',
+ 'end'].join("\n"))
+ end
+end
diff --git a/spec/rubocop/cop/style/space_after_method_name_spec.rb b/spec/rubocop/cop/style/space_after_method_name_spec.rb
new file mode 100644
index 0000000..9722c51
--- /dev/null
+++ b/spec/rubocop/cop/style/space_after_method_name_spec.rb
@@ -0,0 +1,70 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::SpaceAfterMethodName do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for def with space before the parenthesis' do
+ inspect_source(cop,
+ ['def func (x)',
+ ' a',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for defs with space before the parenthesis' do
+ inspect_source(cop,
+ ['def self.func (x)',
+ ' a',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts a def without arguments' do
+ inspect_source(cop,
+ ['def func',
+ ' a',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a defs without arguments' do
+ inspect_source(cop,
+ ['def self.func',
+ ' a',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a def with arguments but no parentheses' do
+ inspect_source(cop,
+ ['def func x',
+ ' a',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a defs with arguments but no parentheses' do
+ inspect_source(cop,
+ ['def self.func x',
+ ' a',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects unwanted space' do
+ new_source = autocorrect_source(cop, ['def func (x)',
+ ' a',
+ 'end',
+ 'def self.func (x)',
+ ' a',
+ 'end'])
+ expect(new_source).to eq(['def func(x)',
+ ' a',
+ 'end',
+ 'def self.func(x)',
+ ' a',
+ 'end'].join("\n"))
+ end
+end
diff --git a/spec/rubocop/cop/style/space_after_not_spec.rb b/spec/rubocop/cop/style/space_after_not_spec.rb
new file mode 100644
index 0000000..55e3a1c
--- /dev/null
+++ b/spec/rubocop/cop/style/space_after_not_spec.rb
@@ -0,0 +1,22 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::SpaceAfterNot do
+ subject(:cop) { described_class.new }
+
+ it 'reports an offense for space after !' do
+ inspect_source(cop, ['! something'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts no space after !' do
+ inspect_source(cop, ['!something'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects by removing redundant space' do
+ new_source = autocorrect_source(cop, '! something')
+ expect(new_source).to eq('!something')
+ end
+end
diff --git a/spec/rubocop/cop/style/space_after_semicolon_spec.rb b/spec/rubocop/cop/style/space_after_semicolon_spec.rb
new file mode 100644
index 0000000..c589aa3
--- /dev/null
+++ b/spec/rubocop/cop/style/space_after_semicolon_spec.rb
@@ -0,0 +1,23 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::SpaceAfterSemicolon do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for semicolon without space after it' do
+ inspect_source(cop, ['x = 1;y = 2'])
+ expect(cop.messages).to eq(
+ ['Space missing after semicolon.'])
+ end
+
+ it 'does not crash if semicolon is the last character of the file' do
+ inspect_source(cop, ['x = 1;'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'auto-corrects missing space' do
+ new_source = autocorrect_source(cop, 'x = 1;y = 2')
+ expect(new_source).to eq('x = 1; y = 2')
+ end
+end
diff --git a/spec/rubocop/cop/style/space_around_equals_in_parameter_default_spec.rb b/spec/rubocop/cop/style/space_around_equals_in_parameter_default_spec.rb
new file mode 100644
index 0000000..3138d51
--- /dev/null
+++ b/spec/rubocop/cop/style/space_around_equals_in_parameter_default_spec.rb
@@ -0,0 +1,75 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::SpaceAroundEqualsInParameterDefault, :config do
+ subject(:cop) { described_class.new(config) }
+
+ context 'when EnforcedStyle is space' do
+ let(:cop_config) { { 'EnforcedStyle' => 'space' } }
+
+ it 'registers an offense for default value assignment without space' do
+ inspect_source(cop, ['def f(x, y=0, z= 1)', 'end'])
+ expect(cop.messages)
+ .to eq(['Surrounding space missing in default value assignment.'] * 2)
+ expect(cop.highlights).to eq(['=', '= '])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'registers an offense for assignment empty string without space' do
+ inspect_source(cop, ['def f(x, y="", z=1)', 'end'])
+ expect(cop.offenses.size).to eq(2)
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' => 'no_space')
+ end
+
+ it 'registers an offense for assignment of empty list without space' do
+ inspect_source(cop, ['def f(x, y=[])', 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts default value assignment with space' do
+ inspect_source(cop, ['def f(x, y = 0, z = {})', 'end'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'auto-corrects missing space' do
+ new_source = autocorrect_source(cop, ['def f(x, y=0, z=1)', 'end'])
+ expect(new_source).to eq(['def f(x, y = 0, z = 1)', 'end'].join("\n"))
+ end
+ end
+
+ context 'when EnforcedStyle is no_space' do
+ let(:cop_config) { { 'EnforcedStyle' => 'no_space' } }
+
+ it 'registers an offense for default value assignment with space' do
+ inspect_source(cop, ['def f(x, y = 0, z =1, w= 2)', 'end'])
+ expect(cop.messages)
+ .to eq(['Surrounding space detected in default value assignment.'] * 3)
+ expect(cop.highlights).to eq([' = ', ' =', '= '])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'registers an offense for assignment empty string with space' do
+ inspect_source(cop, ['def f(x, y = "", z = 1)', 'end'])
+ expect(cop.offenses.size).to eq(2)
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' => 'space')
+ end
+
+ it 'registers an offense for assignment of empty list with space' do
+ inspect_source(cop, ['def f(x, y = [])', 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts default value assignment without space' do
+ inspect_source(cop, ['def f(x, y=0, z={})', 'end'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'auto-corrects unwanted space' do
+ new_source = autocorrect_source(cop, ['def f(x, y = 0, z= 1, w= 2)',
+ 'end'])
+ expect(new_source).to eq(['def f(x, y=0, z=1, w=2)',
+ 'end'].join("\n"))
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/space_around_operators_spec.rb b/spec/rubocop/cop/style/space_around_operators_spec.rb
new file mode 100644
index 0000000..032686b
--- /dev/null
+++ b/spec/rubocop/cop/style/space_around_operators_spec.rb
@@ -0,0 +1,325 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::SpaceAroundOperators do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for assignment without space on both sides' do
+ inspect_source(cop, ['x=0', 'y= 0', 'z =0'])
+ expect(cop.messages).to eq(
+ ["Surrounding space missing for operator '='."] * 3)
+ end
+
+ it 'auto-corrects assignment without space on both sides' do
+ new_source = autocorrect_source(cop, ['x=0', 'y= 0', 'z =0'])
+ expect(new_source).to eq(['x = 0', 'y = 0', 'z = 0'].join("\n"))
+ end
+
+ it 'registers an offense for ternary operator without space' do
+ inspect_source(cop, ['x == 0?1:2'])
+ expect(cop.messages).to eq(
+ ["Surrounding space missing for operator '?'.",
+ "Surrounding space missing for operator ':'."])
+ end
+
+ it 'auto-corrects a ternary operator without space' do
+ new_source = autocorrect_source(cop, 'x == 0?1:2')
+ expect(new_source).to eq('x == 0 ? 1 : 2')
+ end
+
+ it 'registers an offense in presence of modifier if statement' do
+ check_modifier('if')
+ end
+
+ it 'registers an offense in presence of modifier unless statement' do
+ check_modifier('unless')
+ end
+
+ it 'registers an offense in presence of modifier while statement' do
+ check_modifier('unless')
+ end
+
+ it 'registers an offense in presence of modifier until statement' do
+ check_modifier('unless')
+ end
+
+ def check_modifier(keyword)
+ src = ["a=1 #{keyword} condition",
+ 'c=2']
+ inspect_source(cop, src)
+ expect(cop.offenses.map(&:line)).to eq([1, 2])
+ expect(cop.messages).to eq(
+ ["Surrounding space missing for operator '='."] * 2)
+
+ new_source = autocorrect_source(cop, src)
+ expect(new_source)
+ .to eq(src.map { |line| line.sub(/=/, ' = ') }.join("\n"))
+ end
+
+ it 'registers an offense for binary operators that could be unary' do
+ inspect_source(cop, ['a-3', 'x&0xff', 'z+0'])
+ expect(cop.messages).to eq(
+ ["Surrounding space missing for operator '-'.",
+ "Surrounding space missing for operator '&'.",
+ "Surrounding space missing for operator '+'."])
+ end
+
+ it 'auto-corrects missing space in binary operators that could be unary' do
+ new_source = autocorrect_source(cop, ['a-3', 'x&0xff', 'z+0'])
+ expect(new_source).to eq(['a - 3', 'x & 0xff', 'z + 0'].join("\n"))
+ end
+
+ it 'registers an offense for arguments to a method' do
+ inspect_source(cop, ['puts 1+2'])
+ expect(cop.messages).to eq(
+ ["Surrounding space missing for operator '+'."])
+ end
+
+ it 'auto-corrects missing space in arguments to a method' do
+ new_source = autocorrect_source(cop, 'puts 1+2')
+ expect(new_source).to eq('puts 1 + 2')
+ end
+
+ it 'accepts operator surrounded by tabs' do
+ inspect_source(cop, ["a\t+\tb"])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts operator symbols' do
+ inspect_source(cop, ['func(:-)'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts ranges' do
+ inspect_source(cop, ['a, b = (1..2), (1...3)'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts scope operator' do
+ source = ['@io.class == Zlib::GzipWriter']
+ inspect_source(cop, source)
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts ::Kernel::raise' do
+ source = ['::Kernel::raise IllegalBlockError.new']
+ inspect_source(cop, source)
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts exclamation point negation' do
+ inspect_source(cop, ['x = !a&&!b'])
+ expect(cop.messages).to eq(
+ ["Surrounding space missing for operator '&&'."])
+ end
+
+ it 'accepts exclamation point definition' do
+ inspect_source(cop, [' def !',
+ ' !__getobj__',
+ ' end'])
+ expect(cop.offenses).to be_empty
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts a unary' do
+ inspect_source(cop,
+ [' def bm(label_width = 0, *labels, &blk)',
+ ' benchmark(CAPTION, label_width, FORMAT,',
+ ' *labels, &blk)',
+ ' end',
+ '',
+ ' def each &block',
+ ' end',
+ '',
+ ' def self.search *args',
+ ' end',
+ '',
+ ' def each *args',
+ ' end',
+ ''])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts splat operator' do
+ inspect_source(cop, ['return *list if options'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts def of operator' do
+ inspect_source(cop, ['def +(other); end',
+ 'def self.===(other); end'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts an operator at the end of a line' do
+ inspect_source(cop,
+ ["['Favor unless over if for negative ' +",
+ " 'conditions.'] * 2"])
+ expect(cop.messages).to eq([])
+ end
+
+ it 'accepts an assignment with spaces' do
+ inspect_source(cop, ['x = 0'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an operator called with method syntax' do
+ inspect_source(cop, ['Date.today.+(1).to_s'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for operators without spaces' do
+ inspect_source(cop,
+ ['x+= a+b-c*d/e%f^g|h&i||j',
+ 'y -=k&&l'])
+ expect(cop.messages)
+ .to eq(["Surrounding space missing for operator '+='.",
+ "Surrounding space missing for operator '+'.",
+ "Surrounding space missing for operator '-'.",
+ "Surrounding space missing for operator '*'.",
+ "Surrounding space missing for operator '/'.",
+ "Surrounding space missing for operator '%'.",
+ "Surrounding space missing for operator '^'.",
+ "Surrounding space missing for operator '|'.",
+ "Surrounding space missing for operator '&'.",
+ "Surrounding space missing for operator '||'.",
+ "Surrounding space missing for operator '-='.",
+ "Surrounding space missing for operator '&&'."])
+ end
+
+ it 'auto-corrects missing space' do
+ new_source = autocorrect_source(cop, ['x+= a+b-c*d/e%f^g|h&i||j',
+ 'y -=k&&l'])
+ expect(new_source).to eq(['x += a + b - c * d / e % f ^ g | h & i || j',
+ 'y -= k && l'].join("\n"))
+ end
+
+ it 'accepts operators with spaces' do
+ inspect_source(cop,
+ ['x += a + b - c * d / e % f ^ g | h & i || j',
+ 'y -= k && l'])
+ expect(cop.messages).to eq([])
+ end
+
+ it "accepts some operators that are exceptions & don't need spaces" do
+ inspect_source(cop, ['(1..3)',
+ 'ActionController::Base',
+ 'each { |s, t| }'])
+ expect(cop.messages).to eq([])
+ end
+
+ it 'accepts an assignment followed by newline' do
+ inspect_source(cop, ['x =', '0'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offenses for exponent operator with spaces' do
+ inspect_source(cop, ['x = a * b ** 2'])
+ expect(cop.messages).to eq(
+ ['Space around operator ** detected.'])
+ end
+
+ it 'auto-corrects unwanted space around **' do
+ new_source = autocorrect_source(cop, ['x = a * b ** 2',
+ 'y = a * b** 2'])
+ expect(new_source).to eq(['x = a * b**2',
+ 'y = a * b**2'].join("\n"))
+ end
+
+ it 'accepts exponent operator without spaces' do
+ inspect_source(cop, ['x = a * b**2'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for a setter call without spaces' do
+ inspect_source(cop, ['x.y=2'])
+ expect(cop.messages).to eq(
+ ["Surrounding space missing for operator '='."])
+ end
+
+ it 'registers an offense for a hash rocket without spaces' do
+ inspect_source(cop, ['{ 1=>2, a: b }'])
+ expect(cop.messages).to eq(
+ ["Surrounding space missing for operator '=>'."])
+ end
+
+ it 'accepts unary operators without space' do
+ inspect_source(cop, ['[].map(&:size)',
+ '-3',
+ 'arr.collect { |e| -e }',
+ 'x = +2'])
+ expect(cop.messages).to eq([])
+ end
+
+ it 'accepts [] without space' do
+ inspect_source(cop, ['files[2]'])
+ expect(cop.messages).to eq([])
+ end
+
+ it 'accepts argument default values without space' do
+ # These are handled by SpaceAroundEqualsInParameterDefault,
+ # so SpaceAroundOperators leaves them alone.
+ inspect_source(cop,
+ ['def init(name=nil)',
+ 'end'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts the construct class <<self with no space after <<' do
+ inspect_source(cop, ['class <<self',
+ 'end'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'registers an offense for match operators without space' do
+ inspect_source(cop, ['x=~/abc/', 'y !~/abc/'])
+ expect(cop.messages)
+ .to eq(["Surrounding space missing for operator '=~'.",
+ "Surrounding space missing for operator '!~'."])
+ end
+
+ it 'registers an offense for various assignments without space' do
+ inspect_source(cop, ['x||=0', 'y&&=0', 'z*=2',
+ '@a=0', '@@a=0', 'a,b=0', 'A=0', 'x[3]=0', '$A=0'])
+ expect(cop.messages)
+ .to eq(["Surrounding space missing for operator '||='.",
+ "Surrounding space missing for operator '&&='.",
+ "Surrounding space missing for operator '*='.",
+ "Surrounding space missing for operator '='.",
+ "Surrounding space missing for operator '='.",
+ "Surrounding space missing for operator '='.",
+ "Surrounding space missing for operator '='.",
+ "Surrounding space missing for operator '='.",
+ "Surrounding space missing for operator '='."])
+ end
+
+ it 'registers an offense for equality operators without space' do
+ inspect_source(cop, ['x==0', 'y!=0', 'Hash===z'])
+ expect(cop.messages)
+ .to eq(["Surrounding space missing for operator '=='.",
+ "Surrounding space missing for operator '!='.",
+ "Surrounding space missing for operator '==='."])
+ end
+
+ it 'registers an offense for - without space with negative lhs operand' do
+ inspect_source(cop, ['-1-arg'])
+ expect(cop.messages)
+ .to eq(["Surrounding space missing for operator '-'."])
+ end
+
+ it 'registers an offense for inheritance < without space' do
+ inspect_source(cop, ['class ShowSourceTestClass<ShowSourceTestSuperClass',
+ 'end'])
+ expect(cop.messages)
+ .to eq(["Surrounding space missing for operator '<'."])
+ end
+
+ it 'registers an offense for hash rocket without space at rescue' do
+ inspect_source(cop, ['begin',
+ 'rescue Exception=>e',
+ 'end'])
+ expect(cop.messages)
+ .to eq(["Surrounding space missing for operator '=>'."])
+ end
+end
diff --git a/spec/rubocop/cop/style/space_before_block_braces_spec.rb b/spec/rubocop/cop/style/space_before_block_braces_spec.rb
new file mode 100644
index 0000000..13edac3
--- /dev/null
+++ b/spec/rubocop/cop/style/space_before_block_braces_spec.rb
@@ -0,0 +1,72 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::SpaceBeforeBlockBraces do
+ subject(:cop) { described_class.new(config) }
+ let(:config) do
+ merged = Rubocop::ConfigLoader
+ .default_configuration['SpaceBeforeBlockBraces'].merge(cop_config)
+ Rubocop::Config.new('Blocks' => { 'Enabled' => false },
+ 'SpaceBeforeBlockBraces' => merged)
+ end
+ let(:cop_config) { { 'EnforcedStyle' => 'space' } }
+
+ context 'when EnforcedStyle is space' do
+ it 'accepts braces surrounded by spaces' do
+ inspect_source(cop, ['each { puts }'])
+ expect(cop.messages).to be_empty
+ expect(cop.highlights).to be_empty
+ end
+
+ it 'registers an offense for left brace without outer space' do
+ inspect_source(cop, ['each{ puts }'])
+ expect(cop.messages).to eq(['Space missing to the left of {.'])
+ expect(cop.highlights).to eq(['{'])
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' => 'no_space')
+ end
+
+ it 'registers an offense for opposite + correct style' do
+ inspect_source(cop,
+ ['each{ puts }',
+ 'each { puts }'])
+ expect(cop.messages).to eq(['Space missing to the left of {.'])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'auto-corrects missing space' do
+ new_source = autocorrect_source(cop, 'each{}')
+ expect(new_source).to eq('each {}')
+ end
+ end
+
+ context 'when EnforcedStyle is no_space' do
+ let(:cop_config) { { 'EnforcedStyle' => 'no_space' } }
+
+ it 'registers an offense for braces surrounded by spaces' do
+ inspect_source(cop, ['each { puts }'])
+ expect(cop.messages).to eq(['Space detected to the left of {.'])
+ expect(cop.highlights).to eq([' '])
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' => 'space')
+ end
+
+ it 'registers an offense for correct + opposite style' do
+ inspect_source(cop,
+ ['each{ puts }',
+ 'each { puts }'])
+ expect(cop.messages).to eq(['Space detected to the left of {.'])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'auto-corrects unwanted space' do
+ new_source = autocorrect_source(cop, 'each {}')
+ expect(new_source).to eq('each{}')
+ end
+
+ it 'accepts left brace without outer space' do
+ inspect_source(cop, ['each{ puts }'])
+ expect(cop.messages).to be_empty
+ expect(cop.highlights).to be_empty
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/space_before_modifier_keyword_spec.rb b/spec/rubocop/cop/style/space_before_modifier_keyword_spec.rb
new file mode 100644
index 0000000..089a08b
--- /dev/null
+++ b/spec/rubocop/cop/style/space_before_modifier_keyword_spec.rb
@@ -0,0 +1,70 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::SpaceBeforeModifierKeyword do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for missing space before if/unless' do
+ inspect_source(cop, ['(a = 3)if a == 2',
+ 'a = "test"if a == 2',
+ 'a = 42unless a == 2',
+ 'a = [1,2,3]unless a == 2',
+ 'a = {:a => "b"}if a == 2'])
+ expect(cop.highlights).to eq([')', '"', '2', ']', '}'])
+ end
+
+ it 'registers an offense for missing space before while/until' do
+ inspect_source(cop, ['(a = 3)while b',
+ 'a = "test"until b',
+ 'a = 42while b',
+ 'a = [1,2,3]until b',
+ 'a = {:a => "b"}while b'])
+ expect(cop.highlights).to eq([')', '"', '2', ']', '}'])
+ end
+
+ it 'accepts modifiers with preceding space' do
+ inspect_source(cop, ['(a = 3) if b',
+ 'a = "test" unless b',
+ 'a = 42 while b',
+ 'a = [1,2,3] until b'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts elsif at beginning of line' do
+ inspect_source(cop, ["if RUBY_VERSION.between?('1.9.2', '2.0.0')",
+ " require 'testing/performance/ruby/yarv'",
+ 'elsif RUBY_VERSION.between?("1.8.6", "1.9")',
+ " require 'testing/performance/ruby/mri'",
+ 'end'])
+ expect(cop.highlights).to eq([])
+ end
+
+ it 'does not crash on ternary conditionals' do
+ inspect_source(cop, 'a ? b : c')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects missing space' do
+ new_source = autocorrect_source(cop, ['(a = 3)if a == 2',
+ 'a = "test"if a == 2',
+ 'a = 42unless a == 2',
+ 'a = [1,2,3]unless a == 2',
+ 'a = {:a => "b"}if a == 2',
+ '(a = 3)while b',
+ 'a = "test"until b',
+ 'a = 42while b',
+ 'a = [1,2,3]until b',
+ 'a = {:a => "b"}while b'])
+ expect(new_source).to eq(['(a = 3) if a == 2',
+ 'a = "test" if a == 2',
+ 'a = 42 unless a == 2',
+ 'a = [1,2,3] unless a == 2',
+ 'a = {:a => "b"} if a == 2',
+ '(a = 3) while b',
+ 'a = "test" until b',
+ 'a = 42 while b',
+ 'a = [1,2,3] until b',
+ 'a = {:a => "b"} while b'].join("\n"))
+ end
+end
diff --git a/spec/rubocop/cop/style/space_inside_block_braces_spec.rb b/spec/rubocop/cop/style/space_inside_block_braces_spec.rb
new file mode 100644
index 0000000..c810165
--- /dev/null
+++ b/spec/rubocop/cop/style/space_inside_block_braces_spec.rb
@@ -0,0 +1,287 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::SpaceInsideBlockBraces do
+ SUPPORTED_STYLES = %w(space no_space)
+
+ subject(:cop) { described_class.new(config) }
+ let(:config) do
+ merged = Rubocop::ConfigLoader
+ .default_configuration['SpaceInsideBlockBraces'].merge(cop_config)
+ Rubocop::Config.new('Blocks' => { 'Enabled' => false },
+ 'SpaceInsideBlockBraces' => merged)
+ end
+ let(:cop_config) do
+ {
+ 'EnforcedStyle' => 'space',
+ 'SupportedStyles' => SUPPORTED_STYLES,
+ 'SpaceBeforeBlockParameters' => true
+ }
+ end
+
+ context 'with space inside empty braces not allowed' do
+ let(:cop_config) { { 'EnforcedStyleForEmptyBraces' => 'no_space' } }
+
+ it 'accepts empty braces with no space inside' do
+ inspect_source(cop, ['each {}'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts empty braces with line break inside' do
+ inspect_source(cop, [' each {',
+ ' }'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts empty braces with comment and line break inside' do
+ inspect_source(cop, [' each { # Comment',
+ ' }'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'registers an offense for empty braces with space inside' do
+ inspect_source(cop, ['each { }'])
+ expect(cop.messages).to eq(['Space inside empty braces detected.'])
+ expect(cop.highlights).to eq([' '])
+ end
+
+ it 'auto-corrects unwanted space' do
+ new_source = autocorrect_source(cop, 'each { }')
+ expect(new_source).to eq('each {}')
+ end
+
+ it 'does not auto-correct when braces are not empty' do
+ old_source = <<-END
+ a {
+ b
+ }
+ END
+ new_source = autocorrect_source(cop, old_source)
+ expect(new_source).to eq(old_source)
+ end
+ end
+
+ context 'with space inside empty braces allowed' do
+ let(:cop_config) { { 'EnforcedStyleForEmptyBraces' => 'space' } }
+
+ it 'accepts empty braces with space inside' do
+ inspect_source(cop, ['each { }'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'registers an offense for empty braces with no space inside' do
+ inspect_source(cop, ['each {}'])
+ expect(cop.messages).to eq(['Space missing inside empty braces.'])
+ expect(cop.highlights).to eq(['{}'])
+ end
+
+ it 'auto-corrects missing space' do
+ new_source = autocorrect_source(cop, 'each {}')
+ expect(new_source).to eq('each { }')
+ end
+ end
+
+ it 'accepts braces surrounded by spaces' do
+ inspect_source(cop, ['each { puts }'])
+ expect(cop.messages).to be_empty
+ expect(cop.highlights).to be_empty
+ end
+
+ it 'accepts left brace without outer space' do
+ inspect_source(cop, ['each{ puts }'])
+ expect(cop.highlights).to be_empty
+ end
+
+ it 'registers an offense for left brace without inner space' do
+ inspect_source(cop, ['each {puts }'])
+ expect(cop.messages).to eq(['Space missing inside {.'])
+ expect(cop.highlights).to eq(['p'])
+ end
+
+ it 'registers an offense for right brace without inner space' do
+ inspect_source(cop, ['each { puts}'])
+ expect(cop.messages).to eq(['Space missing inside }.'])
+ expect(cop.highlights).to eq(['}'])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'registers offenses for both braces without inner space' do
+ inspect_source(cop, ['a {}',
+ 'b { }',
+ 'each {puts}'])
+ expect(cop.messages).to eq(['Space inside empty braces detected.',
+ 'Space missing inside {.',
+ 'Space missing inside }.'])
+ expect(cop.highlights).to eq([' ', 'p', '}'])
+
+ # Both correct and incorrect code has been found in relation to
+ # EnforcedStyleForEmptyBraces, but that doesn't matter. EnforcedStyle can
+ # be changed to get rid of the EnforcedStyle offenses.
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' =>
+ 'no_space')
+ end
+
+ it 'auto-corrects missing space' do
+ new_source = autocorrect_source(cop, 'each {puts}')
+ expect(new_source).to eq('each { puts }')
+ end
+
+ context 'with passed in parameters' do
+ it 'accepts left brace with inner space' do
+ inspect_source(cop, ['each { |x| puts }'])
+ expect(cop.messages).to be_empty
+ expect(cop.highlights).to be_empty
+ end
+
+ it 'registers an offense for left brace without inner space' do
+ inspect_source(cop, ['each {|x| puts }'])
+ expect(cop.messages).to eq(['Space between { and | missing.'])
+ expect(cop.highlights).to eq(['{|'])
+ end
+
+ it 'auto-corrects missing space' do
+ new_source = autocorrect_source(cop, 'each {|x| puts }')
+ expect(new_source).to eq('each { |x| puts }')
+ end
+
+ context 'and Blocks cop enabled' do
+ let(:config) do
+ Rubocop::Config.new('Blocks' => { 'Enabled' => true },
+ 'SpaceInsideBlockBraces' => cop_config)
+ end
+
+ it 'does auto-correction for single-line blocks' do
+ new_source = autocorrect_source(cop, 'each {|x| puts}')
+ expect(new_source).to eq('each { |x| puts }')
+ end
+
+ it 'does not do auto-correction for multi-line blocks' do
+ # {} will be changed to do..end by the Blocks cop, and then this cop is
+ # not relevant anymore.
+ old_source = ['each {|x|',
+ ' puts',
+ '}']
+ new_source = autocorrect_source(cop, old_source)
+ expect(new_source).to eq(old_source.join("\n"))
+ end
+ end
+
+ context 'and space before block parameters not allowed' do
+ let(:cop_config) do
+ {
+ 'EnforcedStyle' => 'space',
+ 'SupportedStyles' => SUPPORTED_STYLES,
+ 'SpaceBeforeBlockParameters' => false
+ }
+ end
+
+ it 'registers an offense for left brace with inner space' do
+ inspect_source(cop, ['each { |x| puts }'])
+ expect(cop.messages).to eq(['Space between { and | detected.'])
+ expect(cop.highlights).to eq([' '])
+ end
+
+ it 'auto-corrects unwanted space' do
+ new_source = autocorrect_source(cop, 'each { |x| puts }')
+ expect(new_source).to eq('each {|x| puts }')
+ end
+
+ it 'accepts left brace without inner space' do
+ inspect_source(cop, ['each {|x| puts }'])
+ expect(cop.messages).to be_empty
+ expect(cop.highlights).to be_empty
+ end
+ end
+ end
+
+ context 'configured with no_space' do
+ let(:cop_config) do
+ {
+ 'EnforcedStyle' => 'no_space',
+ 'SupportedStyles' => SUPPORTED_STYLES,
+ 'SpaceBeforeBlockParameters' => true
+ }
+ end
+
+ it 'accepts braces without spaces inside' do
+ inspect_source(cop, ['each {puts}'])
+ expect(cop.messages).to be_empty
+ expect(cop.highlights).to be_empty
+ end
+
+ it 'registers an offense for left brace with inner space' do
+ inspect_source(cop, ['each { puts}'])
+ expect(cop.messages).to eq(['Space inside { detected.'])
+ expect(cop.highlights).to eq([' '])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'registers an offense for right brace with inner space' do
+ inspect_source(cop, ['each {puts }'])
+ expect(cop.messages).to eq(['Space inside } detected.'])
+ expect(cop.highlights).to eq([' '])
+ end
+
+ it 'registers offenses for both braces with inner space' do
+ inspect_source(cop, ['each { puts }'])
+ expect(cop.messages).to eq(['Space inside { detected.',
+ 'Space inside } detected.'])
+ expect(cop.highlights).to eq([' ', ' '])
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' =>
+ 'space')
+ end
+
+ it 'accepts left brace without outer space' do
+ inspect_source(cop, ['each {puts}'])
+ expect(cop.highlights).to be_empty
+ end
+
+ it 'auto-corrects unwanted space' do
+ new_source = autocorrect_source(cop, 'each{ puts }')
+ expect(new_source).to eq('each{puts}')
+ end
+
+ context 'with passed in parameters' do
+ context 'and space before block parameters allowed' do
+ it 'accepts left brace with inner space' do
+ inspect_source(cop, ['each { |x| puts}'])
+ expect(cop.messages).to eq([])
+ expect(cop.highlights).to eq([])
+ end
+
+ it 'registers an offense for left brace without inner space' do
+ inspect_source(cop, ['each {|x| puts}'])
+ expect(cop.messages).to eq(['Space between { and | missing.'])
+ expect(cop.highlights).to eq(['{|'])
+ end
+
+ it 'auto-corrects missing space' do
+ new_source = autocorrect_source(cop, 'each {|x| puts}')
+ expect(new_source).to eq('each { |x| puts}')
+ end
+ end
+
+ context 'and space before block parameters not allowed' do
+ let(:cop_config) do
+ {
+ 'EnforcedStyle' => 'no_space',
+ 'SupportedStyles' => SUPPORTED_STYLES,
+ 'SpaceBeforeBlockParameters' => false
+ }
+ end
+
+ it 'registers an offense for left brace with inner space' do
+ inspect_source(cop, ['each { |x| puts}'])
+ expect(cop.messages).to eq(['Space between { and | detected.'])
+ expect(cop.highlights).to eq([' '])
+ end
+
+ it 'auto-corrects unwanted space' do
+ new_source = autocorrect_source(cop, 'each { |x| puts}')
+ expect(new_source).to eq('each {|x| puts}')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/space_inside_brackets_spec.rb b/spec/rubocop/cop/style/space_inside_brackets_spec.rb
new file mode 100644
index 0000000..1900a27
--- /dev/null
+++ b/spec/rubocop/cop/style/space_inside_brackets_spec.rb
@@ -0,0 +1,59 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::SpaceInsideBrackets do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for an array literal with spaces inside' do
+ inspect_source(cop, ['a = [1, 2 ]',
+ 'b = [ 1, 2]'])
+ expect(cop.messages).to eq(
+ ['Space inside square brackets detected.',
+ 'Space inside square brackets detected.'])
+ end
+
+ it 'accepts space inside strings within square brackets' do
+ inspect_source(cop, ["['Encoding:',",
+ " ' Enabled: false']"])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts space inside square brackets if on its own row' do
+ inspect_source(cop, ['a = [',
+ ' 1, 2',
+ ' ]'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts space inside square brackets if with comment' do
+ inspect_source(cop, ['a = [ # Comment',
+ ' 1, 2',
+ ' ]'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts square brackets as method name' do
+ inspect_source(cop, ['def Vector.[](*array)',
+ 'end'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts square brackets called with method call syntax' do
+ inspect_source(cop, ['subject.[](0)'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'only reports a single space once' do
+ inspect_source(cop, ['[ ]'])
+ expect(cop.messages).to eq(
+ ['Space inside square brackets detected.'])
+ end
+
+ it 'auto-corrects unwanted space' do
+ new_source = autocorrect_source(cop, ['a = [1, 2 ]',
+ 'b = [ 1, 2]'])
+ expect(new_source).to eq(['a = [1, 2]',
+ 'b = [1, 2]'].join("\n"))
+ end
+end
diff --git a/spec/rubocop/cop/style/space_inside_hash_literal_braces_spec.rb b/spec/rubocop/cop/style/space_inside_hash_literal_braces_spec.rb
new file mode 100644
index 0000000..8ba94a6
--- /dev/null
+++ b/spec/rubocop/cop/style/space_inside_hash_literal_braces_spec.rb
@@ -0,0 +1,147 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::SpaceInsideHashLiteralBraces, :config do
+ subject(:cop) { described_class.new(config) }
+ let(:cop_config) { { 'EnforcedStyle' => 'space' } }
+
+ context 'with space inside empty braces not allowed' do
+ let(:cop_config) { { 'EnforcedStyleForEmptyBraces' => 'no_space' } }
+
+ it 'accepts empty braces with no space inside' do
+ inspect_source(cop, ['h = {}'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'registers an offense for empty braces with space inside' do
+ inspect_source(cop, ['h = { }'])
+ expect(cop.messages)
+ .to eq(['Space inside empty hash literal braces detected.'])
+ expect(cop.highlights).to eq([' '])
+ end
+
+ it 'auto-corrects unwanted space' do
+ new_source = autocorrect_source(cop, 'h = { }')
+ expect(new_source).to eq('h = {}')
+ end
+ end
+
+ context 'with space inside empty braces allowed' do
+ let(:cop_config) { { 'EnforcedStyleForEmptyBraces' => 'space' } }
+
+ it 'accepts empty braces with space inside' do
+ inspect_source(cop, ['h = { }'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'registers an offense for empty braces with no space inside' do
+ inspect_source(cop, ['h = {}'])
+ expect(cop.messages)
+ .to eq(['Space inside empty hash literal braces missing.'])
+ expect(cop.highlights).to eq(['{'])
+ end
+
+ it 'auto-corrects missing space' do
+ new_source = autocorrect_source(cop, 'h = {}')
+ expect(new_source).to eq('h = { }')
+ end
+ end
+
+ it 'registers an offense for hashes with no spaces if so configured' do
+ inspect_source(cop,
+ ['h = {a: 1, b: 2}',
+ 'h = {a => 1}'])
+ expect(cop.messages).to eq(['Space inside { missing.',
+ 'Space inside } missing.',
+ 'Space inside { missing.',
+ 'Space inside } missing.'])
+ expect(cop.highlights).to eq(['{', '}', '{', '}'])
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' => 'no_space')
+ end
+
+ it 'registers an offense for correct + opposite' do
+ inspect_source(cop,
+ ['h = { a: 1}'])
+ expect(cop.messages).to eq(['Space inside } missing.'])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'auto-corrects missing space' do
+ new_source = autocorrect_source(cop, ['h = {a: 1, b: 2}',
+ 'h = {a => 1 }'])
+ expect(new_source).to eq(['h = { a: 1, b: 2 }',
+ 'h = { a => 1 }'].join("\n"))
+ end
+
+ context 'when EnforcedStyle is no_space' do
+ let(:cop_config) { { 'EnforcedStyle' => 'no_space' } }
+
+ it 'registers an offense for hashes with spaces' do
+ inspect_source(cop,
+ ['h = { a: 1, b: 2 }'])
+ expect(cop.messages).to eq(['Space inside { detected.',
+ 'Space inside } detected.'])
+ expect(cop.highlights).to eq([' ', ' '])
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' => 'space')
+ end
+
+ it 'registers an offense for opposite + correct' do
+ inspect_source(cop,
+ ['h = {a: 1 }'])
+ expect(cop.messages).to eq(['Space inside } detected.'])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'auto-corrects unwanted space' do
+ new_source = autocorrect_source(cop, ['h = { a: 1, b: 2 }',
+ 'h = {a => 1 }'])
+ expect(new_source).to eq(['h = {a: 1, b: 2}',
+ 'h = {a => 1}'].join("\n"))
+ end
+
+ it 'accepts hashes with no spaces' do
+ inspect_source(cop,
+ ['h = {a: 1, b: 2}',
+ 'h = {a => 1}'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts multiline hash' do
+ inspect_source(cop,
+ ['h = {',
+ ' a: 1,',
+ ' b: 2,',
+ '}'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts multiline hash with comment' do
+ inspect_source(cop,
+ ['h = { # Comment',
+ ' a: 1,',
+ ' b: 2,',
+ '}'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ it 'accepts hashes with spaces by default' do
+ inspect_source(cop,
+ ['h = { a: 1, b: 2 }',
+ 'h = { a => 1 }'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts hash literals with no braces' do
+ inspect_source(cop, ['x(a: b.c)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'can handle interpolation in a braceless hash literal' do
+ # A tricky special case where the closing brace of the
+ # interpolation risks getting confused for a hash literal brace.
+ inspect_source(cop, ['f(get: "#{x}")'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/space_inside_parens_spec.rb b/spec/rubocop/cop/style/space_inside_parens_spec.rb
new file mode 100644
index 0000000..18ff2a9
--- /dev/null
+++ b/spec/rubocop/cop/style/space_inside_parens_spec.rb
@@ -0,0 +1,46 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::SpaceInsideParens do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for spaces inside parens' do
+ inspect_source(cop, ['f( 3)',
+ 'g(3 )'])
+ expect(cop.messages).to eq(
+ ['Space inside parentheses detected.',
+ 'Space inside parentheses detected.'])
+ end
+
+ it 'accepts parentheses in block parameter list' do
+ inspect_source(cop,
+ ['list.inject(Tms.new) { |sum, (label, item)|',
+ '}'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts parentheses with no spaces' do
+ inspect_source(cop, ['split("\n")'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts parentheses with line break' do
+ inspect_source(cop, ['f(',
+ ' 1)'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'accepts parentheses with comment and line break' do
+ inspect_source(cop, ['f( # Comment',
+ ' 1)'])
+ expect(cop.messages).to be_empty
+ end
+
+ it 'auto-corrects unwanted space' do
+ new_source = autocorrect_source(cop, ['f( 3)',
+ 'g(3 )'])
+ expect(new_source).to eq(['f(3)',
+ 'g(3)'].join("\n"))
+ end
+end
diff --git a/spec/rubocop/cop/style/special_global_vars_spec.rb b/spec/rubocop/cop/style/special_global_vars_spec.rb
new file mode 100644
index 0000000..f08d2ba
--- /dev/null
+++ b/spec/rubocop/cop/style/special_global_vars_spec.rb
@@ -0,0 +1,57 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::SpecialGlobalVars do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for $:' do
+ inspect_source(cop, ['puts $:'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Prefer `$LOAD_PATH` over `$:`.'])
+ end
+
+ it 'registers an offense for $"' do
+ inspect_source(cop, ['puts $"'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Prefer `$LOADED_FEATURES` over `$"`.'])
+ end
+
+ it 'registers an offense for $0' do
+ inspect_source(cop, ['puts $0'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Prefer `$PROGRAM_NAME` over `$0`.'])
+ end
+
+ it 'registers an offense for $$' do
+ inspect_source(cop, ['puts $$'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages)
+ .to eq(['Prefer `$PROCESS_ID` or `$PID` from the English ' \
+ 'library over `$$`.'])
+ end
+
+ it 'is clear about variables from the English library vs those not' do
+ inspect_source(cop, ['puts $*'])
+ expect(cop.messages)
+ .to eq(['Prefer `$ARGV` from the English library, or `ARGV` over `$*`.'])
+ end
+
+ it 'does not register an offense for backrefs like $1' do
+ inspect_source(cop, ['puts $1'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects $: to $LOAD_PATH' do
+ new_source = autocorrect_source(cop, '$:')
+ expect(new_source).to eq('$LOAD_PATH')
+ end
+
+ it 'auto-corrects $/ to $INPUT_RECORD_SEPARATOR' do
+ new_source = autocorrect_source(cop, '$/')
+ expect(new_source).to eq('$INPUT_RECORD_SEPARATOR')
+ end
+end
diff --git a/spec/rubocop/cop/style/string_literals_spec.rb b/spec/rubocop/cop/style/string_literals_spec.rb
new file mode 100644
index 0000000..9e7f5a7
--- /dev/null
+++ b/spec/rubocop/cop/style/string_literals_spec.rb
@@ -0,0 +1,212 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::StringLiterals, :config do
+ subject(:cop) { described_class.new(config) }
+
+ context 'configured with single quotes preferred' do
+ let(:cop_config) { { 'EnforcedStyle' => 'single_quotes' } }
+
+ it 'registers offense for double quotes when single quotes ' \
+ 'suffice' do
+ inspect_source(cop, ['s = "abc"',
+ 'x = "a\\\\b"',
+ 'y ="\\\\b"',
+ 'z = "a\\\\"'])
+ expect(cop.highlights).to eq(['"abc"',
+ '"a\\\\b"',
+ '"\\\\b"',
+ '"a\\\\"'])
+ expect(cop.messages)
+ .to eq(["Prefer single-quoted strings when you don't need " \
+ 'string interpolation or special symbols.'] * 4)
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' =>
+ 'double_quotes')
+ end
+
+ it 'registers offense for correct + opposite' do
+ inspect_source(cop, ['s = "abc"',
+ "x = 'abc'"])
+ expect(cop.messages)
+ .to eq(["Prefer single-quoted strings when you don't need " \
+ 'string interpolation or special symbols.'])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'accepts single quotes' do
+ inspect_source(cop, ["a = 'x'"])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts %q and %Q quotes' do
+ inspect_source(cop, ['a = %q(x) + %Q[x]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts % quotes' do
+ inspect_source(cop, ['a = %(x)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts heredocs' do
+ inspect_source(cop,
+ ['execute <<-SQL',
+ ' SELECT name from users',
+ 'SQL'])
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts double quotes when they are needed' do
+ src = ['a = "\n"',
+ 'b = "#{encode_severity}:' \
+ '#{sprintf("%3d", line_number)}: #{m}"',
+ 'c = "\'"',
+ 'd = "#@test"',
+ 'e = "#$test"',
+ 'f = "\e"',
+ 'g = "#@@test"']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts double quotes at the start of regexp literals' do
+ inspect_source(cop, ['s = /"((?:[^\\"]|\\.)*)"/'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts double quotes with some other special symbols' do
+ # "Substitutions in double-quoted strings"
+ # http://www.ruby-doc.org/docs/ProgrammingRuby/html/language.html
+ src = ['g = "\xf9"',
+ 'copyright = "\u00A9"']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts " in a %w' do
+ inspect_source(cop, ['%w(")'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts \\\\\n in a string' do # this would be: "\\\n"
+ inspect_source(cop, ['"foo \\\\\n bar"'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'can handle double quotes within embedded expression' do
+ src = ['"#{"A"}"']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'can handle a built-in constant parsed as string' do
+ # Parser will produce str nodes for constants such as __FILE__.
+ src = ['if __FILE__ == $PROGRAM_NAME',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'can handle character literals' do
+ src = 'a = ?/'
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects " with \'' do
+ new_source = autocorrect_source(cop, 's = "abc"')
+ expect(new_source).to eq("s = 'abc'")
+ end
+ end
+
+ context 'configured with double quotes preferred' do
+ let(:cop_config) { { 'EnforcedStyle' => 'double_quotes' } }
+
+ it 'registers offense for single quotes when double quotes would ' \
+ 'be equivalent' do
+ inspect_source(cop, ["s = 'abc'"])
+ expect(cop.highlights).to eq(["'abc'"])
+ expect(cop.messages)
+ .to eq(['Prefer double-quoted strings unless you need ' \
+ 'single quotes to avoid extra backslashes for ' \
+ 'escaping.'])
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' =>
+ 'single_quotes')
+ end
+
+ it 'registers offense for opposite + correct' do
+ inspect_source(cop, ['s = "abc"',
+ "x = 'abc'"])
+ expect(cop.messages)
+ .to eq(['Prefer double-quoted strings unless you need ' \
+ 'single quotes to avoid extra backslashes for ' \
+ 'escaping.'])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'accepts double quotes' do
+ inspect_source(cop, ['a = "x"'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts %q and %Q quotes' do
+ inspect_source(cop, ['a = %q(x) + %Q[x]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts % quotes' do
+ inspect_source(cop, ['a = %(x)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts heredocs' do
+ inspect_source(cop,
+ ['execute <<-SQL',
+ ' SELECT name from users',
+ 'SQL'])
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts single quotes when they are needed' do
+ src = ["a = '\\n'",
+ "b = '\"'"]
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts single quotes at the start of regexp literals' do
+ inspect_source(cop, ["s = /'((?:[^\\']|\\.)*)'/"])
+ expect(cop.offenses).to be_empty
+ end
+
+ it "accepts ' in a %w" do
+ inspect_source(cop, ["%w(')"])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'can handle a built-in constant parsed as string' do
+ # Parser will produce str nodes for constants such as __FILE__.
+ src = ['if __FILE__ == $PROGRAM_NAME',
+ 'end']
+ inspect_source(cop, src)
+ expect(cop.offenses).to be_empty
+ end
+
+ it "auto-corrects ' with \"" do
+ new_source = autocorrect_source(cop, "s = 'abc'")
+ expect(new_source).to eq('s = "abc"')
+ end
+ end
+
+ context 'when configured with a bad value' do
+ let(:cop_config) { { 'EnforcedStyle' => 'other' } }
+
+ it 'fails' do
+ expect { inspect_source(cop, ['a = "b"']) }
+ .to raise_error(RuntimeError)
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/symbol_array_spec.rb b/spec/rubocop/cop/style/symbol_array_spec.rb
new file mode 100644
index 0000000..4adb8fa
--- /dev/null
+++ b/spec/rubocop/cop/style/symbol_array_spec.rb
@@ -0,0 +1,37 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::SymbolArray do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for arrays of symbols', ruby: 2.0 do
+ inspect_source(cop,
+ ['[:one, :two, :three]'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'does not reg an offense for array with non-syms', ruby: 2.0 do
+ inspect_source(cop,
+ ['[:one, :two, "three"]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not reg an offense for array starting with %i', ruby: 2.0 do
+ inspect_source(cop,
+ ['%i(one two three)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not reg an offense for array with one element', ruby: 2.0 do
+ inspect_source(cop,
+ ['[:three]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does nothing on Ruby 1.9', ruby: 1.9 do
+ inspect_source(cop,
+ ['[:one, :two, :three]'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/tab_spec.rb b/spec/rubocop/cop/style/tab_spec.rb
new file mode 100644
index 0000000..fa81d29
--- /dev/null
+++ b/spec/rubocop/cop/style/tab_spec.rb
@@ -0,0 +1,17 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::Tab do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for a line indented with tab' do
+ inspect_source(cop, ["\tx = 0"])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts a line with tab in a string' do
+ inspect_source(cop, ["(x = \"\t\")"])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/trailing_blank_lines_spec.rb b/spec/rubocop/cop/style/trailing_blank_lines_spec.rb
new file mode 100644
index 0000000..80a5121
--- /dev/null
+++ b/spec/rubocop/cop/style/trailing_blank_lines_spec.rb
@@ -0,0 +1,43 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::TrailingBlankLines do
+ subject(:cop) { described_class.new(config) }
+ let(:config) do
+ Rubocop::Config.new('TrailingWhitespace' => { 'Enabled' => true })
+ end
+
+ it 'accepts final newline' do
+ inspect_source(cop, ['x = 0', ''])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for multiple trailing blank lines' do
+ inspect_source(cop, ['x = 0', '', '', '', ''])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.messages).to eq(['3 trailing blank lines detected.'])
+ end
+
+ it 'auto-corrects unwanted blank lines' do
+ new_source = autocorrect_source(cop, ['x = 0', '', '', '', ''])
+ expect(new_source).to eq(['x = 0', ''].join("\n"))
+ end
+
+ it 'does not auto-correct if it interferes with TrailingWhitespace' do
+ original = ['x = 0', '', ' ', '', '']
+ new_source = autocorrect_source(cop, original)
+ expect(new_source).to eq(original.join("\n"))
+ end
+
+ context 'with TrailingWhitespace disabled' do
+ let(:config) do
+ Rubocop::Config.new('TrailingWhitespace' => { 'Enabled' => false })
+ end
+
+ it 'auto-corrects even if some lines have space' do
+ new_source = autocorrect_source(cop, ['x = 0', '', ' ', '', ''])
+ expect(new_source).to eq(['x = 0', ''].join("\n"))
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/trailing_comma_spec.rb b/spec/rubocop/cop/style/trailing_comma_spec.rb
new file mode 100644
index 0000000..2942f13
--- /dev/null
+++ b/spec/rubocop/cop/style/trailing_comma_spec.rb
@@ -0,0 +1,230 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::TrailingComma, :config do
+ subject(:cop) { described_class.new(config) }
+ let(:cop_config) { { 'EnforcedStyleForMultiline' => 'no_comma' } }
+
+ context 'with single line list of values' do
+ it 'registers an offense for trailing comma in an Array literal' do
+ inspect_source(cop, 'VALUES = [1001, 2020, 3333, ]')
+ expect(cop.messages)
+ .to eq(['Avoid comma after the last item of an array.'])
+ expect(cop.highlights).to eq([','])
+ end
+
+ it 'registers an offense for trailing comma in a Hash literal' do
+ inspect_source(cop, 'MAP = { a: 1001, b: 2020, c: 3333, }')
+ expect(cop.messages)
+ .to eq(['Avoid comma after the last item of a hash.'])
+ expect(cop.highlights).to eq([','])
+ end
+
+ it 'registers an offense for trailing comma in a method call' do
+ inspect_source(cop, 'some_method(a, b, c, )')
+ expect(cop.messages)
+ .to eq(['Avoid comma after the last parameter of a method call.'])
+ expect(cop.highlights).to eq([','])
+ end
+
+ it 'registers an offense for trailing comma in a method call with hash' \
+ ' parameters at the end' do
+ inspect_source(cop, 'some_method(a, b, c: 0, d: 1, )')
+ expect(cop.messages)
+ .to eq(['Avoid comma after the last parameter of a method call.'])
+ expect(cop.highlights).to eq([','])
+ end
+
+ it 'accepts Array literal without trailing comma' do
+ inspect_source(cop, 'VALUES = [1001, 2020, 3333]')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts empty Array literal' do
+ inspect_source(cop, 'VALUES = []')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts rescue clause' do
+ # The list of rescued classes is an array.
+ inspect_source(cop, ['begin',
+ ' do_something',
+ 'rescue RuntimeError',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts Hash literal without trailing comma' do
+ inspect_source(cop, 'MAP = { a: 1001, b: 2020, c: 3333 }')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts empty Hash literal' do
+ inspect_source(cop, 'MAP = {}')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts method call without trailing comma' do
+ inspect_source(cop, 'some_method(a, b, c)')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts method call without parameters' do
+ inspect_source(cop, 'some_method')
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'with multi-line list of values' do
+ context 'when EnforcedStyleForMultiline is no_comma' do
+ it 'registers an offense for trailing comma in an Array literal' do
+ inspect_source(cop, ['VALUES = [',
+ ' 1001,',
+ ' 2020,',
+ ' 3333,',
+ ' ]'])
+ expect(cop.highlights).to eq([','])
+ end
+
+ it 'registers an offense for trailing comma in a Hash literal' do
+ inspect_source(cop, ['MAP = { a: 1001,',
+ ' b: 2020,',
+ ' c: 3333,',
+ ' }'])
+ expect(cop.highlights).to eq([','])
+ end
+
+ it 'registers an offense for trailing comma in a method call with ' \
+ 'hash parameters at the end' do
+ inspect_source(cop, ['some_method(',
+ ' a,',
+ ' b,',
+ ' c: 0,',
+ ' d: 1,)'])
+ expect(cop.highlights).to eq([','])
+ end
+
+ it 'accepts an Array literal with no trailing comma' do
+ inspect_source(cop, ['VALUES = [ 1001,',
+ ' 2020,',
+ ' 3333 ]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a Hash literal with no trailing comma' do
+ inspect_source(cop, ['MAP = {',
+ ' a: 1001,',
+ ' b: 2020,',
+ ' c: 3333',
+ ' }'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a method call with ' \
+ 'hash parameters at the end and no trailing comma' do
+ inspect_source(cop, ['some_method(a,',
+ ' b,',
+ ' c: 0,',
+ ' d: 1',
+ ' )'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts comma inside a heredoc' \
+ ' parameters at the end' do
+ inspect_source(cop, ['route(help: {',
+ " 'auth' => <<-HELP.chomp",
+ ',',
+ 'HELP',
+ '})'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'when EnforcedStyleForMultiline is comma' do
+ let(:cop_config) { { 'EnforcedStyleForMultiline' => 'comma' } }
+
+ it 'registers an offense for no trailing comma in an Array literal' do
+ inspect_source(cop, ['VALUES = [',
+ ' 1001,',
+ ' 2020,',
+ ' 3333]'])
+ expect(cop.messages)
+ .to eq(['Put a comma after the last item of a multiline array.'])
+ expect(cop.highlights).to eq(['3333'])
+ end
+
+ it 'registers an offense for no trailing comma in a Hash literal' do
+ inspect_source(cop, ['MAP = { a: 1001,',
+ ' b: 2020,',
+ ' c: 3333 }'])
+ expect(cop.messages)
+ .to eq(['Put a comma after the last item of a multiline hash.'])
+ expect(cop.highlights).to eq(['c: 3333'])
+ end
+
+ it 'registers an offense for no trailing comma in a method call with' \
+ ' hash parameters at the end' do
+ inspect_source(cop, ['some_method(',
+ ' a,',
+ ' b,',
+ ' c: 0,',
+ ' d: 1',
+ ' )'])
+ expect(cop.messages)
+ .to eq(['Put a comma after the last parameter of a multiline ' \
+ 'method call.'])
+ expect(cop.highlights).to eq(['d: 1'])
+ end
+
+ it 'accepts trailing comma in an Array literal' do
+ inspect_source(cop, ['VALUES = [1001,',
+ ' 2020,',
+ ' 3333,',
+ ' ]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts trailing comma in a Hash literal' do
+ inspect_source(cop, ['MAP = {',
+ ' a: 1001,',
+ ' b: 2020,',
+ ' c: 3333,',
+ ' }'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts trailing comma in a method call with hash' \
+ ' parameters at the end' do
+ inspect_source(cop, ['some_method(',
+ ' a,',
+ ' b,',
+ ' c: 0,',
+ ' d: 1,',
+ ' )'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts a multiline word array' do
+ inspect_source(cop, ['ingredients = %w(',
+ ' sausage',
+ ' anchovies',
+ ' olives',
+ ')'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts missing comma after a heredoc' do
+ # A heredoc that's the last item in a literal or parameter list can not
+ # have a trailing comma. It's a syntax error.
+ inspect_source(cop, ['route(help: {',
+ " 'auth' => <<-HELP.chomp",
+ '...',
+ 'HELP',
+ '},)']) # We still need a comma after the hash.
+ expect(cop.offenses).to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/trailing_whitespace_spec.rb b/spec/rubocop/cop/style/trailing_whitespace_spec.rb
new file mode 100644
index 0000000..0607082
--- /dev/null
+++ b/spec/rubocop/cop/style/trailing_whitespace_spec.rb
@@ -0,0 +1,30 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::TrailingWhitespace do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for a line ending with space' do
+ source = ['x = 0 ']
+ inspect_source(cop, source)
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for a line ending with tab' do
+ inspect_source(cop, ["x = 0\t"])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts a line without trailing whitespace' do
+ inspect_source(cop, ["x = 0\n"])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects unwanted space' do
+ new_source = autocorrect_source(cop, ['x = 0 ',
+ "x = 0\t"])
+ expect(new_source).to eq(['x = 0',
+ 'x = 0'].join("\n"))
+ end
+end
diff --git a/spec/rubocop/cop/style/trivial_accessors_spec.rb b/spec/rubocop/cop/style/trivial_accessors_spec.rb
new file mode 100644
index 0000000..c10e793
--- /dev/null
+++ b/spec/rubocop/cop/style/trivial_accessors_spec.rb
@@ -0,0 +1,418 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::TrivialAccessors, :config do
+ subject(:cop) { described_class.new(config) }
+ let(:cop_config) { {} }
+
+ it 'finds trivial reader' do
+ inspect_source(cop,
+ ['def foo',
+ ' @foo',
+ 'end',
+ '',
+ 'def Foo',
+ ' @Foo',
+ 'end'])
+ expect(cop.offenses.size).to eq(2)
+ expect(cop.offenses
+ .map(&:line).sort).to eq([1, 5])
+ expect(cop.messages)
+ .to eq(['Use `attr_reader` to define trivial reader methods.'] * 2)
+ end
+
+ it 'finds trivial reader in a class' do
+ inspect_source(cop,
+ ['class TrivialFoo',
+ ' def foo',
+ ' @foo',
+ ' end',
+ ' def bar',
+ ' !foo',
+ ' end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses
+ .map(&:line).sort).to eq([2])
+ end
+
+ it 'finds trivial reader in a class method' do
+ inspect_source(cop,
+ ['class TrivialFoo',
+ ' def self.foo',
+ ' @foo',
+ ' end',
+ ' def bar',
+ ' !foo',
+ ' end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses
+ .map(&:line).sort).to eq([2])
+ end
+
+ it 'finds trivial reader in a nested class' do
+ inspect_source(cop,
+ ['class TrivialFoo',
+ ' class Nested',
+ ' def foo',
+ ' @foo',
+ ' end',
+ ' end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses
+ .map(&:line).sort).to eq([3])
+ end
+
+ it 'finds trivial readers in a little less trivial class' do
+ inspect_source(cop,
+ ['class TrivialFoo',
+ ' def foo',
+ ' @foo',
+ ' end',
+ ' def foo_and_bar',
+ ' @foo_bar = @foo + @bar',
+ ' end',
+ ' def foo_bar',
+ ' @foo_bar',
+ ' end',
+ ' def foo?',
+ ' foo.present?',
+ ' end',
+ ' def bar?',
+ ' !bar',
+ ' end',
+ ' def foobar',
+ ' foo? ? foo.value : "bar"',
+ ' end',
+ ' def bar',
+ ' foo.bar',
+ ' end',
+ ' def foo_required?',
+ ' super && !bar_required?',
+ ' end',
+ ' def self.from_omniauth(auth)',
+ ' foobars.each do |f|',
+ ' # do stuff',
+ ' end',
+ ' end',
+ ' def regex',
+ ' %r{\A#{visit node}\Z}',
+ ' end',
+ ' def array',
+ ' [foo, bar].join',
+ ' end',
+ ' def string',
+ ' "string"',
+ ' end',
+ ' def class',
+ ' Foo.class',
+ ' end',
+ ' def with_return',
+ ' return foo',
+ ' end',
+ ' def captures',
+ ' (length - 1).times.map { |i| self[i + 1] }',
+ ' end',
+ ' def foo val',
+ ' super',
+ ' @val',
+ ' end',
+ 'end'])
+ expect(cop.offenses.size).to eq(2)
+ expect(cop.offenses
+ .map(&:line).sort).to eq([2, 8])
+ end
+
+ it 'finds trivial reader with braces' do
+ inspect_source(cop,
+ ['class Test',
+ ' # trivial reader with braces',
+ ' def name()',
+ ' @name',
+ ' end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses
+ .map(&:line).sort).to eq([3])
+ end
+
+ it 'finds trivial writer without braces' do
+ inspect_source(cop,
+ ['class Test',
+ ' # trivial writer without braces',
+ ' def name= name',
+ ' @name = name',
+ ' end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses .map(&:line).sort).to eq([3])
+ expect(cop.messages)
+ .to eq(['Use `attr_writer` to define trivial writer methods.'])
+ end
+
+ it 'does not find trivial writer with function calls' do
+ inspect_source(cop,
+ ['class TrivialTest',
+ ' def test=(val)',
+ ' @test = val',
+ ' some_function_call',
+ ' or_more_of_them',
+ ' end',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'finds trivials with less peculiar methods' do
+ inspect_source(cop,
+ ['class NilStats',
+ 'def most_traded_pair',
+ 'end',
+ 'def win_ratio',
+ 'end',
+ 'def win_ratio_percentage()',
+ 'end',
+ 'def pips_won',
+ ' 0.0',
+ 'end',
+ 'def gain_at(date)',
+ ' 1',
+ 'end',
+ 'def gain_percentage',
+ ' 0',
+ 'end',
+ 'def gain_breakdown(options = {})',
+ ' []',
+ 'end',
+ 'def copy_to_all_ratio',
+ ' nil',
+ 'end',
+ 'def trade_population',
+ ' {}',
+ 'end',
+ 'def average_leverage',
+ ' 1',
+ 'end',
+ 'def with_yield',
+ ' yield',
+ 'rescue Error => e',
+ ' #do stuff',
+ 'end',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'treats splats as non-trivial' do
+ inspect_source(cop,
+ [' def splatomatic(*values)',
+ ' @splatomatic = values',
+ ' end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'finds oneliner trivials' do
+ inspect_source(cop,
+ ['class Oneliner',
+ ' def foo; @foo; end',
+ ' def foo= foo; @foo = foo; end',
+ 'end'])
+ expect(cop.offenses.size).to eq(2)
+ expect(cop.offenses
+ .map(&:line).sort).to eq([2, 3])
+ end
+
+ it 'does not find a trivial reader' do
+ inspect_source(cop,
+ ['def bar',
+ ' @bar + foo',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'finds trivial writer' do
+ inspect_source(cop,
+ ['def foo=(val)',
+ ' @foo = val',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses
+ .map(&:line).sort).to eq([1])
+ end
+
+ it 'finds trivial writer in a class' do
+ inspect_source(cop,
+ ['class TrivialFoo',
+ ' def foo=(val)',
+ ' @foo = val',
+ ' end',
+ ' def void(no_value)',
+ ' end',
+ ' def inspect(sexp)',
+ ' each(:def, sexp) do |item|',
+ ' #do stuff',
+ ' end',
+ ' end',
+ ' def if_method(foo)',
+ ' if true',
+ ' unless false',
+ ' #do stuff',
+ ' end',
+ ' end',
+ ' end',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses
+ .map(&:line).sort).to eq([2])
+ end
+
+ it 'finds trivial accessors in a little less trivial class' do
+ inspect_source(cop,
+ ['class TrivialFoo',
+ ' def foo',
+ ' @foo',
+ ' end',
+ ' def foo_and_bar',
+ ' @foo_bar = @foo + @bar',
+ ' end',
+ ' def foo_bar',
+ ' @foo_bar',
+ ' end',
+ ' def bar=(bar_value)',
+ ' @bar = bar_value',
+ ' end',
+ 'end'])
+ expect(cop.offenses.size).to eq(3)
+ expect(cop.offenses
+ .map(&:line).sort).to eq([2, 8, 11])
+ end
+
+ it 'does not find a trivial writer' do
+ inspect_source(cop,
+ ['def bar=(value)',
+ ' @bar = value + 42',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'finds trivial writers in a little less trivial class' do
+ inspect_source(cop,
+ ['class TrivialFoo',
+ ' def foo_bar=(foo, bar)',
+ ' @foo_bar = foo + bar',
+ ' end',
+ ' def universal=(answer=42)',
+ ' @universal = answer',
+ ' end',
+ ' def bar=(bar_value)',
+ ' @bar = bar_value',
+ ' end',
+ 'end'])
+ expect(cop.offenses.size).to eq(2)
+ expect(cop.offenses
+ .map(&:line).sort).to eq([5, 8])
+ end
+
+ it 'does not find trivial accessors with method calls' do
+ inspect_source(cop,
+ ['class TrivialFoo',
+ ' def foo_bar(foo)',
+ ' foo_bar = foo + 42',
+ ' end',
+ ' def foo(value)',
+ ' foo = []',
+ ' # do stuff',
+ ' foo',
+ ' end',
+ ' def bar',
+ ' foo_method',
+ ' end',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not find trivial writer with exceptions' do
+ inspect_source(cop,
+ [' def expiration_formatted=(value)',
+ ' begin',
+ ' @expiration = foo_stuff',
+ ' rescue ArgumentError',
+ ' @expiration = nil',
+ ' end',
+ ' self[:expiration] = @expiration',
+ ' end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts an initialize method looking like a writer' do
+ inspect_source(cop,
+ [' def initialize(value)',
+ ' @top = value',
+ ' end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ context 'exact name match required' do
+ let(:cop_config) { { 'ExactNameMatch' => true } }
+
+ it 'finds only 1 trivial reader' do
+ inspect_source(cop,
+ ['def foo',
+ ' @foo',
+ 'end',
+ '',
+ 'def bar',
+ ' @barr',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses
+ .map(&:line).sort).to eq([1])
+ end
+
+ it 'finds only 1 trivial writer' do
+ inspect_source(cop,
+ ['def foo=(foo)',
+ ' @foo = foo',
+ 'end',
+ '',
+ 'def bar=(bar)',
+ ' @barr = bar',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses
+ .map(&:line).sort).to eq([1])
+ end
+ end
+
+ context 'with predicates allowed' do
+ let(:cop_config) { { 'AllowPredicates' => true } }
+
+ it 'ignores accessors ending with a question mark' do
+ inspect_source(cop,
+ [' def foo?',
+ ' @foo',
+ ' end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'with whitelist defined' do
+ let(:cop_config) { { 'Whitelist' => ['to_foo', 'bar='] } }
+
+ it 'ignores accessors in the whitelist' do
+ inspect_source(cop,
+ [' def to_foo',
+ ' @foo',
+ ' end'])
+ expect(cop.offenses).to be_empty
+ end
+ it 'ignores writers in the whitelist' do
+ inspect_source(cop,
+ [' def bar=(bar)',
+ ' @bar = bar',
+ ' end'])
+ expect(cop.offenses).to be_empty
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/unless_else_spec.rb b/spec/rubocop/cop/style/unless_else_spec.rb
new file mode 100644
index 0000000..d56b52e
--- /dev/null
+++ b/spec/rubocop/cop/style/unless_else_spec.rb
@@ -0,0 +1,23 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::UnlessElse do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for an unless with else' do
+ inspect_source(cop, ['unless x',
+ ' a = 1',
+ 'else',
+ ' a = 0',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts an unless without else' do
+ inspect_source(cop, ['unless x',
+ ' a = 1',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/rubocop/cop/style/variable_interpolation_spec.rb b/spec/rubocop/cop/style/variable_interpolation_spec.rb
new file mode 100644
index 0000000..0cc1ab9
--- /dev/null
+++ b/spec/rubocop/cop/style/variable_interpolation_spec.rb
@@ -0,0 +1,56 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::VariableInterpolation do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for interpolated global variables' do
+ inspect_source(cop,
+ ['puts "this is a #$test"'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['$test'])
+ expect(cop.messages)
+ .to eq(['Replace interpolated variable `$test`' \
+ ' with expression `#{$test}`.'])
+ end
+
+ it 'registers an offense for interpolated regexp back references' do
+ inspect_source(cop,
+ ['puts "this is a #$1"'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['$1'])
+ expect(cop.messages)
+ .to eq(['Replace interpolated variable `$1` with expression `#{$1}`.'])
+ end
+
+ it 'registers an offense for interpolated instance variables' do
+ inspect_source(cop,
+ ['puts "this is a #@test"'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['@test'])
+ expect(cop.messages)
+ .to eq(['Replace interpolated variable `@test`' \
+ ' with expression `#{@test}`.'])
+ end
+
+ it 'registers an offense for interpolated class variables' do
+ inspect_source(cop,
+ ['puts "this is a #@@t"'])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['@@t'])
+ expect(cop.messages)
+ .to eq(['Replace interpolated variable `@@t` with expression `#{@@t}`.'])
+ end
+
+ it 'does not register an offense for variables in expressions' do
+ inspect_source(cop,
+ ['puts "this is a #{@test} #{@@t} #{$t} #{$1}"'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'autocorrects by adding the missing {}' do
+ corrected = autocorrect_source(cop, ['"some #@var"'])
+ expect(corrected).to eq '"some #{@var}"'
+ end
+end
diff --git a/spec/rubocop/cop/style/variable_name_spec.rb b/spec/rubocop/cop/style/variable_name_spec.rb
new file mode 100644
index 0000000..c2e5751
--- /dev/null
+++ b/spec/rubocop/cop/style/variable_name_spec.rb
@@ -0,0 +1,107 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::VariableName, :config do
+ subject(:cop) { described_class.new(config) }
+
+ shared_examples 'always accepted' do
+ it 'accepts screaming snake case globals' do
+ inspect_source(cop, '$MY_GLOBAL = 0')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts screaming snake case constants' do
+ inspect_source(cop, 'MY_CONSTANT = 0')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts assigning to camel case constant' do
+ inspect_source(cop, 'Paren = Struct.new :left, :right, :kind')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts assignment with indexing of self' do
+ inspect_source(cop, 'self[:a] = b')
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'when configured for snake_case' do
+ let(:cop_config) { { 'EnforcedStyle' => 'snake_case' } }
+
+ it 'registers an offense for camel case in local variable name' do
+ inspect_source(cop, 'myLocal = 1')
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['myLocal'])
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' =>
+ 'camelCase')
+ end
+
+ it 'registers an offense for correct + opposite' do
+ inspect_source(cop, ['my_local = 1',
+ 'myLocal = 1'])
+ expect(cop.highlights).to eq(['myLocal'])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'registers an offense for camel case in instance variable name' do
+ inspect_source(cop, '@myAttribute = 3')
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['@myAttribute'])
+ end
+
+ it 'registers an offense for camel case in setter name' do
+ inspect_source(cop, 'self.mySetter = 2')
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['mySetter'])
+ end
+
+ include_examples 'always accepted'
+ end
+
+ context 'when configured for camelCase' do
+ let(:cop_config) { { 'EnforcedStyle' => 'camelCase' } }
+
+ it 'registers an offense for snake case in local variable name' do
+ inspect_source(cop, 'my_local = 1')
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.highlights).to eq(['my_local'])
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' =>
+ 'snake_case')
+ end
+
+ it 'registers an offense for opposite + correct' do
+ inspect_source(cop, ['my_local = 1',
+ 'myLocal = 1'])
+ expect(cop.highlights).to eq(['my_local'])
+ expect(cop.config_to_allow_offenses).to eq('Enabled' => false)
+ end
+
+ it 'accepts camel case in local variable name' do
+ inspect_source(cop, 'myLocal = 1')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts camel case in instance variable name' do
+ inspect_source(cop, '@myAttribute = 3')
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts camel case in setter name' do
+ inspect_source(cop, 'self.mySetter = 2')
+ expect(cop.offenses).to be_empty
+ end
+
+ include_examples 'always accepted'
+ end
+
+ context 'when configured with a bad value' do
+ let(:cop_config) { { 'EnforcedStyle' => 'other' } }
+
+ it 'fails' do
+ expect { inspect_source(cop, 'a = 3') }
+ .to raise_error(RuntimeError)
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/when_then_spec.rb b/spec/rubocop/cop/style/when_then_spec.rb
new file mode 100644
index 0000000..1877a5e
--- /dev/null
+++ b/spec/rubocop/cop/style/when_then_spec.rb
@@ -0,0 +1,40 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::WhenThen do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for when x;' do
+ inspect_source(cop, ['case a',
+ 'when b; c',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts when x then' do
+ inspect_source(cop, ['case a',
+ 'when b then c',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts ; separating statements in the body of when' do
+ inspect_source(cop, ['case a',
+ 'when b then c; d',
+ 'end',
+ '',
+ 'case e',
+ 'when f',
+ ' g; h',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects "when x;" with "when x then"' do
+ new_source = autocorrect_source(cop, ['case a',
+ 'when b; c',
+ 'end'])
+ expect(new_source).to eq("case a\nwhen b then c\nend")
+ end
+end
diff --git a/spec/rubocop/cop/style/while_until_do_spec.rb b/spec/rubocop/cop/style/while_until_do_spec.rb
new file mode 100644
index 0000000..ccbe739
--- /dev/null
+++ b/spec/rubocop/cop/style/while_until_do_spec.rb
@@ -0,0 +1,53 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::WhileUntilDo do
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense for do in multiline while' do
+ inspect_source(cop, ['while cond do',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for do in multiline until' do
+ inspect_source(cop, ['until cond do',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'accepts do in single-line while' do
+ inspect_source(cop, ['while cond do something end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts do in single-line until' do
+ inspect_source(cop, ['until cond do something end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'it accepts multi-line while without do' do
+ inspect_source(cop, ['while cond',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'it accepts multi-line until without do' do
+ inspect_source(cop, ['until cond',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'auto-corrects the usage of "do" in multiline while' do
+ new_source = autocorrect_source(cop, ['while cond do',
+ 'end'])
+ expect(new_source).to eq("while cond\nend")
+ end
+
+ it 'auto-corrects the usage of "do" in multiline until' do
+ new_source = autocorrect_source(cop, ['until cond do',
+ 'end'])
+ expect(new_source).to eq("until cond\nend")
+ end
+end
diff --git a/spec/rubocop/cop/style/while_until_modifier_spec.rb b/spec/rubocop/cop/style/while_until_modifier_spec.rb
new file mode 100644
index 0000000..ebaf398
--- /dev/null
+++ b/spec/rubocop/cop/style/while_until_modifier_spec.rb
@@ -0,0 +1,93 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::WhileUntilModifier do
+ include StatementModifierHelper
+
+ subject(:cop) { described_class.new(config) }
+ let(:config) do
+ hash = { 'LineLength' => { 'Max' => 79 } }
+ Rubocop::Config.new(hash)
+ end
+
+ it "accepts multiline unless that doesn't fit on one line" do
+ check_too_long(cop, 'unless')
+ end
+
+ it 'accepts multiline unless whose body is more than one line' do
+ check_short_multiline(cop, 'unless')
+ end
+
+ it 'registers an offense for multiline while that fits on one line' do
+ check_really_short(cop, 'while')
+ end
+
+ it "accepts multiline while that doesn't fit on one line" do
+ check_too_long(cop, 'while')
+ end
+
+ it 'accepts multiline while whose body is more than one line' do
+ check_short_multiline(cop, 'while')
+ end
+
+ it 'accepts oneline while when condition has local variable assignment' do
+ inspect_source(cop, ['lines = %w{first second third}',
+ 'while (line = lines.shift)',
+ ' puts line',
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for oneline while when assignment is in body' do
+ inspect_source(cop, ['while true',
+ ' x = 0',
+ 'end'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for multiline until that fits on one line' do
+ check_really_short(cop, 'until')
+ end
+
+ it "accepts multiline until that doesn't fit on one line" do
+ check_too_long(cop, 'until')
+ end
+
+ it 'accepts multiline until whose body is more than one line' do
+ check_short_multiline(cop, 'until')
+ end
+
+ it 'accepts an empty condition' do
+ check_empty(cop, 'while')
+ check_empty(cop, 'until')
+ end
+
+ it 'accepts modifier while' do
+ inspect_source(cop, ['ala while bala'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'accepts modifier until' do
+ inspect_source(cop, ['ala until bala'])
+ expect(cop.offenses).to be_empty
+ end
+
+ context 'when the maximum line length is specified by the cop itself' do
+ let(:config) do
+ hash = {
+ 'LineLength' => { 'Max' => 100 },
+ 'WhileUntilModifier' => { 'MaxLineLength' => 79 }
+ }
+ Rubocop::Config.new(hash)
+ end
+
+ it "accepts multiline while that doesn't fit on one line" do
+ check_too_long(cop, 'while')
+ end
+
+ it "accepts multiline until that doesn't fit on one line" do
+ check_too_long(cop, 'until')
+ end
+ end
+end
diff --git a/spec/rubocop/cop/style/word_array_spec.rb b/spec/rubocop/cop/style/word_array_spec.rb
new file mode 100644
index 0000000..e18b112
--- /dev/null
+++ b/spec/rubocop/cop/style/word_array_spec.rb
@@ -0,0 +1,97 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Style::WordArray, :config do
+ subject(:cop) { described_class.new(config) }
+ let(:cop_config) { { 'MinSize' => 0 } }
+
+ it 'registers an offense for arrays of single quoted strings' do
+ inspect_source(cop,
+ ["['one', 'two', 'three']"])
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.config_to_allow_offenses).to eq('MinSize' => 3)
+ end
+
+ it 'registers an offense for arrays of double quoted strings' do
+ inspect_source(cop,
+ ['["one", "two", "three"]'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'registers an offense for arrays with character constants' do
+ inspect_source(cop,
+ ['["one", ?\n]'])
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'does not register an offense for array of non-words' do
+ inspect_source(cop,
+ ['["one space", "two", "three"]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for array containing non-string' do
+ inspect_source(cop,
+ ['["one", "two", 3]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for array starting with %w' do
+ inspect_source(cop,
+ ['%w(one two three)'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for array with one element' do
+ inspect_source(cop,
+ ['["three"]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for array with empty strings' do
+ inspect_source(cop,
+ ['["", "two", "three"]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for array with allowed number of strings' do
+ cop_config['MinSize'] = 3
+
+ inspect_source(cop,
+ ['["one", "two", "three"]'])
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'does not register an offense for an array with comments in it' do
+ inspect_source(cop,
+ ['[',
+ '"foo", # comment here',
+ '"bar", # this thing was done because of a bug',
+ '"baz" # do not delete this line',
+ ']'])
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers an offense for an array with comments outside of it' do
+ inspect_source(cop,
+ ['[',
+ '"foo",',
+ '"bar",',
+ '"baz"',
+ '] # test'])
+
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'auto-corrects an array of words' do
+ new_source = autocorrect_source(cop, "['one', %q(two), 'three']")
+ expect(new_source).to eq('%w(one two three)')
+ end
+
+ it 'auto-corrects an array of words and character constants' do
+ new_source = autocorrect_source(cop, '[%{one}, %Q(two), ?\n, ?\t]')
+ expect(new_source).to eq('%W(one two \n \t)')
+ end
+end
diff --git a/spec/rubocop/cop/team_spec.rb b/spec/rubocop/cop/team_spec.rb
new file mode 100644
index 0000000..fe528a7
--- /dev/null
+++ b/spec/rubocop/cop/team_spec.rb
@@ -0,0 +1,142 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Team do
+ subject(:team) { described_class.new(cop_classes, config, options) }
+ let(:cop_classes) { Rubocop::Cop::Cop.non_rails }
+ let(:config) { Rubocop::ConfigLoader.default_configuration }
+ let(:options) { nil }
+
+ describe '#autocorrect?' do
+ subject { team.autocorrect? }
+
+ context 'when the option argument of .new is omitted' do
+ subject { described_class.new(cop_classes, config).autocorrect? }
+ it { should be_false }
+ end
+
+ context 'when { auto_correct: true } is passed to .new' do
+ let(:options) { { auto_correct: true } }
+ it { should be_true }
+ end
+ end
+
+ describe '#debug?' do
+ subject { team.debug? }
+
+ context 'when the option argument of .new is omitted' do
+ subject { described_class.new(cop_classes, config).debug? }
+ it { should be_false }
+ end
+
+ context 'when { debug: true } is passed to .new' do
+ let(:options) { { debug: true } }
+ it { should be_true }
+ end
+ end
+
+ describe '#inspect_file', :isolated_environment do
+ include FileHelper
+
+ let(:file_path) { '/tmp/example.rb' }
+ let(:offenses) do
+ team.inspect_file(Rubocop::SourceParser.parse_file(file_path))
+ end
+
+ before do
+ create_file(file_path, [
+ '#' * 90,
+ 'puts test;'
+ ])
+ end
+
+ it 'returns offenses' do
+ expect(offenses).not_to be_empty
+ expect(offenses.all? { |o| o.is_a?(Rubocop::Cop::Offense) }).to be_true
+ end
+
+ context 'when Parser reports non-fatal warning for the file' do
+ before do
+ create_file(file_path, [
+ '# encoding: utf-8',
+ '#' * 90,
+ 'puts *test'
+ ])
+ end
+
+ let(:cop_names) { offenses.map(&:cop_name) }
+
+ it 'returns Parser warning offenses' do
+ expect(cop_names).to include('AmbiguousOperator')
+ end
+
+ it 'returns offenses from cops' do
+ expect(cop_names).to include('LineLength')
+ end
+ end
+
+ context 'when autocorrection is enabled' do
+ let(:options) { { auto_correct: true } }
+
+ before do
+ create_file(file_path, [
+ '# encoding: utf-8',
+ 'puts "string"'
+ ])
+ end
+
+ it 'does autocorrection' do
+ team.inspect_file(Rubocop::SourceParser.parse_file(file_path))
+ corrected_source = File.read(file_path)
+ expect(corrected_source).to eq([
+ '# encoding: utf-8',
+ "puts 'string'",
+ ''
+ ].join("\n"))
+ end
+
+ it 'still returns offenses' do
+ expect(offenses.first.cop_name).to eq('StringLiterals')
+ end
+ end
+ end
+
+ describe '#cops' do
+ subject(:cops) { team.cops }
+
+ it 'returns cop instances' do
+ expect(cops).not_to be_empty
+ expect(cops.all? { |c| c.is_a?(Rubocop::Cop::Cop) }).to be_true
+ end
+
+ context 'when only some cop classes are passed to .new' do
+ let(:cop_classes) do
+ [Rubocop::Cop::Lint::Void, Rubocop::Cop::Style::LineLength]
+ end
+
+ it 'returns only intances of the classes' do
+ expect(cops.size).to eq(2)
+ cops.sort! { |a, b| a.name <=> b.name }
+ expect(cops[0].name).to eq('LineLength')
+ expect(cops[1].name).to eq('Void')
+ end
+ end
+
+ context 'when some classes are disabled with config' do
+ before do
+ %w(Void LineLength).each do |cop_name|
+ config.for_cop(cop_name)['Enabled'] = false
+ end
+ end
+
+ let(:cop_names) { cops.map(&:name) }
+
+ it 'does not return intances of the classes' do
+ expect(cops).not_to be_empty
+ expect(cop_names).not_to include('Void')
+ expect(cop_names).not_to include('LineLength')
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/cop/util_spec.rb b/spec/rubocop/cop/util_spec.rb
new file mode 100644
index 0000000..75736e3
--- /dev/null
+++ b/spec/rubocop/cop/util_spec.rb
@@ -0,0 +1,49 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::Util do
+ describe '#line_range' do
+ include ASTHelper
+
+ let(:source) do
+ <<-END
+ foo = 1
+ bar = 2
+ class Test
+ def some_method
+ do_something
+ end
+ end
+ baz = 8
+ END
+ end
+
+ let(:ast) do
+ processed_source = parse_source(source)
+ processed_source.ast
+ end
+
+ let(:node) do
+ target_node = scan_node(ast) do |node|
+ break node if node.type == :class
+ end
+ fail 'No target node found!' unless target_node
+ target_node
+ end
+
+ context 'when Source::Range object is passed' do
+ it 'returns line range of that' do
+ line_range = Rubocop::Cop::Util.line_range(node.loc.expression)
+ expect(line_range).to eq(3..7)
+ end
+ end
+
+ context 'when AST::Node object is passed' do
+ it 'returns line range of the expression' do
+ line_range = Rubocop::Cop::Util.line_range(node)
+ expect(line_range).to eq(3..7)
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/cop/variable_inspector/assignment_spec.rb b/spec/rubocop/cop/variable_inspector/assignment_spec.rb
new file mode 100644
index 0000000..9902df5
--- /dev/null
+++ b/spec/rubocop/cop/variable_inspector/assignment_spec.rb
@@ -0,0 +1,213 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::VariableInspector::Assignment do
+ include ASTHelper
+ include AST::Sexp
+
+ let(:ast) do
+ processed_source = Rubocop::SourceParser.parse(source)
+ processed_source.ast
+ end
+
+ let(:source) do
+ <<-END
+ class SomeClass
+ def some_method(flag)
+ puts 'Hello World!'
+
+ if flag > 0
+ foo = 1
+ end
+ end
+ end
+ END
+ end
+
+ let(:def_node) do
+ found_node = scan_node(ast, include_origin_node: true) do |node|
+ break node if node.type == :def
+ end
+ fail 'No def node found!' unless found_node
+ found_node
+ end
+
+ let(:lvasgn_node) do
+ found_node = scan_node(ast) do |node|
+ break node if node.type == :lvasgn
+ end
+ fail 'No lvasgn node found!' unless found_node
+ found_node
+ end
+
+ let(:name) { lvasgn_node.children.first }
+ let(:scope) { Rubocop::Cop::VariableInspector::Scope.new(def_node) }
+ let(:variable) do
+ Rubocop::Cop::VariableInspector::Variable.new(name, lvasgn_node, scope)
+ end
+ let(:assignment) { described_class.new(lvasgn_node, variable) }
+
+ describe '.new' do
+ let(:variable) { double('variable') }
+
+ context 'when an assignment node is passed' do
+ it 'does not raise error' do
+ node = s(:lvasgn, :foo)
+ expect { described_class.new(node, variable) }.not_to raise_error
+ end
+ end
+
+ context 'when an argument declaration node is passed' do
+ it 'raises error' do
+ node = s(:arg, :foo)
+ expect { described_class.new(node, variable) }
+ .to raise_error(ArgumentError)
+ end
+ end
+
+ context 'when any other type node is passed' do
+ it 'raises error' do
+ node = s(:def)
+ expect { described_class.new(node, variable) }
+ .to raise_error(ArgumentError)
+ end
+ end
+ end
+
+ describe '#name' do
+ it 'returns the variable name' do
+ expect(assignment.name).to eq(:foo)
+ end
+ end
+
+ describe '#meta_assignment_node' do
+ context 'when it is += operator assignment' do
+ let(:source) do
+ <<-END
+ def some_method
+ foo += 1
+ end
+ END
+ end
+
+ it 'returns op_asgn node' do
+ expect(assignment.meta_assignment_node.type).to eq(:op_asgn)
+ end
+ end
+
+ context 'when it is ||= operator assignment' do
+ let(:source) do
+ <<-END
+ def some_method
+ foo ||= 1
+ end
+ END
+ end
+
+ it 'returns or_asgn node' do
+ expect(assignment.meta_assignment_node.type).to eq(:or_asgn)
+ end
+ end
+
+ context 'when it is &&= operator assignment' do
+ let(:source) do
+ <<-END
+ def some_method
+ foo &&= 1
+ end
+ END
+ end
+
+ it 'returns and_asgn node' do
+ expect(assignment.meta_assignment_node.type).to eq(:and_asgn)
+ end
+ end
+
+ context 'when it is multiple assignment' do
+ let(:source) do
+ <<-END
+ def some_method
+ foo, bar = [1, 2]
+ end
+ END
+ end
+
+ it 'returns masgn node' do
+ expect(assignment.meta_assignment_node.type).to eq(:masgn)
+ end
+ end
+ end
+
+ describe '#operator' do
+ context 'when it is normal assignment' do
+ let(:source) do
+ <<-END
+ def some_method
+ foo = 1
+ end
+ END
+ end
+
+ it 'returns =' do
+ expect(assignment.operator).to eq('=')
+ end
+ end
+
+ context 'when it is += operator assignment' do
+ let(:source) do
+ <<-END
+ def some_method
+ foo += 1
+ end
+ END
+ end
+
+ it 'returns +=' do
+ expect(assignment.operator).to eq('+=')
+ end
+ end
+
+ context 'when it is ||= operator assignment' do
+ let(:source) do
+ <<-END
+ def some_method
+ foo ||= 1
+ end
+ END
+ end
+
+ it 'returns ||=' do
+ expect(assignment.operator).to eq('||=')
+ end
+ end
+
+ context 'when it is &&= operator assignment' do
+ let(:source) do
+ <<-END
+ def some_method
+ foo &&= 1
+ end
+ END
+ end
+
+ it 'returns &&=' do
+ expect(assignment.operator).to eq('&&=')
+ end
+ end
+
+ context 'when it is multiple assignment' do
+ let(:source) do
+ <<-END
+ def some_method
+ foo, bar = [1, 2]
+ end
+ END
+ end
+
+ it 'returns =' do
+ expect(assignment.operator).to eq('=')
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/cop/variable_inspector/locatable_spec.rb b/spec/rubocop/cop/variable_inspector/locatable_spec.rb
new file mode 100644
index 0000000..0114020
--- /dev/null
+++ b/spec/rubocop/cop/variable_inspector/locatable_spec.rb
@@ -0,0 +1,734 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::VariableInspector::Locatable do
+ include ASTHelper
+ include AST::Sexp
+
+ class LocatableObject
+ include Rubocop::Cop::VariableInspector::Locatable
+
+ attr_reader :node, :scope
+
+ def initialize(node, scope)
+ @node = node
+ @scope = scope
+ end
+ end
+
+ let(:ast) do
+ processed_source = Rubocop::SourceParser.parse(source)
+ processed_source.ast
+ end
+
+ let(:def_node) do
+ found_node = scan_node(ast, include_origin_node: true) do |node|
+ break node if node.type == :def
+ end
+ fail 'No def node found!' unless found_node
+ found_node
+ end
+
+ let(:lvasgn_node) do
+ found_node = scan_node(ast) do |node|
+ break node if node.type == :lvasgn
+ end
+ fail 'No lvasgn node found!' unless found_node
+ found_node
+ end
+
+ let(:scope) { Rubocop::Cop::VariableInspector::Scope.new(def_node) }
+ let(:assignment) { LocatableObject.new(lvasgn_node, scope) }
+
+ describe '#ancestor_nodes_in_scope' do
+ let(:source) do
+ <<-END
+ class SomeClass
+ def some_method(flag)
+ puts 'Hello World!'
+
+ if flag > 0
+ foo = 1
+ end
+ end
+ end
+ END
+ end
+
+ it 'returns its ancestor nodes in the scope excluding scope node' do
+ ancestor_types = assignment.ancestor_nodes_in_scope.map(&:type)
+ expect(ancestor_types).to eq([:begin, :if])
+ end
+ end
+
+ describe '#branch_point_node' do
+ context 'when it is not in branch' do
+ let(:source) do
+ <<-END
+ def some_method(flag)
+ foo = 1
+ end
+ END
+ end
+
+ it 'returns nil' do
+ expect(assignment.branch_point_node).to be_nil
+ end
+ end
+
+ context 'when it is inside of if' do
+ let(:source) do
+ <<-END
+ def some_method(flag)
+ if flag
+ foo = 1
+ end
+ end
+ END
+ end
+
+ it 'returns the if node' do
+ expect(assignment.branch_point_node.type).to eq(:if)
+ end
+ end
+
+ context 'when it is inside of else of if' do
+ let(:source) do
+ <<-END
+ def some_method(flag)
+ if flag
+ else
+ foo = 1
+ end
+ end
+ END
+ end
+
+ it 'returns the if node' do
+ expect(assignment.branch_point_node.type).to eq(:if)
+ end
+ end
+
+ context 'when it is inside of if condition' do
+ let(:source) do
+ <<-END
+ def some_method(flag)
+ if foo = 1
+ do_something
+ end
+ end
+ END
+ end
+
+ it 'returns nil' do
+ expect(assignment.branch_point_node).to be_nil
+ end
+ end
+
+ context 'when multiple if are nested' do
+ context 'and it is inside of inner if' do
+ let(:source) do
+ <<-END
+ def some_method(a, b)
+ if a
+ if b
+ foo = 1
+ end
+ end
+ end
+ END
+ end
+
+ it 'returns inner if node' do
+ if_node = assignment.branch_point_node
+ expect(if_node.type).to eq(:if)
+ condition_node = if_node.children.first
+ expect(condition_node).to eq(s(:lvar, :b))
+ end
+ end
+
+ context 'and it is inside of inner if condition' do
+ let(:source) do
+ <<-END
+ def some_method(a, b)
+ if a
+ if foo = 1
+ do_something
+ end
+ end
+ end
+ END
+ end
+
+ it 'returns the next outer if node' do
+ if_node = assignment.branch_point_node
+ expect(if_node.type).to eq(:if)
+ condition_node = if_node.children.first
+ expect(condition_node).to eq(s(:lvar, :a))
+ end
+ end
+ end
+
+ context 'when it is inside of when of case' do
+ let(:source) do
+ <<-END
+ def some_method(flag)
+ case flag
+ when 1
+ foo = 1
+ end
+ end
+ END
+ end
+
+ it 'returns the case node' do
+ expect(assignment.branch_point_node.type).to eq(:case)
+ end
+ end
+
+ context 'when it is on the left side of &&' do
+ let(:source) do
+ <<-END
+ def some_method
+ (foo = 1) && do_something
+ end
+ END
+ end
+
+ it 'returns nil' do
+ expect(assignment.branch_point_node).to be_nil
+ end
+ end
+
+ context 'when it is on the right side of &&' do
+ let(:source) do
+ <<-END
+ def some_method
+ do_something && (foo = 1)
+ end
+ END
+ end
+
+ it 'returns the and node' do
+ expect(assignment.branch_point_node.type).to eq(:and)
+ end
+ end
+
+ context 'when it is on the left side of ||' do
+ let(:source) do
+ <<-END
+ def some_method
+ (foo = 1) || do_something
+ end
+ END
+ end
+
+ it 'returns nil' do
+ expect(assignment.branch_point_node).to be_nil
+ end
+ end
+
+ context 'when it is on the right side of ||' do
+ let(:source) do
+ <<-END
+ def some_method
+ do_something || (foo = 1)
+ end
+ END
+ end
+
+ it 'returns the or node' do
+ expect(assignment.branch_point_node.type).to eq(:or)
+ end
+ end
+
+ context 'when multiple && are chained' do
+ context 'and it is on the right side of the right &&' do
+ let(:source) do
+ <<-END
+ def some_method
+ do_something && do_anything && (foo = 1)
+ end
+ END
+ end
+
+ it 'returns the right and node' do
+ and_node = assignment.branch_point_node
+ expect(and_node.type).to eq(:and)
+ right_side_node = and_node.children[1]
+ expect(right_side_node.type).to eq(:begin)
+ end
+ end
+
+ context 'and it is on the right side of the left &&' do
+ let(:source) do
+ <<-END
+ def some_method
+ do_something && (foo = 1) && do_anything
+ end
+ END
+ end
+
+ it 'returns the left and node' do
+ and_node = assignment.branch_point_node
+ expect(and_node.type).to eq(:and)
+ right_side_node = and_node.children[1]
+ expect(right_side_node.type).to eq(:begin)
+ end
+ end
+ end
+
+ context 'when it is inside of begin with rescue' do
+ let(:source) do
+ <<-END
+ def some_method(flag)
+ begin
+ foo = 1
+ rescue
+ do_something
+ end
+ end
+ END
+ end
+
+ it 'returns the rescue node' do
+ expect(assignment.branch_point_node.type).to eq(:rescue)
+ end
+ end
+
+ context 'when it is inside of rescue' do
+ let(:source) do
+ <<-END
+ def some_method(flag)
+ begin
+ do_something
+ rescue
+ foo = 1
+ end
+ end
+ END
+ end
+
+ it 'returns the rescue node' do
+ expect(assignment.branch_point_node.type).to eq(:rescue)
+ end
+ end
+
+ context 'when it is inside of begin with ensure' do
+ let(:source) do
+ <<-END
+ def some_method(flag)
+ begin
+ foo = 1
+ ensure
+ do_something
+ end
+ end
+ END
+ end
+
+ it 'returns the ensure node' do
+ expect(assignment.branch_point_node.type).to eq(:ensure)
+ end
+ end
+
+ context 'when it is inside of ensure' do
+ let(:source) do
+ <<-END
+ def some_method(flag)
+ begin
+ do_something
+ ensure
+ foo = 1
+ end
+ end
+ END
+ end
+
+ it 'returns nil' do
+ expect(assignment.branch_point_node).to be_nil
+ end
+ end
+
+ context 'when it is inside of begin without rescue' do
+ let(:source) do
+ <<-END
+ def some_method(flag)
+ begin
+ foo = 1
+ end
+ end
+ END
+ end
+
+ it 'returns nil' do
+ expect(assignment.branch_point_node).to be_nil
+ end
+ end
+ end
+
+ describe '#branch_body_node' do
+ context 'when it is not in branch' do
+ let(:source) do
+ <<-END
+ def some_method(flag)
+ foo = 1
+ end
+ END
+ end
+
+ it 'returns nil' do
+ expect(assignment.branch_body_node).to be_nil
+ end
+ end
+
+ context 'when it is inside body of if' do
+ let(:source) do
+ <<-END
+ def some_method(flag)
+ if flag
+ foo = 1
+ puts foo
+ end
+ end
+ END
+ end
+
+ it 'returns the body node' do
+ expect(assignment.branch_body_node.type).to eq(:begin)
+ end
+ end
+
+ context 'when it is inside body of else of if' do
+ let(:source) do
+ <<-END
+ def some_method(flag)
+ if flag
+ do_something
+ else
+ foo = 1
+ puts foo
+ end
+ end
+ END
+ end
+
+ it 'returns the body node' do
+ expect(assignment.branch_body_node.type).to eq(:begin)
+ end
+ end
+
+ context 'when it is on the right side of &&' do
+ let(:source) do
+ <<-END
+ def some_method
+ do_something && (foo = 1)
+ end
+ END
+ end
+
+ it 'returns the right side node' do
+ expect(assignment.branch_body_node.type).to eq(:begin)
+ end
+ end
+
+ context 'when it is on the right side of ||' do
+ let(:source) do
+ <<-END
+ def some_method
+ do_something || (foo = 1)
+ end
+ END
+ end
+
+ it 'returns the right side node' do
+ expect(assignment.branch_body_node.type).to eq(:begin)
+ end
+ end
+
+ context 'when it is inside of begin with rescue' do
+ let(:source) do
+ <<-END
+ def some_method(flag)
+ begin
+ foo = 1
+ rescue
+ do_something
+ end
+ end
+ END
+ end
+
+ it 'returns the body node' do
+ expect(assignment.branch_body_node.type).to eq(:lvasgn)
+ end
+ end
+
+ context 'when it is inside of rescue' do
+ let(:source) do
+ <<-END
+ def some_method(flag)
+ begin
+ do_something
+ rescue
+ foo = 1
+ end
+ end
+ END
+ end
+
+ it 'returns the resbody node' do
+ expect(assignment.branch_body_node.type).to eq(:resbody)
+ end
+ end
+
+ context 'when it is inside of begin with ensure' do
+ let(:source) do
+ <<-END
+ def some_method(flag)
+ begin
+ foo = 1
+ ensure
+ do_something
+ end
+ end
+ END
+ end
+
+ it 'returns the body node' do
+ expect(assignment.branch_body_node.type).to eq(:lvasgn)
+ end
+ end
+ end
+
+ describe '#branch_id' do
+ context 'when it is not in branch' do
+ let(:source) do
+ <<-END
+ def some_method(flag)
+ foo = 1
+ end
+ END
+ end
+
+ it 'returns nil' do
+ expect(assignment.branch_id).to be_nil
+ end
+ end
+
+ context 'when it is inside body of if' do
+ let(:source) do
+ <<-END
+ def some_method(flag)
+ if flag
+ foo = 1
+ puts foo
+ end
+ end
+ END
+ end
+
+ it 'returns BRANCHNODEID_if_true' do
+ expect(assignment.branch_id).to match(/^\d+_if_true/)
+ end
+ end
+
+ context 'when it is inside body of else of if' do
+ let(:source) do
+ <<-END
+ def some_method(flag)
+ if flag
+ do_something
+ else
+ foo = 1
+ puts foo
+ end
+ end
+ END
+ end
+
+ it 'returns BRANCHNODEID_if_false' do
+ expect(assignment.branch_id).to match(/^\d+_if_false/)
+ end
+ end
+
+ context 'when it is inside body of when of case' do
+ let(:source) do
+ <<-END
+ def some_method(flag)
+ case flag
+ when first
+ do_something
+ when second
+ foo = 1
+ puts foo
+ else
+ do_something
+ end
+ end
+ END
+ end
+
+ it 'returns BRANCHNODEID_case_whenINDEX' do
+ expect(assignment.branch_id).to match(/^\d+_case_when1/)
+ end
+ end
+
+ context 'when it is inside body of when of case' do
+ let(:source) do
+ <<-END
+ def some_method(flag)
+ case flag
+ when first
+ do_something
+ when second
+ do_something
+ else
+ foo = 1
+ puts foo
+ end
+ end
+ END
+ end
+
+ it 'returns BRANCHNODEID_case_else' do
+ expect(assignment.branch_id).to match(/^\d+_case_else/)
+ end
+ end
+
+ context 'when it is on the left side of &&' do
+ let(:source) do
+ <<-END
+ def some_method
+ (foo = 1) && do_something
+ end
+ END
+ end
+
+ it 'returns nil' do
+ expect(assignment.branch_id).to be_nil
+ end
+ end
+
+ context 'when it is on the right side of &&' do
+ let(:source) do
+ <<-END
+ def some_method
+ do_something && (foo = 1)
+ end
+ END
+ end
+
+ it 'returns BRANCHNODEID_and_right' do
+ expect(assignment.branch_id).to match(/^\d+_and_right/)
+ end
+ end
+
+ context 'when it is on the left side of ||' do
+ let(:source) do
+ <<-END
+ def some_method
+ (foo = 1) || do_something
+ end
+ END
+ end
+
+ it 'returns nil' do
+ expect(assignment.branch_id).to be_nil
+ end
+ end
+
+ context 'when it is on the right side of ||' do
+ let(:source) do
+ <<-END
+ def some_method
+ do_something || (foo = 1)
+ end
+ END
+ end
+
+ it 'returns BRANCHNODEID_or_right' do
+ expect(assignment.branch_id).to match(/^\d+_or_right/)
+ end
+ end
+
+ context 'when it is inside of begin with rescue' do
+ let(:source) do
+ <<-END
+ def some_method(flag)
+ begin
+ foo = 1
+ rescue
+ do_something
+ end
+ end
+ END
+ end
+
+ it 'returns BRANCHNODEID_rescue_main' do
+ expect(assignment.branch_id).to match(/^\d+_rescue_main/)
+ end
+ end
+
+ context 'when it is inside of rescue' do
+ let(:source) do
+ <<-END
+ def some_method(flag)
+ begin
+ do_something
+ rescue FirstError
+ do_something
+ rescue SecondError
+ foo = 1
+ end
+ end
+ END
+ end
+
+ it 'returns BRANCHNODEID_rescue_rescueINDEX' do
+ expect(assignment.branch_id).to match(/^\d+_rescue_rescue1/)
+ end
+ end
+
+ context 'when it is inside of else of rescue' do
+ let(:source) do
+ <<-END
+ def some_method(flag)
+ begin
+ do_something
+ rescue FirstError
+ do_something
+ rescue SecondError
+ do_something
+ else
+ foo = 1
+ end
+ end
+ END
+ end
+
+ it 'returns BRANCHNODEID_rescue_else' do
+ expect(assignment.branch_id).to match(/^\d+_rescue_else/)
+ end
+ end
+
+ context 'when it is inside of begin with ensure' do
+ let(:source) do
+ <<-END
+ def some_method(flag)
+ begin
+ foo = 1
+ ensure
+ do_something
+ end
+ end
+ END
+ end
+
+ it 'returns BRANCHNODEID_ensure_main' do
+ expect(assignment.branch_id).to match(/^\d+_ensure_main/)
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/cop/variable_inspector/scope_spec.rb b/spec/rubocop/cop/variable_inspector/scope_spec.rb
new file mode 100644
index 0000000..1888e6e
--- /dev/null
+++ b/spec/rubocop/cop/variable_inspector/scope_spec.rb
@@ -0,0 +1,184 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::VariableInspector::Scope do
+ include ASTHelper
+ include AST::Sexp
+
+ describe '.new' do
+ context 'when non scope node is passed' do
+ it 'raises error' do
+ node = s(:lvasgn)
+ expect { described_class.new(node) }.to raise_error(ArgumentError)
+ end
+ end
+
+ context 'when begin node is passed' do
+ it 'accepts that as pseudo scope for top level scope' do
+ node = s(:begin)
+ expect { described_class.new(node) }.not_to raise_error
+ end
+ end
+ end
+
+ let(:ast) do
+ ast = Rubocop::SourceParser.parse(source).ast
+ Rubocop::Cop::VariableInspector.wrap_with_top_level_node(ast)
+ end
+
+ let(:scope_node_type) { :def }
+
+ let(:scope_node) do
+ found_node = scan_node(ast, include_origin_node: true) do |node|
+ break node if node.type == scope_node_type
+ end
+ fail 'No scope node found!' unless found_node
+ found_node
+ end
+
+ subject(:scope) { described_class.new(scope_node) }
+
+ describe '#ancestors_of_node' do
+ let(:source) do
+ <<-END
+ puts 1
+
+ class SomeClass
+ def some_method
+ foo = 1
+
+ if foo > 0
+ while foo < 10
+ this_is_target
+ foo += 1
+ end
+ else
+ do_something
+ end
+ end
+ end
+ END
+ end
+
+ let(:target_node) do
+ found_node = scan_node(ast) do |node|
+ next unless node.type == :send
+ _receiver_node, method_name = *node
+ break node if method_name == :this_is_target
+ end
+ fail 'No target node found!' unless found_node
+ found_node
+ end
+
+ it 'returns nodes in between the scope node and the passed node' do
+ ancestor_nodes = scope.ancestors_of_node(target_node)
+ ancestor_types = ancestor_nodes.map(&:type)
+ expect(ancestor_types).to eq([:begin, :if, :while, :begin])
+ end
+ end
+
+ describe '#body_node' do
+ shared_examples 'returns the body node' do
+ it 'returns the body node' do
+ expect(scope.body_node.children[1]).to eq(:this_is_target)
+ end
+ end
+
+ context 'when the scope is instance method' do
+ let(:source) do
+ <<-END
+ def some_method
+ this_is_target
+ end
+ END
+ end
+
+ let(:scope_node_type) { :def }
+
+ include_examples 'returns the body node'
+ end
+
+ context 'when the scope is singleton method' do
+ let(:source) do
+ <<-END
+ def self.some_method
+ this_is_target
+ end
+ END
+ end
+
+ let(:scope_node_type) { :defs }
+
+ include_examples 'returns the body node'
+ end
+
+ context 'when the scope is module' do
+ let(:source) do
+ <<-END
+ module SomeModule
+ this_is_target
+ end
+ END
+ end
+
+ let(:scope_node_type) { :module }
+
+ include_examples 'returns the body node'
+ end
+
+ context 'when the scope is class' do
+ let(:source) do
+ <<-END
+ class SomeClass
+ this_is_target
+ end
+ END
+ end
+
+ let(:scope_node_type) { :class }
+
+ include_examples 'returns the body node'
+ end
+
+ context 'when the scope is singleton class' do
+ let(:source) do
+ <<-END
+ class << self
+ this_is_target
+ end
+ END
+ end
+
+ let(:scope_node_type) { :sclass }
+
+ include_examples 'returns the body node'
+ end
+
+ context 'when the scope is block' do
+ let(:source) do
+ <<-END
+ 1.times do
+ this_is_target
+ end
+ END
+ end
+
+ let(:scope_node_type) { :block }
+
+ include_examples 'returns the body node'
+ end
+
+ context 'when the scope is top level' do
+ let(:source) do
+ <<-END
+ this_is_target
+ END
+ end
+
+ let(:scope_node_type) { :top_level }
+
+ include_examples 'returns the body node'
+ end
+ end
+end
diff --git a/spec/rubocop/cop/variable_inspector/variable_spec.rb b/spec/rubocop/cop/variable_inspector/variable_spec.rb
new file mode 100644
index 0000000..94873ed
--- /dev/null
+++ b/spec/rubocop/cop/variable_inspector/variable_spec.rb
@@ -0,0 +1,73 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::VariableInspector::Variable do
+ include AST::Sexp
+
+ describe '.new' do
+ context 'when non variable declaration node is passed' do
+ it 'raises error' do
+ name = :foo
+ declaration_node = s(:def)
+ scope = Rubocop::Cop::VariableInspector::Scope.new(s(:class))
+ expect { described_class.new(name, declaration_node, scope) }
+ .to raise_error(ArgumentError)
+ end
+ end
+ end
+
+ describe '#referenced?' do
+ let(:name) { :foo }
+ let(:declaration_node) { s(:arg, name) }
+ let(:scope) { double('scope') }
+ let(:variable) { described_class.new(name, declaration_node, scope) }
+
+ subject { variable.referenced? }
+
+ context 'when the variable is not yet assigned' do
+ it { should be_false }
+ end
+
+ context 'when the variable has an assignment' do
+ before do
+ variable.assign(s(:lvasgn, :foo))
+ end
+
+ context 'and the assignment is not yet referenced' do
+ it { should be_false }
+ end
+
+ context 'and the assignment is referenced' do
+ before do
+ variable.assignments.first.reference!
+ end
+
+ it { should be_true }
+ end
+ end
+
+ context 'when the variable has multiple assignments' do
+ before do
+ variable.assign(s(:lvasgn, :foo))
+ variable.assign(s(:lvasgn, :foo))
+ end
+
+ context 'and only once assignment is referenced' do
+ before do
+ variable.assignments[1].reference!
+ end
+
+ it { should be_true }
+ end
+
+ context 'and all assignments are referenced' do
+ before do
+ variable.assignments.each { |a| a.reference! }
+ end
+
+ it { should be_true }
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/cop/variable_inspector/variable_table_spec.rb b/spec/rubocop/cop/variable_inspector/variable_table_spec.rb
new file mode 100644
index 0000000..be08f53
--- /dev/null
+++ b/spec/rubocop/cop/variable_inspector/variable_table_spec.rb
@@ -0,0 +1,269 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::VariableInspector::VariableTable do
+ include AST::Sexp
+
+ subject(:variable_table) { described_class.new }
+
+ describe '#push_scope' do
+ it 'returns pushed scope object' do
+ node = s(:def)
+ scope = variable_table.push_scope(node)
+ expect(scope).to equal(variable_table.current_scope)
+ expect(scope.node).to equal(node)
+ end
+ end
+
+ describe '#pop_scope' do
+ before do
+ node = s(:def)
+ variable_table.push_scope(node)
+ end
+
+ it 'returns popped scope object' do
+ last_scope = variable_table.current_scope
+ popped_scope = variable_table.pop_scope
+ expect(popped_scope).to equal(last_scope)
+ end
+ end
+
+ describe '#current_scope_level' do
+ before do
+ variable_table.push_scope(s(:def))
+ end
+
+ it 'increases by pushing scope' do
+ last_scope_level = variable_table.current_scope_level
+ variable_table.push_scope(s(:def))
+ expect(variable_table.current_scope_level)
+ .to eq(last_scope_level + 1)
+ end
+
+ it 'decreases by popping scope' do
+ last_scope_level = variable_table.current_scope_level
+ variable_table.pop_scope
+ expect(variable_table.current_scope_level)
+ .to eq(last_scope_level - 1)
+ end
+ end
+
+ describe '#declare_variable' do
+ before do
+ 2.times do
+ node = s(:def)
+ variable_table.push_scope(node)
+ end
+ end
+
+ it 'adds variable to current scope with its name as key' do
+ node = s(:lvasgn, :foo)
+ variable_table.declare_variable(:foo, node)
+ expect(variable_table.current_scope.variables)
+ .to have_key(:foo)
+ expect(variable_table.scope_stack[-2].variables)
+ .to be_empty
+ variable = variable_table.current_scope.variables[:foo]
+ expect(variable.declaration_node).to equal(node)
+ end
+
+ it 'returns the added variable' do
+ node = s(:lvasgn, :foo)
+ variable = variable_table.declare_variable(:foo, node)
+ expect(variable.declaration_node).to equal(node)
+ end
+ end
+
+ describe '#find_variable' do
+ before do
+ variable_table.push_scope(s(:class))
+ variable_table.declare_variable(:baz, s(:lvasgn, :baz))
+
+ variable_table.push_scope(s(:def))
+ variable_table.declare_variable(:bar, s(:lvasgn, :bar))
+ end
+
+ context 'when current scope is block' do
+ before do
+ variable_table.push_scope(s(:block))
+ end
+
+ context 'when a variable with the target name exists ' \
+ 'in current scope' do
+ before do
+ variable_table.declare_variable(:foo, s(:lvasgn, :foo))
+ end
+
+ context 'and does not exist in outer scope' do
+ it 'returns the current scope variable' do
+ found_variable = variable_table.find_variable(:foo)
+ expect(found_variable.name).to eq(:foo)
+ end
+ end
+
+ context 'and also exists in outer scope' do
+ before do
+ variable_table.declare_variable(:bar, s(:lvasgn, :bar))
+ end
+
+ it 'returns the current scope variable' do
+ found_variable = variable_table.find_variable(:bar)
+ expect(found_variable.name).to equal(:bar)
+ expect(variable_table.current_scope.variables)
+ .to have_value(found_variable)
+ expect(variable_table.scope_stack[-2].variables)
+ .not_to have_value(found_variable)
+ end
+ end
+ end
+
+ context 'when a variable with the target name does not exist ' \
+ 'in current scope' do
+ context 'but exists in the direct outer scope' do
+ it 'returns the direct outer scope variable' do
+ found_variable = variable_table.find_variable(:bar)
+ expect(found_variable.name).to equal(:bar)
+ end
+ end
+
+ context 'but exists in a indirect outer scope' do
+ context 'when the direct outer scope is block' do
+ before do
+ variable_table.pop_scope
+ variable_table.pop_scope
+
+ variable_table.push_scope(s(:block))
+ variable_table.push_scope(s(:block))
+ end
+
+ it 'returns the indirect outer scope variable' do
+ found_variable = variable_table.find_variable(:baz)
+ expect(found_variable.name).to equal(:baz)
+ end
+ end
+
+ context 'when the direct outer scope is not block' do
+ it 'returns nil' do
+ found_variable = variable_table.find_variable(:baz)
+ expect(found_variable).to be_nil
+ end
+ end
+ end
+
+ context 'and does not exist in all outer scopes' do
+ it 'returns nil' do
+ found_variable = variable_table.find_variable(:non)
+ expect(found_variable).to be_nil
+ end
+ end
+ end
+ end
+
+ context 'when current scope is not block' do
+ before do
+ variable_table.push_scope(s(:def))
+ end
+
+ context 'when a variable with the target name exists ' \
+ 'in current scope' do
+ before do
+ variable_table.declare_variable(:foo, s(:lvasgn, :foo))
+ end
+
+ context 'and does not exist in outer scope' do
+ it 'returns the current scope variable' do
+ found_variable = variable_table.find_variable(:foo)
+ expect(found_variable.name).to eq(:foo)
+ end
+ end
+
+ context 'and also exists in outer scope' do
+ it 'returns the current scope variable' do
+ found_variable = variable_table.find_variable(:foo)
+ expect(found_variable.name).to equal(:foo)
+ expect(variable_table.current_scope.variables)
+ .to have_value(found_variable)
+ expect(variable_table.scope_stack[-2].variables)
+ .not_to have_value(found_variable)
+ end
+ end
+ end
+
+ context 'when a variable with the target name does not exist ' \
+ 'in current scope' do
+ context 'but exists in the direct outer scope' do
+ it 'returns nil' do
+ found_variable = variable_table.find_variable(:bar)
+ expect(found_variable).to be_nil
+ end
+ end
+
+ context 'and does not exist in all outer scopes' do
+ it 'returns nil' do
+ found_variable = variable_table.find_variable(:non)
+ expect(found_variable).to be_nil
+ end
+ end
+ end
+ end
+ end
+
+ describe '#accessible_variables' do
+ let(:accessible_variable_names) do
+ variable_table.accessible_variables.map(&:name)
+ end
+
+ before do
+ variable_table.push_scope(s(:class))
+ end
+
+ context 'when there are no variables' do
+ it 'returns empty array' do
+ expect(variable_table.accessible_variables).to be_empty
+ end
+ end
+
+ context 'when the current scope has some variables' do
+ before do
+ variable_table.declare_variable(:foo, s(:lvasgn, :foo))
+ variable_table.declare_variable(:bar, s(:lvasgn, :bar))
+ end
+
+ it 'returns all the variables' do
+ expect(accessible_variable_names).to match_array([:foo, :bar])
+ end
+ end
+
+ context 'when the direct outer scope has some variables' do
+ before do
+ variable_table.declare_variable(:foo, s(:lvasgn, :foo))
+ end
+
+ context 'and the current scope is block' do
+ before do
+ variable_table.push_scope(s(:block))
+ variable_table.declare_variable(:bar, s(:lvasgn, :bar))
+ variable_table.declare_variable(:baz, s(:lvasgn, :baz))
+ end
+
+ it 'returns the current and direct outer scope variables' do
+ expect(accessible_variable_names)
+ .to match_array([:foo, :bar, :baz])
+ end
+ end
+
+ context 'and the current scope is not block' do
+ before do
+ variable_table.push_scope(s(:def))
+ variable_table.declare_variable(:bar, s(:lvasgn, :bar))
+ variable_table.declare_variable(:baz, s(:lvasgn, :baz))
+ end
+
+ it 'returns only the current scope variables' do
+ expect(accessible_variable_names).to match_array([:bar, :baz])
+ end
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/cop/variable_inspector_spec.rb b/spec/rubocop/cop/variable_inspector_spec.rb
new file mode 100644
index 0000000..4704871
--- /dev/null
+++ b/spec/rubocop/cop/variable_inspector_spec.rb
@@ -0,0 +1,29 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Cop::VariableInspector do
+ include AST::Sexp
+
+ class ExampleInspector
+ include Rubocop::Cop::VariableInspector
+ end
+
+ subject(:inspector) { ExampleInspector.new }
+
+ describe '#process_node' do
+ before do
+ inspector.variable_table.push_scope(s(:def))
+ end
+
+ context 'when processing lvar node' do
+ let(:node) { s(:lvar, :foo) }
+
+ context 'when the variable is not yet declared' do
+ it 'does not raise error' do
+ expect { inspector.process_node(node) }.not_to raise_error
+ end
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/file_inspector_spec.rb b/spec/rubocop/file_inspector_spec.rb
new file mode 100644
index 0000000..a6613ad
--- /dev/null
+++ b/spec/rubocop/file_inspector_spec.rb
@@ -0,0 +1,84 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+module Rubocop
+ class FileInspector
+ attr_writer :errors # Needed only for testing.
+ end
+end
+
+describe Rubocop::FileInspector do
+ subject(:inspector) { described_class.new(options) }
+ let(:options) { {} }
+ let(:offenses) { [] }
+ let(:errors) { [] }
+
+ before(:each) do
+ $stdout = StringIO.new
+ $stderr = StringIO.new
+
+ allow(inspector).to receive(:process_source) do
+ [double('ProcessedSource').as_null_object, []]
+ end
+
+ allow(inspector).to receive(:inspect_file) do
+ inspector.errors = errors
+ [offenses, !:updated_source_file]
+ end
+ end
+
+ after(:each) do
+ $stdout = STDOUT
+ $stderr = STDERR
+ end
+
+ describe '#display_error_summary' do
+ let(:errors) do
+ ['An error occurred while Encoding cop was inspecting file.rb.']
+ end
+
+ it 'displays an error message to stderr when errors are present' do
+ inspector.process_files(['file.rb'], nil) {}
+ inspector.display_error_summary
+ expect($stderr.string.lines.to_a[-6..-5])
+ .to eq(["1 error occurred:\n", "#{errors.first}\n"])
+ end
+ end
+
+ describe '#process_files' do
+ context 'if there are no offenses in inspected files' do
+ it 'returns false' do
+ result = inspector.process_files(['file.rb'], nil) {}
+ expect(result).to eq(false)
+ end
+ end
+
+ context 'if there is an offense in an inspected file' do
+ let(:offenses) do
+ [Rubocop::Cop::Offense.new(:convention,
+ Struct.new(:line, :column,
+ :source_line).new(1, 0, ''),
+ 'Use alias_method instead of alias.',
+ 'Alias')]
+ end
+
+ it 'returns true' do
+ expect(inspector.process_files(['file.rb'], nil) {}).to eq(true)
+ end
+
+ it 'sends the offense to a formatter' do
+ inspector.process_files(['file.rb'], nil) {}
+ expect($stdout.string.split("\n"))
+ .to eq(['Inspecting 1 file',
+ 'C',
+ '',
+ 'Offenses:',
+ '',
+ "file.rb:1:1: C: #{offenses.first.message}",
+ '',
+ '1 file inspected, 1 offense detected'])
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/formatter/base_formatter_spec.rb b/spec/rubocop/formatter/base_formatter_spec.rb
new file mode 100644
index 0000000..77ae15a
--- /dev/null
+++ b/spec/rubocop/formatter/base_formatter_spec.rb
@@ -0,0 +1,191 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+module Rubocop
+ module Formatter
+ describe BaseFormatter do
+ include FileHelper
+
+ describe 'how the API methods are invoked', :isolated_environment do
+ subject(:formatter) { double('formatter').as_null_object }
+ let(:cli) { CLI.new }
+ let(:output) { $stdout.string }
+
+ before do
+ create_file('1_offense.rb', [
+ '# encoding: utf-8',
+ '#' * 90
+ ])
+
+ create_file('4_offenses.rb', [
+ '# encoding: utf-8',
+ 'puts x ',
+ 'test;',
+ 'top;',
+ '#' * 90
+ ])
+
+ create_file('no_offense.rb', [
+ '# encoding: utf-8'
+ ])
+
+ allow(SimpleTextFormatter).to receive(:new).and_return(formatter)
+ $stdout = StringIO.new
+ end
+
+ after do
+ $stdout = STDOUT
+ end
+
+ def run
+ cli.run([])
+ end
+
+ describe 'invocation order' do
+ subject(:formatter) do
+ formatter = double('formatter')
+ def formatter.method_missing(method_name, *args)
+ return if method_name == :output
+ puts method_name
+ end
+ formatter
+ end
+
+ it 'is called in the proper sequence' do
+ run
+ expect(output).to eq([
+ 'started',
+ 'file_started',
+ 'file_finished',
+ 'file_started',
+ 'file_finished',
+ 'file_started',
+ 'file_finished',
+ 'finished',
+ ''
+ ].join("\n"))
+ end
+ end
+
+ shared_examples 'receives all file paths' do |method_name|
+ it 'receives all file paths' do
+ expected_paths = [
+ '1_offense.rb',
+ '4_offenses.rb',
+ 'no_offense.rb'
+ ].map { |path| File.expand_path(path) }.sort
+
+ expect(formatter).to receive(method_name) do |all_files|
+ expect(all_files.sort).to eq(expected_paths)
+ end
+
+ run
+ end
+
+ describe 'the passed files paths' do
+ it 'is frozen' do
+ expect(formatter).to receive(method_name) do |all_files|
+ all_files.each do |path|
+ expect(path).to be_frozen
+ end
+ end
+ run
+ end
+ end
+ end
+
+ describe '#started' do
+ include_examples 'receives all file paths', :started
+ end
+
+ describe '#finished' do
+ context 'when RuboCop finished inspecting all files normally' do
+ include_examples 'receives all file paths', :started
+ end
+
+ context 'when RuboCop is interrupted by user' do
+ it 'received processed file paths' do
+ class << formatter
+ attr_reader :processed_file_count
+
+ def file_finished(file, offenses)
+ @processed_file_count ||= 0
+ @processed_file_count += 1
+ end
+ end
+
+ allow(cli).to receive(:wants_to_quit?) do
+ formatter.processed_file_count == 2
+ end
+
+ expect(formatter).to receive(:finished) do |processed_files|
+ expect(processed_files.size).to eq(2)
+ end
+
+ run
+ end
+ end
+ end
+
+ shared_examples 'receives a file path' do |method_name|
+ it 'receives a file path' do
+ expect(formatter).to receive(method_name)
+ .with(File.expand_path('1_offense.rb'), anything)
+
+ expect(formatter).to receive(method_name)
+ .with(File.expand_path('4_offenses.rb'), anything)
+
+ expect(formatter).to receive(method_name)
+ .with(File.expand_path('no_offense.rb'), anything)
+
+ run
+ end
+
+ describe 'the passed path' do
+ it 'is frozen' do
+ expect(formatter)
+ .to receive(method_name).exactly(3).times do |path|
+ expect(path).to be_frozen
+ end
+ run
+ end
+ end
+ end
+
+ describe '#file_started' do
+ include_examples 'receives a file path', :file_started
+
+ it 'receives file specific information hash' do
+ expect(formatter).to receive(:file_started)
+ .with(anything, an_instance_of(Hash)).exactly(3).times
+ run
+ end
+ end
+
+ describe '#file_finished' do
+ include_examples 'receives a file path', :file_finished
+
+ it 'receives an array of detected offenses for the file' do
+ expect(formatter).to receive(:file_finished)
+ .exactly(3).times do |file, offenses|
+ case File.basename(file)
+ when '1_offense.rb'
+ expect(offenses.size).to eq(1)
+ when '4_offenses.rb'
+ expect(offenses.size).to eq(4)
+ when 'no_offense.rb'
+ expect(offenses).to be_empty
+ else
+ fail
+ end
+ expect(offenses.all? { |o| o.is_a?(Rubocop::Cop::Offense) })
+ .to be_true
+ end
+ run
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/formatter/clang_style_formatter_spec.rb b/spec/rubocop/formatter/clang_style_formatter_spec.rb
new file mode 100644
index 0000000..1944de3
--- /dev/null
+++ b/spec/rubocop/formatter/clang_style_formatter_spec.rb
@@ -0,0 +1,114 @@
+# encoding: utf-8
+
+require 'spec_helper'
+require 'stringio'
+
+module Rubocop
+ module Formatter
+ describe ClangStyleFormatter do
+ subject(:formatter) { described_class.new(output) }
+ let(:output) { StringIO.new }
+
+ describe '#report_file' do
+ it 'displays text containing the offending source line' do
+ cop = Cop::Cop.new
+ source_buffer = Parser::Source::Buffer.new('test', 1)
+ source_buffer.source = ('aa'..'az').to_a.join($RS)
+ cop.add_offense(nil,
+ Parser::Source::Range.new(source_buffer, 0, 2),
+ 'message 1')
+ cop.add_offense(nil,
+ Parser::Source::Range.new(source_buffer, 30, 32),
+ 'message 2')
+
+ formatter.report_file('test', cop.offenses)
+ expect(output.string).to eq ['test:1:1: C: message 1',
+ 'aa',
+ '^^',
+ 'test:11:1: C: message 2',
+ 'ak',
+ '^^',
+ ''].join("\n")
+ end
+
+ context 'when the source line is blank' do
+ it 'does not display offending source line' do
+ cop = Cop::Cop.new
+ source_buffer = Parser::Source::Buffer.new('test', 1)
+ source_buffer.source = ([' ', 'yaba']).to_a.join($RS)
+ cop.add_offense(nil,
+ Parser::Source::Range.new(source_buffer, 0, 2),
+ 'message 1')
+ cop.add_offense(nil,
+ Parser::Source::Range.new(source_buffer, 6, 10),
+ 'message 2')
+
+ formatter.report_file('test', cop.offenses)
+ expect(output.string).to eq ['test:1:1: C: message 1',
+ 'test:2:1: C: message 2',
+ 'yaba',
+ '^^^^',
+ ''].join("\n")
+ end
+ end
+
+ context 'when the offending source spans multiple lines' do
+ it 'displays the first line' do
+ source = ['do_something([this,',
+ ' is,',
+ ' target])'].join($RS)
+
+ source_buffer = Parser::Source::Buffer.new('test', 1)
+ source_buffer.source = source
+
+ location = Parser::Source::Range.new(source_buffer,
+ source.index('['),
+ source.index(']') + 1)
+
+ cop = Cop::Cop.new
+ cop.add_offense(nil, location, 'message 1')
+
+ formatter.report_file('test', cop.offenses)
+ expect(output.string).to eq ['test:1:14: C: message 1',
+ 'do_something([this,',
+ ' ^^^^^^',
+ ''].join("\n")
+ end
+ end
+
+ let(:file) { '/path/to/file' }
+
+ let(:offense) do
+ Cop::Offense.new(:convention, location,
+ 'This is a message.', 'CopName', corrected)
+ end
+
+ let(:location) do
+ source_buffer = Parser::Source::Buffer.new('test', 1)
+ source_buffer.source = "a\n"
+ Parser::Source::Range.new(source_buffer, 0, 1)
+ end
+
+ context 'when the offense is not corrected' do
+ let(:corrected) { false }
+
+ it 'prints message as-is' do
+ formatter.report_file(file, [offense])
+ expect(output.string)
+ .to include(': This is a message.')
+ end
+ end
+
+ context 'when the offense is automatically corrected' do
+ let(:corrected) { true }
+
+ it 'prints [Corrected] along with message' do
+ formatter.report_file(file, [offense])
+ expect(output.string)
+ .to include(': [Corrected] This is a message.')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/formatter/colorizable_spec.rb b/spec/rubocop/formatter/colorizable_spec.rb
new file mode 100644
index 0000000..ed17898
--- /dev/null
+++ b/spec/rubocop/formatter/colorizable_spec.rb
@@ -0,0 +1,107 @@
+# encoding: utf-8
+
+require 'spec_helper'
+require 'rubocop/formatter/colorizable'
+require 'stringio'
+
+module Rubocop
+ module Formatter
+ describe Colorizable do
+ let(:formatter_class) do
+ Class.new(BaseFormatter) do
+ include Colorizable
+ end
+ end
+
+ let(:formatter) do
+ formatter_class.new(output)
+ end
+
+ let(:output) { double('output') }
+
+ around do |example|
+ original_state = Rainbow.enabled
+
+ begin
+ example.run
+ ensure
+ Rainbow.enabled = original_state
+ end
+ end
+
+ describe '#colorize' do
+ subject { formatter.colorize('foo', :red) }
+
+ shared_examples 'does nothing' do
+ it 'does nothing' do
+ should == 'foo'
+ end
+ end
+
+ context 'when the global Rainbow.enabled is true' do
+ before do
+ Rainbow.enabled = true
+ end
+
+ context "and the formatter's output is a tty" do
+ before do
+ allow(output).to receive(:tty?).and_return(true)
+ end
+
+ it 'colorize the passed string' do
+ should == "\e[31mfoo\e[0m"
+ end
+ end
+
+ context "and the formatter's output is not a tty" do
+ before do
+ allow(output).to receive(:tty?).and_return(false)
+ end
+
+ include_examples 'does nothing'
+ end
+ end
+
+ context 'when the global Rainbow.enabled is false' do
+ before do
+ Rainbow.enabled = false
+ end
+
+ context "and the formatter's output is a tty" do
+ before do
+ allow(output).to receive(:tty?).and_return(true)
+ end
+
+ include_examples 'does nothing'
+ end
+
+ context "and the formatter's output is not a tty" do
+ before do
+ allow(output).to receive(:tty?).and_return(false)
+ end
+
+ include_examples 'does nothing'
+ end
+ end
+ end
+
+ [
+ :black,
+ :red,
+ :green,
+ :yellow,
+ :blue,
+ :magenta,
+ :cyan,
+ :white
+ ].each do |color|
+ describe "##{color}" do
+ it "invokes #colorize(string, #{color}" do
+ expect(formatter).to receive(:colorize).with('foo', color)
+ formatter.send(color, 'foo')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/formatter/disabled_config_formatter_spec.rb b/spec/rubocop/formatter/disabled_config_formatter_spec.rb
new file mode 100644
index 0000000..8aac58c
--- /dev/null
+++ b/spec/rubocop/formatter/disabled_config_formatter_spec.rb
@@ -0,0 +1,50 @@
+# encoding: utf-8
+
+require 'spec_helper'
+require 'stringio'
+require 'ostruct'
+
+module Rubocop
+ module Formatter
+ describe DisabledConfigFormatter do
+ subject(:formatter) { described_class.new(output) }
+ let(:output) do
+ o = StringIO.new
+ def o.path
+ 'rubocop-todo.yml'
+ end
+ o
+ end
+ let(:offenses) do
+ [Rubocop::Cop::Offense.new(:convention, location, 'message', 'Cop1'),
+ Rubocop::Cop::Offense.new(:convention, location, 'message', 'Cop2')]
+ end
+ let(:location) { OpenStruct.new(line: 1, column: 5) }
+ before { $stdout = StringIO.new }
+
+ describe '#finished' do
+ it 'displays YAML configuration disabling all cops with offenses' do
+ formatter.file_finished('test.rb', offenses)
+ formatter.finished(['test.rb'])
+ expect(output.string).to eq(described_class::HEADING +
+ ['',
+ '',
+ '# Offense count: 1',
+ 'Cop1:',
+ ' Enabled: false',
+ '',
+ '# Offense count: 1',
+ 'Cop2:',
+ ' Enabled: false',
+ ''].join("\n"))
+ expect($stdout.string)
+ .to eq(['Created rubocop-todo.yml.',
+ 'Run `rubocop --config rubocop-todo.yml`, or',
+ 'add inherit_from: rubocop-todo.yml in a .rubocop.yml ' \
+ 'file.',
+ ''].join("\n"))
+ end
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/formatter/disabled_lines_formatter_spec.rb b/spec/rubocop/formatter/disabled_lines_formatter_spec.rb
new file mode 100644
index 0000000..032efe4
--- /dev/null
+++ b/spec/rubocop/formatter/disabled_lines_formatter_spec.rb
@@ -0,0 +1,69 @@
+# encoding: utf-8
+
+require 'spec_helper'
+require 'stringio'
+require 'tempfile'
+
+module Rubocop
+ module Formatter
+ describe DisabledLinesFormatter do
+ subject(:formatter) { described_class.new(output) }
+ let(:output) { StringIO.new }
+
+ let(:files) do
+ %w(lib/rubocop.rb spec/spec_helper.rb bin/rubocop).map do |path|
+ File.expand_path(path)
+ end
+ end
+
+ let(:file_started) do
+ formatter.file_started(files.first, cop_disabled_line_ranges)
+ end
+
+ describe '#file_started' do
+ before { formatter.started(files) }
+
+ context 'when no disable cop comments are detected' do
+ let(:cop_disabled_line_ranges) { {} }
+ it 'shouldn\'t add to cop_disabled_line_ranges' do
+ expect { file_started }.to_not(
+ change { formatter.cop_disabled_line_ranges })
+ end
+ end
+
+ context 'when any disable cop comments are detected' do
+ let(:cop_disabled_line_ranges) do
+ { cop_disabled_line_ranges: { 'LineLength' => [1..1] } }
+ end
+ it 'should merge the changes into cop_disabled_line_ranges' do
+ expect { file_started }.to(
+ change { formatter.cop_disabled_line_ranges })
+ end
+ end
+ end
+
+ describe '#finished' do
+ context 'when there disabled cops detected' do
+ let(:cop_disabled_line_ranges) do
+ { cop_disabled_line_ranges: { 'LineLength' => [1..1] } }
+ end
+
+ before do
+ formatter.started(files)
+ formatter.file_started('lib/rubocop.rb', cop_disabled_line_ranges)
+ end
+
+ it 'lists disabled cops by file' do
+ formatter.finished(files)
+ expect(output.string).to eq(
+ ['',
+ 'Cops disabled line ranges:',
+ '',
+ 'lib/rubocop.rb:1..1: LineLength',
+ ''].join("\n"))
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/formatter/emacs_style_formatter_spec.rb b/spec/rubocop/formatter/emacs_style_formatter_spec.rb
new file mode 100644
index 0000000..d46f528
--- /dev/null
+++ b/spec/rubocop/formatter/emacs_style_formatter_spec.rb
@@ -0,0 +1,62 @@
+# encoding: utf-8
+
+require 'spec_helper'
+require 'stringio'
+
+module Rubocop
+ module Formatter
+ describe EmacsStyleFormatter do
+ subject(:formatter) { described_class.new(output) }
+ let(:output) { StringIO.new }
+
+ describe '#file_finished' do
+ it 'displays parsable text' do
+ cop = Cop::Cop.new
+ source_buffer = Parser::Source::Buffer.new('test', 1)
+ source_buffer.source = %w(a b cdefghi).join("\n")
+
+ cop.add_offense(nil,
+ Parser::Source::Range.new(source_buffer, 0, 1),
+ 'message 1')
+ cop.add_offense(nil,
+ Parser::Source::Range.new(source_buffer, 9, 10),
+ 'message 2')
+
+ formatter.file_finished('test', cop.offenses)
+ expect(output.string).to eq ['test:1:1: C: message 1',
+ "test:3:6: C: message 2\n"].join("\n")
+ end
+
+ context 'when the offense is automatically corrected' do
+ let(:file) { '/path/to/file' }
+
+ let(:offense) do
+ Cop::Offense.new(:convention, location,
+ 'This is a message.', 'CopName', corrected)
+ end
+
+ let(:location) do
+ source_buffer = Parser::Source::Buffer.new('test', 1)
+ source_buffer.source = "a\n"
+ Parser::Source::Range.new(source_buffer, 0, 1)
+ end
+
+ let(:corrected) { true }
+
+ it 'prints [Corrected] along with message' do
+ formatter.file_finished(file, [offense])
+ expect(output.string)
+ .to include(': [Corrected] This is a message.')
+ end
+ end
+ end
+
+ describe '#finished' do
+ it 'does not report summary' do
+ formatter.finished(['/path/to/file'])
+ expect(output.string).to be_empty
+ end
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/formatter/file_list_formatter_spec.rb b/spec/rubocop/formatter/file_list_formatter_spec.rb
new file mode 100644
index 0000000..54896d6
--- /dev/null
+++ b/spec/rubocop/formatter/file_list_formatter_spec.rb
@@ -0,0 +1,33 @@
+# encoding: utf-8
+
+require 'spec_helper'
+require 'stringio'
+
+module Rubocop
+ module Formatter
+ describe FileListFormatter do
+ subject(:formatter) { described_class.new(output) }
+ let(:output) { StringIO.new }
+
+ describe '#file_finished' do
+ it 'displays parsable text' do
+ cop = Cop::Cop.new
+ source_buffer = Parser::Source::Buffer.new('test', 1)
+ source_buffer.source = %w(a b cdefghi).join("\n")
+
+ cop.add_offense(nil,
+ Parser::Source::Range.new(source_buffer, 0, 1),
+ 'message 1')
+ cop.add_offense(nil,
+ Parser::Source::Range.new(source_buffer, 9, 10),
+ 'message 2')
+
+ formatter.file_finished('test', cop.offenses)
+ formatter.file_finished('test_2', cop.offenses)
+ expect(output.string).to eq ['test',
+ "test_2\n"].join("\n")
+ end
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/formatter/formatter_set_spec.rb b/spec/rubocop/formatter/formatter_set_spec.rb
new file mode 100644
index 0000000..186047f
--- /dev/null
+++ b/spec/rubocop/formatter/formatter_set_spec.rb
@@ -0,0 +1,132 @@
+# encoding: utf-8
+
+require 'spec_helper'
+require 'tempfile'
+
+module Rubocop
+ module Formatter
+ describe FormatterSet do
+ subject(:formatter_set) { described_class.new }
+
+ it 'responds to all formatter API methods' do
+ [:started, :file_started, :file_finished, :finished].each do |method|
+ expect(formatter_set).to respond_to(method)
+ end
+ end
+
+ describe 'formatter API method' do
+ before do
+ formatter_set.add_formatter('simple')
+ formatter_set.add_formatter('emacs')
+ end
+
+ let(:files) { ['/path/to/file1', '/path/to/file2'] }
+
+ it 'invokes same method of all containing formatters' do
+ formatter_set.each do |formatter|
+ expect(formatter).to receive(:started).with(files)
+ end
+ formatter_set.started(files)
+ end
+ end
+
+ describe 'add_formatter' do
+ it 'adds a formatter to itself' do
+ formatter_set.add_formatter('simple')
+ expect(formatter_set.size).to eq(1)
+ end
+
+ it 'adds a formatter with specified formatter type' do
+ formatter_set.add_formatter('simple')
+ expect(formatter_set.first.class).to eq(SimpleTextFormatter)
+ end
+
+ it 'can add multiple formatters by being invoked multiple times' do
+ formatter_set.add_formatter('simple')
+ formatter_set.add_formatter('emacs')
+ expect(formatter_set[0].class).to eq(SimpleTextFormatter)
+ expect(formatter_set[1].class).to eq(EmacsStyleFormatter)
+ end
+
+ context 'when output path is omitted' do
+ it 'adds a formatter outputs to $stdout' do
+ formatter_set.add_formatter('simple')
+ expect(formatter_set.first.output).to eq($stdout)
+ end
+ end
+
+ context 'when output path is specified' do
+ it 'adds a formatter outputs to the specified file' do
+ output_path = Tempfile.new('').path
+ formatter_set.add_formatter('simple', output_path)
+ expect(formatter_set.first.output.class).to eq(File)
+ expect(formatter_set.first.output.path).to eq(output_path)
+ end
+ end
+ end
+
+ describe '#close_output_files' do
+ before do
+ 2.times do
+ output_path = Tempfile.new('').path
+ formatter_set.add_formatter('simple', output_path)
+ end
+ formatter_set.add_formatter('simple')
+ end
+
+ it 'closes all output files' do
+ formatter_set.close_output_files
+ formatter_set[0..1].each do |formatter|
+ expect(formatter.output).to be_closed
+ end
+ end
+
+ it 'does not close non file output' do
+ expect(formatter_set[2].output).not_to be_closed
+ end
+ end
+
+ describe '#builtin_formatter_class' do
+ def builtin_formatter_class(string)
+ described_class.new.send(:builtin_formatter_class, string)
+ end
+
+ it 'returns class which matches passed alias name exactly' do
+ expect(builtin_formatter_class('simple'))
+ .to eq(SimpleTextFormatter)
+ end
+
+ it 'returns class whose first letter of alias name ' \
+ 'matches passed letter' do
+ expect(builtin_formatter_class('s'))
+ .to eq(SimpleTextFormatter)
+ end
+ end
+
+ describe '#custom_formatter_class' do
+ def custom_formatter_class(string)
+ described_class.new.send(:custom_formatter_class, string)
+ end
+
+ it 'returns constant represented by the passed string' do
+ expect(custom_formatter_class('Rubocop')).to eq(Rubocop)
+ end
+
+ it 'can handle namespaced constant name' do
+ expect(custom_formatter_class('Rubocop::CLI')).to eq(Rubocop::CLI)
+ end
+
+ it 'can handle top level namespaced constant name' do
+ expect(custom_formatter_class('::Rubocop::CLI')).to eq(Rubocop::CLI)
+ end
+
+ context 'when non-existent constant name is passed' do
+ it 'raises error' do
+ expect { custom_formatter_class('Rubocop::NonExistentClass') }
+ .to raise_error(NameError)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/formatter/fuubar_style_formatter_spec.rb b/spec/rubocop/formatter/fuubar_style_formatter_spec.rb
new file mode 100644
index 0000000..893899e
--- /dev/null
+++ b/spec/rubocop/formatter/fuubar_style_formatter_spec.rb
@@ -0,0 +1,129 @@
+# encoding: utf-8
+
+require 'spec_helper'
+require 'stringio'
+
+module Rubocop
+ describe Formatter::FuubarStyleFormatter do
+ subject(:formatter) { described_class.new(output) }
+ let(:output) { StringIO.new }
+
+ let(:files) do
+ %w(lib/rubocop.rb spec/spec_helper.rb).map do |path|
+ File.expand_path(path)
+ end
+ end
+
+ describe '#with_color' do
+ around do |example|
+ original_state = formatter.rainbow.enabled
+
+ begin
+ example.run
+ ensure
+ formatter.rainbow.enabled = original_state
+ end
+ end
+
+ context 'when color is enabled' do
+ before do
+ formatter.rainbow.enabled = true
+ end
+
+ it 'outputs coloring sequence code at the beginning and the end' do
+ formatter.with_color { formatter.output.write 'foo' }
+ expect(output.string).to eq("\e[32mfoo\e[0m")
+ end
+ end
+
+ context 'when color is enabled' do
+ before do
+ formatter.rainbow.enabled = false
+ end
+
+ it 'outputs nothing' do
+ formatter.with_color { formatter.output.write 'foo' }
+ expect(output.string).to eq('foo')
+ end
+ end
+ end
+
+ describe '#progressbar_color' do
+ before do
+ formatter.started(files)
+ end
+
+ def offense(severity, corrected = false)
+ source_range = double('source_range').as_null_object
+ Cop::Offense.new(severity, source_range, 'message', 'Cop', corrected)
+ end
+
+ context 'initially' do
+ it 'is green' do
+ expect(formatter.progressbar_color).to eq(:green)
+ end
+ end
+
+ context 'when no offenses are detected in a file' do
+ before do
+ formatter.file_finished(files[0], [])
+ end
+
+ it 'is still green' do
+ expect(formatter.progressbar_color).to eq(:green)
+ end
+ end
+
+ context 'when a convention offense is detected in a file' do
+ before do
+ formatter.file_finished(files[0], [offense(:convention)])
+ end
+
+ it 'is yellow' do
+ expect(formatter.progressbar_color).to eq(:yellow)
+ end
+ end
+
+ context 'when an error offense is detected in a file' do
+ before do
+ formatter.file_finished(files[0], [offense(:error)])
+ end
+
+ it 'is red' do
+ expect(formatter.progressbar_color).to eq(:red)
+ end
+
+ context 'and then a convention offense is detected in the next file' do
+ before do
+ formatter.file_finished(files[1], [offense(:convention)])
+ end
+
+ it 'is still red' do
+ expect(formatter.progressbar_color).to eq(:red)
+ end
+ end
+ end
+
+ context 'when convention and error offenses are detected in a file' do
+ before do
+ offenses = [offense(:convention), offense(:error)]
+ formatter.file_finished(files[0], offenses)
+ end
+
+ it 'is red' do
+ expect(formatter.progressbar_color).to eq(:red)
+ end
+ end
+
+ context 'when a offense is detected in a file and auto-corrected' do
+ before do
+ formatter.file_finished(files[0], [offense(:convention, true)])
+ end
+
+ it 'is green' do
+ expect(formatter.progressbar_color).to eq(:green)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/formatter/json_formatter_spec.rb b/spec/rubocop/formatter/json_formatter_spec.rb
new file mode 100644
index 0000000..c980fb5
--- /dev/null
+++ b/spec/rubocop/formatter/json_formatter_spec.rb
@@ -0,0 +1,152 @@
+# encoding: utf-8
+
+require 'spec_helper'
+require 'stringio'
+
+module Rubocop
+ describe Formatter::JSONFormatter do
+ subject(:formatter) { described_class.new(output) }
+ let(:output) { StringIO.new }
+ let(:files) { %w(/path/to/file1 /path/to/file2) }
+ let(:location) do
+ source_buffer = Parser::Source::Buffer.new('test', 1)
+ source_buffer.source = %w(a b cdefghi).join("\n")
+ Parser::Source::Range.new(source_buffer, 9, 10)
+ end
+ let(:offense) do
+ Cop::Offense.new(:convention, location,
+ 'This is message', 'CopName', true)
+ end
+
+ describe '#started' do
+ let(:summary) { formatter.output_hash[:summary] }
+
+ it 'sets target file count in summary' do
+ expect(summary[:target_file_count]).to be_nil
+ formatter.started(%w(/path/to/file1 /path/to/file2))
+ expect(summary[:target_file_count]).to eq(2)
+ end
+ end
+
+ describe '#file_finished' do
+ before do
+ count = 0
+ allow(formatter).to receive(:hash_for_file) do
+ count += 1
+ end
+ end
+
+ let(:summary) { formatter.output_hash[:summary] }
+
+ it 'adds detected offense count in summary' do
+ expect(summary[:offense_count]).to eq(0)
+
+ formatter.file_started(files[0], {})
+ expect(summary[:offense_count]).to eq(0)
+ formatter.file_finished(files[0], [
+ double('offense1'), double('offense2')
+ ])
+ expect(summary[:offense_count]).to eq(2)
+ end
+
+ it 'adds value of #hash_for_file to #output_hash[:files]' do
+ expect(formatter.output_hash[:files]).to be_empty
+
+ formatter.file_started(files[0], {})
+ expect(formatter.output_hash[:files]).to be_empty
+ formatter.file_finished(files[0], [])
+ expect(formatter.output_hash[:files]).to eq([1])
+
+ formatter.file_started(files[1], {})
+ expect(formatter.output_hash[:files]).to eq([1])
+ formatter.file_finished(files[1], [])
+ expect(formatter.output_hash[:files]).to eq([1, 2])
+ end
+ end
+
+ describe '#finished' do
+ let(:summary) { formatter.output_hash[:summary] }
+
+ it 'sets inspected file count in summary' do
+ expect(summary[:inspected_file_count]).to be_nil
+ formatter.finished(%w(/path/to/file1 /path/to/file2))
+ expect(summary[:inspected_file_count]).to eq(2)
+ end
+
+ it 'outputs #output_hash as JSON' do
+ formatter.finished(files)
+ json = output.string
+ restored_hash = JSON.parse(json, symbolize_names: true)
+ expect(restored_hash).to eq(formatter.output_hash)
+ end
+ end
+
+ describe '#hash_for_file' do
+ subject(:hash) { formatter.hash_for_file(file, offenses) }
+ let(:file) { File.expand_path('spec/spec_helper.rb') }
+ let(:offenses) { [double('offense1'), double('offense2')] }
+
+ it 'sets relative file path for :path key' do
+ expect(hash[:path]).to eq('spec/spec_helper.rb')
+ end
+
+ before do
+ count = 0
+ allow(formatter).to receive(:hash_for_offense) do
+ count += 1
+ end
+ end
+
+ it 'sets an array of #hash_for_offense values for :offenses key' do
+ expect(hash[:offenses]).to eq([1, 2])
+ end
+ end
+
+ describe '#hash_for_offense' do
+ subject(:hash) { formatter.hash_for_offense(offense) }
+
+ it 'sets Offense#severity value for :severity key' do
+ expect(hash[:severity]).to eq(:convention)
+ end
+
+ it 'sets Offense#message value for :message key' do
+ expect(hash[:message]).to eq('This is message')
+ end
+
+ it 'sets Offense#cop_name value for :cop_name key' do
+ expect(hash[:cop_name]).to eq('CopName')
+ end
+
+ it 'sets Offense#corrected? value for :corrected key' do
+ expect(hash[:corrected]).to be_true
+ end
+
+ before do
+ allow(formatter)
+ .to receive(:hash_for_location).and_return(location_hash)
+ end
+
+ let(:location_hash) { { line: 1, column: 2 } }
+
+ it 'sets value of #hash_for_location for :location key' do
+ expect(hash[:location]).to eq(location_hash)
+ end
+ end
+
+ describe '#hash_for_location' do
+ subject(:hash) { formatter.hash_for_location(offense) }
+
+ it 'sets line value for :line key' do
+ expect(hash[:line]).to eq(3)
+ end
+
+ it 'sets column value for :column key' do
+ expect(hash[:column]).to eq(6)
+ end
+
+ it 'sets length value for :length key' do
+ expect(hash[:length]).to eq(1)
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/formatter/offense_count_formatter_spec.rb b/spec/rubocop/formatter/offense_count_formatter_spec.rb
new file mode 100644
index 0000000..6469612
--- /dev/null
+++ b/spec/rubocop/formatter/offense_count_formatter_spec.rb
@@ -0,0 +1,77 @@
+# encoding: utf-8
+
+require 'spec_helper'
+require 'stringio'
+require 'tempfile'
+
+module Rubocop
+ module Formatter
+ describe OffenseCountFormatter do
+ subject(:formatter) { described_class.new(output) }
+ let(:output) { StringIO.new }
+
+ let(:files) do
+ %w(lib/rubocop.rb spec/spec_helper.rb bin/rubocop).map do |path|
+ File.expand_path(path)
+ end
+ end
+
+ let(:finish) { formatter.file_finished(files.first, offenses) }
+
+ describe '#file_finished' do
+ before { formatter.started(files) }
+
+ context 'when no offenses are detected' do
+ let(:offenses) { [] }
+ it 'shouldn\'t add to offense_counts' do
+ expect { finish }.to_not change { formatter.offense_counts }
+ end
+ end
+
+ context 'when any offenses are detected' do
+ let(:offenses) { [double('offense', cop_name: 'OffendedCop')] }
+ it 'should increment the count for the cop in offense_counts' do
+ expect { finish }.to change { formatter.offense_counts }
+ end
+ end
+ end
+
+ describe '#report_summary' do
+ context 'when an offense is detected' do
+ let(:cop_counts) { { 'OffendedCop' => 1 } }
+ it 'shows the cop and the offense count' do
+ formatter.report_summary(1, cop_counts)
+ expect(output.string).to include(
+ "\n1 OffendedCop\n--\n1 Total")
+ end
+ end
+ end
+
+ describe '#finished' do
+ context 'when there are many offenses' do
+ let(:offenses) do
+ %w(CopB CopA CopC CopC).map { |c| double('offense', cop_name: c) }
+ end
+
+ before do
+ formatter.started(files)
+ finish
+ end
+
+ it 'sorts by offense count first and then by cop name' do
+ formatter.finished(files)
+ expect(output.string).to eq(['',
+ '2 CopC',
+ '1 CopA',
+ '1 CopB',
+ '--',
+ '4 Total',
+ '',
+ ''].join("\n"))
+ end
+ end
+ end
+
+ end
+ end
+end
diff --git a/spec/rubocop/formatter/progress_formatter_spec.rb b/spec/rubocop/formatter/progress_formatter_spec.rb
new file mode 100644
index 0000000..2ad98e3
--- /dev/null
+++ b/spec/rubocop/formatter/progress_formatter_spec.rb
@@ -0,0 +1,182 @@
+# encoding: utf-8
+
+require 'spec_helper'
+require 'stringio'
+
+module Rubocop
+ describe Formatter::ProgressFormatter do
+ subject(:formatter) { described_class.new(output) }
+ let(:output) { StringIO.new }
+
+ let(:files) do
+ %w(lib/rubocop.rb spec/spec_helper.rb bin/rubocop).map do |path|
+ File.expand_path(path)
+ end
+ end
+
+ describe '#file_finished' do
+ before do
+ formatter.started(files)
+ formatter.file_started(files.first, {})
+ end
+
+ shared_examples 'calls #report_file_as_mark' do
+ it 'calls #report_as_with_mark' do
+ expect(formatter).to receive(:report_file_as_mark)
+ formatter.file_finished(files.first, offenses)
+ end
+ end
+
+ context 'when no offenses are detected' do
+ let(:offenses) { [] }
+ include_examples 'calls #report_file_as_mark'
+ end
+
+ context 'when any offenses are detected' do
+ let(:offenses) { [double('offense').as_null_object] }
+ include_examples 'calls #report_file_as_mark'
+ end
+ end
+
+ describe '#report_file_as_mark' do
+ before do
+ formatter.report_file_as_mark(files.first, offenses)
+ end
+
+ def offense_with_severity(severity)
+ source_buffer = Parser::Source::Buffer.new('test', 1)
+ source_buffer.source = "a\n"
+ Cop::Offense.new(severity,
+ Parser::Source::Range.new(source_buffer, 0, 1),
+ 'message',
+ 'CopName')
+ end
+
+ context 'when no offenses are detected' do
+ let(:offenses) { [] }
+
+ it 'prints "."' do
+ expect(output.string).to eq('.')
+ end
+ end
+
+ context 'when a refactor severity offense is detected' do
+ let(:offenses) { [offense_with_severity(:refactor)] }
+
+ it 'prints "R"' do
+ expect(output.string).to eq('R')
+ end
+ end
+
+ context 'when a refactor convention offense is detected' do
+ let(:offenses) { [offense_with_severity(:convention)] }
+
+ it 'prints "C"' do
+ expect(output.string).to eq('C')
+ end
+ end
+
+ context 'when different severity offenses are detected' do
+ let(:offenses) do
+ [
+ offense_with_severity(:refactor),
+ offense_with_severity(:error)
+ ]
+ end
+
+ it 'prints highest level mark' do
+ expect(output.string).to eq('E')
+ end
+ end
+ end
+
+ describe '#finished' do
+ before do
+ formatter.started(files)
+ end
+
+ context 'when any offenses are detected' do
+ before do
+ source_buffer = Parser::Source::Buffer.new('test', 1)
+ source = 9.times.map do |index|
+ "This is line #{index + 1}."
+ end
+ source_buffer.source = source.join("\n")
+ line_length = source[0].length + "\n".length
+
+ formatter.file_started(files[0], {})
+ formatter.file_finished(files[0], [
+ Cop::Offense.new(
+ :convention,
+ Parser::Source::Range.new(source_buffer,
+ line_length + 2,
+ line_length + 3),
+ 'foo',
+ 'Cop'
+ )
+ ])
+
+ formatter.file_started(files[1], {})
+ formatter.file_finished(files[1], [
+ ])
+
+ formatter.file_started(files[2], {})
+ formatter.file_finished(files[2], [
+ Cop::Offense.new(
+ :error,
+ Parser::Source::Range.new(source_buffer,
+ 4 * line_length + 1,
+ 4 * line_length + 2),
+ 'bar',
+ 'Cop'
+ ),
+ Cop::Offense.new(
+ :convention,
+ Parser::Source::Range.new(source_buffer,
+ 5 * line_length,
+ 5 * line_length + 1),
+ 'foo',
+ 'Cop'
+ )
+ ])
+ end
+
+ it 'reports all detected offenses for all failed files' do
+ formatter.finished(files)
+ expect(output.string).to include([
+ 'Offenses:',
+ '',
+ 'lib/rubocop.rb:2:3: C: foo',
+ 'This is line 2.',
+ ' ^',
+ 'bin/rubocop:5:2: E: bar',
+ 'This is line 5.',
+ ' ^',
+ 'bin/rubocop:6:1: C: foo',
+ 'This is line 6.',
+ '^'
+ ].join("\n"))
+ end
+ end
+
+ context 'when no offenses are detected' do
+ before do
+ files.each do |file|
+ formatter.file_started(file, {})
+ formatter.file_finished(file, [])
+ end
+ end
+
+ it 'does not report offenses' do
+ formatter.finished(files)
+ expect(output.string).not_to include('Offenses:')
+ end
+ end
+
+ it 'calls #report_summary' do
+ expect(formatter).to receive(:report_summary)
+ formatter.finished(files)
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/formatter/simple_text_formatter_spec.rb b/spec/rubocop/formatter/simple_text_formatter_spec.rb
new file mode 100644
index 0000000..0a02c8a
--- /dev/null
+++ b/spec/rubocop/formatter/simple_text_formatter_spec.rb
@@ -0,0 +1,123 @@
+# encoding: utf-8
+
+require 'spec_helper'
+require 'stringio'
+require 'tempfile'
+
+module Rubocop
+ module Formatter
+ describe SimpleTextFormatter do
+ subject(:formatter) { described_class.new(output) }
+ let(:output) { StringIO.new }
+
+ describe '#report_file' do
+ before do
+ formatter.report_file(file, [offense])
+ end
+
+ let(:file) { '/path/to/file' }
+
+ let(:offense) do
+ Cop::Offense.new(:convention, location,
+ 'This is a message.', 'CopName', corrected)
+ end
+
+ let(:location) do
+ source_buffer = Parser::Source::Buffer.new('test', 1)
+ source_buffer.source = "a\n"
+ Parser::Source::Range.new(source_buffer, 0, 1)
+ end
+
+ let(:corrected) { false }
+
+ context 'the file is under the current working directory' do
+ let(:file) { File.expand_path('spec/spec_helper.rb') }
+
+ it 'prints as relative path' do
+ expect(output.string).to include('== spec/spec_helper.rb ==')
+ end
+ end
+
+ context 'the file is outside of the current working directory' do
+ let(:file) do
+ tempfile = Tempfile.new('')
+ tempfile.close
+ File.expand_path(tempfile.path)
+ end
+
+ it 'prints as absolute path' do
+ expect(output.string).to include("== #{file} ==")
+ end
+ end
+
+ context 'when the offense is not corrected' do
+ let(:corrected) { false }
+
+ it 'prints message as-is' do
+ expect(output.string)
+ .to include(': This is a message.')
+ end
+ end
+
+ context 'when the offense is automatically corrected' do
+ let(:corrected) { true }
+
+ it 'prints [Corrected] along with message' do
+ expect(output.string)
+ .to include(': [Corrected] This is a message.')
+ end
+ end
+ end
+
+ describe '#report_summary' do
+ context 'when no files inspected' do
+ it 'handles pluralization correctly' do
+ formatter.report_summary(0, 0, 0)
+ expect(output.string).to eq(
+ "\n0 files inspected, no offenses detected\n")
+ end
+ end
+
+ context 'when a file inspected and no offenses detected' do
+ it 'handles pluralization correctly' do
+ formatter.report_summary(1, 0, 0)
+ expect(output.string).to eq(
+ "\n1 file inspected, no offenses detected\n")
+ end
+ end
+
+ context 'when a offense detected' do
+ it 'handles pluralization correctly' do
+ formatter.report_summary(1, 1, 0)
+ expect(output.string).to eq(
+ "\n1 file inspected, 1 offense detected\n")
+ end
+ end
+
+ context 'when 2 offenses detected' do
+ it 'handles pluralization correctly' do
+ formatter.report_summary(2, 2, 0)
+ expect(output.string).to eq(
+ "\n2 files inspected, 2 offenses detected\n")
+ end
+ end
+
+ context 'when an offense is corrected' do
+ it 'prints about correction' do
+ formatter.report_summary(1, 1, 1)
+ expect(output.string).to eq(
+ "\n1 file inspected, 1 offense detected, 1 offense corrected\n")
+ end
+ end
+
+ context 'when 2 offenses are corrected' do
+ it 'handles pluralization correctly' do
+ formatter.report_summary(1, 1, 2)
+ expect(output.string).to eq(
+ "\n1 file inspected, 1 offense detected, 2 offenses corrected\n")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/options_spec.rb b/spec/rubocop/options_spec.rb
new file mode 100644
index 0000000..da9a5a9
--- /dev/null
+++ b/spec/rubocop/options_spec.rb
@@ -0,0 +1,176 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Options, :isolated_environment do
+ include FileHelper
+
+ subject(:options) { described_class.new }
+
+ before(:each) do
+ $stdout = StringIO.new
+ $stderr = StringIO.new
+ end
+
+ after(:each) do
+ $stdout = STDOUT
+ $stderr = STDERR
+ end
+
+ def abs(path)
+ File.expand_path(path)
+ end
+
+ describe 'option' do
+ describe '-h/--help' do
+ it 'exits cleanly' do
+ expect { options.parse ['-h'] }.to exit_with_code(0)
+ expect { options.parse ['--help'] }.to exit_with_code(0)
+ end
+
+ it 'shows help text' do
+ begin
+ options.parse(['--help'])
+ rescue SystemExit # rubocop:disable HandleExceptions
+ end
+
+ expected_help = <<-END
+Usage: rubocop [options] [file1, file2, ...]
+ --only COP Run just one cop.
+ -c, --config FILE Specify configuration file.
+ --auto-gen-config Generate a configuration file acting as a
+ TODO list.
+ --force-exclusion Force excluding files specified in the
+ configuration `Exclude` even if they are
+ explicitly passed as arguments.
+ -f, --format FORMATTER Choose an output formatter. This option
+ can be specified multiple times to enable
+ multiple formatters at the same time.
+ [p]rogress (default)
+ [s]imple
+ [c]lang
+ [d]isabled cops via inline comments
+ [fu]ubar
+ [e]macs
+ [j]son
+ [fi]les
+ [o]ffenses
+ custom formatter class name
+ -o, --out FILE Write output to a file instead of STDOUT.
+ This option applies to the previously
+ specified --format, or the default format
+ if no format is specified.
+ -r, --require FILE Require Ruby file.
+ --fail-level SEVERITY Minimum severity for exit with error code.
+ --show-cops [cop1,cop2,...] Shows the given cops, or all cops by
+ default, and their configurations for the
+ current directory.
+ -d, --debug Display debug info.
+ -D, --display-cop-names Display cop names in offense messages.
+ -R, --rails Run extra Rails cops.
+ -l, --lint Run only lint cops.
+ -a, --auto-correct Auto-correct offenses.
+ -n, --no-color Disable color output.
+ -v, --version Display version.
+ -V, --verbose-version Display verbose version.
+ END
+
+ expect($stdout.string).to eq(expected_help)
+ end
+
+ it 'lists all builtin formatters' do
+ begin
+ options.parse(['--help'])
+ rescue SystemExit # rubocop:disable HandleExceptions
+ end
+
+ option_sections = $stdout.string.lines.slice_before(/^\s*-/)
+
+ format_section = option_sections.find do |lines|
+ lines.first =~ /^\s*-f/
+ end
+
+ formatter_keys = format_section.reduce([]) do |keys, line|
+ match = line.match(/^[ ]{39}(\[[a-z\]]+)/)
+ next keys unless match
+ keys << match.captures.first.gsub(/\[|\]/, '')
+ end.sort
+
+ expected_formatter_keys =
+ Rubocop::Formatter::FormatterSet::BUILTIN_FORMATTERS_FOR_KEYS
+ .keys.sort
+
+ expect(formatter_keys).to eq(expected_formatter_keys)
+ end
+ end
+
+ describe 'incompatible cli options' do
+ it 'fails with argument correct error' do
+ msg = 'Incompatible cli options: [:version, :verbose_version]'
+ expect { options.parse %w(-vV) }
+ .to raise_error(ArgumentError, msg)
+ end
+
+ it 'fails with argument correct error' do
+ msg = 'Incompatible cli options: [:version, :show_cops]'
+ expect { options.parse %w(-v --show-cops) }
+ .to raise_error(ArgumentError, msg)
+ end
+
+ it 'fails with argument correct error' do
+ msg = 'Incompatible cli options: [:verbose_version, :show_cops]'
+ expect { options.parse %w(-V --show-cops) }
+ .to raise_error(ArgumentError, msg)
+ end
+
+ it 'fails with argument correct error' do
+ msg = ['Incompatible cli options: [:version, :verbose_version,',
+ ' :show_cops]'].join
+ expect { options.parse %w(-vV --show-cops) }
+ .to raise_error(ArgumentError, msg)
+ end
+ end
+
+ describe '--only' do
+ it 'exits with error if an incorrect cop name is passed' do
+ expect { options.parse(%w(--only 123)) }
+ .to raise_error(ArgumentError, /Unrecognized cop name: 123./)
+ end
+ end
+
+ describe '--require' do
+ let(:required_file_path) { './path/to/required_file.rb' }
+
+ before do
+ create_file('example.rb', '# encoding: utf-8')
+
+ create_file(required_file_path, ['# encoding: utf-8',
+ "puts 'Hello from required file!'"])
+ end
+
+ it 'requires the passed path' do
+ options.parse(['--require', required_file_path, 'example.rb'])
+ expect($stdout.string).to start_with('Hello from required file!')
+ end
+ end
+ end
+
+ unless Rubocop::Version::STRING.start_with?('0')
+ describe '-e/--emacs option' do
+ it 'is dropped in RuboCop 1.0.0' do
+ # This spec can be removed once the option is dropped.
+ expect { options.parse(['--emacs']) }
+ .to raise_error(OptionParser::InvalidOption)
+ end
+ end
+
+ describe '-s/--silent option' do
+ it 'raises error in RuboCop 1.0.0' do
+ # This spec can be removed
+ # once Options#ignore_dropped_options is removed.
+ expect { options.parse(['--silent']) }
+ .to raise_error(OptionParser::InvalidOption)
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/path_util_spec.rb b/spec/rubocop/path_util_spec.rb
new file mode 100644
index 0000000..42c0990
--- /dev/null
+++ b/spec/rubocop/path_util_spec.rb
@@ -0,0 +1,42 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::PathUtil do
+ describe '#relative_path' do
+ pending 'builds paths relative to the current project by default'
+ it 'builds paths relative to PWD by default as a stop-gap' do
+ relative = File.join(Dir.pwd, 'relative')
+ expect(subject.relative_path(relative)).to eq('relative')
+ end
+
+ it 'supports custom base paths' do
+ expect(subject.relative_path('/foo/bar', '/foo')).to eq('bar')
+ end
+ end
+
+ describe '#match_path?' do
+ it 'matches strings to the basename' do
+ expect(subject.match_path?('file', '/dir/file')).to be_true
+ expect(subject.match_path?('file', '/dir/files')).to be_false
+ expect(subject.match_path?('dir', '/dir/file')).to be_false
+ end
+
+ it 'matches strings to the full path' do
+ expect(subject.match_path?('/dir/file', '/dir/file')).to be_true
+ expect(subject.match_path?('/dir/file', '/dir/dir/file')).to be_false
+ end
+
+ it 'matches glob expressions' do
+ expect(subject.match_path?('dir/*', 'dir/file')).to be_true
+ expect(subject.match_path?('dir/*', 'dir/sub/file')).to be_true
+ expect(subject.match_path?('dir/**/*', 'dir/sub/file')).to be_true
+ expect(subject.match_path?('sub/*', 'dir/sub/file')).to be_false
+ end
+
+ it 'matches regexps' do
+ expect(subject.match_path?(/^d.*e$/, 'dir/file')).to be_true
+ expect(subject.match_path?(/^d.*e$/, 'dir/filez')).to be_false
+ end
+ end
+end
diff --git a/spec/rubocop/processed_source_spec.rb b/spec/rubocop/processed_source_spec.rb
new file mode 100644
index 0000000..f4de34a
--- /dev/null
+++ b/spec/rubocop/processed_source_spec.rb
@@ -0,0 +1,114 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::ProcessedSource do
+ subject(:processed_source) do
+ described_class.new(
+ buffer,
+ double('ast'),
+ double('comments'),
+ double('tokens'),
+ diagnostics
+ )
+ end
+
+ let(:diagnostics) { double('diagnostics') }
+
+ let(:source) do
+ [
+ 'def some_method',
+ " puts 'foo'",
+ 'end',
+ 'some_method'
+ ].join("\n")
+ end
+
+ let(:buffer) do
+ buffer = Parser::Source::Buffer.new('(string)', 1)
+ buffer.source = source
+ buffer
+ end
+
+ describe '#lines' do
+ it 'is an array' do
+ expect(processed_source.lines).to be_a(Array)
+ end
+
+ it 'has same number of elements as line count' do
+ expect(processed_source.lines.size).to eq(4)
+ end
+
+ it 'contains lines as string without linefeed' do
+ first_line = processed_source.lines.first
+ expect(first_line).to eq('def some_method')
+ end
+ end
+
+ describe '#[]' do
+ context 'when an index is passed' do
+ it 'returns the line' do
+ expect(processed_source[2]).to eq('end')
+ end
+ end
+
+ context 'when a range is passed' do
+ it 'returns the array of lines' do
+ expect(processed_source[2..3]).to eq(%w(end some_method))
+ end
+ end
+
+ context 'when start index and length are passed' do
+ it 'returns the array of lines' do
+ expect(processed_source[2, 2]).to eq(%w(end some_method))
+ end
+ end
+ end
+
+ describe 'valid_syntax?' do
+ def create_diagnostics(level)
+ Parser::Diagnostic.new(level, :odd_hash, [], double('location'))
+ end
+
+ let(:diagnostics) do
+ [create_diagnostics(level)]
+ end
+
+ context 'when the source has diagnostic with error level' do
+ let(:level) { :error }
+
+ it 'returns false' do
+ expect(processed_source.valid_syntax?).to be_false
+ end
+ end
+
+ context 'when the source has diagnostic with fatal level' do
+ let(:level) { :fatal }
+
+ it 'returns false' do
+ expect(processed_source.valid_syntax?).to be_false
+ end
+ end
+
+ context 'when the source has diagnostic with warning level' do
+ let(:level) { :warning }
+
+ it 'returns true' do
+ expect(processed_source.valid_syntax?).to be_true
+ end
+ end
+
+ context 'when the source has diagnostics with error and warning level' do
+ let(:diagnostics) do
+ [
+ create_diagnostics(:error),
+ create_diagnostics(:warning)
+ ]
+ end
+
+ it 'returns false' do
+ expect(processed_source.valid_syntax?).to be_false
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/source_parser_spec.rb b/spec/rubocop/source_parser_spec.rb
new file mode 100644
index 0000000..59daf88
--- /dev/null
+++ b/spec/rubocop/source_parser_spec.rb
@@ -0,0 +1,85 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::SourceParser, :isolated_environment do
+ include FileHelper
+
+ describe '.parse_file' do
+ let(:file) { 'example.rb' }
+
+ let(:source) do
+ [
+ '# encoding: utf-8',
+ '',
+ 'def some_method',
+ " puts 'foo'",
+ 'end',
+ '',
+ 'some_method'
+ ]
+ end
+
+ before do
+ create_file(file, source)
+ end
+
+ let(:processed_source) do
+ described_class.parse_file(file)
+ end
+
+ it 'returns ProcessedSource' do
+ expect(processed_source).to be_a(Rubocop::ProcessedSource)
+ end
+
+ describe 'the returned processed source' do
+ it 'has the root node of AST' do
+ expect(processed_source.ast).to be_a(Parser::AST::Node)
+ end
+
+ it 'has an array of comments' do
+ expect(processed_source.comments).to be_a(Array)
+ expect(processed_source.comments.first)
+ .to be_a(Parser::Source::Comment)
+ end
+
+ it 'has an array of tokens' do
+ expect(processed_source.tokens).to be_a(Array)
+ expect(processed_source.tokens.first).to be_a(Rubocop::Token)
+ end
+
+ it 'has a source buffer' do
+ expect(processed_source.buffer).to be_a(Parser::Source::Buffer)
+ end
+
+ context 'when the source is valid' do
+ it 'does not have diagnostics' do
+ expect(processed_source.diagnostics).to be_a(Array)
+ expect(processed_source.diagnostics).to be_empty
+ end
+ end
+
+ context 'when the source has invalid syntax' do
+ let(:source) do
+ [
+ '# encoding: utf-8',
+ '',
+ 'def some_method',
+ " puts 'foo'",
+ 'end',
+ '',
+ 'some_method',
+ '',
+ '?invalid_syntax'
+ ]
+ end
+
+ it 'has an array of diagnostics' do
+ expect(processed_source.diagnostics).to be_a(Array)
+ expect(processed_source.diagnostics.first)
+ .to be_a(Parser::Diagnostic)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/target_finder_spec.rb b/spec/rubocop/target_finder_spec.rb
new file mode 100644
index 0000000..ee3474c
--- /dev/null
+++ b/spec/rubocop/target_finder_spec.rb
@@ -0,0 +1,211 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::TargetFinder, :isolated_environment do
+ include FileHelper
+
+ subject(:target_finder) do
+ described_class.new(config_store, options)
+ end
+ let(:config_store) { Rubocop::ConfigStore.new }
+ let(:options) { { force_exclusion: force_exclusion, debug: debug } }
+ let(:force_exclusion) { false }
+ let(:debug) { false }
+
+ before do
+ create_file('dir1/ruby1.rb', '# encoding: utf-8')
+ create_file('dir1/ruby2.rb', '# encoding: utf-8')
+ create_file('dir1/file.txt', '# encoding: utf-8')
+ create_file('dir1/file', '# encoding: utf-8')
+ create_file('dir1/executable', '#!/usr/bin/env ruby')
+ create_file('dir2/ruby3.rb', '# encoding: utf-8')
+ end
+
+ describe '#find' do
+ let(:found_files) { target_finder.find(args) }
+ let(:found_basenames) { found_files.map { |f| File.basename(f) } }
+ let(:args) { [] }
+
+ it 'returns absolute paths' do
+ expect(found_files).not_to be_empty
+ found_files.each do |file|
+ expect(file).to start_with('/')
+ end
+ end
+
+ context 'when no argument is passed' do
+ let(:args) { [] }
+
+ it 'finds files under the current directory' do
+ Dir.chdir('dir1') do
+ expect(found_files).not_to be_empty
+ found_files.each do |file|
+ expect(file).to include('/dir1/')
+ expect(file).not_to include('/dir2/')
+ end
+ end
+ end
+ end
+
+ context 'when a directory path is passed' do
+ let(:args) { ['../dir2'] }
+
+ it 'finds files under the specified directory' do
+ Dir.chdir('dir1') do
+ expect(found_files).not_to be_empty
+ found_files.each do |file|
+ expect(file).to include('/dir2/')
+ expect(file).not_to include('/dir1/')
+ end
+ end
+ end
+ end
+
+ context 'when a file is passed' do
+ let(:args) { ['dir2/file'] }
+
+ it 'picks the file' do
+ expect(found_basenames).to eq(['file'])
+ end
+ end
+
+ context 'when a pattern is passed' do
+ let(:args) { ['dir1/*2.rb'] }
+
+ it 'finds files which match the pattern' do
+ expect(found_basenames).to eq(['ruby2.rb'])
+ end
+ end
+
+ context 'when same paths are passed' do
+ let(:args) { %w(dir1 dir1) }
+
+ it 'does not return duplicated file paths' do
+ count = found_basenames.count { |f| f == 'ruby1.rb' }
+ expect(count).to eq(1)
+ end
+ end
+
+ context 'when some paths are specified in the configuration Exclude ' \
+ 'and they are explicitly passed as arguments' do
+ before do
+ create_file('.rubocop.yml', [
+ 'AllCops:',
+ ' Exclude:',
+ ' - dir1/ruby1.rb',
+ " - 'dir2/*'"
+ ])
+
+ create_file('dir1/.rubocop.yml', [
+ 'AllCops:',
+ ' Exclude:',
+ ' - executable'
+ ])
+ end
+
+ let(:args) do
+ ['dir1/ruby1.rb', 'dir1/ruby2.rb', 'dir1/exe*', 'dir2/ruby3.rb']
+ end
+
+ context 'normally' do
+ it 'does not exludes them' do
+ expect(found_basenames)
+ .to eq(['ruby1.rb', 'ruby2.rb', 'executable', 'ruby3.rb'])
+ end
+ end
+
+ context "when it's forced to adhere file exclusion configuration" do
+ let(:force_exclusion) { true }
+
+ it 'exludes them' do
+ expect(found_basenames).to eq(['ruby2.rb'])
+ end
+ end
+ end
+ end
+
+ describe '#target_files_in_dir' do
+ let(:found_files) { target_finder.target_files_in_dir(base_dir) }
+ let(:found_basenames) { found_files.map { |f| File.basename(f) } }
+ let(:base_dir) { '.' }
+
+ it 'picks files with extension .rb' do
+ rb_file_count = found_files.count { |f| f.end_with?('.rb') }
+ expect(rb_file_count).to eq(3)
+ end
+
+ it 'picks ruby executable files with no extension' do
+ expect(found_basenames).to include('executable')
+ end
+
+ it 'does not pick files with no extension and no ruby shebang' do
+ expect(found_basenames).not_to include('file')
+ end
+
+ it 'picks ruby executable files with no extension' do
+ expect(found_basenames).to include('executable')
+ end
+
+ it 'does not pick directories' do
+ found_basenames = found_files.map { |f| File.basename(f) }
+ expect(found_basenames).not_to include('dir1')
+ end
+
+ it 'picks files specified to be included in config' do
+ config = double('config')
+ allow(config).to receive(:file_to_include?) do |file|
+ File.basename(file) == 'file'
+ end
+ allow(config).to receive(:file_to_exclude?).and_return(false)
+ allow(config_store).to receive(:for).and_return(config)
+
+ expect(found_basenames).to include('file')
+ end
+
+ it 'does not pick files specified to be excluded in config' do
+ config = double('config').as_null_object
+ allow(config).to receive(:file_to_include?).and_return(false)
+ allow(config).to receive(:file_to_exclude?) do |file|
+ File.basename(file) == 'ruby2.rb'
+ end
+ allow(config_store).to receive(:for).and_return(config)
+
+ expect(found_basenames).not_to include('ruby2.rb')
+ end
+
+ context 'when an exception is raised while reading file' do
+ around do |example|
+ original_stderr = $stderr
+ $stderr = StringIO.new
+ begin
+ example.run
+ ensure
+ $stderr = original_stderr
+ end
+ end
+
+ before do
+ allow_any_instance_of(File).to receive(:readline).and_raise(EOFError)
+ end
+
+ context 'and debug mode is enabled' do
+ let(:debug) { true }
+
+ it 'outputs error message' do
+ found_files
+ expect($stderr.string).to include('Unprocessable file')
+ end
+ end
+
+ context 'and debug mode is disabled' do
+ let(:debug) { false }
+
+ it 'outputs nothing' do
+ found_files
+ expect($stderr.string).to be_empty
+ end
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/token_spec.rb b/spec/rubocop/token_spec.rb
new file mode 100644
index 0000000..d818a8b
--- /dev/null
+++ b/spec/rubocop/token_spec.rb
@@ -0,0 +1,25 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Rubocop::Token do
+ describe '.from_parser_token' do
+ subject(:token) { described_class.from_parser_token(parser_token) }
+ let(:parser_token) { [type, [text, range]] }
+ let(:type) { :kDEF }
+ let(:text) { 'def' }
+ let(:range) { double('range') }
+
+ it "sets parser token's type to rubocop token's type" do
+ expect(token.type).to eq(type)
+ end
+
+ it "sets parser token's text to rubocop token's text" do
+ expect(token.text).to eq(text)
+ end
+
+ it "sets parser token's range to rubocop token's pos" do
+ expect(token.pos).to eq(range)
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
new file mode 100644
index 0000000..c8c26be
--- /dev/null
+++ b/spec/spec_helper.rb
@@ -0,0 +1,149 @@
+# encoding: utf-8
+
+if ENV['TRAVIS'] && RUBY_ENGINE == 'jruby'
+ # Force JRuby not to select working directory
+ # as temporary directory on Travis CI.
+ # https://github.com/jruby/jruby/issues/405
+ require 'fileutils'
+ tmp_dir = ENV['TMPDIR'] || ENV['TMP'] || ENV['TEMP'] ||
+ Etc.systmpdir || '/tmp'
+ non_world_writable_tmp_dir = File.join(tmp_dir, 'rubocop')
+ FileUtils.makedirs(non_world_writable_tmp_dir, mode: 0700)
+ ENV['TMPDIR'] = non_world_writable_tmp_dir
+end
+
+if ENV['TRAVIS'] || ENV['COVERAGE']
+ require 'simplecov'
+
+ if ENV['TRAVIS']
+ require 'coveralls'
+ SimpleCov.formatter = Coveralls::SimpleCov::Formatter
+ end
+
+ SimpleCov.start do
+ add_filter '/spec/'
+ add_filter '/vendor/bundle/'
+ end
+end
+
+$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
+$LOAD_PATH.unshift(File.dirname(__FILE__))
+require 'rspec'
+require 'rubocop'
+require 'rubocop/cli'
+
+# disable colors in specs
+Rainbow.enabled = false
+
+module ExitCodeMatchers
+ RSpec::Matchers.define :exit_with_code do |code|
+ actual = nil
+ match do |block|
+ begin
+ block.call
+ rescue SystemExit => e
+ actual = e.status
+ end
+ actual && actual == code
+ end
+ failure_message_for_should do |block|
+ "expected block to call exit(#{code}) but exit" +
+ (actual.nil? ? ' not called' : "(#{actual}) was called")
+ end
+ failure_message_for_should_not do |block|
+ "expected block not to call exit(#{code})"
+ end
+ description do
+ "expect block to call exit(#{code})"
+ end
+ end
+end
+
+RSpec.configure do |config|
+ config.treat_symbols_as_metadata_keys_with_true_values = true
+
+ # These two settings work together to allow you to limit a spec run
+ # to individual examples or groups you care about by tagging them with
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
+ # get run.
+ config.filter_run :focus
+ config.run_all_when_everything_filtered = true
+
+ broken_filter = lambda do |v|
+ v.is_a?(Symbol) ? RUBY_ENGINE == v.to_s : v
+ end
+ config.filter_run_excluding ruby: ->(v) { !RUBY_VERSION.start_with?(v.to_s) }
+ config.filter_run_excluding broken: broken_filter
+
+ config.expect_with :rspec do |c|
+ c.syntax = :expect # disables `should`
+ end
+
+ config.mock_with :rspec do |c|
+ c.syntax = :expect # disables `should_receive` and `stub`
+ end
+
+ config.include(ExitCodeMatchers)
+end
+
+def inspect_source_file(cop, source)
+ Tempfile.open('tmp') { |f| inspect_source(cop, source, f) }
+end
+
+def inspect_source(cop, source, file = nil)
+ Rubocop::Formatter::DisabledConfigFormatter.config_to_allow_offenses = {}
+ processed_source = parse_source(source, file)
+ fail 'Error parsing example code' unless processed_source.valid_syntax?
+ _investigate(cop, processed_source)
+end
+
+def parse_source(source, file = nil)
+ source = source.join($RS) if source.is_a?(Array)
+ if file.is_a? String
+ Rubocop::SourceParser.parse(source, file)
+ elsif file
+ file.write(source)
+ file.rewind
+ Rubocop::SourceParser.parse(source, file.path)
+ else
+ Rubocop::SourceParser.parse(source)
+ end
+end
+
+def autocorrect_source_file(cop, source)
+ Tempfile.open('tmp') { |f| autocorrect_source(cop, source, f) }
+end
+
+def autocorrect_source(cop, source, file = nil)
+ cop.instance_variable_get(:@options)[:auto_correct] = true
+ processed_source = parse_source(source, file)
+ _investigate(cop, processed_source)
+
+ corrector =
+ Rubocop::Cop::Corrector.new(processed_source.buffer, cop.corrections)
+ corrector.rewrite
+end
+
+def _investigate(cop, processed_source)
+ commissioner = Rubocop::Cop::Commissioner.new([cop], raise_error: true)
+ commissioner.investigate(processed_source)
+ commissioner
+end
+
+module Rubocop
+ module Cop
+ class Cop
+ def messages
+ offenses.sort.map(&:message)
+ end
+
+ def highlights
+ offenses.sort.map { |o| o.location.source }
+ end
+ end
+ end
+end
+
+# Requires supporting files with custom matchers and macros, etc,
+# in ./support/ and its subdirectories.
+Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
diff --git a/spec/support/ast_helper.rb b/spec/support/ast_helper.rb
new file mode 100644
index 0000000..591b007
--- /dev/null
+++ b/spec/support/ast_helper.rb
@@ -0,0 +1,15 @@
+# encoding: utf-8
+
+module ASTHelper
+ def scan_node(node, options = {}, &block)
+ yield node if options[:include_origin_node]
+
+ node.children.each do |child|
+ next unless child.is_a?(Parser::AST::Node)
+ yield child
+ scan_node(child, &block)
+ end
+
+ nil
+ end
+end
diff --git a/spec/support/file_helper.rb b/spec/support/file_helper.rb
new file mode 100644
index 0000000..af71d27
--- /dev/null
+++ b/spec/support/file_helper.rb
@@ -0,0 +1,21 @@
+# encoding: utf-8
+
+require 'fileutils'
+
+module FileHelper
+ def create_file(file_path, content)
+ file_path = File.expand_path(file_path)
+
+ dir_path = File.dirname(file_path)
+ FileUtils.makedirs dir_path unless File.exist?(dir_path)
+
+ File.open(file_path, 'w') do |file|
+ case content
+ when String
+ file.puts content
+ when Array
+ file.puts content.join("\n")
+ end
+ end
+ end
+end
diff --git a/spec/support/isolated_environment.rb b/spec/support/isolated_environment.rb
new file mode 100644
index 0000000..58eaee3
--- /dev/null
+++ b/spec/support/isolated_environment.rb
@@ -0,0 +1,34 @@
+# encoding: utf-8
+
+require 'tmpdir'
+require 'fileutils'
+
+shared_context 'isolated environment', :isolated_environment do
+ around do |example|
+ Dir.mktmpdir do |tmpdir|
+ original_home = ENV['HOME']
+
+ # Make sure to expand all symlinks in the path first. Otherwise we may
+ # get mismatched pathnames when loading config files later on.
+ tmpdir = File.realpath(tmpdir)
+
+ # Make upwards search for .rubocop.yml files stop at this directory.
+ Rubocop::ConfigLoader.root_level = tmpdir
+
+ begin
+ virtual_home = File.expand_path(File.join(tmpdir, 'home'))
+ Dir.mkdir(virtual_home)
+ ENV['HOME'] = virtual_home
+
+ working_dir = File.join(tmpdir, 'work')
+ Dir.mkdir(working_dir)
+
+ Dir.chdir(working_dir) do
+ example.run
+ end
+ ensure
+ ENV['HOME'] = original_home
+ end
+ end
+ end
+end
diff --git a/spec/support/mri_syntax_checker.rb b/spec/support/mri_syntax_checker.rb
new file mode 100644
index 0000000..552f0c4
--- /dev/null
+++ b/spec/support/mri_syntax_checker.rb
@@ -0,0 +1,73 @@
+# encoding: utf-8
+
+require 'open3'
+
+# The reincarnation of syntax cop :)
+module MRISyntaxChecker
+ module_function
+
+ def offenses_for_source(source, fake_cop_name = 'Syntax', grep_message = nil)
+ if source.is_a?(Array)
+ source_lines = source
+ source = source_lines.join("\n")
+ else
+ source_lines = source.each_line.to_a
+ end
+
+ source_buffer = Parser::Source::Buffer.new('test', 1)
+ source_buffer.source = source
+
+ offenses = check_syntax(source).each_line.map do |line|
+ check_line(line, source_lines, source_buffer, fake_cop_name,
+ grep_message)
+ end
+
+ offenses.compact
+ end
+
+ def check_line(line, source_lines, source_buffer, fake_cop_name,
+ grep_message)
+ line_number, severity, message = process_line(line)
+ return unless line_number
+ return if grep_message && !message.include?(grep_message)
+ begin_pos = source_lines[0...(line_number - 1)].reduce(0) do |a, e|
+ a + e.length + "\n".length
+ end
+ Rubocop::Cop::Offense.new(severity,
+ Parser::Source::Range.new(source_buffer,
+ begin_pos,
+ begin_pos + 1),
+ message.capitalize,
+ fake_cop_name)
+ end
+
+ def check_syntax(source)
+ fail 'Must be running with MRI' unless RUBY_ENGINE == 'ruby'
+
+ stdin, stderr, thread = nil
+
+ # It's extremely important to run the syntax check in a
+ # clean environment - otherwise it will be extremely slow.
+ if defined? Bundler
+ Bundler.with_clean_env do
+ stdin, _, stderr, thread = Open3.popen3('ruby', '-cw')
+ end
+ else
+ stdin, _, stderr, thread = Open3.popen3('ruby', '-cw')
+ end
+
+ stdin.write(source)
+ stdin.close
+ thread.join
+
+ stderr.read
+ end
+
+ def process_line(line)
+ match_data = line.match(/.+:(\d+): (warning: )?(.+)/)
+ return nil unless match_data
+ line_number, warning, message = match_data.captures
+ severity = warning ? :warning : :error
+ [line_number.to_i, severity, message]
+ end
+end
diff --git a/spec/support/shared_context.rb b/spec/support/shared_context.rb
new file mode 100644
index 0000000..a23bf94
--- /dev/null
+++ b/spec/support/shared_context.rb
@@ -0,0 +1,22 @@
+# encoding: utf-8
+
+# `cop_config` must be declared with #let.
+shared_context 'config', :config do
+ let(:config) do
+ # Module#<
+ unless described_class < Rubocop::Cop::Cop
+ fail '`config` must be used in `describe SomeCopClass do .. end`'
+ end
+
+ unless cop_config.is_a?(Hash)
+ fail '`cop_config` must be declared with #let'
+ end
+
+ cop_name = described_class.cop_name
+ hash = {
+ cop_name =>
+ Rubocop::ConfigLoader.default_configuration[cop_name].merge(cop_config)
+ }
+ Rubocop::Config.new(hash, "#{Dir.pwd}/.rubocop.yml")
+ end
+end
diff --git a/spec/support/shared_examples.rb b/spec/support/shared_examples.rb
new file mode 100644
index 0000000..32abbfe
--- /dev/null
+++ b/spec/support/shared_examples.rb
@@ -0,0 +1,33 @@
+# encoding: utf-8
+
+# `cop` and `source` must be declared with #let.
+
+shared_examples_for 'accepts' do
+ it 'accepts' do
+ inspect_source(cop, source)
+ expect(cop.offenses).to be_empty
+ end
+end
+
+shared_examples_for 'mimics MRI 2.1' do |grep_mri_warning|
+ if RUBY_ENGINE == 'ruby' && RUBY_VERSION.start_with?('2.1')
+ it "mimics MRI #{RUBY_VERSION} built-in syntax checking" do
+ inspect_source(cop, source)
+ offenses_by_mri = MRISyntaxChecker.offenses_for_source(
+ source, cop.name, grep_mri_warning
+ )
+
+ # Compare objects before comparing counts for clear failure output.
+ cop.offenses.each_with_index do |offense_by_cop, index|
+ offense_by_mri = offenses_by_mri[index]
+ # Exclude column attribute since MRI does not
+ # output column number.
+ [:severity, :line, :cop_name].each do |a|
+ expect(offense_by_cop.send(a)).to eq(offense_by_mri.send(a))
+ end
+ end
+
+ expect(cop.offenses.count).to eq(offenses_by_mri.count)
+ end
+ end
+end
diff --git a/spec/support/statement_modifier_helper.rb b/spec/support/statement_modifier_helper.rb
new file mode 100644
index 0000000..996268b
--- /dev/null
+++ b/spec/support/statement_modifier_helper.rb
@@ -0,0 +1,41 @@
+# encoding: utf-8
+
+module StatementModifierHelper
+ def check_empty(cop, keyword)
+ inspect_source(cop, ["#{keyword} cond",
+ 'end'])
+ expect(cop.offenses).to be_empty
+ end
+
+ def check_really_short(cop, keyword)
+ inspect_source(cop, ["#{keyword} a",
+ ' b',
+ 'end'])
+ expect(cop.messages).to eq(
+ ["Favor modifier `#{keyword}` usage when having a single-line body."])
+ expect(cop.offenses.map { |o| o.location.source }).to eq([keyword])
+ end
+
+ def check_too_long(cop, keyword)
+ # This statement is one character too long to fit.
+ condition = 'a' * (40 - keyword.length)
+ body = 'b' * 36
+ expect(" #{body} #{keyword} #{condition}".length).to eq(80)
+
+ inspect_source(cop,
+ [" #{keyword} #{condition}",
+ " #{body}",
+ ' end'])
+
+ expect(cop.offenses).to be_empty
+ end
+
+ def check_short_multiline(cop, keyword)
+ inspect_source(cop,
+ ["#{keyword} ENV['COVERAGE']",
+ " require 'simplecov'",
+ ' SimpleCov.start',
+ 'end'])
+ expect(cop.messages).to be_empty
+ end
+end
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/rubocop.git
More information about the Pkg-ruby-extras-commits
mailing list