danger-warnings 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +45 -0
- data/.travis.yml +27 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +4 -0
- data/Guardfile +19 -0
- data/LICENSE.txt +22 -0
- data/README.md +213 -0
- data/Rakefile +26 -0
- data/danger-warnings.gemspec +54 -0
- data/lib/danger_plugin.rb +1 -0
- data/lib/danger_warnings.rb +1 -0
- data/lib/warnings/gem_version.rb +3 -0
- data/lib/warnings/issue.rb +31 -0
- data/lib/warnings/markdown_util.rb +61 -0
- data/lib/warnings/parser/bandit_parser.rb +45 -0
- data/lib/warnings/parser/parser.rb +65 -0
- data/lib/warnings/parser/parser_factory.rb +25 -0
- data/lib/warnings/plugin.rb +82 -0
- data/lib/warnings/reporter.rb +158 -0
- data/lib/warnings/severity.rb +12 -0
- data/sonar-project.properties +9 -0
- data/spec/assets/assets.rb +8 -0
- data/spec/assets/bandit.json +74 -0
- data/spec/assets/bandit_empty.json +20 -0
- data/spec/assets/bandit_missing_results.json +2 -0
- data/spec/markdown_util_spec.rb +65 -0
- data/spec/parser/bandit_parser_spec.rb +102 -0
- data/spec/parser/parser_factory_spec.rb +34 -0
- data/spec/reporter_spec.rb +255 -0
- data/spec/severity_spec.rb +26 -0
- data/spec/spec_helper.rb +76 -0
- data/spec/warnings_spec.rb +28 -0
- metadata +283 -0
@@ -0,0 +1 @@
|
|
1
|
+
require 'warnings/plugin'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'warnings/gem_version'
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Warnings
|
2
|
+
# An issue definition to be used for reports.
|
3
|
+
class Issue
|
4
|
+
# The name of the file this issue targets.
|
5
|
+
#
|
6
|
+
# @return [String]
|
7
|
+
attr_accessor :file_name
|
8
|
+
# The issue id the linter tool provides.
|
9
|
+
#
|
10
|
+
# @return [String]
|
11
|
+
attr_accessor :id
|
12
|
+
# The line this issue targets.
|
13
|
+
#
|
14
|
+
# @return [Integer]
|
15
|
+
attr_accessor :line
|
16
|
+
# The severity level of this issue.
|
17
|
+
# Possible values are `low` `medium` `high`.
|
18
|
+
#
|
19
|
+
# @return [Symbol]
|
20
|
+
attr_accessor :severity
|
21
|
+
# The text message describe this issue.
|
22
|
+
#
|
23
|
+
# @return [String]
|
24
|
+
attr_accessor :message
|
25
|
+
|
26
|
+
# The name of the issue id.
|
27
|
+
#
|
28
|
+
# @return [String]
|
29
|
+
attr_accessor :name
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require_relative 'issue'
|
2
|
+
|
3
|
+
module Warnings
|
4
|
+
# Utility class to write the markdown report.
|
5
|
+
module MarkdownUtil
|
6
|
+
TABLE_HEADER = 'Severity|File|Message'.freeze
|
7
|
+
COLUMN_SEPARATOR = '|'.freeze
|
8
|
+
TABLE_SEPARATOR = "---#{COLUMN_SEPARATOR}---#{COLUMN_SEPARATOR}---".freeze
|
9
|
+
LINE_SEPARATOR = "\n".freeze
|
10
|
+
|
11
|
+
module_function
|
12
|
+
|
13
|
+
# Generate a markdown text message listing all issues as table.
|
14
|
+
#
|
15
|
+
# @param name [String] The name of the report to be printed.
|
16
|
+
# @param issues [Array<Issue>] List of parsed issues.
|
17
|
+
# @return [String] String in danger markdown format.
|
18
|
+
def generate(name, issues)
|
19
|
+
result = header_name(name)
|
20
|
+
result << header
|
21
|
+
result << issues(issues)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Create the report name string.
|
25
|
+
#
|
26
|
+
# @param report_name [String] The name of the report.
|
27
|
+
# @return [String] Text containing header name of the report.
|
28
|
+
def header_name(report_name)
|
29
|
+
"# #{report_name}#{LINE_SEPARATOR}"
|
30
|
+
end
|
31
|
+
|
32
|
+
# Create the base table header line.
|
33
|
+
#
|
34
|
+
# @return [String] String containing a markdown table header line.
|
35
|
+
def header
|
36
|
+
result = TABLE_HEADER.dup
|
37
|
+
result << LINE_SEPARATOR
|
38
|
+
result << TABLE_SEPARATOR
|
39
|
+
result << LINE_SEPARATOR
|
40
|
+
end
|
41
|
+
|
42
|
+
# Create a string containing all issues prepared to be used in a markdown table.
|
43
|
+
#
|
44
|
+
# @param issues [Array<Issue>] List of parsed issues.
|
45
|
+
# @return [String] String containing all issues.
|
46
|
+
# rubocop:disable Metrics/AbcSize
|
47
|
+
def issues(issues)
|
48
|
+
result = ''
|
49
|
+
issues.each do |issue|
|
50
|
+
result << issue.severity.to_s.capitalize
|
51
|
+
result << COLUMN_SEPARATOR
|
52
|
+
result << "#{issue.file_name}:#{issue.line}"
|
53
|
+
result << COLUMN_SEPARATOR
|
54
|
+
result << "[#{issue.id}-#{issue.name}] #{issue.message}"
|
55
|
+
result << LINE_SEPARATOR
|
56
|
+
end
|
57
|
+
# rubocop:enable Metrics/AbcSize
|
58
|
+
result
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require_relative 'parser'
|
2
|
+
require_relative '../issue'
|
3
|
+
|
4
|
+
module Warnings
|
5
|
+
# Parser class for bandit generated json files.
|
6
|
+
class BanditParser < Parser
|
7
|
+
RESULTS_KEY = 'results'.freeze
|
8
|
+
FILE_TYPES = %i(json).freeze
|
9
|
+
NAME = 'Bandit'.freeze
|
10
|
+
ERROR_MISSING_KEY = "Missing bandit key '#{RESULTS_KEY}'.".freeze
|
11
|
+
|
12
|
+
def file_types
|
13
|
+
FILE_TYPES
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse(file)
|
17
|
+
json_hash = json(file)
|
18
|
+
results_hash = json_hash[RESULTS_KEY]
|
19
|
+
raise(ERROR_MISSING_KEY) if results_hash.nil?
|
20
|
+
|
21
|
+
results_hash.each(&method(:store_issue))
|
22
|
+
end
|
23
|
+
|
24
|
+
def name
|
25
|
+
NAME
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def store_issue(hash)
|
31
|
+
issue = Issue.new
|
32
|
+
issue.file_name = hash['filename']
|
33
|
+
issue.severity = to_severity(hash['issue_severity'])
|
34
|
+
issue.message = hash['issue_text']
|
35
|
+
issue.line = hash['line_number']
|
36
|
+
issue.id = hash['test_id']
|
37
|
+
issue.name = hash['test_name']
|
38
|
+
@issues << issue
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_severity(severity)
|
42
|
+
severity.downcase.to_sym
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'abstract_method'
|
3
|
+
|
4
|
+
module Warnings
|
5
|
+
# Base parser class to define common methods.
|
6
|
+
class Parser
|
7
|
+
ERROR_FILE_NOT_EXIST = 'File \'%s\' does not exist.'.freeze
|
8
|
+
ERROR_EXT_NOT_SUPPORTED = 'File extension \'%s\' is not supported for parser %s.'.freeze
|
9
|
+
# All issues found by the parser.
|
10
|
+
#
|
11
|
+
# @return [Array<Issue>]
|
12
|
+
attr_accessor :issues
|
13
|
+
# Defines all supported file types for the parser.
|
14
|
+
#
|
15
|
+
# @return [Array<Symbol>] Array of file types.
|
16
|
+
abstract_method :file_types
|
17
|
+
# Execute the parser.
|
18
|
+
# Read the file and create an array of issues.
|
19
|
+
#
|
20
|
+
# @return [Array<Issue>] Array of issues.
|
21
|
+
abstract_method :parse
|
22
|
+
# Define a default name for the parser implementation.
|
23
|
+
#
|
24
|
+
# @return [String] Name of the parser implementation.
|
25
|
+
abstract_method :name
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
@issues = []
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
# Parse a file as json content.
|
34
|
+
#
|
35
|
+
# @param file_path [String] Path to a file to be read as json.
|
36
|
+
# @return [String] Hash of json values.
|
37
|
+
def json(file_path)
|
38
|
+
content = read_file(file_path)
|
39
|
+
JSON.parse(content)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# Evaluate and read the file into memory.
|
45
|
+
#
|
46
|
+
# @param file_path [String] Path to a file to be read.
|
47
|
+
# @raise If file does not exist or ist empty.
|
48
|
+
# @return [String] File content.
|
49
|
+
def read_file(file_path)
|
50
|
+
check_extname(file_path)
|
51
|
+
raise(format(ERROR_FILE_NOT_EXIST, file_path)) unless File.exist?(file_path)
|
52
|
+
|
53
|
+
File.read(file_path)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Evaluate the files extension name.
|
57
|
+
#
|
58
|
+
# @param file_path [String] Path to a file to be evaluated.
|
59
|
+
# @raise If file extension is not supported by the current parser.
|
60
|
+
def check_extname(file_path)
|
61
|
+
ext = File.extname(file_path).delete('.')
|
62
|
+
raise(format(ERROR_EXT_NOT_SUPPORTED, ext, self.class.name)) unless file_types.include?(ext.to_sym)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative 'bandit_parser'
|
2
|
+
|
3
|
+
module Warnings
|
4
|
+
# Factory class for supported parsers.
|
5
|
+
class ParserFactory
|
6
|
+
ERROR_NOT_SUPPORTED = 'Parser \'%s\' not supported.'.freeze
|
7
|
+
AVAILABLE_PARSERS = {
|
8
|
+
bandit: BanditParser
|
9
|
+
}.freeze
|
10
|
+
|
11
|
+
# Create a new parser implementation.
|
12
|
+
#
|
13
|
+
# @param type [Symbol] A key symbol / name to identify the parser.
|
14
|
+
# @raise If no implementation could be found for the key.
|
15
|
+
# @return [Parser] Implementation
|
16
|
+
def self.create(type)
|
17
|
+
key = type
|
18
|
+
key = key.to_sym if key.respond_to?(:to_sym)
|
19
|
+
parser = AVAILABLE_PARSERS[key]
|
20
|
+
raise(format(ERROR_NOT_SUPPORTED, key)) if parser.nil?
|
21
|
+
|
22
|
+
parser.new
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require_relative 'reporter'
|
2
|
+
|
3
|
+
module Danger
|
4
|
+
# Create uniform issue reports for different parser types.
|
5
|
+
# @example Create a basic bandit report.
|
6
|
+
# warnings.report(
|
7
|
+
# name: 'Bandit Report',
|
8
|
+
# parser: :bandit,
|
9
|
+
# file: report/bandit.json
|
10
|
+
# )
|
11
|
+
#
|
12
|
+
# @example Create a bandit report and comment inline for all files.
|
13
|
+
# warnings.report(
|
14
|
+
# name: 'Bandit Report',
|
15
|
+
# parser: :bandit,
|
16
|
+
# file: report/bandit.json,
|
17
|
+
# inline: true,
|
18
|
+
# filter: false
|
19
|
+
# )
|
20
|
+
#
|
21
|
+
# @see Kyaak/danger-warnings
|
22
|
+
# @tags warnings, danger, parser, issues, report
|
23
|
+
class DangerWarnings < Plugin
|
24
|
+
# Whether to comment as markdown report or do an inline comment on the file.
|
25
|
+
#
|
26
|
+
# This will be set as default for all reporters used in this danger run.
|
27
|
+
# It can still be overridden by setting the value when using #report.
|
28
|
+
#
|
29
|
+
# @return [Bool] Use inline comments.
|
30
|
+
attr_accessor :inline
|
31
|
+
# Whether to filter and report only for changes files.
|
32
|
+
# If this is set to false, all issues are of a report are included in the comment.
|
33
|
+
#
|
34
|
+
# This will be set as default for all reporters used in this danger run.
|
35
|
+
# It can still be overridden by setting the value when using #report.
|
36
|
+
#
|
37
|
+
# @return [Bool] Filter for changes files.
|
38
|
+
attr_accessor :filter
|
39
|
+
# Whether to fail the PR if any high issue is reported.
|
40
|
+
#
|
41
|
+
# This will be set as default for all reporters used in this danger run.
|
42
|
+
# It can still be overridden by setting the value when using #report.
|
43
|
+
#
|
44
|
+
# @return [Bool] Fail on high issues.
|
45
|
+
attr_accessor :fail_error
|
46
|
+
|
47
|
+
def initialize(dangerfile)
|
48
|
+
super(dangerfile)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Create a report for given arguments.
|
52
|
+
# name: 'Bandit Report',
|
53
|
+
# parser: :bandit,
|
54
|
+
# file: report/bandit.json,
|
55
|
+
# inline: true,
|
56
|
+
# filter: false
|
57
|
+
# @param args List of arguments to be used.
|
58
|
+
# @return [Reporter] The current reporter class which handles the issues.
|
59
|
+
def report(*args)
|
60
|
+
options = args.first
|
61
|
+
reporter = create_reporter(options)
|
62
|
+
reporter.report
|
63
|
+
reporter
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
# rubocop:disable Metrics/AbcSize
|
69
|
+
def create_reporter(options)
|
70
|
+
reporter = Warnings::Reporter.new(self)
|
71
|
+
reporter.parser = options[:parser]
|
72
|
+
reporter.name = options[:name]
|
73
|
+
reporter.file = options[:file]
|
74
|
+
reporter.baseline = options[:baseline]
|
75
|
+
reporter.inline = options[:inline] unless options[:inline].nil?
|
76
|
+
reporter.filter = options[:filter] unless options[:filter].nil?
|
77
|
+
reporter.fail_error = options[:fail_error] unless options[:fail_error].nil?
|
78
|
+
reporter
|
79
|
+
# rubocop:enable Metrics/AbcSize
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require_relative 'parser/parser_factory'
|
2
|
+
require_relative 'markdown_util'
|
3
|
+
require_relative 'severity'
|
4
|
+
|
5
|
+
module Warnings
|
6
|
+
# Base reporter class to define attributes and common method to create a report.
|
7
|
+
class Reporter
|
8
|
+
DEFAULT_INLINE = false
|
9
|
+
DEFAULT_FILTER = true
|
10
|
+
DEFAULT_FAIL = false
|
11
|
+
DEFAULT_NAME = 'Report'.freeze
|
12
|
+
ERROR_PARSER_NOT_SET = 'Parser is not set.'.freeze
|
13
|
+
ERROR_FILE_NOT_SET = 'File is not set.'.freeze
|
14
|
+
ERROR_HIGH_SEVERITY = '%s has high severity errors.'.freeze
|
15
|
+
|
16
|
+
# The name of this reporter. It is used to identify your report in the comments.
|
17
|
+
attr_writer :name
|
18
|
+
# Whether to comment a markdown report or do an inline comment on the file.
|
19
|
+
#
|
20
|
+
# @return [Bool] Use inline comments.
|
21
|
+
attr_accessor :inline
|
22
|
+
# Whether to filter and report only for changes files.
|
23
|
+
# If this is set to false, all issues are of a report are included in the comment.
|
24
|
+
#
|
25
|
+
# @return [Bool] Filter for changes files.
|
26
|
+
attr_accessor :filter
|
27
|
+
# Whether to fail the PR if any high issue is reported.
|
28
|
+
#
|
29
|
+
# @return [Bool] Fail on high issues.
|
30
|
+
attr_accessor :fail_error
|
31
|
+
# The parser to be used to read issues out of the file.
|
32
|
+
#
|
33
|
+
# @return [Symbol] Name of the parser.
|
34
|
+
attr_reader :parser
|
35
|
+
# The file path to parse.
|
36
|
+
#
|
37
|
+
# @return [String] Path to file.
|
38
|
+
attr_accessor :file
|
39
|
+
# Defines the baseline of file paths if needed.
|
40
|
+
# @example src/main/java
|
41
|
+
#
|
42
|
+
# @return [String] Path baseline for git files.
|
43
|
+
attr_accessor :baseline
|
44
|
+
# The generated implementation of the :parser.
|
45
|
+
#
|
46
|
+
# @return [Parser] Parser implementation
|
47
|
+
attr_reader :parser_impl
|
48
|
+
attr_reader :issues
|
49
|
+
|
50
|
+
def initialize(danger)
|
51
|
+
@danger = danger
|
52
|
+
@inline = DEFAULT_INLINE
|
53
|
+
@filter = DEFAULT_FILTER
|
54
|
+
@fail_error = DEFAULT_FAIL
|
55
|
+
@issues = []
|
56
|
+
end
|
57
|
+
|
58
|
+
# Start generating the report.
|
59
|
+
# Evaluate, parse and comment the found issues.
|
60
|
+
def report
|
61
|
+
validate
|
62
|
+
parse
|
63
|
+
filter_issues
|
64
|
+
comment
|
65
|
+
end
|
66
|
+
|
67
|
+
# Define the parser to be used.
|
68
|
+
#
|
69
|
+
# @@raise If no implementation can be found for the symbol.
|
70
|
+
# @param value [Symbol] A symbol key to match a parser implementation.
|
71
|
+
def parser=(value)
|
72
|
+
@parser = value
|
73
|
+
@parser_impl = ParserFactory.create(value)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Return the name of this reporter.
|
77
|
+
# The name can have 3 values:
|
78
|
+
# - The name set using #name=
|
79
|
+
# - If name is not set, the name of the parser
|
80
|
+
# - If name and parser are not set, a DEFAULT_NAME
|
81
|
+
#
|
82
|
+
# @return [String] Name of the reporter.
|
83
|
+
def name
|
84
|
+
result = @name
|
85
|
+
result ||= "#{@parser_impl.name} #{DEFAULT_NAME}" if @parser_impl
|
86
|
+
result || DEFAULT_NAME
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def filter_issues
|
92
|
+
return unless filter
|
93
|
+
|
94
|
+
git_files = @danger.git.modified_files + @danger.git.added_files
|
95
|
+
@issues.select! do |issue|
|
96
|
+
git_files.include?(issue_filename(issue))
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def issue_filename(item)
|
101
|
+
result = ''
|
102
|
+
if baseline
|
103
|
+
result << baseline
|
104
|
+
result << '/' unless baseline.chars.last == '/'
|
105
|
+
end
|
106
|
+
result << item.file_name
|
107
|
+
end
|
108
|
+
|
109
|
+
def validate
|
110
|
+
raise ERROR_PARSER_NOT_SET if @parser_impl.nil?
|
111
|
+
raise ERROR_FILE_NOT_SET if @file.nil?
|
112
|
+
end
|
113
|
+
|
114
|
+
def parse
|
115
|
+
@parser_impl.parse(file)
|
116
|
+
@issues = @parser_impl.issues
|
117
|
+
end
|
118
|
+
|
119
|
+
def comment
|
120
|
+
return if @issues.empty?
|
121
|
+
|
122
|
+
inline ? inline_comment : markdown_comment
|
123
|
+
end
|
124
|
+
|
125
|
+
def inline_comment
|
126
|
+
@issues.each do |issue|
|
127
|
+
text = inline_text(issue)
|
128
|
+
if fail_error && high_issue?(issue)
|
129
|
+
@danger.fail(text, line: issue.line, file: issue.file_name)
|
130
|
+
else
|
131
|
+
@danger.warn(text, line: issue.line, file: issue.file_name)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def inline_text(issue)
|
137
|
+
"#{issue.severity.to_s.capitalize}\n[#{issue.id}-#{issue.name}]\n#{issue.message}"
|
138
|
+
end
|
139
|
+
|
140
|
+
def markdown_comment
|
141
|
+
text = MarkdownUtil.generate(name, @issues)
|
142
|
+
@danger.markdown(text)
|
143
|
+
@danger.fail(format(ERROR_HIGH_SEVERITY, name)) if fail_error && high_issues?
|
144
|
+
end
|
145
|
+
|
146
|
+
def high_issues?
|
147
|
+
result = false
|
148
|
+
@issues.each do |issue|
|
149
|
+
result = true if high_issue?(issue)
|
150
|
+
end
|
151
|
+
result
|
152
|
+
end
|
153
|
+
|
154
|
+
def high_issue?(issue)
|
155
|
+
issue.severity.eql?(:high)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|