spoom 1.0.5 → 1.1.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.
@@ -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) }
data/lib/spoom/git.rb CHANGED
@@ -14,11 +14,12 @@ module Spoom
14
14
  return "", "Error: `#{path}` is not a directory.", false unless File.directory?(path)
15
15
  opts = {}
16
16
  opts[:chdir] = path
17
- _, o, e, s = Open3.popen3(*T.unsafe([command, *T.unsafe(arg), opts]))
17
+ i, o, e, s = Open3.popen3(*T.unsafe([command, *T.unsafe(arg), opts]))
18
18
  out = o.read.to_s
19
19
  o.close
20
20
  err = e.read.to_s
21
21
  e.close
22
+ i.close
22
23
  [out, err, T.cast(s.value, Process::Status).success?]
23
24
  end
24
25
 
data/lib/spoom/printer.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "colorize"
5
4
  require "stringio"
6
5
 
7
6
  module Spoom
data/lib/spoom/sorbet.rb CHANGED
@@ -11,73 +11,112 @@ 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
+ "--no-config",
72
+ "--version",
73
+ *arg,
74
+ path: path,
75
+ capture_err: capture_err,
76
+ sorbet_bin: sorbet_bin
77
+ )
78
+ return nil unless res
79
+ out.split(" ")[2]
80
+ end
57
81
 
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
82
+ sig do
83
+ params(
84
+ arg: String,
85
+ path: String,
86
+ capture_err: T::Boolean,
87
+ sorbet_bin: T.nilable(String)
88
+ ).returns(T.nilable(T::Hash[String, Integer]))
89
+ end
90
+ def srb_metrics(*arg, path: '.', capture_err: false, sorbet_bin: nil)
91
+ metrics_file = "metrics.tmp"
92
+ metrics_path = "#{path}/#{metrics_file}"
93
+ T.unsafe(self).srb_tc(
94
+ "--metrics-file",
95
+ metrics_file,
96
+ *arg,
97
+ path: path,
98
+ capture_err: capture_err,
99
+ sorbet_bin: sorbet_bin
100
+ )
101
+ if File.exist?(metrics_path)
102
+ metrics = Spoom::Sorbet::MetricsParser.parse_file(metrics_path)
103
+ File.delete(metrics_path)
104
+ return metrics
105
+ end
106
+ nil
67
107
  end
68
- nil
69
- end
70
108
 
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]
109
+ # Get `gem` version from the `Gemfile.lock` content
110
+ #
111
+ # Returns `nil` if `gem` cannot be found in the Gemfile.
112
+ sig { params(gem: String, path: String).returns(T.nilable(String)) }
113
+ def version_from_gemfile_lock(gem: 'sorbet', path: '.')
114
+ gemfile_path = "#{path}/Gemfile.lock"
115
+ return nil unless File.exist?(gemfile_path)
116
+ content = File.read(gemfile_path).match(/^ #{gem} \(.*(\d+\.\d+\.\d+).*\)/)
117
+ return nil unless content
118
+ content[1]
119
+ end
81
120
  end
82
121
  end
83
122
  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
@@ -76,6 +76,15 @@ module Spoom
76
76
  Spoom::Git.exec("GIT_COMMITTER_DATE=\"#{date}\" git commit -m '#{message}' --date '#{date}'", path: path)
77
77
  end
78
78
 
79
+ # Run `bundle install` in this project
80
+ sig { returns([T.nilable(String), T.nilable(String), T::Boolean]) }
81
+ def bundle_install
82
+ opts = {}
83
+ opts[:chdir] = path
84
+ out, err, status = Open3.capture3("bundle", "install", opts)
85
+ [out, err, status.success?]
86
+ end
87
+
79
88
  # Run a command with `bundle exec` in this project
80
89
  sig { params(cmd: String, args: String).returns([T.nilable(String), T.nilable(String), T::Boolean]) }
81
90
  def bundle_exec(cmd, *args)
data/lib/spoom/version.rb CHANGED
@@ -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.5"
5
+ VERSION = "1.1.0"
6
6
  end
@@ -0,0 +1,8 @@
1
+ <div class="card">
2
+ <% if title %>
3
+ <h5 class="card-header"><%= title %></h5>
4
+ <% end %>
5
+ <div class="card-body">
6
+ <%= body %>
7
+ </div>
8
+ </div>
@@ -0,0 +1,22 @@
1
+ <div class="card">
2
+ <% if title %>
3
+ <h5 class="card-header"><%= title %></h5>
4
+ <% end %>
5
+ <div class="card-body">
6
+ <div class="container-fluid">
7
+ <div class="row justify-content-md-center">
8
+ <div class="col-12 col-sm-4 col-xl-3">
9
+ <%= pie_sigils.html %>
10
+ </div>
11
+ <div class="d-none d-xl-block col-xl-1"></div>
12
+ <div class="col-12 col-sm-4 col-xl-3">
13
+ <%= pie_calls.html %>
14
+ </div>
15
+ <div class="d-none d-xl-block col-xl-1"></div>
16
+ <div class="col-12 col-sm-4 col-xl-3">
17
+ <%= pie_sigs.html %>
18
+ </div>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ </div>
@@ -0,0 +1,50 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta http-equiv="x-ua-compatible" content="ie=edge" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+
8
+ <title><%= title %></title>
9
+ <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
10
+
11
+ <style>
12
+ .card {
13
+ margin: 30px 0 0 0;
14
+ }
15
+
16
+ .footer {
17
+ color: #6c757d;
18
+ text-align: center;
19
+ margin: 30px 0 0 0;
20
+ }
21
+
22
+ <%= header_style %>
23
+ </style>
24
+ </head>
25
+ <body>
26
+ <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
27
+ <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"></script>
28
+ <script src="https://d3js.org/d3.v4.min.js"></script>
29
+
30
+ <script>
31
+ <%= header_script %>
32
+ </script>
33
+
34
+ <div class="px-5 py-4 container-fluid">
35
+ <div class="row justify-content-center">
36
+ <div class="col-xs-12 col-md-12 col-lg-9 col-xl-8">
37
+ <div class="header">
38
+ <%= header_html %>
39
+ </div>
40
+ <div class="body">
41
+ <%= body_html %>
42
+ </div>
43
+ <div class="footer">
44
+ <%= footer_html %>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ </div>
49
+ </body>
50
+ </html>
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.5
4
+ version: 1.1.0
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-19 00:00:00.000000000 Z
11
+ date: 2021-03-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -70,16 +70,16 @@ dependencies:
70
70
  name: sorbet
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - "~>"
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
- version: 0.5.5
75
+ version: 0.5.6347
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - "~>"
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
- version: 0.5.5
82
+ version: 0.5.6347
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: thor
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -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
@@ -152,6 +151,9 @@ files:
152
151
  - lib/spoom/test_helpers/project.rb
153
152
  - lib/spoom/timeline.rb
154
153
  - lib/spoom/version.rb
154
+ - templates/card.erb
155
+ - templates/card_snapshot.erb
156
+ - templates/page.erb
155
157
  homepage: https://github.com/Shopify/spoom
156
158
  licenses:
157
159
  - MIT