querly 0.15.0 → 1.2.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 +18 -0
- data/.github/workflows/ruby.yml +22 -0
- data/.rubocop.yml +10 -0
- data/CHANGELOG.md +27 -0
- data/README.md +3 -1
- data/Rakefile +1 -1
- data/bin/setup +1 -2
- data/lib/querly.rb +1 -0
- 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/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/logo/Querly horizontal.pdf +793 -0
- data/logo/Querly horizontal.png +0 -0
- data/logo/Querly horizontal.svg +55 -0
- data/logo/Querly logo.png +0 -0
- data/logo/Querly vertical.png +0 -0
- data/manual/patterns.md +14 -2
- data/querly.gemspec +8 -4
- data/sample.yaml +2 -0
- data/template.yml +3 -1
- metadata +73 -16
- 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: 3604de13f36852d57290140b9bf4e4bfeb5811ffb8dbfef054a3e0ce8e9aa47a
|
4
|
+
data.tar.gz: '09038f2030b2c6bc859cfbd7087ae62da8efd2ac4ae1e3b945741d3f65d3146b'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8c2dbbd840b5eb4fa65418c4d2ab81af6154dbbac94c96d083c4f7f9d88e5447666f7d583b54f4c266f52c4f4f15d2f38210c353762c2f8c281785da161d0aec
|
7
|
+
data.tar.gz: 4fd40dc297b03b7de10e3f1bb348a553d1af355f3e6310feed4b9b8e1fe5324757684e443cb8a699952bc0ccfedcf95f072e13890b5cc838f4c2850b7ef09d58
|
@@ -0,0 +1,18 @@
|
|
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: 2.6
|
13
|
+
- run: gem install rubocop rubocop-rubycw --no-document
|
14
|
+
- name: Run RuboCop
|
15
|
+
shell: bash
|
16
|
+
run: |
|
17
|
+
# https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message
|
18
|
+
rubocop | ruby -pe 'sub(/^(.+):(\d+):(\d+): (.): (.+)$/, %q{::error file=\1,line=\2,col=\3::\5})'
|
@@ -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.6, 2.7, head]
|
15
|
+
steps:
|
16
|
+
- uses: actions/checkout@v2
|
17
|
+
- uses: ruby/setup-ruby@v1
|
18
|
+
with:
|
19
|
+
ruby-version: ${{ matrix.ruby }}
|
20
|
+
- run: bundle install --jobs 4 --retry 3
|
21
|
+
- run: bin/setup
|
22
|
+
- run: bundle exec rake build test
|
data/.rubocop.yml
ADDED
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,33 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 1.2.0 (2020-12-15)
|
6
|
+
|
7
|
+
* Relax Thor version requirements by @y-yagi ([#85](https://github.com/soutaro/querly/pull/85))
|
8
|
+
* Fix ERB comment preprocessing by @mallowlabs ([#84](https://github.com/soutaro/querly/pull/84))
|
9
|
+
* Better error message for Ruby code syntax error by @ybiquitous ([#83](https://github.com/soutaro/querly/pull/83))
|
10
|
+
|
11
|
+
## 1.1.0 (2020-05-17)
|
12
|
+
|
13
|
+
* Fix invalid bytes sequence in UTF-8 error by @mallowlabs [#75](https://github.com/soutaro/querly/pull/75)
|
14
|
+
* Detect safe navigation operator as a method call by @pocke [#71](https://github.com/soutaro/querly/pull/71)
|
15
|
+
|
16
|
+
## 1.0.0 (2019-7-19)
|
17
|
+
|
18
|
+
* Add `--config` option for `find` and `console` [#67](https://github.com/soutaro/querly/pull/67)
|
19
|
+
* Improve preprocessor performance by processing concurrently [#68](https://github.com/soutaro/querly/pull/68)
|
20
|
+
|
21
|
+
## 0.16.0 (2019-04-23)
|
22
|
+
|
23
|
+
* Support string literal pattern (@pocke) [#64](https://github.com/soutaro/querly/pull/64)
|
24
|
+
* Allow underscore method name pattern (@pocke) [#63](https://github.com/soutaro/querly/pull/63)
|
25
|
+
* Add erb support (@hanachin) [#61](https://github.com/soutaro/querly/pull/61)
|
26
|
+
* Add `exit` command on console (@wata727) [#59](https://github.com/soutaro/querly/pull/59)
|
27
|
+
|
28
|
+
## 0.15.1 (2019-03-12)
|
29
|
+
|
30
|
+
* Relax parser version requirement
|
31
|
+
|
5
32
|
## 0.15.0 (2019-02-13)
|
6
33
|
|
7
34
|
* Fix broken `querly init` template (@ybiquitous) #56
|
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
![Querly logo](https://github.com/soutaro/querly/blob/master/logo/Querly%20horizontal.png)
|
2
|
+
|
1
3
|
# Querly - Pattern Based Checking Tool for Ruby
|
2
4
|
|
3
|
-
|
5
|
+
![Ruby](https://github.com/soutaro/querly/workflows/Ruby/badge.svg)
|
4
6
|
|
5
7
|
Querly is a query language and tool to find out method calls from Ruby programs.
|
6
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
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/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
|