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 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