spoom 1.0.7 → 1.0.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f16907e7c4ce2a3d368bcac828ae7cfb43f0dde8f857bbfa364c4a3a16e0e433
4
- data.tar.gz: b6d2a532d34b0cd5e8e4964d2edc5e279e5481de511a4e61ea764e120e4bae33
3
+ metadata.gz: 866446fc75971d51c7a71d7ed713cf256f682ffbd0fabfaf97c95b601638876b
4
+ data.tar.gz: a38dd3b7859929349c2d84b4119e5f8d8d374c0cf5062adbb3904065a1d6f0f2
5
5
  SHA512:
6
- metadata.gz: ad2f822b0c34fdb51ae8002bdc2c3f35202e219e334723716b141afe9ab00b18c65f74c3129cff6e4a9cb992d3c673ccb8afa522114cf2759af5cdd888df5100
7
- data.tar.gz: f077abc8f9be4b2768776214601dd1c8d8c5709649536202a0ab9765058de9dc8590712b9a6fb96c30a0ac5ed46e7c21d84ab9bff190e48d99d5e0bddab1d44e
6
+ metadata.gz: 257a78cbdf439ee83e23a823ed39918ada7a1b66786967a77604e5dca2bb825aeb8af2f5fcda6c497686f29f9d0c7dba1d051060d984694f22c0064dcbd8c1a9
7
+ data.tar.gz: 4a66b832da23c6237d1dedc16735cce058306ce66327fd2e074d72a481d55412aa7dc8047f0fa25957e1369747478347b77ccf8cd7c85cdb286fcff6a564a412
data/README.md CHANGED
@@ -69,7 +69,7 @@ $ spoom files
69
69
  List all typechecking errors sorted by location:
70
70
 
71
71
  ```
72
- $ spoom tc -s
72
+ $ spoom tc -s loc
73
73
  ```
74
74
 
75
75
  List all typechecking errors sorted by error code first:
@@ -96,6 +96,31 @@ These options can be combined:
96
96
  $ spoom tc -s -c 7004 -l 10
97
97
  ```
98
98
 
99
+ Remove duplicated error lines:
100
+
101
+ ```
102
+ $ spoom tc -u
103
+ ```
104
+
105
+ Format each error line:
106
+
107
+ ```
108
+ $ spoom tc -f '%C - %F:%L: %M'
109
+ ```
110
+
111
+ Where:
112
+
113
+ * `%C` is the error code
114
+ * `%F` is the file the error is from
115
+ * `%L` is the line the error is from
116
+ * `%M` is the error message
117
+
118
+ Hide the `Errors: X` at the end of the list:
119
+
120
+ ```
121
+ $ spoom tc --no-count
122
+ ```
123
+
99
124
  #### Typing coverage
100
125
 
101
126
  Show metrics about the project contents and the typing coverage:
@@ -183,6 +208,24 @@ Bump the strictness from all files currently at `typed: false` to `typed: true`
183
208
  $ spoom bump --from false --to true -f
184
209
  ```
185
210
 
211
+ Bump the strictness from a list of files (one file by line):
212
+
213
+ ```
214
+ $ spoom bump --from false --to true -o list.txt
215
+ ```
216
+
217
+ Check if files can be bumped without applying any change:
218
+
219
+ ```
220
+ $ spoom bump --from false --to true --dry
221
+ ```
222
+
223
+ Bump files using a custom instance of Sorbet:
224
+
225
+ ```
226
+ $ spoom bump --from false --to true --sorbet /path/to/sorbet/bin
227
+ ```
228
+
186
229
  #### Interact with Sorbet LSP mode
187
230
 
188
231
  **Experimental**
@@ -273,7 +316,7 @@ Create an LSP client:
273
316
 
274
317
  ```rb
275
318
  client = Spoom::LSP::Client.new(
276
- Spoom::Config::SORBET_PATH,
319
+ Spoom::Sorbet::BIN_PATH,
277
320
  "--lsp",
278
321
  "--enable-all-experimental-lsp-features",
279
322
  "--disable-watchman",
@@ -4,10 +4,27 @@
4
4
  require "sorbet-runtime"
5
5
 
6
6
  module Spoom
7
+ extend T::Sig
8
+
9
+ SPOOM_PATH = (Pathname.new(__FILE__) / ".." / "..").to_s
10
+
7
11
  class Error < StandardError; end
8
- end
9
12
 
10
- require "spoom/config"
13
+ sig do
14
+ params(
15
+ cmd: String,
16
+ arg: String,
17
+ path: String,
18
+ capture_err: T::Boolean
19
+ ).returns([String, T::Boolean])
20
+ end
21
+ def self.exec(cmd, *arg, path: '.', capture_err: false)
22
+ method = capture_err ? "popen2e" : "popen2"
23
+ Open3.send(method, [cmd, *arg].join(" "), chdir: path) do |_, o, t|
24
+ [o.read, T.cast(t.value, Process::Status).success?]
25
+ end
26
+ end
27
+ end
11
28
 
12
29
  require "spoom/sorbet"
13
30
  require "spoom/cli"
@@ -17,26 +17,27 @@ module Spoom
17
17
  extend T::Sig
18
18
  include Helper
19
19
 
20
- class_option :color, desc: "Use colors", type: :boolean, default: true
21
- class_option :path, desc: "Run spoom in a specific path", type: :string, default: ".", aliases: :p
20
+ class_option :color, type: :boolean, default: true, desc: "Use colors"
21
+ class_option :path, type: :string, default: ".", aliases: :p, desc: "Run spoom in a specific path"
22
+
22
23
  map T.unsafe(%w[--version -v] => :__print_version)
23
24
 
24
- desc "bump", "bump Sorbet sigils from `false` to `true` when no errors"
25
+ desc "bump", "Bump Sorbet sigils from `false` to `true` when no errors"
25
26
  subcommand "bump", Spoom::Cli::Bump
26
27
 
27
- desc "config", "manage Sorbet config"
28
+ desc "config", "Manage Sorbet config"
28
29
  subcommand "config", Spoom::Cli::Config
29
30
 
30
- desc "coverage", "collect metrics related to Sorbet coverage"
31
+ desc "coverage", "Collect metrics related to Sorbet coverage"
31
32
  subcommand "coverage", Spoom::Cli::Coverage
32
33
 
33
- desc "lsp", "send LSP requests to Sorbet"
34
+ desc "lsp", "Send LSP requests to Sorbet"
34
35
  subcommand "lsp", Spoom::Cli::LSP
35
36
 
36
- desc "tc", "run Sorbet and parses its output"
37
+ desc "tc", "Run Sorbet and parses its output"
37
38
  subcommand "tc", Spoom::Cli::Run
38
39
 
39
- desc "files", "list all the files typechecked by Sorbet"
40
+ desc "files", "List all the files typechecked by Sorbet"
40
41
  def files
41
42
  in_sorbet_project!
42
43
 
@@ -53,7 +54,7 @@ module Spoom
53
54
  end
54
55
  end
55
56
 
56
- desc "--version", "show version"
57
+ desc "--version", "Show version"
57
58
  def __print_version
58
59
  puts "Spoom v#{Spoom::VERSION}"
59
60
  end
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'find'
@@ -12,10 +12,18 @@ module Spoom
12
12
 
13
13
  default_task :bump
14
14
 
15
- desc "bump DIRECTORY", "change Sorbet sigils from one strictness to another when no errors"
16
- option :from, type: :string, default: Spoom::Sorbet::Sigils::STRICTNESS_FALSE
17
- option :to, type: :string, default: Spoom::Sorbet::Sigils::STRICTNESS_TRUE
18
- option :force, desc: "change strictness without type checking", type: :boolean, default: false, aliases: :f
15
+ desc "bump DIRECTORY", "Change Sorbet sigils from one strictness to another when no errors"
16
+ option :from, type: :string, default: Spoom::Sorbet::Sigils::STRICTNESS_FALSE,
17
+ desc: "Change only files from this strictness"
18
+ option :to, type: :string, default: Spoom::Sorbet::Sigils::STRICTNESS_TRUE,
19
+ desc: "Change files to this strictness"
20
+ option :force, type: :boolean, default: false, aliases: :f,
21
+ desc: "Change strictness without type checking"
22
+ option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
23
+ option :dry, type: :boolean, default: false, aliases: :d,
24
+ desc: "Only display what would happen, do not actually change sigils"
25
+ option :only, type: :string, default: nil, aliases: :o,
26
+ desc: "Only change specified list (one file by line)"
19
27
  sig { params(directory: String).void }
20
28
  def bump(directory = ".")
21
29
  in_sorbet_project!
@@ -23,6 +31,9 @@ module Spoom
23
31
  from = options[:from]
24
32
  to = options[:to]
25
33
  force = options[:force]
34
+ dry = options[:dry]
35
+ only = options[:only]
36
+ exec_path = File.expand_path(self.exec_path)
26
37
 
27
38
  unless Sorbet::Sigils.valid_strictness?(from)
28
39
  say_error("Invalid strictness #{from} for option --from")
@@ -36,13 +47,32 @@ module Spoom
36
47
 
37
48
  directory = File.expand_path(directory)
38
49
  files_to_bump = Sorbet::Sigils.files_with_sigil_strictness(directory, from)
50
+
51
+ if only
52
+ list = File.read(only).lines.map { |file| File.expand_path(file.strip) }
53
+ files_to_bump.select! { |file| list.include?(File.expand_path(file)) }
54
+ end
55
+
56
+ if files_to_bump.empty?
57
+ $stderr.puts("No file to bump from #{from} to #{to}")
58
+ exit(0)
59
+ end
60
+
39
61
  Sorbet::Sigils.change_sigil_in_files(files_to_bump, to)
40
62
 
41
- return if force
63
+ if force
64
+ print_changes(files_to_bump, from: from, to: to, dry: dry, path: exec_path)
65
+ undo_changes(files_to_bump, from) if dry
66
+ exit(files_to_bump.empty?)
67
+ end
42
68
 
43
- output, no_errors = Sorbet.srb_tc(path: exec_path, capture_err: true)
69
+ output, no_errors = Sorbet.srb_tc(path: exec_path, capture_err: true, sorbet_bin: options[:sorbet])
44
70
 
45
- return if no_errors
71
+ if no_errors
72
+ print_changes(files_to_bump, from: from, to: to, dry: dry, path: exec_path)
73
+ undo_changes(files_to_bump, from) if dry
74
+ exit(files_to_bump.empty?)
75
+ end
46
76
 
47
77
  errors = Sorbet::Errors::Parser.parse_string(output)
48
78
 
@@ -53,7 +83,35 @@ module Spoom
53
83
  path
54
84
  end.compact.uniq
55
85
 
56
- Sorbet::Sigils.change_sigil_in_files(files_with_errors, from)
86
+ undo_changes(files_with_errors, from)
87
+
88
+ files_changed = files_to_bump - files_with_errors
89
+ print_changes(files_changed, from: from, to: to, dry: dry, path: exec_path)
90
+ undo_changes(files_to_bump, from) if dry
91
+ exit(files_changed.empty?)
92
+ end
93
+
94
+ no_commands do
95
+ def print_changes(files, from: "false", to: "true", dry: false, path: File.expand_path("."))
96
+ if files.empty?
97
+ $stderr.puts("No file to bump from #{from} to #{to}")
98
+ return
99
+ end
100
+ $stderr.write(dry ? "Can bump" : "Bumped")
101
+ $stderr.write(" #{files.size} file#{'s' if files.size > 1}")
102
+ $stderr.puts(" from #{from} to #{to}:")
103
+ files.each do |file|
104
+ file_path = Pathname.new(file).relative_path_from(path)
105
+ $stderr.puts(" + #{file_path}")
106
+ end
107
+ if dry
108
+ $stderr.puts("\nRun `spoom bump --from #{from} --to #{to}` to bump them")
109
+ end
110
+ end
111
+
112
+ def undo_changes(files, from_strictness)
113
+ Sorbet::Sigils.change_sigil_in_files(files, from_strictness)
114
+ end
57
115
  end
58
116
  end
59
117
  end
@@ -11,7 +11,7 @@ module Spoom
11
11
 
12
12
  default_task :show
13
13
 
14
- desc "show", "show Sorbet config"
14
+ desc "show", "Show Sorbet config"
15
15
  def show
16
16
  in_sorbet_project!
17
17
  config = Spoom::Sorbet::Config.parse_file(sorbet_config)
@@ -13,13 +13,16 @@ module Spoom
13
13
 
14
14
  default_task :snapshot
15
15
 
16
- desc "snapshot", "run srb tc and display metrics"
17
- option :save, type: :string, desc: "Save snapshot data as json", lazy_default: DATA_DIR
16
+ desc "snapshot", "Run srb tc and display metrics"
17
+ option :save, type: :string, lazy_default: DATA_DIR, desc: "Save snapshot data as json"
18
+ option :rbi, type: :boolean, default: true, desc: "Exclude RBI files from metrics"
19
+ option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
18
20
  def snapshot
19
21
  in_sorbet_project!
20
-
21
22
  path = exec_path
22
- snapshot = Spoom::Coverage.snapshot(path: path)
23
+ sorbet = options[:sorbet]
24
+
25
+ snapshot = Spoom::Coverage.snapshot(path: path, rbi: options[:rbi], sorbet_bin: sorbet)
23
26
  snapshot.print
24
27
 
25
28
  save_dir = options[:save]
@@ -30,14 +33,16 @@ module Spoom
30
33
  puts "\nSnapshot data saved under #{file}"
31
34
  end
32
35
 
33
- desc "timeline", "replay a project and collect metrics"
34
- option :from, type: :string
35
- option :to, type: :string, default: Time.now.strftime("%F")
36
- option :save, type: :string, desc: "Save snapshot data as json", lazy_default: DATA_DIR
36
+ desc "timeline", "Replay a project and collect metrics"
37
+ option :from, type: :string, desc: "From commit date"
38
+ option :to, type: :string, default: Time.now.strftime("%F"), desc: "To commit date"
39
+ option :save, type: :string, lazy_default: DATA_DIR, desc: "Save snapshot data as json"
37
40
  option :bundle_install, type: :boolean, desc: "Execute `bundle install` before collecting metrics"
41
+ option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
38
42
  def timeline
39
43
  in_sorbet_project!
40
44
  path = exec_path
45
+ sorbet = options[:sorbet]
41
46
 
42
47
  sha_before = Spoom::Git.last_commit(path: path)
43
48
  unless sha_before
@@ -83,10 +88,10 @@ module Spoom
83
88
  if options[:bundle_install]
84
89
  Bundler.with_clean_env do
85
90
  next unless bundle_install(path, sha)
86
- snapshot = Spoom::Coverage.snapshot(path: path)
91
+ snapshot = Spoom::Coverage.snapshot(path: path, sorbet_bin: sorbet)
87
92
  end
88
93
  else
89
- snapshot = Spoom::Coverage.snapshot(path: path)
94
+ snapshot = Spoom::Coverage.snapshot(path: path, sorbet_bin: sorbet)
90
95
  end
91
96
  next unless snapshot
92
97
 
@@ -101,14 +106,20 @@ module Spoom
101
106
  Spoom::Git.checkout(sha_before, path: path)
102
107
  end
103
108
 
104
- desc "report", "produce a typing coverage report"
105
- option :data, type: :string, desc: "Snapshots JSON data", default: DATA_DIR
106
- option :file, type: :string, default: "spoom_report.html", aliases: :f
107
- option :color_ignore, type: :string, default: Spoom::Coverage::D3::COLOR_IGNORE
108
- option :color_false, type: :string, default: Spoom::Coverage::D3::COLOR_FALSE
109
- option :color_true, type: :string, default: Spoom::Coverage::D3::COLOR_TRUE
110
- option :color_strict, type: :string, default: Spoom::Coverage::D3::COLOR_STRICT
111
- option :color_strong, type: :string, default: Spoom::Coverage::D3::COLOR_STRONG
109
+ desc "report", "Produce a typing coverage report"
110
+ option :data, type: :string, default: DATA_DIR, desc: "Snapshots JSON data"
111
+ option :file, type: :string, default: "spoom_report.html", aliases: :f,
112
+ desc: "Save report to file"
113
+ option :color_ignore, type: :string, default: Spoom::Coverage::D3::COLOR_IGNORE,
114
+ desc: "Color used for typed: ignore"
115
+ option :color_false, type: :string, default: Spoom::Coverage::D3::COLOR_FALSE,
116
+ desc: "Color used for typed: false"
117
+ option :color_true, type: :string, default: Spoom::Coverage::D3::COLOR_TRUE,
118
+ desc: "Color used for typed: true"
119
+ option :color_strict, type: :string, default: Spoom::Coverage::D3::COLOR_STRICT,
120
+ desc: "Color used for typed: strict"
121
+ option :color_strong, type: :string, default: Spoom::Coverage::D3::COLOR_STRONG,
122
+ desc: "Color used for typed: strong"
112
123
  def report
113
124
  in_sorbet_project!
114
125
 
@@ -139,7 +150,7 @@ module Spoom
139
150
  puts "\nUse #{colorize('spoom coverage open', :blue)} to open it."
140
151
  end
141
152
 
142
- desc "open", "open the typing coverage report"
153
+ desc "open", "Open the typing coverage report"
143
154
  def open(file = "spoom_report.html")
144
155
  unless File.exist?(file)
145
156
  say_error("No report file to open #{colorize(file, :blue)}")
@@ -1,6 +1,7 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "fileutils"
4
5
  require "pathname"
5
6
  require "stringio"
6
7
 
@@ -54,7 +55,7 @@ module Spoom
54
55
 
55
56
  sig { returns(String) }
56
57
  def sorbet_config
57
- Pathname.new("#{exec_path}/#{Spoom::Config::SORBET_CONFIG}").cleanpath.to_s
58
+ Pathname.new("#{exec_path}/#{Spoom::Sorbet::CONFIG_PATH}").cleanpath.to_s
58
59
  end
59
60
 
60
61
  # Is the `--color` option true?
@@ -12,7 +12,7 @@ module Spoom
12
12
 
13
13
  default_task :show
14
14
 
15
- desc "interactive", "interactive LSP mode"
15
+ desc "interactive", "Interactive LSP mode"
16
16
  def show
17
17
  in_sorbet_project!
18
18
  lsp = lsp_client
@@ -20,7 +20,7 @@ module Spoom
20
20
  puts lsp
21
21
  end
22
22
 
23
- desc "list", "list all known symbols"
23
+ desc "list", "List all known symbols"
24
24
  # TODO: options, filter, limit, kind etc.. filter rbi
25
25
  def list
26
26
  run do |client|
@@ -34,7 +34,7 @@ module Spoom
34
34
  end
35
35
  end
36
36
 
37
- desc "hover", "request hover informations"
37
+ desc "hover", "Request hover informations"
38
38
  # TODO: options, filter, limit, kind etc.. filter rbi
39
39
  def hover(file, line, col)
40
40
  run do |client|
@@ -48,7 +48,7 @@ module Spoom
48
48
  end
49
49
  end
50
50
 
51
- desc "defs", "list definitions of a symbol"
51
+ desc "defs", "List definitions of a symbol"
52
52
  # TODO: options, filter, limit, kind etc.. filter rbi
53
53
  def defs(file, line, col)
54
54
  run do |client|
@@ -58,7 +58,7 @@ module Spoom
58
58
  end
59
59
  end
60
60
 
61
- desc "find", "find symbols matching a query"
61
+ desc "find", "Find symbols matching a query"
62
62
  # TODO: options, filter, limit, kind etc.. filter rbi
63
63
  def find(query)
64
64
  run do |client|
@@ -68,7 +68,7 @@ module Spoom
68
68
  end
69
69
  end
70
70
 
71
- desc "symbols", "list symbols from a file"
71
+ desc "symbols", "List symbols from a file"
72
72
  # TODO: options, filter, limit, kind etc.. filter rbi
73
73
  def symbols(file)
74
74
  run do |client|
@@ -78,7 +78,7 @@ module Spoom
78
78
  end
79
79
  end
80
80
 
81
- desc "refs", "list references to a symbol"
81
+ desc "refs", "List references to a symbol"
82
82
  # TODO: options, filter, limit, kind etc.. filter rbi
83
83
  def refs(file, line, col)
84
84
  run do |client|
@@ -88,7 +88,7 @@ module Spoom
88
88
  end
89
89
  end
90
90
 
91
- desc "sigs", "list signatures for a symbol"
91
+ desc "sigs", "List signatures for a symbol"
92
92
  # TODO: options, filter, limit, kind etc.. filter rbi
93
93
  def sigs(file, line, col)
94
94
  run do |client|
@@ -98,7 +98,7 @@ module Spoom
98
98
  end
99
99
  end
100
100
 
101
- desc "types", "display type of a symbol"
101
+ desc "types", "Display type of a symbol"
102
102
  # TODO: options, filter, limit, kind etc.. filter rbi
103
103
  def types(file, line, col)
104
104
  run do |client|
@@ -113,7 +113,7 @@ module Spoom
113
113
  in_sorbet_project!
114
114
  path = exec_path
115
115
  client = Spoom::LSP::Client.new(
116
- Spoom::Config::SORBET_PATH,
116
+ Spoom::Sorbet::BIN_PATH,
117
117
  "--lsp",
118
118
  "--enable-all-experimental-lsp-features",
119
119
  "--disable-watchman",
@@ -8,59 +8,89 @@ module Spoom
8
8
 
9
9
  default_task :tc
10
10
 
11
- desc "tc", "run srb tc"
12
- option :limit, type: :numeric, aliases: :l
13
- option :code, type: :numeric, aliases: :c
14
- option :sort, type: :string, aliases: :s
15
- def tc
11
+ SORT_CODE = "code"
12
+ SORT_LOC = "loc"
13
+ SORT_ENUM = [SORT_CODE, SORT_LOC]
14
+
15
+ DEFAULT_FORMAT = "%C - %F:%L: %M"
16
+
17
+ desc "tc", "Run `srb tc`"
18
+ option :limit, type: :numeric, aliases: :l, desc: "Limit displayed errors"
19
+ option :code, type: :numeric, aliases: :c, desc: "Filter displayed errors by code"
20
+ option :sort, type: :string, aliases: :s, desc: "Sort errors", enum: SORT_ENUM, lazy_default: SORT_LOC
21
+ option :format, type: :string, aliases: :f, desc: "Format line output"
22
+ option :uniq, type: :boolean, aliases: :u, desc: "Remove duplicated lines"
23
+ option :count, type: :boolean, default: true, desc: "Show errors count"
24
+ option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
25
+ def tc(*arg)
16
26
  in_sorbet_project!
17
27
 
18
28
  path = exec_path
19
29
  limit = options[:limit]
20
30
  sort = options[:sort]
21
31
  code = options[:code]
22
- colors = options[:color]
32
+ uniq = options[:uniq]
33
+ format = options[:format]
34
+ count = options[:count]
35
+ sorbet = options[:sorbet]
23
36
 
24
37
  unless limit || code || sort
25
- return Spoom::Sorbet.srb_tc(path: path, capture_err: false).last
38
+ output, status = T.unsafe(Spoom::Sorbet).srb_tc(*arg, path: path, capture_err: false, sorbet_bin: sorbet)
39
+ $stderr.print(output)
40
+ exit(status)
26
41
  end
27
42
 
28
- output, status = Spoom::Sorbet.srb_tc(path: path, capture_err: true)
43
+ output, status = T.unsafe(Spoom::Sorbet).srb_tc(*arg, path: path, capture_err: true, sorbet_bin: sorbet)
29
44
  if status
30
45
  $stderr.print(output)
31
- return 0
46
+ exit(0)
32
47
  end
33
48
 
34
49
  errors = Spoom::Sorbet::Errors::Parser.parse_string(output)
35
50
  errors_count = errors.size
36
51
 
37
- errors = sort == "code" ? errors.sort_by { |e| [e.code, e.file, e.line, e.message] } : errors.sort
52
+ errors = case sort
53
+ when SORT_CODE
54
+ Spoom::Sorbet::Errors.sort_errors_by_code(errors)
55
+ when SORT_LOC
56
+ errors.sort
57
+ else
58
+ errors # preserve natural sort
59
+ end
60
+
38
61
  errors = errors.select { |e| e.code == code } if code
39
62
  errors = T.must(errors.slice(0, limit)) if limit
40
63
 
41
- errors.each do |e|
42
- code = colorize_code(e.code, colors)
43
- message = colorize_message(e.message, colors)
44
- $stderr.puts "#{code} - #{e.file}:#{e.line}: #{message}"
64
+ lines = errors.map { |e| format_error(e, format || DEFAULT_FORMAT) }
65
+ lines = lines.uniq if uniq
66
+
67
+ lines.each do |line|
68
+ $stderr.puts line
45
69
  end
46
70
 
47
- if errors_count == errors.size
48
- $stderr.puts "Errors: #{errors_count}"
49
- else
50
- $stderr.puts "Errors: #{errors.size} shown, #{errors_count} total"
71
+ if count
72
+ if errors_count == errors.size
73
+ $stderr.puts "Errors: #{errors_count}"
74
+ else
75
+ $stderr.puts "Errors: #{errors.size} shown, #{errors_count} total"
76
+ end
51
77
  end
52
78
 
53
- 1
79
+ exit(1)
54
80
  end
55
81
 
56
82
  no_commands do
57
- def colorize_code(code, colors = true)
58
- return code.to_s unless colors
59
- code.to_s.light_black
83
+ def format_error(error, format)
84
+ line = format
85
+ line = line.gsub(/%C/, colorize(error.code.to_s, :yellow))
86
+ line = line.gsub(/%F/, error.file)
87
+ line = line.gsub(/%L/, error.line.to_s)
88
+ line = line.gsub(/%M/, colorize_message(error.message))
89
+ line
60
90
  end
61
91
 
62
- def colorize_message(message, colors = true)
63
- return message unless colors
92
+ def colorize_message(message)
93
+ return message unless color?
64
94
 
65
95
  cyan = T.let(false, T::Boolean)
66
96
  word = StringIO.new
@@ -11,10 +11,23 @@ module Spoom
11
11
  module Coverage
12
12
  extend T::Sig
13
13
 
14
- sig { params(path: String).returns(Snapshot) }
15
- def self.snapshot(path: '.')
14
+ sig { params(path: String, rbi: T::Boolean, sorbet_bin: T.nilable(String)).returns(Snapshot) }
15
+ def self.snapshot(path: '.', rbi: true, sorbet_bin: nil)
16
+ config = sorbet_config(path: path)
17
+ config.allowed_extensions.push(".rb", ".rbi") if config.allowed_extensions.empty?
18
+
19
+ new_config = config.copy
20
+ new_config.allowed_extensions.reject! { |ext| !rbi && ext == ".rbi" }
21
+
22
+ metrics = Spoom::Sorbet.srb_metrics(
23
+ "--no-config",
24
+ new_config.options_string,
25
+ path: path,
26
+ capture_err: true,
27
+ sorbet_bin: sorbet_bin
28
+ )
29
+
16
30
  snapshot = Snapshot.new
17
- metrics = Spoom::Sorbet.srb_metrics(path: path, capture_err: true)
18
31
  return snapshot unless metrics
19
32
 
20
33
  sha = Spoom::Git.last_commit(path: path)
@@ -59,11 +72,14 @@ module Spoom
59
72
  )
60
73
  end
61
74
 
75
+ sig { params(path: String).returns(Sorbet::Config) }
76
+ def self.sorbet_config(path: ".")
77
+ Sorbet::Config.parse_file("#{path}/#{Spoom::Sorbet::CONFIG_PATH}")
78
+ end
79
+
62
80
  sig { params(path: String).returns(FileTree) }
63
81
  def self.sigils_tree(path: ".")
64
- config_file = "#{path}/#{Spoom::Config::SORBET_CONFIG}"
65
- return FileTree.new unless File.exist?(config_file)
66
- config = Sorbet::Config.parse_file(config_file)
82
+ config = sorbet_config(path: path)
67
83
  files = Sorbet.srb_files(config, path: path)
68
84
  files.select! { |file| file =~ /\.rb$/ }
69
85
  files.reject! { |file| file =~ %r{/test/} }
@@ -41,7 +41,7 @@ module Spoom
41
41
 
42
42
  abstract!
43
43
 
44
- TEMPLATE = T.let("#{Spoom::Config::SPOOM_PATH}/templates/page.erb", String)
44
+ TEMPLATE = T.let("#{Spoom::SPOOM_PATH}/templates/page.erb", String)
45
45
 
46
46
  sig { returns(String) }
47
47
  attr_reader :title
@@ -89,7 +89,7 @@ module Spoom
89
89
  class Card < Template
90
90
  extend T::Sig
91
91
 
92
- TEMPLATE = T.let("#{Spoom::Config::SPOOM_PATH}/templates/card.erb", String)
92
+ TEMPLATE = T.let("#{Spoom::SPOOM_PATH}/templates/card.erb", String)
93
93
 
94
94
  sig { returns(T.nilable(String)) }
95
95
  attr_reader :title, :body
@@ -123,7 +123,7 @@ module Spoom
123
123
  class Snapshot < Card
124
124
  extend T::Sig
125
125
 
126
- TEMPLATE = T.let("#{Spoom::Config::SPOOM_PATH}/templates/card_snapshot.erb", String)
126
+ TEMPLATE = T.let("#{Spoom::SPOOM_PATH}/templates/card_snapshot.erb", String)
127
127
 
128
128
  sig { returns(Coverage::Snapshot) }
129
129
  attr_reader :snapshot
@@ -80,7 +80,7 @@ module Spoom
80
80
 
81
81
  private
82
82
 
83
- sig { params(node: FileTree::Node, collected_nodes: T::Array[Node]).returns(T::Array[String]) }
83
+ sig { params(node: FileTree::Node, collected_nodes: T::Array[Node]).returns(T::Array[Node]) }
84
84
  def collect_nodes(node, collected_nodes = [])
85
85
  collected_nodes << node
86
86
  node.children.values.each { |child| collect_nodes(child, collected_nodes) }
@@ -11,73 +11,111 @@ require "open3"
11
11
 
12
12
  module Spoom
13
13
  module Sorbet
14
- extend T::Sig
14
+ CONFIG_PATH = "sorbet/config"
15
+ GEM_PATH = Gem::Specification.find_by_name("sorbet-static").full_gem_path
16
+ BIN_PATH = (Pathname.new(GEM_PATH) / "libexec" / "sorbet").to_s
15
17
 
16
- sig { params(arg: String, path: String, capture_err: T::Boolean).returns([String, T::Boolean]) }
17
- def self.srb(*arg, path: '.', capture_err: false)
18
- opts = {}
19
- opts[:chdir] = path
20
- out = T.let("", T.nilable(String))
21
- res = T.let(false, T::Boolean)
22
- if capture_err
23
- Open3.popen2e(["bundle", "exec", "srb", *arg].join(" "), opts) do |_, o, t|
24
- out = o.read
25
- res = T.cast(t.value, Process::Status).success?
26
- end
27
- else
28
- Open3.popen2(["bundle", "exec", "srb", *arg].join(" "), opts) do |_, o, t|
29
- out = o.read
30
- res = T.cast(t.value, Process::Status).success?
18
+ class << self
19
+ extend T::Sig
20
+
21
+ sig do
22
+ params(
23
+ arg: String,
24
+ path: String,
25
+ capture_err: T::Boolean,
26
+ sorbet_bin: T.nilable(String)
27
+ ).returns([String, T::Boolean])
28
+ end
29
+ def srb(*arg, path: '.', capture_err: false, sorbet_bin: nil)
30
+ if sorbet_bin
31
+ arg.prepend(sorbet_bin)
32
+ else
33
+ arg.prepend("bundle", "exec", "srb")
31
34
  end
35
+ T.unsafe(Spoom).exec(*arg, path: path, capture_err: capture_err)
32
36
  end
33
- [out || "", res]
34
- end
35
37
 
36
- sig { params(arg: String, path: String, capture_err: T::Boolean).returns([String, T::Boolean]) }
37
- def self.srb_tc(*arg, path: '.', capture_err: false)
38
- srb(*T.unsafe(["tc", *arg]), path: path, capture_err: capture_err)
39
- end
38
+ sig do
39
+ params(
40
+ arg: String,
41
+ path: String,
42
+ capture_err: T::Boolean,
43
+ sorbet_bin: T.nilable(String)
44
+ ).returns([String, T::Boolean])
45
+ end
46
+ def srb_tc(*arg, path: '.', capture_err: false, sorbet_bin: nil)
47
+ arg.prepend("tc") unless sorbet_bin
48
+ T.unsafe(self).srb(*arg, path: path, capture_err: capture_err, sorbet_bin: sorbet_bin)
49
+ end
40
50
 
41
- # List all files typechecked by Sorbet from its `config`
42
- sig { params(config: Config, path: String).returns(T::Array[String]) }
43
- def self.srb_files(config, path: '.')
44
- regs = config.ignore.map { |string| Regexp.new(Regexp.escape(string)) }
45
- exts = config.allowed_extensions.empty? ? ['.rb', '.rbi'] : config.allowed_extensions
46
- Dir.glob((Pathname.new(path) / "**/*{#{exts.join(',')}}").to_s).reject do |f|
47
- regs.any? { |re| re.match?(f) }
48
- end.sort
49
- end
51
+ # List all files typechecked by Sorbet from its `config`
52
+ sig { params(config: Config, path: String).returns(T::Array[String]) }
53
+ def srb_files(config, path: '.')
54
+ regs = config.ignore.map { |string| Regexp.new(Regexp.escape(string)) }
55
+ exts = config.allowed_extensions.empty? ? ['.rb', '.rbi'] : config.allowed_extensions
56
+ Dir.glob((Pathname.new(path) / "**/*{#{exts.join(',')}}").to_s).reject do |f|
57
+ regs.any? { |re| re.match?(f) }
58
+ end.sort
59
+ end
50
60
 
51
- sig { params(arg: String, path: String, capture_err: T::Boolean).returns(T.nilable(String)) }
52
- def self.srb_version(*arg, path: '.', capture_err: false)
53
- out, res = srb(*T.unsafe(["--version", *arg]), path: path, capture_err: capture_err)
54
- return nil unless res
55
- out.split(" ")[2]
56
- end
61
+ sig do
62
+ params(
63
+ arg: String,
64
+ path: String,
65
+ capture_err: T::Boolean,
66
+ sorbet_bin: T.nilable(String)
67
+ ).returns(T.nilable(String))
68
+ end
69
+ def srb_version(*arg, path: '.', capture_err: false, sorbet_bin: nil)
70
+ out, res = T.unsafe(self).srb_tc(
71
+ "--version",
72
+ *arg,
73
+ path: path,
74
+ capture_err: capture_err,
75
+ sorbet_bin: sorbet_bin
76
+ )
77
+ return nil unless res
78
+ out.split(" ")[2]
79
+ end
57
80
 
58
- sig { params(arg: String, path: String, capture_err: T::Boolean).returns(T.nilable(T::Hash[String, Integer])) }
59
- def self.srb_metrics(*arg, path: '.', capture_err: false)
60
- metrics_file = "metrics.tmp"
61
- metrics_path = "#{path}/#{metrics_file}"
62
- srb_tc(*T.unsafe(["--metrics-file=#{metrics_file}", *arg]), path: path, capture_err: capture_err)
63
- if File.exist?(metrics_path)
64
- metrics = Spoom::Sorbet::MetricsParser.parse_file(metrics_path)
65
- File.delete(metrics_path)
66
- return metrics
81
+ sig do
82
+ params(
83
+ arg: String,
84
+ path: String,
85
+ capture_err: T::Boolean,
86
+ sorbet_bin: T.nilable(String)
87
+ ).returns(T.nilable(T::Hash[String, Integer]))
88
+ end
89
+ def srb_metrics(*arg, path: '.', capture_err: false, sorbet_bin: nil)
90
+ metrics_file = "metrics.tmp"
91
+ metrics_path = "#{path}/#{metrics_file}"
92
+ T.unsafe(self).srb_tc(
93
+ "--metrics-file",
94
+ metrics_file,
95
+ *arg,
96
+ path: path,
97
+ capture_err: capture_err,
98
+ sorbet_bin: sorbet_bin
99
+ )
100
+ if File.exist?(metrics_path)
101
+ metrics = Spoom::Sorbet::MetricsParser.parse_file(metrics_path)
102
+ File.delete(metrics_path)
103
+ return metrics
104
+ end
105
+ nil
67
106
  end
68
- nil
69
- end
70
107
 
71
- # Get `gem` version from the `Gemfile.lock` content
72
- #
73
- # Returns `nil` if `gem` cannot be found in the Gemfile.
74
- sig { params(gem: String, path: String).returns(T.nilable(String)) }
75
- def self.version_from_gemfile_lock(gem: 'sorbet', path: '.')
76
- gemfile_path = "#{path}/Gemfile.lock"
77
- return nil unless File.exist?(gemfile_path)
78
- content = File.read(gemfile_path).match(/^ #{gem} \(.*(\d+\.\d+\.\d+).*\)/)
79
- return nil unless content
80
- content[1]
108
+ # Get `gem` version from the `Gemfile.lock` content
109
+ #
110
+ # Returns `nil` if `gem` cannot be found in the Gemfile.
111
+ sig { params(gem: String, path: String).returns(T.nilable(String)) }
112
+ def version_from_gemfile_lock(gem: 'sorbet', path: '.')
113
+ gemfile_path = "#{path}/Gemfile.lock"
114
+ return nil unless File.exist?(gemfile_path)
115
+ content = File.read(gemfile_path).match(/^ #{gem} \(.*(\d+\.\d+\.\d+).*\)/)
116
+ return nil unless content
117
+ content[1]
118
+ end
81
119
  end
82
120
  end
83
121
  end
@@ -36,6 +36,36 @@ module Spoom
36
36
  @allowed_extensions = T.let([], T::Array[String])
37
37
  end
38
38
 
39
+ sig { returns(Config) }
40
+ def copy
41
+ new_config = Sorbet::Config.new
42
+ new_config.paths.concat(@paths)
43
+ new_config.ignore.concat(@ignore)
44
+ new_config.allowed_extensions.concat(@allowed_extensions)
45
+ new_config
46
+ end
47
+
48
+ # Returns self as a string of options that can be passed to Sorbet
49
+ #
50
+ # Example:
51
+ # ~~~rb
52
+ # config = Sorbet::Config.new
53
+ # config.paths << "/foo"
54
+ # config.paths << "/bar"
55
+ # config.ignore << "/baz"
56
+ # config.allowed_extensions << ".rb"
57
+ #
58
+ # puts config.options_string # "/foo /bar --ignore /baz --allowed-extension .rb"
59
+ # ~~~
60
+ sig { returns(String) }
61
+ def options_string
62
+ opts = []
63
+ opts.concat(paths)
64
+ opts.concat(ignore.map { |p| "--ignore #{p}" })
65
+ opts.concat(allowed_extensions.map { |ext| "--allowed-extension #{ext}" })
66
+ opts.join(" ")
67
+ end
68
+
39
69
  class << self
40
70
  extend T::Sig
41
71
 
@@ -4,6 +4,8 @@
4
4
  module Spoom
5
5
  module Sorbet
6
6
  module Errors
7
+ extend T::Sig
8
+
7
9
  # Parse errors from Sorbet output
8
10
  class Parser
9
11
  extend T::Sig
@@ -123,6 +125,7 @@ module Spoom
123
125
  @more = more
124
126
  end
125
127
 
128
+ # By default errors are sorted by location
126
129
  sig { params(other: T.untyped).returns(Integer) }
127
130
  def <=>(other)
128
131
  return 0 unless other.is_a?(Error)
@@ -134,6 +137,11 @@ module Spoom
134
137
  "#{file}:#{line}: #{message} (#{code})"
135
138
  end
136
139
  end
140
+
141
+ sig { params(errors: T::Array[Error]).returns(T::Array[Error]) }
142
+ def self.sort_errors_by_code(errors)
143
+ errors.sort_by { |e| [e.code, e.file, e.line, e.message] }
144
+ end
137
145
  end
138
146
  end
139
147
  end
@@ -11,13 +11,9 @@ require_relative 'lsp/errors'
11
11
  module Spoom
12
12
  module LSP
13
13
  class Client
14
- def initialize(sorbet_cmd, *sorbet_args, path: ".")
14
+ def initialize(sorbet_bin, *sorbet_args, path: ".")
15
15
  @id = 0
16
- Bundler.with_clean_env do
17
- opts = {}
18
- opts[:chdir] = path
19
- @in, @out, @err, @status = Open3.popen3([sorbet_cmd, *sorbet_args].join(" "), opts)
20
- end
16
+ @in, @out, @err, @status = T.unsafe(Open3).popen3(sorbet_bin, *sorbet_args, chdir: path)
21
17
  end
22
18
 
23
19
  def next_id
@@ -56,14 +56,14 @@ module Spoom
56
56
  sig { params(path: T.any(String, Pathname)).returns(T.nilable(String)) }
57
57
  def self.file_strictness(path)
58
58
  return nil unless File.exist?(path)
59
- content = File.read(path)
59
+ content = File.read(path, encoding: Encoding::ASCII_8BIT)
60
60
  strictness_in_content(content)
61
61
  end
62
62
 
63
63
  # changes the sigil in the file at the passed path to the specified new strictness
64
64
  sig { params(path: T.any(String, Pathname), new_strictness: String).returns(T::Boolean) }
65
65
  def self.change_sigil_in_file(path, new_strictness)
66
- content = File.read(path)
66
+ content = File.read(path, encoding: Encoding::ASCII_8BIT)
67
67
  new_content = update_sigil(content, new_strictness)
68
68
 
69
69
  File.write(path, new_content)
@@ -87,7 +87,7 @@ module Spoom
87
87
  extension: String
88
88
  ).returns(T::Array[String])
89
89
  end
90
- def self.files_with_sigil_strictness(directory, strictness, extension = ".rb")
90
+ def self.files_with_sigil_strictness(directory, strictness, extension: ".rb")
91
91
  paths = Dir.glob("#{File.expand_path(directory)}/**/*#{extension}").sort.uniq
92
92
  paths.filter do |path|
93
93
  file_strictness(path) == strictness
@@ -1,6 +1,6 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Spoom
5
- VERSION = "1.0.7"
5
+ VERSION = "1.0.8"
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spoom
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.7
4
+ version: 1.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexandre Terrasa
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-11-30 00:00:00.000000000 Z
11
+ date: 2021-01-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -128,7 +128,6 @@ files:
128
128
  - lib/spoom/cli/helper.rb
129
129
  - lib/spoom/cli/lsp.rb
130
130
  - lib/spoom/cli/run.rb
131
- - lib/spoom/config.rb
132
131
  - lib/spoom/coverage.rb
133
132
  - lib/spoom/coverage/d3.rb
134
133
  - lib/spoom/coverage/d3/base.rb
@@ -1,13 +0,0 @@
1
- # typed: true
2
- # frozen_string_literal: true
3
-
4
- require 'pathname'
5
-
6
- module Spoom
7
- module Config
8
- SORBET_CONFIG = "sorbet/config"
9
- SORBET_GEM_PATH = Gem::Specification.find_by_name("sorbet-static").full_gem_path
10
- SORBET_PATH = (Pathname.new(SORBET_GEM_PATH) / "libexec" / "sorbet").to_s
11
- SPOOM_PATH = (Pathname.new(__FILE__) / ".." / ".." / "..").to_s
12
- end
13
- end