goodcheck 2.5.2 → 3.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +49 -3
- data/LICENSE +1 -1
- data/README.md +8 -445
- data/lib/goodcheck.rb +9 -4
- data/lib/goodcheck/analyzer.rb +13 -9
- data/lib/goodcheck/buffer.rb +9 -21
- data/lib/goodcheck/cli.rb +79 -57
- data/lib/goodcheck/commands/check.rb +41 -27
- data/lib/goodcheck/commands/config_loading.rb +28 -5
- data/lib/goodcheck/commands/init.rb +4 -2
- data/lib/goodcheck/commands/pattern.rb +2 -1
- data/lib/goodcheck/commands/test.rb +38 -30
- data/lib/goodcheck/config.rb +68 -1
- data/lib/goodcheck/config_loader.rb +41 -31
- data/lib/goodcheck/error.rb +3 -0
- data/lib/goodcheck/exit_status.rb +8 -0
- data/lib/goodcheck/glob.rb +14 -3
- data/lib/goodcheck/import_loader.rb +96 -26
- data/lib/goodcheck/issue.rb +3 -3
- data/lib/goodcheck/location.rb +28 -0
- data/lib/goodcheck/logger.rb +4 -4
- data/lib/goodcheck/reporters/json.rb +6 -1
- data/lib/goodcheck/reporters/text.rb +44 -11
- data/lib/goodcheck/rule.rb +3 -1
- data/lib/goodcheck/unarchiver.rb +40 -0
- data/lib/goodcheck/version.rb +1 -1
- metadata +44 -82
- data/.github/dependabot.yml +0 -18
- data/.github/workflows/release.yml +0 -16
- data/.github/workflows/test.yml +0 -46
- data/.gitignore +0 -13
- data/.rubocop.yml +0 -5
- data/Dockerfile +0 -13
- data/Gemfile +0 -6
- data/Rakefile +0 -75
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/cheatsheet.pdf +0 -0
- data/docusaurus/.dockerignore +0 -2
- data/docusaurus/.gitignore +0 -12
- data/docusaurus/Dockerfile +0 -10
- data/docusaurus/docker-compose.yml +0 -18
- data/docusaurus/docs/commands.md +0 -69
- data/docusaurus/docs/configuration.md +0 -300
- data/docusaurus/docs/development.md +0 -15
- data/docusaurus/docs/getstarted.md +0 -46
- data/docusaurus/docs/rules.md +0 -79
- data/docusaurus/website/README.md +0 -193
- data/docusaurus/website/core/Footer.js +0 -100
- data/docusaurus/website/package.json +0 -14
- data/docusaurus/website/pages/en/index.js +0 -207
- data/docusaurus/website/pages/en/versions.js +0 -118
- data/docusaurus/website/sidebars.json +0 -11
- data/docusaurus/website/siteConfig.js +0 -171
- data/docusaurus/website/static/css/code-block-buttons.css +0 -39
- data/docusaurus/website/static/css/custom.css +0 -245
- data/docusaurus/website/static/img/favicon.ico +0 -0
- data/docusaurus/website/static/js/code-block-buttons.js +0 -47
- data/docusaurus/website/versioned_docs/version-1.0.0/commands.md +0 -70
- data/docusaurus/website/versioned_docs/version-1.0.0/configuration.md +0 -296
- data/docusaurus/website/versioned_docs/version-1.0.0/development.md +0 -16
- data/docusaurus/website/versioned_docs/version-1.0.0/getstarted.md +0 -47
- data/docusaurus/website/versioned_docs/version-1.0.0/rules.md +0 -81
- data/docusaurus/website/versioned_docs/version-1.0.2/rules.md +0 -79
- data/docusaurus/website/versioned_docs/version-2.4.0/configuration.md +0 -301
- data/docusaurus/website/versioned_docs/version-2.4.3/rules.md +0 -80
- data/docusaurus/website/versioned_sidebars/version-1.0.0-sidebars.json +0 -11
- data/docusaurus/website/versioned_sidebars/version-1.0.2-sidebars.json +0 -11
- data/docusaurus/website/versioned_sidebars/version-2.4.0-sidebars.json +0 -11
- data/docusaurus/website/versions.json +0 -12
- data/docusaurus/website/yarn.lock +0 -6604
- data/goodcheck.gemspec +0 -35
- data/goodcheck.yml +0 -10
- data/logo/GoodCheck Horizontal.pdf +0 -899
- data/logo/GoodCheck Horizontal.png +0 -0
- data/logo/GoodCheck Horizontal.svg +0 -55
- data/logo/GoodCheck logo.png +0 -0
- data/logo/GoodCheck vertical.png +0 -0
- data/sample.yml +0 -57
data/lib/goodcheck/glob.rb
CHANGED
@@ -1,21 +1,32 @@
|
|
1
1
|
module Goodcheck
|
2
2
|
class Glob
|
3
|
+
FNM_FLAGS = File::FNM_PATHNAME | File::FNM_EXTGLOB | File::FNM_DOTMATCH
|
4
|
+
|
3
5
|
attr_reader :pattern
|
4
6
|
attr_reader :encoding
|
7
|
+
attr_reader :exclude
|
5
8
|
|
6
|
-
def initialize(pattern:, encoding:)
|
9
|
+
def initialize(pattern:, encoding:, exclude:)
|
7
10
|
@pattern = pattern
|
8
11
|
@encoding = encoding
|
12
|
+
@exclude = exclude
|
9
13
|
end
|
10
14
|
|
11
15
|
def test(path)
|
12
|
-
path.fnmatch?(pattern,
|
16
|
+
path.fnmatch?(pattern, FNM_FLAGS) && !excluded?(path)
|
13
17
|
end
|
14
18
|
|
15
19
|
def ==(other)
|
16
20
|
other.is_a?(Glob) &&
|
17
21
|
other.pattern == pattern &&
|
18
|
-
other.encoding == encoding
|
22
|
+
other.encoding == encoding &&
|
23
|
+
other.exclude == exclude
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def excluded?(path)
|
29
|
+
Array(exclude).any? { |exc| path.fnmatch?(exc, FNM_FLAGS) }
|
19
30
|
end
|
20
31
|
end
|
21
32
|
end
|
@@ -1,13 +1,36 @@
|
|
1
1
|
module Goodcheck
|
2
2
|
class ImportLoader
|
3
|
-
class UnexpectedSchemaError <
|
3
|
+
class UnexpectedSchemaError < Error
|
4
4
|
attr_reader :uri
|
5
5
|
|
6
6
|
def initialize(uri)
|
7
|
+
super("Unexpected URI schema: #{uri.scheme}")
|
7
8
|
@uri = uri
|
8
9
|
end
|
9
10
|
end
|
10
11
|
|
12
|
+
class FileNotFound < Error
|
13
|
+
attr_reader :path
|
14
|
+
|
15
|
+
def initialize(path)
|
16
|
+
super("No such a file: #{path}")
|
17
|
+
@path = path
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class HTTPGetError < Error
|
22
|
+
attr_reader :response
|
23
|
+
|
24
|
+
def initialize(res)
|
25
|
+
super("HTTP GET #{res.uri} => #{res.code} #{res.message}")
|
26
|
+
@response = res
|
27
|
+
end
|
28
|
+
|
29
|
+
def error_response?
|
30
|
+
response.is_a?(Net::HTTPClientError) || response.is_a?(Net::HTTPServerError)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
11
34
|
attr_reader :cache_path
|
12
35
|
attr_reader :expires_in
|
13
36
|
attr_reader :force_download
|
@@ -21,23 +44,39 @@ module Goodcheck
|
|
21
44
|
end
|
22
45
|
|
23
46
|
def load(name, &block)
|
24
|
-
uri =
|
47
|
+
uri = begin
|
48
|
+
URI.parse(name)
|
49
|
+
rescue URI::InvalidURIError
|
50
|
+
nil
|
51
|
+
end
|
25
52
|
|
26
|
-
case uri
|
27
|
-
when nil
|
28
|
-
load_file
|
53
|
+
case uri&.scheme
|
54
|
+
when nil
|
55
|
+
load_file name, &block
|
56
|
+
when "file"
|
57
|
+
load_file uri.path, &block
|
29
58
|
when "http", "https"
|
30
59
|
load_http uri, &block
|
31
60
|
else
|
32
|
-
raise UnexpectedSchemaError.new(
|
61
|
+
raise UnexpectedSchemaError.new(uri)
|
33
62
|
end
|
34
63
|
end
|
35
64
|
|
36
|
-
def load_file(
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
65
|
+
def load_file(path, &block)
|
66
|
+
files = Pathname.glob(File.join(config_path.parent.to_path, path), File::FNM_DOTMATCH | File::FNM_EXTGLOB).sort
|
67
|
+
if files.empty?
|
68
|
+
raise FileNotFound.new(path)
|
69
|
+
else
|
70
|
+
files.each do |file|
|
71
|
+
Goodcheck.logger.info "Reading file: #{file}"
|
72
|
+
if unarchiver.tar_gz?(file)
|
73
|
+
unarchiver.tar_gz(file.read) do |content, filename|
|
74
|
+
block.call(content, filename)
|
75
|
+
end
|
76
|
+
else
|
77
|
+
block.call(file.read, file.to_path)
|
78
|
+
end
|
79
|
+
end
|
41
80
|
end
|
42
81
|
end
|
43
82
|
|
@@ -45,7 +84,7 @@ module Goodcheck
|
|
45
84
|
Digest::SHA2.hexdigest(uri.to_s)
|
46
85
|
end
|
47
86
|
|
48
|
-
def load_http(uri)
|
87
|
+
def load_http(uri, &block)
|
49
88
|
hash = cache_name(uri)
|
50
89
|
path = cache_path + hash
|
51
90
|
|
@@ -71,13 +110,19 @@ module Goodcheck
|
|
71
110
|
if download
|
72
111
|
path.rmtree if path.exist?
|
73
112
|
Goodcheck.logger.info "Downloading content..."
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
113
|
+
if unarchiver.tar_gz?(uri.path)
|
114
|
+
unarchiver.tar_gz(http_get(uri)) do |content, filename|
|
115
|
+
block.call(content, filename)
|
116
|
+
write_cache "#{uri}/#{filename}", content
|
117
|
+
end
|
118
|
+
else
|
119
|
+
content = http_get(uri)
|
120
|
+
block.call(content, uri.path)
|
121
|
+
write_cache uri, content
|
122
|
+
end
|
78
123
|
else
|
79
124
|
Goodcheck.logger.info "Reading content from cache..."
|
80
|
-
|
125
|
+
block.call(path.read, path.to_path)
|
81
126
|
end
|
82
127
|
end
|
83
128
|
|
@@ -90,16 +135,41 @@ module Goodcheck
|
|
90
135
|
def http_get(uri, limit = 10)
|
91
136
|
raise ArgumentError, "Too many HTTP redirects" if limit == 0
|
92
137
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
res.
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
138
|
+
max_retry_count = 2
|
139
|
+
retry_count = 0
|
140
|
+
begin
|
141
|
+
res = Net::HTTP.get_response URI(uri)
|
142
|
+
case res
|
143
|
+
when Net::HTTPSuccess
|
144
|
+
res.body
|
145
|
+
when Net::HTTPRedirection
|
146
|
+
location = res['Location']
|
147
|
+
http_get location, limit - 1
|
148
|
+
else
|
149
|
+
raise HTTPGetError.new(res)
|
150
|
+
end
|
151
|
+
rescue HTTPGetError => exn
|
152
|
+
if retry_count < max_retry_count && exn.error_response?
|
153
|
+
retry_count += 1
|
154
|
+
Goodcheck.logger.info "#{retry_count} retry HTTP GET #{exn.response.uri} due to '#{exn.response.code} #{exn.response.message}'..."
|
155
|
+
sleep 1
|
156
|
+
retry
|
157
|
+
else
|
158
|
+
raise
|
159
|
+
end
|
102
160
|
end
|
103
161
|
end
|
162
|
+
|
163
|
+
private
|
164
|
+
|
165
|
+
def unarchiver
|
166
|
+
@unarchiver ||=
|
167
|
+
begin
|
168
|
+
filter = ->(filename) {
|
169
|
+
%w[.yml .yaml].include?(File.extname(filename).downcase) && File.basename(filename) != DEFAULT_CONFIG_FILE
|
170
|
+
}
|
171
|
+
Unarchiver.new(file_filter: filter)
|
172
|
+
end
|
173
|
+
end
|
104
174
|
end
|
105
175
|
end
|
data/lib/goodcheck/issue.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
module Goodcheck
|
2
2
|
class Issue
|
3
3
|
attr_reader :buffer
|
4
|
-
attr_reader :range
|
5
4
|
attr_reader :rule
|
6
5
|
attr_reader :text
|
6
|
+
attr_reader :range
|
7
7
|
|
8
|
-
def initialize(buffer:,
|
8
|
+
def initialize(buffer:, rule:, text: nil, text_begin_pos: nil)
|
9
9
|
@buffer = buffer
|
10
|
-
@range = range
|
11
10
|
@rule = rule
|
12
11
|
@text = text
|
12
|
+
@range = text ? text_begin_pos..(text_begin_pos + text.bytesize - 1) : nil
|
13
13
|
@location = nil
|
14
14
|
end
|
15
15
|
|
data/lib/goodcheck/location.rb
CHANGED
@@ -1,4 +1,18 @@
|
|
1
1
|
module Goodcheck
|
2
|
+
# In the example below, each attribute is:
|
3
|
+
#
|
4
|
+
# - start_line: 2
|
5
|
+
# - start_column: 3
|
6
|
+
# - end_line: 2
|
7
|
+
# - end_column: 9
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
#
|
11
|
+
# 1 |
|
12
|
+
# 2 | A matched text
|
13
|
+
# 3 | ^~~~~~~
|
14
|
+
# 3456789
|
15
|
+
#
|
2
16
|
class Location
|
3
17
|
attr_reader :start_line
|
4
18
|
attr_reader :start_column
|
@@ -12,6 +26,14 @@ module Goodcheck
|
|
12
26
|
@end_column = end_column
|
13
27
|
end
|
14
28
|
|
29
|
+
def one_line?
|
30
|
+
start_line == end_line
|
31
|
+
end
|
32
|
+
|
33
|
+
def column_size
|
34
|
+
end_column - start_column + 1
|
35
|
+
end
|
36
|
+
|
15
37
|
def ==(other)
|
16
38
|
other.is_a?(Location) &&
|
17
39
|
other.start_line == start_line &&
|
@@ -19,5 +41,11 @@ module Goodcheck
|
|
19
41
|
other.end_line == end_line &&
|
20
42
|
other.end_column == end_column
|
21
43
|
end
|
44
|
+
|
45
|
+
alias eql? ==
|
46
|
+
|
47
|
+
def hash
|
48
|
+
self.class.hash ^ start_line.hash ^ start_column.hash ^ end_line.hash ^ end_column.hash
|
49
|
+
end
|
22
50
|
end
|
23
51
|
end
|
data/lib/goodcheck/logger.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
module Goodcheck
|
2
2
|
def self.logger
|
3
|
-
@logger ||=
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
@logger ||= Logger.new(
|
4
|
+
STDERR, level: Logger::ERROR,
|
5
|
+
formatter: ->(severity, time, progname, msg) { "[#{severity}] #{msg}\n" }
|
6
|
+
)
|
7
7
|
end
|
8
8
|
end
|
@@ -26,7 +26,8 @@ module Goodcheck
|
|
26
26
|
end_column: location.end_column
|
27
27
|
},
|
28
28
|
message: issue.rule.message,
|
29
|
-
justifications: issue.rule.justifications
|
29
|
+
justifications: issue.rule.justifications,
|
30
|
+
severity: issue.rule.severity
|
30
31
|
}
|
31
32
|
end
|
32
33
|
stdout.puts ::JSON.dump(json)
|
@@ -44,6 +45,10 @@ module Goodcheck
|
|
44
45
|
def issue(issue)
|
45
46
|
issues << issue
|
46
47
|
end
|
48
|
+
|
49
|
+
def summary
|
50
|
+
# noop
|
51
|
+
end
|
47
52
|
end
|
48
53
|
end
|
49
54
|
end
|
@@ -5,6 +5,8 @@ module Goodcheck
|
|
5
5
|
|
6
6
|
def initialize(stdout:)
|
7
7
|
@stdout = stdout
|
8
|
+
@file_count = 0
|
9
|
+
@issue_count = 0
|
8
10
|
end
|
9
11
|
|
10
12
|
def analysis
|
@@ -12,6 +14,7 @@ module Goodcheck
|
|
12
14
|
end
|
13
15
|
|
14
16
|
def file(path)
|
17
|
+
@file_count += 1
|
15
18
|
yield
|
16
19
|
end
|
17
20
|
|
@@ -20,21 +23,51 @@ module Goodcheck
|
|
20
23
|
end
|
21
24
|
|
22
25
|
def issue(issue)
|
26
|
+
@issue_count += 1
|
27
|
+
|
28
|
+
message = issue.rule.message.lines.first.chomp
|
29
|
+
|
23
30
|
if issue.location
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
31
|
+
start_line = issue.location.start_line
|
32
|
+
start_column = issue.location.start_column
|
33
|
+
start_column_index = start_column - 1
|
34
|
+
line = issue.buffer.line(start_line)
|
35
|
+
column_size = if issue.location.one_line?
|
36
|
+
issue.location.column_size
|
37
|
+
else
|
38
|
+
line.bytesize - start_column
|
39
|
+
end
|
40
|
+
rule = Rainbow("(#{issue.rule.id})").darkgray
|
41
|
+
severity = issue.rule.severity ? Rainbow("[#{issue.rule.severity}]").magenta : ""
|
42
|
+
stdout.puts "#{Rainbow(issue.path).cyan}:#{start_line}:#{start_column}: #{message} #{rule} #{severity}".strip
|
43
|
+
stdout.puts line.chomp
|
44
|
+
stdout.puts (" " * start_column_index) + Rainbow("^" + "~" * (column_size - 1)).yellow
|
32
45
|
else
|
33
|
-
|
34
|
-
line = line ? Rainbow(line).red : '-'
|
35
|
-
stdout.puts "#{issue.path}:-:#{line}:\t#{issue.rule.message.lines.first.chomp}"
|
46
|
+
stdout.puts "#{Rainbow(issue.path).cyan}:-:-: #{message}"
|
36
47
|
end
|
37
48
|
end
|
49
|
+
|
50
|
+
def summary
|
51
|
+
files = case @file_count
|
52
|
+
when 0
|
53
|
+
"no files"
|
54
|
+
when 1
|
55
|
+
"1 file"
|
56
|
+
else
|
57
|
+
"#{@file_count} files"
|
58
|
+
end
|
59
|
+
issues = case @issue_count
|
60
|
+
when 0
|
61
|
+
Rainbow("no issues").green
|
62
|
+
when 1
|
63
|
+
Rainbow("1 issue").red
|
64
|
+
else
|
65
|
+
Rainbow("#{@issue_count} issues").red
|
66
|
+
end
|
67
|
+
|
68
|
+
stdout.puts ""
|
69
|
+
stdout.puts "#{files} inspected, #{issues} detected"
|
70
|
+
end
|
38
71
|
end
|
39
72
|
end
|
40
73
|
end
|
data/lib/goodcheck/rule.rb
CHANGED
@@ -4,12 +4,14 @@ module Goodcheck
|
|
4
4
|
attr_reader :triggers
|
5
5
|
attr_reader :message
|
6
6
|
attr_reader :justifications
|
7
|
+
attr_reader :severity
|
7
8
|
|
8
|
-
def initialize(id:, triggers:, message:, justifications:)
|
9
|
+
def initialize(id:, triggers:, message:, justifications:, severity: nil)
|
9
10
|
@id = id
|
10
11
|
@triggers = triggers
|
11
12
|
@message = message
|
12
13
|
@justifications = justifications
|
14
|
+
@severity = severity
|
13
15
|
end
|
14
16
|
end
|
15
17
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Goodcheck
|
2
|
+
class Unarchiver
|
3
|
+
attr_reader :file_filter
|
4
|
+
|
5
|
+
def initialize(file_filter: ->(_filename) { true })
|
6
|
+
@file_filter = file_filter
|
7
|
+
end
|
8
|
+
|
9
|
+
def tar_gz?(filename)
|
10
|
+
name = filename.to_s.downcase
|
11
|
+
ext = ".tar.gz"
|
12
|
+
name.end_with?(ext) && name != ext
|
13
|
+
end
|
14
|
+
|
15
|
+
def tar_gz(content)
|
16
|
+
require "rubygems/package"
|
17
|
+
|
18
|
+
Gem::Package::TarReader.new(StringIO.new(gz(content))) do |tar_reader|
|
19
|
+
tar_reader.each do |file|
|
20
|
+
if file.file? && file_filter.call(file.full_name)
|
21
|
+
yield file.read, file.full_name
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def gz(content)
|
30
|
+
require "zlib"
|
31
|
+
|
32
|
+
io = Zlib::GzipReader.new(StringIO.new(content))
|
33
|
+
begin
|
34
|
+
io.read
|
35
|
+
ensure
|
36
|
+
io.close
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|