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.
@@ -0,0 +1 @@
1
+ require 'warnings/plugin'
@@ -0,0 +1 @@
1
+ require 'warnings/gem_version'
@@ -0,0 +1,3 @@
1
+ module Warnings
2
+ VERSION = '0.0.1'.freeze
3
+ end
@@ -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