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.
- checksums.yaml +4 -4
- data/Gemfile +0 -1
- data/lib/spoom/cli/coverage.rb +1 -1
- data/lib/spoom/context/bundle.rb +16 -7
- data/lib/spoom/context/file_system.rb +17 -0
- data/lib/spoom/context/git.rb +21 -5
- data/lib/spoom/context/sorbet.rb +24 -7
- data/lib/spoom/deadcode/definition.rb +98 -0
- data/lib/spoom/deadcode/erb.rb +103 -0
- data/lib/spoom/deadcode/index.rb +61 -0
- data/lib/spoom/deadcode/indexer.rb +403 -0
- data/lib/spoom/deadcode/location.rb +58 -0
- data/lib/spoom/deadcode/plugins/base.rb +201 -0
- data/lib/spoom/deadcode/plugins/ruby.rb +64 -0
- data/lib/spoom/deadcode/plugins.rb +5 -0
- data/lib/spoom/deadcode/reference.rb +34 -0
- data/lib/spoom/deadcode/send.rb +18 -0
- data/lib/spoom/deadcode.rb +56 -0
- data/lib/spoom/file_collector.rb +26 -3
- data/lib/spoom/printer.rb +0 -2
- data/lib/spoom/sorbet/lsp/base.rb +0 -6
- data/lib/spoom/sorbet/lsp/structures.rb +1 -1
- data/lib/spoom/sorbet/lsp.rb +2 -2
- data/lib/spoom/sorbet/sigils.rb +2 -2
- data/lib/spoom/sorbet.rb +1 -0
- data/lib/spoom/version.rb +1 -1
- data/lib/spoom.rb +1 -0
- metadata +33 -8
@@ -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,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
|
data/lib/spoom/file_collector.rb
CHANGED
@@ -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
|
-
|
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?
|
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
@@ -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
|
|
data/lib/spoom/sorbet/lsp.rb
CHANGED
@@ -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
|
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
|
104
|
+
return unless json && json["result"]
|
105
105
|
|
106
106
|
Hover.from_json(json["result"])
|
107
107
|
end
|
data/lib/spoom/sorbet/sigils.rb
CHANGED
@@ -28,7 +28,7 @@ module Spoom
|
|
28
28
|
T::Array[String],
|
29
29
|
)
|
30
30
|
|
31
|
-
SIGIL_REGEXP = T.let(/^#[
|
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
|
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
data/lib/spoom.rb
CHANGED
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.
|
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-
|
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:
|
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:
|
98
|
+
name: syntax_tree
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
86
100
|
requirements:
|
87
101
|
- - ">="
|
88
102
|
- !ruby/object:Gem::Version
|
89
|
-
version:
|
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:
|
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:
|
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.
|
208
|
+
rubygems_version: 3.4.17
|
184
209
|
signing_key:
|
185
210
|
specification_version: 4
|
186
211
|
summary: Useful tools for Sorbet enthusiasts.
|