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 +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: []
|