From 34e703d26dd2adfa4f5d6c5561fe6f5b47592a32 Mon Sep 17 00:00:00 2001 From: Kassio Borges <kborges@gitlab.com> Date: Thu, 19 Oct 2023 21:53:26 +0000 Subject: [PATCH] Use Danger to notify about outdated rubocop todos When removing files, contributors might forget to remove the related rubocop todos. To avoid that, this new Danger plugin will add comments on MRs on deleted files that still have related rubocop todos. Changelog: added --- danger/plugins/todos.rb | 11 +++ danger/todos/Dangerfile | 3 + .../tooling/danger/rubocop_todo/cop1.yml | 5 ++ .../tooling/danger/rubocop_todo/cop2.yml | 4 + spec/tooling/danger/outdated_todo_spec.rb | 83 +++++++++++++++++++ tooling/danger/outdated_todo.rb | 58 +++++++++++++ 6 files changed, 164 insertions(+) create mode 100644 danger/plugins/todos.rb create mode 100644 danger/todos/Dangerfile create mode 100644 spec/fixtures/tooling/danger/rubocop_todo/cop1.yml create mode 100644 spec/fixtures/tooling/danger/rubocop_todo/cop2.yml create mode 100644 spec/tooling/danger/outdated_todo_spec.rb create mode 100644 tooling/danger/outdated_todo.rb diff --git a/danger/plugins/todos.rb b/danger/plugins/todos.rb new file mode 100644 index 0000000000000..b31f147f2af28 --- /dev/null +++ b/danger/plugins/todos.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require_relative '../../tooling/danger/outdated_todo' + +module Danger + class Todos < ::Danger::Plugin + def check_outdated_todos(filenames) + Tooling::Danger::OutdatedTodo.new(filenames, context: self).check + end + end +end diff --git a/danger/todos/Dangerfile b/danger/todos/Dangerfile new file mode 100644 index 0000000000000..45494e59bacfa --- /dev/null +++ b/danger/todos/Dangerfile @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +todos.check_outdated_todos(git.deleted_files) diff --git a/spec/fixtures/tooling/danger/rubocop_todo/cop1.yml b/spec/fixtures/tooling/danger/rubocop_todo/cop1.yml new file mode 100644 index 0000000000000..8f240b926823a --- /dev/null +++ b/spec/fixtures/tooling/danger/rubocop_todo/cop1.yml @@ -0,0 +1,5 @@ +--- +Cop1: + Exclude: + - 'app/controllers/application_controller.rb' + - 'app/controllers/acme_challenges_controller.rb' diff --git a/spec/fixtures/tooling/danger/rubocop_todo/cop2.yml b/spec/fixtures/tooling/danger/rubocop_todo/cop2.yml new file mode 100644 index 0000000000000..9ab2c0dabb9b9 --- /dev/null +++ b/spec/fixtures/tooling/danger/rubocop_todo/cop2.yml @@ -0,0 +1,4 @@ +--- +Cop2: + Exclude: + - 'app/controllers/application_controller.rb' diff --git a/spec/tooling/danger/outdated_todo_spec.rb b/spec/tooling/danger/outdated_todo_spec.rb new file mode 100644 index 0000000000000..3a3909c69ac2c --- /dev/null +++ b/spec/tooling/danger/outdated_todo_spec.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require 'gitlab/dangerfiles/spec_helper' + +require_relative '../../../tooling/danger/outdated_todo' + +RSpec.describe Tooling::Danger::OutdatedTodo, feature_category: :tooling do + let(:fake_danger) { double } + let(:filenames) { ['app/controllers/application_controller.rb'] } + + let(:todos) do + [ + File.join('spec', 'fixtures', 'tooling', 'danger', 'rubocop_todo', '**', '*.yml') + ] + end + + subject(:plugin) { described_class.new(filenames, context: fake_danger, todos: todos) } + + context 'when the filenames are mentioned in single todo' do + let(:filenames) { ['app/controllers/acme_challenges_controller.rb'] } + + it 'warns about mentions' do + expect(fake_danger) + .to receive(:warn) + .with <<~MESSAGE + `app/controllers/acme_challenges_controller.rb` was removed but is mentioned in: + - `spec/fixtures/tooling/danger/rubocop_todo/cop1.yml:5` + MESSAGE + + plugin.check + end + end + + context 'when the filenames are mentioned in multiple todos' do + let(:filenames) do + [ + 'app/controllers/application_controller.rb', + 'app/controllers/acme_challenges_controller.rb' + ] + end + + it 'warns about mentions' do + expect(fake_danger) + .to receive(:warn) + .with(<<~FIRSTMESSAGE) + `app/controllers/application_controller.rb` was removed but is mentioned in: + - `spec/fixtures/tooling/danger/rubocop_todo/cop1.yml:4` + - `spec/fixtures/tooling/danger/rubocop_todo/cop2.yml:4` + FIRSTMESSAGE + + expect(fake_danger) + .to receive(:warn) + .with(<<~SECONDMESSAGE) + `app/controllers/acme_challenges_controller.rb` was removed but is mentioned in: + - `spec/fixtures/tooling/danger/rubocop_todo/cop1.yml:5` + SECONDMESSAGE + + plugin.check + end + end + + context 'when the filenames are not mentioned in todos' do + let(:filenames) { ['any/inexisting/file.rb'] } + + it 'does not warn' do + expect(fake_danger).not_to receive(:warn) + + plugin.check + end + end + + context 'when there is no todos' do + let(:filenames) { ['app/controllers/acme_challenges_controller.rb'] } + let(:todos) { [] } + + it 'does not warn' do + expect(fake_danger).not_to receive(:warn) + + plugin.check + end + end +end diff --git a/tooling/danger/outdated_todo.rb b/tooling/danger/outdated_todo.rb new file mode 100644 index 0000000000000..a5f5cc897a9ae --- /dev/null +++ b/tooling/danger/outdated_todo.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module Tooling + module Danger + class OutdatedTodo + TODOS_GLOBS = %w[ + .rubocop_todo/**/*.yml + spec/support/rspec_order_todo.yml + ].freeze + + def initialize(filenames, context:, todos: TODOS_GLOBS) + @filenames = filenames + @context = context + @todos_globs = todos + end + + def check + filenames.each do |filename| + check_filename(filename) + end + end + + private + + attr_reader :filenames, :context + + def check_filename(filename) + mentions = all_mentions_for(filename) + + return if mentions.empty? + + context.warn <<~MESSAGE + `#{filename}` was removed but is mentioned in: + #{mentions.join("\n")} + MESSAGE + end + + def all_mentions_for(filename) + todos + .filter_map { |todo| mentioned_lines(filename, todo) } + .flatten + .map { |todo| "- `#{todo}`" } + end + + def mentioned_lines(filename, todo) + File + .foreach(todo) + .with_index(1) + .select { |text, _line| text.match?(/.*#{filename}.*/) } + .map { |_text, line| "#{todo}:#{line}" } + end + + def todos + @todos ||= @todos_globs.flat_map { |value| Dir.glob(value) } + end + end + end +end -- GitLab