spoom 1.2.1 → 1.2.3

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.
@@ -0,0 +1,64 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Deadcode
6
+ module Plugins
7
+ class Ruby < Base
8
+ extend T::Sig
9
+
10
+ ignore_method_names(
11
+ "==",
12
+ "extended",
13
+ "included",
14
+ "inherited",
15
+ "initialize",
16
+ "method_added",
17
+ "method_missing",
18
+ "prepended",
19
+ "respond_to_missing?",
20
+ "to_s",
21
+ )
22
+
23
+ sig { override.params(indexer: Indexer, send: Send).void }
24
+ def on_send(indexer, send)
25
+ case send.name
26
+ when "const_defined?", "const_get", "const_source_location"
27
+ reference_symbol_as_constant(indexer, send, T.must(send.args.first))
28
+ when "send", "__send__", "try"
29
+ reference_send_first_symbol_as_method(indexer, send)
30
+ when "alias_method"
31
+ last_arg = send.args.last
32
+
33
+ name = case last_arg
34
+ when SyntaxTree::SymbolLiteral
35
+ indexer.node_string(last_arg.value)
36
+ when SyntaxTree::StringLiteral
37
+ last_arg.parts.map { |part| indexer.node_string(part) }.join
38
+ end
39
+
40
+ return unless name
41
+
42
+ indexer.reference_method(name, send.node)
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ sig { params(indexer: Indexer, send: Send, node: SyntaxTree::Node).void }
49
+ def reference_symbol_as_constant(indexer, send, node)
50
+ case node
51
+ when SyntaxTree::SymbolLiteral
52
+ name = indexer.node_string(node.value)
53
+ indexer.reference_constant(name, send.node)
54
+ when SyntaxTree::StringLiteral
55
+ string = T.must(indexer.node_string(node)[1..-2])
56
+ string.split("::").each do |name|
57
+ indexer.reference_constant(name, send.node) unless name.empty?
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,5 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "plugins/base"
5
+ require_relative "plugins/ruby"
@@ -0,0 +1,34 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Deadcode
6
+ # A reference is a call to a method or a constant
7
+ class Reference < T::Struct
8
+ extend T::Sig
9
+
10
+ class Kind < T::Enum
11
+ enums do
12
+ Constant = new
13
+ Method = new
14
+ end
15
+ end
16
+
17
+ const :kind, Kind
18
+ const :name, String
19
+ const :location, Location
20
+
21
+ # Kind
22
+
23
+ sig { returns(T::Boolean) }
24
+ def constant?
25
+ kind == Kind::Constant
26
+ end
27
+
28
+ sig { returns(T::Boolean) }
29
+ def method?
30
+ kind == Kind::Method
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,18 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Deadcode
6
+ # An abstraction to simplify handling of SyntaxTree::CallNode, SyntaxTree::Command, SyntaxTree::CommandCall and
7
+ # SyntaxTree::VCall nodes.
8
+ class Send < T::Struct
9
+ extend T::Sig
10
+
11
+ const :node, SyntaxTree::Node
12
+ const :name, String
13
+ const :recv, T.nilable(SyntaxTree::Node), default: nil
14
+ const :args, T::Array[SyntaxTree::Node], default: []
15
+ const :block, T.nilable(SyntaxTree::Node), default: nil
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,56 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "erubi"
5
+ require "syntax_tree"
6
+
7
+ require_relative "deadcode/erb"
8
+ require_relative "deadcode/index"
9
+ require_relative "deadcode/indexer"
10
+
11
+ require_relative "deadcode/location"
12
+ require_relative "deadcode/definition"
13
+ require_relative "deadcode/reference"
14
+ require_relative "deadcode/send"
15
+ require_relative "deadcode/plugins"
16
+
17
+ module Spoom
18
+ module Deadcode
19
+ class Error < Spoom::Error
20
+ extend T::Sig
21
+ extend T::Helpers
22
+
23
+ abstract!
24
+
25
+ sig { params(message: String, parent: Exception).void }
26
+ def initialize(message, parent:)
27
+ super(message)
28
+ set_backtrace(parent.backtrace)
29
+ end
30
+ end
31
+
32
+ class ParserError < Error; end
33
+ class IndexerError < Error; end
34
+
35
+ class << self
36
+ extend T::Sig
37
+
38
+ sig { params(index: Index, ruby: String, file: String, plugins: T::Array[Deadcode::Plugins::Base]).void }
39
+ def index_ruby(index, ruby, file:, plugins: [])
40
+ node = SyntaxTree.parse(ruby)
41
+ visitor = Spoom::Deadcode::Indexer.new(file, ruby, index, plugins: plugins)
42
+ visitor.visit(node)
43
+ rescue SyntaxTree::Parser::ParseError => e
44
+ raise ParserError.new("Error while parsing #{file} (#{e.message} at #{e.lineno}:#{e.column})", parent: e)
45
+ rescue => e
46
+ raise IndexerError.new("Error while indexing #{file} (#{e.message})", parent: e)
47
+ end
48
+
49
+ sig { params(index: Index, erb: String, file: String, plugins: T::Array[Deadcode::Plugins::Base]).void }
50
+ def index_erb(index, erb, file:, plugins: [])
51
+ ruby = ERB.new(erb).src
52
+ index_ruby(index, ruby, file: file, plugins: plugins)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -12,15 +12,21 @@ module Spoom
12
12
  #
13
13
  # If `allow_extensions` is empty, all files are collected.
14
14
  # If `allow_extensions` is an array of extensions, only files with one of these extensions are collected.
15
+ #
16
+ # If `allow_mime_types` is empty, all files are collected.
17
+ # If `allow_mime_types` is an array of mimetypes, files without an extension are collected if their mimetype is in
18
+ # the list.
15
19
  sig do
16
20
  params(
17
21
  allow_extensions: T::Array[String],
22
+ allow_mime_types: T::Array[String],
18
23
  exclude_patterns: T::Array[String],
19
24
  ).void
20
25
  end
21
- def initialize(allow_extensions: [], exclude_patterns: [])
26
+ def initialize(allow_extensions: [], allow_mime_types: [], exclude_patterns: [])
22
27
  @files = T.let([], T::Array[String])
23
28
  @allow_extensions = allow_extensions
29
+ @allow_mime_types = allow_mime_types
24
30
  @exclude_patterns = exclude_patterns
25
31
  end
26
32
 
@@ -68,12 +74,29 @@ module Spoom
68
74
  return false if @allow_extensions.empty?
69
75
 
70
76
  extension = File.extname(path)
71
- @allow_extensions.none? { |allowed| extension == allowed }
77
+ if extension.empty?
78
+ return true if @allow_mime_types.empty?
79
+
80
+ mime = mime_type_for(path)
81
+ @allow_mime_types.none? { |allowed| mime == allowed }
82
+ else
83
+ @allow_extensions.none? { |allowed| extension == allowed }
84
+ end
72
85
  end
73
86
 
74
87
  sig { params(path: String).returns(T::Boolean) }
75
88
  def excluded_path?(path)
76
- @exclude_patterns.any? { |pattern| File.fnmatch?(pattern, path) }
89
+ @exclude_patterns.any? do |pattern|
90
+ # Use `FNM_PATHNAME` so patterns do not match directory separators
91
+ # Use `FNM_EXTGLOB` to allow file globbing through `{a,b}`
92
+ File.fnmatch?(pattern, path, File::FNM_PATHNAME | File::FNM_EXTGLOB)
93
+ end
94
+ end
95
+
96
+ sig { params(path: String).returns(T.nilable(String)) }
97
+ def mime_type_for(path)
98
+ # The `file` command appears to be hanging on MacOS for some files so we timeout after 1s.
99
+ %x{timeout 1s file --mime-type -b '#{path}'}.split("; ").first&.strip
77
100
  end
78
101
  end
79
102
  end
data/lib/spoom/printer.rb CHANGED
@@ -10,8 +10,6 @@ module Spoom
10
10
 
11
11
  include Colorize
12
12
 
13
- abstract!
14
-
15
13
  sig { returns(T.any(IO, StringIO)) }
16
14
  attr_accessor :out
17
15
 
@@ -12,9 +12,6 @@ module Spoom
12
12
  class Message
13
13
  extend T::Sig
14
14
 
15
- sig { returns(String) }
16
- attr_reader :jsonrpc
17
-
18
15
  sig { void }
19
16
  def initialize
20
17
  @jsonrpc = T.let("2.0", String)
@@ -43,9 +40,6 @@ module Spoom
43
40
  sig { returns(Integer) }
44
41
  attr_reader :id
45
42
 
46
- sig { returns(String) }
47
- attr_reader :method
48
-
49
43
  sig { returns(T::Hash[T.untyped, T.untyped]) }
50
44
  attr_reader :params
51
45
 
@@ -310,7 +310,7 @@ module Spoom
310
310
  extend T::Sig
311
311
 
312
312
  sig { returns(T::Set[Integer]) }
313
- attr_accessor :seen
313
+ attr_reader :seen
314
314
 
315
315
  sig { returns(T.nilable(String)) }
316
316
  attr_accessor :prefix
@@ -53,7 +53,7 @@ module Spoom
53
53
  sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
54
54
  def read
55
55
  raw_string = read_raw
56
- return nil unless raw_string
56
+ return unless raw_string
57
57
 
58
58
  json = JSON.parse(raw_string)
59
59
 
@@ -101,7 +101,7 @@ module Spoom
101
101
  },
102
102
  ))
103
103
 
104
- return nil unless json && json["result"]
104
+ return unless json && json["result"]
105
105
 
106
106
  Hover.from_json(json["result"])
107
107
  end
@@ -28,7 +28,7 @@ module Spoom
28
28
  T::Array[String],
29
29
  )
30
30
 
31
- SIGIL_REGEXP = T.let(/^#[\ t]*typed[\ t]*:[ \t]*(\w*)[ \t]*/.freeze, Regexp)
31
+ SIGIL_REGEXP = T.let(/^#[[:blank:]]*typed:[[:blank:]]*(\S*)/, Regexp)
32
32
 
33
33
  class << self
34
34
  extend T::Sig
@@ -61,7 +61,7 @@ module Spoom
61
61
  # * returns nil if no sigil
62
62
  sig { params(path: T.any(String, Pathname)).returns(T.nilable(String)) }
63
63
  def file_strictness(path)
64
- return nil unless File.file?(path)
64
+ return unless File.file?(path)
65
65
 
66
66
  content = File.read(path, encoding: Encoding::ASCII_8BIT)
67
67
  strictness_in_content(content)
data/lib/spoom/sorbet.rb CHANGED
@@ -35,6 +35,7 @@ module Spoom
35
35
 
36
36
  CONFIG_PATH = "sorbet/config"
37
37
  GEM_PATH = T.let(Gem::Specification.find_by_name("sorbet-static").full_gem_path, String)
38
+ GEM_VERSION = T.let(Gem::Specification.find_by_name("sorbet-static-and-runtime").version.to_s, String)
38
39
  BIN_PATH = T.let((Pathname.new(GEM_PATH) / "libexec" / "sorbet").to_s, String)
39
40
 
40
41
  KILLED_CODE = 137
data/lib/spoom/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Spoom
5
- VERSION = "1.2.1"
5
+ VERSION = "1.2.3"
6
6
  end
data/lib/spoom.rb CHANGED
@@ -15,6 +15,7 @@ end
15
15
  require "spoom/file_collector"
16
16
  require "spoom/context"
17
17
  require "spoom/colors"
18
+ require "spoom/deadcode"
18
19
  require "spoom/sorbet"
19
20
  require "spoom/cli"
20
21
  require "spoom/version"
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.2.1
4
+ version: 1.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexandre Terrasa
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-03-31 00:00:00.000000000 Z
11
+ date: 2023-08-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -67,7 +67,21 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: 13.0.1
69
69
  - !ruby/object:Gem::Dependency
70
- name: sorbet
70
+ name: erubi
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 1.10.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 1.10.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: sorbet-static-and-runtime
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
87
  - - ">="
@@ -81,19 +95,19 @@ dependencies:
81
95
  - !ruby/object:Gem::Version
82
96
  version: 0.5.10187
83
97
  - !ruby/object:Gem::Dependency
84
- name: sorbet-runtime
98
+ name: syntax_tree
85
99
  requirement: !ruby/object:Gem::Requirement
86
100
  requirements:
87
101
  - - ">="
88
102
  - !ruby/object:Gem::Version
89
- version: 0.5.9204
103
+ version: 6.1.1
90
104
  type: :runtime
91
105
  prerelease: false
92
106
  version_requirements: !ruby/object:Gem::Requirement
93
107
  requirements:
94
108
  - - ">="
95
109
  - !ruby/object:Gem::Version
96
- version: 0.5.9204
110
+ version: 6.1.1
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: thor
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -143,6 +157,17 @@ files:
143
157
  - lib/spoom/coverage/d3/timeline.rb
144
158
  - lib/spoom/coverage/report.rb
145
159
  - lib/spoom/coverage/snapshot.rb
160
+ - lib/spoom/deadcode.rb
161
+ - lib/spoom/deadcode/definition.rb
162
+ - lib/spoom/deadcode/erb.rb
163
+ - lib/spoom/deadcode/index.rb
164
+ - lib/spoom/deadcode/indexer.rb
165
+ - lib/spoom/deadcode/location.rb
166
+ - lib/spoom/deadcode/plugins.rb
167
+ - lib/spoom/deadcode/plugins/base.rb
168
+ - lib/spoom/deadcode/plugins/ruby.rb
169
+ - lib/spoom/deadcode/reference.rb
170
+ - lib/spoom/deadcode/send.rb
146
171
  - lib/spoom/file_collector.rb
147
172
  - lib/spoom/file_tree.rb
148
173
  - lib/spoom/printer.rb
@@ -173,14 +198,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
173
198
  requirements:
174
199
  - - ">="
175
200
  - !ruby/object:Gem::Version
176
- version: 2.7.0
201
+ version: 3.0.0
177
202
  required_rubygems_version: !ruby/object:Gem::Requirement
178
203
  requirements:
179
204
  - - ">="
180
205
  - !ruby/object:Gem::Version
181
206
  version: '0'
182
207
  requirements: []
183
- rubygems_version: 3.4.9
208
+ rubygems_version: 3.4.17
184
209
  signing_key:
185
210
  specification_version: 4
186
211
  summary: Useful tools for Sorbet enthusiasts.