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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +49 -3
  3. data/LICENSE +1 -1
  4. data/README.md +8 -445
  5. data/lib/goodcheck.rb +9 -4
  6. data/lib/goodcheck/analyzer.rb +13 -9
  7. data/lib/goodcheck/buffer.rb +9 -21
  8. data/lib/goodcheck/cli.rb +79 -57
  9. data/lib/goodcheck/commands/check.rb +41 -27
  10. data/lib/goodcheck/commands/config_loading.rb +28 -5
  11. data/lib/goodcheck/commands/init.rb +4 -2
  12. data/lib/goodcheck/commands/pattern.rb +2 -1
  13. data/lib/goodcheck/commands/test.rb +38 -30
  14. data/lib/goodcheck/config.rb +68 -1
  15. data/lib/goodcheck/config_loader.rb +41 -31
  16. data/lib/goodcheck/error.rb +3 -0
  17. data/lib/goodcheck/exit_status.rb +8 -0
  18. data/lib/goodcheck/glob.rb +14 -3
  19. data/lib/goodcheck/import_loader.rb +96 -26
  20. data/lib/goodcheck/issue.rb +3 -3
  21. data/lib/goodcheck/location.rb +28 -0
  22. data/lib/goodcheck/logger.rb +4 -4
  23. data/lib/goodcheck/reporters/json.rb +6 -1
  24. data/lib/goodcheck/reporters/text.rb +44 -11
  25. data/lib/goodcheck/rule.rb +3 -1
  26. data/lib/goodcheck/unarchiver.rb +40 -0
  27. data/lib/goodcheck/version.rb +1 -1
  28. metadata +44 -82
  29. data/.github/dependabot.yml +0 -18
  30. data/.github/workflows/release.yml +0 -16
  31. data/.github/workflows/test.yml +0 -46
  32. data/.gitignore +0 -13
  33. data/.rubocop.yml +0 -5
  34. data/Dockerfile +0 -13
  35. data/Gemfile +0 -6
  36. data/Rakefile +0 -75
  37. data/bin/console +0 -14
  38. data/bin/setup +0 -8
  39. data/cheatsheet.pdf +0 -0
  40. data/docusaurus/.dockerignore +0 -2
  41. data/docusaurus/.gitignore +0 -12
  42. data/docusaurus/Dockerfile +0 -10
  43. data/docusaurus/docker-compose.yml +0 -18
  44. data/docusaurus/docs/commands.md +0 -69
  45. data/docusaurus/docs/configuration.md +0 -300
  46. data/docusaurus/docs/development.md +0 -15
  47. data/docusaurus/docs/getstarted.md +0 -46
  48. data/docusaurus/docs/rules.md +0 -79
  49. data/docusaurus/website/README.md +0 -193
  50. data/docusaurus/website/core/Footer.js +0 -100
  51. data/docusaurus/website/package.json +0 -14
  52. data/docusaurus/website/pages/en/index.js +0 -207
  53. data/docusaurus/website/pages/en/versions.js +0 -118
  54. data/docusaurus/website/sidebars.json +0 -11
  55. data/docusaurus/website/siteConfig.js +0 -171
  56. data/docusaurus/website/static/css/code-block-buttons.css +0 -39
  57. data/docusaurus/website/static/css/custom.css +0 -245
  58. data/docusaurus/website/static/img/favicon.ico +0 -0
  59. data/docusaurus/website/static/js/code-block-buttons.js +0 -47
  60. data/docusaurus/website/versioned_docs/version-1.0.0/commands.md +0 -70
  61. data/docusaurus/website/versioned_docs/version-1.0.0/configuration.md +0 -296
  62. data/docusaurus/website/versioned_docs/version-1.0.0/development.md +0 -16
  63. data/docusaurus/website/versioned_docs/version-1.0.0/getstarted.md +0 -47
  64. data/docusaurus/website/versioned_docs/version-1.0.0/rules.md +0 -81
  65. data/docusaurus/website/versioned_docs/version-1.0.2/rules.md +0 -79
  66. data/docusaurus/website/versioned_docs/version-2.4.0/configuration.md +0 -301
  67. data/docusaurus/website/versioned_docs/version-2.4.3/rules.md +0 -80
  68. data/docusaurus/website/versioned_sidebars/version-1.0.0-sidebars.json +0 -11
  69. data/docusaurus/website/versioned_sidebars/version-1.0.2-sidebars.json +0 -11
  70. data/docusaurus/website/versioned_sidebars/version-2.4.0-sidebars.json +0 -11
  71. data/docusaurus/website/versions.json +0 -12
  72. data/docusaurus/website/yarn.lock +0 -6604
  73. data/goodcheck.gemspec +0 -35
  74. data/goodcheck.yml +0 -10
  75. data/logo/GoodCheck Horizontal.pdf +0 -899
  76. data/logo/GoodCheck Horizontal.png +0 -0
  77. data/logo/GoodCheck Horizontal.svg +0 -55
  78. data/logo/GoodCheck logo.png +0 -0
  79. data/logo/GoodCheck vertical.png +0 -0
  80. data/sample.yml +0 -57
data/lib/goodcheck.rb CHANGED
@@ -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
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"
@@ -33,3 +33,8 @@ require "goodcheck/commands/init"
33
33
  require "goodcheck/commands/test"
34
34
  require "goodcheck/import_loader"
35
35
  require "goodcheck/commands/pattern"
36
+ require "goodcheck/unarchiver"
37
+
38
+ module Goodcheck
39
+ DEFAULT_CONFIG_FILE = "goodcheck.yml".freeze
40
+ end
@@ -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
@@ -1,8 +1,5 @@
1
1
  module Goodcheck
2
2
  class Buffer
3
- attr_reader :path
4
- attr_reader :content
5
-
6
3
  DISABLE_LINE_PATTERNS = [
7
4
  /\/\/ goodcheck-disable-line$/, #JS, Java, C, ...
8
5
  /# goodcheck-disable-line$/, # Ruby, Python, PHP, ...
@@ -25,10 +22,8 @@ module Goodcheck
25
22
  /' goodcheck-disable-next-line$/, # VB
26
23
  ].freeze
27
24
 
28
- class << self
29
- attr_accessor :DISABLE_LINE_PATTERNS
30
- attr_accessor :DISABLE_NEXT_LINE_PATTERNS
31
- end
25
+ attr_reader :path
26
+ attr_reader :content
32
27
 
33
28
  def initialize(path:, content:)
34
29
  @path = path
@@ -43,7 +38,7 @@ module Goodcheck
43
38
  start_position = 0
44
39
 
45
40
  content.split(/\n/, -1).each do |line|
46
- range = start_position...(start_position + line.bytesize)
41
+ range = start_position..(start_position + line.bytesize)
47
42
  @line_ranges << range
48
43
  start_position = range.end + 1
49
44
  end
@@ -51,10 +46,10 @@ module Goodcheck
51
46
 
52
47
  @line_ranges
53
48
  end
54
-
49
+
55
50
  def line_disabled?(line_number)
56
51
  if line_number > 1
57
- return true if DISABLE_NEXT_LINE_PATTERNS.any? { |pattern| line(line_number - 1).match?(pattern) }
52
+ return true if DISABLE_NEXT_LINE_PATTERNS.any? { |pattern| line(line_number - 1).match?(pattern) }
58
53
  end
59
54
 
60
55
  if line_number <= lines.length
@@ -70,7 +65,9 @@ module Goodcheck
70
65
  end
71
66
 
72
67
  if line_index
73
- [line_index + 1, position - line_ranges[line_index].begin]
68
+ line_number = line_index + 1
69
+ column_number = position - line_ranges[line_index].begin + 1
70
+ [line_number, column_number]
74
71
  end
75
72
  end
76
73
 
@@ -79,16 +76,7 @@ module Goodcheck
79
76
  end
80
77
 
81
78
  def line(line_number)
82
- lines[line_number-1]
83
- end
84
-
85
- def position_for_location(line, column)
86
- if (range = line_ranges[line-1])
87
- pos = range.begin + column
88
- if pos <= range.end
89
- pos
90
- end
91
- end
79
+ lines[line_number - 1]
92
80
  end
93
81
  end
94
82
  end
data/lib/goodcheck/cli.rb CHANGED
@@ -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,7 @@ module Goodcheck
19
19
  pattern: "Print regexp for rules",
20
20
  version: "Print version",
21
21
  help: "Show help and quit"
22
- }
23
-
22
+ }.freeze
24
23
 
25
24
  def run(args)
26
25
  command = args.shift&.to_sym
@@ -30,16 +29,22 @@ module Goodcheck
30
29
  elsif command == :"--version"
31
30
  version(args)
32
31
  else
33
- stderr.puts "Invalid command: #{command}" if command
32
+ if command
33
+ stderr.puts "invalid command: #{command}"
34
+ stderr.puts ""
35
+ end
34
36
  help(args)
35
- 1
37
+ EXIT_ERROR
36
38
  end
39
+ rescue OptionParser::ParseError => exn
40
+ stderr.puts exn
41
+ EXIT_ERROR
37
42
  rescue => exn
38
43
  stderr.puts exn.inspect
39
44
  exn.backtrace.each do |bt|
40
45
  stderr.puts " #{bt}"
41
46
  end
42
- 1
47
+ EXIT_ERROR
43
48
  end
44
49
 
45
50
  def home_path
@@ -51,35 +56,31 @@ module Goodcheck
51
56
  end
52
57
 
53
58
  def check(args)
54
- config_path = Pathname("goodcheck.yml")
59
+ config_path = Pathname(DEFAULT_CONFIG_FILE)
55
60
  targets = []
56
61
  rules = []
57
- format = nil
58
- loglevel = Logger::ERROR
62
+ formats = [:text, :json]
63
+ format = :text
64
+ loglevel = nil
59
65
  force_download = false
60
66
 
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|
67
+ OptionParser.new("Usage: goodcheck check [options] paths...") do |opts|
68
+ config_option(opts) { |config| config_path = config }
69
+ verbose_option(opts) { |level| loglevel = level }
70
+ debug_option(opts) { |level| loglevel = level }
71
+ force_download_option(opts) { force_download = true }
72
+ common_options(opts)
73
+
74
+ opts.on("-R RULE", "--rule=RULE", "Only rule(s) to check") do |rule|
66
75
  rules << rule
67
76
  end
68
- opts.on("--format=<text|json> [default: 'text']") do |f|
77
+
78
+ opts.on("--format=<#{formats.join('|')}>", formats, "Output format [default: '#{format}']") do |f|
69
79
  format = f
70
80
  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
81
  end.parse!(args)
81
82
 
82
- Goodcheck.logger.level = loglevel
83
+ Goodcheck.logger.level = loglevel if loglevel
83
84
 
84
85
  if args.empty?
85
86
  targets << Pathname(".")
@@ -88,13 +89,12 @@ module Goodcheck
88
89
  end
89
90
 
90
91
  reporter = case format
91
- when "text", nil
92
+ when :text
92
93
  Reporters::Text.new(stdout: stdout)
93
- when "json"
94
+ when :json
94
95
  Reporters::JSON.new(stdout: stdout, stderr: stderr)
95
96
  else
96
- stderr.puts "Unknown format: #{format}"
97
- return 1
97
+ raise ArgumentError, format.inspect
98
98
  end
99
99
 
100
100
  Goodcheck.logger.info "Configuration = #{config_path}"
@@ -108,23 +108,16 @@ module Goodcheck
108
108
  end
109
109
 
110
110
  def test(args)
111
- config_path = Pathname("goodcheck.yml")
112
- loglevel = Logger::ERROR
111
+ config_path = Pathname(DEFAULT_CONFIG_FILE)
112
+ loglevel = ::Logger::ERROR
113
113
  force_download = false
114
114
 
115
115
  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
116
+ config_option(opts) { |config| config_path = config }
117
+ verbose_option(opts) { |level| loglevel = level }
118
+ debug_option(opts) { |level| loglevel = level }
119
+ force_download_option(opts) { force_download = true }
120
+ common_options(opts)
128
121
  end.parse!(args)
129
122
 
130
123
  Goodcheck.logger.level = loglevel
@@ -137,14 +130,14 @@ module Goodcheck
137
130
  end
138
131
 
139
132
  def init(args)
140
- config_path = Pathname("goodcheck.yml")
133
+ config_path = Pathname(DEFAULT_CONFIG_FILE)
141
134
  force = false
142
135
 
143
136
  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
137
+ config_option(opts) { |config| config_path = config }
138
+ common_options(opts)
139
+
140
+ opts.on("--force", "Overwrite an existing file") do
148
141
  force = true
149
142
  end
150
143
  end.parse!(args)
@@ -152,9 +145,9 @@ module Goodcheck
152
145
  Commands::Init.new(stdout: stdout, stderr: stderr, path: config_path, force: force).run
153
146
  end
154
147
 
155
- def version(args)
148
+ def version(_args = nil)
156
149
  stdout.puts "goodcheck #{VERSION}"
157
- 0
150
+ EXIT_SUCCESS
158
151
  end
159
152
 
160
153
  def help(args)
@@ -164,19 +157,48 @@ module Goodcheck
164
157
  COMMANDS.each do |c, msg|
165
158
  stdout.puts " goodcheck #{c}\t#{msg}"
166
159
  end
167
- 0
160
+ EXIT_SUCCESS
168
161
  end
169
162
 
170
163
  def pattern(args)
171
- config_path = Pathname("goodcheck.yml")
164
+ config_path = Pathname(DEFAULT_CONFIG_FILE)
172
165
 
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
166
+ OptionParser.new do |opts|
167
+ opts.banner = "Usage: goodcheck pattern [options] ids..."
168
+ config_option(opts) { |config| config_path = config }
169
+ common_options(opts)
177
170
  end.parse!(args)
178
171
 
179
172
  Commands::Pattern.new(stdout: stdout, stderr: stderr, path: config_path, ids: Set.new(args), home_path: home_path).run
180
173
  end
174
+
175
+ def config_option(opts)
176
+ opts.on("-c CONFIG", "--config=CONFIG", "Configuration file path [default: '#{DEFAULT_CONFIG_FILE}']") do |config|
177
+ yield Pathname(config)
178
+ end
179
+ end
180
+
181
+ def verbose_option(opts)
182
+ opts.on("-v", "--verbose", "Set log level to verbose") { yield ::Logger::INFO }
183
+ end
184
+
185
+ def debug_option(opts)
186
+ opts.on("-d", "--debug", "Set log level to debug") { yield ::Logger::DEBUG }
187
+ end
188
+
189
+ def force_download_option(opts, &block)
190
+ opts.on("--force", "Download importing files always", &block)
191
+ end
192
+
193
+ def common_options(opts)
194
+ opts.on_tail("--version", COMMANDS.fetch(:version)) do
195
+ version
196
+ exit EXIT_SUCCESS
197
+ end
198
+ opts.on_tail("-h", "--help", COMMANDS.fetch(:help)) do
199
+ stdout.puts opts.help
200
+ exit EXIT_SUCCESS
201
+ end
202
+ end
181
203
  end
182
204
  end
@@ -13,6 +13,7 @@ module Goodcheck
13
13
 
14
14
  include ConfigLoading
15
15
  include HomePath
16
+ include ExitStatus
16
17
 
17
18
  def initialize(config_path:, rules:, targets:, reporter:, stderr:, home_path:, force_download:)
18
19
  @config_path = config_path
@@ -30,6 +31,14 @@ module Goodcheck
30
31
 
31
32
  reporter.analysis do
32
33
  load_config!(force_download: force_download, cache_path: cache_dir_path)
34
+
35
+ unless missing_rules.empty?
36
+ missing_rules.each do |rule|
37
+ stderr.puts "missing rule: #{rule}"
38
+ end
39
+ return EXIT_ERROR
40
+ end
41
+
33
42
  each_check do |buffer, rule, trigger|
34
43
  reported_issues = Set[]
35
44
 
@@ -46,38 +55,43 @@ module Goodcheck
46
55
  end
47
56
  end
48
57
 
49
- issue_reported ? 2 : 0
58
+ reporter.summary
59
+
60
+ issue_reported ? EXIT_MATCH : EXIT_SUCCESS
61
+ end
62
+ end
63
+
64
+ def missing_rules
65
+ @missing_rules ||= begin
66
+ config_rule_ids = config.rules.map(&:id)
67
+ rules.reject { |rule| config_rule_ids.include?(rule) }
50
68
  end
51
69
  end
52
70
 
53
71
  def each_check
54
72
  targets.each do |target|
55
73
  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
74
+ each_file target, immediate: true do |path|
75
+ Goodcheck.logger.debug "Checking file: #{path}"
76
+ reporter.file(path) do
77
+ buffers = {}
78
+
79
+ config.rules_for_path(path, rules_filter: rules) do |rule, glob, trigger|
80
+ Goodcheck.logger.debug "Checking rule: #{rule.id}"
81
+ begin
82
+ encoding = glob&.encoding || Encoding.default_external.name
83
+
84
+ if buffers[encoding]
85
+ buffer = buffers[encoding]
86
+ else
87
+ content = path.read(encoding: encoding).encode(Encoding.default_internal || Encoding::UTF_8)
88
+ buffer = Buffer.new(path: path, content: content)
89
+ buffers[encoding] = buffer
80
90
  end
91
+
92
+ yield buffer, rule, trigger
93
+ rescue ArgumentError => exn
94
+ stderr.puts "#{path}: #{exn.inspect}"
81
95
  end
82
96
  end
83
97
  end
@@ -94,7 +108,7 @@ module Goodcheck
94
108
  when DEFAULT_EXCLUSIONS.include?(path.basename.to_s)
95
109
  # noop
96
110
  when immediate || !excluded?(path)
97
- path.children.each do |child|
111
+ path.children.sort.each do |child|
98
112
  each_file(child, &block)
99
113
  end
100
114
  end
@@ -113,7 +127,7 @@ module Goodcheck
113
127
  end
114
128
 
115
129
  def excluded?(path)
116
- config.exclude_paths.any? {|pattern| path.fnmatch?(pattern, File::FNM_PATHNAME | File::FNM_EXTGLOB) }
130
+ config.exclude_path?(path)
117
131
  end
118
132
  end
119
133
  end