├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .rubocop.yml ├── CHANGELOG.md ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── Rakefile ├── config └── default.yml ├── lib ├── rubocop-disable_syntax.rb └── rubocop │ ├── cop │ └── style │ │ └── disable_syntax.rb │ ├── disable_syntax.rb │ └── disable_syntax │ ├── plugin.rb │ └── version.rb ├── rubocop-disable_syntax.gemspec └── test ├── disable_syntax_test.rb └── test_helper.rb /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | test: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v3 9 | - uses: ruby/setup-ruby@v1 10 | with: 11 | ruby-version: 3.2 12 | bundler-cache: true 13 | - run: bundle exec rake 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | plugins: 2 | - rubocop-minitest 3 | - rubocop-disable_syntax 4 | 5 | AllCops: 6 | TargetRubyVersion: 3.2 7 | NewCops: enable 8 | SuggestExtensions: false 9 | 10 | Style/StringLiterals: 11 | EnforcedStyle: double_quotes 12 | 13 | Style/Documentation: 14 | Enabled: false 15 | 16 | Style/IfUnlessModifier: 17 | Enabled: false 18 | 19 | Style/GuardClause: 20 | Enabled: false 21 | 22 | Style/NegatedIf: 23 | Enabled: false 24 | 25 | Style/SymbolArray: 26 | Enabled: false 27 | 28 | Style/WordArray: 29 | Enabled: false 30 | 31 | Style/DisableSyntax: 32 | DisableSyntax: 33 | - unless 34 | - safe_navigation 35 | - endless_methods 36 | - arguments_forwarding 37 | - numbered_parameters 38 | - shorthand_hash_syntax 39 | - and_or_not 40 | - until 41 | - percent_literals 42 | 43 | Layout/EmptyLinesAroundAccessModifier: 44 | EnforcedStyle: only_before 45 | 46 | Layout/IndentationConsistency: 47 | EnforcedStyle: indented_internal_methods 48 | 49 | Naming/FileName: 50 | Exclude: 51 | - lib/rubocop-disable_syntax.rb 52 | 53 | Gemspec/RequireMFA: 54 | Enabled: false 55 | 56 | Bundler/OrderedGems: 57 | Enabled: false 58 | 59 | Metrics: 60 | Enabled: false 61 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## master (unreleased) 2 | 3 | ## 0.2.0 (2025-03-23) 4 | 5 | - Drop support for ruby < 3.2 6 | - Pluginify the gem (drop support for rubocop < 1.72) 7 | 8 | ## 0.1.1 (2023-10-13) 9 | 10 | - Mark cop as unsafe to autocorrect 11 | 12 | ## 0.1.0 (2023-09-26) 13 | 14 | - First release 15 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | # Specify your gem's dependencies in rubocop-disable_syntax.gemspec 6 | gemspec 7 | 8 | gem "rake", "~> 13.0" 9 | gem "minitest", "~> 5.0" 10 | gem "rubocop-minitest" 11 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | rubocop-disable_syntax (0.2.0) 5 | lint_roller 6 | rubocop (>= 1.72.0) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | ast (2.4.3) 12 | json (2.10.2) 13 | language_server-protocol (3.17.0.4) 14 | lint_roller (1.1.0) 15 | minitest (5.25.5) 16 | parallel (1.26.3) 17 | parser (3.3.7.2) 18 | ast (~> 2.4.1) 19 | racc 20 | racc (1.8.1) 21 | rainbow (3.1.1) 22 | rake (13.2.1) 23 | regexp_parser (2.10.0) 24 | rubocop (1.74.0) 25 | json (~> 2.3) 26 | language_server-protocol (~> 3.17.0.2) 27 | lint_roller (~> 1.1.0) 28 | parallel (~> 1.10) 29 | parser (>= 3.3.0.2) 30 | rainbow (>= 2.2.2, < 4.0) 31 | regexp_parser (>= 2.9.3, < 3.0) 32 | rubocop-ast (>= 1.38.0, < 2.0) 33 | ruby-progressbar (~> 1.7) 34 | unicode-display_width (>= 2.4.0, < 4.0) 35 | rubocop-ast (1.41.0) 36 | parser (>= 3.3.7.2) 37 | rubocop-minitest (0.37.1) 38 | lint_roller (~> 1.1) 39 | rubocop (>= 1.72.1, < 2.0) 40 | rubocop-ast (>= 1.38.0, < 2.0) 41 | ruby-progressbar (1.13.0) 42 | unicode-display_width (3.1.4) 43 | unicode-emoji (~> 4.0, >= 4.0.4) 44 | unicode-emoji (4.0.4) 45 | 46 | PLATFORMS 47 | x86_64-darwin-21 48 | x86_64-linux 49 | 50 | DEPENDENCIES 51 | minitest (~> 5.0) 52 | rake (~> 13.0) 53 | rubocop-disable_syntax! 54 | rubocop-minitest 55 | 56 | BUNDLED WITH 57 | 2.4.14 58 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 fatkodima 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rubocop-disable_syntax 2 | 3 | [![Build Status](https://github.com/fatkodima/rubocop-disable_syntax/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/fatkodima/rubocop-disable_syntax/actions/workflows/ci.yml) 4 | 5 | `rubocop-disable_syntax` is a [RuboCop](https://github.com/rubocop/rubocop) plugin that allows to disable some unfavorite ruby syntax such as `unless`, safe navigation etc. 6 | 7 | ## Requirements 8 | 9 | - ruby 3.2+ 10 | - rubocop 1.72+ 11 | 12 | ## Installation 13 | 14 | Add this line to your application's Gemfile: 15 | 16 | ```ruby 17 | gem 'rubocop-disable_syntax', group: :development, require: false 18 | ``` 19 | 20 | And then run: 21 | 22 | ```sh 23 | $ bundle install 24 | ``` 25 | 26 | ## Usage 27 | 28 | You need to tell RuboCop to load the `rubocop-disable_syntax` extension. 29 | 30 | Put this into your `.rubocop.yml`. 31 | 32 | ```yaml 33 | plugins: 34 | - rubocop-disable_syntax 35 | ``` 36 | 37 | **Note**: The plugin system is supported in RuboCop 1.72+. In earlier versions, use `require` instead of `plugins`. 38 | 39 | All the ruby syntax features are enabled by default and so this gem acts as a no-op. You need to manually configure 40 | which ruby features you want to disable: 41 | 42 | ```yml 43 | Style/DisableSyntax: 44 | DisableSyntax: 45 | - unless 46 | - ternary 47 | - safe_navigation 48 | - endless_methods 49 | - arguments_forwarding 50 | - numbered_parameters 51 | - pattern_matching 52 | - shorthand_hash_syntax 53 | - and_or_not 54 | - until 55 | - percent_literals 56 | ``` 57 | 58 | * `unless` - no `unless` keyword 59 | * `ternary` - no ternary operator (`condition ? foo : bar`) 60 | * `safe_navigation` - no safe navigation operator (`&.`) 61 | * `endless_methods` - no endless methods (`def foo = 1`) 62 | * `arguments_forwarding` - no arguments forwarding (`foo(...)`, `foo(*)`, `foo(**)`, `foo(&)`) 63 | * `numbered_parameters` - no numbered parameters (`foo.each { puts _1 }`) 64 | * `pattern_matching` - no pattern matching 65 | * `shorthand_hash_syntax` - no shorthand hash syntax (`{ x:, y: }`) 66 | * `and_or_not` - no `and`/`or`/`not` keywords (should use `&&`/`||`/`!` instead) 67 | * `until` - no `until` keyword 68 | * `percent_literals` - no any `%` style literals (`%w[foo bar]`, `%i[foo bar]`, `%q("str")`, `%r{/regex/}`) 69 | 70 | ## Development 71 | 72 | After checking out the repo, run `bundle install` to install dependencies. Then, run `rake` to run the linter and tests. 73 | 74 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org). 75 | 76 | ## Contributing 77 | 78 | Bug reports and pull requests are welcome on GitHub at https://github.com/fatkodima/rubocop-disable_syntax. 79 | 80 | ## License 81 | 82 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 83 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/gem_tasks" 4 | require "rake/testtask" 5 | 6 | Rake::TestTask.new(:test) do |t| 7 | t.libs << "test" 8 | t.libs << "lib" 9 | t.test_files = FileList["test/**/*_test.rb"] 10 | end 11 | 12 | require "rubocop/rake_task" 13 | RuboCop::RakeTask.new 14 | 15 | task default: [:rubocop, :test] 16 | -------------------------------------------------------------------------------- /config/default.yml: -------------------------------------------------------------------------------- 1 | Style/DisableSyntax: 2 | Description: 'Forbid some unfavorite ruby syntax, such as `unless`, safe navigation etc.' 3 | Enabled: true 4 | SafeAutoCorrect: false 5 | VersionAdded: '0.1' 6 | SupportedDisableSyntax: 7 | - unless 8 | - ternary 9 | - safe_navigation 10 | - endless_methods 11 | - arguments_forwarding 12 | - numbered_parameters 13 | - pattern_matching 14 | - shorthand_hash_syntax 15 | - and_or_not 16 | - until 17 | - percent_literals 18 | DisableSyntax: [] 19 | -------------------------------------------------------------------------------- /lib/rubocop-disable_syntax.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rubocop" 4 | 5 | require_relative "rubocop/disable_syntax" 6 | require_relative "rubocop/disable_syntax/version" 7 | require_relative "rubocop/disable_syntax/plugin" 8 | require_relative "rubocop/cop/style/disable_syntax" 9 | -------------------------------------------------------------------------------- /lib/rubocop/cop/style/disable_syntax.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Style 6 | # Forbid some unfavorite ruby syntax, such as `unless`, safe navigation etc. 7 | # 8 | # @safety 9 | # Autocorrection is unsafe because there is a different operator precedence 10 | # between logical operators (`&&`, `||` and `!`) and semantic operators (`and`, `or` and `not`), 11 | # and that might change the behavior. 12 | # 13 | # You can set syntax you want to disable via `DisableSyntax`. 14 | # Available are: 'unless', 'ternary', 'safe_navigation', 'endless_methods', 15 | # 'arguments_forwarding', 'numbered_parameters', 'pattern_matching', 16 | # 'shorthand_hash_syntax', 'and_or_not', 'until', and 'percent_literals'. 17 | # 18 | # @example DisableSyntax: ['unless'] 19 | # # bad 20 | # do_something unless condition 21 | # 22 | # # good 23 | # do_something if !condition 24 | # 25 | class DisableSyntax < Base 26 | extend AutoCorrector 27 | 28 | def on_if(node) 29 | if node.unless? && !unless_allowed? 30 | add_offense(node, message: "Do not use `unless`.") do |corrector| 31 | corrector.replace(node.loc.keyword, "if") 32 | corrector.wrap(node.condition, "!(", ")") 33 | end 34 | elsif node.ternary? && !ternary_allowed? 35 | add_offense(node, message: "Do not use ternary operator.") 36 | end 37 | end 38 | 39 | def on_csend(node) 40 | if !safe_navigation_allowed? 41 | add_offense(node, message: "Do not use `&.`.") 42 | end 43 | on_send(node) 44 | end 45 | 46 | def on_send(node) 47 | if !arguments_forwarding_allowed? && arguments_forwarding?(node) 48 | add_offense(node, message: "Do not use arguments forwarding.") 49 | elsif node.prefix_not? && !and_or_not_allowed? 50 | add_offense(node, message: "Use `!` instead of `not`.") do |corrector| 51 | corrector.replace(node.loc.selector, "!") 52 | end 53 | end 54 | end 55 | 56 | def on_def(node) 57 | if node.endless? && !endless_methods_allowed? 58 | add_offense(node, message: "Do not use endless methods.") do |corrector| 59 | arguments = node.arguments.any? ? node.arguments.source : "" 60 | 61 | corrector.replace(node, <<~RUBY.strip) 62 | def #{node.method_name}#{arguments} 63 | #{node.body.source} 64 | end 65 | RUBY 66 | end 67 | end 68 | end 69 | alias on_defs on_def 70 | 71 | def on_numblock(node) 72 | if !numbered_parameters_allowed? 73 | add_offense(node, message: "Do not use numbered parameters.") 74 | end 75 | end 76 | 77 | def on_case_match(node) 78 | if !pattern_matching_allowed? 79 | add_offense(node, message: "Do not use pattern matching.") 80 | end 81 | end 82 | 83 | def on_hash(node) 84 | return if shorthand_hash_syntax_allowed? || node.pairs.none?(&:value_omission?) 85 | 86 | add_offense(node, message: "Do not use shorthand hash syntax.") do |corrector| 87 | node.pairs.each do |pair| 88 | if pair.value_omission? 89 | hash_key_source = pair.key.source 90 | corrector.replace(pair, "#{hash_key_source}: #{hash_key_source}") 91 | end 92 | end 93 | end 94 | end 95 | 96 | def on_and(node) 97 | if node.semantic_operator? && !and_or_not_allowed? 98 | add_offense(node, message: "Use `#{node.alternate_operator}` instead of `#{node.operator}`.") do |corrector| 99 | corrector.replace(node.loc.operator, node.alternate_operator) 100 | end 101 | end 102 | end 103 | alias on_or on_and 104 | 105 | def on_until(node) 106 | return if until_allowed? 107 | 108 | add_offense(node, message: "Do not use `until`.") do |corrector| 109 | corrector.replace(node.loc.keyword, "while") 110 | corrector.wrap(node.condition, "!(", ")") 111 | end 112 | end 113 | 114 | def on_array(node) 115 | if node.percent_literal? && !percent_literals_allowed? 116 | add_offense(node, message: "Do not use `%` literals for arrays.") 117 | end 118 | end 119 | 120 | def on_regexp(node) 121 | if node.percent_r_literal? && !percent_literals_allowed? 122 | add_offense(node, message: "Do not use `%` literals for regexes.") 123 | end 124 | end 125 | 126 | def on_str(node) 127 | if !percent_literals_allowed? && str_percent_literal?(node) 128 | add_offense(node, message: "Do not use `%` literals for strings.") 129 | end 130 | end 131 | 132 | private 133 | def unless_allowed? 134 | !disable_syntax.include?("unless") 135 | end 136 | 137 | def ternary_allowed? 138 | !disable_syntax.include?("ternary") 139 | end 140 | 141 | def safe_navigation_allowed? 142 | !disable_syntax.include?("safe_navigation") 143 | end 144 | 145 | def endless_methods_allowed? 146 | !disable_syntax.include?("endless_methods") 147 | end 148 | 149 | def arguments_forwarding_allowed? 150 | !disable_syntax.include?("arguments_forwarding") 151 | end 152 | 153 | def arguments_forwarding?(send_node) 154 | send_node.arguments.any? do |arg| 155 | (arg.block_pass_type? && arg.source == "&") || # foo(&) 156 | arg.forwarded_args_type? || # foo(...) 157 | arg.forwarded_restarg_type? || # foo(*) 158 | (arg.hash_type? && arg.source == "**") # foo(**) 159 | end 160 | end 161 | 162 | def numbered_parameters_allowed? 163 | !disable_syntax.include?("numbered_parameters") 164 | end 165 | 166 | def pattern_matching_allowed? 167 | !disable_syntax.include?("pattern_matching") 168 | end 169 | 170 | def shorthand_hash_syntax_allowed? 171 | !disable_syntax.include?("shorthand_hash_syntax") 172 | end 173 | 174 | def and_or_not_allowed? 175 | !disable_syntax.include?("and_or_not") 176 | end 177 | 178 | def until_allowed? 179 | !disable_syntax.include?("until") 180 | end 181 | 182 | def percent_literals_allowed? 183 | !disable_syntax.include?("percent_literals") 184 | end 185 | 186 | def str_percent_literal?(node) 187 | if node.loc.respond_to?(:begin) && node.loc.begin 188 | node.loc.begin.source.start_with?("%") 189 | end 190 | end 191 | 192 | def disable_syntax 193 | @disable_syntax ||= begin 194 | supported_disable_syntax = cop_config.fetch("SupportedDisableSyntax", []) 195 | disable_syntax = cop_config.fetch("DisableSyntax", []) 196 | if (extra_syntax = disable_syntax - supported_disable_syntax).any? 197 | raise "Unknown `DisableSyntax` value(s): #{extra_syntax.join(', ')}" 198 | end 199 | 200 | disable_syntax 201 | end 202 | end 203 | end 204 | end 205 | end 206 | end 207 | -------------------------------------------------------------------------------- /lib/rubocop/disable_syntax.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "disable_syntax/version" 4 | 5 | module RuboCop 6 | module DisableSyntax 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/rubocop/disable_syntax/plugin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "lint_roller" 4 | 5 | module RuboCop 6 | module DisableSyntax 7 | # A plugin that integrates RuboCop Disable Syntax with RuboCop's plugin system. 8 | class Plugin < LintRoller::Plugin 9 | def about 10 | LintRoller::About.new( 11 | name: "rubocop-disable_syntax", 12 | version: VERSION, 13 | homepage: "https://github.com/fatkodima/rubocop-disable_syntax", 14 | description: "A RuboCop plugin that allows to disable some unfavorite ruby syntax, " \ 15 | "such as `unless`, safe navigation etc." 16 | ) 17 | end 18 | 19 | def supported?(context) 20 | context.engine == :rubocop 21 | end 22 | 23 | def rules(_context) 24 | LintRoller::Rules.new( 25 | type: :path, 26 | config_format: :rubocop, 27 | value: Pathname.new(__dir__).join("../../../config/default.yml") 28 | ) 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/rubocop/disable_syntax/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module DisableSyntax 5 | VERSION = "0.2.0" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /rubocop-disable_syntax.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "lib/rubocop/disable_syntax/version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "rubocop-disable_syntax" 7 | spec.version = RuboCop::DisableSyntax::VERSION 8 | spec.authors = ["fatkodima"] 9 | spec.email = ["fatkodima123@gmail.com"] 10 | 11 | spec.summary = "A RuboCop plugin that allows to disable some unfavorite ruby syntax, " \ 12 | "such as `unless`, safe navigation etc." 13 | spec.homepage = "https://github.com/fatkodima/rubocop-disable_syntax" 14 | spec.license = "MIT" 15 | spec.required_ruby_version = ">= 3.2.0" 16 | 17 | spec.metadata["homepage_uri"] = spec.homepage 18 | spec.metadata["source_code_uri"] = spec.homepage 19 | spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md" 20 | 21 | spec.files = Dir["**/*.{md,txt}", "{lib,config}/**/*"] 22 | spec.require_paths = ["lib"] 23 | 24 | spec.metadata["default_lint_roller_plugin"] = "RuboCop::DisableSyntax::Plugin" 25 | 26 | spec.add_dependency "lint_roller" 27 | spec.add_dependency "rubocop", ">= 1.72.0" 28 | end 29 | -------------------------------------------------------------------------------- /test/disable_syntax_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class DisableSyntaxTest < Minitest::Test 6 | def setup 7 | configure_cop 8 | end 9 | 10 | def test_accepts_unless_by_default 11 | assert_no_offenses(<<~RUBY) 12 | foo unless condition 13 | RUBY 14 | end 15 | 16 | def test_accepts_if 17 | disable_syntax("unless") 18 | 19 | assert_no_offenses(<<~RUBY) 20 | foo if condition 21 | RUBY 22 | end 23 | 24 | def test_registers_offense_when_unless_is_disabled 25 | disable_syntax("unless") 26 | 27 | assert_offense(<<~RUBY) 28 | foo unless condition 29 | ^^^^^^^^^^^^^^^^^^^^ Do not use `unless`. 30 | RUBY 31 | 32 | assert_correction(<<~RUBY) 33 | foo if !(condition) 34 | RUBY 35 | end 36 | 37 | def test_accepts_ternary_by_default 38 | assert_no_offenses(<<~RUBY) 39 | condition ? foo : bar 40 | RUBY 41 | end 42 | 43 | def test_registers_offense_when_ternary_is_disabled 44 | disable_syntax("ternary") 45 | 46 | assert_offense(<<~RUBY) 47 | condition ? foo : bar 48 | ^^^^^^^^^^^^^^^^^^^^^ Do not use ternary operator. 49 | RUBY 50 | end 51 | 52 | def test_accepts_safe_navigation_by_default 53 | assert_no_offenses(<<~RUBY) 54 | obj&.foo 55 | RUBY 56 | end 57 | 58 | def test_accepts_method_calls 59 | disable_syntax("safe_navigation") 60 | 61 | assert_no_offenses(<<~RUBY) 62 | obj.foo 63 | RUBY 64 | end 65 | 66 | def test_registers_offense_when_safe_navigation_is_disabled 67 | disable_syntax("safe_navigation") 68 | 69 | assert_offense(<<~RUBY) 70 | obj&.foo 71 | ^^^^^^^^ Do not use `&.`. 72 | RUBY 73 | end 74 | 75 | def test_accepts_endless_methods_by_default 76 | assert_no_offenses(<<~RUBY) 77 | def foo = 1 78 | RUBY 79 | end 80 | 81 | def test_accepts_regular_methods 82 | disable_syntax("endless_methods") 83 | 84 | assert_no_offenses(<<~RUBY) 85 | def foo 86 | 1 87 | end 88 | RUBY 89 | end 90 | 91 | def test_registers_offense_when_endless_methods_are_disabled 92 | disable_syntax("endless_methods") 93 | 94 | assert_offense(<<~RUBY) 95 | def foo = 1 96 | ^^^^^^^^^^^ Do not use endless methods. 97 | RUBY 98 | 99 | assert_correction(<<~RUBY) 100 | def foo 101 | 1 102 | end 103 | RUBY 104 | end 105 | 106 | def test_accepts_arguments_forwarding_by_default 107 | assert_no_offenses(<<~RUBY) 108 | def foo(*) bar(*) end 109 | def foo(**) bar(**) end 110 | def foo(&) bar(&) end 111 | def foo(...) bar(...) end 112 | RUBY 113 | end 114 | 115 | def test_accepts_named_arguments_forwarding 116 | disable_syntax("arguments_forwarding") 117 | 118 | assert_no_offenses(<<~RUBY) 119 | def foo(*args) bar(*args) end 120 | def foo(**options) bar(**options) end 121 | def foo(&block) bar(&block) end 122 | RUBY 123 | end 124 | 125 | def test_registers_offense_when_arguments_forwarding_is_disabled 126 | disable_syntax("arguments_forwarding") 127 | 128 | assert_offense(<<~RUBY) 129 | def foo(*) 130 | bar(*) 131 | ^^^^^^ Do not use arguments forwarding. 132 | end 133 | 134 | def foo(**) 135 | bar(**) 136 | ^^^^^^^ Do not use arguments forwarding. 137 | end 138 | 139 | def foo(&) 140 | bar(&) 141 | ^^^^^^ Do not use arguments forwarding. 142 | end 143 | 144 | def foo(...) 145 | bar(...) 146 | ^^^^^^^^ Do not use arguments forwarding. 147 | end 148 | 149 | def foo(arg, *) 150 | bar(*) 151 | ^^^^^^ Do not use arguments forwarding. 152 | end 153 | RUBY 154 | end 155 | 156 | def test_accepts_numbered_parameters_by_default 157 | assert_no_offenses(<<~RUBY) 158 | foo.each { puts _1 } 159 | RUBY 160 | end 161 | 162 | def test_registers_offense_when_numbered_parameters_are_disabled 163 | disable_syntax("numbered_parameters") 164 | 165 | assert_offense(<<~RUBY) 166 | foo.each { puts _1 } 167 | ^^^^^^^^^^^^^^^^^^^^ Do not use numbered parameters. 168 | RUBY 169 | end 170 | 171 | def test_accepts_pattern_matching_by_default 172 | assert_no_offenses(<<~RUBY) 173 | case foo 174 | in bar 175 | baz 176 | end 177 | RUBY 178 | end 179 | 180 | def test_registers_offense_when_pattern_matching_is_disabled 181 | disable_syntax("pattern_matching") 182 | 183 | assert_offense(<<~RUBY) 184 | case foo 185 | ^^^^^^^^ Do not use pattern matching. 186 | in bar 187 | baz 188 | end 189 | RUBY 190 | end 191 | 192 | def test_accepts_shorthand_hash_syntax_by_default 193 | assert_no_offenses(<<~RUBY) 194 | x = 1 195 | { x: } 196 | RUBY 197 | end 198 | 199 | def test_accepts_hash_literals 200 | disable_syntax("shorthand_hash_syntax") 201 | 202 | assert_no_offenses(<<~RUBY) 203 | { x: x } 204 | RUBY 205 | end 206 | 207 | def test_registers_offense_when_shorthand_hash_syntax_is_disabled 208 | disable_syntax("shorthand_hash_syntax") 209 | 210 | assert_offense(<<~RUBY) 211 | x = 1 212 | { x: } 213 | ^^^^^^ Do not use shorthand hash syntax. 214 | RUBY 215 | 216 | assert_correction(<<~RUBY) 217 | x = 1 218 | { x: x } 219 | RUBY 220 | end 221 | 222 | def test_accepts_semantic_operators_by_default 223 | assert_no_offenses(<<~RUBY) 224 | x and y 225 | x or y 226 | not x 227 | RUBY 228 | end 229 | 230 | def test_registers_offense_when_and_or_not_is_disabled 231 | disable_syntax("and_or_not") 232 | 233 | assert_offense(<<~RUBY) 234 | x and y 235 | ^^^^^^^ Use `&&` instead of `and`. 236 | x or y 237 | ^^^^^^ Use `||` instead of `or`. 238 | not x 239 | ^^^^^ Use `!` instead of `not`. 240 | RUBY 241 | 242 | assert_correction(<<~RUBY) 243 | x && y 244 | x || y 245 | ! x 246 | RUBY 247 | end 248 | 249 | def test_accepts_until_by_default 250 | assert_no_offenses(<<~RUBY) 251 | foo until condition 252 | RUBY 253 | end 254 | 255 | def test_registers_offense_when_until_is_disabled 256 | disable_syntax("until") 257 | 258 | assert_offense(<<~RUBY) 259 | foo until condition 260 | ^^^^^^^^^^^^^^^^^^^ Do not use `until`. 261 | RUBY 262 | 263 | assert_correction(<<~RUBY) 264 | foo while !(condition) 265 | RUBY 266 | end 267 | 268 | def test_accepts_percent_literals_by_default 269 | assert_no_offenses(<<~RUBY) 270 | %w[foo bar] 271 | %i[foo bar] 272 | %q("foo") 273 | %r{foo} 274 | RUBY 275 | end 276 | 277 | def test_registers_offense_when_percent_literals_are_disabled 278 | disable_syntax("percent_literals") 279 | 280 | assert_offense(<<~RUBY) 281 | %w[foo bar] 282 | ^^^^^^^^^^^ Do not use `%` literals for arrays. 283 | %i[foo bar] 284 | ^^^^^^^^^^^ Do not use `%` literals for arrays. 285 | %q("foo") 286 | ^^^^^^^^^ Do not use `%` literals for strings. 287 | %r{foo} 288 | ^^^^^^^ Do not use `%` literals for regexes. 289 | RUBY 290 | end 291 | 292 | def test_raises_when_unknown_disable_syntax_directive_is_set 293 | disable_syntax("unknown") 294 | 295 | error = assert_raises(RuntimeError) do 296 | assert_no_offenses("foo unless condition") 297 | end 298 | 299 | assert_equal "Unknown `DisableSyntax` value(s): unknown", error.message 300 | end 301 | 302 | private 303 | def configure_cop 304 | disable_syntax([]) 305 | end 306 | 307 | def disable_syntax(list) 308 | configuration = RuboCop::Config.new( 309 | { 310 | "Style/DisableSyntax" => { 311 | "SupportedDisableSyntax" => [ 312 | "unless", 313 | "ternary", 314 | "safe_navigation", 315 | "endless_methods", 316 | "arguments_forwarding", 317 | "numbered_parameters", 318 | "pattern_matching", 319 | "shorthand_hash_syntax", 320 | "and_or_not", 321 | "until", 322 | "percent_literals" 323 | ], 324 | "DisableSyntax" => Array(list) 325 | } 326 | }, 327 | "#{Dir.pwd}/.rubocop.yml" 328 | ) 329 | 330 | @cop = RuboCop::Cop::Style::DisableSyntax.new(configuration) 331 | end 332 | end 333 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.unshift File.expand_path("../lib", __dir__) 4 | require "rubocop-disable_syntax" 5 | 6 | require "minitest/autorun" 7 | 8 | require "rubocop/rspec/expect_offense" 9 | require "rubocop/cop/legacy/corrector" 10 | 11 | # Adapted from rubocop-minitest gem. 12 | module AssertOffense 13 | private 14 | def format_offense(source, **replacements) 15 | replacements.each do |keyword, value| 16 | value = value.to_s 17 | source = source.gsub("%{#{keyword}}", value) 18 | .gsub("^{#{keyword}}", "^" * value.size) 19 | .gsub("_{#{keyword}}", " " * value.size) 20 | end 21 | source 22 | end 23 | 24 | def assert_no_offenses(source, file = nil) 25 | setup_assertion 26 | 27 | offenses = inspect_source(source, @cop, file) 28 | 29 | expected_annotations = RuboCop::RSpec::ExpectOffense::AnnotatedSource.parse(source) 30 | actual_annotations = expected_annotations.with_offense_annotations(offenses) 31 | 32 | assert_equal(source, actual_annotations.to_s) 33 | end 34 | 35 | def assert_offense(source, file = nil, **replacements) 36 | setup_assertion 37 | 38 | @cop.instance_variable_get(:@options)[:autocorrect] = true 39 | 40 | source = format_offense(source, **replacements) 41 | expected_annotations = RuboCop::RSpec::ExpectOffense::AnnotatedSource.parse(source) 42 | if expected_annotations.plain_source == source 43 | raise "Use `assert_no_offenses` to assert that no offenses are found" 44 | end 45 | 46 | @processed_source = parse_source!(expected_annotations.plain_source, file) 47 | 48 | offenses = _investigate(@cop, @processed_source) 49 | 50 | actual_annotations = expected_annotations.with_offense_annotations(offenses) 51 | 52 | assert_equal(expected_annotations.to_s, actual_annotations.to_s) 53 | end 54 | 55 | def _investigate(cop, processed_source) 56 | team = RuboCop::Cop::Team.new([cop], configuration, raise_error: true) 57 | report = team.investigate(processed_source) 58 | @last_corrector = report.correctors.first || RuboCop::Cop::Corrector.new(processed_source) 59 | report.offenses 60 | end 61 | 62 | def assert_correction(correction, loop: true) 63 | raise "`assert_correction` must follow `assert_offense`" if !@processed_source 64 | 65 | iteration = 0 66 | new_source = loop do 67 | iteration += 1 68 | 69 | corrected_source = @last_corrector.rewrite 70 | 71 | break corrected_source if !loop 72 | break corrected_source if @last_corrector.empty? || corrected_source == @processed_source.buffer.source 73 | 74 | if iteration > RuboCop::Runner::MAX_ITERATIONS 75 | raise RuboCop::Runner::InfiniteCorrectionLoop.new(@processed_source.path, []) 76 | end 77 | 78 | # Prepare for next loop 79 | @processed_source = parse_source!(corrected_source, @processed_source.path) 80 | 81 | _investigate(@cop, @processed_source) 82 | end 83 | 84 | assert_equal(correction, new_source) 85 | end 86 | 87 | def setup_assertion 88 | RuboCop::Formatter::DisabledConfigFormatter.config_to_allow_offenses = {} 89 | RuboCop::Formatter::DisabledConfigFormatter.detected_styles = {} 90 | end 91 | 92 | def inspect_source(source, cop, file = nil) 93 | processed_source = parse_source!(source, file) 94 | raise "Error parsing example code" if !processed_source.valid_syntax? 95 | 96 | _investigate(cop, processed_source) 97 | end 98 | 99 | def investigate(cop, processed_source) 100 | needed = Hash.new { |h, k| h[k] = [] } 101 | Array(cop.class.joining_forces).each { |force| needed[force] << cop } 102 | forces = needed.map do |force_class, joining_cops| 103 | force_class.new(joining_cops) 104 | end 105 | 106 | commissioner = RuboCop::Cop::Commissioner.new([cop], forces, raise_error: true) 107 | commissioner.investigate(processed_source) 108 | commissioner 109 | end 110 | 111 | def parse_source!(source, file = nil) 112 | if file.respond_to?(:write) 113 | file.write(source) 114 | file.rewind 115 | file = file.path 116 | end 117 | 118 | processed_source = RuboCop::ProcessedSource.new(source, 3.2, file) 119 | 120 | # Follow up https://github.com/rubocop/rubocop/pull/10987. 121 | # When support for RuboCop 1.37.1 ends, this condition can be removed. 122 | if processed_source.respond_to?(:config) && processed_source.respond_to?(:registry) 123 | processed_source.config = configuration 124 | processed_source.registry = registry 125 | end 126 | 127 | processed_source 128 | end 129 | 130 | def configuration 131 | @configuration ||= RuboCop::Config.new({}, "#{Dir.pwd}/.rubocop.yml") 132 | end 133 | 134 | def registry 135 | @registry ||= begin 136 | cops = configuration.keys.map { |cop| RuboCop::Cop::Registry.global.find_by_cop_name(cop) } 137 | cops << cop_class if defined?(cop_class) && !cops.include?(cop_class) 138 | cops.compact! 139 | RuboCop::Cop::Registry.new(cops) 140 | end 141 | end 142 | end 143 | 144 | Minitest::Test.include(AssertOffense) 145 | --------------------------------------------------------------------------------