Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
3 changed files
with
258 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |