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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8e7ef8ebef70bc8827cdcb4b2c0e49ed6c53686e0db409908354f3762ad07c10
|
4
|
+
data.tar.gz: a6adb405f0379cbe040e743f177e758e2bdaadcdea13ee9c7f75d84f764434b1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 86d74bc7d9554d67c1421908116bbab7615d77e9cb096d461f7885429d495f2b02f19d6024117f7083423cfe7c48165b18918c143f29574896bfbb76a4fb651f
|
7
|
+
data.tar.gz: 754d41e8f26425a42288388879fe1658214adea03002421495297486343a827d6dc79a395e895553ed15f67cdc766a16f4a174aab2a3671bd2455e305a8c6f13
|
data/Gemfile
CHANGED
data/lib/spoom/cli/coverage.rb
CHANGED
data/lib/spoom/context/bundle.rb
CHANGED
@@ -10,12 +10,18 @@ module Spoom
|
|
10
10
|
|
11
11
|
requires_ancestor { Context }
|
12
12
|
|
13
|
-
# Read the
|
13
|
+
# Read the contents of the Gemfile in this context directory
|
14
14
|
sig { returns(T.nilable(String)) }
|
15
15
|
def read_gemfile
|
16
16
|
read("Gemfile")
|
17
17
|
end
|
18
18
|
|
19
|
+
# Read the contents of the Gemfile.lock in this context directory
|
20
|
+
sig { returns(T.nilable(String)) }
|
21
|
+
def read_gemfile_lock
|
22
|
+
read("Gemfile.lock")
|
23
|
+
end
|
24
|
+
|
19
25
|
# Set the `contents` of the Gemfile in this context directory
|
20
26
|
sig { params(contents: String, append: T::Boolean).void }
|
21
27
|
def write_gemfile!(contents, append: false)
|
@@ -41,17 +47,20 @@ module Spoom
|
|
41
47
|
bundle("exec #{command}", version: version, capture_err: capture_err)
|
42
48
|
end
|
43
49
|
|
50
|
+
sig { returns(T::Hash[String, Bundler::LazySpecification]) }
|
51
|
+
def gemfile_lock_specs
|
52
|
+
return {} unless file?("Gemfile.lock")
|
53
|
+
|
54
|
+
parser = Bundler::LockfileParser.new(read_gemfile_lock)
|
55
|
+
parser.specs.map { |spec| [spec.name, spec] }.to_h
|
56
|
+
end
|
57
|
+
|
44
58
|
# Get `gem` version from the `Gemfile.lock` content
|
45
59
|
#
|
46
60
|
# Returns `nil` if `gem` cannot be found in the Gemfile.
|
47
61
|
sig { params(gem: String).returns(T.nilable(String)) }
|
48
62
|
def gem_version_from_gemfile_lock(gem)
|
49
|
-
|
50
|
-
|
51
|
-
content = read("Gemfile.lock").match(/^ #{gem} \(.*(\d+\.\d+\.\d+).*\)/)
|
52
|
-
return nil unless content
|
53
|
-
|
54
|
-
content[1]
|
63
|
+
gemfile_lock_specs[gem]&.version&.to_s
|
55
64
|
end
|
56
65
|
end
|
57
66
|
end
|
@@ -43,6 +43,23 @@ module Spoom
|
|
43
43
|
glob("*")
|
44
44
|
end
|
45
45
|
|
46
|
+
sig do
|
47
|
+
params(
|
48
|
+
allow_extensions: T::Array[String],
|
49
|
+
allow_mime_types: T::Array[String],
|
50
|
+
exclude_patterns: T::Array[String],
|
51
|
+
).returns(T::Array[String])
|
52
|
+
end
|
53
|
+
def collect_files(allow_extensions: [], allow_mime_types: [], exclude_patterns: [])
|
54
|
+
collector = FileCollector.new(
|
55
|
+
allow_extensions: allow_extensions,
|
56
|
+
allow_mime_types: allow_mime_types,
|
57
|
+
exclude_patterns: exclude_patterns,
|
58
|
+
)
|
59
|
+
collector.visit_path(absolute_path)
|
60
|
+
collector.files.map { |file| file.delete_prefix("#{absolute_path}/") }
|
61
|
+
end
|
62
|
+
|
46
63
|
# Does `relative_path` point to an existing file in this context directory?
|
47
64
|
sig { params(relative_path: String).returns(T::Boolean) }
|
48
65
|
def file?(relative_path)
|
data/lib/spoom/context/git.rb
CHANGED
@@ -13,7 +13,7 @@ module Spoom
|
|
13
13
|
sig { params(string: String).returns(T.nilable(Commit)) }
|
14
14
|
def parse_line(string)
|
15
15
|
sha, epoch = string.split(" ", 2)
|
16
|
-
return
|
16
|
+
return unless sha && epoch
|
17
17
|
|
18
18
|
time = Time.strptime(epoch, "%s")
|
19
19
|
Commit.new(sha: sha, time: time)
|
@@ -63,8 +63,18 @@ module Spoom
|
|
63
63
|
git("checkout #{ref}")
|
64
64
|
end
|
65
65
|
|
66
|
+
# Run `git checkout -b <branch-name> <ref>` in this context directory
|
67
|
+
sig { params(branch_name: String, ref: T.nilable(String)).returns(ExecResult) }
|
68
|
+
def git_checkout_new_branch!(branch_name, ref: nil)
|
69
|
+
if ref
|
70
|
+
git("checkout -b #{branch_name} #{ref}")
|
71
|
+
else
|
72
|
+
git("checkout -b #{branch_name}")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
66
76
|
# Run `git add . && git commit` in this context directory
|
67
|
-
sig { params(message: String, time: Time, allow_empty: T::Boolean).
|
77
|
+
sig { params(message: String, time: Time, allow_empty: T::Boolean).returns(ExecResult) }
|
68
78
|
def git_commit!(message: "message", time: Time.now.utc, allow_empty: false)
|
69
79
|
git("add --all")
|
70
80
|
|
@@ -78,7 +88,7 @@ module Spoom
|
|
78
88
|
sig { returns(T.nilable(String)) }
|
79
89
|
def git_current_branch
|
80
90
|
res = git("branch --show-current")
|
81
|
-
return
|
91
|
+
return unless res.status
|
82
92
|
|
83
93
|
res.out.strip
|
84
94
|
end
|
@@ -93,10 +103,10 @@ module Spoom
|
|
93
103
|
sig { params(short_sha: T::Boolean).returns(T.nilable(Spoom::Git::Commit)) }
|
94
104
|
def git_last_commit(short_sha: true)
|
95
105
|
res = git_log("HEAD --format='%#{short_sha ? "h" : "H"} %at' -1")
|
96
|
-
return
|
106
|
+
return unless res.status
|
97
107
|
|
98
108
|
out = res.out.strip
|
99
|
-
return
|
109
|
+
return if out.empty?
|
100
110
|
|
101
111
|
Spoom::Git::Commit.parse_line(out)
|
102
112
|
end
|
@@ -106,6 +116,12 @@ module Spoom
|
|
106
116
|
git("log #{arg.join(" ")}")
|
107
117
|
end
|
108
118
|
|
119
|
+
# Run `git push <remote> <ref>` in this context directory
|
120
|
+
sig { params(remote: String, ref: String, force: T::Boolean).returns(ExecResult) }
|
121
|
+
def git_push!(remote, ref, force: false)
|
122
|
+
git("push #{force ? "-f" : ""} #{remote} #{ref}")
|
123
|
+
end
|
124
|
+
|
109
125
|
sig { params(arg: String).returns(ExecResult) }
|
110
126
|
def git_show(*arg)
|
111
127
|
git("show #{arg.join(" ")}")
|
data/lib/spoom/context/sorbet.rb
CHANGED
@@ -52,7 +52,7 @@ module Spoom
|
|
52
52
|
sorbet_bin: sorbet_bin,
|
53
53
|
capture_err: capture_err,
|
54
54
|
)
|
55
|
-
return
|
55
|
+
return unless file?(metrics_file)
|
56
56
|
|
57
57
|
metrics_path = absolute_path_to(metrics_file)
|
58
58
|
metrics = Spoom::Sorbet::MetricsParser.parse_file(metrics_path)
|
@@ -69,7 +69,24 @@ module Spoom
|
|
69
69
|
allowed_extensions = Spoom::Sorbet::Config::DEFAULT_ALLOWED_EXTENSIONS if allowed_extensions.empty?
|
70
70
|
allowed_extensions -= [".rbi"] unless include_rbis
|
71
71
|
|
72
|
-
excluded_patterns = config.ignore.map
|
72
|
+
excluded_patterns = config.ignore.map do |string|
|
73
|
+
# We need to simulate the behavior of Sorbet's `--ignore` flag.
|
74
|
+
#
|
75
|
+
# From Sorbet docs on `--ignore`:
|
76
|
+
# > Ignores input files that contain the given string in their paths (relative to the input path passed to
|
77
|
+
# > Sorbet). Strings beginning with / match against the prefix of these relative paths; others are substring
|
78
|
+
# > matchs. Matches must be against whole folder and file names, so `foo` matches `/foo/bar.rb` and
|
79
|
+
# > `/bar/foo/baz.rb` but not `/foo.rb` or `/foo2/bar.rb`.
|
80
|
+
string = if string.start_with?("/")
|
81
|
+
# Strings beginning with / match against the prefix of these relative paths
|
82
|
+
File.join(absolute_path, string)
|
83
|
+
else
|
84
|
+
# Others are substring matchs
|
85
|
+
File.join(absolute_path, "**", string)
|
86
|
+
end
|
87
|
+
# Matches must be against whole folder and file names
|
88
|
+
"#{string.delete_suffix("/")}{,/**}"
|
89
|
+
end
|
73
90
|
|
74
91
|
collector = FileCollector.new(allow_extensions: allowed_extensions, exclude_patterns: excluded_patterns)
|
75
92
|
collector.visit_paths(config.paths.map { |path| absolute_path_to(path) })
|
@@ -92,7 +109,7 @@ module Spoom
|
|
92
109
|
sig { params(arg: String, sorbet_bin: T.nilable(String), capture_err: T::Boolean).returns(T.nilable(String)) }
|
93
110
|
def srb_version(*arg, sorbet_bin: nil, capture_err: true)
|
94
111
|
res = T.unsafe(self).srb_tc("--no-config", "--version", *arg, sorbet_bin: sorbet_bin, capture_err: capture_err)
|
95
|
-
return
|
112
|
+
return unless res.status
|
96
113
|
|
97
114
|
res.out.split(" ")[2]
|
98
115
|
end
|
@@ -130,10 +147,10 @@ module Spoom
|
|
130
147
|
sig { returns(T.nilable(Spoom::Git::Commit)) }
|
131
148
|
def sorbet_intro_commit
|
132
149
|
res = git_log("--diff-filter=A --format='%h %at' -1 -- sorbet/config")
|
133
|
-
return
|
150
|
+
return unless res.status
|
134
151
|
|
135
152
|
out = res.out.strip
|
136
|
-
return
|
153
|
+
return if out.empty?
|
137
154
|
|
138
155
|
Spoom::Git::Commit.parse_line(out)
|
139
156
|
end
|
@@ -142,10 +159,10 @@ module Spoom
|
|
142
159
|
sig { returns(T.nilable(Spoom::Git::Commit)) }
|
143
160
|
def sorbet_removal_commit
|
144
161
|
res = git_log("--diff-filter=D --format='%h %at' -1 -- sorbet/config")
|
145
|
-
return
|
162
|
+
return unless res.status
|
146
163
|
|
147
164
|
out = res.out.strip
|
148
|
-
return
|
165
|
+
return if out.empty?
|
149
166
|
|
150
167
|
Spoom::Git::Commit.parse_line(out)
|
151
168
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Deadcode
|
6
|
+
# A definition is a class, module, method, constant, etc. being defined in the code
|
7
|
+
class Definition < T::Struct
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
class Kind < T::Enum
|
11
|
+
enums do
|
12
|
+
AttrReader = new("attr_reader")
|
13
|
+
AttrWriter = new("attr_writer")
|
14
|
+
Class = new("class")
|
15
|
+
Constant = new("constant")
|
16
|
+
Method = new("method")
|
17
|
+
Module = new("module")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Status < T::Enum
|
22
|
+
enums do
|
23
|
+
# A definition is marked as `ALIVE` if it has at least one reference with the same name
|
24
|
+
ALIVE = new
|
25
|
+
# A definition is marked as `DEAD` if it has no reference with the same name
|
26
|
+
DEAD = new
|
27
|
+
# A definition can be marked as `IGNORED` if it is not relevant for the analysis
|
28
|
+
IGNORED = new
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
const :kind, Kind
|
33
|
+
const :name, String
|
34
|
+
const :full_name, String
|
35
|
+
const :location, Location
|
36
|
+
const :status, Status, default: Status::DEAD
|
37
|
+
|
38
|
+
# Kind
|
39
|
+
|
40
|
+
sig { returns(T::Boolean) }
|
41
|
+
def attr_reader?
|
42
|
+
kind == Kind::AttrReader
|
43
|
+
end
|
44
|
+
|
45
|
+
sig { returns(T::Boolean) }
|
46
|
+
def attr_writer?
|
47
|
+
kind == Kind::AttrWriter
|
48
|
+
end
|
49
|
+
|
50
|
+
sig { returns(T::Boolean) }
|
51
|
+
def class?
|
52
|
+
kind == Kind::Class
|
53
|
+
end
|
54
|
+
|
55
|
+
sig { returns(T::Boolean) }
|
56
|
+
def constant?
|
57
|
+
kind == Kind::Constant
|
58
|
+
end
|
59
|
+
|
60
|
+
sig { returns(T::Boolean) }
|
61
|
+
def method?
|
62
|
+
kind == Kind::Method
|
63
|
+
end
|
64
|
+
|
65
|
+
sig { returns(T::Boolean) }
|
66
|
+
def module?
|
67
|
+
kind == Kind::Module
|
68
|
+
end
|
69
|
+
|
70
|
+
# Status
|
71
|
+
|
72
|
+
sig { returns(T::Boolean) }
|
73
|
+
def alive?
|
74
|
+
status == Status::ALIVE
|
75
|
+
end
|
76
|
+
|
77
|
+
sig { void }
|
78
|
+
def alive!
|
79
|
+
@status = Status::ALIVE
|
80
|
+
end
|
81
|
+
|
82
|
+
sig { returns(T::Boolean) }
|
83
|
+
def dead?
|
84
|
+
status == Status::DEAD
|
85
|
+
end
|
86
|
+
|
87
|
+
sig { returns(T::Boolean) }
|
88
|
+
def ignored?
|
89
|
+
status == Status::IGNORED
|
90
|
+
end
|
91
|
+
|
92
|
+
sig { void }
|
93
|
+
def ignored!
|
94
|
+
@status = Status::IGNORED
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Copied from https://github.com/rails/rails/blob/main/actionview/lib/action_view/template/handlers/erb/erubi.rb.
|
5
|
+
#
|
6
|
+
# Copyright (c) David Heinemeier Hansson
|
7
|
+
#
|
8
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
9
|
+
# a copy of this software and associated documentation files (the
|
10
|
+
# "Software"), to deal in the Software without restriction, including
|
11
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
12
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
13
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
14
|
+
# the following conditions:
|
15
|
+
#
|
16
|
+
# The above copyright notice and this permission notice shall be
|
17
|
+
# included in all copies or substantial portions of the Software.
|
18
|
+
#
|
19
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
20
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
21
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
22
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
23
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
24
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
25
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
26
|
+
module Spoom
|
27
|
+
module Deadcode
|
28
|
+
# Custom engine to handle ERB templates as used by Rails
|
29
|
+
class ERB < ::Erubi::Engine
|
30
|
+
extend T::Sig
|
31
|
+
|
32
|
+
sig { params(input: T.untyped, properties: T.untyped).void }
|
33
|
+
def initialize(input, properties = {})
|
34
|
+
@newline_pending = 0
|
35
|
+
|
36
|
+
properties = Hash[properties]
|
37
|
+
properties[:bufvar] ||= "@output_buffer"
|
38
|
+
properties[:preamble] ||= ""
|
39
|
+
properties[:postamble] ||= "#{properties[:bufvar]}.to_s"
|
40
|
+
properties[:escapefunc] = ""
|
41
|
+
|
42
|
+
super
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
sig { params(text: T.untyped).void }
|
48
|
+
def add_text(text)
|
49
|
+
return if text.empty?
|
50
|
+
|
51
|
+
if text == "\n"
|
52
|
+
@newline_pending += 1
|
53
|
+
else
|
54
|
+
src << bufvar << ".safe_append='"
|
55
|
+
src << "\n" * @newline_pending if @newline_pending > 0
|
56
|
+
src << text.gsub(/['\\]/, '\\\\\&')
|
57
|
+
src << "'.freeze;"
|
58
|
+
|
59
|
+
@newline_pending = 0
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
|
64
|
+
|
65
|
+
sig { params(indicator: T.untyped, code: T.untyped).void }
|
66
|
+
def add_expression(indicator, code)
|
67
|
+
flush_newline_if_pending(src)
|
68
|
+
|
69
|
+
src << bufvar << if (indicator == "==") || @escape
|
70
|
+
".safe_expr_append="
|
71
|
+
else
|
72
|
+
".append="
|
73
|
+
end
|
74
|
+
|
75
|
+
if BLOCK_EXPR.match?(code)
|
76
|
+
src << " " << code
|
77
|
+
else
|
78
|
+
src << "(" << code << ");"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
sig { params(code: T.untyped).void }
|
83
|
+
def add_code(code)
|
84
|
+
flush_newline_if_pending(src)
|
85
|
+
super
|
86
|
+
end
|
87
|
+
|
88
|
+
sig { params(_: T.untyped).void }
|
89
|
+
def add_postamble(_)
|
90
|
+
flush_newline_if_pending(src)
|
91
|
+
super
|
92
|
+
end
|
93
|
+
|
94
|
+
sig { params(src: T.untyped).void }
|
95
|
+
def flush_newline_if_pending(src)
|
96
|
+
if @newline_pending > 0
|
97
|
+
src << bufvar << ".safe_append='#{"\n" * @newline_pending}'.freeze;"
|
98
|
+
@newline_pending = 0
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Deadcode
|
6
|
+
class Index
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig { returns(T::Hash[String, T::Array[Definition]]) }
|
10
|
+
attr_reader :definitions
|
11
|
+
|
12
|
+
sig { returns(T::Hash[String, T::Array[Reference]]) }
|
13
|
+
attr_reader :references
|
14
|
+
|
15
|
+
sig { void }
|
16
|
+
def initialize
|
17
|
+
@definitions = T.let({}, T::Hash[String, T::Array[Definition]])
|
18
|
+
@references = T.let({}, T::Hash[String, T::Array[Reference]])
|
19
|
+
end
|
20
|
+
|
21
|
+
# Indexing
|
22
|
+
|
23
|
+
sig { params(definition: Definition).void }
|
24
|
+
def define(definition)
|
25
|
+
(@definitions[definition.name] ||= []) << definition
|
26
|
+
end
|
27
|
+
|
28
|
+
sig { params(reference: Reference).void }
|
29
|
+
def reference(reference)
|
30
|
+
(@references[reference.name] ||= []) << reference
|
31
|
+
end
|
32
|
+
|
33
|
+
# Mark all definitions having a reference of the same name as `alive`
|
34
|
+
#
|
35
|
+
# To be called once all the files have been indexed and all the definitions and references discovered.
|
36
|
+
sig { void }
|
37
|
+
def finalize!
|
38
|
+
@references.keys.each do |name|
|
39
|
+
definitions_for_name(name).each(&:alive!)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Utils
|
44
|
+
|
45
|
+
sig { params(name: String).returns(T::Array[Definition]) }
|
46
|
+
def definitions_for_name(name)
|
47
|
+
@definitions[name] || []
|
48
|
+
end
|
49
|
+
|
50
|
+
sig { returns(T::Array[Definition]) }
|
51
|
+
def all_definitions
|
52
|
+
@definitions.values.flatten
|
53
|
+
end
|
54
|
+
|
55
|
+
sig { returns(T::Array[Reference]) }
|
56
|
+
def all_references
|
57
|
+
@references.values.flatten
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|