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
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Pluginscan
|
|
2
|
+
# Extends Check with helpers for making patterns and ignores based on a list of function names
|
|
3
|
+
class FunctionCheck < Check
|
|
4
|
+
def initialize(check_hash)
|
|
5
|
+
check_hash.default = []
|
|
6
|
+
check_hash[:patterns] = self.class.patterns(check_hash[:patterns], check_hash[:function_names])
|
|
7
|
+
check_hash[:ignores] = self.class.ignores(check_hash[:ignores], check_hash[:function_names])
|
|
8
|
+
super(check_hash)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.function_list(functions)
|
|
12
|
+
# ^ Start of line
|
|
13
|
+
# [^a-z0-9|_] Characters which would imply that the word isn't the whole function name
|
|
14
|
+
# \s* any amount of whitespace
|
|
15
|
+
# \\( a literal open bracket - i.e. the start of the function arguments
|
|
16
|
+
functions.map{ |function| Regexp.new("(^|[^a-z0-9|_])(#{function})\s*\\(") }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.function_ignores(functions)
|
|
20
|
+
# If the author is creating a similarly named function then that should be ignored (?)
|
|
21
|
+
functions.map { |function| Regexp.new("function\s+[^a-z0-9|_]?#{function}\s*\\(") }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private def self.patterns(patterns, function_names)
|
|
25
|
+
Array(patterns) + function_list(function_names)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private def self.ignores(ignores, function_names)
|
|
29
|
+
Array(ignores) + function_ignores(function_names) + CommentChecker::COMMENT_REGEXPS
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Pluginscan
|
|
2
|
+
# Extends Check with helpers for making patterns and ignores based on a list of variables or constantst
|
|
3
|
+
class VariableCheck < Check
|
|
4
|
+
def initialize(check_hash)
|
|
5
|
+
super(check_hash)
|
|
6
|
+
@patterns = Array(check_hash[:variables]).map{ |var| Regexp.new(Regexp.escape(var)) }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def ignore?(variable, content)
|
|
10
|
+
VariableSafetyChecker.new.all_safe?(variable, content) || CommentChecker.commented?(content)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
module Pluginscan
|
|
2
|
+
# Responsible for deciding whether usages of a variable in a string are safe
|
|
3
|
+
class VariableSafetyChecker
|
|
4
|
+
# Functions which, if they surround the variable, make it safe
|
|
5
|
+
# because they return a boolean and not the value of the varaible
|
|
6
|
+
SAFE_FUNCTIONS = [
|
|
7
|
+
"isset",
|
|
8
|
+
"empty",
|
|
9
|
+
"in_array",
|
|
10
|
+
"strpos",
|
|
11
|
+
"strlen",
|
|
12
|
+
"if",
|
|
13
|
+
"switch",
|
|
14
|
+
"is_email",
|
|
15
|
+
|
|
16
|
+
# PHP typechecks - seen in the wild:
|
|
17
|
+
"is_array",
|
|
18
|
+
|
|
19
|
+
# PHP typechecks - not seen in the wild:
|
|
20
|
+
"is_bool",
|
|
21
|
+
"is_callable",
|
|
22
|
+
"is_double",
|
|
23
|
+
"is_float",
|
|
24
|
+
"is_int",
|
|
25
|
+
"is_integer",
|
|
26
|
+
"is_long",
|
|
27
|
+
"is_null",
|
|
28
|
+
"is_numeric",
|
|
29
|
+
"is_object",
|
|
30
|
+
"is_real",
|
|
31
|
+
"is_resource",
|
|
32
|
+
"is_scalar",
|
|
33
|
+
"is_string",
|
|
34
|
+
|
|
35
|
+
"intval",
|
|
36
|
+
"absint",
|
|
37
|
+
"wp_verify_nonce",
|
|
38
|
+
"count",
|
|
39
|
+
"sizeof",
|
|
40
|
+
"unset",
|
|
41
|
+
|
|
42
|
+
# Candidates for inclusion - not seen in the wild:
|
|
43
|
+
# "gettype",
|
|
44
|
+
# "settype",
|
|
45
|
+
# "boolval",
|
|
46
|
+
# "doubleval", # might match eval?
|
|
47
|
+
# "floatval",
|
|
48
|
+
].freeze
|
|
49
|
+
|
|
50
|
+
# Infixes which, if they are used around the variable, make it safe,
|
|
51
|
+
# because they are checking the value, not returning it
|
|
52
|
+
SAFE_INFIXES = [
|
|
53
|
+
'==',
|
|
54
|
+
'===',
|
|
55
|
+
'!=',
|
|
56
|
+
'!==',
|
|
57
|
+
'<',
|
|
58
|
+
'>',
|
|
59
|
+
'<=',
|
|
60
|
+
'>=',
|
|
61
|
+
].freeze
|
|
62
|
+
|
|
63
|
+
INFIX_CHARS = %w(= < > !).freeze
|
|
64
|
+
|
|
65
|
+
def all_safe?(variable, content)
|
|
66
|
+
match_count(variable, content) <= safe_count(variable, content)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def match_count(variable, content)
|
|
70
|
+
content.scan(variable).count # `scan` returns ALL matches
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def safe_count(variable, content)
|
|
74
|
+
safe_function_count(variable, content) +
|
|
75
|
+
safe_infix_count(variable, content)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# The number of matches which are safe by being wrapped in a function
|
|
81
|
+
private def safe_function_count(variable, content)
|
|
82
|
+
SAFE_FUNCTIONS.map { |function|
|
|
83
|
+
wrapped_in_function_count(function, variable, content)
|
|
84
|
+
}.inject(:+)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# The number of matches which are safe by being checked in an infix
|
|
88
|
+
private def safe_infix_count(variable, content)
|
|
89
|
+
SAFE_INFIXES.map { |infix|
|
|
90
|
+
used_in_infix_check_count(infix, variable, content)
|
|
91
|
+
}.inject(:+)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# TODO: the below methods feel private, but are directly tested
|
|
95
|
+
# That makes me feel like there's an object to be extracted here
|
|
96
|
+
def used_in_infix_check_count(infix, variable, content)
|
|
97
|
+
variable = Regexp.escape variable
|
|
98
|
+
infix = Regexp.escape infix
|
|
99
|
+
non_infix = "[^#{Regexp.escape INFIX_CHARS.join}]"
|
|
100
|
+
|
|
101
|
+
equals_before_regexp = /#{non_infix}#{infix}\ *#{variable}\ *\[/
|
|
102
|
+
equals_after_regexp = /#{variable}\ *\[[^\[]+\]\ *#{infix}#{non_infix}/
|
|
103
|
+
content.scan(equals_before_regexp).count +
|
|
104
|
+
content.scan(equals_after_regexp).count
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def wrapped_in_function_count(function_name, variable, content)
|
|
108
|
+
variable = Regexp.escape variable
|
|
109
|
+
content.scan(/#{function_name}\ *\(\ *#{variable}[^)]*\)/).count
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Pluginscan
|
|
2
|
+
Finding = Struct.new(:lineno, :line, :match, :ignored)
|
|
3
|
+
|
|
4
|
+
class CheckFindings
|
|
5
|
+
attr_reader :findings
|
|
6
|
+
attr_reader :check
|
|
7
|
+
|
|
8
|
+
def initialize(check)
|
|
9
|
+
@check = check
|
|
10
|
+
@findings = []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def add(more_findings)
|
|
14
|
+
@findings += more_findings
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def any_findings?
|
|
18
|
+
!findings.empty?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def all_ignored?
|
|
22
|
+
@findings.all?(&:ignored)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def count
|
|
26
|
+
findings.count
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Pluginscan
|
|
2
|
+
# Wraps a couple of helper methods around an array of issues
|
|
3
|
+
class Issues
|
|
4
|
+
include Enumerable
|
|
5
|
+
|
|
6
|
+
def initialize(issues)
|
|
7
|
+
@issues = issues
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def each(&block)
|
|
11
|
+
@issues.each(&block)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def scanned_files_count
|
|
15
|
+
@issues.count
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def found_problems_count
|
|
19
|
+
found_problems.reduce(0){ |count, (_file, file_findings)| count + file_findings.map(&:count).reduce(:+) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private def files
|
|
23
|
+
@issues.keys
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private def found_problems
|
|
27
|
+
# Ignore files which didn't have any problems
|
|
28
|
+
Issues.new(@issues.select { |_file, file_findings| !file_findings.empty? })
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require 'pluginscan/printer'
|
|
2
|
+
require 'pluginscan/reports/issues_report/issues_printer/file_issues_printer'
|
|
3
|
+
require 'pluginscan/reports/issues_report/issues_printer/check_findings_printer'
|
|
4
|
+
require 'pluginscan/reports/issues_report/issues_printer/finding_printer'
|
|
5
|
+
|
|
6
|
+
module Pluginscan
|
|
7
|
+
class IssuesPrinter < Printer
|
|
8
|
+
def initialize(hide_ignores, output = $stdout)
|
|
9
|
+
@hide_ignores = hide_ignores
|
|
10
|
+
@output = output
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def print(data)
|
|
14
|
+
issues = data[:issues]
|
|
15
|
+
file_count = data[:file_count]
|
|
16
|
+
print_headline(issues, file_count)
|
|
17
|
+
print_results(issues)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def print_headline(issues, file_count)
|
|
23
|
+
@output.puts "Scanned #{issues.scanned_files_count} out of #{file_count} files and found #{issues.found_problems_count} things:".color(:blue)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def print_results(issues)
|
|
27
|
+
printer = FileIssuesPrinter.new(@hide_ignores, @output)
|
|
28
|
+
issues.each do |file, findings|
|
|
29
|
+
findings = findings.reject(&:all_ignored?) if @hide_ignores
|
|
30
|
+
printer.print(file, findings)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Pluginscan
|
|
2
|
+
# Responsible for printing a check description and all of the
|
|
3
|
+
# findings associated with that check
|
|
4
|
+
class CheckFindingsPrinter < Printer
|
|
5
|
+
def print(check, findings)
|
|
6
|
+
return if findings.empty? && check.name != 'Encoding' # Encoding deliberately has no findings because its a file-level check. TODO: Find a better way to handle this. The benefits of filtering out findings before we try and print them are significant.
|
|
7
|
+
|
|
8
|
+
@output.puts CheckView.new(check).title_line
|
|
9
|
+
print_findings(findings)
|
|
10
|
+
print_blank_line
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def print_findings(findings)
|
|
16
|
+
printer = FindingPrinter.new(@output)
|
|
17
|
+
findings.each do |finding|
|
|
18
|
+
printer.print(finding)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Decorate Check with view-specific methods
|
|
24
|
+
class CheckView < SimpleDelegator
|
|
25
|
+
def initialize(check)
|
|
26
|
+
@check = check
|
|
27
|
+
super
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def title_line
|
|
31
|
+
name = "#{@check.name}:".color(:red)
|
|
32
|
+
message = @check.message.color(:yellow)
|
|
33
|
+
|
|
34
|
+
" #{name} #{message}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require 'delegate' # This shouldn't (?) be required - should be autoloaded, but apparently isn't
|
|
2
|
+
|
|
3
|
+
module Pluginscan
|
|
4
|
+
# Print a findings report for an individual file
|
|
5
|
+
class FileIssuesPrinter < Printer
|
|
6
|
+
def initialize(hide_ignores, output = $stdout)
|
|
7
|
+
@hide_ignores = hide_ignores
|
|
8
|
+
@output = output
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def print(file, checks_findings)
|
|
12
|
+
return if checks_findings.empty?
|
|
13
|
+
|
|
14
|
+
@output.puts FileView.new(file).file_path
|
|
15
|
+
print_findings(checks_findings)
|
|
16
|
+
# Doesn't need a blank line: each block of findings ends with a blank line
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def print_findings(checks_findings)
|
|
22
|
+
printer = CheckFindingsPrinter.new(@output)
|
|
23
|
+
checks_findings.each do |check_findings|
|
|
24
|
+
findings = check_findings.findings
|
|
25
|
+
findings.reject!(&:ignored) if @hide_ignores
|
|
26
|
+
printer.print(check_findings.check, findings)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class FileView < SimpleDelegator
|
|
32
|
+
def file_path
|
|
33
|
+
color(:green)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Pluginscan
|
|
2
|
+
# Responsible for printing a single finding on the command line
|
|
3
|
+
class FindingPrinter < Printer
|
|
4
|
+
def print(finding)
|
|
5
|
+
finding = FindingView.new(finding)
|
|
6
|
+
finding = IgnoredFindingView.new(finding) if finding.ignored
|
|
7
|
+
@output.puts finding.source_line
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class FindingView < SimpleDelegator
|
|
12
|
+
def source_line
|
|
13
|
+
data = {
|
|
14
|
+
lineno: lineno,
|
|
15
|
+
line: highlight_matches(line.strip),
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
# pad line number to 5. Because some plugin files really are over 10k lines
|
|
19
|
+
format " %<lineno>5s: %{line}\n", data
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def highlight_matches(line)
|
|
25
|
+
return line unless match # TODO: nil checks are evil!
|
|
26
|
+
|
|
27
|
+
line.gsub match, match.color(:cyan)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class IgnoredFindingView < SimpleDelegator
|
|
32
|
+
def source_line
|
|
33
|
+
i_symbol = "[I]".color(:red)
|
|
34
|
+
|
|
35
|
+
super.gsub(/^ /, " #{i_symbol}")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'pluginscan/reports/issues_report/issues_printer'
|
|
2
|
+
require 'pluginscan/reports/issues_report/error_list_printer'
|
|
3
|
+
|
|
4
|
+
module Pluginscan
|
|
5
|
+
# Responsible for creating an object which can print out the list of issues
|
|
6
|
+
# in one of several different ways
|
|
7
|
+
class IssuesPrinterFactory
|
|
8
|
+
def self.create_printer(issues_format, hide_ignores = false, output = $stdout)
|
|
9
|
+
case issues_format
|
|
10
|
+
when :report
|
|
11
|
+
IssuesPrinter.new(hide_ignores, output)
|
|
12
|
+
when :error_list
|
|
13
|
+
ErrorListPrinter.new(hide_ignores, output)
|
|
14
|
+
else
|
|
15
|
+
fail Pluginscan::UnknownIssuesFormat, "Unknown issues formatter '#{issues_format}'"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require 'pluginscan/reports/issues_report/issues_scanner/file_issues_scanner'
|
|
2
|
+
require 'pluginscan/reports/issues_report/issues_scanner/line_issues_scanner'
|
|
3
|
+
require 'pluginscan/reports/issues_report/issues_scanner/utf8_checker'
|
|
4
|
+
require 'pluginscan/reports/issues_report/issues_models/check_findings'
|
|
5
|
+
require 'pluginscan/reports/issues_report/issues_models/issues'
|
|
6
|
+
|
|
7
|
+
module Pluginscan
|
|
8
|
+
class IssuesScanner
|
|
9
|
+
def initialize(checks)
|
|
10
|
+
file_issues_scanner = FileIssuesScanner.new(checks)
|
|
11
|
+
@file_scanner = FileScanner.new(file_issues_scanner)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def scan(files)
|
|
15
|
+
issues = scan_files(files)
|
|
16
|
+
|
|
17
|
+
Issues.new(issues)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def scan_files(file_paths)
|
|
23
|
+
file_paths.inject({}) do |issues, file_path|
|
|
24
|
+
issues.merge file_path => scan_file(file_path)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def scan_file(file_path)
|
|
29
|
+
file_contents = File.read(file_path)
|
|
30
|
+
@file_scanner.scan(file_contents)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Responsible for checking that a file's contents is valid
|
|
35
|
+
# and if so, running an issues scan on it
|
|
36
|
+
class FileScanner
|
|
37
|
+
def initialize(issues_scanner)
|
|
38
|
+
@issues_scanner = issues_scanner
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def scan(file_contents)
|
|
42
|
+
# Check file contents are valid UTF-8 to avoid exceptions later
|
|
43
|
+
invalid_utf8 = UTF8Checker.new.check(file_contents)
|
|
44
|
+
return Array(invalid_utf8) if invalid_utf8
|
|
45
|
+
|
|
46
|
+
@issues_scanner.scan(file_contents)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'stringio'
|
|
2
|
+
|
|
3
|
+
module Pluginscan
|
|
4
|
+
# Responsible for scanning a file for a set of issue types
|
|
5
|
+
class FileIssuesScanner
|
|
6
|
+
attr_reader :file_results
|
|
7
|
+
|
|
8
|
+
def initialize(checks)
|
|
9
|
+
@checks = checks
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Returns an array of CheckFindings objects
|
|
13
|
+
def scan(file_contents)
|
|
14
|
+
# Run each check on the file
|
|
15
|
+
checks_findings = @checks.map { |check| LinesIssuesScanner.new(check).scan(file_contents) }
|
|
16
|
+
checks_findings.select(&:any_findings?)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
module Pluginscan
|
|
22
|
+
# Responsible for scanning each line of a file for issues of a certain type
|
|
23
|
+
class LinesIssuesScanner
|
|
24
|
+
def initialize(check)
|
|
25
|
+
@line_issues_scanner = LineIssuesScanner.new(check)
|
|
26
|
+
@check_findings = CheckFindings.new(check)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def scan(file_contents)
|
|
30
|
+
string_io = StringIO.new(file_contents)
|
|
31
|
+
@check_findings.tap do |check_findings|
|
|
32
|
+
string_io.each_line do |line|
|
|
33
|
+
check_findings.add @line_issues_scanner.scan(line, string_io.lineno)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|