danger-warnings 0.0.1
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 +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
|