Skip to content

Commit

Permalink
Add TAP reporter
Browse files Browse the repository at this point in the history
To provide better compatibility with more tools, I thought that it would
be nice to add a TAP reporter to SCSS-Lint.

  http://testanything.org/

For a list of TAP consumers, look at

  http://testanything.org/consumers.html

Fixes #698
  • Loading branch information
lencioni committed Feb 22, 2016
1 parent 219413b commit 05fdc9a
Show file tree
Hide file tree
Showing 3 changed files with 258 additions and 0 deletions.
38 changes: 38 additions & 0 deletions README.md
Expand Up @@ -292,6 +292,44 @@ Outputs JSON with filenames and an array of issue objects.
}
```

### TAP

Outputs [TAP version 13](https://testanything.org) format.

```
TAP version 13
1..5
ok 1 - ok1.scss
not ok 2 - not-ok1.scss:123:10 SCSSLint::Linter::PrivateNamingConvention
---
message: Description of lint 1
severity: warning
data:
file: not-ok1.scss
line: 123
column: 10
---
not ok 3 - not-ok2.scss:20:2 SCSSLint::Linter::PrivateNamingConvention
---
message: Description of lint 2
severity: error
data:
file: not-ok2.scss
line: 20
column: 2
---
not ok 4 - not-ok2.scss:21:3 SCSSLint::Linter::PrivateNamingConvention
---
message: Description of lint 3
severity: warning
data:
file: not-ok2.scss
line: 21
column: 3
---
ok 5 - ok2.scss
```

### Plugins

There are also formatters that integrate with third-party tools which are available as plugins.
Expand Down
123 changes: 123 additions & 0 deletions lib/scss_lint/reporter/tap_reporter.rb
@@ -0,0 +1,123 @@
module SCSSLint
# Reports in TAP format.
# http://testanything.org/
class Reporter::TAPReporter < Reporter
TAP_VERSION = 'TAP version 13'.freeze

def report_lints
output = [TAP_VERSION, format_plan(files, lints)]
return format_output(output) unless files.any?

output.concat(format_files(files, lints))
format_output(output)
end

private

# @param files [Array<String>]
# @param lints [Array<SCSSLint::Lint>]
# @return [String]
def format_plan(files, lints)
files_with_lints = lints.map(&:filename).uniq
extra_lines = lints.count - files_with_lints.count
comment = files.count == 0 ? ' # No files to lint' : ''
"1..#{files.count + extra_lines}#{comment}"
end

# @param files [Array<String>]
# @param lints [Array<SCSSLint::Lint>]
# @return [Array<String>] one item per ok file or not ok lint
def format_files(files, lints)
unless lints.any?
# There are no lints, so we can take a shortcut and just output an ok
# test line for every file.
return files.map.with_index do |filename, index|
format_ok(filename, index + 1)
end
end

# There are lints, so we need to go through each file, find the lints
# for the file, and add them to the output.

# Since we'll be looking up lints by filename for each filename, we want
# to make a first pass to group all of the lints by filename to make
# lookup fast.
grouped_lints = group_lints_by_filename(lints)

test_number = 1
files.map do |filename|
if grouped_lints.key?(filename)
# This file has lints, so we want to generate a "not ok" test line for
# each failing lint.
grouped_lints[filename].map do |lint|
formatted = format_not_ok(lint, test_number)
test_number += 1
formatted
end
else
formatted = format_ok(filename, test_number)
test_number += 1
[formatted]
end
end.flatten
end

# @param lints [Array<SCSSLint::Lint>]
# @return [Hash] keyed by filename, values are arrays of lints
def group_lints_by_filename(lints)
grouped_lints = {}
lints.each do |lint|
grouped_lints[lint.filename] ||= []
grouped_lints[lint.filename] << lint
end
grouped_lints
end

# @param filename [String]
# @param test_number [Number]
# @return [String]
def format_ok(filename, test_number)
"ok #{test_number} - #{filename}"
end

# @param lint [SCSSLint::Lint]
# @param test_number [Number]
# @return [String]
def format_not_ok(lint, test_number)
location = lint.location
test_line_description = "#{lint.filename}:#{location.line}:#{location.column}"
test_line_description += " #{lint.linter.name}" if lint.linter

<<-EOS.strip
not ok #{test_number} - #{test_line_description}
---
message: #{lint.description}
severity: #{lint.severity}
data:
file: #{lint.filename}
line: #{lint.location.line}
column: #{lint.location.column}
---
EOS
end

# @param output [Array<String>]
# @return [String]
def format_output(output)
output.join("\n") + "\n"
end

def location(lint)
"#{log.cyan(lint.filename)}:#{log.magenta(lint.location.line.to_s)}"
end

def type(lint)
lint.error? ? log.red('[E]') : log.yellow('[W]')
end

def message(lint)
linter_name = log.green("#{lint.linter.name}: ") if lint.linter
"#{linter_name}#{lint.description}"
end
end
end
97 changes: 97 additions & 0 deletions spec/scss_lint/reporter/tap_reporter_spec.rb
@@ -0,0 +1,97 @@
require 'spec_helper'

describe SCSSLint::Reporter::TAPReporter do
let(:logger) { SCSSLint::Logger.new($stdout) }
subject { described_class.new(lints, filenames, logger) }

describe '#report_lints' do
context 'when there are no files' do
let(:filenames) { [] }
let(:lints) { [] }

it 'returns the TAP version, plan, and explanation' do
subject.report_lints.should == "TAP version 13\n1..0 # No files to lint\n"
end
end

context 'when there are files but no lints' do
let(:filenames) { ['file.scss', 'another-file.scss'] }
let(:lints) { [] }

it 'returns the TAP version, plan, and ok test lines' do
subject.report_lints.should eq(<<-EOS)
TAP version 13
1..2
ok 1 - file.scss
ok 2 - another-file.scss
EOS
end
end

context 'when there are some lints' do
let(:filenames) { %w[ok1.scss not-ok1.scss not-ok2.scss ok2.scss] }

let(:lints) do
[
SCSSLint::Lint.new(
SCSSLint::Linter::PrivateNamingConvention,
filenames[1],
SCSSLint::Location.new(123, 10, 8),
'Description of lint 1',
:warning
),
SCSSLint::Lint.new(
SCSSLint::Linter::PrivateNamingConvention,
filenames[2],
SCSSLint::Location.new(20, 2, 6),
'Description of lint 2',
:error
),
SCSSLint::Lint.new(
SCSSLint::Linter::PrivateNamingConvention,
filenames[2],
SCSSLint::Location.new(21, 3, 4),
'Description of lint 3',
:warning
),
]
end

it 'returns the TAP version, plan, and correct test lines' do
subject.report_lints.should eq(<<-EOS)
TAP version 13
1..5
ok 1 - ok1.scss
not ok 2 - not-ok1.scss:123:10 SCSSLint::Linter::PrivateNamingConvention
---
message: Description of lint 1
severity: warning
data:
file: not-ok1.scss
line: 123
column: 10
---
not ok 3 - not-ok2.scss:20:2 SCSSLint::Linter::PrivateNamingConvention
---
message: Description of lint 2
severity: error
data:
file: not-ok2.scss
line: 20
column: 2
---
not ok 4 - not-ok2.scss:21:3 SCSSLint::Linter::PrivateNamingConvention
---
message: Description of lint 3
severity: warning
data:
file: not-ok2.scss
line: 21
column: 3
---
ok 5 - ok2.scss
EOS
end
end
end
end

0 comments on commit 05fdc9a

Please sign in to comment.