spoom 1.2.1 → 1.2.3

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