spoom 1.5.4 → 1.6.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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/lib/spoom/backtrace_filter/minitest.rb +2 -3
  3. data/lib/spoom/cli/deadcode.rb +1 -2
  4. data/lib/spoom/cli/helper.rb +36 -28
  5. data/lib/spoom/cli/srb/assertions.rb +48 -0
  6. data/lib/spoom/cli/srb/bump.rb +1 -2
  7. data/lib/spoom/cli/srb/sigs.rb +133 -18
  8. data/lib/spoom/cli/srb.rb +8 -4
  9. data/lib/spoom/cli.rb +1 -2
  10. data/lib/spoom/colors.rb +2 -6
  11. data/lib/spoom/context/bundle.rb +8 -9
  12. data/lib/spoom/context/exec.rb +2 -5
  13. data/lib/spoom/context/file_system.rb +12 -19
  14. data/lib/spoom/context/git.rb +14 -19
  15. data/lib/spoom/context/sorbet.rb +13 -26
  16. data/lib/spoom/context.rb +3 -7
  17. data/lib/spoom/coverage/d3/base.rb +6 -8
  18. data/lib/spoom/coverage/d3/circle_map.rb +6 -16
  19. data/lib/spoom/coverage/d3/pie.rb +14 -19
  20. data/lib/spoom/coverage/d3/timeline.rb +46 -47
  21. data/lib/spoom/coverage/d3.rb +2 -4
  22. data/lib/spoom/coverage/report.rb +38 -76
  23. data/lib/spoom/coverage/snapshot.rb +7 -13
  24. data/lib/spoom/coverage.rb +3 -5
  25. data/lib/spoom/deadcode/definition.rb +12 -14
  26. data/lib/spoom/deadcode/erb.rb +10 -8
  27. data/lib/spoom/deadcode/index.rb +19 -23
  28. data/lib/spoom/deadcode/indexer.rb +5 -6
  29. data/lib/spoom/deadcode/plugins/action_mailer.rb +2 -3
  30. data/lib/spoom/deadcode/plugins/action_mailer_preview.rb +2 -3
  31. data/lib/spoom/deadcode/plugins/actionpack.rb +4 -4
  32. data/lib/spoom/deadcode/plugins/active_model.rb +2 -3
  33. data/lib/spoom/deadcode/plugins/active_record.rb +2 -3
  34. data/lib/spoom/deadcode/plugins/active_support.rb +2 -1
  35. data/lib/spoom/deadcode/plugins/base.rb +29 -32
  36. data/lib/spoom/deadcode/plugins/graphql.rb +2 -3
  37. data/lib/spoom/deadcode/plugins/minitest.rb +4 -4
  38. data/lib/spoom/deadcode/plugins/namespaces.rb +5 -5
  39. data/lib/spoom/deadcode/plugins/rails.rb +5 -5
  40. data/lib/spoom/deadcode/plugins/rubocop.rb +4 -4
  41. data/lib/spoom/deadcode/plugins/ruby.rb +3 -4
  42. data/lib/spoom/deadcode/plugins/sorbet.rb +12 -6
  43. data/lib/spoom/deadcode/plugins/thor.rb +2 -3
  44. data/lib/spoom/deadcode/plugins.rb +2 -4
  45. data/lib/spoom/deadcode/remover.rb +37 -59
  46. data/lib/spoom/deadcode/send.rb +2 -8
  47. data/lib/spoom/file_collector.rb +10 -18
  48. data/lib/spoom/file_tree.rb +31 -46
  49. data/lib/spoom/location.rb +9 -20
  50. data/lib/spoom/model/builder.rb +60 -15
  51. data/lib/spoom/model/model.rb +65 -68
  52. data/lib/spoom/model/namespace_visitor.rb +3 -2
  53. data/lib/spoom/model/reference.rb +4 -8
  54. data/lib/spoom/model/references_visitor.rb +49 -29
  55. data/lib/spoom/parse.rb +17 -3
  56. data/lib/spoom/poset.rb +17 -19
  57. data/lib/spoom/printer.rb +10 -13
  58. data/lib/spoom/sorbet/assertions.rb +278 -0
  59. data/lib/spoom/sorbet/config.rb +8 -12
  60. data/lib/spoom/sorbet/errors.rb +16 -31
  61. data/lib/spoom/sorbet/lsp/base.rb +9 -15
  62. data/lib/spoom/sorbet/lsp/errors.rb +8 -16
  63. data/lib/spoom/sorbet/lsp/structures.rb +36 -59
  64. data/lib/spoom/sorbet/lsp.rb +15 -17
  65. data/lib/spoom/sorbet/metrics.rb +3 -5
  66. data/lib/spoom/sorbet/sigils.rb +7 -11
  67. data/lib/spoom/sorbet/sigs.rb +118 -25
  68. data/lib/spoom/sorbet.rb +3 -9
  69. data/lib/spoom/timeline.rb +4 -6
  70. data/lib/spoom/version.rb +1 -1
  71. data/lib/spoom/visitor.rb +298 -151
  72. data/lib/spoom.rb +0 -2
  73. data/rbi/spoom.rbi +3963 -0
  74. metadata +6 -3
@@ -4,16 +4,14 @@
4
4
  module Spoom
5
5
  # Build a file hierarchy from a set of file paths.
6
6
  class FileTree
7
- extend T::Sig
8
-
9
- sig { params(paths: T::Enumerable[String]).void }
7
+ #: (?T::Enumerable[String] paths) -> void
10
8
  def initialize(paths = [])
11
9
  @roots = T.let({}, T::Hash[String, Node])
12
10
  add_paths(paths)
13
11
  end
14
12
 
15
13
  # Add all `paths` to the tree
16
- sig { params(paths: T::Enumerable[String]).void }
14
+ #: (T::Enumerable[String] paths) -> void
17
15
  def add_paths(paths)
18
16
  paths.each { |path| add_path(path) }
19
17
  end
@@ -21,7 +19,7 @@ module Spoom
21
19
  # Add a `path` to the tree
22
20
  #
23
21
  # This will create all nodes until the root of `path`.
24
- sig { params(path: String).returns(Node) }
22
+ #: (String path) -> Node
25
23
  def add_path(path)
26
24
  parts = path.split("/")
27
25
  if path.empty? || parts.size == 1
@@ -35,13 +33,13 @@ module Spoom
35
33
  end
36
34
 
37
35
  # All root nodes
38
- sig { returns(T::Array[Node]) }
36
+ #: -> Array[Node]
39
37
  def roots
40
38
  @roots.values
41
39
  end
42
40
 
43
41
  # All the nodes in this tree
44
- sig { returns(T::Array[Node]) }
42
+ #: -> Array[Node]
45
43
  def nodes
46
44
  v = CollectNodes.new
47
45
  v.visit_tree(self)
@@ -49,13 +47,13 @@ module Spoom
49
47
  end
50
48
 
51
49
  # All the paths in this tree
52
- sig { returns(T::Array[String]) }
50
+ #: -> Array[String]
53
51
  def paths
54
52
  nodes.map(&:path)
55
53
  end
56
54
 
57
55
  # Return a map of typing scores for each node in the tree
58
- sig { params(context: Context).returns(T::Hash[Node, Float]) }
56
+ #: (Context context) -> Hash[Node, Float]
59
57
  def nodes_strictness_scores(context)
60
58
  v = CollectScores.new(context)
61
59
  v.visit_tree(self)
@@ -63,12 +61,12 @@ module Spoom
63
61
  end
64
62
 
65
63
  # Return a map of typing scores for each path in the tree
66
- sig { params(context: Context).returns(T::Hash[String, Float]) }
64
+ #: (Context context) -> Hash[String, Float]
67
65
  def paths_strictness_scores(context)
68
66
  nodes_strictness_scores(context).map { |node, score| [node.path, score] }.to_h
69
67
  end
70
68
 
71
- sig { params(out: T.any(IO, StringIO), colors: T::Boolean).void }
69
+ #: (?out: (IO | StringIO), ?colors: bool) -> void
72
70
  def print(out: $stdout, colors: true)
73
71
  printer = Printer.new({}, out: out, colors: colors)
74
72
  printer.visit_tree(self)
@@ -76,8 +74,6 @@ module Spoom
76
74
 
77
75
  # A node representing either a file or a directory inside a FileTree
78
76
  class Node < T::Struct
79
- extend T::Sig
80
-
81
77
  # Node parent or `nil` if the node is a root one
82
78
  const :parent, T.nilable(Node)
83
79
 
@@ -88,7 +84,7 @@ module Spoom
88
84
  const :children, T::Hash[String, Node], default: {}
89
85
 
90
86
  # Full path to this node from root
91
- sig { returns(String) }
87
+ #: -> String
92
88
  def path
93
89
  parent = self.parent
94
90
  return name unless parent
@@ -99,22 +95,21 @@ module Spoom
99
95
 
100
96
  # An abstract visitor for FileTree
101
97
  class Visitor
102
- extend T::Sig
103
98
  extend T::Helpers
104
99
 
105
100
  abstract!
106
101
 
107
- sig { params(tree: FileTree).void }
102
+ #: (FileTree tree) -> void
108
103
  def visit_tree(tree)
109
104
  visit_nodes(tree.roots)
110
105
  end
111
106
 
112
- sig { params(node: FileTree::Node).void }
107
+ #: (FileTree::Node node) -> void
113
108
  def visit_node(node)
114
109
  visit_nodes(node.children.values)
115
110
  end
116
111
 
117
- sig { params(nodes: T::Array[FileTree::Node]).void }
112
+ #: (Array[FileTree::Node] nodes) -> void
118
113
  def visit_nodes(nodes)
119
114
  nodes.each { |node| visit_node(node) }
120
115
  end
@@ -122,18 +117,17 @@ module Spoom
122
117
 
123
118
  # A visitor that collects all the nodes in a tree
124
119
  class CollectNodes < Visitor
125
- extend T::Sig
126
-
127
- sig { returns(T::Array[FileTree::Node]) }
120
+ #: Array[FileTree::Node]
128
121
  attr_reader :nodes
129
122
 
130
- sig { void }
123
+ #: -> void
131
124
  def initialize
132
125
  super()
133
126
  @nodes = T.let([], T::Array[FileTree::Node])
134
127
  end
135
128
 
136
- sig { override.params(node: FileTree::Node).void }
129
+ # @override
130
+ #: (FileTree::Node node) -> void
137
131
  def visit_node(node)
138
132
  @nodes << node
139
133
  super
@@ -142,19 +136,18 @@ module Spoom
142
136
 
143
137
  # A visitor that collects the strictness of each node in a tree
144
138
  class CollectStrictnesses < Visitor
145
- extend T::Sig
146
-
147
- sig { returns(T::Hash[Node, T.nilable(String)]) }
139
+ #: Hash[Node, String?]
148
140
  attr_reader :strictnesses
149
141
 
150
- sig { params(context: Context).void }
142
+ #: (Context context) -> void
151
143
  def initialize(context)
152
144
  super()
153
145
  @context = context
154
146
  @strictnesses = T.let({}, T::Hash[Node, T.nilable(String)])
155
147
  end
156
148
 
157
- sig { override.params(node: FileTree::Node).void }
149
+ # @override
150
+ #: (FileTree::Node node) -> void
158
151
  def visit_node(node)
159
152
  path = node.path
160
153
  @strictnesses[node] = @context.read_file_strictness(path) if @context.file?(path)
@@ -165,19 +158,18 @@ module Spoom
165
158
 
166
159
  # A visitor that collects the typing score of each node in a tree
167
160
  class CollectScores < CollectStrictnesses
168
- extend T::Sig
169
-
170
- sig { returns(T::Hash[Node, Float]) }
161
+ #: Hash[Node, Float]
171
162
  attr_reader :scores
172
163
 
173
- sig { params(context: Context).void }
164
+ #: (Context context) -> void
174
165
  def initialize(context)
175
166
  super
176
167
  @context = context
177
168
  @scores = T.let({}, T::Hash[Node, Float])
178
169
  end
179
170
 
180
- sig { override.params(node: FileTree::Node).void }
171
+ # @override
172
+ #: (FileTree::Node node) -> void
181
173
  def visit_node(node)
182
174
  super
183
175
 
@@ -186,7 +178,7 @@ module Spoom
186
178
 
187
179
  private
188
180
 
189
- sig { params(node: Node).returns(Float) }
181
+ #: (Node node) -> Float
190
182
  def node_score(node)
191
183
  if @context.file?(node.path)
192
184
  strictness_score(@strictnesses[node])
@@ -195,7 +187,7 @@ module Spoom
195
187
  end
196
188
  end
197
189
 
198
- sig { params(strictness: T.nilable(String)).returns(Float) }
190
+ #: (String? strictness) -> Float
199
191
  def strictness_score(strictness)
200
192
  case strictness
201
193
  when "true", "strict", "strong"
@@ -210,15 +202,7 @@ module Spoom
210
202
  #
211
203
  # See `FileTree#print`
212
204
  class Printer < Visitor
213
- extend T::Sig
214
-
215
- sig do
216
- params(
217
- strictnesses: T::Hash[FileTree::Node, T.nilable(String)],
218
- out: T.any(IO, StringIO),
219
- colors: T::Boolean,
220
- ).void
221
- end
205
+ #: (Hash[FileTree::Node, String?] strictnesses, ?out: (IO | StringIO), ?colors: bool) -> void
222
206
  def initialize(strictnesses, out: $stdout, colors: true)
223
207
  super()
224
208
  @strictnesses = strictnesses
@@ -226,7 +210,8 @@ module Spoom
226
210
  @printer = T.let(Spoom::Printer.new(out: out, colors: colors), Spoom::Printer)
227
211
  end
228
212
 
229
- sig { override.params(node: FileTree::Node).void }
213
+ # @override
214
+ #: (FileTree::Node node) -> void
230
215
  def visit_node(node)
231
216
  @printer.printt
232
217
  if node.children.empty?
@@ -251,7 +236,7 @@ module Spoom
251
236
 
252
237
  private
253
238
 
254
- sig { params(strictness: T.nilable(String)).returns(Color) }
239
+ #: (String? strictness) -> Color
255
240
  def strictness_color(strictness)
256
241
  case strictness
257
242
  when "false"
@@ -3,16 +3,12 @@
3
3
 
4
4
  module Spoom
5
5
  class Location
6
- extend T::Sig
7
-
8
6
  include Comparable
9
7
 
10
8
  class LocationError < Spoom::Error; end
11
9
 
12
10
  class << self
13
- extend T::Sig
14
-
15
- sig { params(location_string: String).returns(Location) }
11
+ #: (String location_string) -> Location
16
12
  def from_string(location_string)
17
13
  file, rest = location_string.split(":", 2)
18
14
  raise LocationError, "Invalid location string `#{location_string}`: missing file name" unless file
@@ -43,7 +39,7 @@ module Spoom
43
39
  )
44
40
  end
45
41
 
46
- sig { params(file: String, location: Prism::Location).returns(Location) }
42
+ #: (String file, Prism::Location location) -> Location
47
43
  def from_prism(file, location)
48
44
  new(
49
45
  file,
@@ -55,21 +51,13 @@ module Spoom
55
51
  end
56
52
  end
57
53
 
58
- sig { returns(String) }
54
+ #: String
59
55
  attr_reader :file
60
56
 
61
- sig { returns(T.nilable(Integer)) }
57
+ #: Integer?
62
58
  attr_reader :start_line, :start_column, :end_line, :end_column
63
59
 
64
- sig do
65
- params(
66
- file: String,
67
- start_line: T.nilable(Integer),
68
- start_column: T.nilable(Integer),
69
- end_line: T.nilable(Integer),
70
- end_column: T.nilable(Integer),
71
- ).void
72
- end
60
+ #: (String file, ?start_line: Integer?, ?start_column: Integer?, ?end_line: Integer?, ?end_column: Integer?) -> void
73
61
  def initialize(file, start_line: nil, start_column: nil, end_line: nil, end_column: nil)
74
62
  raise LocationError,
75
63
  "Invalid location: end line is required if start line is provided" if start_line && !end_line
@@ -89,7 +77,7 @@ module Spoom
89
77
  @end_column = end_column
90
78
  end
91
79
 
92
- sig { params(other: Location).returns(T::Boolean) }
80
+ #: (Location other) -> bool
93
81
  def include?(other)
94
82
  return false unless @file == other.file
95
83
  return false if (@start_line || -Float::INFINITY) > (other.start_line || -Float::INFINITY)
@@ -102,7 +90,8 @@ module Spoom
102
90
  true
103
91
  end
104
92
 
105
- sig { override.params(other: BasicObject).returns(T.nilable(Integer)) }
93
+ # @override
94
+ #: (BasicObject other) -> Integer?
106
95
  def <=>(other)
107
96
  return unless Location === other
108
97
 
@@ -125,7 +114,7 @@ module Spoom
125
114
  comparison_array_self <=> comparison_array_other
126
115
  end
127
116
 
128
- sig { returns(String) }
117
+ #: -> String
129
118
  def to_s
130
119
  if @start_line && @start_column
131
120
  "#{@file}:#{@start_line}:#{@start_column}-#{@end_line}:#{@end_column}"
@@ -5,14 +5,18 @@ module Spoom
5
5
  class Model
6
6
  # Populate a Model by visiting the nodes from a Ruby file
7
7
  class Builder < NamespaceVisitor
8
- extend T::Sig
9
-
10
- sig { params(model: Model, file: String).void }
11
- def initialize(model, file)
8
+ #: (Model model, String file, ?comments: Array[Prism::Comment]) -> void
9
+ def initialize(model, file, comments:)
12
10
  super()
13
11
 
14
12
  @model = model
15
13
  @file = file
14
+ @comments_by_line = T.let(
15
+ comments.to_h do |c|
16
+ [c.location.start_line, c]
17
+ end,
18
+ T::Hash[Integer, Prism::Comment],
19
+ )
16
20
  @namespace_nesting = T.let([], T::Array[Namespace])
17
21
  @visibility_stack = T.let([Visibility::Public], T::Array[Visibility])
18
22
  @last_sigs = T.let([], T::Array[Sig])
@@ -20,13 +24,15 @@ module Spoom
20
24
 
21
25
  # Classes
22
26
 
23
- sig { override.params(node: Prism::ClassNode).void }
27
+ # @override
28
+ #: (Prism::ClassNode node) -> void
24
29
  def visit_class_node(node)
25
30
  @namespace_nesting << Class.new(
26
31
  @model.register_symbol(@names_nesting.join("::")),
27
32
  owner: @namespace_nesting.last,
28
33
  location: node_location(node),
29
34
  superclass_name: node.superclass&.slice,
35
+ comments: node_comments(node),
30
36
  )
31
37
  @visibility_stack << Visibility::Public
32
38
  super
@@ -35,12 +41,14 @@ module Spoom
35
41
  @last_sigs.clear
36
42
  end
37
43
 
38
- sig { override.params(node: Prism::SingletonClassNode).void }
44
+ # @override
45
+ #: (Prism::SingletonClassNode node) -> void
39
46
  def visit_singleton_class_node(node)
40
47
  @namespace_nesting << SingletonClass.new(
41
48
  @model.register_symbol(@names_nesting.join("::")),
42
49
  owner: @namespace_nesting.last,
43
50
  location: node_location(node),
51
+ comments: node_comments(node),
44
52
  )
45
53
  @visibility_stack << Visibility::Public
46
54
  super
@@ -51,12 +59,14 @@ module Spoom
51
59
 
52
60
  # Modules
53
61
 
54
- sig { override.params(node: Prism::ModuleNode).void }
62
+ # @override
63
+ #: (Prism::ModuleNode node) -> void
55
64
  def visit_module_node(node)
56
65
  @namespace_nesting << Module.new(
57
66
  @model.register_symbol(@names_nesting.join("::")),
58
67
  owner: @namespace_nesting.last,
59
68
  location: node_location(node),
69
+ comments: node_comments(node),
60
70
  )
61
71
  @visibility_stack << Visibility::Public
62
72
  super
@@ -67,7 +77,8 @@ module Spoom
67
77
 
68
78
  # Constants
69
79
 
70
- sig { override.params(node: Prism::ConstantPathWriteNode).void }
80
+ # @override
81
+ #: (Prism::ConstantPathWriteNode node) -> void
71
82
  def visit_constant_path_write_node(node)
72
83
  @last_sigs.clear
73
84
 
@@ -83,12 +94,14 @@ module Spoom
83
94
  owner: @namespace_nesting.last,
84
95
  location: node_location(node),
85
96
  value: node.value.slice,
97
+ comments: node_comments(node),
86
98
  )
87
99
 
88
100
  super
89
101
  end
90
102
 
91
- sig { override.params(node: Prism::ConstantWriteNode).void }
103
+ # @override
104
+ #: (Prism::ConstantWriteNode node) -> void
92
105
  def visit_constant_write_node(node)
93
106
  @last_sigs.clear
94
107
 
@@ -97,12 +110,14 @@ module Spoom
97
110
  owner: @namespace_nesting.last,
98
111
  location: node_location(node),
99
112
  value: node.value.slice,
113
+ comments: node_comments(node),
100
114
  )
101
115
 
102
116
  super
103
117
  end
104
118
 
105
- sig { override.params(node: Prism::MultiWriteNode).void }
119
+ # @override
120
+ #: (Prism::MultiWriteNode node) -> void
106
121
  def visit_multi_write_node(node)
107
122
  @last_sigs.clear
108
123
 
@@ -114,6 +129,7 @@ module Spoom
114
129
  owner: @namespace_nesting.last,
115
130
  location: node_location(const),
116
131
  value: node.value.slice,
132
+ comments: node_comments(const),
117
133
  )
118
134
  end
119
135
  end
@@ -123,7 +139,8 @@ module Spoom
123
139
 
124
140
  # Methods
125
141
 
126
- sig { override.params(node: Prism::DefNode).void }
142
+ # @override
143
+ #: (Prism::DefNode node) -> void
127
144
  def visit_def_node(node)
128
145
  recv = node.receiver
129
146
 
@@ -134,6 +151,7 @@ module Spoom
134
151
  location: node_location(node),
135
152
  visibility: current_visibility,
136
153
  sigs: collect_sigs,
154
+ comments: node_comments(node),
137
155
  )
138
156
  end
139
157
 
@@ -142,7 +160,8 @@ module Spoom
142
160
 
143
161
  # Accessors
144
162
 
145
- sig { override.params(node: Prism::CallNode).void }
163
+ # @override
164
+ #: (Prism::CallNode node) -> void
146
165
  def visit_call_node(node)
147
166
  return if node.receiver && !node.receiver.is_a?(Prism::SelfNode)
148
167
 
@@ -160,6 +179,7 @@ module Spoom
160
179
  location: node_location(arg),
161
180
  visibility: current_visibility,
162
181
  sigs: sigs,
182
+ comments: node_comments(node),
163
183
  )
164
184
  end
165
185
  when :attr_reader
@@ -173,6 +193,7 @@ module Spoom
173
193
  location: node_location(arg),
174
194
  visibility: current_visibility,
175
195
  sigs: sigs,
196
+ comments: node_comments(node),
176
197
  )
177
198
  end
178
199
  when :attr_writer
@@ -186,6 +207,7 @@ module Spoom
186
207
  location: node_location(arg),
187
208
  visibility: current_visibility,
188
209
  sigs: sigs,
210
+ comments: node_comments(node),
189
211
  )
190
212
  end
191
213
  when :include
@@ -225,22 +247,45 @@ module Spoom
225
247
 
226
248
  private
227
249
 
228
- sig { returns(Visibility) }
250
+ #: -> Visibility
229
251
  def current_visibility
230
252
  T.must(@visibility_stack.last)
231
253
  end
232
254
 
233
- sig { returns(T::Array[Sig]) }
255
+ #: -> Array[Sig]
234
256
  def collect_sigs
235
257
  sigs = @last_sigs
236
258
  @last_sigs = []
237
259
  sigs
238
260
  end
239
261
 
240
- sig { params(node: Prism::Node).returns(Location) }
262
+ #: (Prism::Node node) -> Location
241
263
  def node_location(node)
242
264
  Location.from_prism(@file, node.location)
243
265
  end
266
+
267
+ #: (Prism::Node node) -> Array[Comment]
268
+ def node_comments(node)
269
+ comments = []
270
+
271
+ start_line = node.location.start_line
272
+ start_line -= 1 unless @comments_by_line.key?(start_line)
273
+
274
+ start_line.downto(1) do |line|
275
+ comment = @comments_by_line[line]
276
+ break unless comment
277
+
278
+ spoom_comment = Comment.new(
279
+ comment.slice.gsub(/^#\s?/, "").rstrip,
280
+ Location.from_prism(@file, comment.location),
281
+ )
282
+
283
+ comments.unshift(spoom_comment)
284
+ @comments_by_line.delete(line)
285
+ end
286
+
287
+ comments
288
+ end
244
289
  end
245
290
  end
246
291
  end