Skip to content

Comments

Add autocorrection support for RSpec/IteratedExpectation for single expectations#2100

Merged
pirj merged 1 commit intorubocop:masterfrom
lovro-bikic:iterated-expectation-autocorrection
Aug 4, 2025
Merged

Add autocorrection support for RSpec/IteratedExpectation for single expectations#2100
pirj merged 1 commit intorubocop:masterfrom
lovro-bikic:iterated-expectation-autocorrection

Conversation

@lovro-bikic
Copy link
Contributor

@lovro-bikic lovro-bikic commented Aug 2, 2025

Currently, RSpec/IteratedExpectation doesn't support autocorrection.

This PR adds autocorrection support for single expectations, which is the most common scenario based on real-world data.

For example, the cop will correct this:

articles.each { |a| expect(a).to be_valid }

to:

expect(articles).all to(be_valid)

Autocorrection isn't performed when to is given more than one argument, or when the block argument is used in the matcher.

real-world-rails report (75 offenses, 65 are correctable):

Report
# rubocop ../real-world-rails/apps/*/spec --only RSpec/IteratedExpectation -f clang

# ~/real-world-rails/apps/alaveteli/spec/controllers/admin_user_controller_spec.rb:395:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
Comment.find(comment_ids).each { |comment| expect(comment).to be_visible }

# ~/real-world-rails/apps/alaveteli/spec/controllers/admin_user_controller_spec.rb:423:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
Comment.find(comment_ids).each { |c| expect(c).to be_visible }

# ~/real-world-rails/apps/alaveteli/spec/controllers/outgoing_messages/delivery_statuses_controller_spec.rb:70:9: C: RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
logs.each do |log|

# ~/real-world-rails/apps/alaveteli/spec/controllers/outgoing_messages/delivery_statuses_controller_spec.rb:92:9: C: RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
logs.each do |log|

# ~/real-world-rails/apps/alaveteli/spec/controllers/statistics_controller_spec.rb:51:11: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
graph['cis_below'].each { |v| expect(v).to be_instance_of(Float) }

# ~/real-world-rails/apps/alaveteli/spec/controllers/statistics_controller_spec.rb:52:11: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
graph['cis_above'].each { |v| expect(v).to be_instance_of(Float) }

# ~/real-world-rails/apps/alaveteli/spec/models/ability_spec.rb:645:9: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
all_the_abilities.each do |ability|

# ~/real-world-rails/apps/alaveteli/spec/models/alaveteli_pro/to_do_list/list_spec.rb:19:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
described_class.new(user).items.each do |item|

# ~/real-world-rails/apps/alaveteli/spec/models/info_request_batch_spec.rb:747:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
info_request_batch.info_requests.each do |request|

# ~/real-world-rails/apps/alchemy_cms/spec/decorators/alchemy/element_editor_spec.rb:22:9: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
ingredients.each do |ingredient|

# ~/real-world-rails/apps/alonetone/spec/models/waveform_spec.rb:15:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
data.each { |sample| expect(sample).to be_kind_of(Numeric) }

# ~/real-world-rails/apps/bike_index/spec/models/bike_sticker_spec.rb:347:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
[sticker, sticker2, spokecard, spokecard2].each { |bike_sticker| expect(bike_sticker).to be_valid }

# ~/real-world-rails/apps/canvas-lms/spec/apis/v1/account_reports_api_spec.rb:102:7: C: RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
json.each do |report|

# ~/real-world-rails/apps/canvas-lms/spec/apis/v1/assignment_groups_api_spec.rb:203:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
group["assignments"].each do |assignment|

# ~/real-world-rails/apps/canvas-lms/spec/apis/v1/conversations_api_spec.rb:139:9: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
conversation["participants"].each do |user|

# ~/real-world-rails/apps/canvas-lms/spec/apis/v1/discussion_topics_api_spec.rb:864:9: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
links.each do |link|

# ~/real-world-rails/apps/canvas-lms/spec/apis/v1/discussion_topics_api_spec.rb:878:9: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
links.each do |link|

# ~/real-world-rails/apps/canvas-lms/spec/apis/v1/discussion_topics_api_spec.rb:933:9: C: RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
links.each do |link|

# ~/real-world-rails/apps/canvas-lms/spec/apis/v1/files_controller_api_spec.rb:642:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
json.pluck("url").each { |url| expect(url).to include "verifier=" }

# ~/real-world-rails/apps/canvas-lms/spec/apis/v1/files_controller_api_spec.rb:658:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
json.pluck("url").each { |url| expect(url).to include "verifier=" }

# ~/real-world-rails/apps/canvas-lms/spec/lib/job_live_events_context_spec.rb:58:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
context_values.each do |value|

# ~/real-world-rails/apps/canvas-lms/spec/models/conditional_release/stats_spec.rb:200:9: C: RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
details[:follow_on_assignments].each do |detail|

# ~/real-world-rails/apps/canvas-lms/spec/models/master_courses/master_migration_spec.rb:2917:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
copied_things.each do |copied_obj|

# ~/real-world-rails/apps/canvas-lms/spec/models/quizzes/quiz_question/answer_group_spec.rb:67:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
question_data.answers.to_a.each do |a|

# ~/real-world-rails/apps/canvas-lms/spec/models/quizzes/quiz_question/answer_parsers/answer_parser_spec_helper.rb:50:5: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
ids.each { |id| expect(id).to be_a(Integer) }

# ~/real-world-rails/apps/canvas-lms/spec/models/quizzes/quiz_question/answer_parsers/calculated_spec.rb:57:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
@question[:formulas].each do |formula|

# ~/real-world-rails/apps/canvas-lms/spec/models/sis_batch_spec.rb:750:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
@section.enrollments.not_fake.each do |e|

# ~/real-world-rails/apps/canvas-lms/spec/models/user_spec.rb:3492:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
test_student.reload.enrollments.each { |e| expect(e).to be_deleted }

# ~/real-world-rails/apps/canvas-lms/spec/selenium/admin/admin_sub_accounts_spec.rb:113:19: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
displayed ? sub_accounts.each { |account| expect(account).to be_displayed } : sub_accounts.each { |account| expect(account).not_to be_displayed }

# ~/real-world-rails/apps/canvas-lms/spec/selenium/admin/site_admin_jobs_spec.rb:51:5: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
all_jobs.each { |job| expect(job).to have_class("selected") }

# ~/real-world-rails/apps/canvas-lms/spec/selenium/courses/course_settings_spec.rb:612:5: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
ffj("#tab-details input:visible").each do |input|

# ~/real-world-rails/apps/canvas-lms/spec/selenium/helpers/announcements_common.rb:70:30: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
(expected_results > 1) ? ff(".ic-announcement-row").each { |topic| expect(topic).to include_text(expected_text) } : (expect(f(".discussionTopicIndexList .discussion-topic")).to include_text(expected_text))

# ~/real-world-rails/apps/canvas-lms/spec/selenium/helpers/eportfolios_common.rb:36:5: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
sections.each do |section|

# ~/real-world-rails/apps/canvas-lms/spec/selenium/helpers/eportfolios_common.rb:102:5: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
pages.each do |page|

# ~/real-world-rails/apps/canvas-lms/spec/selenium/helpers/legacy_announcements_common.rb:70:30: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
(expected_results > 1) ? ff(".discussionTopicIndexList .discussion-topic").each { |topic| expect(topic).to include_text(expected_text) } : (expect(f(".discussionTopicIndexList .discussion-topic")).to include_text(expected_text))

# ~/real-world-rails/apps/canvas-lms/spec/selenium/profile/profile_spec.rb:383:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
links.each do |l|

# ~/real-world-rails/apps/cm42-central/spec/helpers/projects_helper_spec.rb:8:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
point_scale_options.each do |option|

# ~/real-world-rails/apps/cm42-central/spec/helpers/projects_helper_spec.rb:18:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
iteration_length_options.each do |option|

# ~/real-world-rails/apps/cm42-central/spec/helpers/projects_helper_spec.rb:28:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
day_name_options.each do |option|

# ~/real-world-rails/apps/diaspora/spec/integration/mentioning_spec.rb:188:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
[user2, user3].each do |user|

# ~/real-world-rails/apps/diaspora/spec/integration/mentioning_spec.rb:332:11: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
[user1, user2, user3].each do |user|

# ~/real-world-rails/apps/discourse/spec/system/private_message_map_spec.rb:116:5: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
private_message_map.participants_details.each do |details|

# ~/real-world-rails/apps/foodsoft/spec/models/order_spec.rb:112:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
order.order_articles.each { |oa| expect(oa).to be_valid }

# ~/real-world-rails/apps/foodsoft/spec/models/supplier_spec.rb:36:5: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
supplier.articles.each { |a| expect(a).to have_received(:mark_as_deleted) }

# ~/real-world-rails/apps/foodsoft/spec/models/supplier_spec.rb:47:5: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
supplier.articles.each { |a| expect(a).to be_valid }

# ~/real-world-rails/apps/huginn/spec/helpers/dot_helper_spec.rb:58:9: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
@agents.each do |agent|

# ~/real-world-rails/apps/hyku/spec/tasks/rake_spec.rb:72:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
accounts.each do |account|

# ~/real-world-rails/apps/identity-idp/spec/services/backup_code_generator_spec.rb:22:5: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
codes.each do |code|

# ~/real-world-rails/apps/identity-idp/spec/services/doc_auth/lexis_nexis/responses/true_id_response_spec.rb:207:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
passed_alerts.each do |alert|

# ~/real-world-rails/apps/identity-idp/spec/services/doc_auth/lexis_nexis/responses/true_id_response_spec.rb:215:9: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
alert[:region_ref].each do |region_ref|

# ~/real-world-rails/apps/manageiq/spec/lib/vmdb/settings_spec.rb:15:11: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
value.each { |v| expect(v).to be_kind_of(Config::Options) }

# ~/real-world-rails/apps/openfoodnetwork/spec/lib/open_food_network/order_cycle_form_applicator_spec.rb:177:13: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
exchanges.each { |ex| expect(ex).to receive(:destroy) }

# ~/real-world-rails/apps/openfoodnetwork/spec/mailers/producer_mailer_spec.rb:82:5: C: RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
body_lines_including(mail, p1.name).each do |line|

# ~/real-world-rails/apps/openfoodnetwork/spec/models/spree/order_spec.rb:227:7: C: RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
order.shipments.each do |shipment|

# ~/real-world-rails/apps/openproject/spec/controllers/work_packages/moves_controller_spec.rb:397:13: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
subject.map(&:project_id).each do |id|

# ~/real-world-rails/apps/openproject/spec/controllers/work_packages/moves_controller_spec.rb:403:13: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
subject.map(&:assigned_to_id).each do |id|

# ~/real-world-rails/apps/openproject/spec/controllers/work_packages/moves_controller_spec.rb:409:13: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
subject.map(&:responsible_id).each do |id|

# ~/real-world-rails/apps/openproject/spec/controllers/work_packages/moves_controller_spec.rb:415:13: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
subject.map(&:status_id).each do |id|

# ~/real-world-rails/apps/openproject/spec/controllers/work_packages/moves_controller_spec.rb:421:13: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
subject.map(&:version_id).each do |id|

# ~/real-world-rails/apps/openproject/spec/controllers/work_packages/moves_controller_spec.rb:427:13: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
subject.map(&:start_date).each do |date|

# ~/real-world-rails/apps/openproject/spec/controllers/work_packages/moves_controller_spec.rb:433:13: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
subject.map(&:due_date).each do |date|

# ~/real-world-rails/apps/openproject/spec/services/custom_actions/update_work_package_service_spec.rb:130:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
[alter_action1, alter_action2].each do |alter_action|

# ~/real-world-rails/apps/openproject/spec/services/work_packages/delete_service_spec.rb:157:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
descendants.each do |descendant|

# ~/real-world-rails/apps/prague-server/spec/workers/calculate_organization_totals_worker_spec.rb:17:5: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
tags.each { |tag| expect(tag).to receive(:reset_redis_keys!) }

# ~/real-world-rails/apps/prague-server/spec/workers/calculate_organization_totals_worker_spec.rb:25:5: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
namespaces.each { |namespace| expect(namespace).to receive(:reset_redis_keys!) }

# ~/real-world-rails/apps/prague-server/spec/workers/calculate_organization_totals_worker_spec.rb:43:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
tags.each {|tag| expect(tag).to receive(:incrby).with(charge_amount, status: charge.status, charge_date: charge.created_at) }

# ~/real-world-rails/apps/prison-visits/spec/configuration/prison_data_spec.rb:74:9: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
enabled.each do |prison|

# ~/real-world-rails/apps/prison-visits/spec/configuration/prison_data_spec.rb:89:11: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
times.each do |time|

# ~/real-world-rails/apps/rescue-rails/spec/features/cat/cat_sort_spec.rb:56:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
ages.each do |age|

# ~/real-world-rails/apps/rescue-rails/spec/features/cat/cat_sort_spec.rb:142:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
cat_names.each do |name|

# ~/real-world-rails/apps/rescue-rails/spec/features/dog/dog_sort_spec.rb:56:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
ages.each do |age|

# ~/real-world-rails/apps/rescue-rails/spec/features/dog/dog_sort_spec.rb:142:7: C: [Correctable] RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
dog_names.each do |name|

# ~/real-world-rails/apps/sharetribe/spec/view_utils/shape_service_spec.rb:37:7: C: RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
units.each{|unit|

# ~/real-world-rails/apps/verboice/spec/models/external_service_spec.rb:67:7: C: RSpec/IteratedExpectation: Prefer using the all matcher instead of iterating over an array.
external_service.call_flows.each do |call_flow|

Before submitting the PR make sure the following are checked:

  • Feature branch is up-to-date with master (if not - rebase it).
  • Squashed related commits together.
  • Added tests.
  • Updated documentation.
  • Added an entry to the CHANGELOG.md if the new code introduces user-observable changes.
  • The build (bundle exec rake) passes (be sure to run this locally, since it may produce updated documentation that you will need to commit).

If you have created a new cop:

  • Added the new cop to config/default.yml.
  • The cop is configured as Enabled: pending in config/default.yml.
  • The cop is configured as Enabled: true in .rubocop.yml.
  • The cop documents examples of good and bad code.
  • The tests assert both that bad code is reported and that good code is not reported.
  • Set VersionAdded: "<<next>>" in default/config.yml.

If you have modified an existing cop's configuration options:

  • Set VersionChanged: "<<next>>" in config/default.yml.

@lovro-bikic lovro-bikic requested a review from a team as a code owner August 2, 2025 09:37
Copy link
Member

@pirj pirj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic, thank you!

@lovro-bikic lovro-bikic force-pushed the iterated-expectation-autocorrection branch 2 times, most recently from d209545 to 4aa45c1 Compare August 3, 2025 11:48
@lovro-bikic lovro-bikic force-pushed the iterated-expectation-autocorrection branch from 4aa45c1 to 4af6191 Compare August 3, 2025 11:49
@pirj pirj merged commit 02e78e2 into rubocop:master Aug 4, 2025
27 checks passed
@lovro-bikic lovro-bikic deleted the iterated-expectation-autocorrection branch August 4, 2025 12:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants