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.
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,19 +1,19 @@
1
1
  require "strscan"
2
2
  require "pathname"
3
+ require "set"
3
4
  require "strong_json"
4
5
  require "yaml"
5
6
  require "json"
6
- require "active_support/core_ext/hash/indifferent_access"
7
- require "active_support/core_ext/integer/inflections"
8
- require "active_support/tagged_logging"
7
+ require "logger"
9
8
  require "rainbow"
10
9
  require "digest/sha2"
11
- require "httpclient"
10
+ require "net/http"
12
11
 
13
12
  require "goodcheck/version"
13
+ require "goodcheck/error"
14
14
  require "goodcheck/logger"
15
15
  require "goodcheck/home_path"
16
-
16
+ require "goodcheck/exit_status"
17
17
  require "goodcheck/glob"
18
18
  require "goodcheck/buffer"
19
19
  require "goodcheck/location"
@@ -13,7 +13,7 @@ module Goodcheck
13
13
  def scan(&block)
14
14
  if block_given?
15
15
  if trigger.patterns.empty?
16
- yield Issue.new(buffer: buffer, range: nil, rule: rule, text: nil)
16
+ yield Issue.new(buffer: buffer, rule: rule)
17
17
  else
18
18
  var_pats, novar_pats = trigger.patterns.partition {|pat|
19
19
  pat.is_a?(Pattern::Token) && !pat.variables.empty?
@@ -44,9 +44,7 @@ module Goodcheck
44
44
  while true
45
45
  case
46
46
  when scanner.scan_until(regexp)
47
- text = scanner.matched
48
- range = (scanner.pos - text.bytesize) .. scanner.pos
49
- issues << Issue.new(buffer: buffer, range: range, rule: rule, text: text)
47
+ issues << new_issue_with_matched(scanner)
50
48
  else
51
49
  break
52
50
  end
@@ -55,7 +53,7 @@ module Goodcheck
55
53
  issues.each(&block)
56
54
  else
57
55
  unless regexp =~ buffer.content
58
- yield Issue.new(buffer: buffer, range: nil, rule: rule, text: nil)
56
+ yield Issue.new(buffer: buffer, rule: rule)
59
57
  end
60
58
  end
61
59
  end
@@ -68,9 +66,7 @@ module Goodcheck
68
66
  case
69
67
  when scanner.scan_until(pat.regexp)
70
68
  if pat.test_variables(scanner)
71
- text = scanner.matched
72
- range = (scanner.pos - text.bytesize) .. scanner.pos
73
- yield Issue.new(buffer: buffer, range: range, rule: rule, text: text)
69
+ yield new_issue_with_matched(scanner)
74
70
  end
75
71
  else
76
72
  break
@@ -84,11 +80,19 @@ module Goodcheck
84
80
  break
85
81
  end
86
82
  else
87
- yield Issue.new(buffer: buffer, range: nil, rule: rule, text: nil)
83
+ yield Issue.new(buffer: buffer, rule: rule)
88
84
  break
89
85
  end
90
86
  end
91
87
  end
92
88
  end
89
+
90
+ private
91
+
92
+ def new_issue_with_matched(scanner)
93
+ Issue.new(buffer: buffer, rule: rule,
94
+ text: scanner.matched,
95
+ text_begin_pos: scanner.pos - scanner.matched_size)
96
+ end
93
97
  end
94
98
  end
@@ -8,7 +8,8 @@ module Goodcheck
8
8
  /# goodcheck-disable-line$/, # Ruby, Python, PHP, ...
9
9
  /-- goodcheck-disable-line$/, # Haskel, SQL, ...
10
10
  /<!-- goodcheck-disable-line -->$/, # HTML, Markdown, ...
11
- /\/* goodcheck-disable-line *\/$/, # CSS, SCSS,
11
+ /\/\* goodcheck-disable-line \*\/$/, # CSS, SCSS,
12
+ /\{\s*\/\* goodcheck-disable-line \*\/\s*\}$/, # JSX, ...
12
13
  /<%# goodcheck-disable-line %>$/, # ERB, ...
13
14
  /' goodcheck-disable-line$/, # VB
14
15
  ].freeze
@@ -18,7 +19,8 @@ module Goodcheck
18
19
  /# goodcheck-disable-next-line$/, # Ruby, Python, PHP, ...
19
20
  /-- goodcheck-disable-next-line$/, # Haskel, SQL, ...
20
21
  /<!-- goodcheck-disable-next-line -->$/, # HTML, Markdown, ...
21
- /\/* goodcheck-disable-next-line *\/$/, # CSS, SCSS,
22
+ /\/\* goodcheck-disable-next-line \*\/$/, # CSS, SCSS,
23
+ /\{\s*\/\* goodcheck-disable-next-line \*\/\s*\}$/, # JSX, ...
22
24
  /<%# goodcheck-disable-next-line %>$/, # ERB, ...
23
25
  /' goodcheck-disable-next-line$/, # VB
24
26
  ].freeze
@@ -41,7 +43,7 @@ module Goodcheck
41
43
  start_position = 0
42
44
 
43
45
  content.split(/\n/, -1).each do |line|
44
- range = start_position...(start_position + line.bytesize)
46
+ range = start_position..(start_position + line.bytesize)
45
47
  @line_ranges << range
46
48
  start_position = range.end + 1
47
49
  end
@@ -49,10 +51,10 @@ module Goodcheck
49
51
 
50
52
  @line_ranges
51
53
  end
52
-
54
+
53
55
  def line_disabled?(line_number)
54
56
  if line_number > 1
55
- return true if DISABLE_NEXT_LINE_PATTERNS.any? { |pattern| line(line_number - 1).match?(pattern) }
57
+ return true if DISABLE_NEXT_LINE_PATTERNS.any? { |pattern| line(line_number - 1).match?(pattern) }
56
58
  end
57
59
 
58
60
  if line_number <= lines.length
@@ -68,7 +70,9 @@ module Goodcheck
68
70
  end
69
71
 
70
72
  if line_index
71
- [line_index + 1, position - line_ranges[line_index].begin]
73
+ line_number = line_index + 1
74
+ column_number = position - line_ranges[line_index].begin + 1
75
+ [line_number, column_number]
72
76
  end
73
77
  end
74
78
 
@@ -77,16 +81,7 @@ module Goodcheck
77
81
  end
78
82
 
79
83
  def line(line_number)
80
- lines[line_number-1]
81
- end
82
-
83
- def position_for_location(line, column)
84
- if (range = line_ranges[line-1])
85
- pos = range.begin + column
86
- if pos <= range.end
87
- pos
88
- end
89
- end
84
+ lines[line_number - 1]
90
85
  end
91
86
  end
92
87
  end
@@ -1,9 +1,9 @@
1
1
  require "optparse"
2
2
 
3
- Version = Goodcheck::VERSION
4
-
5
3
  module Goodcheck
6
4
  class CLI
5
+ include ExitStatus
6
+
7
7
  attr_reader :stdout
8
8
  attr_reader :stderr
9
9
 
@@ -19,8 +19,9 @@ module Goodcheck
19
19
  pattern: "Print regexp for rules",
20
20
  version: "Print version",
21
21
  help: "Show help and quit"
22
- }
22
+ }.freeze
23
23
 
24
+ DEFAULT_CONFIG_FILE = Pathname("goodcheck.yml").freeze
24
25
 
25
26
  def run(args)
26
27
  command = args.shift&.to_sym
@@ -30,16 +31,22 @@ module Goodcheck
30
31
  elsif command == :"--version"
31
32
  version(args)
32
33
  else
33
- stderr.puts "Invalid command: #{command}" if command
34
+ if command
35
+ stderr.puts "invalid command: #{command}"
36
+ stderr.puts ""
37
+ end
34
38
  help(args)
35
- 1
39
+ EXIT_ERROR
36
40
  end
41
+ rescue OptionParser::ParseError => exn
42
+ stderr.puts exn
43
+ EXIT_ERROR
37
44
  rescue => exn
38
45
  stderr.puts exn.inspect
39
46
  exn.backtrace.each do |bt|
40
47
  stderr.puts " #{bt}"
41
48
  end
42
- 1
49
+ EXIT_ERROR
43
50
  end
44
51
 
45
52
  def home_path
@@ -51,35 +58,31 @@ module Goodcheck
51
58
  end
52
59
 
53
60
  def check(args)
54
- config_path = Pathname("goodcheck.yml")
61
+ config_path = DEFAULT_CONFIG_FILE
55
62
  targets = []
56
63
  rules = []
57
- format = nil
58
- loglevel = Logger::ERROR
64
+ formats = [:text, :json]
65
+ format = :text
66
+ loglevel = nil
59
67
  force_download = false
60
68
 
61
- OptionParser.new("Usage: goodcheck check [options] dirs...") do |opts|
62
- opts.on("-c CONFIG", "--config=CONFIG") do |config|
63
- config_path = Pathname(config)
64
- end
65
- opts.on("-R RULE", "--rule=RULE") do |rule|
69
+ OptionParser.new("Usage: goodcheck check [options] paths...") do |opts|
70
+ config_option(opts) { |config| config_path = config }
71
+ verbose_option(opts) { |level| loglevel = level }
72
+ debug_option(opts) { |level| loglevel = level }
73
+ force_download_option(opts) { force_download = true }
74
+ common_options(opts)
75
+
76
+ opts.on("-R RULE", "--rule=RULE", "Only rule(s) to check") do |rule|
66
77
  rules << rule
67
78
  end
68
- opts.on("--format=<text|json> [default: 'text']") do |f|
79
+
80
+ opts.on("--format=<#{formats.join('|')}>", formats, "Output format [default: '#{format}']") do |f|
69
81
  format = f
70
82
  end
71
- opts.on("-v", "--verbose") do
72
- loglevel = Logger::INFO
73
- end
74
- opts.on("-d", "--debug") do
75
- loglevel = Logger::DEBUG
76
- end
77
- opts.on("--force") do
78
- force_download = true
79
- end
80
83
  end.parse!(args)
81
84
 
82
- Goodcheck.logger.level = loglevel
85
+ Goodcheck.logger.level = loglevel if loglevel
83
86
 
84
87
  if args.empty?
85
88
  targets << Pathname(".")
@@ -88,13 +91,12 @@ module Goodcheck
88
91
  end
89
92
 
90
93
  reporter = case format
91
- when "text", nil
94
+ when :text
92
95
  Reporters::Text.new(stdout: stdout)
93
- when "json"
96
+ when :json
94
97
  Reporters::JSON.new(stdout: stdout, stderr: stderr)
95
98
  else
96
- stderr.puts "Unknown format: #{format}"
97
- return 1
99
+ raise ArgumentError, format.inspect
98
100
  end
99
101
 
100
102
  Goodcheck.logger.info "Configuration = #{config_path}"
@@ -108,23 +110,16 @@ module Goodcheck
108
110
  end
109
111
 
110
112
  def test(args)
111
- config_path = Pathname("goodcheck.yml")
112
- loglevel = Logger::ERROR
113
+ config_path = DEFAULT_CONFIG_FILE
114
+ loglevel = ::Logger::ERROR
113
115
  force_download = false
114
116
 
115
117
  OptionParser.new("Usage: goodcheck test [options]") do |opts|
116
- opts.on("-c CONFIG", "--config=CONFIG") do |config|
117
- config_path = Pathname(config)
118
- end
119
- opts.on("-v", "--verbose") do
120
- loglevel = Logger::INFO
121
- end
122
- opts.on("-d", "--debug") do
123
- loglevel = Logger::DEBUG
124
- end
125
- opts.on("--force") do
126
- force_download = true
127
- end
118
+ config_option(opts) { |config| config_path = config }
119
+ verbose_option(opts) { |level| loglevel = level }
120
+ debug_option(opts) { |level| loglevel = level }
121
+ force_download_option(opts) { force_download = true }
122
+ common_options(opts)
128
123
  end.parse!(args)
129
124
 
130
125
  Goodcheck.logger.level = loglevel
@@ -137,14 +132,14 @@ module Goodcheck
137
132
  end
138
133
 
139
134
  def init(args)
140
- config_path = Pathname("goodcheck.yml")
135
+ config_path = DEFAULT_CONFIG_FILE
141
136
  force = false
142
137
 
143
138
  OptionParser.new("Usage: goodcheck init [options]") do |opts|
144
- opts.on("-c CONFIG", "--config=CONFIG") do |config|
145
- config_path = Pathname(config)
146
- end
147
- opts.on("--force") do
139
+ config_option(opts) { |config| config_path = config }
140
+ common_options(opts)
141
+
142
+ opts.on("--force", "Overwrite an existing file") do
148
143
  force = true
149
144
  end
150
145
  end.parse!(args)
@@ -152,9 +147,9 @@ module Goodcheck
152
147
  Commands::Init.new(stdout: stdout, stderr: stderr, path: config_path, force: force).run
153
148
  end
154
149
 
155
- def version(args)
150
+ def version(_args = nil)
156
151
  stdout.puts "goodcheck #{VERSION}"
157
- 0
152
+ EXIT_SUCCESS
158
153
  end
159
154
 
160
155
  def help(args)
@@ -164,19 +159,48 @@ module Goodcheck
164
159
  COMMANDS.each do |c, msg|
165
160
  stdout.puts " goodcheck #{c}\t#{msg}"
166
161
  end
167
- 0
162
+ EXIT_SUCCESS
168
163
  end
169
164
 
170
165
  def pattern(args)
171
- config_path = Pathname("goodcheck.yml")
166
+ config_path = DEFAULT_CONFIG_FILE
172
167
 
173
- OptionParser.new("Usage: goodcheck pattern [options] ids...") do |opts|
174
- opts.on("-c CONFIG", "--config=CONFIG") do |config|
175
- config_path = Pathname(config)
176
- end
168
+ OptionParser.new do |opts|
169
+ opts.banner = "Usage: goodcheck pattern [options] ids..."
170
+ config_option(opts) { |config| config_path = config }
171
+ common_options(opts)
177
172
  end.parse!(args)
178
173
 
179
174
  Commands::Pattern.new(stdout: stdout, stderr: stderr, path: config_path, ids: Set.new(args), home_path: home_path).run
180
175
  end
176
+
177
+ def config_option(opts)
178
+ opts.on("-c CONFIG", "--config=CONFIG", "Configuration file path [default: '#{DEFAULT_CONFIG_FILE}']") do |config|
179
+ yield Pathname(config)
180
+ end
181
+ end
182
+
183
+ def verbose_option(opts)
184
+ opts.on("-v", "--verbose", "Set log level to verbose") { yield ::Logger::INFO }
185
+ end
186
+
187
+ def debug_option(opts)
188
+ opts.on("-d", "--debug", "Set log level to debug") { yield ::Logger::DEBUG }
189
+ end
190
+
191
+ def force_download_option(opts, &block)
192
+ opts.on("--force", "Download importing files always", &block)
193
+ end
194
+
195
+ def common_options(opts)
196
+ opts.on_tail("--version", COMMANDS.fetch(:version)) do
197
+ version
198
+ exit EXIT_SUCCESS
199
+ end
200
+ opts.on_tail("-h", "--help", COMMANDS.fetch(:help)) do
201
+ stdout.puts opts.help
202
+ exit EXIT_SUCCESS
203
+ end
204
+ end
181
205
  end
182
206
  end
@@ -13,6 +13,9 @@ module Goodcheck
13
13
 
14
14
  include ConfigLoading
15
15
  include HomePath
16
+ include ExitStatus
17
+
18
+ EXIT_MATCH = 2
16
19
 
17
20
  def initialize(config_path:, rules:, targets:, reporter:, stderr:, home_path:, force_download:)
18
21
  @config_path = config_path
@@ -30,6 +33,14 @@ module Goodcheck
30
33
 
31
34
  reporter.analysis do
32
35
  load_config!(force_download: force_download, cache_path: cache_dir_path)
36
+
37
+ unless missing_rules.empty?
38
+ missing_rules.each do |rule|
39
+ stderr.puts "missing rule: #{rule}"
40
+ end
41
+ return EXIT_ERROR
42
+ end
43
+
33
44
  each_check do |buffer, rule, trigger|
34
45
  reported_issues = Set[]
35
46
 
@@ -46,38 +57,43 @@ module Goodcheck
46
57
  end
47
58
  end
48
59
 
49
- issue_reported ? 2 : 0
60
+ reporter.summary
61
+
62
+ issue_reported ? EXIT_MATCH : EXIT_SUCCESS
63
+ end
64
+ end
65
+
66
+ def missing_rules
67
+ @missing_rules ||= begin
68
+ config_rule_ids = config.rules.map(&:id)
69
+ rules.reject { |rule| config_rule_ids.include?(rule) }
50
70
  end
51
71
  end
52
72
 
53
73
  def each_check
54
74
  targets.each do |target|
55
75
  Goodcheck.logger.info "Checking target: #{target}"
56
- Goodcheck.logger.tagged target.to_s do
57
- each_file target, immediate: true do |path|
58
- Goodcheck.logger.debug "Checking file: #{path}"
59
- Goodcheck.logger.tagged path.to_s do
60
- reporter.file(path) do
61
- buffers = {}
62
-
63
- config.rules_for_path(path, rules_filter: rules) do |rule, glob, trigger|
64
- Goodcheck.logger.debug "Checking rule: #{rule.id}"
65
- begin
66
- encoding = glob&.encoding || Encoding.default_external.name
67
-
68
- if buffers[encoding]
69
- buffer = buffers[encoding]
70
- else
71
- content = path.read(encoding: encoding).encode(Encoding.default_internal || Encoding::UTF_8)
72
- buffer = Buffer.new(path: path, content: content)
73
- buffers[encoding] = buffer
74
- end
75
-
76
- yield buffer, rule, trigger
77
- rescue ArgumentError => exn
78
- stderr.puts "#{path}: #{exn.inspect}"
79
- end
76
+ each_file target, immediate: true do |path|
77
+ Goodcheck.logger.debug "Checking file: #{path}"
78
+ reporter.file(path) do
79
+ buffers = {}
80
+
81
+ config.rules_for_path(path, rules_filter: rules) do |rule, glob, trigger|
82
+ Goodcheck.logger.debug "Checking rule: #{rule.id}"
83
+ begin
84
+ encoding = glob&.encoding || Encoding.default_external.name
85
+
86
+ if buffers[encoding]
87
+ buffer = buffers[encoding]
88
+ else
89
+ content = path.read(encoding: encoding).encode(Encoding.default_internal || Encoding::UTF_8)
90
+ buffer = Buffer.new(path: path, content: content)
91
+ buffers[encoding] = buffer
80
92
  end
93
+
94
+ yield buffer, rule, trigger
95
+ rescue ArgumentError => exn
96
+ stderr.puts "#{path}: #{exn.inspect}"
81
97
  end
82
98
  end
83
99
  end
@@ -94,7 +110,7 @@ module Goodcheck
94
110
  when DEFAULT_EXCLUSIONS.include?(path.basename.to_s)
95
111
  # noop
96
112
  when immediate || !excluded?(path)
97
- path.children.each do |child|
113
+ path.children.sort.each do |child|
98
114
  each_file(child, &block)
99
115
  end
100
116
  end