goodcheck 2.5.2 → 3.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.
- 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
|