querly 0.15.0 → 1.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 876ab59fc5630b135ecc548b1fe7e520bc05a9549885940a6c7e67ce03562922
4
- data.tar.gz: 2010216ed4481282e6b7cbdfc2a15817519e36e15c90d2617250477b5e73288a
3
+ metadata.gz: 3604de13f36852d57290140b9bf4e4bfeb5811ffb8dbfef054a3e0ce8e9aa47a
4
+ data.tar.gz: '09038f2030b2c6bc859cfbd7087ae62da8efd2ac4ae1e3b945741d3f65d3146b'
5
5
  SHA512:
6
- metadata.gz: e19840999c05c19ad328569415c0d83852de02f277fb0924133ac6f21a85a32f688eaa4821213acdafec0dfcc451f322b3b19ace20ad637ed4561839225cc632
7
- data.tar.gz: 4b597d4ad3e6b426b63e3293892818a0621d8d6bc0000a897c9adf1227033bc340c4388cdc47f8c2cdc67266ebb90919eaf8be71e5ca397c232163de2348a3f3
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
@@ -0,0 +1,10 @@
1
+ require:
2
+ - rubocop-rubycw
3
+
4
+ AllCops:
5
+ DisabledByDefault: true
6
+ Exclude:
7
+ - test/data/**/*.rb
8
+
9
+ Rubycw/Rubycw:
10
+ Enabled: true
@@ -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
- [![Build Status](https://travis-ci.org/soutaro/querly.svg?branch=master)](https://travis-ci.org/soutaro/querly)
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
@@ -11,7 +11,7 @@ task :default => :test
11
11
  task :build => :racc
12
12
  task :test => :racc
13
13
 
14
- rule /\.rb/ => ".y" do |t|
14
+ rule %r/\.rb/ => ".y" do |t|
15
15
  sh "racc", "-v", "-o", "#{t.name}", "#{t.source}"
16
16
  end
17
17
 
data/bin/setup CHANGED
@@ -4,5 +4,4 @@ IFS=$'\n\t'
4
4
  set -vx
5
5
 
6
6
  bundle install
7
-
8
- # Do any other automated setup that you need to do here
7
+ bundle exec rake racc
@@ -5,6 +5,7 @@ require "parser/ruby25"
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'
@@ -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
- root_option = options[:root]
36
- root_path = root_option ? Pathname(root_option).realpath : config_path.parent.realpath
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])
@@ -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: nil, rule: nil)
49
+ @analyzer = Analyzer.new(config: config, rule: nil)
46
50
 
47
- ScriptEnumerator.new(paths: paths, config: nil).each do |path, script|
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..."
@@ -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
- STDTOU.puts "pattern: #{pattern_str}"
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: nil, rule: nil)
55
+ @analyzer = Analyzer.new(config: config, rule: nil)
52
56
 
53
- ScriptEnumerator.new(paths: paths, config: nil).each do |path, script|
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
@@ -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
- STDERR.puts error.inspect
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)
@@ -25,7 +25,7 @@ module Querly
25
25
  yield self
26
26
 
27
27
  children.each do |child|
28
- child.each_subpair &block
28
+ child.each_subpair(&block)
29
29
  end
30
30
  else
31
31
  enum_for :each_subpair
@@ -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
- if pair.node.type == :send && pair.parent
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
- node&.type == :send && test_node(node.children[0])
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
@@ -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], args: Argument::AnySeq.new, block: val[1]) : Expr::Vcall.new(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:/)
@@ -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