goodcheck 2.5.2 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
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