syntax_finder 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f396dd84ec243335733edbe3abc5c22c0f32ea6ade48dfc16336ad385f0a2bd8
4
+ data.tar.gz: 3e6c73de9f48e65ed5f645b7c0b8c25c139826202d739ca884545c1ff48a7b40
5
+ SHA512:
6
+ metadata.gz: 0ce254cd70a3952f0a1d976cbd5ba9fe776ebb81137d8387d19b917548e4e3a7c5521ecdece26c70ebd712a67491327d1379855ad4dc058780f845b4bbaef0d0
7
+ data.tar.gz: 78a82aa09159d3cf9e764ea342c71e60a21574f0b55acfb76510ab802bdb8a9e7b5a042c200fdf62704958dce4304aa864f3d9a66211c19f707eb55ab46c58a8
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Koichi Sasada
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # SyntaxFinder
2
+
3
+ Find Ruby syntax patterns with Prism.
4
+
5
+ ## How to use
6
+
7
+ Write your pattern in Ruby.
8
+
9
+ ```ruby
10
+ # samples/if_then_finder.rb
11
+
12
+ require 'syntax_finder'
13
+
14
+ # Count up all `if` statements and `then` keywords.
15
+
16
+ class IfThenFinder < SyntaxFinder
17
+ def look node
18
+ if node.type == :if_node
19
+ inc :if
20
+ inc :then if node.then_keyword_loc
21
+ end
22
+ end
23
+ end
24
+ ```
25
+
26
+ and run the script with Ruby script file names listed in STDIN like this.
27
+
28
+ ```sh
29
+ $ find ruby/ruby -name '*.rb' | ruby samples/if_then_finder.rb
30
+ [[:if, 32153], [:FILES, 9558], [:then, 3627], [:FAILED_PARSE, 5]]
31
+ ```
32
+
33
+ In this case, with *.rb files in ruby/ruby directory, there are
34
+
35
+ * 9,558 files
36
+ * 32,153 if statements
37
+ * 3,627 then keywords
38
+ * 5 files are failed because of parsing.
39
+
40
+ You can specify files with arguments like that:
41
+
42
+ ```sh
43
+ $ ruby samples/if_then_finder.rb samples/*.rb
44
+ [[[:FILES, 16]], [[:if, 26], [:then, 1]]]
45
+ ```
46
+
47
+ You can specify `-j` or `-jN` for parallel processing like that:
48
+
49
+ ```sh
50
+ $ time find ruby/ruby -name '*.rb' | ruby samples/if_then_finder.rb
51
+ [[:if, 32153], [:FILES, 9558], [:then, 3627], [:FAILED_PARSE, 5]]
52
+
53
+ real 0m3.627s
54
+ user 0m3.451s
55
+ sys 0m0.219s
56
+
57
+ $ time find ruby/ruby -name '*.rb' | ruby samples/if_then_finder.rb -j
58
+ [[:if, 32153], [:FILES, 9558], [:then, 3627], [:FAILED_PARSE, 5]]
59
+
60
+ real 0m0.962s
61
+ user 0m7.944s
62
+ sys 0m0.854s
63
+ ```
64
+
65
+ ## How to write a finder
66
+
67
+ 1. Define a class derived with `SyntaxFinder` class.
68
+ 2. Define a `look(node)` method with your expected pattern. `node` is Prism's node.
69
+ 3. Use `inc(key)` if you want to aggregate the statistics in `look()`.
70
+
71
+ See `examples/` for more examples.
72
+
73
+ ## Development
74
+
75
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test-unit` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
76
+
77
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
78
+
79
+ ## Contributing
80
+
81
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ko1/syntax_finder.
82
+
83
+ ## License
84
+
85
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
11
+
12
+ task default: :test
@@ -0,0 +1,3 @@
1
+ class SyntaxFinder
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,272 @@
1
+ require 'prism'
2
+ require 'optparse'
3
+ require_relative 'syntax_finder/version'
4
+
5
+ class SyntaxFinder
6
+ def initialize file
7
+ @file = file
8
+ end
9
+
10
+ ### Traversing and looking methods
11
+
12
+ # Please write your pattern here to investigate the node.
13
+ # This method is called for each node.
14
+ def look node
15
+ # override here
16
+ end
17
+
18
+ # If you need to pass some context while traversing,
19
+ # redefine this method with context like `def traverse node, context = nil`.
20
+ def traverse node
21
+ look node
22
+ traverse_rest node
23
+ end
24
+
25
+ def traverse_rest node
26
+ node.child_nodes.compact.each do |child|
27
+ traverse child
28
+ end
29
+ end
30
+
31
+ ### Utility methods for look
32
+
33
+ # node location information
34
+ def nloc node
35
+ "#{@file}:#{node.location.start_line}"
36
+ end
37
+
38
+ # node line (1st line of node lines)
39
+ def nline node
40
+ node.slice_lines.lines.first.chomp
41
+ end
42
+
43
+ # node lines
44
+ def nlines node
45
+ node.slice_lines
46
+ end
47
+
48
+ def success? r
49
+ return true if r.success?
50
+
51
+ if r.errors.size == 1 &&
52
+ r.errors.first.type == :script_not_found
53
+ true
54
+ else
55
+ false
56
+ end
57
+ end
58
+
59
+ # any object can be a key.
60
+ # if key is [A-Z_]+, it's system key.
61
+ # it's returns true if the key is already exists.
62
+ def inc key
63
+ has_key = @@result.has_key? key
64
+ @@result[key] += 1
65
+ has_key
66
+ end
67
+
68
+ def note key, note
69
+ @@result_notes[key] << note
70
+ end
71
+
72
+ def self.inc key
73
+ @@result[key] += 1
74
+ end
75
+
76
+ def self.note key, note
77
+ @@result_notes[key] << note
78
+ end
79
+
80
+ ### Execution management
81
+
82
+ def start_traverse
83
+ puts "Start #{@file}" if $DEBUG
84
+
85
+ @root = Prism.parse_file(@file)
86
+ if success?(@root)
87
+ traverse @ast = @root.value
88
+ else
89
+ # pp [@file, @root.errors]
90
+ # inc :FAILED, @file
91
+ inc :FAILED_PARSE
92
+ end
93
+ rescue Interrupt
94
+ exit
95
+ rescue Exception => e
96
+ pp [e, @file, e.backtrace] if $DEBUG
97
+ inc [:FAILED, e.class]
98
+ end
99
+
100
+ @@finders = [self]
101
+
102
+ def self.inherited klass
103
+ @@finders << klass
104
+ end
105
+
106
+ def self.last_finder
107
+ @@finders.last
108
+ end
109
+
110
+ @@result = Hash.new(0)
111
+ @@result_notes = Hash.new{|h, k| h[k] = []}
112
+ @@opt = {}
113
+
114
+ def self.setup_parallel finder, pn
115
+ taskq = @@opt[:taskq] = Queue.new
116
+ resq = @@opt[:resq] = Queue.new
117
+ term_cmd = '-- terminate'
118
+
119
+ @@opt[:threads] = pn.times.map{
120
+ Thread.new _1 do |ti|
121
+ task_r, task_w = IO.pipe
122
+ res_r, res_w = IO.pipe
123
+
124
+ pid = fork do
125
+ trap(:INT, 'IGNORE')
126
+
127
+ loop do
128
+ file = task_r.gets.chomp
129
+
130
+ case file
131
+ when term_cmd
132
+ @@result_notes.default_proc = nil
133
+ last_result = [@@result, @@result_notes]
134
+ break
135
+ else
136
+ kick finder, file
137
+ last_result = nil
138
+ end
139
+ rescue Interrupt
140
+ STDERR.puts "Worker is interrupted (pid: #{Process.pid})"
141
+ @@result_notes.default_proc = nil
142
+ last_result = [@@result, @@result_notes]
143
+ exit
144
+ ensure
145
+ res_w.puts Marshal.dump(last_result).dump
146
+ end
147
+ end
148
+
149
+ result = nil
150
+
151
+ while true
152
+ file = taskq.pop
153
+ task_w.puts file || term_cmd
154
+ result = Marshal.load(res_r.gets.chomp.undump) # wait for the result
155
+
156
+ if result
157
+ resq << result
158
+ break
159
+ end
160
+ end
161
+ ensure
162
+ p [Thread.current, $!] if $DEBUG
163
+ Process.kill :KILL, pid
164
+ Process.waitpid pid
165
+ end
166
+ }
167
+ end
168
+
169
+ def self.cleanup_parallel
170
+ return unless taskq = @@opt[:taskq]
171
+ trap(:INT){
172
+ trap(:INT, 'DEFAULT')
173
+ }
174
+ taskq.close
175
+
176
+ @@opt[:parallel].times do |i|
177
+ results, notes = @@opt[:resq].pop
178
+ @@result.merge!(results){|k, a, b| a + b}
179
+ @@result_notes.merge!(notes)
180
+ end
181
+ end
182
+
183
+ def self.parse_opt finder, argv
184
+ o = OptionParser.new
185
+ o.on '-v', '--verbose', 'VERBOSE mode' do
186
+ $VERBOSE = true
187
+ end
188
+
189
+ o.on '-d', '--debug' do
190
+ $DEBUG = true
191
+ end
192
+
193
+ o.on '-q', '--quiet' do
194
+ @@opt[:quiet] = true
195
+ end
196
+
197
+ o.on '-j', '--jobs[=N]', 'Parallel mode' do
198
+ require 'etc'
199
+
200
+ pn = @@opt[:parallel] = _1&.to_i || Etc.nprocessors
201
+ setup_parallel finder, pn
202
+ end
203
+
204
+ o.parse! argv
205
+ pp options: @@opt if $DEBUG
206
+ end
207
+
208
+ def self.kick finder, file
209
+ finder.new(file).start_traverse
210
+ end
211
+
212
+ def self.aggregate_results
213
+ if @@result_notes.size > 0
214
+ results = @@result.sort_by{|k, v| -v}.map{|k, v|
215
+ if !(note = @@result_notes[k]).empty?
216
+ [k, v, note]
217
+ else
218
+ [k, v]
219
+ end
220
+ }
221
+ else
222
+ results = @@result.sort_by{|k, v| -v}
223
+ end
224
+
225
+ results.partition{|key,|
226
+ Symbol === key && key.length > 1 && /^[A-Z_]+$/ =~ key
227
+ }
228
+ end
229
+
230
+ def self.print_result
231
+ system_results, user_results = aggregate_results
232
+ pp [system_results, user_results]
233
+ end
234
+
235
+ def self.run finder, argv = ARGV
236
+ @already_run = true
237
+ parse_opt finder, argv
238
+
239
+ files = argv.empty? ? ARGF : argv
240
+ files.each do |f|
241
+ f = f.scrub.strip
242
+ next unless FileTest.file?(f)
243
+
244
+ inc :FILES
245
+
246
+ if q = @@opt[:taskq]
247
+ q << f
248
+ else
249
+ kick finder, f
250
+ end
251
+ end
252
+ rescue Interrupt
253
+ STDERR.puts "-- Interrupted"
254
+ exit
255
+ ensure
256
+ cleanup_parallel
257
+ finder.print_result if finder
258
+ end
259
+
260
+ END{
261
+ if !@already_run && @@finders.size == 2
262
+ self.run last_finder
263
+ end
264
+ }
265
+ end
266
+
267
+ # for pretty integer results
268
+ class Integer
269
+ def inspect
270
+ self.to_s.gsub(/(\d)(?=\d{3}+$)/, '\\1_')
271
+ end
272
+ end
@@ -0,0 +1,27 @@
1
+ require 'syntax_finder'
2
+
3
+ # Cound up all of local variable names.
4
+
5
+ class LVarFinder < SyntaxFinder
6
+ def look node
7
+ if node.respond_to? :locals
8
+ node.locals.each{|lv|
9
+ case lv
10
+ when Symbol
11
+ inc lv
12
+ when Prism::BlockLocalVariableNode
13
+ inc lv.name
14
+ else
15
+ pp [node.type, lv.type, node.locals].inspect
16
+ exit
17
+ end
18
+ }
19
+ end
20
+ end
21
+
22
+ def self.print_result
23
+ @@result.sort_by{|lv, v| [-v, lv]}.each{|lv, v|
24
+ puts "#{lv}\t#{v}"
25
+ }
26
+ end
27
+ end
@@ -0,0 +1,22 @@
1
+ require 'syntax_finder'
2
+
3
+ # Cound up all of method names.
4
+
5
+ class MethodNameFinder < SyntaxFinder
6
+ def look node
7
+ if node.type == :call_node
8
+ inc [:call, node.name]
9
+ # pp [nloc(node),nlines(node).lines.first.chomp] unless @@opt[:quiet]
10
+ elsif node.type == :def_node
11
+ inc [:def, node.name]
12
+ # pp [nloc(node),nlines(node).lines.first.chomp] unless @@opt[:quiet]
13
+ end
14
+ end
15
+
16
+ def self.print_result
17
+ @@result.sort_by{|(t, m), v| [t, -v]}.each{|(t, m), v|
18
+ puts "#{v}\t#{t}\t#{m}"
19
+ }
20
+ end
21
+ end
22
+
@@ -0,0 +1,13 @@
1
+ relative 'syntax_finder'
2
+
3
+ # Cound up all of regexps.
4
+
5
+ class RegexpFinder < SyntaxFinder
6
+ def look node
7
+ if node.type == :regular_expression_node
8
+ # pp [[nloc(node), nlines(node).lines]] unless @@opt[:quiet]
9
+ inc :regexp
10
+ inc node.unescaped
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ require 'syntax_finder'
2
+
3
+ # cound `assert*(params)` and `assert params` patterns.`
4
+
5
+ class AssertEqualParenFinder < SyntaxFinder
6
+ def look node
7
+ if node.type == :call_node &&
8
+ /^assert/ =~ node.name && node.arguments
9
+ then
10
+ if node.opening_loc
11
+ inc :paren
12
+ else
13
+ inc :no_paren
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ require 'syntax_finder'
2
+
3
+ # Check call with paren or no paren
4
+
5
+ class IfThenFinder < SyntaxFinder
6
+ def look node
7
+ if node.type == :call_node
8
+ has_params = !node.arguments.nil?
9
+ has_paren = !node.opening_loc.nil?
10
+ inc paren: has_paren, params: has_params
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,27 @@
1
+ require 'syntax_finder'
2
+
3
+ # Check def with paren or no paren
4
+
5
+ class DefParamsFinder < SyntaxFinder
6
+ def look node
7
+ if node.type == :def_node
8
+ inc :def
9
+ if node.parameters
10
+ params = {
11
+ required: node.parameters.requireds.size,
12
+ optional: node.parameters.optionals.size,
13
+ rest: !node.parameters.rest.nil?,
14
+ posts: node.parameters.posts.size,
15
+ keywords: node.parameters.keywords.size,
16
+ keyword_rest: !node.parameters.keyword_rest.nil?
17
+ }.reject { |_, v| v == 0 || v == false }
18
+ else
19
+ params = {required: 0}
20
+ end
21
+
22
+ unless inc params
23
+ note params, nloc(node)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ require 'syntax_finder'
2
+
3
+ # Check def with paren or no paren
4
+
5
+ class IfThenFinder < SyntaxFinder
6
+ def look node
7
+ if node.type == :def_node
8
+ has_params = !node.parameters.nil?
9
+ has_paren = !node.lparen_loc.nil?
10
+ inc paren: has_paren, params: has_params
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ require 'syntax_finder'
2
+
3
+ # Check indentation with `if` and `and`/`or`
4
+
5
+ class IfCondContIndnetFinder < SyntaxFinder
6
+ def look node
7
+ if node.type == :if_node
8
+ inc :if
9
+ inc :then if node.then_keyword_loc
10
+ cond = node.predicate
11
+
12
+ case cond.type
13
+ when :and_node, :or_node
14
+ inc op: cond.operator_loc.slice
15
+ d = cond.location.end_line - cond.location.start_line
16
+ if d > 0
17
+ base = node.location.start_column
18
+ rest_lines = nlines(cond).lines[1..]
19
+ inc indent: rest_lines.map{|line|
20
+ /^(\s*)/ =~ line
21
+ $1.size - base
22
+ }.min
23
+ # puts nlines(cond)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+
@@ -0,0 +1,12 @@
1
+ require 'syntax_finder'
2
+
3
+ # Count up all `if` statements and `then` keywords.
4
+
5
+ class IfThenFinder < SyntaxFinder
6
+ def look node
7
+ if node.type == :if_node
8
+ inc :if
9
+ inc :then if node.then_keyword_loc
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ require 'syntax_finder'
2
+
3
+ class IntegerRangeFinder < SYntaxFinder
4
+ def look node
5
+ if node.type == :range_node
6
+ left = node.left
7
+ right = node.right
8
+
9
+ if((left.nil? || left.type == :integer_node) &&
10
+ (right.nil? || right.type == :integer_node))
11
+
12
+ inc :integer_range
13
+ # pp [[nloc(node), nlines(node).lines]] unless @@opt[:quiet]
14
+ inc [left&.value, right&.value].inspect
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ require 'syntax_finder'
2
+
3
+ class IntegerSizeFinder < SyntaxFinder
4
+ def look node
5
+ if node.type == :call_node && node.name == :'size' &&
6
+ node.receiver&.type == :integer_node
7
+ pp [nloc(node), nlines(node).lines.first.chomp]
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,18 @@
1
+ require 'syntax_finder'
2
+
3
+ # count all of given method names via comma separated environment variables,
4
+ # like `NAMES=map,collect`
5
+
6
+ NAMES = ENV['NAMES']&.yield_self{|m| m.split(',').map{|m| m.strip.to_sym}}.to_h{|e| [e, true]} || raise
7
+
8
+ class MethodNameFinder < SyntaxFinder
9
+ def look node
10
+ case t = node.type
11
+ when :call_node, :def_node
12
+ if NAMES[name = node.name]
13
+ inc [t, name]
14
+ pp [nloc(node),nlines(node).lines.first.chomp] if $VERBOSE
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ require 'syntax_finder'
2
+
3
+ class PragmaFinder < SyntaxFinder
4
+ def look node
5
+ @root.magic_comments.each{|c|
6
+ inc key = [c.key, c.value]
7
+ note key, @file if $VERBOSE
8
+ }
9
+
10
+ # No need for recursive travaersal.
11
+ end
12
+
13
+ def traverse node
14
+ look node
15
+ # check only root node
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ require 'syntax_finder'
2
+
3
+ class ReqKWwoParenFinder < SyntaxFinder
4
+ def look node
5
+ if node.type == :call_node &&
6
+ node.opening_loc.nil? &&
7
+ node.arguments &&
8
+ node.arguments.arguments.any?{|n| n.type == :keyword_hash_node && n.elements.any?{|e|
9
+ e.type == :assoc_node &&
10
+ e.operator_loc.nil? &&
11
+ e.key.location.start_line != e.value.location.start_line}}
12
+
13
+ inc :found
14
+ puts "# #{nloc(node)}"
15
+ puts '> ' + nlines(node).lines.join('> ')
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ require 'syntax_finder'
2
+
3
+ class ReqKWwoParenFinder < SyntaxFinder
4
+ def look node
5
+ if node.type == :def_node &&
6
+ node.lparen_loc.nil? &&
7
+ node.parameters&.keywords&.last&.type == :required_keyword_parameter_node &&
8
+ node.parameters&.block.nil?
9
+
10
+ inc :found
11
+ pp [nloc(node), nlines(node).lines.first.chomp]
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ require 'syntax_finder'
2
+
3
+ class SingletonClassInDefFinder < SyntaxFinder
4
+ # use traverse instead of look
5
+ def traverse node, in_def = nil, in_sclass = nil
6
+ if node.type == :class_node && in_sclass && node.constant_path.type != :constant_read_node
7
+ pp [nloc(node), nlines(node).lines.first.chomp]
8
+ end
9
+
10
+ if node.type == :def_node
11
+ in_def = true; in_sclass = false
12
+ elsif node.type == :singleton_class_node && in_def
13
+ in_sclass = true
14
+ end
15
+
16
+ node.child_nodes.compact.each{|n| traverse n, in_def, in_sclass}
17
+ end
18
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: syntax_finder
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Koichi Sasada
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-01-14 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Analyze your Ruby scripts with prism.
14
+ email:
15
+ - ko1@atdot.net
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - LICENSE.txt
21
+ - README.md
22
+ - Rakefile
23
+ - lib/syntax_finder.rb
24
+ - lib/syntax_finder/version.rb
25
+ - samples/all_lvar_finder.rb
26
+ - samples/all_method_name_finder.rb
27
+ - samples/all_regexp_finder.rb
28
+ - samples/assert_equal_paren.rb
29
+ - samples/call_paren_finder.rb
30
+ - samples/def_params_finder.rb
31
+ - samples/def_paren_finder.rb
32
+ - samples/if_cond_cont_indent_finder.rb
33
+ - samples/if_then_finder.rb
34
+ - samples/integer_range_finder.rb
35
+ - samples/integer_size_finder.rb
36
+ - samples/method_name_finder.rb
37
+ - samples/pragma_finder.rb
38
+ - samples/required_kwcall_wo_paren_finder.rb
39
+ - samples/required_kwdef_wo_paren_finder.rb
40
+ - samples/singleton_class_in_def_finder.rb
41
+ homepage: https://github.com/ko1/syntax_finder
42
+ licenses:
43
+ - MIT
44
+ metadata:
45
+ homepage_uri: https://github.com/ko1/syntax_finder
46
+ source_code_uri: https://github.com/ko1/syntax_finder
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: 3.0.0
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubygems_version: 3.5.9
63
+ signing_key:
64
+ specification_version: 4
65
+ summary: Analyze your Ruby scripts with prism.
66
+ test_files: []