querly 0.15.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/rubocop.yml +15 -0
- data/.github/workflows/ruby.yml +22 -0
- data/.rubocop.yml +10 -0
- data/CHANGELOG.md +28 -0
- data/README.md +2 -1
- data/Rakefile +1 -1
- data/bin/setup +1 -2
- data/lib/querly.rb +2 -1
- data/lib/querly/cli.rb +27 -9
- data/lib/querly/cli/console.rb +8 -4
- data/lib/querly/cli/find.rb +8 -4
- data/lib/querly/cli/formatter.rb +6 -1
- data/lib/querly/cli/test.rb +1 -1
- data/lib/querly/node_pair.rb +1 -1
- data/lib/querly/pattern/expr.rb +8 -6
- data/lib/querly/pattern/parser.y +8 -9
- data/lib/querly/pp/cli.rb +20 -1
- data/lib/querly/script.rb +20 -0
- data/lib/querly/script_enumerator.rb +18 -24
- data/lib/querly/version.rb +1 -1
- data/manual/patterns.md +14 -2
- data/querly.gemspec +10 -4
- data/sample.yaml +2 -0
- data/template.yml +3 -1
- metadata +69 -18
- data/.travis.yml +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c9b153bfa5b7a49deb8a6bbff12517452d2b23498b371a4fcbc154fac2330dd9
|
4
|
+
data.tar.gz: 7d08df5b8053cbb9e45d6db6b132e6dcb9171797d5687caf96b3827aed53fae2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c2a5371c77896ce523078b014875744f52a248b1e84f04772e0b1ae7eee3cdf876136915c87de876436dc180e9c0b1cbb6ef487a9aaf4dccaf7faad68223546d
|
7
|
+
data.tar.gz: 2671df7da016eeb25038a504ee5c7e203e6079ead0adcf2cdfeb25c02c3a3157ee5a05347f9cdd33e25289d12b28de5a38a15fb595bb258e49accf5c4d818b3d
|
@@ -0,0 +1,15 @@
|
|
1
|
+
name: RuboCop
|
2
|
+
|
3
|
+
on: pull_request
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
rubocop:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
steps:
|
9
|
+
- uses: actions/checkout@v2
|
10
|
+
- uses: ruby/setup-ruby@v1
|
11
|
+
with:
|
12
|
+
ruby-version: "3.0"
|
13
|
+
- run: gem install rubocop rubocop-rubycw
|
14
|
+
- name: Run RuboCop
|
15
|
+
run: rubocop --format github
|
@@ -0,0 +1,22 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- master
|
7
|
+
pull_request: {}
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
test:
|
11
|
+
runs-on: ubuntu-latest
|
12
|
+
strategy:
|
13
|
+
matrix:
|
14
|
+
ruby: ["2.7", "3.0", head]
|
15
|
+
steps:
|
16
|
+
- uses: actions/checkout@v2
|
17
|
+
- uses: ruby/setup-ruby@v1
|
18
|
+
with:
|
19
|
+
ruby-version: ${{ matrix.ruby }}
|
20
|
+
bundler-cache: true
|
21
|
+
- run: bin/setup
|
22
|
+
- run: bundle exec rake build test
|
data/.rubocop.yml
ADDED
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,34 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 1.3.0 (2021-07-05)
|
6
|
+
|
7
|
+
* Require Ruby 2.7 or 3.0 by @yubiquitous ([#88](https://github.com/soutaro/querly/pull/88))
|
8
|
+
* Use 3.0 compatible parser by @yubiquitous ([#88](https://github.com/soutaro/querly/pull/88))
|
9
|
+
|
10
|
+
## 1.2.0 (2020-12-15)
|
11
|
+
|
12
|
+
* Relax Thor version requirements by @y-yagi ([#85](https://github.com/soutaro/querly/pull/85))
|
13
|
+
* Fix ERB comment preprocessing by @mallowlabs ([#84](https://github.com/soutaro/querly/pull/84))
|
14
|
+
* Better error message for Ruby code syntax error by @ybiquitous ([#83](https://github.com/soutaro/querly/pull/83))
|
15
|
+
|
16
|
+
## 1.1.0 (2020-05-17)
|
17
|
+
|
18
|
+
* Fix invalid bytes sequence in UTF-8 error by @mallowlabs [#75](https://github.com/soutaro/querly/pull/75)
|
19
|
+
* Detect safe navigation operator as a method call by @pocke [#71](https://github.com/soutaro/querly/pull/71)
|
20
|
+
|
21
|
+
## 1.0.0 (2019-7-19)
|
22
|
+
|
23
|
+
* Add `--config` option for `find` and `console` [#67](https://github.com/soutaro/querly/pull/67)
|
24
|
+
* Improve preprocessor performance by processing concurrently [#68](https://github.com/soutaro/querly/pull/68)
|
25
|
+
|
26
|
+
## 0.16.0 (2019-04-23)
|
27
|
+
|
28
|
+
* Support string literal pattern (@pocke) [#64](https://github.com/soutaro/querly/pull/64)
|
29
|
+
* Allow underscore method name pattern (@pocke) [#63](https://github.com/soutaro/querly/pull/63)
|
30
|
+
* Add erb support (@hanachin) [#61](https://github.com/soutaro/querly/pull/61)
|
31
|
+
* Add `exit` command on console (@wata727) [#59](https://github.com/soutaro/querly/pull/59)
|
32
|
+
|
5
33
|
## 0.15.1 (2019-03-12)
|
6
34
|
|
7
35
|
* Relax parser version requirement
|
data/README.md
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
![Querly logo](https://github.com/soutaro/querly/blob/master/logo/Querly%20horizontal.png)
|
2
|
+
|
2
3
|
# Querly - Pattern Based Checking Tool for Ruby
|
3
4
|
|
4
|
-
|
5
|
+
![Ruby](https://github.com/soutaro/querly/workflows/Ruby/badge.svg)
|
5
6
|
|
6
7
|
Querly is a query language and tool to find out method calls from Ruby programs.
|
7
8
|
Define rules to check your program with patterns to find out *bad* pieces.
|
data/Rakefile
CHANGED
data/bin/setup
CHANGED
data/lib/querly.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
require 'pathname'
|
2
2
|
require "yaml"
|
3
3
|
require "rainbow"
|
4
|
-
require "parser/
|
4
|
+
require "parser/ruby30"
|
5
5
|
require "set"
|
6
6
|
require "open3"
|
7
7
|
require "active_support/inflector"
|
8
|
+
require "parallel"
|
8
9
|
|
9
10
|
require "querly/version"
|
10
11
|
require 'querly/analyzer'
|
data/lib/querly/cli.rb
CHANGED
@@ -12,6 +12,7 @@ module Querly
|
|
12
12
|
option :root
|
13
13
|
option :format, default: "text", type: :string, enum: %w(text json)
|
14
14
|
option :rule, type: :string
|
15
|
+
option :threads, default: Parallel.processor_count, type: :numeric
|
15
16
|
def check(*paths)
|
16
17
|
require 'querly/cli/formatter'
|
17
18
|
|
@@ -23,6 +24,8 @@ module Querly
|
|
23
24
|
end
|
24
25
|
formatter.start
|
25
26
|
|
27
|
+
threads = Integer(options[:threads])
|
28
|
+
|
26
29
|
begin
|
27
30
|
unless config_path.file?
|
28
31
|
STDERR.puts <<-Message
|
@@ -32,20 +35,15 @@ Specify configuration file by --config option.
|
|
32
35
|
exit 1
|
33
36
|
end
|
34
37
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
config = begin
|
39
|
-
yaml = YAML.load(config_path.read)
|
40
|
-
Config.load(yaml, config_path: config_path, root_dir: root_path, stderr: STDERR)
|
38
|
+
begin
|
39
|
+
config = config(root_option: options[:root])
|
41
40
|
rescue => exn
|
42
41
|
formatter.config_error config_path, exn
|
43
|
-
exit 1
|
44
42
|
end
|
45
43
|
|
46
44
|
analyzer = Analyzer.new(config: config, rule: options[:rule])
|
47
45
|
|
48
|
-
ScriptEnumerator.new(paths: paths.empty? ? [Pathname.pwd] : paths.map {|path| Pathname(path) }, config: config).each do |path, script|
|
46
|
+
ScriptEnumerator.new(paths: paths.empty? ? [Pathname.pwd] : paths.map {|path| Pathname(path) }, config: config, threads: threads).each do |path, script|
|
49
47
|
case script
|
50
48
|
when Script
|
51
49
|
analyzer.scripts << script
|
@@ -67,6 +65,8 @@ Specify configuration file by --config option.
|
|
67
65
|
end
|
68
66
|
|
69
67
|
desc "console [paths]", "Start console for given paths"
|
68
|
+
option :config, default: "querly.yml"
|
69
|
+
option :threads, default: Parallel.processor_count, type: :numeric
|
70
70
|
def console(*paths)
|
71
71
|
require 'querly/cli/console'
|
72
72
|
home_path = if (path = ENV["QUERLY_HOME"])
|
@@ -75,21 +75,32 @@ Specify configuration file by --config option.
|
|
75
75
|
Pathname(Dir.home) + ".querly"
|
76
76
|
end
|
77
77
|
home_path.mkdir unless home_path.exist?
|
78
|
+
config = config_path.file? ? config(root_option: nil) : nil
|
79
|
+
threads = Integer(options[:threads])
|
78
80
|
|
79
81
|
Console.new(
|
80
82
|
paths: paths.empty? ? [Pathname.pwd] : paths.map {|path| Pathname(path) },
|
81
83
|
history_path: home_path + "history",
|
82
|
-
history_size: ENV["QUERLY_HISTORY_SIZE"]&.to_i || 1_000_000
|
84
|
+
history_size: ENV["QUERLY_HISTORY_SIZE"]&.to_i || 1_000_000,
|
85
|
+
config: config,
|
86
|
+
threads: threads
|
83
87
|
).start
|
84
88
|
end
|
85
89
|
|
86
90
|
desc "find pattern [paths]", "Find for the pattern in given paths"
|
91
|
+
option :config, default: "querly.yml"
|
92
|
+
option :threads, default: Parallel.processor_count, type: :numeric
|
87
93
|
def find(pattern, *paths)
|
88
94
|
require 'querly/cli/find'
|
89
95
|
|
96
|
+
config = config_path.file? ? config(root_option: nil) : nil
|
97
|
+
threads = Integer(options[:threads])
|
98
|
+
|
90
99
|
Find.new(
|
91
100
|
pattern: pattern,
|
92
101
|
paths: paths.empty? ? [Pathname.pwd] : paths.map {|path| Pathname(path) },
|
102
|
+
config: config,
|
103
|
+
threads: threads
|
93
104
|
).start
|
94
105
|
end
|
95
106
|
|
@@ -125,6 +136,13 @@ Specify configuration file by --config option.
|
|
125
136
|
|
126
137
|
private
|
127
138
|
|
139
|
+
def config(root_option:)
|
140
|
+
root_path = root_option ? Pathname(root_option).realpath : config_path.parent.realpath
|
141
|
+
|
142
|
+
yaml = YAML.load(config_path.read)
|
143
|
+
Config.load(yaml, config_path: config_path, root_dir: root_path, stderr: STDERR)
|
144
|
+
end
|
145
|
+
|
128
146
|
def config_path
|
129
147
|
[Pathname(options[:config]),
|
130
148
|
Pathname("querly.yaml")].compact.find(&:file?) || Pathname(options[:config])
|
data/lib/querly/cli/console.rb
CHANGED
@@ -8,13 +8,17 @@ module Querly
|
|
8
8
|
attr_reader :paths
|
9
9
|
attr_reader :history_path
|
10
10
|
attr_reader :history_size
|
11
|
+
attr_reader :config
|
11
12
|
attr_reader :history
|
13
|
+
attr_reader :threads
|
12
14
|
|
13
|
-
def initialize(paths:, history_path:, history_size:)
|
15
|
+
def initialize(paths:, history_path:, history_size:, config: nil, threads:)
|
14
16
|
@paths = paths
|
15
17
|
@history_path = history_path
|
16
18
|
@history_size = history_size
|
19
|
+
@config = config
|
17
20
|
@history = []
|
21
|
+
@threads = threads
|
18
22
|
end
|
19
23
|
|
20
24
|
def start
|
@@ -42,9 +46,9 @@ Querly #{VERSION}, interactive console
|
|
42
46
|
def analyzer
|
43
47
|
return @analyzer if @analyzer
|
44
48
|
|
45
|
-
@analyzer = Analyzer.new(config:
|
49
|
+
@analyzer = Analyzer.new(config: config, rule: nil)
|
46
50
|
|
47
|
-
ScriptEnumerator.new(paths: paths, config:
|
51
|
+
ScriptEnumerator.new(paths: paths, config: config, threads: threads).each do |path, script|
|
48
52
|
case script
|
49
53
|
when Script
|
50
54
|
@analyzer.scripts << script
|
@@ -60,7 +64,7 @@ Querly #{VERSION}, interactive console
|
|
60
64
|
def start_loop
|
61
65
|
while line = Readline.readline("> ", true)
|
62
66
|
case line
|
63
|
-
when "quit"
|
67
|
+
when "quit", "exit"
|
64
68
|
exit
|
65
69
|
when "reload!"
|
66
70
|
STDOUT.print "reloading..."
|
data/lib/querly/cli/find.rb
CHANGED
@@ -7,10 +7,14 @@ module Querly
|
|
7
7
|
|
8
8
|
attr_reader :pattern_str
|
9
9
|
attr_reader :paths
|
10
|
+
attr_reader :config
|
11
|
+
attr_reader :threads
|
10
12
|
|
11
|
-
def initialize(pattern:, paths:)
|
13
|
+
def initialize(pattern:, paths:, config: nil, threads:)
|
12
14
|
@pattern_str = pattern
|
13
15
|
@paths = paths
|
16
|
+
@config = config
|
17
|
+
@threads = threads
|
14
18
|
end
|
15
19
|
|
16
20
|
def start
|
@@ -36,7 +40,7 @@ module Querly
|
|
36
40
|
puts "#{count} results"
|
37
41
|
rescue => exn
|
38
42
|
STDOUT.puts Rainbow("Error: #{exn}").red
|
39
|
-
|
43
|
+
STDOUT.puts "pattern: #{pattern_str}"
|
40
44
|
STDOUT.puts "Backtrace:"
|
41
45
|
STDOUT.puts format_backtrace(exn.backtrace)
|
42
46
|
end
|
@@ -48,9 +52,9 @@ module Querly
|
|
48
52
|
def analyzer
|
49
53
|
return @analyzer if @analyzer
|
50
54
|
|
51
|
-
@analyzer = Analyzer.new(config:
|
55
|
+
@analyzer = Analyzer.new(config: config, rule: nil)
|
52
56
|
|
53
|
-
ScriptEnumerator.new(paths: paths, config:
|
57
|
+
ScriptEnumerator.new(paths: paths, config: config, threads: threads).each do |path, script|
|
54
58
|
case script
|
55
59
|
when Script
|
56
60
|
@analyzer.scripts << script
|
data/lib/querly/cli/formatter.rb
CHANGED
@@ -46,7 +46,12 @@ module Querly
|
|
46
46
|
|
47
47
|
def script_error(path, error)
|
48
48
|
STDERR.puts Rainbow("Failed to load script: #{path}").red
|
49
|
-
|
49
|
+
|
50
|
+
if error.is_a? Parser::SyntaxError
|
51
|
+
STDERR.puts error.diagnostic.render
|
52
|
+
else
|
53
|
+
STDERR.puts error.inspect
|
54
|
+
end
|
50
55
|
end
|
51
56
|
|
52
57
|
def issue_found(script, rule, pair)
|
data/lib/querly/cli/test.rb
CHANGED
data/lib/querly/node_pair.rb
CHANGED
data/lib/querly/pattern/expr.rb
CHANGED
@@ -118,7 +118,7 @@ module Querly
|
|
118
118
|
|
119
119
|
when :str
|
120
120
|
return false unless type == :string
|
121
|
-
test_value(node.children.first)
|
121
|
+
test_value(node.children.first.scrub)
|
122
122
|
|
123
123
|
when :sym
|
124
124
|
return false unless type == :symbol
|
@@ -147,7 +147,8 @@ module Querly
|
|
147
147
|
|
148
148
|
def =~(pair)
|
149
149
|
# Skip send node with block
|
150
|
-
|
150
|
+
type = pair.node.type
|
151
|
+
if (type == :send || type == :csend) && pair.parent
|
151
152
|
if pair.parent.node.type == :block
|
152
153
|
if pair.parent.node.children.first.equal? pair.node
|
153
154
|
return false
|
@@ -176,7 +177,7 @@ module Querly
|
|
176
177
|
node = node.children.first if node&.type == :block
|
177
178
|
|
178
179
|
case node&.type
|
179
|
-
when :send
|
180
|
+
when :send, :csend
|
180
181
|
return false unless test_name(node)
|
181
182
|
return false unless test_receiver(node.children[0])
|
182
183
|
return false unless test_args(node.children.drop(2), args)
|
@@ -290,7 +291,8 @@ module Querly
|
|
290
291
|
if receiver.test_node(node)
|
291
292
|
true
|
292
293
|
else
|
293
|
-
|
294
|
+
type = node&.type
|
295
|
+
(type == :send || type == :csend) && test_node(node.children[0])
|
294
296
|
end
|
295
297
|
end
|
296
298
|
end
|
@@ -315,7 +317,7 @@ module Querly
|
|
315
317
|
# We don't want lvar without method call
|
316
318
|
# Skips when the node is not receiver of :send
|
317
319
|
parent_node = pair.parent&.node
|
318
|
-
if parent_node && parent_node.type == :send && parent_node.children.first.equal?(node)
|
320
|
+
if parent_node && (parent_node.type == :send || parent_node.type == :csend) && parent_node.children.first.equal?(node)
|
319
321
|
test_node(node)
|
320
322
|
end
|
321
323
|
else
|
@@ -325,7 +327,7 @@ module Querly
|
|
325
327
|
|
326
328
|
def test_node(node)
|
327
329
|
case node&.type
|
328
|
-
when :send
|
330
|
+
when :send, :csend
|
329
331
|
node.children[1] == name
|
330
332
|
when :lvar
|
331
333
|
node.children.first == name
|
data/lib/querly/pattern/parser.y
CHANGED
@@ -72,7 +72,7 @@ keyword: LIDENT | UIDENT
|
|
72
72
|
constant: UIDENT { result = [val[0]] }
|
73
73
|
| UIDENT COLONCOLON constant { result = [val[0]] + val[2] }
|
74
74
|
|
75
|
-
send: LIDENT block { result = val[1] != nil ? Expr::Send.new(receiver: nil, name: val[0],
|
75
|
+
send: LIDENT block { result = val[1] != nil ? Expr::Send.new(receiver: nil, name: val[0], block: val[1]) : Expr::Vcall.new(name: val[0]) }
|
76
76
|
| UIDENT block { result = Expr::Send.new(receiver: nil, name: val[0], block: val[1]) }
|
77
77
|
| method_name { result = Expr::Send.new(receiver: nil, name: val[0], block: nil) }
|
78
78
|
| method_name_or_ident LPAREN args RPAREN block { result = Expr::Send.new(receiver: nil,
|
@@ -83,18 +83,15 @@ send: LIDENT block { result = val[1] != nil ? Expr::Send.new(receiver: nil, name
|
|
83
83
|
name: val[1],
|
84
84
|
args: Argument::AnySeq.new,
|
85
85
|
block: val[2]) }
|
86
|
-
| receiver method_name_or_ident block { result = Expr::Send.new(receiver: val[0],
|
87
|
-
name: val[1],
|
88
|
-
args: Argument::AnySeq.new,
|
89
|
-
block: val[2]) }
|
90
|
-
| receiver method_name_or_ident LPAREN args RPAREN block { result = Expr::Send.new(receiver: val[0],
|
91
|
-
name: val[1],
|
92
|
-
args: val[3],
|
93
|
-
block: val[5]) }
|
94
86
|
| receiver method_name_or_ident LPAREN args RPAREN block { result = Expr::Send.new(receiver: val[0],
|
95
87
|
name: val[1],
|
96
88
|
args: val[3],
|
97
89
|
block: val[5]) }
|
90
|
+
| receiver UNDERBAR block { result = Expr::Send.new(receiver: val[0], name: /.+/, block: val[2]) }
|
91
|
+
| receiver UNDERBAR LPAREN args RPAREN block { result = Expr::Send.new(receiver: val[0],
|
92
|
+
name: /.+/,
|
93
|
+
args: val[3],
|
94
|
+
block: val[5]) }
|
98
95
|
|
99
96
|
receiver: expr DOT { result = val[0] }
|
100
97
|
| expr DOTDOTDOT { result = Expr::ReceiverContext.new(receiver: val[0]) }
|
@@ -136,6 +133,8 @@ def next_token
|
|
136
133
|
[:NIL, false]
|
137
134
|
when input.scan(/:string:/)
|
138
135
|
[:STRING, nil]
|
136
|
+
when input.scan(/"([^"]+)"/)
|
137
|
+
[:STRING, input[1]]
|
139
138
|
when input.scan(/:dstr:/)
|
140
139
|
[:DSTR, nil]
|
141
140
|
when input.scan(/:int:/)
|
data/lib/querly/pp/cli.rb
CHANGED
@@ -42,7 +42,7 @@ module Querly
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def run
|
45
|
-
available_commands = [:haml]
|
45
|
+
available_commands = [:haml, :erb]
|
46
46
|
|
47
47
|
if available_commands.include?(command)
|
48
48
|
send :"run_#{command}"
|
@@ -70,6 +70,25 @@ module Querly
|
|
70
70
|
stdout.print compiler.precompiled
|
71
71
|
end
|
72
72
|
end
|
73
|
+
|
74
|
+
def run_erb
|
75
|
+
require 'better_html'
|
76
|
+
require 'better_html/parser'
|
77
|
+
load_libs
|
78
|
+
source = stdin.read
|
79
|
+
source_buffer = Parser::Source::Buffer.new('(erb)')
|
80
|
+
source_buffer.source = source
|
81
|
+
parser = BetterHtml::Parser.new(source_buffer, template_language: :html)
|
82
|
+
|
83
|
+
new_source = source.gsub(/./, ' ')
|
84
|
+
parser.ast.descendants(:erb).each do |erb_node|
|
85
|
+
indicator_node, _, code_node, = *erb_node
|
86
|
+
next if indicator_node&.loc&.source == '#'
|
87
|
+
new_source[code_node.loc.range] = code_node.loc.source
|
88
|
+
new_source[code_node.loc.range.end] = ';'
|
89
|
+
end
|
90
|
+
stdout.puts new_source
|
91
|
+
end
|
73
92
|
end
|
74
93
|
end
|
75
94
|
end
|
data/lib/querly/script.rb
CHANGED
@@ -3,6 +3,16 @@ module Querly
|
|
3
3
|
attr_reader :path
|
4
4
|
attr_reader :node
|
5
5
|
|
6
|
+
def self.load(path:, source:)
|
7
|
+
parser = Parser::Ruby30.new(Builder.new).tap do |parser|
|
8
|
+
parser.diagnostics.all_errors_are_fatal = true
|
9
|
+
parser.diagnostics.ignore_warnings = true
|
10
|
+
end
|
11
|
+
buffer = Parser::Source::Buffer.new(path.to_s, 1)
|
12
|
+
buffer.source = source
|
13
|
+
self.new(path: path, node: parser.parse(buffer))
|
14
|
+
end
|
15
|
+
|
6
16
|
def initialize(path:, node:)
|
7
17
|
@path = path
|
8
18
|
@node = node
|
@@ -11,5 +21,15 @@ module Querly
|
|
11
21
|
def root_pair
|
12
22
|
NodePair.new(node: node)
|
13
23
|
end
|
24
|
+
|
25
|
+
class Builder < Parser::Builders::Default
|
26
|
+
def string_value(token)
|
27
|
+
value(token)
|
28
|
+
end
|
29
|
+
|
30
|
+
def emit_lambda
|
31
|
+
true
|
32
|
+
end
|
33
|
+
end
|
14
34
|
end
|
15
35
|
end
|
@@ -2,23 +2,36 @@ module Querly
|
|
2
2
|
class ScriptEnumerator
|
3
3
|
attr_reader :paths
|
4
4
|
attr_reader :config
|
5
|
+
attr_reader :threads
|
5
6
|
|
6
|
-
def initialize(paths:, config:)
|
7
|
+
def initialize(paths:, config:, threads:)
|
7
8
|
@paths = paths
|
8
9
|
@config = config
|
10
|
+
@threads = threads
|
9
11
|
end
|
10
12
|
|
13
|
+
# Yields `Script` object concurrently, in different threads.
|
11
14
|
def each(&block)
|
15
|
+
if block_given?
|
16
|
+
Parallel.each(each_path, in_threads: threads) do |path|
|
17
|
+
load_script_from_path path, &block
|
18
|
+
end
|
19
|
+
else
|
20
|
+
self.enum_for :each
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def each_path(&block)
|
12
25
|
if block_given?
|
13
26
|
paths.each do |path|
|
14
27
|
if path.directory?
|
15
28
|
enumerate_files_in_dir(path, &block)
|
16
29
|
else
|
17
|
-
|
30
|
+
yield path
|
18
31
|
end
|
19
32
|
end
|
20
33
|
else
|
21
|
-
|
34
|
+
enum_for :each_path
|
22
35
|
end
|
23
36
|
end
|
24
37
|
|
@@ -45,9 +58,7 @@ module Querly
|
|
45
58
|
path.read
|
46
59
|
end
|
47
60
|
|
48
|
-
|
49
|
-
buffer.source = source
|
50
|
-
script = Script.new(path: path, node: parser.parse(buffer))
|
61
|
+
script = Script.load(path: path, source: source)
|
51
62
|
rescue StandardError, LoadError, Preprocessor::Error => exn
|
52
63
|
script = exn
|
53
64
|
end
|
@@ -55,13 +66,6 @@ module Querly
|
|
55
66
|
yield(path, script)
|
56
67
|
end
|
57
68
|
|
58
|
-
def parser
|
59
|
-
Parser::Ruby25.new(Builder.new).tap do |parser|
|
60
|
-
parser.diagnostics.all_errors_are_fatal = true
|
61
|
-
parser.diagnostics.ignore_warnings = true
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
69
|
def preprocessors
|
66
70
|
config&.preprocessors || {}
|
67
71
|
end
|
@@ -98,17 +102,7 @@ module Querly
|
|
98
102
|
preprocessors.key?(path.extname)
|
99
103
|
end
|
100
104
|
|
101
|
-
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
class Builder < Parser::Builders::Default
|
106
|
-
def string_value(token)
|
107
|
-
value(token)
|
108
|
-
end
|
109
|
-
|
110
|
-
def emit_lambda
|
111
|
-
true
|
105
|
+
yield path if should_load_file
|
112
106
|
end
|
113
107
|
end
|
114
108
|
end
|
data/lib/querly/version.rb
CHANGED
data/manual/patterns.md
CHANGED
@@ -71,6 +71,8 @@ bar.foo.baz # foo...bar...baz does not match
|
|
71
71
|
* `1.23` (float)
|
72
72
|
* `:foobar` (symbol)
|
73
73
|
* `:symbol:` (any symbol literal)
|
74
|
+
* `"foobar"` (string)
|
75
|
+
* NOTE: It only supports double quotation.
|
74
76
|
* `:string:` (any string literal)
|
75
77
|
* `:dstr:` (any dstr `"hi #{name}"`)
|
76
78
|
* `true`, `false` (true and false)
|
@@ -145,9 +147,9 @@ end
|
|
145
147
|
|
146
148
|
# Interpolation Syntax
|
147
149
|
|
148
|
-
If you want to describe a pattern that can not be described with
|
150
|
+
If you want to describe a pattern that can not be described with above syntax, you can use interpolation as follows:
|
149
151
|
|
150
|
-
```
|
152
|
+
```yaml
|
151
153
|
id: find_by_abc_and_def
|
152
154
|
pattern:
|
153
155
|
subject: "'finder(...)"
|
@@ -164,6 +166,16 @@ It matches with `find_by_email_and_name(...)`.
|
|
164
166
|
- If value of meta var is a string `foo`, it matches send nodes with exactly same method name
|
165
167
|
- If value of meta var is a regexp `/foo/`, it matches send nodes with method name which `=~` the regexp
|
166
168
|
|
169
|
+
You can also use `as` syntax with `:symbol:` and so on.
|
170
|
+
|
171
|
+
```yaml
|
172
|
+
id: migration_references
|
173
|
+
pattern:
|
174
|
+
subject: "t.integer(:symbol: as 'column, ...)"
|
175
|
+
where:
|
176
|
+
column: '/.+_id/'
|
177
|
+
```
|
178
|
+
|
167
179
|
# Difference from Ruby
|
168
180
|
|
169
181
|
* Method call parenthesis cannot be omitted (if omitted, it means *any arguments*)
|
data/querly.gemspec
CHANGED
@@ -22,14 +22,20 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
23
|
spec.require_paths = ["lib"]
|
24
24
|
|
25
|
+
spec.required_ruby_version = ">= 2.7"
|
26
|
+
|
25
27
|
spec.add_development_dependency "bundler", ">= 1.12"
|
26
|
-
spec.add_development_dependency "rake", "~>
|
28
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
27
29
|
spec.add_development_dependency "minitest", "~> 5.0"
|
28
|
-
spec.add_development_dependency "racc", "
|
30
|
+
spec.add_development_dependency "racc", ">= 1.4.14"
|
29
31
|
spec.add_development_dependency "unification_assertion", "0.0.1"
|
32
|
+
spec.add_development_dependency "better_html", "~> 1.0.13"
|
33
|
+
spec.add_development_dependency "slim", "~> 4.0.1"
|
34
|
+
spec.add_development_dependency "haml", "~> 5.0.4"
|
30
35
|
|
31
|
-
spec.add_dependency 'thor', ">= 0.19.0"
|
32
|
-
spec.add_dependency "parser", ">=
|
36
|
+
spec.add_dependency 'thor', ">= 0.19.0"
|
37
|
+
spec.add_dependency "parser", ">= 3.0"
|
33
38
|
spec.add_dependency "rainbow", ">= 2.1"
|
34
39
|
spec.add_dependency "activesupport", ">= 5.0"
|
40
|
+
spec.add_dependency "parallel", "~>1.17"
|
35
41
|
end
|
data/sample.yaml
CHANGED
data/template.yml
CHANGED
@@ -58,7 +58,9 @@ rules:
|
|
58
58
|
assert_empty some.count
|
59
59
|
|
60
60
|
preprocessor:
|
61
|
-
.slim: slimrb --compile
|
61
|
+
# .slim: slimrb --compile # Install `slim` gem for slim support
|
62
|
+
# .erb: querly-pp erb # Install `better_erb` gem for erb support
|
63
|
+
# .haml: querly-pp haml # Install `haml` gem for haml support
|
62
64
|
|
63
65
|
check:
|
64
66
|
- path: /
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: querly
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Soutaro Matsumoto
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-07-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -30,14 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '13.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '13.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: minitest
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -56,14 +56,14 @@ dependencies:
|
|
56
56
|
name: racc
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: 1.4.14
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: 1.4.14
|
69
69
|
- !ruby/object:Gem::Dependency
|
@@ -80,6 +80,48 @@ dependencies:
|
|
80
80
|
- - '='
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: 0.0.1
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: better_html
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 1.0.13
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 1.0.13
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: slim
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 4.0.1
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 4.0.1
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: haml
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 5.0.4
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 5.0.4
|
83
125
|
- !ruby/object:Gem::Dependency
|
84
126
|
name: thor
|
85
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -87,9 +129,6 @@ dependencies:
|
|
87
129
|
- - ">="
|
88
130
|
- !ruby/object:Gem::Version
|
89
131
|
version: 0.19.0
|
90
|
-
- - "<"
|
91
|
-
- !ruby/object:Gem::Version
|
92
|
-
version: 0.21.0
|
93
132
|
type: :runtime
|
94
133
|
prerelease: false
|
95
134
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -97,23 +136,20 @@ dependencies:
|
|
97
136
|
- - ">="
|
98
137
|
- !ruby/object:Gem::Version
|
99
138
|
version: 0.19.0
|
100
|
-
- - "<"
|
101
|
-
- !ruby/object:Gem::Version
|
102
|
-
version: 0.21.0
|
103
139
|
- !ruby/object:Gem::Dependency
|
104
140
|
name: parser
|
105
141
|
requirement: !ruby/object:Gem::Requirement
|
106
142
|
requirements:
|
107
143
|
- - ">="
|
108
144
|
- !ruby/object:Gem::Version
|
109
|
-
version:
|
145
|
+
version: '3.0'
|
110
146
|
type: :runtime
|
111
147
|
prerelease: false
|
112
148
|
version_requirements: !ruby/object:Gem::Requirement
|
113
149
|
requirements:
|
114
150
|
- - ">="
|
115
151
|
- !ruby/object:Gem::Version
|
116
|
-
version:
|
152
|
+
version: '3.0'
|
117
153
|
- !ruby/object:Gem::Dependency
|
118
154
|
name: rainbow
|
119
155
|
requirement: !ruby/object:Gem::Requirement
|
@@ -142,6 +178,20 @@ dependencies:
|
|
142
178
|
- - ">="
|
143
179
|
- !ruby/object:Gem::Version
|
144
180
|
version: '5.0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: parallel
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - "~>"
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '1.17'
|
188
|
+
type: :runtime
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - "~>"
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '1.17'
|
145
195
|
description: Querly is a query language and tool to find out method calls from Ruby
|
146
196
|
programs. Define rules to check your program with patterns to find out *bad* pieces.
|
147
197
|
Querly finds out matching pieces from your program.
|
@@ -153,8 +203,10 @@ executables:
|
|
153
203
|
extensions: []
|
154
204
|
extra_rdoc_files: []
|
155
205
|
files:
|
206
|
+
- ".github/workflows/rubocop.yml"
|
207
|
+
- ".github/workflows/ruby.yml"
|
156
208
|
- ".gitignore"
|
157
|
-
- ".
|
209
|
+
- ".rubocop.yml"
|
158
210
|
- CHANGELOG.md
|
159
211
|
- Gemfile
|
160
212
|
- LICENSE
|
@@ -212,15 +264,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
212
264
|
requirements:
|
213
265
|
- - ">="
|
214
266
|
- !ruby/object:Gem::Version
|
215
|
-
version: '
|
267
|
+
version: '2.7'
|
216
268
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
217
269
|
requirements:
|
218
270
|
- - ">="
|
219
271
|
- !ruby/object:Gem::Version
|
220
272
|
version: '0'
|
221
273
|
requirements: []
|
222
|
-
|
223
|
-
rubygems_version: 2.7.6
|
274
|
+
rubygems_version: 3.1.2
|
224
275
|
signing_key:
|
225
276
|
specification_version: 4
|
226
277
|
summary: Pattern Based Checking Tool for Ruby
|