Skip to content

Commit

Permalink
Add markdown formatter (#10542)
Browse files Browse the repository at this point in the history
  • Loading branch information
joe-sharp authored Apr 25, 2022
1 parent 0f2bc94 commit 6ff34fd
Show file tree
Hide file tree
Showing 9 changed files with 280 additions and 0 deletions.
1 change: 1 addition & 0 deletions changelog/new_add_markdown_formatter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [#10542](https://github.com/rubocop/rubocop/pull/10542): Add markdown formatter. ([@joe-sharp][])
9 changes: 9 additions & 0 deletions docs/modules/ROOT/pages/formatters.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,15 @@ Useful for CI environments. It will create an HTML report like http://f.cl.ly/it
$ rubocop --format html -o rubocop.html
----

== Markdown Formatter

Useful for CI environments, especially if posting comments back to pull requests. It will create a markdown report like https://github.com/rubocop/rubocop/blob/master/spec/fixtures/markdown_formatter/expected.md[this].

[source,sh]
----
$ rubocop --format markdown -o rubocop.md
----

== TAP Formatter

*Machine-parsable*
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,7 @@
require_relative 'rubocop/formatter/html_formatter'
require_relative 'rubocop/formatter/json_formatter'
require_relative 'rubocop/formatter/junit_formatter'
require_relative 'rubocop/formatter/markdown_formatter'
require_relative 'rubocop/formatter/offense_count_formatter'
require_relative 'rubocop/formatter/progress_formatter'
require_relative 'rubocop/formatter/quiet_formatter'
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop/formatter/formatter_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class FormatterSet < Array
'[h]tml' => HTMLFormatter,
'[j]son' => JSONFormatter,
'[ju]nit' => JUnitFormatter,
'[m]arkdown' => MarkdownFormatter,
'[o]ffenses' => OffenseCountFormatter,
'[pa]cman' => PacmanFormatter,
'[p]rogress' => ProgressFormatter,
Expand Down
76 changes: 76 additions & 0 deletions lib/rubocop/formatter/markdown_formatter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# frozen_string_literal: true

module RuboCop
module Formatter
# This formatter displays the report data in markdown
class MarkdownFormatter < BaseFormatter
include TextUtil
include PathUtil
attr_reader :files, :summary

def initialize(output, options = {})
super
@files = []
@summary = Struct.new(:offense_count, :inspected_files, :target_files).new(0)
end

def started(target_files)
summary.target_files = target_files
end

def file_finished(file, offenses)
files << Struct.new(:path, :offenses).new(file, offenses)
summary.offense_count += offenses.count
end

def finished(inspected_files)
summary.inspected_files = inspected_files
render_markdown
end

private

def render_markdown
n_files = pluralize(summary.inspected_files.count, 'file')
n_offenses = pluralize(summary.offense_count, 'offense', no_for_zero: true)

output.write "# RuboCop Inspection Report\n\n"
output.write "#{n_files} inspected, #{n_offenses} detected:\n\n"
write_file_messages
end

def write_file_messages
files.each do |file|
write_heading(file)
file.offenses.each do |offense|
write_context(offense)
write_code(offense)
end
end
end

def write_heading(file)
filename = relative_path(file.path)
n_offenses = pluralize(file.offenses.count, 'offense')

output.write "### #{filename} - (#{n_offenses})\n"
end

def write_context(offense)
output.write(
" * **Line # #{offense.location.line} - #{offense.severity}:** #{offense.message}\n\n"
)
end

def write_code(offense)
code = offense.location.source_line + possible_ellipses(offense.location)

output.write " ```rb\n #{code}\n ```\n\n" unless code.blank?
end

def possible_ellipses(location)
location.first_line == location.last_line ? '' : ' ...'
end
end
end
end
139 changes: 139 additions & 0 deletions spec/fixtures/markdown_formatter/expected.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# RuboCop Inspection Report

3 files inspected, 22 offenses detected:

### app/controllers/application_controller.rb - (1 offense)
* **Line # 1 - convention:** Style/FrozenStringLiteralComment: Missing frozen string literal comment.

```rb
class ApplicationController < ActionController::Base
```

### app/controllers/books_controller.rb - (14 offenses)
* **Line # 1 - convention:** Style/Documentation: Missing top-level documentation comment for `class BooksController`.

```rb
class BooksController < ApplicationController
```

* **Line # 1 - convention:** Style/FrozenStringLiteralComment: Missing frozen string literal comment.

```rb
class BooksController < ApplicationController
```

* **Line # 2 - convention:** Style/SymbolArray: Use `%i` or `%I` for an array of symbols.

```rb
before_action :set_book, only: [:show, :edit, :update, :destroy]
```

* **Line # 12 - convention:** Style/EmptyMethod: Put empty method definitions on a single line.

```rb
def show ...
```

* **Line # 21 - convention:** Style/EmptyMethod: Put empty method definitions on a single line.

```rb
def edit ...
```

* **Line # 31 - convention:** Layout/LineLength: Line is too long. [121/120]

```rb
format.html { redirect_to @book, notice: 'Book was successfully created.' } # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
```

* **Line # 45 - convention:** Layout/LineLength: Line is too long. [121/120]

```rb
format.html { redirect_to @book, notice: 'Book was successfully updated.' } # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
```

* **Line # 59 - convention:** Layout/LineLength: Line is too long. [121/120]

```rb
format.html { redirect_to books_url, notice: 'Book was successfully destroyed.' } # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
```

* **Line # 64 - convention:** Layout/EmptyLinesAroundAccessModifier: Keep a blank line before and after `private`.

```rb
private
```

* **Line # 66 - convention:** Layout/IndentationWidth: Use 2 (not 4) spaces for indentation.

```rb
def set_book
```

* **Line # 66 - convention:** Layout/IndentationConsistency: Inconsistent indentation detected.

```rb
def set_book ...
```

* **Line # 70 - convention:** Layout/LineLength: Line is too long. [121/120]

```rb
# Never trust parameters from the scary internet, only allow the allow list through. aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
```

* **Line # 71 - convention:** Layout/IndentationWidth: Use 2 (not 4) spaces for indentation.

```rb
def book_params
```

* **Line # 71 - convention:** Layout/IndentationConsistency: Inconsistent indentation detected.

```rb
def book_params ...
```

### app/models/book.rb - (7 offenses)
* **Line # 1 - convention:** Style/Documentation: Missing top-level documentation comment for `class Book`.

```rb
class Book < ActiveRecord::Base
```

* **Line # 1 - convention:** Style/FrozenStringLiteralComment: Missing frozen string literal comment.

```rb
class Book < ActiveRecord::Base
```

* **Line # 2 - convention:** Naming/MethodName: Use snake_case for method names.

```rb
def someMethod
```

* **Line # 3 - warning:** Lint/UselessAssignment: Useless assignment to variable - `foo`.

```rb
foo = bar = baz
```

* **Line # 3 - warning:** Lint/UselessAssignment: Useless assignment to variable - `bar`. Did you mean `baz`?

```rb
foo = bar = baz
```

* **Line # 4 - convention:** Style/RescueModifier: Avoid using `rescue` in its modifier form.

```rb
Regexp.new(/\A<p>(.*)<\/p>\Z/m).match(full_document)[1] rescue full_document
```

* **Line # 4 - convention:** Style/RegexpLiteral: Use `%r` around regular expression.

```rb
Regexp.new(/\A<p>(.*)<\/p>\Z/m).match(full_document)[1] rescue full_document
```

1 change: 1 addition & 0 deletions spec/fixtures/markdown_formatter/project
51 changes: 51 additions & 0 deletions spec/rubocop/formatter/markdown_formatter_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Formatter::MarkdownFormatter, :isolated_environment do
spec_root = File.expand_path('../..', __dir__)

around do |example|
project_path = File.join(spec_root, 'fixtures/markdown_formatter/project')
FileUtils.cp_r(project_path, '.')

Dir.chdir(File.basename(project_path)) { example.run }
end

# Run without Style/EndOfLine as it gives different results on
# different platforms.
# Metrics/AbcSize is very strict, exclude it too
let(:options) { %w[--except Layout/EndOfLine,Metrics/AbcSize --format markdown --out] }

let(:actual_markdown_path) do
path = File.expand_path('result.md')
RuboCop::CLI.new.run([*options, path])
path
end

let(:actual_markdown_path_cached) do
path = File.expand_path('result_cached.md')
2.times { RuboCop::CLI.new.run([*options, path]) }
path
end

let(:actual_markdown) { File.read(actual_markdown_path, encoding: Encoding::UTF_8) }

let(:actual_markdown_cached) { File.read(actual_markdown_path_cached, encoding: Encoding::UTF_8) }

let(:expected_markdown_path) { File.join(spec_root, 'fixtures/markdown_formatter/expected.md') }

let(:expected_markdown) do
markdown = File.read(expected_markdown_path, encoding: Encoding::UTF_8)
# Avoid failure on version bump
markdown.sub(/(class="version".{0,20})\d+(?:\.\d+){2}/i) do
Regexp.last_match(1) + RuboCop::Version::STRING
end
end

it 'outputs the result in Markdown' do
expect(actual_markdown).to eq(expected_markdown)
end

it 'outputs the cached result in Markdown' do
expect(actual_markdown_cached).to eq(expected_markdown)
end
end
1 change: 1 addition & 0 deletions spec/rubocop/options_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ def abs(path)
[h]tml
[j]son
[ju]nit
[m]arkdown
[o]ffenses
[pa]cman
[p]rogress (default)
Expand Down

0 comments on commit 6ff34fd

Please sign in to comment.