pluginscan 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.gitlab-ci.yml +16 -0
- data/.rspec +3 -0
- data/.rubocop.yml +46 -0
- data/.rubocop_todo.yml +36 -0
- data/CHANGELOG.md +89 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +90 -0
- data/README.md +56 -0
- data/Rakefile +2 -0
- data/TODO.md +8 -0
- data/bin/pluginscan +53 -0
- data/lib/file_creator.rb +18 -0
- data/lib/pluginscan.rb +69 -0
- data/lib/pluginscan/error.rb +9 -0
- data/lib/pluginscan/error_printer.rb +17 -0
- data/lib/pluginscan/file_finder.rb +42 -0
- data/lib/pluginscan/printer.rb +14 -0
- data/lib/pluginscan/reports/cloc_report.rb +27 -0
- data/lib/pluginscan/reports/cloc_report/cloc.rb +21 -0
- data/lib/pluginscan/reports/cloc_report/cloc_printer.rb +42 -0
- data/lib/pluginscan/reports/cloc_report/cloc_scanner.rb +41 -0
- data/lib/pluginscan/reports/cloc_report/system_cloc.rb +33 -0
- data/lib/pluginscan/reports/issues_report.rb +24 -0
- data/lib/pluginscan/reports/issues_report/error_list_printer.rb +99 -0
- data/lib/pluginscan/reports/issues_report/issue_checks.rb +382 -0
- data/lib/pluginscan/reports/issues_report/issue_checks/check.rb +55 -0
- data/lib/pluginscan/reports/issues_report/issue_checks/comment_checker.rb +13 -0
- data/lib/pluginscan/reports/issues_report/issue_checks/function_check.rb +32 -0
- data/lib/pluginscan/reports/issues_report/issue_checks/variable_check.rb +14 -0
- data/lib/pluginscan/reports/issues_report/issue_checks/variable_safety_checker.rb +112 -0
- data/lib/pluginscan/reports/issues_report/issues_models/check_findings.rb +29 -0
- data/lib/pluginscan/reports/issues_report/issues_models/issues.rb +31 -0
- data/lib/pluginscan/reports/issues_report/issues_printer.rb +34 -0
- data/lib/pluginscan/reports/issues_report/issues_printer/check_findings_printer.rb +37 -0
- data/lib/pluginscan/reports/issues_report/issues_printer/file_issues_printer.rb +36 -0
- data/lib/pluginscan/reports/issues_report/issues_printer/finding_printer.rb +38 -0
- data/lib/pluginscan/reports/issues_report/issues_printer_factory.rb +19 -0
- data/lib/pluginscan/reports/issues_report/issues_scanner.rb +49 -0
- data/lib/pluginscan/reports/issues_report/issues_scanner/file_issues_scanner.rb +39 -0
- data/lib/pluginscan/reports/issues_report/issues_scanner/line_issues_scanner.rb +15 -0
- data/lib/pluginscan/reports/issues_report/issues_scanner/utf8_checker.rb +14 -0
- data/lib/pluginscan/reports/sloccount_report.rb +26 -0
- data/lib/pluginscan/reports/sloccount_report/sloccount.rb +19 -0
- data/lib/pluginscan/reports/sloccount_report/sloccount_printer.rb +22 -0
- data/lib/pluginscan/reports/sloccount_report/sloccount_scanner.rb +86 -0
- data/lib/pluginscan/reports/vulnerability_report.rb +28 -0
- data/lib/pluginscan/reports/vulnerability_report/advisories_api.rb +23 -0
- data/lib/pluginscan/reports/vulnerability_report/vulnerabilities_printer.rb +55 -0
- data/lib/pluginscan/reports/vulnerability_report/vulnerability_scanner.rb +17 -0
- data/lib/pluginscan/reports/vulnerability_report/wp_vuln_db_api.rb +77 -0
- data/lib/pluginscan/version.rb +3 -0
- data/pluginscan.gemspec +31 -0
- data/spec/acceptance/cloc_spec.rb +54 -0
- data/spec/acceptance/create_error_list_file_spec.rb +29 -0
- data/spec/acceptance/issues_spec.rb +197 -0
- data/spec/acceptance/pluginscan_spec.rb +18 -0
- data/spec/acceptance/sloccount_spec.rb +39 -0
- data/spec/acceptance/vulnerabilities_spec.rb +57 -0
- data/spec/acceptance_spec_helper.rb +10 -0
- data/spec/checks_examples_spec.rb +352 -0
- data/spec/file_creator_spec.rb +51 -0
- data/spec/pluginscan/cloc_scanner/cloc_scanner_spec.rb +64 -0
- data/spec/pluginscan/cloc_scanner/cloc_spec.rb +30 -0
- data/spec/pluginscan/file_finder_spec.rb +91 -0
- data/spec/pluginscan/issues_scanner/check_findings_spec.rb +22 -0
- data/spec/pluginscan/issues_scanner/error_list_printer_ignores_spec.rb +35 -0
- data/spec/pluginscan/issues_scanner/error_list_printer_spec.rb +42 -0
- data/spec/pluginscan/issues_scanner/file_issues_scanner_spec.rb +25 -0
- data/spec/pluginscan/issues_scanner/issues_printer_factory_spec.rb +9 -0
- data/spec/pluginscan/issues_scanner/issues_spec.rb +55 -0
- data/spec/pluginscan/issues_scanner/variable_check_spec.rb +13 -0
- data/spec/pluginscan/issues_scanner/variable_safety_checker_spec.rb +81 -0
- data/spec/pluginscan/issues_scanner_spec.rb +21 -0
- data/spec/pluginscan/sloccount_scanner/sloccount_scanner_spec.rb +95 -0
- data/spec/pluginscan/sloccount_scanner/sloccount_spec.rb +72 -0
- data/spec/pluginscan/vulnerability_scanner_spec.rb +96 -0
- data/spec/process_spec_helper.rb +6 -0
- data/spec/spec_helper.rb +70 -0
- data/spec/support/acceptance_helpers.rb +68 -0
- data/spec/support/file_helpers.rb +35 -0
- data/spec/support/heredoc_helper.rb +7 -0
- data/spec/support/process_helpers.rb +25 -0
- data/spec/support/shared_examples_for_issue_checks.rb +31 -0
- data/spec/support/vcr_helper.rb +6 -0
- data/vcr_cassettes/wpvulndb/relevanssi.yml +78 -0
- metadata +342 -0
data/lib/pluginscan.rb
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require 'pluginscan/reports/sloccount_report'
|
|
2
|
+
require 'pluginscan/reports/cloc_report'
|
|
3
|
+
require 'pluginscan/reports/vulnerability_report'
|
|
4
|
+
require 'pluginscan/reports/issues_report'
|
|
5
|
+
require 'pluginscan/reports/issues_report/issues_printer_factory'
|
|
6
|
+
require 'pluginscan/error'
|
|
7
|
+
|
|
8
|
+
module Pluginscan
|
|
9
|
+
class Scanner
|
|
10
|
+
DEFAULT_OPTIONS = {
|
|
11
|
+
output: $stdout,
|
|
12
|
+
sloccount: true,
|
|
13
|
+
cloc: true,
|
|
14
|
+
advisories: true,
|
|
15
|
+
issues_format: :report,
|
|
16
|
+
hide_ignores: false,
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
def initialize(options = {})
|
|
20
|
+
@options = DEFAULT_OPTIONS.merge options
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def scan(plugin_directory)
|
|
24
|
+
fail Errno::ENOENT unless Dir.exist? plugin_directory
|
|
25
|
+
|
|
26
|
+
output_sloccount_report(plugin_directory) if @options[:sloccount]
|
|
27
|
+
output_cloc_report(plugin_directory) if @options[:cloc]
|
|
28
|
+
|
|
29
|
+
output_vulnerability_report(plugin_directory) if @options[:advisories]
|
|
30
|
+
|
|
31
|
+
output_issues_report(plugin_directory)
|
|
32
|
+
output_error_list_to_file(plugin_directory) if @options[:error_list_file]
|
|
33
|
+
|
|
34
|
+
true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private def output_sloccount_report(plugin_directory)
|
|
38
|
+
sloccount_printer = SLOCCountPrinter.new(@options[:output])
|
|
39
|
+
Reports::SLOCCountReport.print(plugin_directory, sloccount_printer, error_printer)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private def output_cloc_report(plugin_directory)
|
|
43
|
+
cloc_printer = CLOCPrinter.new(@options[:output])
|
|
44
|
+
Reports::CLOCReport.print(plugin_directory, cloc_printer, error_printer)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private def output_vulnerability_report(plugin_directory)
|
|
48
|
+
vulnerabilities_printer = VulnerabilitiesPrinter.new(@options[:output])
|
|
49
|
+
Reports::VulnerabilityReport.print(plugin_directory, vulnerabilities_printer, error_printer)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private def output_issues_report(plugin_directory)
|
|
53
|
+
printer = IssuesPrinterFactory.create_printer(@options[:issues_format], @options[:hide_ignores], @options[:output])
|
|
54
|
+
Reports::IssuesReport.new(plugin_directory, printer).print
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private def error_printer
|
|
58
|
+
@error_printer ||= ErrorPrinter.new(@options[:output])
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private def output_error_list_to_file(plugin_directory)
|
|
62
|
+
error_list_file = @options[:error_list_file]
|
|
63
|
+
fail(IOError, IOError.message(error_list_file.class)) unless error_list_file.respond_to?(:puts)
|
|
64
|
+
|
|
65
|
+
printer = IssuesPrinterFactory.create_printer(:error_list, @options[:hide_ignores], error_list_file)
|
|
66
|
+
Reports::IssuesReport.new(plugin_directory, printer).print
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module Pluginscan
|
|
2
|
+
class Error < StandardError; end
|
|
3
|
+
class UnknownIssuesFormat < Error; end
|
|
4
|
+
class IOError < Error
|
|
5
|
+
def self.message(file_class)
|
|
6
|
+
"Expected error_list_file to be an I/O object (e.g. a file) which implements `puts`. Got a #{file_class}"
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'pluginscan/printer'
|
|
2
|
+
|
|
3
|
+
module Pluginscan
|
|
4
|
+
class ErrorPrinter < Printer
|
|
5
|
+
def print(error)
|
|
6
|
+
print_error_line(error)
|
|
7
|
+
print_blank_line
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def print_error_line(error)
|
|
13
|
+
label = "[ERROR]".color(:red)
|
|
14
|
+
@output.puts "#{label} #{error}"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require 'find'
|
|
2
|
+
|
|
3
|
+
module Pluginscan
|
|
4
|
+
# Responsible for searching through a directory for php files, and counting the total files
|
|
5
|
+
class FileFinder
|
|
6
|
+
def initialize(directory)
|
|
7
|
+
@directory = directory
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def count
|
|
11
|
+
found_files.count
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def php_files
|
|
15
|
+
found_files.php_files
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def found_files
|
|
21
|
+
@found_files ||= find_files
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def find_files
|
|
25
|
+
found_files = FoundFiles.new
|
|
26
|
+
|
|
27
|
+
Find.find @directory do |file|
|
|
28
|
+
found_files.count += 1 unless Dir.exist?(file) # Skip directories
|
|
29
|
+
found_files.php_files << file if file =~ /\.php$/
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
found_files
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
FoundFiles = Struct.new(:php_files, :count) do
|
|
36
|
+
def initialize
|
|
37
|
+
self.php_files = []
|
|
38
|
+
self.count = 0
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'pluginscan/reports/cloc_report/cloc'
|
|
2
|
+
require 'pluginscan/reports/cloc_report/system_cloc'
|
|
3
|
+
require 'pluginscan/reports/cloc_report/cloc_scanner'
|
|
4
|
+
require 'pluginscan/reports/cloc_report/cloc_printer'
|
|
5
|
+
require 'pluginscan/error_printer'
|
|
6
|
+
|
|
7
|
+
module Pluginscan
|
|
8
|
+
module Reports
|
|
9
|
+
module CLOCReport
|
|
10
|
+
def self.print(plugin_directory, printer, error_printer)
|
|
11
|
+
cloc = CLOCScanner.new.scan(plugin_directory)
|
|
12
|
+
printer.print(cloc)
|
|
13
|
+
|
|
14
|
+
rescue CLOCScanner::Unavailable
|
|
15
|
+
error_printer.print("The 'cloc' command is unavailable. Is it installed and in your path?")
|
|
16
|
+
rescue CLOCScanner::Exception => e
|
|
17
|
+
error_printer.print("An error occurred while calculating the sloccount with CLOC:\n #{e.message}")
|
|
18
|
+
rescue StandardError => e
|
|
19
|
+
# We don't want an error with CLOC to interrupt the rest of pluginscan
|
|
20
|
+
# TODO: not sure this is sensible
|
|
21
|
+
error_printer.print("An error occurred while calculating the sloccount with CLOC:\n #{e.message}")
|
|
22
|
+
else
|
|
23
|
+
return true
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
class CLOC
|
|
2
|
+
class CSVError < StandardError; end
|
|
3
|
+
|
|
4
|
+
def initialize(cloc_csv)
|
|
5
|
+
fail "Not a CSV: #{cloc_csv}" unless cloc_csv.is_a? CSV::Table
|
|
6
|
+
@cloc_csv = cloc_csv
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def language_counts
|
|
10
|
+
@cloc_csv.map { |row|
|
|
11
|
+
next if row.empty?
|
|
12
|
+
language = row["language"]
|
|
13
|
+
sloc = Integer(row["code"])
|
|
14
|
+
file_count = Integer(row["files"])
|
|
15
|
+
LanguageCount.new(language, sloc, file_count)
|
|
16
|
+
}.compact
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
LanguageCount = Struct.new(:language, :sloc, :file_count)
|
|
20
|
+
end
|
|
21
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require 'pluginscan/printer'
|
|
2
|
+
|
|
3
|
+
module Pluginscan
|
|
4
|
+
class CLOCPrinter < Printer
|
|
5
|
+
def print(cloc)
|
|
6
|
+
print_headline
|
|
7
|
+
print_results(cloc)
|
|
8
|
+
print_blank_line
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def print_headline
|
|
14
|
+
@output.puts "SLOC counts from the 'CLOC' tool:".color(:blue)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def print_results(cloc)
|
|
18
|
+
print_no_result if cloc.language_counts.empty?
|
|
19
|
+
|
|
20
|
+
cloc.language_counts.each{ |language_count| print_result language_count }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def print_result(language_count)
|
|
24
|
+
data = {
|
|
25
|
+
language: language_count.language.color(:green),
|
|
26
|
+
sloc: language_count.sloc.to_s.color(:red),
|
|
27
|
+
file_count: language_count.file_count,
|
|
28
|
+
}
|
|
29
|
+
# Field widths need to account for the colouring characters: 9 of them in total
|
|
30
|
+
# e.g. for red this is \e[31m at the beginning and \e[0m at the end,
|
|
31
|
+
# with \e counting as one character:
|
|
32
|
+
# pad sloc to 5 (+9 = 14)
|
|
33
|
+
# pad language to 12 (+9 = 21)
|
|
34
|
+
@output.puts format(" %<language>-21s %<sloc>14s lines across %<file_count>2d files\n", data)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def print_no_result
|
|
38
|
+
sadface = ":(".color(:red)
|
|
39
|
+
@output.puts " CLOC didn't find any code #{sadface}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require 'csv'
|
|
2
|
+
|
|
3
|
+
# Responsible for running the `cloc` system command and handling any resulting errors
|
|
4
|
+
class CLOCScanner
|
|
5
|
+
class Exception < StandardError; end
|
|
6
|
+
class ArgumentError < RuntimeError; end
|
|
7
|
+
class Unavailable < RuntimeError; end
|
|
8
|
+
class NoDirectory < RuntimeError; end
|
|
9
|
+
class CSVError < RuntimeError; end
|
|
10
|
+
|
|
11
|
+
def initialize(system_cloc = SystemCloc.new)
|
|
12
|
+
@system_cloc = system_cloc
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def scan(directory)
|
|
16
|
+
fail ArgumentError, "directory must must be a string (or quack like a string)" unless directory.respond_to?(:to_str)
|
|
17
|
+
fail Unavailable, "The 'cloc' command is unavailable. Is it installed and in your path?" unless cloc_available?
|
|
18
|
+
fail NoDirectory, "No such directory: '#{directory}'" unless Dir.exist?(directory)
|
|
19
|
+
|
|
20
|
+
result, status = @system_cloc.call(directory)
|
|
21
|
+
|
|
22
|
+
if status.success?
|
|
23
|
+
CLOC.new(cloc_csv(result))
|
|
24
|
+
else
|
|
25
|
+
raise Exception, "CLOC raised an error we didn't recognise. Here's the output:\n#{result}"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def cloc_csv(cloc_result)
|
|
32
|
+
CSV.parse(cloc_result.lstrip, headers: true)
|
|
33
|
+
rescue CSV::MalformedCSVError => e
|
|
34
|
+
raise CSVError, "The CSV generated by CLOC was malformed: #{e}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def cloc_available?
|
|
38
|
+
_result, status = @system_cloc.which
|
|
39
|
+
status.success?
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# A thin wrapper around the `cloc` system call
|
|
2
|
+
# The CLOC project is at http://cloc.sourceforge.net/ and can be installed on OSX using homebrew:
|
|
3
|
+
# brew install cloc
|
|
4
|
+
#
|
|
5
|
+
# DANGER: not covered by tests
|
|
6
|
+
class SystemCloc
|
|
7
|
+
def initialize(command_name = 'cloc')
|
|
8
|
+
@command_name = command_name
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def which
|
|
12
|
+
# DANGER!!! calling system command
|
|
13
|
+
Open3.capture2("which #{@command_name}")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call(directory)
|
|
17
|
+
# DANGER!!! calling system command
|
|
18
|
+
# Should be safe from injection because of the `Dir.exist?` check.
|
|
19
|
+
Open3.capture2("#{@command_name} --csv --quiet #{directory} 2> #{error_log_name}")
|
|
20
|
+
ensure
|
|
21
|
+
delete_empty_error_log
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def error_log_name
|
|
27
|
+
"#{@command_name}_error.log"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def delete_empty_error_log
|
|
31
|
+
File.delete(error_log_name) if File.zero?(error_log_name)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require 'pluginscan/file_finder'
|
|
2
|
+
require 'pluginscan/reports/issues_report/issues_scanner'
|
|
3
|
+
require 'pluginscan/reports/issues_report/issue_checks'
|
|
4
|
+
|
|
5
|
+
module Pluginscan
|
|
6
|
+
module Reports
|
|
7
|
+
class IssuesReport
|
|
8
|
+
def initialize(plugin_directory, printer = IssuesPrinter.new)
|
|
9
|
+
found_files = FileFinder.new(plugin_directory)
|
|
10
|
+
issues = IssuesScanner.new(THE_CHECKS).scan(found_files.php_files)
|
|
11
|
+
@data = {
|
|
12
|
+
issues: issues,
|
|
13
|
+
file_count: found_files.count,
|
|
14
|
+
}
|
|
15
|
+
@printer = printer
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def print
|
|
19
|
+
@printer.print(@data)
|
|
20
|
+
true
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
module Pluginscan
|
|
2
|
+
class ErrorListPrinter < Printer
|
|
3
|
+
def initialize(hide_ignores = false, output = $stdout)
|
|
4
|
+
@hide_ignores = hide_ignores
|
|
5
|
+
@output = output
|
|
6
|
+
@line_printer = ErrorLinePrinter.new
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def print(data)
|
|
10
|
+
issues = data[:issues]
|
|
11
|
+
@output.puts error_lines(issues)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# TODO: this should be the print method; return an array of lines, and let the caller be responsible for outputting it
|
|
15
|
+
def error_lines(issues)
|
|
16
|
+
issues.inject([]) do |output, (file, file_findings)|
|
|
17
|
+
output + file_error_lines(file, file_findings)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private def file_error_lines(file, file_findings)
|
|
22
|
+
file_findings.inject([]) do |checks_output, check_findings|
|
|
23
|
+
checks_output + check_output(file, check_findings.check, check_findings.findings)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private def check_output(file, check, findings)
|
|
28
|
+
findings.reject!(&:ignored) if @hide_ignores
|
|
29
|
+
findings.map do |finding|
|
|
30
|
+
error_line = ErrorLineFactory.build(file, check.name, finding)
|
|
31
|
+
@line_printer.print(error_line)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class ErrorLinePrinter
|
|
37
|
+
def print(el)
|
|
38
|
+
"\"#{el.file}\", line #{el.line_number}, col #{el.column_number}: #{el.message}"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# This is almost like a view model: exposes methods for use in a template
|
|
43
|
+
class ErrorLine
|
|
44
|
+
attr_reader :file
|
|
45
|
+
|
|
46
|
+
def initialize(file, check_name, finding)
|
|
47
|
+
@file = file
|
|
48
|
+
@check_name = check_name
|
|
49
|
+
@finding = PrintableFinding.new(finding)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def line_number
|
|
53
|
+
# TODO: why is the original called lineno?? That's a rubbish name!
|
|
54
|
+
@finding.lineno
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def column_number
|
|
58
|
+
@finding.col_number
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def message
|
|
62
|
+
"[#{@check_name}] #{@finding.line}"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
class IgnoredErrorLine < ErrorLine
|
|
67
|
+
# TODO: would decoration be better here?
|
|
68
|
+
def message
|
|
69
|
+
super.sub("]", "][IGNORE]")
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
class ErrorLineFactory
|
|
74
|
+
def self.build(file, check_name, finding)
|
|
75
|
+
klass(finding.ignored).new(file, check_name, finding)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def self.klass(ignored)
|
|
79
|
+
ignored ? IgnoredErrorLine : ErrorLine
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
class PrintableFinding < SimpleDelegator
|
|
84
|
+
def col_number
|
|
85
|
+
# Seems like vim treats tabs as single spaces for the purposes of calculating columns - at least on my setup
|
|
86
|
+
# so we don't need to expand the tabs
|
|
87
|
+
__getobj__.line.index(match) + 1
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def line
|
|
91
|
+
escape_special_chars(__getobj__.line.strip)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private def escape_special_chars(string)
|
|
95
|
+
# Vim interprets `:` as a delimiter
|
|
96
|
+
string.gsub(":", '\:')
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|