goodcheck 2.5.0 → 2.7.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 +4 -4
- data/CHANGELOG.md +31 -2
- data/README.md +4 -447
- data/lib/goodcheck.rb +5 -5
- data/lib/goodcheck/analyzer.rb +13 -9
- data/lib/goodcheck/buffer.rb +11 -16
- data/lib/goodcheck/cli.rb +80 -56
- data/lib/goodcheck/commands/check.rb +42 -26
- data/lib/goodcheck/commands/config_loading.rb +20 -2
- data/lib/goodcheck/commands/init.rb +4 -2
- data/lib/goodcheck/commands/pattern.rb +2 -1
- data/lib/goodcheck/commands/test.rb +14 -11
- data/lib/goodcheck/config_loader.rb +17 -20
- data/lib/goodcheck/error.rb +3 -0
- data/lib/goodcheck/exit_status.rb +6 -0
- data/lib/goodcheck/glob.rb +14 -3
- data/lib/goodcheck/import_loader.rb +42 -10
- 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 +4 -0
- data/lib/goodcheck/reporters/text.rb +42 -11
- data/lib/goodcheck/version.rb +1 -1
- metadata +29 -92
- data/.github/workflows/test.yml +0 -37
- data/.gitignore +0 -13
- data/.rubocop.yml +0 -5
- data/Dockerfile +0 -13
- data/Gemfile +0 -6
- data/Rakefile +0 -70
- data/benchmark/gc.c +0 -12221
- 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 -10
- data/docusaurus/website/yarn.lock +0 -6806
- data/goodcheck.gemspec +0 -36
- 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
@@ -1,11 +1,27 @@
|
|
1
1
|
module Goodcheck
|
2
2
|
module Commands
|
3
3
|
module ConfigLoading
|
4
|
+
class ConfigFileNotFound < Error
|
5
|
+
attr_reader :path
|
6
|
+
|
7
|
+
def initialize(path:)
|
8
|
+
super(path.to_s)
|
9
|
+
@path = path
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
4
13
|
attr_reader :config
|
5
14
|
|
6
15
|
def load_config!(force_download:, cache_path:)
|
16
|
+
config_content =
|
17
|
+
begin
|
18
|
+
config_path.read
|
19
|
+
rescue Errno::ENOENT
|
20
|
+
raise ConfigFileNotFound.new(path: config_path)
|
21
|
+
end
|
22
|
+
|
7
23
|
import_loader = ImportLoader.new(cache_path: cache_path, force_download: force_download, config_path: config_path)
|
8
|
-
content = JSON.parse(JSON.dump(YAML.
|
24
|
+
content = JSON.parse(JSON.dump(YAML.safe_load(config_content, filename: config_path.to_s)), symbolize_names: true)
|
9
25
|
loader = ConfigLoader.new(path: config_path, content: content, stderr: stderr, import_loader: import_loader)
|
10
26
|
@config = loader.load
|
11
27
|
end
|
@@ -13,7 +29,9 @@ module Goodcheck
|
|
13
29
|
def handle_config_errors(stderr)
|
14
30
|
begin
|
15
31
|
yield
|
16
|
-
|
32
|
+
rescue ConfigFileNotFound => exn
|
33
|
+
stderr.puts "Configuration file not found: #{exn.path}"
|
34
|
+
1
|
17
35
|
rescue Psych::Exception => exn
|
18
36
|
stderr.puts "Unexpected error happens while loading YAML file: #{exn.inspect}"
|
19
37
|
exn.backtrace.each do |trace_loc|
|
@@ -58,6 +58,8 @@ rules:
|
|
58
58
|
# - vendor
|
59
59
|
EOC
|
60
60
|
|
61
|
+
include ExitStatus
|
62
|
+
|
61
63
|
attr_reader :stdout
|
62
64
|
attr_reader :stderr
|
63
65
|
attr_reader :path
|
@@ -73,7 +75,7 @@ rules:
|
|
73
75
|
def run
|
74
76
|
if path.file? && !force
|
75
77
|
stderr.puts "#{path} already exists. Try --force option to overwrite the file."
|
76
|
-
return
|
78
|
+
return EXIT_ERROR
|
77
79
|
end
|
78
80
|
|
79
81
|
path.open("w") do |io|
|
@@ -82,7 +84,7 @@ rules:
|
|
82
84
|
|
83
85
|
stdout.puts "Wrote #{path}. ✍️"
|
84
86
|
|
85
|
-
|
87
|
+
EXIT_SUCCESS
|
86
88
|
end
|
87
89
|
end
|
88
90
|
end
|
@@ -9,6 +9,7 @@ module Goodcheck
|
|
9
9
|
|
10
10
|
include ConfigLoading
|
11
11
|
include HomePath
|
12
|
+
include ExitStatus
|
12
13
|
|
13
14
|
def initialize(stdout:, stderr:, path:, ids:, home_path:)
|
14
15
|
@stdout = stdout
|
@@ -34,7 +35,7 @@ module Goodcheck
|
|
34
35
|
end
|
35
36
|
end
|
36
37
|
|
37
|
-
|
38
|
+
EXIT_SUCCESS
|
38
39
|
end
|
39
40
|
end
|
40
41
|
end
|
@@ -3,6 +3,7 @@ module Goodcheck
|
|
3
3
|
class Test
|
4
4
|
include ConfigLoading
|
5
5
|
include HomePath
|
6
|
+
include ExitStatus
|
6
7
|
|
7
8
|
attr_reader :stdout
|
8
9
|
attr_reader :stderr
|
@@ -24,13 +25,13 @@ module Goodcheck
|
|
24
25
|
|
25
26
|
if config.rules.empty?
|
26
27
|
stdout.puts "No rules."
|
27
|
-
return
|
28
|
+
return EXIT_SUCCESS
|
28
29
|
end
|
29
30
|
|
30
|
-
validate_rule_uniqueness or return
|
31
|
-
validate_rules or return
|
31
|
+
validate_rule_uniqueness or return EXIT_ERROR
|
32
|
+
validate_rules or return EXIT_ERROR
|
32
33
|
|
33
|
-
|
34
|
+
EXIT_SUCCESS
|
34
35
|
end
|
35
36
|
end
|
36
37
|
|
@@ -50,7 +51,8 @@ module Goodcheck
|
|
50
51
|
true
|
51
52
|
else
|
52
53
|
count = duplicated_ids.size
|
53
|
-
|
54
|
+
duplication = count == 1 ? 'duplication' : 'duplications'
|
55
|
+
stdout.puts(Rainbow(" Found #{count} #{duplication}.😞").red)
|
54
56
|
duplicated_ids.each do |id|
|
55
57
|
stdout.puts " #{id}"
|
56
58
|
end
|
@@ -75,7 +77,7 @@ module Goodcheck
|
|
75
77
|
if trigger.by_pattern?
|
76
78
|
stdout.puts " Testing pattern..."
|
77
79
|
else
|
78
|
-
stdout.puts "
|
80
|
+
stdout.puts " #{index + 1}. Testing trigger..."
|
79
81
|
end
|
80
82
|
|
81
83
|
pass_errors = trigger.passes.each.with_index.select do |pass, _|
|
@@ -91,7 +93,7 @@ module Goodcheck
|
|
91
93
|
rule_ok = false
|
92
94
|
|
93
95
|
pass_errors.each do |_, index|
|
94
|
-
stdout.puts " #{
|
96
|
+
stdout.puts " #{index + 1}. #{Rainbow('pass').green} example matched.😱"
|
95
97
|
failed_rule_ids << rule.id
|
96
98
|
end
|
97
99
|
end
|
@@ -101,7 +103,7 @@ module Goodcheck
|
|
101
103
|
rule_ok = false
|
102
104
|
|
103
105
|
fail_errors.each do |_, index|
|
104
|
-
stdout.puts " #{
|
106
|
+
stdout.puts " #{index + 1}. #{Rainbow('fail').red} example didn't match.😱"
|
105
107
|
failed_rule_ids << rule.id
|
106
108
|
end
|
107
109
|
end
|
@@ -131,10 +133,11 @@ module Goodcheck
|
|
131
133
|
end
|
132
134
|
|
133
135
|
rule_count = success_count + failure_count
|
136
|
+
rule = rule_count == 1 ? "1 rule" : "#{rule_count} rules"
|
137
|
+
success = Rainbow(success_count == 1 ? "1 success" : "#{success_count} successes").green
|
138
|
+
failure = Rainbow(failure_count == 1 ? "1 failure" : "#{failure_count} failures").red
|
134
139
|
stdout.puts ""
|
135
|
-
stdout.puts
|
136
|
-
Rainbow("#{success_count} #{'success'.pluralize(success_count)}").green,
|
137
|
-
Rainbow("#{failure_count} #{'failure'.pluralize(failure_count)}").red].join(", ")
|
140
|
+
stdout.puts "Tested #{rule}, #{success}, #{failure}"
|
138
141
|
|
139
142
|
test_pass
|
140
143
|
end
|
@@ -2,7 +2,7 @@ module Goodcheck
|
|
2
2
|
class ConfigLoader
|
3
3
|
include ArrayHelper
|
4
4
|
|
5
|
-
class InvalidPattern <
|
5
|
+
class InvalidPattern < Error; end
|
6
6
|
|
7
7
|
Schema = StrongJSON.new do
|
8
8
|
def self.array_or(type)
|
@@ -22,7 +22,8 @@ module Goodcheck
|
|
22
22
|
let :deprecated_token_pattern, object(token: string, case_insensitive: boolean?)
|
23
23
|
|
24
24
|
let :encoding, enum(*Encoding.name_list.map {|name| literal(name) })
|
25
|
-
let :glob_obj, object(pattern: string, encoding: optional(encoding)
|
25
|
+
let :glob_obj, object(pattern: string, encoding: optional(encoding),
|
26
|
+
exclude: enum?(string, array(string)))
|
26
27
|
let :one_glob, enum(glob_obj,
|
27
28
|
string,
|
28
29
|
detector: -> (value) {
|
@@ -213,21 +214,19 @@ module Goodcheck
|
|
213
214
|
|
214
215
|
def load
|
215
216
|
Goodcheck.logger.info "Loading configuration: #{path}"
|
216
|
-
|
217
|
-
Schema.config.coerce(content)
|
217
|
+
Schema.config.coerce(content)
|
218
218
|
|
219
|
-
|
219
|
+
rules = []
|
220
220
|
|
221
|
-
|
221
|
+
load_rules(rules, array(content[:rules]))
|
222
222
|
|
223
|
-
|
224
|
-
|
225
|
-
|
223
|
+
Array(content[:import]).each do |import|
|
224
|
+
load_import rules, import
|
225
|
+
end
|
226
226
|
|
227
|
-
|
227
|
+
exclude_paths = Array(content[:exclude])
|
228
228
|
|
229
|
-
|
230
|
-
end
|
229
|
+
Config.new(rules: rules, exclude_paths: exclude_paths)
|
231
230
|
end
|
232
231
|
|
233
232
|
def load_rules(rules, array)
|
@@ -239,13 +238,11 @@ module Goodcheck
|
|
239
238
|
def load_import(rules, import)
|
240
239
|
Goodcheck.logger.info "Importing rules from #{import}"
|
241
240
|
|
242
|
-
|
243
|
-
|
244
|
-
json = JSON.parse(JSON.dump(YAML.load(content, filename: import)), symbolize_names: true)
|
241
|
+
import_loader.load(import) do |content|
|
242
|
+
json = JSON.parse(JSON.dump(YAML.load(content, filename: import)), symbolize_names: true)
|
245
243
|
|
246
|
-
|
247
|
-
|
248
|
-
end
|
244
|
+
Schema.rules.coerce json
|
245
|
+
load_rules(rules, json)
|
249
246
|
end
|
250
247
|
end
|
251
248
|
|
@@ -344,9 +341,9 @@ module Goodcheck
|
|
344
341
|
globs.map do |glob|
|
345
342
|
case glob
|
346
343
|
when String
|
347
|
-
Glob.new(pattern: glob, encoding: nil)
|
344
|
+
Glob.new(pattern: glob, encoding: nil, exclude: nil)
|
348
345
|
when Hash
|
349
|
-
Glob.new(pattern: glob[:pattern], encoding: glob[:encoding])
|
346
|
+
Glob.new(pattern: glob[:pattern], encoding: glob[:encoding], exclude: glob[:exclude])
|
350
347
|
end
|
351
348
|
end
|
352
349
|
end
|
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,23 @@
|
|
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
|
+
|
11
21
|
attr_reader :cache_path
|
12
22
|
attr_reader :expires_in
|
13
23
|
attr_reader :force_download
|
@@ -24,20 +34,26 @@ module Goodcheck
|
|
24
34
|
uri = URI.parse(name)
|
25
35
|
|
26
36
|
case uri.scheme
|
27
|
-
when nil
|
28
|
-
load_file
|
37
|
+
when nil
|
38
|
+
load_file name, &block
|
39
|
+
when "file"
|
40
|
+
load_file uri.path, &block
|
29
41
|
when "http", "https"
|
30
42
|
load_http uri, &block
|
31
43
|
else
|
32
|
-
raise UnexpectedSchemaError.new(
|
44
|
+
raise UnexpectedSchemaError.new(uri)
|
33
45
|
end
|
34
46
|
end
|
35
47
|
|
36
|
-
def load_file(
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
48
|
+
def load_file(path)
|
49
|
+
files = Dir.glob(File.join(config_path.parent.to_path, path), File::FNM_DOTMATCH | File::FNM_EXTGLOB).sort
|
50
|
+
if files.empty?
|
51
|
+
raise FileNotFound.new(path)
|
52
|
+
else
|
53
|
+
files.each do |file|
|
54
|
+
Goodcheck.logger.info "Reading file: #{file}"
|
55
|
+
yield File.read(file)
|
56
|
+
end
|
41
57
|
end
|
42
58
|
end
|
43
59
|
|
@@ -71,7 +87,7 @@ module Goodcheck
|
|
71
87
|
if download
|
72
88
|
path.rmtree if path.exist?
|
73
89
|
Goodcheck.logger.info "Downloading content..."
|
74
|
-
content =
|
90
|
+
content = http_get uri
|
75
91
|
Goodcheck.logger.debug "Downloaded content: #{content[0, 1024].inspect}#{content.size > 1024 ? "..." : ""}"
|
76
92
|
yield content
|
77
93
|
write_cache uri, content
|
@@ -85,5 +101,21 @@ module Goodcheck
|
|
85
101
|
path = cache_path + cache_name(uri)
|
86
102
|
path.write(content)
|
87
103
|
end
|
104
|
+
|
105
|
+
# @see https://ruby-doc.org/stdlib-2.7.0/libdoc/net/http/rdoc/Net/HTTP.html#class-Net::HTTP-label-Following+Redirection
|
106
|
+
def http_get(uri, limit = 10)
|
107
|
+
raise ArgumentError, "Too many HTTP redirects" if limit == 0
|
108
|
+
|
109
|
+
res = Net::HTTP.get_response URI(uri)
|
110
|
+
case res
|
111
|
+
when Net::HTTPSuccess
|
112
|
+
res.body
|
113
|
+
when Net::HTTPRedirection
|
114
|
+
location = res['Location']
|
115
|
+
http_get location, limit - 1
|
116
|
+
else
|
117
|
+
raise "Error: HTTP GET #{uri.inspect} #{res.inspect}"
|
118
|
+
end
|
119
|
+
end
|
88
120
|
end
|
89
121
|
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
|