diff --git a/danger/plugins/todos.rb b/danger/plugins/todos.rb new file mode 100644 index 0000000000000000000000000000000000000000..b31f147f2af28117cbe20617f37d2efa19a4d0a8 --- /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 0000000000000000000000000000000000000000..45494e59bacfabb773d614390300bf64a4ce1b49 --- /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 0000000000000000000000000000000000000000..8f240b926823a8ecf7b21639b1c2bca6746e1186 --- /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 0000000000000000000000000000000000000000..9ab2c0dabb9b91e8f3fe4ce99558e1b9ae41aa5c --- /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 0000000000000000000000000000000000000000..3a3909c69ac2cab28b90579041a644a2d84d2828 --- /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 0000000000000000000000000000000000000000..a5f5cc897a9ae4f7914ca74d6ef886cb9c02177e --- /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