goodcheck 2.5.0 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -2
  3. data/README.md +4 -447
  4. data/lib/goodcheck.rb +5 -5
  5. data/lib/goodcheck/analyzer.rb +13 -9
  6. data/lib/goodcheck/buffer.rb +11 -16
  7. data/lib/goodcheck/cli.rb +80 -56
  8. data/lib/goodcheck/commands/check.rb +42 -26
  9. data/lib/goodcheck/commands/config_loading.rb +20 -2
  10. data/lib/goodcheck/commands/init.rb +4 -2
  11. data/lib/goodcheck/commands/pattern.rb +2 -1
  12. data/lib/goodcheck/commands/test.rb +14 -11
  13. data/lib/goodcheck/config_loader.rb +17 -20
  14. data/lib/goodcheck/error.rb +3 -0
  15. data/lib/goodcheck/exit_status.rb +6 -0
  16. data/lib/goodcheck/glob.rb +14 -3
  17. data/lib/goodcheck/import_loader.rb +42 -10
  18. data/lib/goodcheck/issue.rb +3 -3
  19. data/lib/goodcheck/location.rb +28 -0
  20. data/lib/goodcheck/logger.rb +4 -4
  21. data/lib/goodcheck/reporters/json.rb +4 -0
  22. data/lib/goodcheck/reporters/text.rb +42 -11
  23. data/lib/goodcheck/version.rb +1 -1
  24. metadata +29 -92
  25. data/.github/workflows/test.yml +0 -37
  26. data/.gitignore +0 -13
  27. data/.rubocop.yml +0 -5
  28. data/Dockerfile +0 -13
  29. data/Gemfile +0 -6
  30. data/Rakefile +0 -70
  31. data/benchmark/gc.c +0 -12221
  32. data/bin/console +0 -14
  33. data/bin/setup +0 -8
  34. data/cheatsheet.pdf +0 -0
  35. data/docusaurus/.dockerignore +0 -2
  36. data/docusaurus/.gitignore +0 -12
  37. data/docusaurus/Dockerfile +0 -10
  38. data/docusaurus/docker-compose.yml +0 -18
  39. data/docusaurus/docs/commands.md +0 -69
  40. data/docusaurus/docs/configuration.md +0 -300
  41. data/docusaurus/docs/development.md +0 -15
  42. data/docusaurus/docs/getstarted.md +0 -46
  43. data/docusaurus/docs/rules.md +0 -79
  44. data/docusaurus/website/README.md +0 -193
  45. data/docusaurus/website/core/Footer.js +0 -100
  46. data/docusaurus/website/package.json +0 -14
  47. data/docusaurus/website/pages/en/index.js +0 -207
  48. data/docusaurus/website/pages/en/versions.js +0 -118
  49. data/docusaurus/website/sidebars.json +0 -11
  50. data/docusaurus/website/siteConfig.js +0 -171
  51. data/docusaurus/website/static/css/code-block-buttons.css +0 -39
  52. data/docusaurus/website/static/css/custom.css +0 -245
  53. data/docusaurus/website/static/img/favicon.ico +0 -0
  54. data/docusaurus/website/static/js/code-block-buttons.js +0 -47
  55. data/docusaurus/website/versioned_docs/version-1.0.0/commands.md +0 -70
  56. data/docusaurus/website/versioned_docs/version-1.0.0/configuration.md +0 -296
  57. data/docusaurus/website/versioned_docs/version-1.0.0/development.md +0 -16
  58. data/docusaurus/website/versioned_docs/version-1.0.0/getstarted.md +0 -47
  59. data/docusaurus/website/versioned_docs/version-1.0.0/rules.md +0 -81
  60. data/docusaurus/website/versioned_docs/version-1.0.2/rules.md +0 -79
  61. data/docusaurus/website/versioned_docs/version-2.4.0/configuration.md +0 -301
  62. data/docusaurus/website/versioned_docs/version-2.4.3/rules.md +0 -80
  63. data/docusaurus/website/versioned_sidebars/version-1.0.0-sidebars.json +0 -11
  64. data/docusaurus/website/versioned_sidebars/version-1.0.2-sidebars.json +0 -11
  65. data/docusaurus/website/versioned_sidebars/version-2.4.0-sidebars.json +0 -11
  66. data/docusaurus/website/versions.json +0 -10
  67. data/docusaurus/website/yarn.lock +0 -6806
  68. data/goodcheck.gemspec +0 -36
  69. data/goodcheck.yml +0 -10
  70. data/logo/GoodCheck Horizontal.pdf +0 -899
  71. data/logo/GoodCheck Horizontal.png +0 -0
  72. data/logo/GoodCheck Horizontal.svg +0 -55
  73. data/logo/GoodCheck logo.png +0 -0
  74. data/logo/GoodCheck vertical.png +0 -0
  75. 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.load(config_path.read, filename: config_path.to_s)), symbolize_names: true)
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 1
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
- 0
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
- 0
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 0
28
+ return EXIT_SUCCESS
28
29
  end
29
30
 
30
- validate_rule_uniqueness or return 1
31
- validate_rules or return 1
31
+ validate_rule_uniqueness or return EXIT_ERROR
32
+ validate_rules or return EXIT_ERROR
32
33
 
33
- 0
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
- stdout.puts(Rainbow(" Found #{count} #{'duplication'.pluralize(count)}.😞").red)
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 " Testing #{(index+1).ordinalize} trigger..."
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 " #{(index+1).ordinalize} #{Rainbow('pass').green} example matched.😱"
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 " #{(index+1).ordinalize} #{Rainbow('fail').red} example didn't match.😱"
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 ["Tested #{rule_count} #{'rule'.pluralize(rule_count)}",
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 < StandardError; end
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
- Goodcheck.logger.tagged "#{path}" do
217
- Schema.config.coerce(content)
217
+ Schema.config.coerce(content)
218
218
 
219
- rules = []
219
+ rules = []
220
220
 
221
- load_rules(rules, array(content[:rules]))
221
+ load_rules(rules, array(content[:rules]))
222
222
 
223
- Array(content[:import]).each do |import|
224
- load_import rules, import
225
- end
223
+ Array(content[:import]).each do |import|
224
+ load_import rules, import
225
+ end
226
226
 
227
- exclude_paths = Array(content[:exclude])
227
+ exclude_paths = Array(content[:exclude])
228
228
 
229
- Config.new(rules: rules, exclude_paths: exclude_paths)
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
- Goodcheck.logger.tagged import do
243
- import_loader.load(import) do |content|
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
- Schema.rules.coerce json
247
- load_rules(rules, json)
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
@@ -0,0 +1,3 @@
1
+ module Goodcheck
2
+ class Error < StandardError; end
3
+ end
@@ -0,0 +1,6 @@
1
+ module Goodcheck
2
+ module ExitStatus
3
+ EXIT_SUCCESS = 0
4
+ EXIT_ERROR = 1
5
+ end
6
+ end
@@ -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, File::FNM_PATHNAME | File::FNM_EXTGLOB | File::FNM_DOTMATCH)
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 < StandardError
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, "file"
28
- load_file uri, &block
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("Unexpected URI schema: #{uri.class.name}")
44
+ raise UnexpectedSchemaError.new(uri)
33
45
  end
34
46
  end
35
47
 
36
- def load_file(uri)
37
- path = (config_path.parent + uri.path)
38
-
39
- begin
40
- yield path.read
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 = HTTPClient.new.get_content(uri)
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
@@ -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:, range:, rule:, text:)
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
 
@@ -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
@@ -1,8 +1,8 @@
1
1
  module Goodcheck
2
2
  def self.logger
3
- @logger ||= ActiveSupport::TaggedLogging.new(Logger.new(STDERR)).tap do |logger|
4
- logger.push_tags VERSION
5
- logger.level = Logger::ERROR
6
- end
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