├── .claude ├── claude-on-edit.config.js └── settings.json ├── .github ├── dependabot.yml └── workflows │ └── rspec.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── .vscode └── settings.json ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── add_doc_links.rb ├── bin ├── console └── setup ├── config └── default.yml ├── lib ├── rubocop │ └── cop │ │ └── sgcop │ │ ├── active_job_queue_adapter.rb │ │ ├── capybara │ │ ├── fragile_selector.rb │ │ ├── matchers.rb │ │ ├── sleep.rb │ │ └── spec_stability_check.rb │ │ ├── enumerize_default_option.rb │ │ ├── enumerize_predicates_option.rb │ │ ├── error_message_format.rb │ │ ├── hash_fetch_default.rb │ │ ├── i18n_localize_format_string.rb │ │ ├── load_defaults_version_match.rb │ │ ├── nested_resources_without_module.rb │ │ ├── no_accepts_nested_attributes_for.rb │ │ ├── on_load_arguments.rb │ │ ├── prefer_absolute_path_partial.rb │ │ ├── request_remote_ip.rb │ │ ├── resource_action_order.rb │ │ ├── resources_without_only.rb │ │ ├── restricted_view_helpers.rb │ │ ├── retry_on_infinite_attempts.rb │ │ ├── rspec │ │ ├── action_mailer_test_helper.rb │ │ ├── active_job_test_helper.rb │ │ ├── conditional_example.rb │ │ ├── no_method_call_in_expectation.rb │ │ ├── redundant_let_reference.rb │ │ └── redundant_perform_enqueued_jobs.rb │ │ ├── simple_form_association.rb │ │ ├── simple_format.rb │ │ ├── strftime_restriction.rb │ │ ├── strict_loading_required.rb │ │ ├── transaction_requires_new.rb │ │ ├── ujs_options.rb │ │ └── unscoped.rb ├── sgcop.rb └── sgcop │ ├── inject.rb │ └── version.rb ├── rails └── rubocop.yml ├── ruby └── rubocop.yml ├── sgcop.gemspec └── spec ├── rubocop └── cop │ └── sgcop │ ├── active_job_queue_adapter_spec.rb │ ├── capybara │ ├── fragile_selector_spec.rb │ ├── matchers_spec.rb │ ├── sleep_spec.rb │ └── spec_stability_check_spec.rb │ ├── enumerize_default_option_spec.rb │ ├── enumerize_predicates_option_spec.rb │ ├── error_message_format_spec.rb │ ├── hash_fetch_default_spec.rb │ ├── i18n_localize_format_string_spec.rb │ ├── load_defaults_version_match_spec.rb │ ├── nested_resources_without_module_spec.rb │ ├── no_accepts_nested_attributes_for_spec.rb │ ├── on_load_arguments_spec.rb │ ├── prefer_absolute_path_partial_spec.rb │ ├── request_remote_ip_spec.rb │ ├── resource_action_order_spec.rb │ ├── resources_without_only_spec.rb │ ├── restricted_view_helpers_spec.rb │ ├── retry_on_infinite_attempts_spec.rb │ ├── rspec │ ├── action_mailer_test_helper_spec.rb │ ├── active_job_test_helper_spec.rb │ ├── conditional_example_spec.rb │ ├── no_method_call_in_expectation_spec.rb │ ├── redundant_let_reference_spec.rb │ └── redundant_perform_enqueued_jobs_spec.rb │ ├── simple_form_association_spec.rb │ ├── simple_format_spec.rb │ ├── strftime_restriction_spec.rb │ ├── strict_loading_required_spec.rb │ ├── transaction_requires_new_spec.rb │ ├── ujs_options_spec.rb │ └── unscoped_spec.rb ├── sgcop_spec.rb └── spec_helper.rb /.claude/claude-on-edit.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | "**/*.rb": [ 3 | "bundle exec rubocop -a" 4 | ], 5 | }; 6 | -------------------------------------------------------------------------------- /.claude/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "PostToolUse": [ 4 | { 5 | "matcher": "Write|Edit|MultiEdit", 6 | "hooks": [ 7 | { 8 | "type": "command", 9 | "command": "npx -y @aki77/claude-on-edit" 10 | } 11 | ] 12 | } 13 | ] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | # Check for updates to GitHub Actions every weekday 7 | interval: "weekly" 8 | -------------------------------------------------------------------------------- /.github/workflows/rspec.yml: -------------------------------------------------------------------------------- 1 | name: Ruby 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | 8 | jobs: 9 | rspec: 10 | runs-on: ubuntu-latest 11 | env: 12 | BUNDLE_JOBS: 4 13 | BUNDLE_RETRY: 3 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | ruby: ["3.1", "3.2", "3.3", "3.4"] 18 | steps: 19 | - uses: actions/checkout@v5 20 | - name: Set up Ruby 21 | uses: ruby/setup-ruby@v1 22 | with: 23 | ruby-version: ${{ matrix.ruby }} 24 | bundler-cache: true 25 | - name: Run rspec 26 | run: bundle exec rspec 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.floo 2 | /.flooignore 3 | /.bundle/ 4 | /.yardoc 5 | /Gemfile.lock 6 | /_yardoc/ 7 | /coverage/ 8 | /doc/ 9 | /pkg/ 10 | /spec/reports/ 11 | /tmp/ 12 | /vendor/bundle 13 | .byebug_history 14 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: ruby/rubocop.yml 2 | 3 | AllCops: 4 | Exclude: 5 | - "sgcop.gemspec" 6 | 7 | Metrics/BlockLength: 8 | Exclude: 9 | - 'spec/**/*.rb' 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "autocorrect", 4 | "dstr" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in sgcop.gemspec 4 | gemspec 5 | 6 | gem 'bundler' 7 | gem 'rake', '~> 13.0' 8 | gem 'rspec', '~> 3.9' 9 | gem 'debug' 10 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 maedana 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 | # Sgcop 2 | 3 | SonicGarden 標準の rubocop設定支援をするツール 4 | 5 | ## Installation 6 | 7 | ``` 8 | gem 'sgcop', github: 'SonicGarden/sgcop', branch: 'main' 9 | ``` 10 | 11 | ## Usage 12 | 13 | SonicGarden 標準スタイルを用意しています。 14 | 15 | プロジェクトルートに .rubocop.yml ファイルをつくり、Rails ではないプロジェクトの場合は以下のように書く。 16 | 17 | ``` 18 | inherit_gem: 19 | sgcop: ruby/rubocop.yml 20 | ``` 21 | 22 | Rails プロジェクトの場合は以下のように書く。 23 | 24 | ``` 25 | inherit_gem: 26 | sgcop: rails/rubocop.yml 27 | ``` 28 | 29 | そして実行。 30 | 31 | ``` 32 | rubocop 33 | ``` 34 | 35 | ## カスタムCop一覧 36 | 37 | sgcopが提供するカスタムCopの一覧です。 38 | 39 | ### Rails関連 40 | 41 | | Cop名 | 説明 | デフォルト | 42 | |-------|------|:----------:| 43 | | [`Sgcop/ActiveJobQueueAdapter`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/active_job_queue_adapter.rb) | ActiveJobのキューアダプタ設定をチェック | ✅ | 44 | | [`Sgcop/LoadDefaultsVersionMatch`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/load_defaults_version_match.rb) | config.load_defaultsのバージョンがRailsバージョンと一致することを確認 | ✅ | 45 | | [`Sgcop/NestedResourcesWithoutModule`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/nested_resources_without_module.rb) | ネストされたルーティングでmoduleオプションの使用を推奨 | ❌ | 46 | | [`Sgcop/NoAcceptsNestedAttributesFor`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/no_accepts_nested_attributes_for.rb) | accepts_nested_attributes_forの使用を制限 | ❌ | 47 | | [`Sgcop/ErrorMessageFormat`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/error_message_format.rb) | エラーメッセージはシンボルを使用することを強制 | ❌ | 48 | | [`Sgcop/OnLoadArguments`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/on_load_arguments.rb) | on_loadブロックの引数使用をチェック | ✅ | 49 | | [`Sgcop/PreferAbsolutePathPartial`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/prefer_absolute_path_partial.rb) | パーシャルファイルのrenderは絶対パスで指定(autocorrect対応) | ✅ | 50 | | [`Sgcop/RequestRemoteIp`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/request_remote_ip.rb) | request.remote_ipの適切な使用を確認 | ✅ | 51 | | [`Sgcop/ResourceActionOrder`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/resource_action_order.rb) | resourcesルーティングのアクション順序を統一 | ✅ | 52 | | [`Sgcop/ResourcesWithoutOnly`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/resources_without_only.rb) | resourcesルーティングでonlyオプションの使用を推奨 | ✅ | 53 | | [`Sgcop/RetryOnInfiniteAttempts`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/retry_on_infinite_attempts.rb) | retry_onでの無限リトライを防止 | ✅ | 54 | | [`Sgcop/SimpleFormat`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/simple_format.rb) | simple_formatメソッドの安全な使用を確認 | ✅ | 55 | | [`Sgcop/SimpleFormAssociation`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/simple_form_association.rb) | SimpleFormのassociationメソッドの適切な使用をチェック | ✅ | 56 | | [`Sgcop/StrictLoadingRequired`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/strict_loading_required.rb) | N+1問題を防ぐstrict_loadingの使用を推奨 | ❌ | 57 | | [`Sgcop/TransactionRequiresNew`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/transaction_requires_new.rb) | requires_new: trueを使用したトランザクションをチェック | ✅ | 58 | | [`Sgcop/UjsOptions`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/ujs_options.rb) | Rails UJSオプションの適切な使用を確認 | ✅ | 59 | | [`Sgcop/Unscoped`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/unscoped.rb) | unscopedメソッドの使用を制限 | ✅ | 60 | 61 | ### Capybara関連 62 | 63 | | Cop名 | 説明 | デフォルト | 64 | |-------|------|:----------:| 65 | | [`Sgcop/Capybara/FragileSelector`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/capybara/fragile_selector.rb) | 脆弱なCSSセレクタの使用を防止(data属性の使用を推奨) | ❌ | 66 | | [`Sgcop/Capybara/Matchers`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/capybara/matchers.rb) | Capybaraマッチャーの適切な使用をチェック | ✅ | 67 | | [`Sgcop/Capybara/Sleep`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/capybara/sleep.rb) | テストでのsleepの使用を制限 | ✅ | 68 | | [`Sgcop/Capybara/SpecStabilityCheck`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/capybara/spec_stability_check.rb) | 非同期処理テストで適切な待機マッチャーの使用を強制してテストを安定化 | ✅ | 69 | 70 | ### RSpec関連 71 | 72 | | Cop名 | 説明 | デフォルト | 73 | |-------|------|:----------:| 74 | | [`Sgcop/RSpec/ActionMailerTestHelper`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/rspec/action_mailer_test_helper.rb) | ActionMailerテストヘルパーの適切な使用を確認 | ❌ | 75 | | [`Sgcop/RSpec/ActiveJobTestHelper`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/rspec/active_job_test_helper.rb) | RSpecのActiveJobマッチャーの代わりにActiveJob::TestHelperのメソッド使用を推奨 | ❌ | 76 | | [`Sgcop/RSpec/ConditionalExample`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/rspec/conditional_example.rb) | 条件付きexpectを避け、常に無条件でアサーションを行うことを推奨 | ✅ | 77 | | [`Sgcop/RSpec/RedundantLetReference`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/rspec/redundant_let_reference.rb) | letを参照するだけの無意味な処理を検出し、let!の使用や直接セットアップを推奨 | ✅ | 78 | | [`Sgcop/RSpec/RedundantPerformEnqueuedJobs`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/rspec/redundant_perform_enqueued_jobs.rb) | ActionMailer::TestHelperメソッドとperform_enqueued_jobsの冗長な使用を防止 | ✅ | 79 | | [`Sgcop/RSpec/NoMethodCallInExpectation`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/rspec/no_method_call_in_expectation.rb) | RSpecのexpectationでメソッド呼び出しではなくリテラル値の使用を推奨 | ❌ | 80 | 81 | ### その他 82 | 83 | | Cop名 | 説明 | デフォルト | 84 | |-------|------|:----------:| 85 | | [`Sgcop/EnumerizeDefaultOption`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/enumerize_default_option.rb) | Enumerizeのdefaultオプションの使用をチェック | ❌ | 86 | | [`Sgcop/EnumerizePredicatesOption`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/enumerize_predicates_option.rb) | Enumerizeのpredicatesオプションの使用を制限(メソッド名コンフリクトを防止) | ❌ | 87 | | [`Sgcop/HashFetchDefault`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/hash_fetch_default.rb) | Hash#fetchのデフォルト値の適切な使用を確認 | ✅ | 88 | | [`Sgcop/I18nLocalizeFormatString`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/i18n_localize_format_string.rb) | I18n.lのformatオプションで文字列使用を制限し、シンボル使用を推奨 | ✅ | 89 | | [`Sgcop/RestrictedViewHelpers`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/restricted_view_helpers.rb) | 特定のビューヘルパーメソッドの使用を制限(設定でカスタマイズ可能) | ✅ | 90 | | [`Sgcop/StrftimeRestriction`](https://github.com/SonicGarden/sgcop/blob/main/lib/rubocop/cop/sgcop/strftime_restriction.rb) | strftimeメソッドの使用を制限し、I18n.lの使用を推奨 | ✅ | 91 | 92 | ## しつけ方 93 | 94 | http://blog.onk.ninja/2015/10/27/rubocop-getting-started 95 | 96 | 自動修正して楽したいならこちら 97 | 98 | http://blog.onk.ninja/2015/10/27/rubocop-getting-started#治安の悪いアプリに-rubocop-を導入する 99 | 100 | ### 参考サイト 101 | 102 | - Rubocop チートシート http://qiita.com/kitaro_tn/items/abb881c098b3df3f9871 103 | - 設定一覧(本家) https://github.com/bbatsov/rubocop/tree/master/config 104 | 105 | ## 設定ファイルにドキュメントのリンクを付加するスクリプト(sgcop 開発者向け) 106 | 107 | ruby/rubocop.yml, rails/rubocop.yml に新しい cop(ルール)設定を追加した後、以下のコマンドを実行すると、rubocop の cop のドキュメントへのリンクをコメントとして追加します。 108 | 109 | ``` 110 | ruby add_doc_links.rb 111 | ``` 112 | 113 | ### 追加されるコメントの例 114 | 115 | ``` 116 | # https://docs.rubocop.org/rubocop/cops_style.html#styleasciicomments 117 | Style/AsciiComments: 118 | Enabled: false 119 | ``` 120 | 121 | ## Contributing 122 | 123 | Bug reports and pull requests are welcome on GitHub at https://github.com/SonicGarden/sgcop. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct. 124 | 125 | ## License 126 | 127 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 128 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task default: :spec 7 | -------------------------------------------------------------------------------- /add_doc_links.rb: -------------------------------------------------------------------------------- 1 | # yaml の各ルールにドキュメントのリンクを付加するスクリプト 2 | 3 | require 'fileutils' 4 | 5 | TOP_CATEGORY_MAP = { 6 | 'Rails' => 'rails', 7 | 'RSpec' => 'rspec', 8 | 'Performance' => 'performance', 9 | 'Capybara' => 'capybara', 10 | 'FactoryBot' => 'factory_bot' 11 | } 12 | DOC_COMMENT_REGEXP = %r{\A# https://docs\.rubocop\.org/} 13 | 14 | def add_doc_link_comments(filename) 15 | tmp_filename = filename + ".tmp" 16 | File.open(tmp_filename, 'w') do |io| 17 | IO.foreach(filename) do |line| 18 | if DOC_COMMENT_REGEXP.match?(line) 19 | next 20 | elsif (m = %r{^(.+/.+):}.match(line)) 21 | words = m[1].split('/') 22 | top_category = words[0] 23 | category = words[0..-2].join('_').downcase 24 | name = words.join('').downcase 25 | top_category_path_name = TOP_CATEGORY_MAP[top_category] 26 | comment = 27 | if top_category_path_name 28 | "# https://docs.rubocop.org/rubocop-#{top_category_path_name}/cops_#{category}.html##{name}" 29 | else 30 | "# https://docs.rubocop.org/rubocop/cops_#{category}.html##{name}" 31 | end 32 | io.puts(comment) 33 | end 34 | io.write(line) 35 | end 36 | end 37 | FileUtils.mv(tmp_filename, filename) 38 | end 39 | 40 | FILES = %w[ 41 | ruby/rubocop.yml 42 | rails/rubocop.yml 43 | ] 44 | 45 | FILES.each do |filename| 46 | add_doc_link_comments(filename) 47 | end 48 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "sgcop" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | 5 | bundle install 6 | 7 | # Do any other automated setup that you need to do here 8 | -------------------------------------------------------------------------------- /config/default.yml: -------------------------------------------------------------------------------- 1 | Sgcop/Unscoped: 2 | Description: "絞り込みが全解除されるので見えてはいけないものが見えてしまう可能性があるよ" 3 | Enabled: true 4 | Reference: 5 | - https://techracho.bpsinc.jp/hachi8833/2021_11_04/47302 6 | 7 | Sgcop/SimpleFormat: 8 | Description: "simple_formatでエスケープされていないときに警告" 9 | Enabled: true 10 | 11 | Sgcop/SimpleFormAssociation: 12 | Description: "simple_formのassociationメソッドではcollectionを明示しよう" 13 | Enabled: true 14 | 15 | Sgcop/RequestRemoteIp: 16 | Description: "クライアントのIPを取得するならrequest.remote_ipの必要があるよ" 17 | Enabled: true 18 | Reference: 19 | - https://api.rubyonrails.org/classes/ActionDispatch/Request.html#method-i-remote_ip-3D 20 | 21 | Sgcop/UjsOptions: 22 | Description: "Rails UJS Attributesは非推奨となっているよ" 23 | Enabled: true 24 | Reference: 25 | - https://edgeapi.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to-label-Deprecated-3A+Rails+UJS+Attributes 26 | 27 | Sgcop/ActiveJobQueueAdapter: 28 | Description: "config/initializers以下で設定しても効かないよ" 29 | Enabled: true 30 | Include: 31 | - "config/initializers/*.rb" 32 | Reference: 33 | - https://qiita.com/sukechannnn/items/6cf8933c78656f543d54 34 | 35 | Sgcop/OnLoadArguments: 36 | Description: ActiveSupport.on_loadの引数が許可されていないnameだよ 37 | Enabled: true 38 | AllowedNames: 39 | - action_cable 40 | - action_cable_channel 41 | - action_cable_connection 42 | - action_cable_connection_test_case 43 | - action_controller_api 44 | - action_controller 45 | - action_controller_base 46 | - action_controller_test_case 47 | - action_dispatch_integration_test 48 | - action_dispatch_response 49 | - action_dispatch_request 50 | - action_dispatch_system_test_case 51 | - action_mailbox 52 | - action_mailbox_inbound_email 53 | - action_mailbox_record 54 | - action_mailbox_test_case 55 | - action_mailer 56 | - action_mailer_test_case 57 | - action_text_content 58 | - action_text_record 59 | - action_text_rich_text 60 | - action_text_encrypted_rich_text 61 | - action_view 62 | - action_view_test_case 63 | - active_job 64 | - active_job_test_case 65 | - active_model 66 | - active_record 67 | - active_record_fixtures 68 | - active_record_postgresqladapter 69 | - active_record_mysql2adapter 70 | - active_record_trilogyadapter 71 | - active_record_sqlite3adapter 72 | - active_storage_attachment 73 | - active_storage_variant_record 74 | - active_storage_blob 75 | - active_storage_record 76 | - active_support_test_case 77 | - i18n 78 | Reference: 79 | - https://github.com/rails/rails/blob/e9b61852343c83365aa73db6abcfae1e5b31272f/guides/source/configuring.md?plain=1#L3591 80 | 81 | Sgcop/TransactionRequiresNew: 82 | Description: "`requires_new: true`オプションを付けないとネストしたトランザクションでロールバックされない可能性があるよ" 83 | Enabled: true 84 | Reference: 85 | - https://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html#module-ActiveRecord::Transactions::ClassMethods-label-Nested+transactions 86 | 87 | Sgcop/LoadDefaultsVersionMatch: 88 | Description: "Railsのバージョンとload_defaultsのバージョンが揃っていないよ" 89 | Enabled: true 90 | Include: 91 | - "config/application.rb" 92 | 93 | Sgcop/RetryOnInfiniteAttempts: 94 | Description: "永遠にリトライする可能性があって危険だよ" 95 | Enabled: true 96 | Include: 97 | - "app/jobs/*.rb" 98 | - "config/initializers/*.rb" 99 | 100 | Sgcop/Capybara/Sleep: 101 | Description: "sleepに頼らない安定したspecを書こう" 102 | Enabled: true 103 | Include: 104 | - "spec/features/**/*_spec.rb" 105 | - "spec/system/**/*_spec.rb" 106 | Reference: 107 | - https://qiita.com/shunichi/items/1cb7f7cfca74438513d3 108 | 109 | Sgcop/Capybara/Matchers: 110 | Description: "マッチャーの書き方を統一" 111 | Enabled: true 112 | Include: 113 | - "spec/features/**/*_spec.rb" 114 | - "spec/system/**/*_spec.rb" 115 | - "spec/components/**/*_spec.rb" 116 | PreferredMethods: 117 | have_text: "have_content" 118 | 119 | Sgcop/Capybara/FragileSelector: 120 | Description: "スタイル変更やマークアップ変更で壊れやすいセレクタの使用を避けよう" 121 | Enabled: false 122 | Include: 123 | - "spec/system/**/*_spec.rb" 124 | 125 | Sgcop/ResourceActionOrder: 126 | Description: "routes.rbでのresource/resources内のaction順序をRails/ActionOrderと同じ順序に統一" 127 | Enabled: true 128 | Include: 129 | - "config/routes.rb" 130 | - "config/routes/*.rb" 131 | 132 | Sgcop/ResourcesWithoutOnly: 133 | Description: "resourcesルーティングには`only:`オプションを使用しよう" 134 | Enabled: true 135 | Include: 136 | - "config/routes.rb" 137 | - "config/routes/*.rb" 138 | 139 | Sgcop/StrictLoadingRequired: 140 | Description: "includesやpreloadを使用して変数代入する場合はstrict_loadingを追加することで仕組みでN+1を防ごう" 141 | Enabled: false 142 | Reference: 143 | - https://guides.rubyonrails.org/active_record_querying.html#strict-loading 144 | Include: 145 | - "app/controllers/**/*.rb" 146 | - "app/components/**/*.rb" 147 | 148 | Sgcop/EnumerizeDefaultOption: 149 | Description: "enumerizeのdefaultオプションは予測しない挙動を引き起こすため使用を避けてデータベースレベルでデフォルト値を設定しよう" 150 | Enabled: false 151 | Include: 152 | - "app/models/*.rb" 153 | 154 | Sgcop/EnumerizePredicatesOption: 155 | Description: "enumerizeのpredicatesオプションの使用を制限する(メソッド名がコンフリクトしやすく、定義を追いにくくなるため)" 156 | Enabled: false 157 | AllowWithPrefix: false # trueにすると predicates: { prefix: true } の場合は許可 158 | Include: 159 | - "app/models/**/*.rb" 160 | 161 | Sgcop/ErrorMessageFormat: 162 | Description: "エラーメッセージはシンボルで指定しよう。エラーの種別で検証することで壊れにくいテストが書けます。" 163 | Enabled: false 164 | Include: 165 | - "app/models/**/*.rb" 166 | - "app/forms/**/*.rb" 167 | - "app/validators/**/*.rb" 168 | Reference: 169 | - https://guides.rubyonrails.org/i18n.html#translations-for-active-record-models 170 | 171 | Sgcop/HashFetchDefault: 172 | Description: "Hash#fetchを使用してデフォルト値を扱うことでfalseyな値を保持しよう" 173 | Enabled: true 174 | Reference: 175 | - https://rubystyle.guide/#hash-fetch-defaults 176 | 177 | Sgcop/Rspec/ActionMailerTestHelper: 178 | Description: "ActionMailer::Base.deliveriesの代わりにActionMailer::TestHelperのメソッドを使用しよう" 179 | Enabled: false 180 | Include: 181 | - "spec/**/*_spec.rb" 182 | Reference: 183 | - https://api.rubyonrails.org/classes/ActionMailer/TestHelper.html 184 | 185 | Sgcop/Rspec/ActiveJobTestHelper: 186 | Description: "RSpecのActiveJobマッチャーの代わりにActiveJob::TestHelperのメソッドを使用しよう" 187 | Enabled: false 188 | Include: 189 | - "spec/**/*_spec.rb" 190 | Reference: 191 | - https://api.rubyonrails.org/classes/ActiveJob/TestHelper.html 192 | 193 | Sgcop/Rspec/RedundantPerformEnqueuedJobs: 194 | Description: "ActionMailer::TestHelperのメソッドは内部でperform_enqueued_jobsを使用しているため、外側のperform_enqueued_jobsは冗長だよ" 195 | Enabled: true 196 | Include: 197 | - "spec/**/*_spec.rb" 198 | Reference: 199 | - https://api.rubyonrails.org/classes/ActionMailer/TestHelper.html 200 | - https://github.com/rails/rails/blob/main/actionmailer/lib/action_mailer/test_helper.rb 201 | 202 | Sgcop/Rspec/RedundantLetReference: 203 | Description: "letを参照するだけの無意味な処理を検出し、let!の使用や直接セットアップを推奨" 204 | Enabled: true 205 | Include: 206 | - "spec/**/*_spec.rb" 207 | 208 | Sgcop/Rspec/ConditionalExample: 209 | Description: "条件付きexpectを避け、常に無条件でアサーションを行うことを推奨" 210 | Enabled: true 211 | Include: 212 | - "spec/**/*_spec.rb" 213 | 214 | Sgcop/Rspec/NoMethodCallInExpectation: 215 | Description: "期待値でメソッド呼び出しではなくリテラル値を使用しよう" 216 | Enabled: false 217 | Include: 218 | - "spec/**/*_spec.rb" 219 | TargetMatchers: 220 | - have_content 221 | - have_text 222 | - have_css 223 | - have_selector 224 | - have_title 225 | - eq 226 | - eql 227 | - equal 228 | - include 229 | - match 230 | AllowedPatterns: 231 | - "_path$" 232 | - "_url$" 233 | - "^I18n\\.t$" 234 | - "^I18n\\.l$" 235 | - "^id$" 236 | - "^to_date$" 237 | - "^to_i$" 238 | - "^to_s$" 239 | 240 | Sgcop/NoAcceptsNestedAttributesFor: 241 | Description: "accepts_nested_attributes_forの使用を避けよう。DHHはこの機能をRailsから削除したがっている。Form Objectsの使用を検討しよう" 242 | Enabled: false 243 | Include: 244 | - "app/models/**/*.rb" 245 | Reference: 246 | - https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html 247 | - https://github.com/rails/rails/issues/41419 248 | 249 | Sgcop/NestedResourcesWithoutModule: 250 | Description: "ネストしたresourcesルーティングには`module:`オプションを使用しよう" 251 | Enabled: false 252 | Include: 253 | - "config/routes.rb" 254 | - "config/routes/*.rb" 255 | 256 | Sgcop/RestrictedViewHelpers: 257 | Description: "特定のビューヘルパーメソッドの使用を制限" 258 | Enabled: true 259 | Include: 260 | - "app/views/**/*.erb" 261 | - "app/helpers/**/*.rb" 262 | - "app/components/**/*.rb" 263 | RestrictedMethods: [] 264 | 265 | Sgcop/StrftimeRestriction: 266 | Description: "strftimeではなくI18n.lを使用してローカライズしてください" 267 | Enabled: true 268 | Include: 269 | - "app/**/*" 270 | AllowedPatterns: 271 | - "%a" # 曜日の省略名 (Sun, Mon, etc.) 272 | - "%A" # 曜日の完全名 (Sunday, Monday, etc.) 273 | - "%w" # 曜日の数字 (0=Sunday, 1=Monday, etc.) 274 | Reference: 275 | - https://guides.rubyonrails.org/i18n.html#adding-date-time-formats 276 | 277 | Sgcop/I18nLocalizeFormatString: 278 | Description: "I18n.lのformatオプションには文字列ではなくシンボルを使用してください。ロケールファイルに定義してシンボルで参照することで、ロケールごとの切り替えが可能になります" 279 | Enabled: true 280 | Reference: 281 | - https://guides.rubyonrails.org/i18n.html#adding-date-time-formats 282 | 283 | Sgcop/PreferAbsolutePathPartial: 284 | Description: "パーシャルファイルのrenderは絶対パスで指定しよう" 285 | Enabled: true 286 | Include: 287 | - "app/views/**/*" 288 | - "app/components/**/*" 289 | 290 | Sgcop/Capybara/SpecStabilityCheck: 291 | Description: "非同期処理を伴うテストでは適切な待機処理を追加してテストを安定させよう" 292 | Enabled: true 293 | Include: 294 | - "spec/system/**/*_spec.rb" 295 | WatchedMethods: 296 | - assert_enqueued_emails 297 | - assert_emails 298 | - assert_no_emails 299 | - assert_enqueued_jobs 300 | - capture_emails 301 | - expect 302 | WaitMatcherPatterns: 303 | - "^have_" 304 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/active_job_queue_adapter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | class ActiveJobQueueAdapter < Base 7 | MSG = 'Do not set config.active_job.queue_adapter in config/initializers.' 8 | 9 | def on_send(node) 10 | return unless in_initializers_directory? 11 | return unless active_job_adapter_set?(node) 12 | 13 | add_offense(node) 14 | end 15 | 16 | private 17 | 18 | def in_initializers_directory? 19 | processed_source.file_path.include?('config/initializers/') 20 | end 21 | 22 | def active_job_adapter_set?(node) 23 | return unless node.send_type? 24 | return unless node.method_name == :queue_adapter= 25 | return unless node.receiver&.method_name == :active_job 26 | return unless node.receiver.receiver&.type == :lvar && node.receiver.receiver&.children&.first == :config 27 | 28 | true 29 | end 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/capybara/fragile_selector.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | module Capybara 7 | class FragileSelector < Base 8 | MSG_CLASS = 'Avoid using CSS class selectors as they are fragile and break when styles change. Use data attributes or accessible attributes instead.' 9 | MSG_ID = 'Avoid using ID selectors as they are fragile and break when markup changes. Use data attributes or accessible attributes instead.' 10 | MSG_HREF = 'Avoid using partial href matching as it is fragile. Use data attributes or accessible attributes instead.' 11 | MSG_WITHIN_CLASS = 'Avoid using CSS class selectors in within blocks. Use data attributes or accessible attributes instead.' 12 | MSG_XPATH = 'Avoid using XPath selectors as they are fragile and break when markup changes. Use data attributes or accessible attributes instead.' 13 | 14 | CAPYBARA_METHODS = %i[find find_all all first click_on within have_css].freeze 15 | 16 | def on_send(node) 17 | return unless capybara_method?(node) 18 | 19 | if node.method_name == :within 20 | check_within_selector(node) 21 | else 22 | check_selector(node) 23 | end 24 | end 25 | 26 | private 27 | 28 | def capybara_method?(node) 29 | CAPYBARA_METHODS.include?(node.method_name) 30 | end 31 | 32 | def check_selector(node) 33 | # XPathセレクタのチェック 34 | if xpath_selector?(node) 35 | add_offense(node.first_argument, message: MSG_XPATH) 36 | return 37 | end 38 | 39 | return unless node.first_argument&.str_type? 40 | 41 | selector = node.first_argument.value 42 | check_fragile_patterns(node.first_argument, selector) 43 | end 44 | 45 | def check_within_selector(node) 46 | return unless node.first_argument 47 | 48 | # XPathセレクタのチェック 49 | if xpath_selector?(node) 50 | add_offense(node.first_argument, message: MSG_XPATH) 51 | return 52 | end 53 | 54 | if node.first_argument.str_type? 55 | selector = node.first_argument.value 56 | check_fragile_patterns(node.first_argument, selector, within_context: true) 57 | end 58 | end 59 | 60 | def check_fragile_patterns(node, selector, within_context: false) 61 | return unless selector.is_a?(String) 62 | 63 | if css_class_selector?(selector) 64 | msg = within_context ? MSG_WITHIN_CLASS : MSG_CLASS 65 | add_offense(node, message: msg) 66 | elsif id_selector?(selector) 67 | add_offense(node, message: MSG_ID) 68 | elsif partial_href_selector?(selector) 69 | add_offense(node, message: MSG_HREF) 70 | end 71 | end 72 | 73 | def css_class_selector?(selector) 74 | selector.match?(/^\.[\w-]+/) || selector.match?(/\s+\.[\w-]+/) || 75 | selector.match?(/^[\w]+\.[\w\\:_-]+/) 76 | end 77 | 78 | def id_selector?(selector) 79 | selector.match?(/^#[\w-]+/) || selector.match?(/\s+#[\w-]+/) 80 | end 81 | 82 | def partial_href_selector?(selector) 83 | selector.match?(/\[href\*=/) 84 | end 85 | 86 | def xpath_selector?(node) 87 | # find(:xpath, '...') の形式をチェック 88 | return false unless node.first_argument&.sym_type? 89 | 90 | node.first_argument.value == :xpath 91 | end 92 | end 93 | end 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/capybara/matchers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | module Capybara 7 | # SEE: https://github.com/rubocop-hq/rubocop/blob/v1.0.0/lib/rubocop/cop/style/string_methods.rb 8 | class Matchers < Base 9 | include MethodPreference 10 | extend AutoCorrector 11 | 12 | MSG = 'Prefer `%s` over `%s`.' 13 | 14 | def on_send(node) 15 | return unless (preferred_method = preferred_method(node.method_name)) 16 | 17 | message = format(MSG, prefer: preferred_method, current: node.method_name) 18 | 19 | add_offense(node.loc.selector, message: message) do |corrector| 20 | corrector.replace(node.loc.selector, preferred_method(node.method_name)) 21 | end 22 | end 23 | alias on_csend on_send 24 | end 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/capybara/sleep.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | module Capybara 7 | # SEE: https://github.com/rubocop-hq/rubocop-rails/blob/master/lib/rubocop/cop/rails/exit.rb 8 | class Sleep < Base 9 | MSG = 'Do not use `sleep` in spec.' 10 | 11 | def on_send(node) 12 | add_offense(node) if offending_node?(node) 13 | end 14 | 15 | private 16 | 17 | def offending_node?(node) 18 | node.method_name == :sleep && right_receiver?(node.receiver) 19 | end 20 | 21 | def right_receiver?(receiver_node) 22 | return true unless receiver_node 23 | 24 | _a, receiver_node_class, _c = *receiver_node 25 | 26 | receiver_node_class == :Kernel 27 | end 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/capybara/spec_stability_check.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | module Capybara 7 | class SpecStabilityCheck < Base 8 | MSG = 'ページの変化を伴う非同期処理後には、適切な待機処理(例: expect(page).to have_content(\'更新しました。\'))を追加してテストを安定させましょう' 9 | 10 | def on_block(node) 11 | return unless watched_method_block?(node) 12 | return if contains_wait_matcher?(node.body) 13 | 14 | add_offense(node.send_node) 15 | end 16 | 17 | private 18 | 19 | def watched_method_block?(node) 20 | watched_methods = cop_config.fetch('WatchedMethods', %w[assert_enqueued_emails]) 21 | return false unless node.send_node&.receiver.nil? 22 | 23 | watched_methods.include?(node.send_node.method_name.to_s) 24 | end 25 | 26 | def contains_wait_matcher?(node) 27 | return false unless node 28 | 29 | wait_matcher_patterns = cop_config.fetch('WaitMatcherPatterns', ['^have_']) 30 | 31 | node.each_descendant(:send) do |send_node| 32 | return true if expect_to_matcher?(send_node, wait_matcher_patterns) 33 | end 34 | 35 | false 36 | end 37 | 38 | def expect_to_matcher?(send_node, wait_matcher_patterns) 39 | return false unless send_node.method_name == :to 40 | return false unless send_node.receiver&.method_name == :expect 41 | 42 | send_node.arguments.any? do |arg| 43 | next false unless arg.send_type? 44 | 45 | method_name = arg.method_name.to_s 46 | wait_matcher_patterns.any? { |pattern| method_name.match?(/#{pattern}/) } 47 | end 48 | end 49 | end 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/enumerize_default_option.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | class EnumerizeDefaultOption < Base 7 | MSG = 'Do not use `default` option in `enumerize`. Use database-level default values instead.' 8 | 9 | def_node_matcher :enumerize_with_default?, <<~PATTERN 10 | (send _ :enumerize ... (hash $...)) 11 | PATTERN 12 | 13 | def on_send(node) 14 | enumerize_with_default?(node) do |pairs| 15 | pairs.each do |pair| 16 | key, _value = *pair 17 | if key.sym_type? && key.value == :default 18 | add_offense(pair) 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/enumerize_predicates_option.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | class EnumerizePredicatesOption < Base 7 | MSG = 'Do not use `predicates` option in `enumerize`.' 8 | MSG_WITH_PREFIX = 'Use `predicates: { prefix: true }` instead of `predicates: %s`.' 9 | 10 | def_node_matcher :enumerize_with_predicates?, <<~PATTERN 11 | (send _ :enumerize ... (hash $...)) 12 | PATTERN 13 | 14 | def on_send(node) 15 | enumerize_with_predicates?(node) do |pairs| 16 | pairs.each do |pair| 17 | key, value = *pair 18 | next unless key.sym_type? && key.value == :predicates 19 | 20 | if should_register_offense?(value) 21 | message = offense_message(value) 22 | add_offense(pair, message: message) 23 | end 24 | end 25 | end 26 | end 27 | 28 | private 29 | 30 | def should_register_offense?(value_node) 31 | return true if value_node.true_type? || value_node.false_type? 32 | 33 | return true unless value_node.hash_type? 34 | return false if allow_with_prefix? && has_prefix_option?(value_node) 35 | 36 | true 37 | end 38 | 39 | def offense_message(value_node) 40 | if allow_with_prefix? && value_node.true_type? 41 | format(MSG_WITH_PREFIX, value: value_node.source) 42 | else 43 | MSG 44 | end 45 | end 46 | 47 | def has_prefix_option?(hash_node) 48 | hash_node.pairs.any? do |pair| 49 | key, value = *pair 50 | key.sym_type? && key.value == :prefix && value.true_type? 51 | end 52 | end 53 | 54 | def allow_with_prefix? 55 | cop_config.fetch('AllowWithPrefix', false) 56 | end 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/error_message_format.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | class ErrorMessageFormat < Base 7 | MSG = 'Error message should be a symbol.' 8 | 9 | def on_send(node) 10 | check_validates_message(node) 11 | check_errors_add(node) 12 | end 13 | 14 | private 15 | 16 | def check_validates_message(node) 17 | return unless node.method_name == :validates 18 | 19 | find_message_in_validates(node) 20 | end 21 | 22 | def find_message_in_validates(node) 23 | node.arguments.each do |arg| 24 | next unless arg.hash_type? 25 | 26 | check_validation_options(node, arg) 27 | end 28 | end 29 | 30 | def check_validation_options(node, hash_arg) 31 | hash_arg.each_pair do |_key, value| 32 | next unless value.hash_type? 33 | 34 | check_message_option(node, value) 35 | end 36 | end 37 | 38 | def check_message_option(node, options_hash) 39 | options_hash.each_pair do |opt_key, opt_value| 40 | next unless message_key?(opt_key) 41 | next if opt_value.sym_type? || variable_type?(opt_value) 42 | 43 | add_offense(node) 44 | end 45 | end 46 | 47 | def message_key?(key_node) 48 | key_node.sym_type? && key_node.value == :message 49 | end 50 | 51 | def check_errors_add(node) 52 | return unless errors_add_call?(node) 53 | 54 | message_arg = node.arguments[1] 55 | return unless message_arg 56 | return if message_arg.sym_type? || variable_type?(message_arg) 57 | 58 | add_offense(node) 59 | end 60 | 61 | def variable_type?(node) 62 | node.lvar_type? || node.ivar_type? || node.cvar_type? || node.gvar_type? || node.send_type? 63 | end 64 | 65 | def errors_add_call?(node) 66 | node.method_name == :add && 67 | node.receiver&.send_type? && 68 | node.receiver.method_name == :errors 69 | end 70 | end 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/hash_fetch_default.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | class HashFetchDefault < Base 7 | extend AutoCorrector 8 | 9 | MSG = 'Use `Hash#fetch` with a default value instead of `||` to preserve falsey values.' 10 | 11 | def_node_matcher :hash_element_access, <<~PATTERN 12 | (send $_ :[] $_) 13 | PATTERN 14 | 15 | def on_or(node) 16 | left_node = node.lhs 17 | return unless hash_element_access(left_node) 18 | 19 | add_offense(node) do |corrector| 20 | hash_obj, key = hash_element_access(left_node) 21 | default_value = node.rhs 22 | 23 | corrector.replace(node, "#{hash_obj.source}.fetch(#{key.source}, #{default_value.source})") 24 | end 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/i18n_localize_format_string.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | class I18nLocalizeFormatString < Base 7 | MSG = 'I18n.lのformatオプションには文字列ではなくシンボルを使用してください。ロケールファイルに定義してシンボルで参照することで、ロケールごとの切り替えが可能になります。' 8 | 9 | def on_send(node) 10 | return unless localize_method?(node) 11 | 12 | format_arg = extract_format_argument(node) 13 | return unless format_arg 14 | 15 | # 文字列でフォーマットが指定されている場合は警告 16 | if format_arg.str_type? 17 | add_offense(format_arg) 18 | end 19 | end 20 | 21 | private 22 | 23 | def localize_method?(node) 24 | # I18n.l, I18n.localize 25 | if node.receiver&.const_type? && node.receiver.short_name == :I18n 26 | %i[l localize].include?(node.method_name) 27 | # View helper methods: l, localize 28 | else 29 | node.receiver.nil? && %i[l localize].include?(node.method_name) 30 | end 31 | end 32 | 33 | def extract_format_argument(node) 34 | # Check if there are at least 2 arguments (object and format option) 35 | return nil unless node.arguments.size >= 2 36 | 37 | # Look for hash argument 38 | hash_arg = node.arguments.find(&:hash_type?) 39 | return nil unless hash_arg 40 | 41 | # Find format key in hash 42 | hash_arg.pairs.each do |pair| 43 | key = pair.key 44 | next unless key.sym_type? && key.value == :format 45 | 46 | return pair.value 47 | end 48 | 49 | nil 50 | end 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/load_defaults_version_match.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | class LoadDefaultsVersionMatch < Base 7 | MSG = 'The load_defaults version (%s) does not match the Rails version (%s) specified in the Gemfile.' 8 | 9 | def_node_matcher :load_defaults_version, <<-PATTERN 10 | (send (send nil? :config) :load_defaults (float $_)) 11 | PATTERN 12 | 13 | def on_send(node) 14 | load_defaults_version(node) do |version| 15 | gemfile_version = extract_rails_version 16 | if gemfile_version && gemfile_version != Gem::Version.new(version) 17 | add_offense(node, message: format(MSG, version: version, gemfile_version: gemfile_version)) 18 | end 19 | end 20 | end 21 | 22 | private 23 | 24 | def extract_rails_version 25 | # TODO: rubocopで提供されているAPIを使ってもうちょっとスマートに書けそうな気がする 26 | gemfile_lock_path = File.join(Dir.pwd, 'Gemfile.lock') 27 | return nil unless File.exist?(gemfile_lock_path) 28 | 29 | gemfile_lock_content = File.read(gemfile_lock_path) 30 | match = gemfile_lock_content.match(/^\s*rails \((\d+\.\d+)[\d.]+\)/) 31 | return nil unless match 32 | 33 | Gem::Version.new(match[1]) 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/nested_resources_without_module.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | class NestedResourcesWithoutModule < Base 7 | MSG = 'Use `module:` option in nested resource/resources routing.' 8 | 9 | def on_send(node) 10 | return unless resources_method?(node) 11 | return if inside_member_or_collection_block?(node) 12 | return unless inside_resources_block?(node) 13 | return if has_module_option?(node) 14 | 15 | add_offense(node) 16 | end 17 | 18 | private 19 | 20 | def inside_resources_block?(node) 21 | parent = node.parent 22 | while parent 23 | # Skip the immediate parent if the node has a block 24 | # and the parent is that block 25 | if parent.block_type? && parent.send_node == node 26 | parent = parent.parent 27 | next 28 | end 29 | 30 | if parent.block_type? && resources_method?(parent.send_node) 31 | return true 32 | end 33 | 34 | parent = parent.parent 35 | end 36 | false 37 | end 38 | 39 | def resources_method?(node) 40 | return false unless node 41 | 42 | node.method?(:resource) || node.method?(:resources) 43 | end 44 | 45 | def has_module_option?(node) 46 | node.each_child_node(:hash) do |hash_node| 47 | hash_node.each_pair do |key_node, _value_node| 48 | next unless key_node.sym_type? 49 | 50 | return true if %i[module namespace scope].include?(key_node.value) 51 | end 52 | end 53 | 54 | false 55 | end 56 | 57 | def inside_member_or_collection_block?(node) 58 | parent = node.parent 59 | while parent 60 | if parent.block_type? 61 | send_node = parent.send_node 62 | return true if send_node&.method?(:member) || send_node&.method?(:collection) 63 | end 64 | 65 | parent = parent.parent 66 | end 67 | false 68 | end 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/no_accepts_nested_attributes_for.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | class NoAcceptsNestedAttributesFor < Base 7 | MSG = 'Avoid using `accepts_nested_attributes_for`. Consider using Form Objects instead.' 8 | 9 | def_node_matcher :accepts_nested_attributes_for?, <<~PATTERN 10 | (send nil? :accepts_nested_attributes_for ...) 11 | PATTERN 12 | 13 | def on_send(node) 14 | return unless accepts_nested_attributes_for?(node) 15 | 16 | add_offense(node) 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/on_load_arguments.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | class OnLoadArguments < Base 7 | MSG = 'Do not use unpermitted name as arguments for `ActiveSupport.on_load`' 8 | 9 | def on_send(node) 10 | return unless node.method_name == :on_load 11 | return unless node.receiver&.const_name == 'ActiveSupport' 12 | 13 | arguments = node.arguments 14 | return if arguments.empty? 15 | 16 | add_offense(node) unless allowed_names.include?(arguments.first.value) 17 | end 18 | 19 | private 20 | 21 | def allowed_names 22 | cop_config['AllowedNames'].map(&:to_sym) 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/prefer_absolute_path_partial.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | class PreferAbsolutePathPartial < Base 7 | extend AutoCorrector 8 | 9 | MSG = 'パーシャルは絶対パスで指定してください。' 10 | 11 | def_node_matcher :render_with_string?, <<~PATTERN 12 | (send nil? :render $str ...) 13 | PATTERN 14 | 15 | def_node_matcher :render_with_partial_option?, <<~PATTERN 16 | (send nil? :render (hash <(pair (sym :partial) $str) ...>)) 17 | PATTERN 18 | 19 | def_node_matcher :render_with_collection?, <<~PATTERN 20 | (send nil? :render (hash <(pair (sym :collection) _) (pair (sym :partial) $str) ...>)) 21 | PATTERN 22 | 23 | def on_send(node) 24 | check_render(node) 25 | end 26 | 27 | private 28 | 29 | def check_render(node) 30 | render_with_string?(node) do |path_node| 31 | check_path(node, path_node) 32 | end 33 | 34 | render_with_partial_option?(node) do |path_node| 35 | check_path(node, path_node) 36 | end 37 | 38 | render_with_collection?(node) do |path_node| 39 | check_path(node, path_node) 40 | end 41 | end 42 | 43 | def check_path(node, path_node) 44 | path = path_node.value 45 | return if absolute_path?(path) 46 | 47 | add_offense(path_node) do |corrector| 48 | absolute_path = calculate_absolute_path(node, path) 49 | corrector.replace(path_node, %("#{absolute_path}")) 50 | end 51 | end 52 | 53 | def absolute_path?(path) 54 | path.include?('/') 55 | end 56 | 57 | def calculate_absolute_path(node, relative_path) 58 | file_path = processed_source.file_path 59 | return relative_path unless file_path 60 | 61 | # app/views/users/show.html.erb -> users 62 | match = file_path.match(%r{app/views/(.+?)/[^/]+\.\w+}) 63 | return relative_path unless match 64 | 65 | directory = match[1] 66 | "#{directory}/#{relative_path}" 67 | end 68 | end 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/request_remote_ip.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | class RequestRemoteIp < Base 7 | MSG = 'Use `request.remote_ip` instead of ' \ 8 | '`request.remote_addr`.' 9 | 10 | def_node_matcher :remote_addr?, <<~PATTERN 11 | (send (send nil? :request) {:remote_addr}) 12 | PATTERN 13 | 14 | def on_send(node) 15 | remote_addr?(node) do 16 | add_offense(node) 17 | end 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/resource_action_order.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | class ResourceActionOrder < Base 7 | extend AutoCorrector 8 | 9 | MSG = 'Actions should be ordered in the same sequence as Rails/ActionOrder: %s' 10 | 11 | EXPECTED_ORDER = %i[index show new edit create update destroy].freeze 12 | 13 | def on_send(node) 14 | return unless resource_method?(node) 15 | 16 | actions = extract_actions(node) 17 | return if actions.empty? 18 | 19 | expected_actions = sort_actions(actions) 20 | return if actions == expected_actions 21 | 22 | add_offense( 23 | node, 24 | message: format(MSG, expected_order: expected_actions.join(', ')) 25 | ) do |corrector| 26 | autocorrect(corrector, node, expected_actions) 27 | end 28 | end 29 | 30 | private 31 | 32 | def resource_method?(node) 33 | node.method?(:resource) || node.method?(:resources) 34 | end 35 | 36 | def extract_actions(node) 37 | actions = [] 38 | 39 | node.each_child_node(:hash) do |hash_node| 40 | hash_node.each_pair do |key_node, value_node| 41 | next unless key_node.sym_type? 42 | 43 | case key_node.value 44 | when :only, :except 45 | actions.concat(extract_action_symbols(value_node)) 46 | end 47 | end 48 | end 49 | 50 | actions.uniq 51 | end 52 | 53 | def extract_action_symbols(node) 54 | case node.type 55 | when :array 56 | node.children.filter_map { |child| child.value if child.sym_type? } 57 | when :sym 58 | [node.value] 59 | else 60 | [] 61 | end 62 | end 63 | 64 | def sort_actions(actions) 65 | actions.sort_by { |action| EXPECTED_ORDER.index(action) || Float::INFINITY } 66 | end 67 | 68 | def autocorrect(corrector, node, expected_actions) 69 | node.each_child_node(:hash) do |hash_node| 70 | hash_node.each_pair do |key_node, value_node| 71 | next unless key_node.sym_type? 72 | 73 | case key_node.value 74 | when :only, :except 75 | correct_action_list(corrector, value_node, expected_actions) 76 | end 77 | end 78 | end 79 | end 80 | 81 | def correct_action_list(corrector, node, actions) 82 | case node.type 83 | when :array 84 | new_content = build_array_content(actions, node) 85 | corrector.replace(node, new_content) 86 | when :sym 87 | corrector.replace(node, ":#{actions.first}") if actions.size == 1 88 | end 89 | end 90 | 91 | def build_array_content(actions, node) 92 | if percent_array?(node) 93 | "%i[#{actions.join(' ')}]" 94 | else 95 | action_strings = actions.map { |action| ":#{action}" } 96 | "[#{action_strings.join(', ')}]" 97 | end 98 | end 99 | 100 | def percent_array?(node) 101 | node.source.start_with?('%i') 102 | end 103 | end 104 | end 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/resources_without_only.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | class ResourcesWithoutOnly < Base 7 | extend AutoCorrector 8 | MSG = 'Use `only:` option in resource/resources routing.' 9 | 10 | def on_send(node) 11 | return unless resources_method?(node) 12 | return if has_only_option?(node) 13 | 14 | add_offense(node) do |corrector| 15 | autocorrect(corrector, node) 16 | end 17 | end 18 | 19 | private 20 | 21 | def resources_method?(node) 22 | node.method?(:resource) || node.method?(:resources) 23 | end 24 | 25 | def has_only_option?(node) 26 | node.each_child_node(:hash) do |hash_node| 27 | hash_node.each_pair do |key_node, _value_node| 28 | next unless key_node.sym_type? 29 | 30 | return true if key_node.value == :only 31 | end 32 | end 33 | 34 | false 35 | end 36 | 37 | def autocorrect(corrector, node) # rubocop:disable Metrics/PerceivedComplexity 38 | method_name = node.method_name 39 | default_actions = default_actions_for(method_name) 40 | 41 | except_values = extract_except_values(node) 42 | only_actions = if except_values 43 | default_actions - except_values 44 | else 45 | default_actions 46 | end 47 | 48 | if node.arguments.any?(&:hash_type?) 49 | # 既存のハッシュオプションがある場合 50 | hash_node = node.arguments.find(&:hash_type?) 51 | 52 | if except_values 53 | # except:を削除してonly:に置き換える 54 | replace_except_with_only(corrector, hash_node, only_actions) 55 | else 56 | # only:を追加 57 | corrector.insert_after(hash_node.source_range.end.adjust(begin_pos: -1), ", only: #{format_actions(only_actions)}") 58 | end 59 | else 60 | # オプションがない場合 61 | corrector.insert_after(node.first_argument.source_range, ", only: #{format_actions(only_actions)}") 62 | end 63 | end 64 | 65 | def extract_except_values(node) 66 | node.each_child_node(:hash) do |hash_node| 67 | hash_node.each_pair do |key_node, value_node| 68 | next unless key_node.sym_type? && key_node.value == :except 69 | 70 | return extract_action_symbols(value_node) 71 | end 72 | end 73 | nil 74 | end 75 | 76 | def extract_action_symbols(value_node) 77 | case value_node.type 78 | when :sym 79 | [value_node.value] 80 | when :array 81 | value_node.children.map do |child| 82 | child.value if child.sym_type? 83 | end.compact 84 | else 85 | [] 86 | end 87 | end 88 | 89 | def replace_except_with_only(corrector, hash_node, only_actions) 90 | hash_node.each_pair do |key_node, value_node| 91 | next unless key_node.sym_type? && key_node.value == :except 92 | 93 | # except: [...] の範囲全体を取得 94 | range = key_node.source_range.join(value_node.source_range) 95 | 96 | # 後ろにカンマがある場合は、後ろのカンマとスペースを含めて置き換え 97 | if !preceded_by_comma?(hash_node, key_node) && followed_by_comma?(hash_node, value_node) 98 | range = range.adjust(end_pos: 2) # ", "を含める 99 | end 100 | 101 | corrector.replace(range, "only: #{format_actions(only_actions)}") 102 | end 103 | end 104 | 105 | def preceded_by_comma?(hash_node, key_node) 106 | # ハッシュ内でこのキーの前に他のキーがあるかチェック 107 | pairs = hash_node.pairs 108 | index = pairs.find_index { |pair| pair.key == key_node } 109 | index&.positive? 110 | end 111 | 112 | def followed_by_comma?(hash_node, value_node) 113 | # ハッシュ内でこの値の後に他のキーがあるかチェック 114 | pairs = hash_node.pairs 115 | index = pairs.find_index { |pair| pair.value == value_node } 116 | index && index < pairs.size - 1 117 | end 118 | 119 | def default_actions_for(method_name) 120 | case method_name 121 | when :resources 122 | %i[index show new edit create update destroy] 123 | when :resource 124 | %i[show new edit create update destroy] 125 | end 126 | end 127 | 128 | def format_actions(actions) 129 | "[#{actions.map { |action| ":#{action}" }.join(', ')}]" 130 | end 131 | end 132 | end 133 | end 134 | end 135 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/restricted_view_helpers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | class RestrictedViewHelpers < Base 7 | def on_send(node) 8 | return if restricted_methods.empty? 9 | return unless node.receiver.nil? 10 | 11 | method_name = node.method_name.to_s 12 | return unless restricted_methods.key?(method_name) 13 | 14 | message = restricted_methods[method_name] 15 | add_offense(node, message: message) 16 | end 17 | 18 | private 19 | 20 | def restricted_methods 21 | @restricted_methods ||= cop_config['RestrictedMethods'] || {} 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/retry_on_infinite_attempts.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | class RetryOnInfiniteAttempts < Base 7 | MSG = 'Avoid using `Float::INFINITY` or `:unlimited` for attempts in `retry_on` method.' 8 | 9 | def_node_matcher :retry_on_with_infinite_attempts?, <<~PATTERN 10 | (send _ :retry_on ... (hash $...)) 11 | PATTERN 12 | 13 | def on_send(node) 14 | retry_on_with_infinite_attempts?(node) do |pairs| 15 | pairs.each do |pair| 16 | key, value = *pair 17 | if key.value == :attempts && ((value.const_type? && value.source == 'Float::INFINITY') || (value.sym_type? && value.value == :unlimited)) 18 | add_offense(pair) 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/rspec/action_mailer_test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | module Rspec 7 | class ActionMailerTestHelper < Base 8 | MSG = 'Use ActionMailer::TestHelper methods instead of ActionMailer::Base.deliveries.' 9 | MSG_SIZE_COUNT = 'Use `assert_emails(count)` instead of ActionMailer::Base.deliveries.%s.' 10 | MSG_EMPTY = 'Use `assert_no_emails` instead of ActionMailer::Base.deliveries.empty?.' 11 | MSG_ANY = 'Use `assert_emails` to verify emails were sent instead of ActionMailer::Base.deliveries.any?.' 12 | MSG_ACCESS = 'Use `capture_emails { ... }` to access sent emails instead of ActionMailer::Base.deliveries.%s.' 13 | 14 | def_node_matcher :deliveries_call?, <<~PATTERN 15 | (send 16 | (const 17 | (const nil? :ActionMailer) :Base) :deliveries) 18 | PATTERN 19 | 20 | def_node_matcher :deliveries_method_call?, <<~PATTERN 21 | (send 22 | (send 23 | (const 24 | (const nil? :ActionMailer) :Base) :deliveries) $...) 25 | PATTERN 26 | 27 | def_node_matcher :deliveries_array_access?, <<~PATTERN 28 | (send 29 | (send 30 | (const 31 | (const nil? :ActionMailer) :Base) :deliveries) :[] ...) 32 | PATTERN 33 | 34 | def on_send(node) 35 | if deliveries_call?(node) && !node.parent&.send_type? 36 | add_offense(node, message: MSG) 37 | elsif deliveries_method_call?(node) 38 | check_deliveries_method(node) 39 | end 40 | end 41 | 42 | private 43 | 44 | def check_deliveries_method(node) 45 | method_name = node.method_name 46 | message = message_for_method(method_name) 47 | add_offense(node, message: message) if message 48 | end 49 | 50 | def message_for_method(method_name) 51 | case method_name 52 | when :size, :count 53 | format(MSG_SIZE_COUNT, method: method_name) 54 | when :empty? 55 | MSG_EMPTY 56 | when :any? 57 | MSG_ANY 58 | when :first, :last 59 | format(MSG_ACCESS, method: method_name) 60 | when :[] 61 | format(MSG_ACCESS, method: '[]') 62 | when :clear 63 | MSG 64 | end 65 | end 66 | end 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/rspec/active_job_test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | module Rspec 7 | class ActiveJobTestHelper < Base 8 | MSG_HAVE_ENQUEUED_JOB = 'Use `assert_enqueued_jobs(count)` or `assert_enqueued_with` instead of `have_enqueued_job`.' 9 | MSG_HAVE_BEEN_ENQUEUED = 'Use `assert_enqueued_with` instead of `have_been_enqueued`.' 10 | MSG_ENQUEUE_JOB = 'Use `assert_enqueued_jobs` with a block instead of `enqueue_job`.' 11 | MSG_HAVE_PERFORMED_JOB = 'Use `assert_performed_jobs(count)` or `assert_performed_with` instead of `have_performed_job`.' 12 | MSG_HAVE_BEEN_PERFORMED = 'Use `assert_performed_with` instead of `have_been_performed`.' 13 | MSG_PERFORM_JOB = 'Use `assert_performed_jobs` with a block instead of `perform_job`.' 14 | 15 | def_node_matcher :active_job_matcher?, <<~PATTERN 16 | (send nil? ${:have_enqueued_job :have_been_enqueued :enqueue_job 17 | :have_performed_job :have_been_performed :perform_job} ...) 18 | PATTERN 19 | 20 | def on_send(node) 21 | active_job_matcher?(node) do |matcher_name| 22 | message = message_for_matcher(matcher_name) 23 | add_offense(node, message: message) 24 | end 25 | end 26 | 27 | private 28 | 29 | def message_for_matcher(matcher_name) 30 | case matcher_name 31 | in :have_enqueued_job 32 | MSG_HAVE_ENQUEUED_JOB 33 | in :have_been_enqueued 34 | MSG_HAVE_BEEN_ENQUEUED 35 | in :enqueue_job 36 | MSG_ENQUEUE_JOB 37 | in :have_performed_job 38 | MSG_HAVE_PERFORMED_JOB 39 | in :have_been_performed 40 | MSG_HAVE_BEEN_PERFORMED 41 | in :perform_job 42 | MSG_PERFORM_JOB 43 | end 44 | end 45 | end 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/rspec/conditional_example.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | module Rspec 7 | class ConditionalExample < Base 8 | MSG = 'Avoid conditional expectations. Always assert expectations unconditionally.' 9 | 10 | def on_if(node) 11 | # elsif節がある場合は検出しない(複雑な条件分岐) 12 | return if node.elsif? 13 | # else節がある場合も検出しない(if/else, unless/elseなど) 14 | return if node.else_branch 15 | 16 | # 修飾子形式でもブロック形式でも、if_branchにexpectがあれば検出 17 | add_offense(node) if contains_expect?(node.if_branch) 18 | end 19 | 20 | private 21 | 22 | def contains_expect?(node) 23 | return false unless node 24 | 25 | # expectを直接呼んでいるか、子ノードにexpectがあるかをチェック 26 | expect_call?(node) || check_children_for_expect(node) 27 | end 28 | 29 | def check_children_for_expect(node) 30 | return false unless node.is_a?(RuboCop::AST::Node) 31 | 32 | node.each_child_node.any? { |child| contains_expect?(child) } 33 | end 34 | 35 | def expect_call?(node) 36 | node.send_type? && node.method_name == :expect && node.receiver.nil? 37 | end 38 | end 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/rspec/no_method_call_in_expectation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | module Rspec 7 | class NoMethodCallInExpectation < Base 8 | MSG = 'Use literal values instead of method calls in expectations. Use "%s" with literal values.' 9 | 10 | def on_send(node) 11 | return unless expectation_call?(node) 12 | 13 | matcher_node = node.arguments.first 14 | return unless matcher_node&.send_type? 15 | 16 | matcher_name = matcher_node.method_name.to_s 17 | return unless target_matchers.include?(matcher_name) 18 | 19 | check_matcher_arguments(matcher_node, matcher_name) 20 | end 21 | 22 | private 23 | 24 | def check_matcher_arguments(matcher_node, matcher_name) 25 | matcher_node.arguments.each do |arg| 26 | next if literal_value?(arg) 27 | next if variable_reference?(arg) 28 | next if allowed_method?(arg) 29 | next if allowed_interpolation?(arg) 30 | 31 | add_offense(arg, message: format(MSG, matcher: matcher_name)) 32 | end 33 | end 34 | 35 | def expectation_call?(node) 36 | %i[to not_to to_not].include?(node.method_name) && 37 | node.receiver&.send_type? && 38 | node.receiver.method_name == :expect 39 | end 40 | 41 | def literal_value?(node) 42 | return true if node.nil? 43 | 44 | simple_literal?(node) || complex_literal?(node) 45 | end 46 | 47 | def simple_literal?(node) 48 | %i[str sym int float true false nil regexp].include?(node.type) 49 | end 50 | 51 | def complex_literal?(node) 52 | case node.type 53 | when :array 54 | all_children_literal?(node) 55 | when :hash 56 | all_hash_pairs_literal?(node) 57 | else 58 | false 59 | end 60 | end 61 | 62 | def all_children_literal?(node) 63 | node.children.all? { |child| literal_value?(child) || variable_reference?(child) } 64 | end 65 | 66 | def all_hash_pairs_literal?(node) 67 | node.children.all? do |pair| 68 | key, value = pair.children 69 | (literal_value?(key) || variable_reference?(key)) && 70 | (literal_value?(value) || variable_reference?(value) || allowed_method?(value)) 71 | end 72 | end 73 | 74 | def variable_reference?(node) 75 | return false unless node 76 | 77 | case node.type 78 | when :lvar, :ivar, :cvar, :gvar, :const 79 | # ローカル変数、インスタンス変数、クラス変数、グローバル変数、定数 80 | true 81 | when :send 82 | # レシーバーがなく、引数もないsendノードは変数参照の可能性が高い 83 | # ただし、カッコがある場合(()付き)はメソッド呼び出しとして扱う 84 | # node.loc.selectorはメソッド名の位置、node.loc.beginはカッコの開始位置 85 | node.receiver.nil? && node.arguments.empty? && !node.loc.begin 86 | else 87 | false 88 | end 89 | end 90 | 91 | def allowed_method?(node) 92 | return false unless node.send_type? 93 | 94 | method_name = build_method_call_string(node) 95 | allowed_patterns.any? { |pattern| method_name.match?(pattern) } 96 | end 97 | 98 | def build_method_call_string(node) 99 | return node.method_name.to_s if node.receiver.nil? 100 | 101 | if node.receiver.const_type? 102 | "#{node.receiver.const_name}.#{node.method_name}" 103 | elsif node.receiver.send_type? 104 | "#{build_method_call_string(node.receiver)}.#{node.method_name}" 105 | else 106 | node.method_name.to_s 107 | end 108 | end 109 | 110 | def target_matchers 111 | cop_config['TargetMatchers'] || [] 112 | end 113 | 114 | def allowed_patterns 115 | patterns = cop_config['AllowedPatterns'] || [] 116 | patterns.map { |pattern| Regexp.new(pattern) } 117 | end 118 | 119 | def allowed_interpolation?(node) 120 | return false unless node.dstr_type? 121 | 122 | node.children.all? do |child| 123 | # str type nodes are literal strings in interpolation 124 | next true if child.str_type? 125 | 126 | # Check if the interpolated content is an allowed method 127 | if child.begin_type? && child.children.size == 1 128 | interpolated_node = child.children.first 129 | variable_reference?(interpolated_node) || allowed_method?(interpolated_node) 130 | else 131 | false 132 | end 133 | end 134 | end 135 | end 136 | end 137 | end 138 | end 139 | end 140 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/rspec/redundant_let_reference.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | module Rspec 7 | class RedundantLetReference < Base 8 | MSG_BEFORE = 'Use `let!` instead of referencing `let` in `before` block, or move the setup directly into `before`.' 9 | MSG_IT = 'Use `let!` for eager evaluation or move the setup directly into the test block instead of just referencing `let`.' 10 | 11 | def_node_matcher :before_block?, <<~PATTERN 12 | (block 13 | (send nil? :before ...) 14 | ... 15 | $_) 16 | PATTERN 17 | 18 | def_node_matcher :it_block?, <<~PATTERN 19 | (block 20 | (send nil? {:it :specify :example} ...) 21 | ... 22 | $_) 23 | PATTERN 24 | 25 | def_node_matcher :let_definition?, <<~PATTERN 26 | (block 27 | (send nil? {:let :let!} (sym $_)) 28 | ...) 29 | PATTERN 30 | 31 | def on_block(node) 32 | if (body = before_block?(node)) 33 | check_block_body(body, node, MSG_BEFORE) 34 | elsif (body = it_block?(node)) 35 | check_block_body(body, node, MSG_IT) 36 | end 37 | end 38 | 39 | private 40 | 41 | def check_block_body(body_node, block_node, message) 42 | return unless body_node 43 | 44 | if body_node.begin_type? 45 | body_node.children.each do |child_node| 46 | check_single_statement(child_node, block_node, message) 47 | end 48 | else 49 | check_single_statement(body_node, block_node, message) 50 | end 51 | end 52 | 53 | def check_single_statement(node, block_node, message) 54 | return unless node.send_type? && node.receiver.nil? && node.arguments.empty? 55 | 56 | variable_name = node.method_name 57 | return unless let_variable_defined?(variable_name, block_node) 58 | 59 | add_offense(node, message: message) 60 | end 61 | 62 | def let_variable_defined?(variable_name, block_node) 63 | parent_node = block_node.parent 64 | while parent_node 65 | parent_node.each_child_node do |child| 66 | if let_definition?(child) == variable_name 67 | return true 68 | end 69 | end 70 | parent_node = parent_node.parent 71 | end 72 | false 73 | end 74 | end 75 | end 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/rspec/redundant_perform_enqueued_jobs.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | module Rspec 7 | class RedundantPerformEnqueuedJobs < Base 8 | MSG = '`%s` internally uses `perform_enqueued_jobs`, so the outer `perform_enqueued_jobs` is redundant.' 9 | 10 | def_node_matcher :perform_enqueued_jobs_block?, <<~PATTERN 11 | (block 12 | (send nil? :perform_enqueued_jobs ...) 13 | ... 14 | $_) 15 | PATTERN 16 | 17 | def_node_matcher :action_mailer_test_helper_method?, <<~PATTERN 18 | (block 19 | (send nil? {:capture_emails :deliver_enqueued_emails :assert_emails :assert_no_emails} ...) 20 | ...) 21 | PATTERN 22 | 23 | def_node_matcher :action_mailer_test_helper_method_call?, <<~PATTERN 24 | (send nil? {:capture_emails :deliver_enqueued_emails :assert_emails :assert_no_emails} ...) 25 | PATTERN 26 | 27 | def on_block(node) 28 | return unless perform_enqueued_jobs_block?(node) 29 | 30 | block_body = perform_enqueued_jobs_block?(node) 31 | check_for_redundant_calls(block_body, node) 32 | end 33 | 34 | private 35 | 36 | def check_for_redundant_calls(body_node, parent_node) 37 | return unless body_node 38 | 39 | body_node.each_descendant do |descendant| 40 | if action_mailer_test_helper_method?(descendant) 41 | method_name = descendant.send_node.method_name 42 | next unless method_with_perform_enqueued_jobs?(method_name, descendant) 43 | 44 | add_offense( 45 | descendant.send_node, 46 | message: format(MSG, method: method_name) 47 | ) 48 | elsif action_mailer_test_helper_method_call?(descendant) 49 | method_name = descendant.method_name 50 | next unless method_with_perform_enqueued_jobs?(method_name, descendant) 51 | 52 | add_offense( 53 | descendant, 54 | message: format(MSG, method: method_name) 55 | ) 56 | end 57 | end 58 | end 59 | 60 | def method_with_perform_enqueued_jobs?(method_name, node) 61 | case method_name 62 | when :capture_emails, :deliver_enqueued_emails 63 | true 64 | when :assert_emails, :assert_no_emails 65 | node.block_type? || node.block_literal? 66 | else 67 | false 68 | end 69 | end 70 | end 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/simple_form_association.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | class SimpleFormAssociation < Base 7 | MSG = 'Specify the `collection` option' 8 | 9 | def_node_matcher :has_association_call?, <<~PATTERN 10 | (block 11 | (send _ {:simple_form_for | :simple_fields_for | :simple_nested_form_for} ...) 12 | (args (arg _f)) 13 | `$(send (lvar _f) :association $...)) 14 | PATTERN 15 | 16 | def_node_matcher :has_collection_option?, <<~PATTERN 17 | (hash <(pair (sym :collection) _) ...>) 18 | PATTERN 19 | 20 | def on_block(node) 21 | match = has_association_call?(node) 22 | return if match.nil? 23 | 24 | send_node, args_node = match 25 | return if args_node.any? { |nd| has_collection_option?(nd) } 26 | 27 | add_offense(send_node) 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/simple_format.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | class SimpleFormat < Base 7 | MSG = 'simple_format does not escape HTML tags.' 8 | ESCAPE_METHODS = %i[h html_escape].freeze 9 | 10 | def on_send(node) 11 | receiver, method_name, *args = *node 12 | if receiver.nil? && method_name == :simple_format && warning_args?(args) 13 | add_offense(node) 14 | end 15 | end 16 | 17 | private 18 | 19 | def warning_args?(args) 20 | return false if args.first.nil? 21 | return true if args.first.child_nodes.empty? 22 | 23 | _receiver, method_name, *_args = *args.first 24 | !ESCAPE_METHODS.member?(method_name) 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/strftime_restriction.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | class StrftimeRestriction < Base 7 | MSG = 'strftimeではなくI18n.lを使用してローカライズしてください。' 8 | 9 | def on_send(node) 10 | return unless node.method_name == :strftime 11 | return if allowed_pattern?(node) 12 | 13 | add_offense(node.loc.expression, message: MSG) 14 | end 15 | 16 | private 17 | 18 | def allowed_pattern?(node) 19 | return false unless node.arguments.any? 20 | 21 | first_argument = node.arguments.first 22 | return false unless first_argument.str_type? 23 | 24 | pattern = first_argument.value 25 | allowed_patterns.include?(pattern) 26 | end 27 | 28 | def allowed_patterns 29 | @allowed_patterns ||= cop_config['AllowedPatterns'] || [] 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/strict_loading_required.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | class StrictLoadingRequired < Base 7 | MSG = 'Add `.strict_loading` when using `includes` or `preload` with variable assignment' 8 | 9 | def_node_matcher :variable_assignment?, <<~PATTERN 10 | ({lvasgn ivasgn cvasgn gvasgn} _ $_) 11 | PATTERN 12 | 13 | def_node_matcher :includes_or_preload_call?, <<~PATTERN 14 | (send _ {:includes :preload} ...) 15 | PATTERN 16 | 17 | def_node_matcher :strict_loading_call?, <<~PATTERN 18 | (send _ :strict_loading ...) 19 | PATTERN 20 | 21 | def on_lvasgn(node) 22 | check_assignment(node) 23 | end 24 | 25 | def on_ivasgn(node) 26 | check_assignment(node) 27 | end 28 | 29 | def on_cvasgn(node) 30 | check_assignment(node) 31 | end 32 | 33 | def on_gvasgn(node) 34 | check_assignment(node) 35 | end 36 | 37 | private 38 | 39 | def check_assignment(node) 40 | value = variable_assignment?(node) 41 | return unless value 42 | 43 | return unless contains_includes_or_preload?(value) 44 | return if contains_strict_loading?(value) 45 | 46 | add_offense(value) 47 | end 48 | 49 | def contains_includes_or_preload?(node) 50 | return false unless node.is_a?(RuboCop::AST::Node) 51 | 52 | if includes_or_preload_call?(node) 53 | true 54 | elsif node.send_type? 55 | contains_includes_or_preload?(node.receiver) 56 | else 57 | false 58 | end 59 | end 60 | 61 | def contains_strict_loading?(node) 62 | return false unless node.is_a?(RuboCop::AST::Node) 63 | 64 | if strict_loading_call?(node) 65 | true 66 | elsif node.send_type? 67 | contains_strict_loading?(node.receiver) 68 | else 69 | false 70 | end 71 | end 72 | end 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/transaction_requires_new.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | class TransactionRequiresNew < Base 7 | MSG = 'Use `requires_new: true` with `transaction` when `ActiveRecord::Rollback` is raised inside the block.' 8 | 9 | def_node_matcher :transaction_method?, <<~PATTERN 10 | (send _? :transaction ...) 11 | PATTERN 12 | 13 | def_node_search :contains_raise_active_record_rollback?, <<~PATTERN 14 | (send nil? :raise (const (const nil? :ActiveRecord) :Rollback)) 15 | PATTERN 16 | 17 | def_node_matcher :transaction_with_requires_new?, <<~PATTERN 18 | (send _? :transaction (hash <(pair (sym :requires_new) (true)) ...>)) 19 | PATTERN 20 | 21 | def on_block(node) 22 | transaction_node = node.children.first 23 | return unless transaction_method?(transaction_node) && contains_raise_active_record_rollback?(node) 24 | 25 | add_offense(transaction_node, message: MSG) unless transaction_with_requires_new?(transaction_node) 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/ujs_options.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | class UjsOptions < Base 7 | MSG = 'Deprecated: Rails UJS Attributes.' 8 | 9 | def_node_matcher :link_to_ujs_options?, <<~PATTERN 10 | (send nil? #link_to_matcher? ... (hash <(pair (sym #deprecated_link_args?) _) ...>)) 11 | PATTERN 12 | 13 | def_node_matcher :form_ujs_options?, <<~PATTERN 14 | (send nil? #form_helper_matcher? ... (hash <(pair (sym #deprecated_form_args?) _) ...>)) 15 | PATTERN 16 | 17 | def on_send(node) 18 | # TODO: 本来は引数の箇所にのみ警告を表示したい 19 | link_to_ujs_options?(node) { add_offense(node) } 20 | form_ujs_options?(node) { add_offense(node) } 21 | end 22 | 23 | private 24 | 25 | def link_to_matcher?(method_name) 26 | method_name == :link_to 27 | end 28 | 29 | def form_helper_matcher?(method_name) 30 | method_name.to_s.end_with?('_form_with', '_form_for') || 31 | %i[form_with form_for button_to].include?(method_name) 32 | end 33 | 34 | def deprecated_link_args?(args) 35 | %i[method remote].include?(args) 36 | end 37 | 38 | def deprecated_form_args?(args) 39 | %i[remote local].include?(args) 40 | end 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/rubocop/cop/sgcop/unscoped.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module Sgcop 6 | class Unscoped < Base 7 | MSG = 'Do not use `unscoped`' 8 | 9 | def on_send(node) 10 | return if node.method_name != :unscoped 11 | 12 | add_offense(node) 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/sgcop.rb: -------------------------------------------------------------------------------- 1 | require 'rubocop' 2 | require 'sgcop/version' 3 | require 'sgcop/inject' 4 | 5 | Sgcop::Inject.defaults! 6 | 7 | require 'rubocop/cop/sgcop/simple_format' 8 | require 'rubocop/cop/sgcop/request_remote_ip' 9 | require 'rubocop/cop/sgcop/simple_form_association' 10 | require 'rubocop/cop/sgcop/unscoped' 11 | require 'rubocop/cop/sgcop/ujs_options' 12 | require 'rubocop/cop/sgcop/active_job_queue_adapter' 13 | require 'rubocop/cop/sgcop/on_load_arguments' 14 | require 'rubocop/cop/sgcop/transaction_requires_new' 15 | require 'rubocop/cop/sgcop/load_defaults_version_match' 16 | require 'rubocop/cop/sgcop/retry_on_infinite_attempts' 17 | require 'rubocop/cop/sgcop/capybara/sleep' 18 | require 'rubocop/cop/sgcop/capybara/matchers' 19 | require 'rubocop/cop/sgcop/capybara/fragile_selector' 20 | require 'rubocop/cop/sgcop/resource_action_order' 21 | require 'rubocop/cop/sgcop/resources_without_only' 22 | require 'rubocop/cop/sgcop/strict_loading_required' 23 | require 'rubocop/cop/sgcop/enumerize_default_option' 24 | require 'rubocop/cop/sgcop/enumerize_predicates_option' 25 | require 'rubocop/cop/sgcop/hash_fetch_default' 26 | require 'rubocop/cop/sgcop/rspec/action_mailer_test_helper' 27 | require 'rubocop/cop/sgcop/rspec/active_job_test_helper' 28 | require 'rubocop/cop/sgcop/rspec/redundant_perform_enqueued_jobs' 29 | require 'rubocop/cop/sgcop/rspec/redundant_let_reference' 30 | require 'rubocop/cop/sgcop/rspec/conditional_example' 31 | require 'rubocop/cop/sgcop/rspec/no_method_call_in_expectation' 32 | require 'rubocop/cop/sgcop/capybara/spec_stability_check' 33 | require 'rubocop/cop/sgcop/no_accepts_nested_attributes_for' 34 | require 'rubocop/cop/sgcop/nested_resources_without_module' 35 | require 'rubocop/cop/sgcop/error_message_format' 36 | require 'rubocop/cop/sgcop/restricted_view_helpers' 37 | require 'rubocop/cop/sgcop/strftime_restriction' 38 | require 'rubocop/cop/sgcop/i18n_localize_format_string' 39 | require 'rubocop/cop/sgcop/prefer_absolute_path_partial' 40 | -------------------------------------------------------------------------------- /lib/sgcop/inject.rb: -------------------------------------------------------------------------------- 1 | module Sgcop 2 | module Inject 3 | DEFAULT_FILE = File.expand_path('../../config/default.yml', __dir__) 4 | 5 | def self.defaults! 6 | path = File.absolute_path(DEFAULT_FILE) 7 | hash = RuboCop::ConfigLoader.send(:load_yaml_configuration, path) 8 | config = RuboCop::Config.new(hash, path) 9 | puts "configuration from #{DEFAULT_FILE}" if RuboCop::ConfigLoader.debug? 10 | config = RuboCop::ConfigLoader.merge_with_default(config, path) 11 | RuboCop::ConfigLoader.instance_variable_set(:@default_configuration, config) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/sgcop/version.rb: -------------------------------------------------------------------------------- 1 | module Sgcop 2 | VERSION = '1.24.6'.freeze 3 | end 4 | -------------------------------------------------------------------------------- /sgcop.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('lib', __dir__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require 'sgcop/version' 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = 'sgcop' 7 | spec.version = Sgcop::VERSION 8 | spec.authors = ['maedana'] 9 | spec.email = ['maedana@sonicgarden.jp'] 10 | 11 | spec.summary = 'SonicGarden標準コーディングスタイル' 12 | spec.description = '各プロジェクトのrubocopのデフォルト設定とすることを目的としている' 13 | spec.homepage = 'https://github.com/SonicGarden/sgcop' 14 | spec.license = 'MIT' 15 | 16 | # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host' 17 | # to allow pushing to a single host or delete this section to allow pushing to any host. 18 | if spec.respond_to?(:metadata) 19 | spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'" 20 | else 21 | raise "RubyGems 2.0 or newer is required to protect against " \ 22 | "public gem pushes." 23 | end 24 | 25 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 26 | spec.bindir = 'exe' 27 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 28 | spec.require_paths = ['lib'] 29 | 30 | spec.add_dependency 'rubocop', '~> 1.81.1' 31 | spec.add_dependency 'rubocop-capybara', '~> 2.22.0' 32 | spec.add_dependency 'rubocop-factory_bot', '~> 2.27.0' 33 | spec.add_dependency 'rubocop-performance', '~> 1.26.0' 34 | spec.add_dependency 'rubocop-rails', '~> 2.33.3' 35 | spec.add_dependency 'rubocop-rake', '~> 0.7.1' 36 | spec.add_dependency 'rubocop-rspec', '~> 3.7.0' 37 | spec.add_dependency 'rubocop-rspec_rails', '~> 2.31.0' 38 | end 39 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/active_job_queue_adapter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::ActiveJobQueueAdapter, :config do 4 | subject(:cop) { described_class.new } 5 | 6 | it 'registers an offense when setting config.active_job.queue_adapter in an initializer' do 7 | expect_offense(<<~RUBY, 'config/initializers/activejob.rb') 8 | Rails.application.configure do |config| 9 | config.active_job.queue_adapter = :sidekiq 10 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/ActiveJobQueueAdapter: Do not set config.active_job.queue_adapter in config/initializers. 11 | end 12 | RUBY 13 | end 14 | 15 | it 'does not register an offense when setting config.active_job.queue_adapter in an config/application.rb' do 16 | expect_no_offenses(<<~RUBY, 'config/application.rb') 17 | Rails.application.configure do |config| 18 | config.active_job.queue_adapter = :sidekiq 19 | end 20 | RUBY 21 | end 22 | 23 | it 'does not register an offense when setting config.active_job.queue_adapter outside of an initializer' do 24 | expect_no_offenses(<<~RUBY) 25 | class MyJob < ActiveJob::Base 26 | queue_adapter :sidekiq 27 | end 28 | RUBY 29 | end 30 | 31 | it 'does not register an offense when setting a different config option in an initializer' do 32 | expect_no_offenses(<<~RUBY) 33 | config.active_job.queue_name_prefix = 'my_app' 34 | RUBY 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/capybara/fragile_selector_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::Capybara::FragileSelector, :config do 4 | it 'registers an offense for CSS class selector with find' do 5 | expect_offense(<<~RUBY) 6 | find('.btn-primary').click 7 | ^^^^^^^^^^^^^^ Avoid using CSS class selectors as they are fragile and break when styles change. Use data attributes or accessible attributes instead. 8 | RUBY 9 | end 10 | 11 | it 'registers an offense for ID selector with find' do 12 | expect_offense(<<~RUBY) 13 | find('#user_email').set('x') 14 | ^^^^^^^^^^^^^ Avoid using ID selectors as they are fragile and break when markup changes. Use data attributes or accessible attributes instead. 15 | RUBY 16 | end 17 | 18 | it 'registers an offense for partial href matching' do 19 | expect_offense(<<~RUBY) 20 | find('a[href*="edit"]').click 21 | ^^^^^^^^^^^^^^^^^ Avoid using partial href matching as it is fragile. Use data attributes or accessible attributes instead. 22 | RUBY 23 | end 24 | 25 | it 'registers an offense for CSS class selector in within block' do 26 | expect_offense(<<~RUBY) 27 | within 'div.tw\\:bg-base-200' do 28 | ^^^^^^^^^^^^^^^^^^^^^ Avoid using CSS class selectors in within blocks. Use data attributes or accessible attributes instead. 29 | click_on 'Submit' 30 | end 31 | RUBY 32 | end 33 | 34 | it 'registers an offense for element with class selector' do 35 | expect_offense(<<~RUBY) 36 | find('div.container').click 37 | ^^^^^^^^^^^^^^^ Avoid using CSS class selectors as they are fragile and break when styles change. Use data attributes or accessible attributes instead. 38 | RUBY 39 | end 40 | 41 | it 'registers an offense for class selector with all' do 42 | expect_offense(<<~RUBY) 43 | all('.item').each { |item| item.click } 44 | ^^^^^^^ Avoid using CSS class selectors as they are fragile and break when styles change. Use data attributes or accessible attributes instead. 45 | RUBY 46 | end 47 | 48 | it 'registers an offense for ID selector with first' do 49 | expect_offense(<<~RUBY) 50 | first('#header').click 51 | ^^^^^^^^^ Avoid using ID selectors as they are fragile and break when markup changes. Use data attributes or accessible attributes instead. 52 | RUBY 53 | end 54 | 55 | it 'does not register an offense for data attribute selector' do 56 | expect_no_offenses(<<~RUBY) 57 | find('[data-test="submit-button"]').click 58 | RUBY 59 | end 60 | 61 | it 'does not register an offense for role attribute selector' do 62 | expect_no_offenses(<<~RUBY) 63 | find('[role="button"]').click 64 | RUBY 65 | end 66 | 67 | it 'does not register an offense for aria-label selector' do 68 | expect_no_offenses(<<~RUBY) 69 | find('[aria-label="Close"]').click 70 | RUBY 71 | end 72 | 73 | it 'does not register an offense for exact href matching' do 74 | expect_no_offenses(<<~RUBY) 75 | find('a[href="/users/edit"]').click 76 | RUBY 77 | end 78 | 79 | it 'does not register an offense for text content selector' do 80 | expect_no_offenses(<<~RUBY) 81 | click_on 'Submit' 82 | RUBY 83 | end 84 | 85 | it 'registers an offense for xpath selector' do 86 | expect_offense(<<~RUBY) 87 | find(:xpath, '//button[@type="submit"]').click 88 | ^^^^^^ Avoid using XPath selectors as they are fragile and break when markup changes. Use data attributes or accessible attributes instead. 89 | RUBY 90 | end 91 | 92 | it 'registers an offense for xpath selector in within block' do 93 | expect_offense(<<~RUBY) 94 | within :xpath, '//div[@class="container"]' do 95 | ^^^^^^ Avoid using XPath selectors as they are fragile and break when markup changes. Use data attributes or accessible attributes instead. 96 | click_on 'Submit' 97 | end 98 | RUBY 99 | end 100 | 101 | it 'does not register an offense for non-string arguments' do 102 | expect_no_offenses(<<~RUBY) 103 | find(some_variable).click 104 | RUBY 105 | end 106 | 107 | context 'with have_css matcher' do 108 | it 'registers an offense for CSS class selector' do 109 | expect_offense(<<~RUBY) 110 | expect(page).to have_css('.btn-primary') 111 | ^^^^^^^^^^^^^^ Avoid using CSS class selectors as they are fragile and break when styles change. Use data attributes or accessible attributes instead. 112 | RUBY 113 | end 114 | 115 | it 'registers an offense for ID selector' do 116 | expect_offense(<<~RUBY) 117 | expect(page).to have_css('#user_email') 118 | ^^^^^^^^^^^^^ Avoid using ID selectors as they are fragile and break when markup changes. Use data attributes or accessible attributes instead. 119 | RUBY 120 | end 121 | 122 | it 'registers an offense for element with class selector' do 123 | expect_offense(<<~RUBY) 124 | expect(page).to have_css('div.container') 125 | ^^^^^^^^^^^^^^^ Avoid using CSS class selectors as they are fragile and break when styles change. Use data attributes or accessible attributes instead. 126 | RUBY 127 | end 128 | 129 | it 'registers an offense for partial href matching' do 130 | expect_offense(<<~RUBY) 131 | expect(page).to have_css('a[href*="edit"]') 132 | ^^^^^^^^^^^^^^^^^ Avoid using partial href matching as it is fragile. Use data attributes or accessible attributes instead. 133 | RUBY 134 | end 135 | 136 | it 'registers an offense for xpath selector' do 137 | expect_offense(<<~RUBY) 138 | expect(page).to have_css(:xpath, '//button[@type="submit"]') 139 | ^^^^^^ Avoid using XPath selectors as they are fragile and break when markup changes. Use data attributes or accessible attributes instead. 140 | RUBY 141 | end 142 | 143 | it 'does not register an offense for data attribute selector' do 144 | expect_no_offenses(<<~RUBY) 145 | expect(page).to have_css('[data-test="submit-button"]') 146 | RUBY 147 | end 148 | 149 | it 'does not register an offense for role attribute selector' do 150 | expect_no_offenses(<<~RUBY) 151 | expect(page).to have_css('[role="button"]') 152 | RUBY 153 | end 154 | 155 | it 'does not register an offense for aria-label selector' do 156 | expect_no_offenses(<<~RUBY) 157 | expect(page).to have_css('[aria-label="Close"]') 158 | RUBY 159 | end 160 | 161 | it 'does not register an offense for exact href matching' do 162 | expect_no_offenses(<<~RUBY) 163 | expect(page).to have_css('a[href="/users/edit"]') 164 | RUBY 165 | end 166 | end 167 | end 168 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/capybara/matchers_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::Capybara::Matchers, :config do 4 | let(:cop_config) do 5 | { 6 | 'PreferredMethods' => { 7 | 'have_text' => 'have_content' 8 | } 9 | } 10 | end 11 | 12 | it 'registers an offense for have_text' do 13 | expect_offense(<<~RUBY) 14 | it do 15 | expect(page).to have_text 'test' 16 | ^^^^^^^^^ Prefer `have_content` over `have_text`. 17 | end 18 | RUBY 19 | 20 | expect_correction(<<~RUBY) 21 | it do 22 | expect(page).to have_content 'test' 23 | end 24 | RUBY 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/capybara/sleep_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::Capybara::Sleep do 4 | subject(:cop) { RuboCop::Cop::Sgcop::Capybara::Sleep.new } 5 | 6 | it 'registers an offense for an sleep call with no receiver' do 7 | expect_offense(<<~RUBY) 8 | it do 9 | click_on 'button' 10 | sleep 10 11 | ^^^^^^^^ Sgcop/Capybara/Sleep: Do not use `sleep` in spec. 12 | end 13 | RUBY 14 | end 15 | 16 | it 'does register an offense for explicit Kernel.sleep calls' do 17 | expect_offense(<<~RUBY) 18 | it do 19 | click_on 'button' 20 | Kernel.sleep 10 21 | ^^^^^^^^^^^^^^^ Sgcop/Capybara/Sleep: Do not use `sleep` in spec. 22 | end 23 | RUBY 24 | end 25 | 26 | it 'does not register an offense for an explicit sleep call on an object' do 27 | expect_no_offenses('Object.new.sleep') 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/capybara/spec_stability_check_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe RuboCop::Cop::Sgcop::Capybara::SpecStabilityCheck do 6 | subject(:cop) { RuboCop::Cop::Sgcop::Capybara::SpecStabilityCheck.new } 7 | 8 | context 'with default configuration' do 9 | it 'registers an offense when assert_enqueued_emails block lacks wait matcher' do 10 | expect_offense(<<~RUBY) 11 | assert_enqueued_emails 1 do 12 | ^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/Capybara/SpecStabilityCheck: ページの変化を伴う非同期処理後には、適切な待機処理(例: expect(page).to have_content('更新しました。'))を追加してテストを安定させましょう 13 | within('tbody tr', text: '第2希望') do 14 | click_button '確定する' 15 | end 16 | end 17 | RUBY 18 | end 19 | 20 | it 'does not register an offense when assert_enqueued_emails block has wait matcher' do 21 | expect_no_offenses(<<~RUBY) 22 | assert_enqueued_emails 1 do 23 | within('tbody tr', text: '第2希望') do 24 | click_button '確定する' 25 | expect(page).to have_content('更新しました。') 26 | end 27 | end 28 | RUBY 29 | end 30 | 31 | it 'registers an offense for form submission without wait matcher' do 32 | expect_offense(<<~RUBY) 33 | assert_enqueued_emails 1 do 34 | ^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/Capybara/SpecStabilityCheck: ページの変化を伴う非同期処理後には、適切な待機処理(例: expect(page).to have_content('更新しました。'))を追加してテストを安定させましょう 35 | fill_in 'Email', with: 'user@example.com' 36 | click_button '送信' 37 | end 38 | RUBY 39 | end 40 | 41 | it 'does not register an offense for form submission with wait matcher' do 42 | expect_no_offenses(<<~RUBY) 43 | assert_enqueued_emails 1 do 44 | fill_in 'Email', with: 'user@example.com' 45 | click_button '送信' 46 | expect(page).to have_content('送信しました') 47 | end 48 | RUBY 49 | end 50 | 51 | it 'registers an offense for cancellation action without wait matcher' do 52 | expect_offense(<<~RUBY) 53 | assert_no_emails do 54 | ^^^^^^^^^^^^^^^^ Sgcop/Capybara/SpecStabilityCheck: ページの変化を伴う非同期処理後には、適切な待機処理(例: expect(page).to have_content('更新しました。'))を追加してテストを安定させましょう 55 | click_button 'キャンセル' 56 | end 57 | RUBY 58 | end 59 | 60 | it 'works with different wait matchers' do 61 | expect_no_offenses(<<~RUBY) 62 | assert_enqueued_emails 1 do 63 | fill_in 'Name', with: 'John' 64 | click_button '送信' 65 | expect(page).to have_css('.success-message') 66 | end 67 | RUBY 68 | 69 | expect_no_offenses(<<~RUBY) 70 | assert_enqueued_emails 1 do 71 | select '重要', from: 'Priority' 72 | click_button '保存' 73 | expect(page).to have_selector('#notification') 74 | end 75 | RUBY 76 | end 77 | 78 | it 'ignores non-watched methods' do 79 | expect_no_offenses(<<~RUBY) 80 | perform_enqueued_jobs do 81 | fill_in 'Message', with: 'Test' 82 | click_button '送信' 83 | end 84 | RUBY 85 | end 86 | end 87 | 88 | context 'with custom configuration' do 89 | subject(:cop) do 90 | config = RuboCop::Config.new( 91 | 'Sgcop/Capybara/SpecStabilityCheck' => { 92 | 'WatchedMethods' => %w[assert_enqueued_jobs], 93 | 'WaitMatcherPatterns' => ['^have_text$', '^custom_matcher$'], 94 | } 95 | ) 96 | RuboCop::Cop::Sgcop::Capybara::SpecStabilityCheck.new(config) 97 | end 98 | 99 | it 'works with custom watched methods for background jobs' do 100 | expect_offense(<<~RUBY) 101 | assert_enqueued_jobs 1 do 102 | ^^^^^^^^^^^^^^^^^^^^^^ ページの変化を伴う非同期処理後には、適切な待機処理(例: expect(page).to have_content('更新しました。'))を追加してテストを安定させましょう 103 | click_button 'バックグラウンド処理開始' 104 | end 105 | RUBY 106 | end 107 | 108 | it 'works with custom wait matchers for background jobs' do 109 | expect_no_offenses(<<~RUBY) 110 | assert_enqueued_jobs 1 do 111 | click_button 'バックグラウンド処理開始' 112 | expect(page).to have_text('処理を開始しました') 113 | end 114 | RUBY 115 | 116 | expect_no_offenses(<<~RUBY) 117 | assert_enqueued_jobs 1 do 118 | check 'Send notification' 119 | click_button 'Save' 120 | expect(page).to custom_matcher 121 | end 122 | RUBY 123 | end 124 | 125 | it 'ignores default matchers when custom ones are configured' do 126 | expect_offense(<<~RUBY) 127 | assert_enqueued_jobs 1 do 128 | ^^^^^^^^^^^^^^^^^^^^^^ ページの変化を伴う非同期処理後には、適切な待機処理(例: expect(page).to have_content('更新しました。'))を追加してテストを安定させましょう 129 | click_button 'Start processing' 130 | expect(page).to have_content('Processing started') # have_contentはカスタム設定に含まれていない 131 | end 132 | RUBY 133 | end 134 | end 135 | end 136 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/enumerize_default_option_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::EnumerizeDefaultOption do 4 | subject(:cop) { described_class.new } 5 | 6 | it 'registers an offense when using default option in enumerize' do 7 | expect_offense(<<~RUBY) 8 | enumerize :role, in: ROLES, default: :operator 9 | ^^^^^^^^^^^^^^^^^^ Sgcop/EnumerizeDefaultOption: Do not use `default` option in `enumerize`. Use database-level default values instead. 10 | RUBY 11 | end 12 | 13 | it 'registers an offense when using default option with other options' do 14 | expect_offense(<<~RUBY) 15 | enumerize :status, in: %w[active inactive], default: 'active', scope: true 16 | ^^^^^^^^^^^^^^^^^ Sgcop/EnumerizeDefaultOption: Do not use `default` option in `enumerize`. Use database-level default values instead. 17 | RUBY 18 | end 19 | 20 | it 'registers an offense when using default option in class method call' do 21 | expect_offense(<<~RUBY) 22 | class User < ApplicationRecord 23 | enumerize :role, in: ROLES, default: :operator 24 | ^^^^^^^^^^^^^^^^^^ Sgcop/EnumerizeDefaultOption: Do not use `default` option in `enumerize`. Use database-level default values instead. 25 | end 26 | RUBY 27 | end 28 | 29 | it 'does not register an offense when enumerize is used without default option' do 30 | expect_no_offenses(<<~RUBY) 31 | enumerize :role, in: ROLES 32 | RUBY 33 | end 34 | 35 | it 'does not register an offense when enumerize is used with other options' do 36 | expect_no_offenses(<<~RUBY) 37 | enumerize :status, in: %w[active inactive], scope: true 38 | RUBY 39 | end 40 | 41 | it 'does not register an offense when enumerize is used without options' do 42 | expect_no_offenses(<<~RUBY) 43 | enumerize :role, in: ROLES 44 | RUBY 45 | end 46 | 47 | it 'does not register an offense for other method calls with default' do 48 | expect_no_offenses(<<~RUBY) 49 | some_method :param, default: :value 50 | RUBY 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/enumerize_predicates_option_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::EnumerizePredicatesOption do 4 | subject(:cop) { described_class.new(config) } 5 | let(:config) { RuboCop::Config.new(cop_config) } 6 | let(:cop_config) { { 'Sgcop/EnumerizePredicatesOption' => { 'AllowWithPrefix' => allow_with_prefix } } } 7 | let(:allow_with_prefix) { false } 8 | 9 | context 'when AllowWithPrefix is false' do 10 | it 'registers an offense when using predicates: true' do 11 | expect_offense(<<~RUBY) 12 | enumerize :status, in: %w[active inactive], predicates: true 13 | ^^^^^^^^^^^^^^^^ Do not use `predicates` option in `enumerize`. 14 | RUBY 15 | end 16 | 17 | it 'registers an offense when using predicates: false' do 18 | expect_offense(<<~RUBY) 19 | enumerize :status, in: %w[active inactive], predicates: false 20 | ^^^^^^^^^^^^^^^^^ Do not use `predicates` option in `enumerize`. 21 | RUBY 22 | end 23 | 24 | it 'registers an offense when using predicates with prefix option' do 25 | expect_offense(<<~RUBY) 26 | enumerize :status, in: %w[active inactive], predicates: { prefix: true } 27 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not use `predicates` option in `enumerize`. 28 | RUBY 29 | end 30 | 31 | it 'registers an offense when using predicates with other hash options' do 32 | expect_offense(<<~RUBY) 33 | enumerize :status, in: %w[active inactive], predicates: { only: [:active] } 34 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not use `predicates` option in `enumerize`. 35 | RUBY 36 | end 37 | 38 | it 'registers an offense when using predicates in a class' do 39 | expect_offense(<<~RUBY) 40 | class User < ApplicationRecord 41 | enumerize :role, in: ROLES, predicates: true 42 | ^^^^^^^^^^^^^^^^ Do not use `predicates` option in `enumerize`. 43 | end 44 | RUBY 45 | end 46 | 47 | it 'does not register an offense when enumerize is used without predicates option' do 48 | expect_no_offenses(<<~RUBY) 49 | enumerize :status, in: %w[active inactive] 50 | RUBY 51 | end 52 | 53 | it 'does not register an offense when enumerize is used with other options' do 54 | expect_no_offenses(<<~RUBY) 55 | enumerize :status, in: %w[active inactive], scope: true 56 | RUBY 57 | end 58 | 59 | it 'does not register an offense for other method calls with predicates' do 60 | expect_no_offenses(<<~RUBY) 61 | some_method :param, predicates: true 62 | RUBY 63 | end 64 | end 65 | 66 | context 'when AllowWithPrefix is true' do 67 | let(:allow_with_prefix) { true } 68 | 69 | it 'registers an offense with message suggesting prefix when using predicates: true' do 70 | expect_offense(<<~RUBY) 71 | enumerize :status, in: %w[active inactive], predicates: true 72 | ^^^^^^^^^^^^^^^^ Use `predicates: { prefix: true }` instead of `predicates: true`. 73 | RUBY 74 | end 75 | 76 | it 'registers an offense when using predicates: false' do 77 | expect_offense(<<~RUBY) 78 | enumerize :status, in: %w[active inactive], predicates: false 79 | ^^^^^^^^^^^^^^^^^ Do not use `predicates` option in `enumerize`. 80 | RUBY 81 | end 82 | 83 | it 'does not register an offense when using predicates: { prefix: true }' do 84 | expect_no_offenses(<<~RUBY) 85 | enumerize :status, in: %w[active inactive], predicates: { prefix: true } 86 | RUBY 87 | end 88 | 89 | it 'registers an offense when using predicates with prefix: false' do 90 | expect_offense(<<~RUBY) 91 | enumerize :status, in: %w[active inactive], predicates: { prefix: false } 92 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not use `predicates` option in `enumerize`. 93 | RUBY 94 | end 95 | 96 | it 'registers an offense when using predicates with other hash options' do 97 | expect_offense(<<~RUBY) 98 | enumerize :status, in: %w[active inactive], predicates: { only: [:active] } 99 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not use `predicates` option in `enumerize`. 100 | RUBY 101 | end 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/error_message_format_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::ErrorMessageFormat do 4 | subject(:cop) { RuboCop::Cop::Sgcop::ErrorMessageFormat.new } 5 | 6 | it 'registers an offense for validates with hardcoded message string' do 7 | expect_offense(<<~RUBY) 8 | validates :terms_of_service, acceptance: { message: "must be agreed to" } 9 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/ErrorMessageFormat: Error message should be a symbol. 10 | RUBY 11 | end 12 | 13 | it 'registers an offense for validates with Japanese hardcoded message' do 14 | expect_offense(<<~RUBY) 15 | validates :name, presence: { message: "は必須です" } 16 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/ErrorMessageFormat: Error message should be a symbol. 17 | RUBY 18 | end 19 | 20 | it 'registers an offense for errors.add with hardcoded string' do 21 | expect_offense(<<~RUBY) 22 | errors.add(:name, 'は不正な文字列です。') 23 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/ErrorMessageFormat: Error message should be a symbol. 24 | RUBY 25 | end 26 | 27 | it 'registers an offense for errors.add with English message' do 28 | expect_offense(<<~RUBY) 29 | errors.add(:email, "is invalid") 30 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/ErrorMessageFormat: Error message should be a symbol. 31 | RUBY 32 | end 33 | 34 | it 'registers an offense for errors.add with interpolated string' do 35 | expect_offense(<<~'RUBY') 36 | errors.add(:base, "#{field} is required") 37 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/ErrorMessageFormat: Error message should be a symbol. 38 | RUBY 39 | end 40 | 41 | it 'does not register an offense for validates without message option' do 42 | expect_no_offenses(<<~RUBY) 43 | validates :name, presence: true 44 | RUBY 45 | end 46 | 47 | it 'does not register an offense for validates with symbol message' do 48 | expect_no_offenses(<<~RUBY) 49 | validates :name, presence: { message: :blank } 50 | RUBY 51 | end 52 | 53 | it 'does not register an offense for errors.add with symbol' do 54 | expect_no_offenses(<<~RUBY) 55 | errors.add(:name, :invalid) 56 | RUBY 57 | end 58 | 59 | it 'does not register an offense for errors.add with I18n.t' do 60 | expect_no_offenses(<<~RUBY) 61 | errors.add(:name, I18n.t('errors.messages.invalid')) 62 | RUBY 63 | end 64 | 65 | it 'does not register an offense for validates with I18n.t message' do 66 | expect_no_offenses(<<~RUBY) 67 | validates :name, presence: { message: I18n.t('errors.messages.blank') } 68 | RUBY 69 | end 70 | 71 | it 'registers an offense for validates with proc message' do 72 | expect_offense(<<~RUBY) 73 | validates :name, presence: { message: -> { I18n.t('errors.messages.blank') } } 74 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/ErrorMessageFormat: Error message should be a symbol. 75 | RUBY 76 | end 77 | 78 | it 'does not register an offense for errors.add with local variable' do 79 | expect_no_offenses(<<~RUBY) 80 | message = calculate_message 81 | errors.add(:name, message) 82 | RUBY 83 | end 84 | 85 | it 'does not register an offense for errors.add with instance variable' do 86 | expect_no_offenses(<<~RUBY) 87 | errors.add(:name, @message) 88 | RUBY 89 | end 90 | 91 | it 'does not register an offense for errors.add with class variable' do 92 | expect_no_offenses(<<~RUBY) 93 | errors.add(:name, @@message) 94 | RUBY 95 | end 96 | 97 | it 'does not register an offense for errors.add with global variable' do 98 | expect_no_offenses(<<~RUBY) 99 | errors.add(:name, $message) 100 | RUBY 101 | end 102 | 103 | it 'does not register an offense for errors.add with method call' do 104 | expect_no_offenses(<<~RUBY) 105 | errors.add(:name, generate_message) 106 | RUBY 107 | end 108 | 109 | it 'does not register an offense for errors.add with method call with arguments' do 110 | expect_no_offenses(<<~RUBY) 111 | errors.add(:name, format_message(:invalid)) 112 | RUBY 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/hash_fetch_default_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::HashFetchDefault do 4 | subject(:cop) { RuboCop::Cop::Sgcop::HashFetchDefault.new } 5 | 6 | it 'registers an offense when using || with hash access' do 7 | expect_offense(<<~RUBY) 8 | batman[:is_evil] || true 9 | ^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/HashFetchDefault: Use `Hash#fetch` with a default value instead of `||` to preserve falsey values. 10 | RUBY 11 | 12 | expect_correction(<<~RUBY) 13 | batman.fetch(:is_evil, true) 14 | RUBY 15 | end 16 | 17 | it 'registers an offense with method calls on hash' do 18 | expect_offense(<<~RUBY) 19 | config[:enabled] || false 20 | ^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/HashFetchDefault: Use `Hash#fetch` with a default value instead of `||` to preserve falsey values. 21 | RUBY 22 | 23 | expect_correction(<<~RUBY) 24 | config.fetch(:enabled, false) 25 | RUBY 26 | end 27 | 28 | it 'registers an offense with string keys' do 29 | expect_offense(<<~RUBY) 30 | params["value"] || "default" 31 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/HashFetchDefault: Use `Hash#fetch` with a default value instead of `||` to preserve falsey values. 32 | RUBY 33 | 34 | expect_correction(<<~RUBY) 35 | params.fetch("value", "default") 36 | RUBY 37 | end 38 | 39 | it 'registers an offense with complex default values' do 40 | expect_offense(<<~RUBY) 41 | options[:retries] || MAX_RETRIES 42 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/HashFetchDefault: Use `Hash#fetch` with a default value instead of `||` to preserve falsey values. 43 | RUBY 44 | 45 | expect_correction(<<~RUBY) 46 | options.fetch(:retries, MAX_RETRIES) 47 | RUBY 48 | end 49 | 50 | it 'does not register an offense for non-hash access with ||' do 51 | expect_no_offenses(<<~RUBY) 52 | name || "Anonymous" 53 | RUBY 54 | end 55 | 56 | it 'does not register an offense for method calls without ||' do 57 | expect_no_offenses(<<~RUBY) 58 | batman[:is_evil] 59 | RUBY 60 | end 61 | 62 | it 'does not register an offense when already using fetch' do 63 | expect_no_offenses(<<~RUBY) 64 | batman.fetch(:is_evil, true) 65 | RUBY 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/i18n_localize_format_string_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::I18nLocalizeFormatString do 4 | subject(:cop) { described_class.new } 5 | 6 | context 'I18n.lメソッド' do 7 | it '文字列でフォーマットが指定された場合は警告される(日本語)' do 8 | expect_offense(<<~RUBY) 9 | I18n.l(Date.today, format: '%Y年%m月%d日') 10 | ^^^^^^^^^^^ Sgcop/I18nLocalizeFormatString: I18n.lのformatオプションには文字列ではなくシンボルを使用してください。ロケールファイルに定義してシンボルで参照することで、ロケールごとの切り替えが可能になります。 11 | RUBY 12 | end 13 | 14 | it '文字列でフォーマットが指定された場合は警告される(英語)' do 15 | expect_offense(<<~RUBY) 16 | I18n.l(Time.now, format: '%Y-%m-%d %H:%M:%S') 17 | ^^^^^^^^^^^^^^^^^^^ Sgcop/I18nLocalizeFormatString: I18n.lのformatオプションには文字列ではなくシンボルを使用してください。ロケールファイルに定義してシンボルで参照することで、ロケールごとの切り替えが可能になります。 18 | RUBY 19 | end 20 | 21 | it '空文字列でも警告される' do 22 | expect_offense(<<~RUBY) 23 | I18n.l(Date.today, format: '') 24 | ^^ Sgcop/I18nLocalizeFormatString: I18n.lのformatオプションには文字列ではなくシンボルを使用してください。ロケールファイルに定義してシンボルで参照することで、ロケールごとの切り替えが可能になります。 25 | RUBY 26 | end 27 | 28 | it 'シンボルでフォーマットが指定された場合は警告されない' do 29 | expect_no_offenses(<<~RUBY) 30 | I18n.l(Date.today, format: :long) 31 | RUBY 32 | end 33 | 34 | it 'formatオプションがない場合は警告されない' do 35 | expect_no_offenses(<<~RUBY) 36 | I18n.l(Date.today) 37 | RUBY 38 | end 39 | end 40 | 41 | context 'I18n.localizeメソッド' do 42 | it '文字列でフォーマットが指定された場合は警告される' do 43 | expect_offense(<<~RUBY) 44 | I18n.localize(Date.today, format: '%Y年%m月') 45 | ^^^^^^^^ Sgcop/I18nLocalizeFormatString: I18n.lのformatオプションには文字列ではなくシンボルを使用してください。ロケールファイルに定義してシンボルで参照することで、ロケールごとの切り替えが可能になります。 46 | RUBY 47 | end 48 | 49 | it 'シンボルでフォーマットが指定された場合は警告されない' do 50 | expect_no_offenses(<<~RUBY) 51 | I18n.localize(Time.now, format: :short) 52 | RUBY 53 | end 54 | end 55 | 56 | context 'ビューヘルパーのlメソッド' do 57 | it '文字列でフォーマットが指定された場合は警告される(日本語)' do 58 | expect_offense(<<~RUBY) 59 | l(Date.today, format: '%Y年%m月%d日') 60 | ^^^^^^^^^^^ Sgcop/I18nLocalizeFormatString: I18n.lのformatオプションには文字列ではなくシンボルを使用してください。ロケールファイルに定義してシンボルで参照することで、ロケールごとの切り替えが可能になります。 61 | RUBY 62 | end 63 | 64 | it '文字列でフォーマットが指定された場合は警告される(英語)' do 65 | expect_offense(<<~RUBY) 66 | l(Time.now, format: '%H:%M:%S') 67 | ^^^^^^^^^^ Sgcop/I18nLocalizeFormatString: I18n.lのformatオプションには文字列ではなくシンボルを使用してください。ロケールファイルに定義してシンボルで参照することで、ロケールごとの切り替えが可能になります。 68 | RUBY 69 | end 70 | 71 | it 'シンボルでフォーマットが指定された場合は警告されない' do 72 | expect_no_offenses(<<~RUBY) 73 | l(Date.today, format: :long) 74 | RUBY 75 | end 76 | end 77 | 78 | context 'ビューヘルパーのlocalizeメソッド' do 79 | it '文字列でフォーマットが指定された場合は警告される' do 80 | expect_offense(<<~RUBY) 81 | localize(DateTime.now, format: '%Y年%m月%d日 %H時') 82 | ^^^^^^^^^^^^^^^ Sgcop/I18nLocalizeFormatString: I18n.lのformatオプションには文字列ではなくシンボルを使用してください。ロケールファイルに定義してシンボルで参照することで、ロケールごとの切り替えが可能になります。 83 | RUBY 84 | end 85 | 86 | it 'シンボルでフォーマットが指定された場合は警告されない' do 87 | expect_no_offenses(<<~RUBY) 88 | localize(Time.zone.now, format: :custom) 89 | RUBY 90 | end 91 | end 92 | 93 | context '複雑なパターン' do 94 | it '変数を使用したフォーマットは警告されない' do 95 | expect_no_offenses(<<~RUBY) 96 | format_string = '%Y年%m月%d日' 97 | I18n.l(Date.today, format: format_string) 98 | RUBY 99 | end 100 | 101 | it 'formatオプションがない場合は警告されない' do 102 | expect_no_offenses(<<~RUBY) 103 | I18n.l(Date.today) 104 | RUBY 105 | end 106 | end 107 | 108 | context '他のメソッド' do 109 | it 'I18n.tメソッドは対象外' do 110 | expect_no_offenses(<<~RUBY) 111 | I18n.t('date.formats.long', format: '%Y年%m月%d日') 112 | RUBY 113 | end 114 | 115 | it 'strftimeメソッドは対象外' do 116 | expect_no_offenses(<<~RUBY) 117 | Date.today.strftime('%Y年%m月%d日') 118 | RUBY 119 | end 120 | 121 | it 'レシーバーがあるlメソッドは対象外' do 122 | expect_no_offenses(<<~RUBY) 123 | helper.l(Date.today, format: '%Y年%m月%d日') 124 | RUBY 125 | end 126 | end 127 | end 128 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/load_defaults_version_match_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::LoadDefaultsVersionMatch do 4 | subject(:cop) { described_class.new } 5 | 6 | let(:gemfile_lock_content) { ' rails (6.1.3.4)' } 7 | 8 | before do 9 | allow(File).to receive(:exist?).and_return(true) 10 | allow(Dir).to receive(:pwd).and_return('/fakepath') 11 | allow(File).to receive(:read).with('/fakepath/Gemfile.lock').and_return(gemfile_lock_content) 12 | end 13 | 14 | it 'registers an offense when load_defaults version does not match Rails version' do 15 | expect_offense(<<~RUBY) 16 | config.load_defaults 6.0 17 | ^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/LoadDefaultsVersionMatch: The load_defaults version (6.0) does not match the Rails version (6.1) specified in the Gemfile. 18 | RUBY 19 | end 20 | 21 | it 'does not register an offense when load_defaults version matches Rails version' do 22 | expect_no_offenses(<<~RUBY) 23 | config.load_defaults 6.1 24 | RUBY 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/nested_resources_without_module_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::NestedResourcesWithoutModule do 4 | subject(:cop) { RuboCop::Cop::Sgcop::NestedResourcesWithoutModule.new } 5 | 6 | context 'nested resources with module option' do 7 | it 'does not register an offense' do 8 | expect_no_offenses(<<~RUBY, 'config/routes.rb') 9 | Rails.application.routes.draw do 10 | resources :posts, only: %i[index show new edit create update destroy] do 11 | resources :comments, only: %i[create], module: :posts 12 | end 13 | end 14 | RUBY 15 | end 16 | end 17 | 18 | context 'nested resources with namespace option' do 19 | it 'does not register an offense' do 20 | expect_no_offenses(<<~RUBY, 'config/routes.rb') 21 | Rails.application.routes.draw do 22 | resources :posts, only: %i[index show new edit create update destroy] do 23 | resources :comments, only: %i[create], namespace: :posts 24 | end 25 | end 26 | RUBY 27 | end 28 | end 29 | 30 | context 'nested resources with scope option' do 31 | it 'does not register an offense' do 32 | expect_no_offenses(<<~RUBY, 'config/routes.rb') 33 | Rails.application.routes.draw do 34 | resources :posts, only: %i[index show new edit create update destroy] do 35 | resources :comments, only: %i[create], scope: :posts 36 | end 37 | end 38 | RUBY 39 | end 40 | end 41 | 42 | context 'nested resources without module option' do 43 | it 'registers an offense' do 44 | expect_offense(<<~RUBY, 'config/routes.rb') 45 | Rails.application.routes.draw do 46 | resources :posts, only: %i[index show new edit create update destroy] do 47 | resources :comments, only: %i[create] 48 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/NestedResourcesWithoutModule: Use `module:` option in nested resource/resources routing. 49 | end 50 | end 51 | RUBY 52 | end 53 | 54 | it 'registers an offense with other options' do 55 | expect_offense(<<~RUBY, 'config/routes.rb') 56 | Rails.application.routes.draw do 57 | resources :posts, only: %i[index show new edit create update destroy] do 58 | resources :comments, only: %i[create], path: 'remarks' 59 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/NestedResourcesWithoutModule: Use `module:` option in nested resource/resources routing. 60 | end 61 | end 62 | RUBY 63 | end 64 | end 65 | 66 | context 'nested resource (singular) without module option' do 67 | it 'registers an offense' do 68 | expect_offense(<<~RUBY, 'config/routes.rb') 69 | Rails.application.routes.draw do 70 | resources :posts, only: %i[index show new edit create update destroy] do 71 | resource :comment, only: %i[create] 72 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/NestedResourcesWithoutModule: Use `module:` option in nested resource/resources routing. 73 | end 74 | end 75 | RUBY 76 | end 77 | end 78 | 79 | context 'deeply nested resources' do 80 | it 'registers offense for nested resources without module' do 81 | expect_offense(<<~RUBY, 'config/routes.rb') 82 | Rails.application.routes.draw do 83 | resources :posts, only: %i[index show new edit create update destroy] do 84 | resources :comments, only: %i[index create], module: :posts do 85 | resources :reactions, only: %i[create] 86 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/NestedResourcesWithoutModule: Use `module:` option in nested resource/resources routing. 87 | end 88 | end 89 | end 90 | RUBY 91 | end 92 | 93 | it 'does not register offense when all have module' do 94 | expect_no_offenses(<<~RUBY, 'config/routes.rb') 95 | Rails.application.routes.draw do 96 | resources :posts, only: %i[index show new edit create update destroy] do 97 | resources :comments, only: %i[index create], module: :posts do 98 | resources :reactions, only: %i[create], module: :comments 99 | end 100 | end 101 | end 102 | RUBY 103 | end 104 | end 105 | 106 | context 'top-level resources' do 107 | it 'does not register an offense for non-nested resources' do 108 | expect_no_offenses(<<~RUBY, 'config/routes.rb') 109 | Rails.application.routes.draw do 110 | resources :posts, only: %i[index show new edit create update destroy] 111 | resources :comments, only: %i[index create] 112 | end 113 | RUBY 114 | end 115 | end 116 | 117 | context 'multiple nested resources in same block' do 118 | it 'registers offense for each without module' do 119 | expect_offense(<<~RUBY, 'config/routes.rb') 120 | Rails.application.routes.draw do 121 | resources :posts, only: %i[index show new edit create update destroy] do 122 | resources :comments, only: %i[create] 123 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/NestedResourcesWithoutModule: Use `module:` option in nested resource/resources routing. 124 | resources :likes, only: %i[create destroy] 125 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/NestedResourcesWithoutModule: Use `module:` option in nested resource/resources routing. 126 | end 127 | end 128 | RUBY 129 | end 130 | 131 | it 'registers offense only for those without module' do 132 | expect_offense(<<~RUBY, 'config/routes.rb') 133 | Rails.application.routes.draw do 134 | resources :posts, only: %i[index show new edit create update destroy] do 135 | resources :comments, only: %i[create], module: :posts 136 | resources :likes, only: %i[create destroy] 137 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/NestedResourcesWithoutModule: Use `module:` option in nested resource/resources routing. 138 | end 139 | end 140 | RUBY 141 | end 142 | end 143 | 144 | context 'resources with member block' do 145 | it 'does not register offense for member actions' do 146 | expect_no_offenses(<<~RUBY, 'config/routes.rb') 147 | Rails.application.routes.draw do 148 | resources :posts, only: %i[index show new edit create update destroy] do 149 | member do 150 | get :preview 151 | patch :publish 152 | end 153 | end 154 | end 155 | RUBY 156 | end 157 | 158 | it 'does not register offense for nested resources with member block' do 159 | expect_no_offenses(<<~RUBY, 'config/routes.rb') 160 | Rails.application.routes.draw do 161 | resources :posts do 162 | resources :comments, only: %i[create destroy], module: :posts do 163 | member do 164 | patch :approve 165 | patch :reject 166 | end 167 | end 168 | end 169 | end 170 | RUBY 171 | end 172 | end 173 | 174 | context 'resources with collection block' do 175 | it 'does not register offense for collection actions' do 176 | expect_no_offenses(<<~RUBY, 'config/routes.rb') 177 | Rails.application.routes.draw do 178 | resources :posts, only: %i[index show new edit create update destroy] do 179 | collection do 180 | get :search 181 | post :bulk_update 182 | end 183 | end 184 | end 185 | RUBY 186 | end 187 | end 188 | 189 | context 'nested resources with member block without module' do 190 | it 'registers offense for resources without module even with member block' do 191 | expect_offense(<<~RUBY, 'config/routes.rb') 192 | Rails.application.routes.draw do 193 | resources :posts do 194 | resources :comments, only: %i[create destroy] do 195 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/NestedResourcesWithoutModule: Use `module:` option in nested resource/resources routing. 196 | member do 197 | patch :approve 198 | patch :reject 199 | end 200 | end 201 | end 202 | end 203 | RUBY 204 | end 205 | end 206 | end 207 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/no_accepts_nested_attributes_for_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::NoAcceptsNestedAttributesFor do 4 | subject(:cop) { RuboCop::Cop::Sgcop::NoAcceptsNestedAttributesFor.new } 5 | 6 | context 'accepts_nested_attributes_forが使用されている場合' do 7 | it '単一のアソシエーションで違反が検出される' do 8 | expect_offense(<<~RUBY) 9 | class User < ApplicationRecord 10 | accepts_nested_attributes_for :posts 11 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/NoAcceptsNestedAttributesFor: Avoid using `accepts_nested_attributes_for`. Consider using Form Objects instead. 12 | end 13 | RUBY 14 | end 15 | 16 | it '複数のアソシエーションで違反が検出される' do 17 | expect_offense(<<~RUBY) 18 | class User < ApplicationRecord 19 | accepts_nested_attributes_for :posts, :comments 20 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/NoAcceptsNestedAttributesFor: Avoid using `accepts_nested_attributes_for`. Consider using Form Objects instead. 21 | end 22 | RUBY 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/on_load_arguments_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::OnLoadArguments do 4 | subject(:cop) { RuboCop::Cop::Sgcop::OnLoadArguments.new } 5 | 6 | it '許可されていない引数の場合は警告される' do 7 | expect_offense(<<~RUBY) 8 | ActiveSupport.on_load(:aciton_mailer) do 9 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/OnLoadArguments: Do not use unpermitted name as arguments for `ActiveSupport.on_load` 10 | end 11 | RUBY 12 | end 13 | 14 | it '許可された引数の場合は警告されない' do 15 | expect_no_offenses(<<~RUBY) 16 | ActiveSupport.on_load(:active_record) do 17 | end 18 | RUBY 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/prefer_absolute_path_partial_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::PreferAbsolutePathPartial do 4 | subject(:cop) { described_class.new } 5 | 6 | context '文字列でパーシャルを指定' do 7 | it '相対パスは警告' do 8 | expect_offense(<<~RUBY, 'app/views/users/show.html.erb') 9 | render 'form' 10 | ^^^^^^ Sgcop/PreferAbsolutePathPartial: パーシャルは絶対パスで指定してください。 11 | RUBY 12 | 13 | expect_correction(<<~RUBY) 14 | render "users/form" 15 | RUBY 16 | end 17 | 18 | it '絶対パスは警告なし' do 19 | expect_no_offenses(<<~RUBY) 20 | render 'users/form' 21 | RUBY 22 | end 23 | 24 | it 'shared/のパーシャルは警告なし' do 25 | expect_no_offenses(<<~RUBY) 26 | render 'shared/header' 27 | RUBY 28 | end 29 | end 30 | 31 | context 'partial:オプションでパーシャルを指定' do 32 | it '相対パスは警告' do 33 | expect_offense(<<~RUBY, 'app/views/users/show.html.erb') 34 | render partial: 'user_item' 35 | ^^^^^^^^^^^ Sgcop/PreferAbsolutePathPartial: パーシャルは絶対パスで指定してください。 36 | RUBY 37 | 38 | expect_correction(<<~RUBY) 39 | render partial: "users/user_item" 40 | RUBY 41 | end 42 | 43 | it '絶対パスは警告なし' do 44 | expect_no_offenses(<<~RUBY) 45 | render partial: 'users/user_item' 46 | RUBY 47 | end 48 | end 49 | 50 | context 'コレクションレンダリング' do 51 | it '相対パスは警告' do 52 | expect_offense(<<~RUBY, 'app/views/users/show.html.erb') 53 | render partial: 'user', collection: @users 54 | ^^^^^^ Sgcop/PreferAbsolutePathPartial: パーシャルは絶対パスで指定してください。 55 | RUBY 56 | 57 | expect_correction(<<~RUBY) 58 | render partial: "users/user", collection: @users 59 | RUBY 60 | end 61 | 62 | it '絶対パスは警告なし' do 63 | expect_no_offenses(<<~RUBY) 64 | render partial: 'users/user', collection: @users 65 | RUBY 66 | end 67 | end 68 | 69 | context '異なるディレクトリ構造' do 70 | it 'ネストしたディレクトリでも正しく絶対パスに変換' do 71 | expect_offense(<<~RUBY, 'app/views/admin/posts/edit.html.erb') 72 | render 'form' 73 | ^^^^^^ Sgcop/PreferAbsolutePathPartial: パーシャルは絶対パスで指定してください。 74 | RUBY 75 | 76 | expect_correction(<<~RUBY) 77 | render "admin/posts/form" 78 | RUBY 79 | end 80 | end 81 | 82 | context 'components配下のファイル' do 83 | it 'コンポーネント内の相対パスも警告' do 84 | expect_offense(<<~RUBY, 'app/components/button_component.html.erb') 85 | render 'icon' 86 | ^^^^^^ Sgcop/PreferAbsolutePathPartial: パーシャルは絶対パスで指定してください。 87 | RUBY 88 | end 89 | end 90 | 91 | context 'renderメソッド以外' do 92 | it 'render_to_stringは警告なし' do 93 | expect_no_offenses(<<~RUBY) 94 | render_to_string 'form' 95 | RUBY 96 | end 97 | 98 | it 'partial:以外のオプションは警告なし' do 99 | expect_no_offenses(<<~RUBY) 100 | render template: 'form' 101 | RUBY 102 | end 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/request_remote_ip_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::RequestRemoteIp do 4 | subject(:cop) { RuboCop::Cop::Sgcop::RequestRemoteIp.new } 5 | 6 | it 'request.remote_addrが呼ばれていたら警告' do 7 | expect_offense(<<~RUBY) 8 | def log 9 | logger.info request.remote_addr 10 | ^^^^^^^^^^^^^^^^^^^ Sgcop/RequestRemoteIp: Use `request.remote_ip` instead of `request.remote_addr`. 11 | end 12 | RUBY 13 | end 14 | 15 | it 'request.remote_ipが呼ばれていたら警告なし' do 16 | expect_no_offenses(<<~RUBY) 17 | def log 18 | logger.info request.remote_ip 19 | end 20 | RUBY 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/resource_action_order_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::ResourceActionOrder do 4 | subject(:cop) { RuboCop::Cop::Sgcop::ResourceActionOrder.new } 5 | 6 | context 'resources with only option' do 7 | it 'correct order' do 8 | expect_no_offenses(<<~RUBY, 'config/routes.rb') 9 | Rails.application.routes.draw do 10 | resources :users, only: [:index, :show, :create] 11 | end 12 | RUBY 13 | end 14 | 15 | it 'incorrect order' do 16 | expect_offense(<<~RUBY, 'config/routes.rb') 17 | Rails.application.routes.draw do 18 | resources :users, only: [:create, :index, :show] 19 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/ResourceActionOrder: Actions should be ordered in the same sequence as Rails/ActionOrder: index, show, create 20 | end 21 | RUBY 22 | 23 | expect_correction(<<~RUBY) 24 | Rails.application.routes.draw do 25 | resources :users, only: [:index, :show, :create] 26 | end 27 | RUBY 28 | end 29 | 30 | it 'single action' do 31 | expect_no_offenses(<<~RUBY, 'config/routes.rb') 32 | Rails.application.routes.draw do 33 | resources :users, only: :show 34 | end 35 | RUBY 36 | end 37 | 38 | it 'percent notation incorrect order' do 39 | expect_offense(<<~RUBY, 'config/routes.rb') 40 | Rails.application.routes.draw do 41 | resources :users, only: %i[edit show] 42 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/ResourceActionOrder: Actions should be ordered in the same sequence as Rails/ActionOrder: show, edit 43 | end 44 | RUBY 45 | 46 | expect_correction(<<~RUBY) 47 | Rails.application.routes.draw do 48 | resources :users, only: %i[show edit] 49 | end 50 | RUBY 51 | end 52 | 53 | it 'all actions in wrong order' do 54 | expect_offense(<<~RUBY, 'config/routes.rb') 55 | Rails.application.routes.draw do 56 | resources :users, only: [:destroy, :update, :create, :edit, :new, :show, :index] 57 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/ResourceActionOrder: Actions should be ordered in the same sequence as Rails/ActionOrder: index, show, new, edit, create, update, destroy 58 | end 59 | RUBY 60 | 61 | expect_correction(<<~RUBY) 62 | Rails.application.routes.draw do 63 | resources :users, only: [:index, :show, :new, :edit, :create, :update, :destroy] 64 | end 65 | RUBY 66 | end 67 | end 68 | 69 | context 'resource with only option' do 70 | it 'correct order' do 71 | expect_no_offenses(<<~RUBY, 'config/routes.rb') 72 | Rails.application.routes.draw do 73 | resource :profile, only: [:show, :edit, :update] 74 | end 75 | RUBY 76 | end 77 | 78 | it 'incorrect order' do 79 | expect_offense(<<~RUBY, 'config/routes.rb') 80 | Rails.application.routes.draw do 81 | resource :profile, only: [:edit, :show, :update] 82 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/ResourceActionOrder: Actions should be ordered in the same sequence as Rails/ActionOrder: show, edit, update 83 | end 84 | RUBY 85 | 86 | expect_correction(<<~RUBY) 87 | Rails.application.routes.draw do 88 | resource :profile, only: [:show, :edit, :update] 89 | end 90 | RUBY 91 | end 92 | end 93 | 94 | context 'resources with except option' do 95 | it 'correct order (implicit)' do 96 | expect_no_offenses(<<~RUBY, 'config/routes.rb') 97 | Rails.application.routes.draw do 98 | resources :users, except: [:destroy] 99 | end 100 | RUBY 101 | end 102 | 103 | it 'incorrect order in except' do 104 | expect_offense(<<~RUBY, 'config/routes.rb') 105 | Rails.application.routes.draw do 106 | resources :users, except: [:show, :index] 107 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/ResourceActionOrder: Actions should be ordered in the same sequence as Rails/ActionOrder: index, show 108 | end 109 | RUBY 110 | 111 | expect_correction(<<~RUBY) 112 | Rails.application.routes.draw do 113 | resources :users, except: [:index, :show] 114 | end 115 | RUBY 116 | end 117 | end 118 | 119 | context 'not routes.rb file' do 120 | it 'ignores non-routes files' do 121 | expect_no_offenses(<<~RUBY, 'app/controllers/users_controller.rb') 122 | class UsersController < ApplicationController 123 | resources :users, only: [:create, :index, :show] 124 | end 125 | RUBY 126 | end 127 | end 128 | 129 | context 'resources without action options' do 130 | it 'ignores resources without only/except' do 131 | expect_no_offenses(<<~RUBY, 'config/routes.rb') 132 | Rails.application.routes.draw do 133 | resources :users 134 | end 135 | RUBY 136 | end 137 | end 138 | 139 | context 'complex routes structure' do 140 | it 'handles nested resources' do 141 | expect_offense(<<~RUBY, 'config/routes.rb') 142 | Rails.application.routes.draw do 143 | resources :users, only: [:create, :index] do 144 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/ResourceActionOrder: Actions should be ordered in the same sequence as Rails/ActionOrder: index, create 145 | resources :posts, only: [:show, :index] 146 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/ResourceActionOrder: Actions should be ordered in the same sequence as Rails/ActionOrder: index, show 147 | end 148 | end 149 | RUBY 150 | 151 | expect_correction(<<~RUBY) 152 | Rails.application.routes.draw do 153 | resources :users, only: [:index, :create] do 154 | resources :posts, only: [:index, :show] 155 | end 156 | end 157 | RUBY 158 | end 159 | end 160 | end 161 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/resources_without_only_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::ResourcesWithoutOnly do 4 | subject(:cop) { RuboCop::Cop::Sgcop::ResourcesWithoutOnly.new } 5 | 6 | context 'resources with only option' do 7 | it 'does not register an offense' do 8 | expect_no_offenses(<<~RUBY, 'config/routes.rb') 9 | Rails.application.routes.draw do 10 | resources :users, only: [:index, :show] 11 | end 12 | RUBY 13 | end 14 | 15 | it 'does not register an offense with single action' do 16 | expect_no_offenses(<<~RUBY, 'config/routes.rb') 17 | Rails.application.routes.draw do 18 | resources :users, only: :show 19 | end 20 | RUBY 21 | end 22 | 23 | it 'does not register an offense with percent notation' do 24 | expect_no_offenses(<<~RUBY, 'config/routes.rb') 25 | Rails.application.routes.draw do 26 | resources :users, only: %i[show edit] 27 | end 28 | RUBY 29 | end 30 | end 31 | 32 | context 'resources with except option only' do 33 | it 'registers an offense' do 34 | expect_offense(<<~RUBY, 'config/routes.rb') 35 | Rails.application.routes.draw do 36 | resources :users, except: [:destroy] 37 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/ResourcesWithoutOnly: Use `only:` option in resource/resources routing. 38 | end 39 | RUBY 40 | end 41 | end 42 | 43 | context 'resources without any options' do 44 | it 'registers an offense' do 45 | expect_offense(<<~RUBY, 'config/routes.rb') 46 | Rails.application.routes.draw do 47 | resources :users 48 | ^^^^^^^^^^^^^^^^ Sgcop/ResourcesWithoutOnly: Use `only:` option in resource/resources routing. 49 | end 50 | RUBY 51 | end 52 | 53 | it 'registers an offense with other options' do 54 | expect_offense(<<~RUBY, 'config/routes.rb') 55 | Rails.application.routes.draw do 56 | resources :users, path: 'members' 57 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/ResourcesWithoutOnly: Use `only:` option in resource/resources routing. 58 | end 59 | RUBY 60 | end 61 | end 62 | 63 | context 'resource (singular)' do 64 | it 'registers an offense without only option' do 65 | expect_offense(<<~RUBY, 'config/routes.rb') 66 | Rails.application.routes.draw do 67 | resource :profile 68 | ^^^^^^^^^^^^^^^^^ Sgcop/ResourcesWithoutOnly: Use `only:` option in resource/resources routing. 69 | end 70 | RUBY 71 | end 72 | 73 | it 'registers an offense with except option only' do 74 | expect_offense(<<~RUBY, 'config/routes.rb') 75 | Rails.application.routes.draw do 76 | resource :profile, except: [:destroy] 77 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/ResourcesWithoutOnly: Use `only:` option in resource/resources routing. 78 | end 79 | RUBY 80 | end 81 | 82 | it 'does not register an offense with only option' do 83 | expect_no_offenses(<<~RUBY, 'config/routes.rb') 84 | Rails.application.routes.draw do 85 | resource :profile, only: [:show, :edit, :update] 86 | end 87 | RUBY 88 | end 89 | end 90 | 91 | context 'routes in subdirectory' do 92 | it 'registers an offense in config/routes/*.rb' do 93 | expect_offense(<<~RUBY, 'config/routes/admin.rb') 94 | Rails.application.routes.draw do 95 | resources :users 96 | ^^^^^^^^^^^^^^^^ Sgcop/ResourcesWithoutOnly: Use `only:` option in resource/resources routing. 97 | end 98 | RUBY 99 | end 100 | 101 | it 'does not register an offense with only option in subdirectory' do 102 | expect_no_offenses(<<~RUBY, 'config/routes/api.rb') 103 | Rails.application.routes.draw do 104 | resources :users, only: [:index] 105 | end 106 | RUBY 107 | end 108 | end 109 | 110 | context 'nested resources' do 111 | it 'registers offense for both parent and child resources without only' do 112 | expect_offense(<<~RUBY, 'config/routes.rb') 113 | Rails.application.routes.draw do 114 | resources :users do 115 | ^^^^^^^^^^^^^^^^ Sgcop/ResourcesWithoutOnly: Use `only:` option in resource/resources routing. 116 | resources :posts 117 | ^^^^^^^^^^^^^^^^ Sgcop/ResourcesWithoutOnly: Use `only:` option in resource/resources routing. 118 | end 119 | end 120 | RUBY 121 | end 122 | 123 | it 'registers offense only for resources without only option' do 124 | expect_offense(<<~RUBY, 'config/routes.rb') 125 | Rails.application.routes.draw do 126 | resources :users, only: [:index] do 127 | resources :posts 128 | ^^^^^^^^^^^^^^^^ Sgcop/ResourcesWithoutOnly: Use `only:` option in resource/resources routing. 129 | end 130 | end 131 | RUBY 132 | end 133 | end 134 | 135 | context 'resources with both only and except options' do 136 | it 'does not register an offense when only option is present' do 137 | expect_no_offenses(<<~RUBY, 'config/routes.rb') 138 | Rails.application.routes.draw do 139 | resources :users, only: [:index], except: [:show] 140 | end 141 | RUBY 142 | end 143 | end 144 | 145 | context 'autocorrect' do 146 | context 'resources without options' do 147 | it 'adds only option with all default actions' do 148 | expect_offense(<<~RUBY, 'config/routes.rb') 149 | resources :users 150 | ^^^^^^^^^^^^^^^^ Sgcop/ResourcesWithoutOnly: Use `only:` option in resource/resources routing. 151 | RUBY 152 | 153 | expect_correction(<<~RUBY) 154 | resources :users, only: [:index, :show, :new, :edit, :create, :update, :destroy] 155 | RUBY 156 | end 157 | end 158 | 159 | context 'resources with other options' do 160 | it 'adds only option to existing options' do 161 | expect_offense(<<~RUBY, 'config/routes.rb') 162 | resources :users, path: 'members' 163 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/ResourcesWithoutOnly: Use `only:` option in resource/resources routing. 164 | RUBY 165 | 166 | expect_correction(<<~RUBY) 167 | resources :users, path: 'members', only: [:index, :show, :new, :edit, :create, :update, :destroy] 168 | RUBY 169 | end 170 | end 171 | 172 | context 'resource (singular)' do 173 | it 'adds only option with default actions (no index)' do 174 | expect_offense(<<~RUBY, 'config/routes.rb') 175 | resource :profile 176 | ^^^^^^^^^^^^^^^^^ Sgcop/ResourcesWithoutOnly: Use `only:` option in resource/resources routing. 177 | RUBY 178 | 179 | expect_correction(<<~RUBY) 180 | resource :profile, only: [:show, :new, :edit, :create, :update, :destroy] 181 | RUBY 182 | end 183 | end 184 | 185 | context 'with except option' do 186 | it 'converts except to only with remaining actions' do 187 | expect_offense(<<~RUBY, 'config/routes.rb') 188 | resources :users, except: [:destroy] 189 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/ResourcesWithoutOnly: Use `only:` option in resource/resources routing. 190 | RUBY 191 | 192 | expect_correction(<<~RUBY) 193 | resources :users, only: [:index, :show, :new, :edit, :create, :update] 194 | RUBY 195 | end 196 | 197 | it 'converts except with multiple actions' do 198 | expect_offense(<<~RUBY, 'config/routes.rb') 199 | resources :users, except: [:edit, :update, :destroy] 200 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/ResourcesWithoutOnly: Use `only:` option in resource/resources routing. 201 | RUBY 202 | 203 | expect_correction(<<~RUBY) 204 | resources :users, only: [:index, :show, :new, :create] 205 | RUBY 206 | end 207 | 208 | it 'converts except with single action (no array)' do 209 | expect_offense(<<~RUBY, 'config/routes.rb') 210 | resources :users, except: :destroy 211 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/ResourcesWithoutOnly: Use `only:` option in resource/resources routing. 212 | RUBY 213 | 214 | expect_correction(<<~RUBY) 215 | resources :users, only: [:index, :show, :new, :edit, :create, :update] 216 | RUBY 217 | end 218 | 219 | it 'converts except with other options present' do 220 | expect_offense(<<~RUBY, 'config/routes.rb') 221 | resources :users, path: 'members', except: [:destroy] 222 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/ResourcesWithoutOnly: Use `only:` option in resource/resources routing. 223 | RUBY 224 | 225 | expect_correction(<<~RUBY) 226 | resources :users, path: 'members', only: [:index, :show, :new, :edit, :create, :update] 227 | RUBY 228 | end 229 | 230 | it 'converts except for singular resource' do 231 | expect_offense(<<~RUBY, 'config/routes.rb') 232 | resource :profile, except: [:destroy] 233 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/ResourcesWithoutOnly: Use `only:` option in resource/resources routing. 234 | RUBY 235 | 236 | expect_correction(<<~RUBY) 237 | resource :profile, only: [:show, :new, :edit, :create, :update] 238 | RUBY 239 | end 240 | end 241 | 242 | context 'nested resources' do 243 | it 'corrects both parent and child resources' do 244 | expect_offense(<<~RUBY, 'config/routes.rb') 245 | resources :users do 246 | ^^^^^^^^^^^^^^^^ Sgcop/ResourcesWithoutOnly: Use `only:` option in resource/resources routing. 247 | resources :posts 248 | ^^^^^^^^^^^^^^^^ Sgcop/ResourcesWithoutOnly: Use `only:` option in resource/resources routing. 249 | end 250 | RUBY 251 | 252 | expect_correction(<<~RUBY) 253 | resources :users, only: [:index, :show, :new, :edit, :create, :update, :destroy] do 254 | resources :posts, only: [:index, :show, :new, :edit, :create, :update, :destroy] 255 | end 256 | RUBY 257 | end 258 | end 259 | end 260 | end 261 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/restricted_view_helpers_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::RestrictedViewHelpers do 4 | subject(:cop) { described_class.new(config) } 5 | let(:config) do 6 | RuboCop::Config.new( 7 | 'Sgcop/RestrictedViewHelpers' => { 8 | 'RestrictedMethods' => { 9 | 'simple_format' => 'cssで改行反映されるようにしてください。', 10 | 'raw' => 'sanitizeメソッドを使用してください。', 11 | }, 12 | } 13 | ) 14 | end 15 | 16 | it 'simple_formatが使用された場合は警告される' do 17 | expect_offense(<<~RUBY) 18 | def some_method 19 | simple_format(text) 20 | ^^^^^^^^^^^^^^^^^^^ cssで改行反映されるようにしてください。 21 | end 22 | RUBY 23 | end 24 | 25 | it 'rawが使用された場合は警告される' do 26 | expect_offense(<<~RUBY) 27 | def some_method 28 | raw(html_content) 29 | ^^^^^^^^^^^^^^^^^ sanitizeメソッドを使用してください。 30 | end 31 | RUBY 32 | end 33 | 34 | it '複数の禁止メソッドが使用された場合は全て警告される' do 35 | expect_offense(<<~RUBY) 36 | def some_method 37 | simple_format(text) 38 | ^^^^^^^^^^^^^^^^^^^ cssで改行反映されるようにしてください。 39 | raw(html_content) 40 | ^^^^^^^^^^^^^^^^^ sanitizeメソッドを使用してください。 41 | end 42 | RUBY 43 | end 44 | 45 | it '禁止メソッドがレシーバ付きで呼ばれた場合は警告されない' do 46 | expect_no_offenses(<<~RUBY) 47 | def some_method 48 | helper.simple_format(text) 49 | other.raw(html_content) 50 | end 51 | RUBY 52 | end 53 | 54 | it '禁止されていないメソッドは警告されない' do 55 | expect_no_offenses(<<~RUBY) 56 | def some_method 57 | sanitize(html_content) 58 | content_tag(:p, text) 59 | end 60 | RUBY 61 | end 62 | 63 | context '設定が空の場合' do 64 | let(:config) do 65 | RuboCop::Config.new( 66 | 'Sgcop/RestrictedViewHelpers' => { 67 | 'RestrictedMethods' => {}, 68 | } 69 | ) 70 | end 71 | 72 | it '警告が発生しない' do 73 | expect_no_offenses(<<~RUBY) 74 | def some_method 75 | simple_format(text) 76 | raw(html_content) 77 | end 78 | RUBY 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/retry_on_infinite_attempts_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::RetryOnInfiniteAttempts do 4 | subject(:cop) { described_class.new } 5 | 6 | it 'registers an offense when using Float::INFINITY for attempts' do 7 | expect_offense(<<~RUBY) 8 | retry_on StandardError, wait: :polynomially_longer, attempts: Float::INFINITY 9 | ^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/RetryOnInfiniteAttempts: Avoid using `Float::INFINITY` or `:unlimited` for attempts in `retry_on` method. 10 | RUBY 11 | end 12 | 13 | it 'registers an offense when using :unlimited for attempts' do 14 | expect_offense(<<~RUBY) 15 | TestJob.retry_on TestError, wait: :polynomially_longer, attempts: :unlimited 16 | ^^^^^^^^^^^^^^^^^^^^ Sgcop/RetryOnInfiniteAttempts: Avoid using `Float::INFINITY` or `:unlimited` for attempts in `retry_on` method. 17 | RUBY 18 | end 19 | 20 | it 'registers an offense when using :unlimited for attempts' do 21 | expect_offense(<<~RUBY) 22 | TestJob.retry_on TestError, DummyError, wait: :polynomially_longer, attempts: :unlimited 23 | ^^^^^^^^^^^^^^^^^^^^ Sgcop/RetryOnInfiniteAttempts: Avoid using `Float::INFINITY` or `:unlimited` for attempts in `retry_on` method. 24 | RUBY 25 | end 26 | 27 | it 'does not register an offense for finite attempts' do 28 | expect_no_offenses(<<~RUBY) 29 | retry_on StandardError, wait: :polynomially_longer, attempts: 3 30 | RUBY 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/rspec/action_mailer_test_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::Rspec::ActionMailerTestHelper do 4 | subject(:cop) { RuboCop::Cop::Sgcop::Rspec::ActionMailerTestHelper.new } 5 | 6 | context 'in spec files' do 7 | it 'registers an offense for ActionMailer::Base.deliveries' do 8 | expect_offense(<<~RUBY, 'spec/models/user_spec.rb') 9 | ActionMailer::Base.deliveries 10 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/Rspec/ActionMailerTestHelper: Use ActionMailer::TestHelper methods instead of ActionMailer::Base.deliveries. 11 | RUBY 12 | end 13 | 14 | it 'registers an offense for ActionMailer::Base.deliveries.size' do 15 | expect_offense(<<~RUBY, 'spec/models/user_spec.rb') 16 | ActionMailer::Base.deliveries.size 17 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/Rspec/ActionMailerTestHelper: Use `assert_emails(count)` instead of ActionMailer::Base.deliveries.size. 18 | RUBY 19 | end 20 | 21 | it 'registers an offense for ActionMailer::Base.deliveries.count' do 22 | expect_offense(<<~RUBY, 'spec/models/user_spec.rb') 23 | ActionMailer::Base.deliveries.count 24 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/Rspec/ActionMailerTestHelper: Use `assert_emails(count)` instead of ActionMailer::Base.deliveries.count. 25 | RUBY 26 | end 27 | 28 | it 'registers an offense for ActionMailer::Base.deliveries.empty?' do 29 | expect_offense(<<~RUBY, 'spec/models/user_spec.rb') 30 | ActionMailer::Base.deliveries.empty? 31 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/Rspec/ActionMailerTestHelper: Use `assert_no_emails` instead of ActionMailer::Base.deliveries.empty?. 32 | RUBY 33 | end 34 | 35 | it 'registers an offense for ActionMailer::Base.deliveries.any?' do 36 | expect_offense(<<~RUBY, 'spec/models/user_spec.rb') 37 | ActionMailer::Base.deliveries.any? 38 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/Rspec/ActionMailerTestHelper: Use `assert_emails` to verify emails were sent instead of ActionMailer::Base.deliveries.any?. 39 | RUBY 40 | end 41 | 42 | it 'registers an offense for ActionMailer::Base.deliveries.first' do 43 | expect_offense(<<~RUBY, 'spec/models/user_spec.rb') 44 | ActionMailer::Base.deliveries.first 45 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/Rspec/ActionMailerTestHelper: Use `capture_emails { ... }` to access sent emails instead of ActionMailer::Base.deliveries.first. 46 | RUBY 47 | end 48 | 49 | it 'registers an offense for ActionMailer::Base.deliveries.last' do 50 | expect_offense(<<~RUBY, 'spec/models/user_spec.rb') 51 | ActionMailer::Base.deliveries.last 52 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/Rspec/ActionMailerTestHelper: Use `capture_emails { ... }` to access sent emails instead of ActionMailer::Base.deliveries.last. 53 | RUBY 54 | end 55 | 56 | it 'registers an offense for ActionMailer::Base.deliveries[0]' do 57 | expect_offense(<<~RUBY, 'spec/models/user_spec.rb') 58 | ActionMailer::Base.deliveries[0] 59 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/Rspec/ActionMailerTestHelper: Use `capture_emails { ... }` to access sent emails instead of ActionMailer::Base.deliveries.[]. 60 | RUBY 61 | end 62 | 63 | it 'registers an offense for chained method calls' do 64 | expect_offense(<<~RUBY, 'spec/models/user_spec.rb') 65 | expect(ActionMailer::Base.deliveries.size).to eq(1) 66 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/Rspec/ActionMailerTestHelper: Use `assert_emails(count)` instead of ActionMailer::Base.deliveries.size. 67 | RUBY 68 | end 69 | 70 | it 'registers an offense for ActionMailer::Base.deliveries.clear' do 71 | expect_offense(<<~RUBY, 'spec/models/user_spec.rb') 72 | ActionMailer::Base.deliveries.clear 73 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/Rspec/ActionMailerTestHelper: Use ActionMailer::TestHelper methods instead of ActionMailer::Base.deliveries. 74 | RUBY 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/rspec/active_job_test_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::Rspec::ActiveJobTestHelper do 4 | subject(:cop) { RuboCop::Cop::Sgcop::Rspec::ActiveJobTestHelper.new } 5 | 6 | context 'in spec files' do 7 | it 'registers an offense for have_enqueued_job matcher' do 8 | expect_offense(<<~RUBY, 'spec/models/user_spec.rb') 9 | expect { User.create }.to have_enqueued_job(UserMailerJob) 10 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/Rspec/ActiveJobTestHelper: Use `assert_enqueued_jobs(count)` or `assert_enqueued_with` instead of `have_enqueued_job`. 11 | RUBY 12 | end 13 | 14 | it 'registers an offense for have_enqueued_job with arguments' do 15 | expect_offense(<<~RUBY, 'spec/models/user_spec.rb') 16 | expect { User.create }.to have_enqueued_job(UserMailerJob).with(user_id: 1) 17 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/Rspec/ActiveJobTestHelper: Use `assert_enqueued_jobs(count)` or `assert_enqueued_with` instead of `have_enqueued_job`. 18 | RUBY 19 | end 20 | 21 | it 'registers an offense for have_been_enqueued matcher' do 22 | expect_offense(<<~RUBY, 'spec/models/user_spec.rb') 23 | expect(UserMailerJob).to have_been_enqueued 24 | ^^^^^^^^^^^^^^^^^^ Sgcop/Rspec/ActiveJobTestHelper: Use `assert_enqueued_with` instead of `have_been_enqueued`. 25 | RUBY 26 | end 27 | 28 | it 'registers an offense for enqueue_job matcher' do 29 | expect_offense(<<~RUBY, 'spec/models/user_spec.rb') 30 | expect { User.create }.to enqueue_job(UserMailerJob) 31 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/Rspec/ActiveJobTestHelper: Use `assert_enqueued_jobs` with a block instead of `enqueue_job`. 32 | RUBY 33 | end 34 | 35 | it 'registers an offense for have_performed_job matcher' do 36 | expect_offense(<<~RUBY, 'spec/models/user_spec.rb') 37 | expect { User.create }.to have_performed_job(UserMailerJob) 38 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/Rspec/ActiveJobTestHelper: Use `assert_performed_jobs(count)` or `assert_performed_with` instead of `have_performed_job`. 39 | RUBY 40 | end 41 | 42 | it 'registers an offense for have_been_performed matcher' do 43 | expect_offense(<<~RUBY, 'spec/models/user_spec.rb') 44 | expect(UserMailerJob).to have_been_performed 45 | ^^^^^^^^^^^^^^^^^^^ Sgcop/Rspec/ActiveJobTestHelper: Use `assert_performed_with` instead of `have_been_performed`. 46 | RUBY 47 | end 48 | 49 | it 'registers an offense for perform_job matcher' do 50 | expect_offense(<<~RUBY, 'spec/models/user_spec.rb') 51 | expect { User.create }.to perform_job(UserMailerJob) 52 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/Rspec/ActiveJobTestHelper: Use `assert_performed_jobs` with a block instead of `perform_job`. 53 | RUBY 54 | end 55 | 56 | it 'registers an offense with not_to' do 57 | expect_offense(<<~RUBY, 'spec/models/user_spec.rb') 58 | expect { User.create }.not_to have_enqueued_job(UserMailerJob) 59 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/Rspec/ActiveJobTestHelper: Use `assert_enqueued_jobs(count)` or `assert_enqueued_with` instead of `have_enqueued_job`. 60 | RUBY 61 | end 62 | 63 | it 'registers an offense with to_not' do 64 | expect_offense(<<~RUBY, 'spec/models/user_spec.rb') 65 | expect { User.create }.to_not enqueue_job(UserMailerJob) 66 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/Rspec/ActiveJobTestHelper: Use `assert_enqueued_jobs` with a block instead of `enqueue_job`. 67 | RUBY 68 | end 69 | 70 | it 'does not register an offense for other matchers' do 71 | expect_no_offenses(<<~RUBY, 'spec/models/user_spec.rb') 72 | expect(User.count).to eq(1) 73 | RUBY 74 | end 75 | 76 | it 'does not register an offense for assert_enqueued_jobs' do 77 | expect_no_offenses(<<~RUBY, 'spec/models/user_spec.rb') 78 | assert_enqueued_jobs(1) { User.create } 79 | RUBY 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/rspec/conditional_example_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::Rspec::ConditionalExample do 4 | subject(:cop) { RuboCop::Cop::Sgcop::Rspec::ConditionalExample.new } 5 | 6 | context 'when expect has if modifier' do 7 | it 'registers an offense' do 8 | expect_offense(<<~RUBY) 9 | expect(page).to have_content(project.description) if project.description.present? 10 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/Rspec/ConditionalExample: Avoid conditional expectations. Always assert expectations unconditionally. 11 | RUBY 12 | end 13 | end 14 | 15 | context 'when expect has unless modifier' do 16 | it 'registers an offense' do 17 | expect_offense(<<~RUBY) 18 | expect(result).to be_success unless error_occurred 19 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/Rspec/ConditionalExample: Avoid conditional expectations. Always assert expectations unconditionally. 20 | RUBY 21 | end 22 | end 23 | 24 | context 'when expect is inside if block without else' do 25 | it 'registers an offense' do 26 | expect_offense(<<~RUBY) 27 | if condition 28 | ^^^^^^^^^^^^ Sgcop/Rspec/ConditionalExample: Avoid conditional expectations. Always assert expectations unconditionally. 29 | expect(value).to eq(expected) 30 | end 31 | RUBY 32 | end 33 | end 34 | 35 | context 'when multiple expects are inside if block without else' do 36 | it 'registers an offense' do 37 | expect_offense(<<~RUBY) 38 | if condition 39 | ^^^^^^^^^^^^ Sgcop/Rspec/ConditionalExample: Avoid conditional expectations. Always assert expectations unconditionally. 40 | expect(value1).to eq(expected1) 41 | expect(value2).to eq(expected2) 42 | end 43 | RUBY 44 | end 45 | end 46 | 47 | context 'when expect is inside unless block without else' do 48 | it 'registers an offense' do 49 | expect_offense(<<~RUBY) 50 | unless error_occurred 51 | ^^^^^^^^^^^^^^^^^^^^^ Sgcop/Rspec/ConditionalExample: Avoid conditional expectations. Always assert expectations unconditionally. 52 | expect(result).to be_success 53 | end 54 | RUBY 55 | end 56 | end 57 | 58 | context 'when expect is inside if block with else' do 59 | it 'does not register an offense' do 60 | expect_no_offenses(<<~RUBY) 61 | if condition 62 | expect(value).to eq(expected1) 63 | else 64 | expect(value).to eq(expected2) 65 | end 66 | RUBY 67 | end 68 | end 69 | 70 | context 'when expect is inside if block with elsif and else' do 71 | it 'does not register an offense' do 72 | expect_no_offenses(<<~RUBY) 73 | if condition1 74 | expect(value).to eq(expected1) 75 | elsif condition2 76 | expect(value).to eq(expected2) 77 | else 78 | expect(value).to eq(expected3) 79 | end 80 | RUBY 81 | end 82 | end 83 | 84 | context 'when expect is used without conditions' do 85 | it 'does not register an offense' do 86 | expect_no_offenses(<<~RUBY) 87 | expect(page).to have_content('Hello') 88 | RUBY 89 | end 90 | end 91 | 92 | context 'when expect is used with method chain' do 93 | it 'does not register an offense' do 94 | expect_no_offenses(<<~RUBY) 95 | expect(page).to have_content('Hello').and have_link('Click') 96 | RUBY 97 | end 98 | end 99 | 100 | context 'when if block contains non-expect code' do 101 | it 'does not register an offense' do 102 | expect_no_offenses(<<~RUBY) 103 | if condition 104 | visit home_path 105 | click_on 'Submit' 106 | end 107 | RUBY 108 | end 109 | end 110 | 111 | context 'when if block contains expect with other code' do 112 | it 'registers an offense' do 113 | expect_offense(<<~RUBY) 114 | if condition 115 | ^^^^^^^^^^^^ Sgcop/Rspec/ConditionalExample: Avoid conditional expectations. Always assert expectations unconditionally. 116 | visit home_path 117 | expect(page).to have_content('Success') 118 | end 119 | RUBY 120 | end 121 | end 122 | 123 | context 'when condition check is not about expect' do 124 | it 'does not register an offense' do 125 | expect_no_offenses(<<~RUBY) 126 | result = perform_action if condition 127 | RUBY 128 | end 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/rspec/no_method_call_in_expectation_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::Rspec::NoMethodCallInExpectation do 4 | subject(:cop) { RuboCop::Cop::Sgcop::Rspec::NoMethodCallInExpectation.new(config) } 5 | let(:config) do 6 | RuboCop::Config.new( 7 | 'Sgcop/Rspec/NoMethodCallInExpectation' => { 8 | 'TargetMatchers' => %w[have_content have_text have_css have_selector eq include], 9 | 'AllowedPatterns' => ['^I18n\.t$', '^I18n\.l$'], 10 | } 11 | ) 12 | end 13 | 14 | context 'when using method calls in have_content' do 15 | it 'registers an offense' do 16 | expect_offense(<<~RUBY) 17 | expect(page).to have_content(project.title) 18 | ^^^^^^^^^^^^^ Use literal values instead of method calls in expectations. Use "have_content" with literal values. 19 | RUBY 20 | end 21 | end 22 | 23 | context 'when using method calls in have_text' do 24 | it 'registers an offense' do 25 | expect_offense(<<~RUBY) 26 | expect(page).to have_text(user.name) 27 | ^^^^^^^^^ Use literal values instead of method calls in expectations. Use "have_text" with literal values. 28 | RUBY 29 | end 30 | end 31 | 32 | context 'when using method calls in eq' do 33 | it 'registers an offense' do 34 | expect_offense(<<~RUBY) 35 | expect(result).to eq(calculate_value()) 36 | ^^^^^^^^^^^^^^^^^ Use literal values instead of method calls in expectations. Use "eq" with literal values. 37 | RUBY 38 | end 39 | end 40 | 41 | context 'when using method calls in include' do 42 | it 'registers an offense' do 43 | expect_offense(<<~RUBY) 44 | expect(array).to include(item.value) 45 | ^^^^^^^^^^ Use literal values instead of method calls in expectations. Use "include" with literal values. 46 | RUBY 47 | end 48 | end 49 | 50 | context 'when using instance variable method calls' do 51 | it 'registers an offense' do 52 | expect_offense(<<~RUBY) 53 | expect(page).to have_content(@project.title) 54 | ^^^^^^^^^^^^^^ Use literal values instead of method calls in expectations. Use "have_content" with literal values. 55 | RUBY 56 | end 57 | end 58 | 59 | context 'when using local variables' do 60 | it 'does not register an offense' do 61 | expect_no_offenses(<<~RUBY) 62 | expect(result).to eq(expected_value) 63 | RUBY 64 | end 65 | end 66 | 67 | context 'when using instance variables' do 68 | it 'does not register an offense' do 69 | expect_no_offenses(<<~RUBY) 70 | expect(page).to have_content(@project) 71 | RUBY 72 | end 73 | end 74 | 75 | context 'when using constants' do 76 | it 'does not register an offense' do 77 | expect_no_offenses(<<~RUBY) 78 | expect(page).to have_content(PROJECT_TITLE) 79 | RUBY 80 | end 81 | end 82 | 83 | context 'when using literal string' do 84 | it 'does not register an offense' do 85 | expect_no_offenses(<<~RUBY) 86 | expect(page).to have_content("プロジェクトタイトル") 87 | RUBY 88 | end 89 | end 90 | 91 | context 'when using literal number' do 92 | it 'does not register an offense' do 93 | expect_no_offenses(<<~RUBY) 94 | expect(count).to eq(5) 95 | RUBY 96 | end 97 | end 98 | 99 | context 'when using literal symbol' do 100 | it 'does not register an offense' do 101 | expect_no_offenses(<<~RUBY) 102 | expect(status).to eq(:success) 103 | RUBY 104 | end 105 | end 106 | 107 | context 'when using literal boolean' do 108 | it 'does not register an offense' do 109 | expect_no_offenses(<<~RUBY) 110 | expect(result).to eq(true) 111 | expect(flag).to eq(false) 112 | RUBY 113 | end 114 | end 115 | 116 | context 'when using nil' do 117 | it 'does not register an offense' do 118 | expect_no_offenses(<<~RUBY) 119 | expect(value).to eq(nil) 120 | RUBY 121 | end 122 | end 123 | 124 | context 'when using literal array' do 125 | it 'does not register an offense' do 126 | expect_no_offenses(<<~RUBY) 127 | expect(items).to eq(["item1", "item2"]) 128 | RUBY 129 | end 130 | end 131 | 132 | context 'when using array with variables' do 133 | it 'does not register an offense' do 134 | expect_no_offenses(<<~RUBY) 135 | expect(items).to eq([item1, item2, @item3]) 136 | RUBY 137 | end 138 | end 139 | 140 | context 'when using literal hash' do 141 | it 'does not register an offense' do 142 | expect_no_offenses(<<~RUBY) 143 | expect(data).to eq({ name: "Test", value: 123 }) 144 | RUBY 145 | end 146 | end 147 | 148 | context 'when using hash with variables' do 149 | it 'does not register an offense' do 150 | expect_no_offenses(<<~RUBY) 151 | expect(data).to eq({ name: username, value: count }) 152 | RUBY 153 | end 154 | end 155 | 156 | context 'when using regexp' do 157 | it 'does not register an offense' do 158 | expect_no_offenses(<<~RUBY) 159 | expect(text).to match(/pattern/) 160 | RUBY 161 | end 162 | end 163 | 164 | context 'when using I18n.t' do 165 | it 'does not register an offense' do 166 | expect_no_offenses(<<~RUBY) 167 | expect(page).to have_content(I18n.t('projects.title')) 168 | RUBY 169 | end 170 | 171 | it 'does not register an offense with have_selector text option' do 172 | expect_no_offenses(<<~RUBY) 173 | expect(page).to have_selector 'h1', text: I18n.t('text.title') 174 | RUBY 175 | end 176 | end 177 | 178 | context 'when using I18n.l' do 179 | it 'does not register an offense' do 180 | expect_no_offenses(<<~RUBY) 181 | expect(page).to have_content(I18n.l(date)) 182 | RUBY 183 | end 184 | end 185 | 186 | context 'when using URL helpers' do 187 | let(:config) do 188 | RuboCop::Config.new( 189 | 'Sgcop/Rspec/NoMethodCallInExpectation' => { 190 | 'TargetMatchers' => %w[have_content eq have_selector], 191 | 'AllowedPatterns' => ['_path$', '_url$'], 192 | } 193 | ) 194 | end 195 | 196 | it 'does not register an offense for _path helpers' do 197 | expect_no_offenses(<<~RUBY) 198 | expect(page).to have_content(root_path) 199 | expect(current_path).to eq(users_path) 200 | RUBY 201 | end 202 | 203 | it 'does not register an offense for _url helpers' do 204 | expect_no_offenses(<<~RUBY) 205 | expect(page).to have_content(root_url) 206 | expect(location).to eq(users_url) 207 | RUBY 208 | end 209 | 210 | it 'does not register an offense for URL helpers inside string interpolation' do 211 | expect_no_offenses(<<~RUBY) 212 | expect(page).to have_selector "a[href='\#{root_path}']" 213 | RUBY 214 | end 215 | 216 | it 'does not register an offense for multiple URL helpers inside string interpolation' do 217 | expect_no_offenses(<<~RUBY) 218 | expect(page).to have_selector "a[href='\#{user_path(1)}'][data-id='\#{item_path(2)}']" 219 | RUBY 220 | end 221 | 222 | it 'does not register an offense for variables inside string interpolation' do 223 | expect_no_offenses(<<~RUBY) 224 | expect(page).to have_selector "a[href='\#{path_var}'][data-value='\#{value}']" 225 | RUBY 226 | end 227 | 228 | it 'registers an offense for method calls in string interpolation' do 229 | expect_offense(<<~RUBY) 230 | expect(page).to have_selector "a[href='\#{user.path}']" 231 | ^^^^^^^^^^^^^^^^^^^^^^^^ Use literal values instead of method calls in expectations. Use "have_selector" with literal values. 232 | RUBY 233 | end 234 | end 235 | 236 | context 'when using non-configured matcher' do 237 | it 'does not register an offense' do 238 | expect_no_offenses(<<~RUBY) 239 | expect(result).to be_truthy 240 | expect(page).to have_link(link.text) 241 | RUBY 242 | end 243 | end 244 | 245 | context 'when using multiple arguments' do 246 | it 'registers offenses for method call arguments only' do 247 | expect_offense(<<~RUBY) 248 | expect(page).to have_content("Fixed Text", user.name) 249 | ^^^^^^^^^ Use literal values instead of method calls in expectations. Use "have_content" with literal values. 250 | RUBY 251 | end 252 | 253 | it 'does not register offenses for variable arguments' do 254 | expect_no_offenses(<<~RUBY) 255 | expect(page).to have_content("Fixed Text", dynamic_text) 256 | RUBY 257 | end 258 | end 259 | 260 | context 'when custom configuration' do 261 | let(:config) do 262 | RuboCop::Config.new( 263 | 'Sgcop/Rspec/NoMethodCallInExpectation' => { 264 | 'TargetMatchers' => %w[eq], 265 | 'AllowedPatterns' => ['^helper_method$'], 266 | } 267 | ) 268 | end 269 | 270 | it 'only checks configured matchers' do 271 | expect_no_offenses(<<~RUBY) 272 | expect(page).to have_content(project.name) 273 | RUBY 274 | 275 | expect_offense(<<~RUBY) 276 | expect(result).to eq(user.value) 277 | ^^^^^^^^^^ Use literal values instead of method calls in expectations. Use "eq" with literal values. 278 | RUBY 279 | end 280 | 281 | it 'allows configured methods' do 282 | expect_no_offenses(<<~RUBY) 283 | expect(page).to eq(helper_method('key')) 284 | RUBY 285 | end 286 | end 287 | end 288 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/rspec/redundant_let_reference_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::Rspec::RedundantLetReference do 4 | subject(:cop) { RuboCop::Cop::Sgcop::Rspec::RedundantLetReference.new } 5 | 6 | context 'when let is referenced only in before block' do 7 | it 'registers an offense' do 8 | expect_offense(<<~RUBY) 9 | let(:user) { create(:user) } 10 | 11 | before do 12 | user 13 | ^^^^ Sgcop/Rspec/RedundantLetReference: Use `let!` instead of referencing `let` in `before` block, or move the setup directly into `before`. 14 | end 15 | RUBY 16 | end 17 | end 18 | 19 | context 'when let is referenced only in it block' do 20 | it 'registers an offense' do 21 | expect_offense(<<~RUBY) 22 | let(:user) { create(:user) } 23 | 24 | it do 25 | user 26 | ^^^^ Sgcop/Rspec/RedundantLetReference: Use `let!` for eager evaluation or move the setup directly into the test block instead of just referencing `let`. 27 | end 28 | RUBY 29 | end 30 | end 31 | 32 | context 'when let is referenced with other statements in before block' do 33 | it 'registers an offense for the let reference' do 34 | expect_offense(<<~RUBY) 35 | let(:user) { create(:user) } 36 | 37 | before do 38 | user 39 | ^^^^ Sgcop/Rspec/RedundantLetReference: Use `let!` instead of referencing `let` in `before` block, or move the setup directly into `before`. 40 | do_something 41 | end 42 | RUBY 43 | end 44 | end 45 | 46 | context 'when let is referenced with other statements in it block' do 47 | it 'registers an offense for the let reference' do 48 | expect_offense(<<~RUBY) 49 | let(:user) { create(:user) } 50 | 51 | it do 52 | user 53 | ^^^^ Sgcop/Rspec/RedundantLetReference: Use `let!` for eager evaluation or move the setup directly into the test block instead of just referencing `let`. 54 | visit home_path 55 | end 56 | RUBY 57 | end 58 | end 59 | 60 | context 'when let is used meaningfully in before block' do 61 | it 'does not register an offense' do 62 | expect_no_offenses(<<~RUBY) 63 | let(:user) { create(:user) } 64 | 65 | before do 66 | user.update(name: 'test') 67 | end 68 | RUBY 69 | end 70 | end 71 | 72 | context 'when let is used meaningfully in it block' do 73 | it 'does not register an offense' do 74 | expect_no_offenses(<<~RUBY) 75 | let(:user) { create(:user) } 76 | 77 | it do 78 | expect(user.name).to eq('test') 79 | end 80 | RUBY 81 | end 82 | end 83 | 84 | context 'when let is assigned to a variable' do 85 | it 'does not register an offense' do 86 | expect_no_offenses(<<~RUBY) 87 | let(:user) { create(:user) } 88 | 89 | it do 90 | current_user = user 91 | expect(current_user.name).to eq('test') 92 | end 93 | RUBY 94 | end 95 | end 96 | 97 | context 'when let! is used instead of let' do 98 | it 'does not register an offense' do 99 | expect_no_offenses(<<~RUBY) 100 | let!(:user) { create(:user) } 101 | 102 | before do 103 | # no need to reference user 104 | end 105 | RUBY 106 | end 107 | end 108 | 109 | context 'when variable is not defined by let' do 110 | it 'does not register an offense' do 111 | expect_no_offenses(<<~RUBY) 112 | def user 113 | @user ||= create(:user) 114 | end 115 | 116 | before do 117 | user 118 | end 119 | RUBY 120 | end 121 | end 122 | 123 | context 'when let reference has arguments' do 124 | it 'does not register an offense' do 125 | expect_no_offenses(<<~RUBY) 126 | let(:user) { create(:user) } 127 | 128 | it do 129 | user(some_arg) 130 | end 131 | RUBY 132 | end 133 | end 134 | 135 | context 'when let reference has a receiver' do 136 | it 'does not register an offense' do 137 | expect_no_offenses(<<~RUBY) 138 | let(:user) { create(:user) } 139 | 140 | it do 141 | something.user 142 | end 143 | RUBY 144 | end 145 | end 146 | end 147 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/rspec/redundant_perform_enqueued_jobs_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::Rspec::RedundantPerformEnqueuedJobs do 4 | subject(:cop) { RuboCop::Cop::Sgcop::Rspec::RedundantPerformEnqueuedJobs.new } 5 | 6 | context 'when perform_enqueued_jobs wraps capture_emails' do 7 | it 'registers an offense' do 8 | expect_offense(<<~RUBY, 'spec/system/user_spec.rb') 9 | perform_enqueued_jobs do 10 | emails = capture_emails do 11 | ^^^^^^^^^^^^^^ Sgcop/Rspec/RedundantPerformEnqueuedJobs: `capture_emails` internally uses `perform_enqueued_jobs`, so the outer `perform_enqueued_jobs` is redundant. 12 | click_button '新規登録' 13 | end 14 | end 15 | RUBY 16 | end 17 | end 18 | 19 | context 'when perform_enqueued_jobs wraps deliver_enqueued_emails' do 20 | it 'registers an offense' do 21 | expect_offense(<<~RUBY, 'spec/system/user_spec.rb') 22 | perform_enqueued_jobs do 23 | deliver_enqueued_emails do 24 | ^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/Rspec/RedundantPerformEnqueuedJobs: `deliver_enqueued_emails` internally uses `perform_enqueued_jobs`, so the outer `perform_enqueued_jobs` is redundant. 25 | UserMailer.welcome.deliver_later 26 | end 27 | end 28 | RUBY 29 | end 30 | end 31 | 32 | context 'when perform_enqueued_jobs wraps assert_emails with block' do 33 | it 'registers an offense' do 34 | expect_offense(<<~RUBY, 'spec/system/user_spec.rb') 35 | perform_enqueued_jobs do 36 | assert_emails 1 do 37 | ^^^^^^^^^^^^^^^ Sgcop/Rspec/RedundantPerformEnqueuedJobs: `assert_emails` internally uses `perform_enqueued_jobs`, so the outer `perform_enqueued_jobs` is redundant. 38 | click_button '新規登録' 39 | end 40 | end 41 | RUBY 42 | end 43 | end 44 | 45 | context 'when perform_enqueued_jobs wraps assert_no_emails with block' do 46 | it 'registers an offense' do 47 | expect_offense(<<~RUBY, 'spec/system/user_spec.rb') 48 | perform_enqueued_jobs do 49 | assert_no_emails do 50 | ^^^^^^^^^^^^^^^^ Sgcop/Rspec/RedundantPerformEnqueuedJobs: `assert_no_emails` internally uses `perform_enqueued_jobs`, so the outer `perform_enqueued_jobs` is redundant. 51 | click_button 'キャンセル' 52 | end 53 | end 54 | RUBY 55 | end 56 | end 57 | 58 | context 'when capture_emails is used without perform_enqueued_jobs' do 59 | it 'does not register an offense' do 60 | expect_no_offenses(<<~RUBY, 'spec/system/user_spec.rb') 61 | emails = capture_emails do 62 | click_button '新規登録' 63 | end 64 | RUBY 65 | end 66 | end 67 | 68 | context 'when assert_emails without block is used in perform_enqueued_jobs' do 69 | it 'does not register an offense' do 70 | expect_no_offenses(<<~RUBY, 'spec/system/user_spec.rb') 71 | perform_enqueued_jobs do 72 | UserMailer.welcome.deliver_later 73 | assert_emails 1 74 | end 75 | RUBY 76 | end 77 | end 78 | 79 | context 'when assert_no_emails without block is used in perform_enqueued_jobs' do 80 | it 'does not register an offense' do 81 | expect_no_offenses(<<~RUBY, 'spec/system/user_spec.rb') 82 | perform_enqueued_jobs do 83 | # Some action that shouldn't send emails 84 | assert_no_emails 85 | end 86 | RUBY 87 | end 88 | end 89 | 90 | context 'when nested perform_enqueued_jobs with capture_emails' do 91 | it 'registers an offense for nested capture_emails' do 92 | expect_offense(<<~RUBY, 'spec/system/user_spec.rb') 93 | perform_enqueued_jobs do 94 | some_action 95 | emails = capture_emails do 96 | ^^^^^^^^^^^^^^ Sgcop/Rspec/RedundantPerformEnqueuedJobs: `capture_emails` internally uses `perform_enqueued_jobs`, so the outer `perform_enqueued_jobs` is redundant. 97 | click_button '送信' 98 | end 99 | expect(emails.size).to eq(1) 100 | end 101 | RUBY 102 | end 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/simple_form_association_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::SimpleFormAssociation do 4 | subject(:cop) { RuboCop::Cop::Sgcop::SimpleFormAssociation.new } 5 | 6 | context 'simple_form_for ブロック内での association メソッド呼び出し' do 7 | it 'collection オプションなしで呼び出している場合は警告' do 8 | expect_offense(<<~RUBY) 9 | simple_form_for user do |f| 10 | f.association :group 11 | ^^^^^^^^^^^^^^^^^^^^ Sgcop/SimpleFormAssociation: Specify the `collection` option 12 | end 13 | RUBY 14 | end 15 | 16 | it 'collection オプションありで呼び出している場合は警告しない' do 17 | expect_no_offenses(<<~RUBY) 18 | simple_form_for user do |f| 19 | f.association :group, collection: current_company.groups 20 | end 21 | RUBY 22 | end 23 | 24 | it 'SimpleForm::FormBuilder でない変数に対する association メソッド呼び出しには警告しない' do 25 | expect_no_offenses(<<~RUBY) 26 | simple_form_for user do |f| 27 | other_object.association :group 28 | end 29 | RUBY 30 | end 31 | end 32 | 33 | context 'simple_nested_form_for ブロック内での association メソッド呼び出し' do 34 | it 'collection オプションなしで呼び出している場合は警告' do 35 | expect_offense(<<~RUBY) 36 | simple_nested_form_for user do |f| 37 | f.association :group 38 | ^^^^^^^^^^^^^^^^^^^^ Sgcop/SimpleFormAssociation: Specify the `collection` option 39 | end 40 | RUBY 41 | end 42 | 43 | it 'collection オプションありで呼び出している場合は警告しない' do 44 | expect_no_offenses(<<~RUBY) 45 | simple_nested_form_for user do |f| 46 | f.association :group, collection: current_company.groups 47 | end 48 | RUBY 49 | end 50 | 51 | it 'SimpleForm::FormBuilder でない変数に対する association メソッド呼び出しには警告しない' do 52 | expect_no_offenses(<<~RUBY) 53 | simple_nested_form_for user do |f| 54 | other_object.association :group 55 | end 56 | RUBY 57 | end 58 | end 59 | 60 | context 'simple_fields_for ブロック内での association メソッド呼び出し' do 61 | it 'collection オプションなしで呼び出している場合は警告' do 62 | expect_offense(<<~RUBY) 63 | simple_form_for user do |f| 64 | f.simple_fields_for :posts do |ff| 65 | ff.association :category 66 | ^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/SimpleFormAssociation: Specify the `collection` option 67 | end 68 | end 69 | RUBY 70 | end 71 | 72 | it 'collection オプションありで呼び出している場合は警告しない' do 73 | expect_no_offenses(<<~RUBY) 74 | simple_form_for user do |f| 75 | f.simple_fields_for :posts do |ff| 76 | ff.association :category, collection: Category.all 77 | end 78 | end 79 | RUBY 80 | end 81 | 82 | it 'SimpleForm::FormBuilder でない変数に対する association メソッド呼び出しには警告しない' do 83 | expect_no_offenses(<<~RUBY) 84 | simple_form_for user do |f| 85 | f.simple_fields_for :posts do |ff| 86 | other_object.association :category 87 | end 88 | end 89 | RUBY 90 | end 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/simple_format_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::SimpleFormat do 4 | subject(:cop) { RuboCop::Cop::Sgcop::SimpleFormat.new } 5 | 6 | it 'simple_formatに変数が直接渡されていたら警告' do 7 | expect_offense(<<~RUBY) 8 | def html_format(text) 9 | simple_format(text) 10 | ^^^^^^^^^^^^^^^^^^^ Sgcop/SimpleFormat: simple_format does not escape HTML tags. 11 | end 12 | RUBY 13 | end 14 | 15 | it 'simple_formatにオブジェクトのメソッドが渡されていたら警告' do 16 | expect_offense(<<~RUBY) 17 | def html_format(object) 18 | simple_format(object.body) 19 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/SimpleFormat: simple_format does not escape HTML tags. 20 | end 21 | RUBY 22 | end 23 | 24 | it 'simple_formatにエスケープメソッドの結果が渡されていたら警告なし' do 25 | expect_no_offenses(<<~RUBY) 26 | simple_format(h(text)) 27 | RUBY 28 | end 29 | 30 | it 'simple_formatにエスケープメソッド以外の結果が渡されていたら警告' do 31 | expect_offense(<<~RUBY) 32 | simple_format(t(text)) 33 | ^^^^^^^^^^^^^^^^^^^^^^ Sgcop/SimpleFormat: simple_format does not escape HTML tags. 34 | RUBY 35 | end 36 | 37 | it 'simple_formatにインスタンス変数が渡されていたら警告' do 38 | expect_offense(<<~RUBY) 39 | simple_format(@object) 40 | ^^^^^^^^^^^^^^^^^^^^^^ Sgcop/SimpleFormat: simple_format does not escape HTML tags. 41 | RUBY 42 | end 43 | 44 | it 'simple_formatにインスタンス変数のメソッドが渡されていたら警告' do 45 | expect_offense(<<~RUBY) 46 | simple_format(@object.body) 47 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/SimpleFormat: simple_format does not escape HTML tags. 48 | RUBY 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/strftime_restriction_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::StrftimeRestriction do 4 | subject(:cop) { described_class.new(config) } 5 | let(:config) { RuboCop::Config.new(cop_config) } 6 | let(:cop_config) { { 'Sgcop/StrftimeRestriction' => {} } } 7 | 8 | it 'Date.todayでstrftimeが使用された場合は警告される' do 9 | expect_offense(<<~RUBY) 10 | Date.today.strftime('%Y-%m-%d') 11 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ strftimeではなくI18n.lを使用してローカライズしてください。 12 | RUBY 13 | end 14 | 15 | it 'Time.nowでstrftimeが使用された場合は警告される' do 16 | expect_offense(<<~RUBY) 17 | Time.now.strftime('%H:%M:%S') 18 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ strftimeではなくI18n.lを使用してローカライズしてください。 19 | RUBY 20 | end 21 | 22 | it 'DateTime.nowでstrftimeが使用された場合は警告される' do 23 | expect_offense(<<~RUBY) 24 | DateTime.now.strftime('%Y年%m月%d日') 25 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ strftimeではなくI18n.lを使用してローカライズしてください。 26 | RUBY 27 | end 28 | 29 | it 'Time.currentでstrftimeが使用された場合は警告される' do 30 | expect_offense(<<~RUBY) 31 | Time.current.strftime('%Y/%m/%d') 32 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ strftimeではなくI18n.lを使用してローカライズしてください。 33 | RUBY 34 | end 35 | 36 | it 'Time.zoneでstrftimeが使用された場合は警告される' do 37 | expect_offense(<<~RUBY) 38 | Time.zone.now.strftime('%Y-%m-%d') 39 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ strftimeではなくI18n.lを使用してローカライズしてください。 40 | RUBY 41 | end 42 | 43 | it '変数に格納された日付オブジェクトでstrftimeが使用された場合は警告される' do 44 | expect_offense(<<~RUBY) 45 | def format_date 46 | date = Date.today 47 | date.strftime('%Y-%m-%d') 48 | ^^^^^^^^^^^^^^^^^^^^^^^^^ strftimeではなくI18n.lを使用してローカライズしてください。 49 | end 50 | RUBY 51 | end 52 | 53 | it 'インスタンス変数の日付オブジェクトでstrftimeが使用された場合は警告される' do 54 | expect_offense(<<~RUBY) 55 | def format_date 56 | @created_at.strftime('%Y-%m-%d') 57 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ strftimeではなくI18n.lを使用してローカライズしてください。 58 | end 59 | RUBY 60 | end 61 | 62 | it 'to_dateの結果でstrftimeが使用された場合は警告される' do 63 | expect_offense(<<~RUBY) 64 | '2024-01-01'.to_date.strftime('%B %d, %Y') 65 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ strftimeではなくI18n.lを使用してローカライズしてください。 66 | RUBY 67 | end 68 | 69 | it 'ActiveSupport::TimeWithZoneでstrftimeが使用された場合は警告される' do 70 | expect_offense(<<~RUBY) 71 | Time.zone.parse('2024-01-01').strftime('%Y-%m-%d') 72 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ strftimeではなくI18n.lを使用してローカライズしてください。 73 | RUBY 74 | end 75 | 76 | it '1.day.agoでstrftimeが使用された場合は警告される' do 77 | expect_offense(<<~RUBY) 78 | 1.day.ago.strftime('%Y-%m-%d') 79 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ strftimeではなくI18n.lを使用してローカライズしてください。 80 | RUBY 81 | end 82 | 83 | it 'beginning_of_monthでstrftimeが使用された場合は警告される' do 84 | expect_offense(<<~RUBY) 85 | Date.today.beginning_of_month.strftime('%Y-%m') 86 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ strftimeではなくI18n.lを使用してローカライズしてください。 87 | RUBY 88 | end 89 | 90 | it 'I18n.lが使用されている場合は警告されない' do 91 | expect_no_offenses(<<~RUBY) 92 | I18n.l(Date.today, format: :long) 93 | RUBY 94 | end 95 | 96 | it 'I18n.localizeが使用されている場合は警告されない' do 97 | expect_no_offenses(<<~RUBY) 98 | I18n.localize(Time.now, format: :short) 99 | RUBY 100 | end 101 | 102 | it '日付オブジェクト以外でもstrftimeが使用された場合は警告される' do 103 | expect_offense(<<~RUBY) 104 | some_object.strftime('%Y-%m-%d') 105 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ strftimeではなくI18n.lを使用してローカライズしてください。 106 | RUBY 107 | end 108 | 109 | it '複数のstrftimeが使用された場合は全て警告される' do 110 | expect_offense(<<~RUBY) 111 | def format_dates 112 | Date.today.strftime('%Y-%m-%d') 113 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ strftimeではなくI18n.lを使用してローカライズしてください。 114 | Time.now.strftime('%H:%M:%S') 115 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ strftimeではなくI18n.lを使用してローカライズしてください。 116 | @updated_at.strftime('%Y年%m月%d日') 117 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ strftimeではなくI18n.lを使用してローカライズしてください。 118 | end 119 | RUBY 120 | end 121 | 122 | context '許可されたパターンの場合' do 123 | let(:cop_config) { { 'Sgcop/StrftimeRestriction' => { 'AllowedPatterns' => ['%a', '%A', '%w'] } } } 124 | it '%aパターンは警告されない' do 125 | expect_no_offenses(<<~RUBY) 126 | Date.today.strftime('%a') 127 | RUBY 128 | end 129 | 130 | it '%Aパターンは警告されない' do 131 | expect_no_offenses(<<~RUBY) 132 | Time.now.strftime('%A') 133 | RUBY 134 | end 135 | 136 | it '%wパターンは警告されない' do 137 | expect_no_offenses(<<~RUBY) 138 | DateTime.now.strftime('%w') 139 | RUBY 140 | end 141 | 142 | it '許可されていないパターンは警告される' do 143 | expect_offense(<<~RUBY) 144 | Date.today.strftime('%Y-%m-%d') 145 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ strftimeではなくI18n.lを使用してローカライズしてください。 146 | RUBY 147 | end 148 | 149 | it '引数が動的な場合は警告される' do 150 | expect_offense(<<~RUBY) 151 | format = '%a' 152 | Date.today.strftime(format) 153 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ strftimeではなくI18n.lを使用してローカライズしてください。 154 | RUBY 155 | end 156 | end 157 | 158 | context 'AllowedPatternsが設定されていない場合' do 159 | it '%aパターンも警告される' do 160 | expect_offense(<<~RUBY) 161 | Date.today.strftime('%a') 162 | ^^^^^^^^^^^^^^^^^^^^^^^^^ strftimeではなくI18n.lを使用してローカライズしてください。 163 | RUBY 164 | end 165 | end 166 | end 167 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/strict_loading_required_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::StrictLoadingRequired do 4 | subject(:cop) { RuboCop::Cop::Sgcop::StrictLoadingRequired.new } 5 | 6 | context 'includesメソッドが使用されている場合' do 7 | it 'strict_loadingが含まれていない場合は警告される' do 8 | expect_offense(<<~RUBY) 9 | users = User.includes(:posts) 10 | ^^^^^^^^^^^^^^^^^^^^^ Sgcop/StrictLoadingRequired: Add `.strict_loading` when using `includes` or `preload` with variable assignment 11 | RUBY 12 | end 13 | 14 | it 'strict_loadingが含まれている場合は警告されない' do 15 | expect_no_offenses(<<~RUBY) 16 | users = User.includes(:posts).strict_loading 17 | RUBY 18 | end 19 | 20 | it 'strict_loadingが先に呼ばれている場合も警告されない' do 21 | expect_no_offenses(<<~RUBY) 22 | users = User.strict_loading.includes(:posts) 23 | RUBY 24 | end 25 | end 26 | 27 | context 'preloadメソッドが使用されている場合' do 28 | it 'strict_loadingが含まれていない場合は警告される' do 29 | expect_offense(<<~RUBY) 30 | articles = Article.preload(:comments) 31 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/StrictLoadingRequired: Add `.strict_loading` when using `includes` or `preload` with variable assignment 32 | RUBY 33 | end 34 | 35 | it 'strict_loadingが含まれている場合は警告されない' do 36 | expect_no_offenses(<<~RUBY) 37 | articles = Article.preload(:comments).strict_loading 38 | RUBY 39 | end 40 | end 41 | 42 | context '複雑なメソッドチェーンの場合' do 43 | it 'includesとstrict_loadingが一緒に呼ばれている場合は警告されない' do 44 | expect_no_offenses(<<~RUBY) 45 | users = User.where(active: true).includes(:posts).strict_loading.order(:name) 46 | RUBY 47 | end 48 | 49 | it 'preloadとwhereチェーンでstrict_loadingがない場合は警告される' do 50 | expect_offense(<<~RUBY) 51 | users = User.where(active: true).preload(:posts).order(:name) 52 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/StrictLoadingRequired: Add `.strict_loading` when using `includes` or `preload` with variable assignment 53 | RUBY 54 | end 55 | end 56 | 57 | context '異なる変数代入タイプの場合' do 58 | it 'インスタンス変数への代入でも警告される' do 59 | expect_offense(<<~RUBY) 60 | @users = User.includes(:posts) 61 | ^^^^^^^^^^^^^^^^^^^^^ Sgcop/StrictLoadingRequired: Add `.strict_loading` when using `includes` or `preload` with variable assignment 62 | RUBY 63 | end 64 | end 65 | 66 | context '変数代入されていない場合' do 67 | it 'スコープ定義では警告されない' do 68 | expect_no_offenses(<<~RUBY) 69 | scope :with_posts, -> { includes(:posts) } 70 | RUBY 71 | end 72 | 73 | it '直接使用されている場合は警告されない' do 74 | expect_no_offenses(<<~RUBY) 75 | User.includes(:posts).each { |u| puts u.name } 76 | RUBY 77 | end 78 | 79 | it 'メソッドのreturn値として使用されている場合は警告されない' do 80 | expect_no_offenses(<<~RUBY) 81 | def users_with_posts 82 | User.includes(:posts) 83 | end 84 | RUBY 85 | end 86 | end 87 | 88 | context '複数のアソシエーションを含む場合' do 89 | it 'includesで複数のアソシエーションを指定してもstrict_loadingがない場合は警告される' do 90 | expect_offense(<<~RUBY) 91 | users = User.includes(:posts, :comments) 92 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/StrictLoadingRequired: Add `.strict_loading` when using `includes` or `preload` with variable assignment 93 | RUBY 94 | end 95 | 96 | it 'ネストしたアソシエーションでもstrict_loadingがない場合は警告される' do 97 | expect_offense(<<~RUBY) 98 | users = User.includes(posts: :comments) 99 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/StrictLoadingRequired: Add `.strict_loading` when using `includes` or `preload` with variable assignment 100 | RUBY 101 | end 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/transaction_requires_new_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::TransactionRequiresNew do 4 | subject(:cop) { described_class.new } 5 | 6 | it 'registers an offense for a transaction block raising ActiveRecord::Rollback directly without `requires_new: true`' do 7 | expect_offense(<<~RUBY) 8 | ActiveRecord::Base.transaction do 9 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/TransactionRequiresNew: Use `requires_new: true` with `transaction` when `ActiveRecord::Rollback` is raised inside the block. 10 | raise ActiveRecord::Rollback 11 | end 12 | RUBY 13 | end 14 | 15 | it 'registers an offense for a shorthand transaction block raising ActiveRecord::Rollback directly without `requires_new: true`' do 16 | expect_offense(<<~RUBY) 17 | transaction do 18 | ^^^^^^^^^^^ Sgcop/TransactionRequiresNew: Use `requires_new: true` with `transaction` when `ActiveRecord::Rollback` is raised inside the block. 19 | raise ActiveRecord::Rollback 20 | end 21 | RUBY 22 | end 23 | 24 | it 'registers an offense for a transaction block conditionally raising ActiveRecord::Rollback without `requires_new: true`' do 25 | expect_offense(<<~RUBY) 26 | transaction do 27 | ^^^^^^^^^^^ Sgcop/TransactionRequiresNew: Use `requires_new: true` with `transaction` when `ActiveRecord::Rollback` is raised inside the block. 28 | res = save 29 | raise ActiveRecord::Rollback unless res 30 | end 31 | RUBY 32 | end 33 | 34 | it 'does not register an offense for a transaction block with `requires_new: true` raising ActiveRecord::Rollback directly' do 35 | expect_no_offenses(<<~RUBY) 36 | ActiveRecord::Base.transaction(requires_new: true) do 37 | raise ActiveRecord::Rollback 38 | end 39 | RUBY 40 | end 41 | 42 | it 'does not register an offense for a transaction block with `requires_new: true` conditionally raising ActiveRecord::Rollback' do 43 | expect_no_offenses(<<~RUBY) 44 | transaction(requires_new: true) do 45 | res = save 46 | raise ActiveRecord::Rollback unless res 47 | end 48 | RUBY 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/ujs_options_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::UjsOptions do 4 | subject(:cop) { described_class.new } 5 | 6 | context 'link_to' do 7 | it 'methodオプションは警告' do 8 | expect_offense(<<~RUBY) 9 | link_to 'title', url, method: :delete 10 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/UjsOptions: Deprecated: Rails UJS Attributes. 11 | RUBY 12 | end 13 | 14 | it 'remoteオプションは警告' do 15 | expect_offense(<<~RUBY) 16 | link_to 'title', url, remote: true 17 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/UjsOptions: Deprecated: Rails UJS Attributes. 18 | RUBY 19 | end 20 | end 21 | 22 | context 'form_with' do 23 | it 'remoteオプションは警告' do 24 | expect_offense(<<~RUBY) 25 | form_with model: @user, remote: true 26 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/UjsOptions: Deprecated: Rails UJS Attributes. 27 | RUBY 28 | end 29 | 30 | it 'localオプションは警告' do 31 | expect_offense(<<~RUBY) 32 | form_with model: @user, local: false 33 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/UjsOptions: Deprecated: Rails UJS Attributes. 34 | RUBY 35 | end 36 | 37 | it 'methodオプションは警告なし' do 38 | expect_no_offenses(<<~RUBY) 39 | form_with model: @user, method: :patch 40 | RUBY 41 | end 42 | end 43 | 44 | context 'form_for' do 45 | it 'remoteオプションは警告' do 46 | expect_offense(<<~RUBY) 47 | form_for @user, remote: true 48 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/UjsOptions: Deprecated: Rails UJS Attributes. 49 | RUBY 50 | end 51 | 52 | it 'localオプションは警告' do 53 | expect_offense(<<~RUBY) 54 | form_for @user, local: true 55 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/UjsOptions: Deprecated: Rails UJS Attributes. 56 | RUBY 57 | end 58 | 59 | it 'methodオプションは警告なし' do 60 | expect_no_offenses(<<~RUBY) 61 | form_for @user, method: :put 62 | RUBY 63 | end 64 | end 65 | 66 | context 'simple_form_for' do 67 | it 'remoteオプションは警告' do 68 | expect_offense(<<~RUBY) 69 | simple_form_for @user, remote: true 70 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/UjsOptions: Deprecated: Rails UJS Attributes. 71 | RUBY 72 | end 73 | 74 | it 'localオプションは警告' do 75 | expect_offense(<<~RUBY) 76 | simple_form_for @user, local: false 77 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/UjsOptions: Deprecated: Rails UJS Attributes. 78 | RUBY 79 | end 80 | 81 | it 'methodオプションは警告なし' do 82 | expect_no_offenses(<<~RUBY) 83 | simple_form_for @user, method: :patch 84 | RUBY 85 | end 86 | end 87 | 88 | context 'bootstrap_form_with' do 89 | it 'remoteオプションは警告' do 90 | expect_offense(<<~RUBY) 91 | bootstrap_form_with model: @user, remote: true 92 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/UjsOptions: Deprecated: Rails UJS Attributes. 93 | RUBY 94 | end 95 | 96 | it 'localオプションは警告' do 97 | expect_offense(<<~RUBY) 98 | bootstrap_form_with model: @user, local: true 99 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/UjsOptions: Deprecated: Rails UJS Attributes. 100 | RUBY 101 | end 102 | 103 | it 'methodオプションは警告なし' do 104 | expect_no_offenses(<<~RUBY) 105 | bootstrap_form_with model: @user, method: :delete 106 | RUBY 107 | end 108 | end 109 | 110 | context 'button_to' do 111 | it 'remoteオプションは警告' do 112 | expect_offense(<<~RUBY) 113 | button_to 'Submit', url, remote: true 114 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/UjsOptions: Deprecated: Rails UJS Attributes. 115 | RUBY 116 | end 117 | 118 | it 'localオプションは警告' do 119 | expect_offense(<<~RUBY) 120 | button_to 'Submit', url, local: false 121 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sgcop/UjsOptions: Deprecated: Rails UJS Attributes. 122 | RUBY 123 | end 124 | 125 | it 'methodオプションは警告なし' do 126 | expect_no_offenses(<<~RUBY) 127 | button_to 'Delete', url, method: :delete 128 | RUBY 129 | end 130 | end 131 | end 132 | -------------------------------------------------------------------------------- /spec/rubocop/cop/sgcop/unscoped_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RuboCop::Cop::Sgcop::Unscoped do 4 | subject(:cop) { RuboCop::Cop::Sgcop::Unscoped.new } 5 | 6 | it 'unscoped が含まれている場合は警告される' do 7 | expect_offense(<<~RUBY) 8 | class BooksController < ApplicationController 9 | def index 10 | @books = books.unscoped.default_order 11 | ^^^^^^^^^^^^^^ Sgcop/Unscoped: Do not use `unscoped` 12 | end 13 | end 14 | RUBY 15 | end 16 | 17 | it 'unscoped が含まれていない場合は警告されない' do 18 | expect_no_offenses(<<~RUBY) 19 | class BooksController < ApplicationController 20 | def index 21 | @books = books.default_order 22 | end 23 | end 24 | RUBY 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/sgcop_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Sgcop do 4 | end 5 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 2 | require 'sgcop' 3 | require 'debug' 4 | 5 | require 'rubocop/rspec/support' 6 | 7 | RSpec.configure do |config| 8 | config.include RuboCop::RSpec::ExpectOffense 9 | end 10 | --------------------------------------------------------------------------------