syntax_finder 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +85 -0
- data/Rakefile +12 -0
- data/lib/syntax_finder/version.rb +3 -0
- data/lib/syntax_finder.rb +272 -0
- data/samples/all_lvar_finder.rb +27 -0
- data/samples/all_method_name_finder.rb +22 -0
- data/samples/all_regexp_finder.rb +13 -0
- data/samples/assert_equal_paren.rb +17 -0
- data/samples/call_paren_finder.rb +13 -0
- data/samples/def_params_finder.rb +27 -0
- data/samples/def_paren_finder.rb +13 -0
- data/samples/if_cond_cont_indent_finder.rb +29 -0
- data/samples/if_then_finder.rb +12 -0
- data/samples/integer_range_finder.rb +18 -0
- data/samples/integer_size_finder.rb +10 -0
- data/samples/method_name_finder.rb +18 -0
- data/samples/pragma_finder.rb +17 -0
- data/samples/required_kwcall_wo_paren_finder.rb +18 -0
- data/samples/required_kwdef_wo_paren_finder.rb +14 -0
- data/samples/singleton_class_in_def_finder.rb +18 -0
- metadata +66 -0
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,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,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,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: []
|