danger-warnings 0.0.1 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1700e0b2ada290034b7845b68d581e81c73997f235d038f46903ec00526a3074
4
- data.tar.gz: 482a5cd7478ce44da357b53f9932d06f64eac91adfab014ba205942434319dd2
3
+ metadata.gz: 90dcfaf900785427cae7f5996651fd006b6fc4fee2f67eb0674e330ff171ccab
4
+ data.tar.gz: f9c6800309d84929707bde221549d6ad3da3b84ddf51171cbad5f314132feccd
5
5
  SHA512:
6
- metadata.gz: 682d89cec48fff79bb526b88fec0e0c628db4a88b7f52dcc39a2603913d4e09b1798ffbf33beb9cf31697d56ce8a94ffd73159bdbc159ae313f44698f3f34ccb
7
- data.tar.gz: cddb903ba7cfd7b8532360adb1c598f49eaa64d4c5a770c0c83a73088c16f89425a166bd200e4e8a153832829f8d487d2400eec64d20b47711874ce05e8ab388
6
+ metadata.gz: 184a35b56e0e5c80ae92f169df055033e26d88f7d09db9310987933582fab388c9579939b57ff886d789bc486d941b166b3ae17d6df8411fcfd4695f876c17a6
7
+ data.tar.gz: e942fad54a71a5f1bf3cdb31675228db17744ae7abf3777b80c4160a4739489250b19611422c7c469c69156d5113c430ac2300bc5f26545d1669cc178ae67d43
@@ -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
@@ -0,0 +1,5 @@
1
+ warnings.report(
2
+ parser: :rubocop,
3
+ file: 'spec/assets/rubocop.txt',
4
+ filter: false
5
+ )
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 lint [tools](#parsers). <br>
83
- The purpose is a simple to use plugin regardless of the linter tool used to create the issues.
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
- |Number|Name|ID|File Format|
212
- |:---:|---|---|---|
213
- |1|[bandit](https://github.com/PyCQA/bandit)|bandit|json|
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
@@ -1,3 +1,3 @@
1
1
  module Warnings
2
- VERSION = '0.0.1'.freeze
2
+ VERSION = '0.1.0'.freeze
3
3
  end
@@ -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 report.
5
- module MarkdownUtil
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}---".freeze
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 generate(name, issues)
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 << "[#{issue.id}-#{issue.name}] #{issue.message}"
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.id = hash['test_id']
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
- ERROR_EXT_NOT_SUPPORTED = 'File extension \'%s\' is not supported for parser %s.'.freeze
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