danger-warnings 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/Dangerfile +5 -0
- data/README.md +33 -5
- data/lib/warnings/gem_version.rb +1 -1
- data/lib/warnings/{markdown_util.rb → helper/message_util.rb} +31 -7
- data/lib/warnings/helper/severity_util.rb +45 -0
- data/lib/warnings/parser/bandit_parser.rb +2 -8
- data/lib/warnings/parser/parser.rb +23 -15
- data/lib/warnings/parser/parser_factory.rb +5 -1
- data/lib/warnings/parser/pylint_parser.rb +38 -0
- data/lib/warnings/parser/rubocop_parser.rb +77 -0
- data/lib/warnings/plugin.rb +1 -1
- data/lib/warnings/{issue.rb → report/issue.rb} +2 -7
- data/lib/warnings/{reporter.rb → report/reporter.rb} +4 -9
- data/spec/assets/empty.txt +0 -0
- data/spec/assets/pylint.txt +582 -0
- data/spec/assets/rubocop.json +265 -0
- data/spec/assets/rubocop.txt +27 -0
- data/spec/assets/rubocop_multi_offenses.json +142 -0
- data/spec/helper/message_util_spec.rb +108 -0
- data/spec/helper/severity_util_spec.rb +70 -0
- data/spec/parser/bandit_parser_spec.rb +8 -36
- data/spec/parser/parser_factory_spec.rb +24 -12
- data/spec/parser/pylint_parser_spec.rb +57 -0
- data/spec/parser/rubocop_parser_spec.rb +94 -0
- data/spec/{reporter_spec.rb → report/reporter_spec.rb} +42 -2
- data/spec/spec_helper.rb +1 -1
- data/spec/spec_helper/assets.rb +53 -0
- data/spec/warnings_spec.rb +0 -5
- metadata +31 -14
- data/lib/warnings/severity.rb +0 -12
- data/spec/assets/assets.rb +0 -8
- data/spec/markdown_util_spec.rb +0 -65
- data/spec/severity_spec.rb +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90dcfaf900785427cae7f5996651fd006b6fc4fee2f67eb0674e330ff171ccab
|
4
|
+
data.tar.gz: f9c6800309d84929707bde221549d6ad3da3b84ddf51171cbad5f314132feccd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 184a35b56e0e5c80ae92f169df055033e26d88f7d09db9310987933582fab388c9579939b57ff886d789bc486d941b166b3ae17d6df8411fcfd4695f876c17a6
|
7
|
+
data.tar.gz: e942fad54a71a5f1bf3cdb31675228db17744ae7abf3777b80c4160a4739489250b19611422c7c469c69156d5113c430ac2300bc5f26545d1669cc178ae67d43
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## [0.1.0] - 2019-01-23
|
10
|
+
### Added
|
11
|
+
- PylintParser - Support Pylint formatted reports
|
12
|
+
- RuboCopParser - Support RuboCop formatted reports
|
13
|
+
|
14
|
+
### Changed
|
15
|
+
- Renamed issue.id to issue.category
|
16
|
+
|
17
|
+
### Removed
|
18
|
+
- Remove issue.name
|
19
|
+
|
9
20
|
## [0.0.1] - 2019-01-21
|
10
21
|
### Added
|
11
22
|
- Initial release
|
data/Dangerfile
ADDED
data/README.md
CHANGED
@@ -10,6 +10,17 @@
|
|
10
10
|
|
11
11
|
</br>
|
12
12
|
|
13
|
+
<div align="center">
|
14
|
+
<!-- Version -->
|
15
|
+
<a href="https://badge.fury.io/rb/danger-warnings">
|
16
|
+
<img src="https://badge.fury.io/rb/danger-warnings.svg" alt="Version" />
|
17
|
+
</a>
|
18
|
+
<!-- Downloads -->
|
19
|
+
<a href="https://badge.fury.io/rb/danger-warnings">
|
20
|
+
<img src="https://img.shields.io/gem/dt/danger-warnings.svg" alt="Downloads" />
|
21
|
+
</a>
|
22
|
+
</div>
|
23
|
+
|
13
24
|
<div align="center">
|
14
25
|
<!-- Build Status -->
|
15
26
|
<a href="https://travis-ci.org/Kyaak/danger-warnings">
|
@@ -79,8 +90,10 @@
|
|
79
90
|
|
80
91
|
</br>
|
81
92
|
|
82
|
-
This [danger](https://github.com/danger/danger) plugin provides a uniform report format for various
|
83
|
-
The purpose is a simple to use plugin regardless of the
|
93
|
+
This [danger](https://github.com/danger/danger) plugin provides a uniform report format for various [tools](#parsers). <br>
|
94
|
+
The purpose is a simple to use plugin regardless of the tool used to find issues in your project :detective:
|
95
|
+
|
96
|
+
This plugin was inspired by the work of [warnings-ng-plugin](https://github.com/jenkinsci/warnings-ng-plugin) :bowing_man:
|
84
97
|
|
85
98
|
## Table of Contents
|
86
99
|
- [How it looks like](#how-does-it-look)
|
@@ -206,8 +219,23 @@ All [default](#override-default-settings) fields can be passed as parameters to
|
|
206
219
|
|
207
220
|
These will override the configuration for this report **only**.
|
208
221
|
|
222
|
+
#### What it does not
|
223
|
+
It is not the responsibility of this plugin to exclude / include files or directories. We will only process the result and present it to you.
|
224
|
+
Something like this belongs to your tool configuration before running it.
|
225
|
+
|
209
226
|
## Parsers
|
210
227
|
|
211
|
-
|
212
|
-
|
213
|
-
|
228
|
+
Find a list with supported report formats and their parsers.
|
229
|
+
|
230
|
+
If your desired parser is not explicitly named, look into your tools documentation - maybe you can format
|
231
|
+
the report in a different style (and give it a custom name when calling `warnings.report`).
|
232
|
+
|
233
|
+
`any` file format means that the file is most likely read line by line, so the extension is not important.
|
234
|
+
|
235
|
+
Your parser is missing and you cannot export into another format? -> [Create an Issue](https://github.com/Kyaak/danger-warnings/issues)
|
236
|
+
|
237
|
+
|Number|Name|ID|File Format|Formatter|
|
238
|
+
|:---:|:---|:---|:---:|:----:|
|
239
|
+
|1|[Bandit](https://github.com/PyCQA/bandit)|bandit|json|json
|
240
|
+
|2|[Pylint](https://github.com/PyCQA/pylint)|pylint|any|parseable
|
241
|
+
|3|[RuboCop](https://github.com/rubocop-hq/rubocop)|rubocop|json, any|json, simple
|
data/lib/warnings/gem_version.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
-
require_relative 'issue'
|
1
|
+
require_relative '../report/issue'
|
2
2
|
|
3
3
|
module Warnings
|
4
|
-
# Utility class to write the markdown
|
5
|
-
module
|
6
|
-
TABLE_HEADER = 'Severity|File|Message'.freeze
|
4
|
+
# Utility class to write the markdown and inline reports.
|
5
|
+
module MessageUtil
|
6
|
+
TABLE_HEADER = '|Severity|File|Message|'.freeze
|
7
7
|
COLUMN_SEPARATOR = '|'.freeze
|
8
|
-
TABLE_SEPARATOR = "---#{COLUMN_SEPARATOR}---#{COLUMN_SEPARATOR}
|
8
|
+
TABLE_SEPARATOR = "#{COLUMN_SEPARATOR}---#{COLUMN_SEPARATOR}---#{COLUMN_SEPARATOR}---#{COLUMN_SEPARATOR}".freeze
|
9
9
|
LINE_SEPARATOR = "\n".freeze
|
10
10
|
|
11
11
|
module_function
|
@@ -15,12 +15,20 @@ module Warnings
|
|
15
15
|
# @param name [String] The name of the report to be printed.
|
16
16
|
# @param issues [Array<Issue>] List of parsed issues.
|
17
17
|
# @return [String] String in danger markdown format.
|
18
|
-
def
|
18
|
+
def markdown(name, issues)
|
19
19
|
result = header_name(name)
|
20
20
|
result << header
|
21
21
|
result << issues(issues)
|
22
22
|
end
|
23
23
|
|
24
|
+
# Create an inline comment containing all issue information.
|
25
|
+
#
|
26
|
+
# @param issue [Issue] The issue to report.
|
27
|
+
# @return String Text to add as comment.
|
28
|
+
def inline(issue)
|
29
|
+
"#{issue.severity.to_s.capitalize}\n#{meta_information(issue)}\n#{issue.message}"
|
30
|
+
end
|
31
|
+
|
24
32
|
# Create the report name string.
|
25
33
|
#
|
26
34
|
# @param report_name [String] The name of the report.
|
@@ -47,15 +55,31 @@ module Warnings
|
|
47
55
|
def issues(issues)
|
48
56
|
result = ''
|
49
57
|
issues.each do |issue|
|
58
|
+
result << COLUMN_SEPARATOR.dup
|
50
59
|
result << issue.severity.to_s.capitalize
|
51
60
|
result << COLUMN_SEPARATOR
|
52
61
|
result << "#{issue.file_name}:#{issue.line}"
|
53
62
|
result << COLUMN_SEPARATOR
|
54
|
-
result << "
|
63
|
+
result << "#{meta_information(issue)} #{issue.message}"
|
64
|
+
result << COLUMN_SEPARATOR
|
55
65
|
result << LINE_SEPARATOR
|
56
66
|
end
|
57
67
|
# rubocop:enable Metrics/AbcSize
|
58
68
|
result
|
59
69
|
end
|
70
|
+
|
71
|
+
# Combine meta information about the issue.
|
72
|
+
# Meta information are considered infos about the check itself.
|
73
|
+
# e.g. category
|
74
|
+
#
|
75
|
+
# @param issue [Issue] Issue to extract information.
|
76
|
+
# @return String combined information.
|
77
|
+
def meta_information(issue)
|
78
|
+
return unless issue.category
|
79
|
+
|
80
|
+
result = '['
|
81
|
+
result << issue.category
|
82
|
+
result << ']'
|
83
|
+
end
|
60
84
|
end
|
61
85
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Warnings
|
2
|
+
# Defines severity levels and provides helper methods.
|
3
|
+
module SeverityUtil
|
4
|
+
LOW = :low
|
5
|
+
MEDIUM = :medium
|
6
|
+
HIGH = :high
|
7
|
+
|
8
|
+
module_function
|
9
|
+
|
10
|
+
# Map a common shortened severity [R/C/W/E/F0000] to a defined severity level.
|
11
|
+
#
|
12
|
+
# @param name [String] The shortened severity without '[]'
|
13
|
+
# @return [Symbol] Mapped severity level.
|
14
|
+
def rcwef_short(name)
|
15
|
+
char = name.chars.first.downcase
|
16
|
+
case char
|
17
|
+
when 'r', 'c'
|
18
|
+
LOW
|
19
|
+
when 'w'
|
20
|
+
MEDIUM
|
21
|
+
when 'e', 'f'
|
22
|
+
HIGH
|
23
|
+
else
|
24
|
+
LOW
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Map a common full severity to a defined severity level.
|
29
|
+
#
|
30
|
+
# @param name [String] The shortened severity without '[]'
|
31
|
+
# @return [Symbol] Mapped severity level.
|
32
|
+
def rcwef_full(name)
|
33
|
+
case name.downcase
|
34
|
+
when 'refactor', 'convention'
|
35
|
+
LOW
|
36
|
+
when 'warning'
|
37
|
+
MEDIUM
|
38
|
+
when 'error', 'fatal'
|
39
|
+
HIGH
|
40
|
+
else
|
41
|
+
LOW
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -1,18 +1,13 @@
|
|
1
1
|
require_relative 'parser'
|
2
|
-
require_relative '../issue'
|
2
|
+
require_relative '../report/issue'
|
3
3
|
|
4
4
|
module Warnings
|
5
5
|
# Parser class for bandit generated json files.
|
6
6
|
class BanditParser < Parser
|
7
7
|
RESULTS_KEY = 'results'.freeze
|
8
|
-
FILE_TYPES = %i(json).freeze
|
9
8
|
NAME = 'Bandit'.freeze
|
10
9
|
ERROR_MISSING_KEY = "Missing bandit key '#{RESULTS_KEY}'.".freeze
|
11
10
|
|
12
|
-
def file_types
|
13
|
-
FILE_TYPES
|
14
|
-
end
|
15
|
-
|
16
11
|
def parse(file)
|
17
12
|
json_hash = json(file)
|
18
13
|
results_hash = json_hash[RESULTS_KEY]
|
@@ -33,8 +28,7 @@ module Warnings
|
|
33
28
|
issue.severity = to_severity(hash['issue_severity'])
|
34
29
|
issue.message = hash['issue_text']
|
35
30
|
issue.line = hash['line_number']
|
36
|
-
issue.
|
37
|
-
issue.name = hash['test_name']
|
31
|
+
issue.category = "#{hash['test_id']}-#{hash['test_name']}"
|
38
32
|
@issues << issue
|
39
33
|
end
|
40
34
|
|
@@ -5,15 +5,12 @@ module Warnings
|
|
5
5
|
# Base parser class to define common methods.
|
6
6
|
class Parser
|
7
7
|
ERROR_FILE_NOT_EXIST = 'File \'%s\' does not exist.'.freeze
|
8
|
-
|
8
|
+
ERROR_EXT_NOT_JSON = '%s is not a json file.'.freeze
|
9
|
+
EXT_JSON = 'json'.freeze
|
9
10
|
# All issues found by the parser.
|
10
11
|
#
|
11
12
|
# @return [Array<Issue>]
|
12
13
|
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
14
|
# Execute the parser.
|
18
15
|
# Read the file and create an array of issues.
|
19
16
|
#
|
@@ -30,15 +27,36 @@ module Warnings
|
|
30
27
|
|
31
28
|
protected
|
32
29
|
|
30
|
+
# Check if the file is a json file.
|
31
|
+
#
|
32
|
+
# @param file_path [String] Path to a file to be read as json.
|
33
|
+
# @return [Bool] Whether the file is a json or not.
|
34
|
+
def json?(file_path)
|
35
|
+
File.extname(file_path).delete('.').downcase.eql?(EXT_JSON)
|
36
|
+
end
|
37
|
+
|
33
38
|
# Parse a file as json content.
|
34
39
|
#
|
35
40
|
# @param file_path [String] Path to a file to be read as json.
|
36
41
|
# @return [String] Hash of json values.
|
37
42
|
def json(file_path)
|
43
|
+
raise(format(ERROR_EXT_NOT_JSON, file_path)) unless json?(file_path)
|
44
|
+
|
38
45
|
content = read_file(file_path)
|
39
46
|
JSON.parse(content)
|
40
47
|
end
|
41
48
|
|
49
|
+
# Read the file into memory and serve each line.
|
50
|
+
#
|
51
|
+
# @param file_path [String] Path to a file to be read.
|
52
|
+
# @raise If file does not exist.
|
53
|
+
# @return [String] Array of line contents.
|
54
|
+
def read_lines(file_path)
|
55
|
+
raise(format(ERROR_FILE_NOT_EXIST, file_path)) unless File.exist?(file_path)
|
56
|
+
|
57
|
+
File.readlines(file_path, chomp: true)
|
58
|
+
end
|
59
|
+
|
42
60
|
private
|
43
61
|
|
44
62
|
# Evaluate and read the file into memory.
|
@@ -47,19 +65,9 @@ module Warnings
|
|
47
65
|
# @raise If file does not exist or ist empty.
|
48
66
|
# @return [String] File content.
|
49
67
|
def read_file(file_path)
|
50
|
-
check_extname(file_path)
|
51
68
|
raise(format(ERROR_FILE_NOT_EXIST, file_path)) unless File.exist?(file_path)
|
52
69
|
|
53
70
|
File.read(file_path)
|
54
71
|
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
72
|
end
|
65
73
|
end
|
@@ -1,11 +1,15 @@
|
|
1
1
|
require_relative 'bandit_parser'
|
2
|
+
require_relative 'pylint_parser'
|
3
|
+
require_relative 'rubocop_parser'
|
2
4
|
|
3
5
|
module Warnings
|
4
6
|
# Factory class for supported parsers.
|
5
7
|
class ParserFactory
|
6
8
|
ERROR_NOT_SUPPORTED = 'Parser \'%s\' not supported.'.freeze
|
7
9
|
AVAILABLE_PARSERS = {
|
8
|
-
bandit: BanditParser
|
10
|
+
bandit: BanditParser,
|
11
|
+
pylint: PylintParser,
|
12
|
+
rubocop: RubocopParser
|
9
13
|
}.freeze
|
10
14
|
|
11
15
|
# Create a new parser implementation.
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative 'parser'
|
2
|
+
require_relative '../report/issue'
|
3
|
+
require_relative '../helper/severity_util'
|
4
|
+
|
5
|
+
module Warnings
|
6
|
+
# Parser class for pylint formatted files.
|
7
|
+
class PylintParser < Parser
|
8
|
+
NAME = 'Pylint'.freeze
|
9
|
+
ISSUE_PATTERN = /(.*):(\d+):\s*\[(\w\d+)\]\s*(.*)/.freeze
|
10
|
+
|
11
|
+
def parse(file)
|
12
|
+
read_lines(file).each do |line|
|
13
|
+
match = line.scan(ISSUE_PATTERN)
|
14
|
+
store_issue(match[0]) unless match.empty?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def name
|
19
|
+
NAME
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# Match the regex result and store it as issue implementation.
|
25
|
+
#
|
26
|
+
# @param match [Array<String>] The regex matches for a single issue.
|
27
|
+
# @return Void
|
28
|
+
def store_issue(match)
|
29
|
+
issue = Issue.new
|
30
|
+
issue.file_name = match[0]
|
31
|
+
issue.line = match[1]
|
32
|
+
issue.category = match[2]
|
33
|
+
issue.severity = SeverityUtil.rcwef_short(issue.category)
|
34
|
+
issue.message = match[3]
|
35
|
+
@issues << issue
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require_relative 'parser'
|
2
|
+
require_relative '../report/issue'
|
3
|
+
require_relative '../helper/severity_util'
|
4
|
+
|
5
|
+
module Warnings
|
6
|
+
# Parser class for rubocop reports.
|
7
|
+
class RubocopParser < Parser
|
8
|
+
NAME = 'RuboCop'.freeze
|
9
|
+
FILE_PATTERN = /==\s(.*)\s==/.freeze
|
10
|
+
ISSUE_PATTERN = /(\w):\s*(\d+):\s*\d+:\s(.*)/.freeze
|
11
|
+
|
12
|
+
def parse(file)
|
13
|
+
if json?(file)
|
14
|
+
extract_json_issues(file)
|
15
|
+
else
|
16
|
+
extract_pattern_issues(file)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def name
|
21
|
+
NAME
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def extract_json_issues(file)
|
27
|
+
json_hash = json(file)
|
28
|
+
files = json_hash['files']
|
29
|
+
files.each(&method(:store_json_issue))
|
30
|
+
end
|
31
|
+
|
32
|
+
def extract_pattern_issues(file)
|
33
|
+
last_file = nil
|
34
|
+
read_lines(file).each do |line|
|
35
|
+
file_match = line.scan(FILE_PATTERN)
|
36
|
+
unless file_match.empty?
|
37
|
+
last_file = file_match[0][0]
|
38
|
+
next
|
39
|
+
end
|
40
|
+
issue_match = line.scan(ISSUE_PATTERN)
|
41
|
+
next if issue_match.empty?
|
42
|
+
|
43
|
+
issue_content = issue_match[0]
|
44
|
+
issue = {
|
45
|
+
severity: issue_content[0],
|
46
|
+
line: issue_content[1],
|
47
|
+
message: issue_content[2]
|
48
|
+
}
|
49
|
+
store_simple_issue(last_file, issue)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def store_json_issue(file_hash)
|
54
|
+
offenses = file_hash['offenses']
|
55
|
+
return if offenses.empty?
|
56
|
+
|
57
|
+
offenses.each do |offense|
|
58
|
+
issue = Issue.new
|
59
|
+
issue.file_name = file_hash['path']
|
60
|
+
issue.line = offense['location']['line']
|
61
|
+
issue.category = offense['cop_name']
|
62
|
+
issue.severity = SeverityUtil.rcwef_full(offense['severity'])
|
63
|
+
issue.message = offense['message']
|
64
|
+
@issues << issue
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def store_simple_issue(file, issue_hash)
|
69
|
+
issue = Issue.new
|
70
|
+
issue.file_name = file
|
71
|
+
issue.line = issue_hash[:line].to_i
|
72
|
+
issue.severity = SeverityUtil.rcwef_full(issue_hash[:severity])
|
73
|
+
issue.message = issue_hash[:message]
|
74
|
+
@issues << issue
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|