offense_to_corrector 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e3aa29d338b6fbe90a8b026e2a5a7008e4772bfe52b988a26856b6cd4c585a1b
4
- data.tar.gz: 63a528f40daa7da31df15a872d66fc0181a72fb457b1e5c23885deb752dbe6f1
3
+ metadata.gz: 633ca47b27f11bd775d9fd27182d22586603b950b3e3c8ce6e3976162ead1fb1
4
+ data.tar.gz: 43e72f100670136da7907c3ea63d3298242974b40b2cf1f330cae73f5f366d60
5
5
  SHA512:
6
- metadata.gz: b565e890a4a449467882d5ed53787990342fa5515c38e50f71653480594f4113bb564c1f353be2bdf879ca6cb9263dd9dbf5a58bef4b048e3106febaa303026b
7
- data.tar.gz: 23de46b53c06aa98dfc0c4342cce5e4344c955187dbc7d65c5161e96271a7268a89f0fe09939a46a34e7b7f524ed37e24958eeddb647bf69e0a08170be13f41d
6
+ metadata.gz: cb3b1ebad0fb6a238d839cca290065d806ada04fbb9a03858e663befbf1994f5bb5bda222a9cd7bca5a3b0b299733f179fb1f5ce0a201be261101e3a908e0972
7
+ data.tar.gz: 7608dfcd7d6d04a8c4e53f3fc1f448ba5c89585f67a4c15ab25fe87cb0992ed731fdc3ee9590fb1f01b6145ef48f47f4c16f813d894cac91982cab1276ee6f2c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
- ## [0.1.0] - 2022-02-20
3
+ ## [0.0.2] - 2022-02-21
4
4
 
5
- - Initial release
5
+ - Refined underlying algorithms for computing nearest nodes
6
+ - Range intersection dropped for string overlaps.
7
+ - Closest node implemented on length of overlap and percentage of node source
8
+ overlap to prevent matching higher-up parent nodes, but also avoid matching
9
+ entire small child nodes that aren't as relevant.
10
+ - Fixed errors with unfindable atom nodes.
11
+ - Broke apart mono-file into related subdirectories, added basic specs
12
+
13
+ ## [0.0.1] - 2022-02-20
14
+
15
+ - Initial release.
data/Gemfile.lock CHANGED
@@ -8,12 +8,44 @@ GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
10
  ast (2.4.2)
11
+ coderay (1.1.3)
11
12
  diff-lcs (1.5.0)
13
+ ffi (1.15.5)
14
+ formatador (1.1.0)
15
+ guard (2.18.0)
16
+ formatador (>= 0.2.4)
17
+ listen (>= 2.7, < 4.0)
18
+ lumberjack (>= 1.0.12, < 2.0)
19
+ nenv (~> 0.1)
20
+ notiffany (~> 0.0)
21
+ pry (>= 0.13.0)
22
+ shellany (~> 0.0)
23
+ thor (>= 0.18.1)
24
+ guard-compat (1.2.1)
25
+ guard-rspec (4.7.3)
26
+ guard (~> 2.1)
27
+ guard-compat (~> 1.1)
28
+ rspec (>= 2.99.0, < 4.0)
29
+ listen (3.7.1)
30
+ rb-fsevent (~> 0.10, >= 0.10.3)
31
+ rb-inotify (~> 0.9, >= 0.9.10)
32
+ lumberjack (1.2.8)
33
+ method_source (1.0.0)
34
+ nenv (0.3.0)
35
+ notiffany (0.1.3)
36
+ nenv (~> 0.1)
37
+ shellany (~> 0.0)
12
38
  parallel (1.21.0)
13
39
  parser (3.1.0.0)
14
40
  ast (~> 2.4.1)
41
+ pry (0.14.1)
42
+ coderay (~> 1.1)
43
+ method_source (~> 1.0)
15
44
  rainbow (3.1.1)
16
45
  rake (13.0.6)
46
+ rb-fsevent (0.11.1)
47
+ rb-inotify (0.10.1)
48
+ ffi (~> 1.0)
17
49
  regexp_parser (2.2.1)
18
50
  rexml (3.2.5)
19
51
  rspec (3.11.0)
@@ -41,12 +73,15 @@ GEM
41
73
  rubocop-ast (1.15.2)
42
74
  parser (>= 3.0.1.1)
43
75
  ruby-progressbar (1.11.0)
76
+ shellany (0.0.1)
77
+ thor (1.2.1)
44
78
  unicode-display_width (2.1.0)
45
79
 
46
80
  PLATFORMS
47
81
  x86_64-darwin-20
48
82
 
49
83
  DEPENDENCIES
84
+ guard-rspec
50
85
  offense_to_corrector!
51
86
  rake (~> 13.0)
52
87
  rspec (~> 3.0)
data/Guardfile ADDED
@@ -0,0 +1,14 @@
1
+ guard :rspec, cmd: "bundle exec rspec" do
2
+ require "guard/rspec/dsl"
3
+ dsl = Guard::RSpec::Dsl.new(self)
4
+
5
+ # RSpec files
6
+ rspec = dsl.rspec
7
+ watch(rspec.spec_helper) { rspec.spec_dir }
8
+ watch(rspec.spec_support) { rspec.spec_dir }
9
+ watch(rspec.spec_files)
10
+
11
+ # Ruby files
12
+ ruby = dsl.ruby
13
+ dsl.watch_spec_files_for(ruby.lib_files)
14
+ end
@@ -0,0 +1,93 @@
1
+ module OffenseToCorrector
2
+ module AstTools
3
+ def atom?(value)
4
+ !value.is_a?(RuboCop::AST::Node)
5
+ end
6
+
7
+ # I may have to work on getting this one later to try and
8
+ # narrow the band of `AtomNode`
9
+ # def childless?(node)
10
+ # false # TODO
11
+ # end
12
+
13
+ def string_intersection(a, b)
14
+ # Smaller string inside larger string
15
+ target, search_string = a.size < b.size ? [a, b] : [b, a]
16
+
17
+ last_char_index = target.size.downto(1).find do |i|
18
+ search_string.include?(target[0..i])
19
+ end or return ""
20
+
21
+ target[0..last_char_index]
22
+ end
23
+
24
+ # How much do two ranges overlap? Used to see how well a node
25
+ # matches with the associated underline
26
+ def range_overlap_count(a, b)
27
+ return [a.end, b.end].min - b.begin if a.cover?(b.begin)
28
+ return [a.end, b.end].min - a.begin if b.cover?(a.begin)
29
+
30
+ 0
31
+ end
32
+
33
+ # See if a range overlaps another one, bidirectional
34
+ def range_overlap?(a, b)
35
+ a.cover?(b.begin) || b.cover?(a.begin)
36
+ end
37
+
38
+ # To get an AST we need the processed source of a string
39
+ def processed_source_from(string)
40
+ RuboCop::ProcessedSource.new(string, RUBY_VERSION.to_f)
41
+ end
42
+
43
+ # So why bother with the above then? If we end up into correctors
44
+ # and tree-rewrites we need that original processed source to be
45
+ # the basis of the AST, otherwise we get object ID mismatches.
46
+ def ast_from(value)
47
+ case value
48
+ when RuboCop::ProcessedSource
49
+ value.ast
50
+ else
51
+ processed_source_from(value).ast
52
+ end
53
+ end
54
+
55
+ # Not needed quite yet, but could very potentially be used to verify
56
+ # how accurate generated cops are.
57
+ def get_corrector(value)
58
+ RuboCop::Cop::Corrector.new(value.buffer)
59
+ end
60
+
61
+ # Descendants leaves out a _lot_ of detail potentially. There has to
62
+ # be a better way to deal with this, but not thinking of one right now.
63
+ def get_children(source_node)
64
+ recurse = -> parent do
65
+ collected_children = []
66
+ parent.children.each do |child|
67
+ next if child.nil?
68
+
69
+ # If it's a regular parent carry on as you were
70
+ unless atom?(child) # || childless?(child)
71
+ collected_children.concat([child, *recurse[child]])
72
+ next
73
+ end
74
+
75
+ # Otherwise we want to find where that parent is in the source code,
76
+ # and the range it exists in, to create an `AtomNode`
77
+ source = child.to_s
78
+
79
+ parent_begin = parent.location.expression.to_range.begin
80
+ range_begin = source_node.source.index(source, parent_begin)
81
+ range_end = range_begin + source.size
82
+ range = range_begin..range_end
83
+
84
+ collected_children << AtomNode.new(parent:, source:, range:)
85
+ end
86
+
87
+ collected_children
88
+ end
89
+
90
+ [source_node, *recurse[source_node]]
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,36 @@
1
+ module OffenseToCorrector
2
+ # The annoying thing is that `RuboCop::AST::Node` 's children / descendant
3
+ # methods don't capture all the relevant data, so we have to cheat a bit
4
+ # by wrapping atoms (String, Symbol, Int, etc) in a class to get around that.
5
+ class AtomNode
6
+ attr_reader :source, :range, :parent
7
+
8
+ def initialize(source:, range:, parent:)
9
+ @source = source
10
+ @range = range
11
+ @parent = parent
12
+ end
13
+
14
+ def to_s
15
+ relevant_children = @parent.children.map do |child|
16
+ next "..." unless child.to_s == self.source.to_s # Wildcard
17
+
18
+ case child
19
+ when String then %("#{child}") # Literal string
20
+ when Symbol then ":#{child}" # Literal symbol
21
+ when nil then "nil?"
22
+ else child
23
+ end
24
+ end
25
+
26
+ # The trick here is that these aren't nodes, but we do care about
27
+ # what the "parent" is that contains it to get something we can
28
+ # work with. All other children are replaced with wildcards.
29
+ "(#{self.parent.type} #{relevant_children.join(' ')})"
30
+ end
31
+
32
+ def deconstruct_keys(keys)
33
+ { source:, range:, parent: }
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,35 @@
1
+ module OffenseToCorrector
2
+ class Offense
3
+ # How many underline carots (^), and potentially an error after
4
+ OFFENSE_MATCH = /^ *?(?<underline>\^+) *?(?<error>.*)?$/
5
+
6
+ attr_reader :error, :range, :source
7
+
8
+ def initialize(error:, range:, source:)
9
+ @error = error
10
+ @range = range
11
+ @source = source
12
+ end
13
+
14
+ def deconstruct_keys(keys)
15
+ { error:, range:, source: }
16
+ end
17
+
18
+ # See if there's an underline, if so get how long it is and
19
+ # the error message after it
20
+ #
21
+ def self.parse(line:, previous_line:)
22
+ match_data = OFFENSE_MATCH.match(line) or return nil
23
+ underline = match_data[:underline]
24
+ error = match_data[:error].lstrip
25
+
26
+ underline_start = line.index(underline)
27
+ underline_end = underline_start + underline.size
28
+ range = underline_start...underline_end
29
+
30
+ source = previous_line.slice(range).chomp
31
+
32
+ new(error:, range:, source:)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,86 @@
1
+ module OffenseToCorrector
2
+ class OffenseParser
3
+ include AstTools
4
+
5
+ attr_reader :ast, :source, :offense, :ast_nodes
6
+
7
+ def initialize(string)
8
+ @ast_lines, @offense = parse_string(string)
9
+ @source = processed_source_from(@ast_lines.join("\n"))
10
+ @ast = ast_from(@source)
11
+ @ast_nodes = get_children(@ast)
12
+ @template = OffenseTemplate.new
13
+ end
14
+
15
+ # Render that into the cop skeleton. Perhaps having too much
16
+ # fun here with rightward assignment.
17
+ def render
18
+ call => {
19
+ offense: { error: },
20
+ node_offense_info: { offending_node:, offending_node_matcher: }
21
+ }
22
+
23
+ @template.render(
24
+ class_name: "TODO",
25
+ match_pattern: offending_node_matcher,
26
+ error_message: error,
27
+ node_type: offending_node.type,
28
+ cop_type: "Lint",
29
+ node_location: "selector",
30
+ offense_severity: "warning"
31
+ )
32
+ end
33
+
34
+ # Quick info on what all is being worked on and what info we got
35
+ def deconstruct_keys(keys)
36
+ { ast:, source:, offense:, node_offense_info: }
37
+ end
38
+
39
+ def node_offense_info
40
+ return @node_offense_info if defined?(@node_offense_info)
41
+
42
+ # Find the node, or atom, with the most overlap with the offense
43
+ # range defined by that underline. That's defined as a pair of
44
+ # the length of the intersection as well as what percentage that
45
+ # intersection makes of the full node source.
46
+ offending_node = @ast_nodes.max_by do |node|
47
+ intersection = string_intersection(node.source, @offense.source)
48
+ [intersection.size, intersection.size.fdiv(node.source.size)]
49
+ end
50
+
51
+ @node_offense_info ||= {
52
+ offending_node:,
53
+ offending_node_matcher: offending_node.to_s
54
+ }
55
+ end
56
+
57
+ private def node_range(node)
58
+ if node.is_a?(AtomNode)
59
+ node.range
60
+ else
61
+ node.location.expression.to_range
62
+ end
63
+ end
64
+
65
+ # Figure which part of the passed in code string is AST vs offense
66
+ private def parse_string(string)
67
+ ast_lines = []
68
+ offense = nil
69
+ lines = string.lines
70
+
71
+ lines.each_with_index do |line, i|
72
+ # No sense of a meta-line being above every other line
73
+ meta_info = i > 0 && Offense.parse(line:, previous_line: lines[i - 1])
74
+
75
+ if meta_info
76
+ raise "Cannot have multiple offenses" unless offense.nil?
77
+ offense = meta_info
78
+ else
79
+ ast_lines << line
80
+ end
81
+ end
82
+
83
+ [ast_lines, offense]
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,30 @@
1
+ module OffenseToCorrector
2
+ # ERB template for rendering a cop skeleton, may make this more useful
3
+ # later, but mostly quick templating for now.
4
+ class OffenseTemplate
5
+ def initialize(name: "autocorrector_template.erb")
6
+ @template = File.read(OffenseToCorrector.load_template(name))
7
+ @erb = ERB.new(@template)
8
+ end
9
+
10
+ def render(
11
+ class_name: "TODO",
12
+ match_pattern:,
13
+ error_message: "",
14
+ node_type:,
15
+ cop_type: "Lint",
16
+ node_location: "selector",
17
+ offense_severity: "warning"
18
+ )
19
+ @erb.result_with_hash(
20
+ class_name:,
21
+ match_pattern:,
22
+ error_message:,
23
+ node_type:,
24
+ cop_type:,
25
+ node_location:,
26
+ offense_severity:
27
+ )
28
+ end
29
+ end
30
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OffenseToCorrector
4
- VERSION = "0.0.1"
4
+ VERSION = "0.0.2"
5
5
  end
@@ -5,6 +5,12 @@ require_relative "offense_to_corrector/version"
5
5
  require "rubocop"
6
6
  require "erb"
7
7
 
8
+ require "offense_to_corrector/atom_node"
9
+ require "offense_to_corrector/ast_tools"
10
+ require "offense_to_corrector/offense"
11
+ require "offense_to_corrector/offense_parser"
12
+ require "offense_to_corrector/offense_template"
13
+
8
14
  module OffenseToCorrector
9
15
  module_function def load_template(name)
10
16
  File.join(File.dirname(__FILE__), "offense_to_corrector/templates", name)
@@ -17,242 +23,4 @@ module OffenseToCorrector
17
23
  module_function def offense_to_cop(code)
18
24
  OffenseParser.new(code).render
19
25
  end
20
-
21
- # The annoying thing is that `RuboCop::AST::Node` 's children / descendant
22
- # methods don't capture all the relevant data, so we have to cheat a bit
23
- # by wrapping atoms (String, Symbol, Int, etc) in a class to get around that.
24
- AtomNode = Struct.new(:source, :range, :parent, keyword_init: true) do
25
- def type
26
- self.parent.type
27
- end
28
-
29
- def to_s
30
- relevant_children = self.parent.children.map do |c|
31
- next "..." unless c.to_s == self.source.to_s # Wildcard
32
-
33
- case c
34
- when String then %("#{c}") # Literal string
35
- when Symbol then ":#{c}" # Literal symbol
36
- else c
37
- end
38
- end
39
-
40
- # The trick here is that these aren't nodes, but we do care about
41
- # what the "parent" is that contains it to get something we can
42
- # work with. All other children are replaced with wildcards.
43
- "(#{self.parent.type} #{relevant_children.join(' ')})"
44
- end
45
- end
46
-
47
- module AstTools
48
- def atom?(value)
49
- !value.is_a?(RuboCop::AST::Node)
50
- end
51
-
52
- # I may have to work on getting this one later to try and
53
- # narrow the band of `AtomNode`
54
- # def childless?(node)
55
- # false # TODO
56
- # end
57
-
58
- # How much do two ranges overlap? Used to see how well a node
59
- # matches with the associated underline
60
- def range_overlap_count(a, b)
61
- return (b.begin...[a.end, b.end].min).size if a.cover?(b.begin)
62
- return (a.begin...[a.end, b.end].min).size if b.cover?(a.begin)
63
-
64
- 0
65
- end
66
-
67
- # See if a range overlaps another one, bidirectional
68
- def range_overlap?(a, b)
69
- a.cover?(b.begin) || b.cover?(a.begin)
70
- end
71
-
72
- # To get an AST we need the processed source of a string
73
- def processed_source_from(string)
74
- RuboCop::ProcessedSource.new(string, RUBY_VERSION.to_f)
75
- end
76
-
77
- # So why bother with the above then? If we end up into correctors
78
- # and tree-rewrites we need that original processed source to be
79
- # the basis of the AST, otherwise we get object ID mismatches.
80
- def ast_from(value)
81
- case value
82
- when RuboCop::ProcessedSource
83
- value.ast
84
- else
85
- processed_source_from(value).ast
86
- end
87
- end
88
-
89
- # Not needed quite yet, but could very potentially be used to verify
90
- # how accurate generated cops are.
91
- def get_corrector(value)
92
- RuboCop::Cop::Corrector.new(value.buffer)
93
- end
94
-
95
- # Descendants leaves out a _lot_ of detail potentially. There has to
96
- # be a better way to deal with this, but not thinking of one right now.
97
- def get_children(source_node)
98
- recurse = -> node do
99
- collected_children = []
100
- node.children.each do |child|
101
- next if child.nil?
102
-
103
- # If it's a regular node carry on as you were
104
- unless atom?(child) # || childless?(child)
105
- collected_children.concat([child, *recurse[child]])
106
- next
107
- end
108
-
109
- # Otherwise we want to find where that node is in the source code,
110
- # and the range it exists in, to create an `AtomNode`
111
- child_string = child.to_s
112
- range_begin = source_node.source.index(/\b#{child_string}\b/)
113
- range_end = range_begin + child_string.size
114
-
115
- collected_children << AtomNode.new(
116
- parent: node,
117
- source: child_string,
118
- range: range_begin..range_end
119
- )
120
- end
121
-
122
- collected_children
123
- end
124
-
125
- [source_node, *recurse[source_node]]
126
- end
127
- end
128
-
129
- # ERB template for rendering a cop skeleton, may make this more useful
130
- # later, but mostly quick templating for now.
131
- class OffenseTemplate
132
- def initialize(name: "autocorrector_template.erb")
133
- @template = File.read(OffenseToCorrector.load_template(name))
134
- @erb = ERB.new(@template)
135
- end
136
-
137
- def render(
138
- class_name: "TODO",
139
- match_pattern:,
140
- error_message: "",
141
- node_type:,
142
- cop_type: "Lint",
143
- node_location: "selector",
144
- offense_severity: "warning"
145
- )
146
- @erb.result_with_hash(
147
- class_name:,
148
- match_pattern:,
149
- error_message:,
150
- node_type:,
151
- cop_type:,
152
- node_location:,
153
- offense_severity:
154
- )
155
- end
156
- end
157
-
158
- # Bit more of a structured container for an offense (the underline)
159
- Offense = Struct.new(:line, :error, :range, keyword_init: true)
160
-
161
- class OffenseParser
162
- include AstTools
163
-
164
- # How many underline carots (^), and potentially an error after
165
- OFFENSE_MATCH = /^ *?(?<underline>\^+) *?(?<error>.*)?$/
166
-
167
- attr_reader :ast, :source, :offense
168
-
169
- def initialize(string)
170
- @ast_lines, @offense = parse_string(string)
171
- @source = processed_source_from(@ast_lines.join("\n"))
172
- @ast = ast_from(@source)
173
- @ast_nodes = get_children(@ast)
174
- @template = OffenseTemplate.new
175
- end
176
-
177
- # Render that into the cop skeleton. Perhaps having too much
178
- # fun here with rightward assignment.
179
- def render
180
- call => {
181
- offense: { error: },
182
- node_offense_info: { offending_node:, offending_node_matcher: }
183
- }
184
-
185
- @template.render(
186
- class_name: "TODO",
187
- match_pattern: offending_node_matcher,
188
- error_message: error,
189
- node_type: offending_node.type,
190
- cop_type: "Lint",
191
- node_location: "selector",
192
- offense_severity: "warning"
193
- )
194
- end
195
-
196
- # Quick info on what all is being worked on and what info we got
197
- def call
198
- { ast:, offense:, node_offense_info: }
199
- end
200
-
201
- def node_offense_info
202
- return @node_offense_info if defined?(@node_offense_info)
203
-
204
- # Find the node, or atom, with the most overlap with the offense
205
- # range defined by that underline.
206
- offending_node = @ast_nodes.max do |node|
207
- node_range = if node.is_a?(AtomNode)
208
- node.range
209
- else
210
- node.location.expression.to_range
211
- end
212
-
213
- # Except we're doing it as a percentage, otherwise parent nodes
214
- # will dominate that count potentially. The closer to 100% overlap
215
- # the better.
216
- overlap_count = range_overlap_count(node_range, @offense[:range])
217
- overlap_count.fdiv(node.source.size)
218
- end
219
-
220
- @node_offense_info ||= {
221
- offending_node:,
222
- offending_node_matcher: offending_node.to_s
223
- }
224
- end
225
-
226
- # See if there's an underline, if so get how long it is and
227
- # the error message after it
228
- private def offending_meta_from(line)
229
- match_data = OFFENSE_MATCH.match(line) or return nil
230
- underline = match_data[:underline]
231
- error = match_data[:error].lstrip
232
-
233
- underline_start = line.index(underline)
234
- underline_end = underline_start + underline.size
235
-
236
- { match_data:, error:, range: underline_start..underline_end }
237
- end
238
-
239
- # Figure which part of the passed in code string is AST vs offense
240
- private def parse_string(string)
241
- ast_lines = []
242
- offense = nil
243
-
244
- string.lines.each_with_index do |line, i|
245
- meta_info = offending_meta_from(line)
246
-
247
- if meta_info
248
- raise "Cannot have multiple offenses" unless offense.nil?
249
- offense = Offense.new(line: i, **meta_info.slice(:error, :range))
250
- else
251
- ast_lines << line
252
- end
253
- end
254
-
255
- [ast_lines, offense]
256
- end
257
- end
258
26
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: offense_to_corrector
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brandon Weaver
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: guard-rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  description:
28
42
  email:
29
43
  - keystonelemur@gmail.com
@@ -36,10 +50,16 @@ files:
36
50
  - CODE_OF_CONDUCT.md
37
51
  - Gemfile
38
52
  - Gemfile.lock
53
+ - Guardfile
39
54
  - LICENSE.txt
40
55
  - README.md
41
56
  - Rakefile
42
57
  - lib/offense_to_corrector.rb
58
+ - lib/offense_to_corrector/ast_tools.rb
59
+ - lib/offense_to_corrector/atom_node.rb
60
+ - lib/offense_to_corrector/offense.rb
61
+ - lib/offense_to_corrector/offense_parser.rb
62
+ - lib/offense_to_corrector/offense_template.rb
43
63
  - lib/offense_to_corrector/templates/autocorrector_template.erb
44
64
  - lib/offense_to_corrector/version.rb
45
65
  - sig/offense_to_corrector.rbs