querly 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b38a9eb384827611a39b2d1bef7e1b8cb574f2c8
4
+ data.tar.gz: c02c0c2ebb9844465855e2691e02715b5c73b359
5
+ SHA512:
6
+ metadata.gz: e9a5550cec1e87aad9dcfd0372af5a65ec82b46f685c8960c89b7a34316e64ffc2172136a4a238dfa3803349796df235d960ca4ca24df503f8895c4776a9631f
7
+ data.tar.gz: fa20ac97b8485b262e625e20a945719c254a7a13f4006e0e8513156567324e3b932c6d75e623b16062085026eb9ad8e4ad902adb169f786026588ebd64cfc729
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /.idea
10
+ /parser.output
11
+ /lib/querly/pattern/parser.rb
12
+ parser.output
13
+ /Gemfile.lock
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.12.5
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in querly.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # Querly - Query Method Calls from Ruby Programs
2
+
3
+ [![Build Status](https://travis-ci.org/soutaro/querly.svg?branch=master)](https://travis-ci.org/soutaro/querly)
4
+
5
+ Querly is a query language and tool to find out method calls from Ruby programs.
6
+ You write simple query for method calls, and Querly finds out *wrong* pieces from your program.
7
+
8
+ ## Overview
9
+
10
+ Your project should have many local rules:
11
+
12
+ * Should not use `Customer#update_mail` but 30x faster `Customer.update_all_email` (Slower `#update_mail` is left just for existing code, but new code should not use it)
13
+ * Should not use `root_url` without `locale:` parameter
14
+ * Should not use `Net::HTTP` for Web API calls, but use `HTTPClient`
15
+
16
+ These local rule violations will be found during code review.
17
+ Reviewers will ask commiter to revise, commiter will fix, fine.
18
+ Really?
19
+ It is boring and time consuming.
20
+ We need some automation!
21
+
22
+ However, that rules cannot be the standard.
23
+ They make sense only in your project.
24
+ Okay, start writing a plug-in for RuboCop? (or other checking tools)
25
+
26
+ Instead of writing RuboCop plug-in, just define a Querly rule in a few lines of YAML.
27
+
28
+ ```yml
29
+ rules:
30
+ - id: my_project.use_faster_email_update
31
+ pattern: update_mail
32
+ message: When updating Customer#email, newly written code should use 30x faster Customer.update_all_email
33
+ justification:
34
+ - When you are editing old code (it should be refactored...)
35
+ - You are sure updating only small number of customers, and performance does not matter
36
+
37
+ - id: my_project.root_url_without_locale
38
+ pattern: "root_url(!locale: _)"
39
+ message: Links to top page should be with locale parameter
40
+
41
+ - id: my_project.net_http
42
+ pattern: Net::HTTP
43
+ message: Use HTTPClient to make HTTP request
44
+ ```
45
+
46
+ Write down your local rules, and let Querly do boring checks.
47
+ Focus on spec, design, UX, and other important things during code review!
48
+
49
+ ## Installation
50
+
51
+ Install via RubyGems.
52
+
53
+ $ gem install querly
54
+
55
+ Or you can put it in your Gemfile.
56
+
57
+ ```rb
58
+ gem 'querly'
59
+ ```
60
+
61
+ ## Quick Start
62
+
63
+ Copy the following YAML and paste as `querly.yml` in your project's repo.
64
+
65
+ ```yaml
66
+ rules:
67
+ - id: sample.debug_print
68
+ pattern:
69
+ - p
70
+ - pp
71
+ message: Delete debug print
72
+ ```
73
+
74
+ Run `querly` in the repo.
75
+
76
+ ```
77
+ $ querly check .
78
+ ```
79
+
80
+ If your code contains `p` or `pp` calls, querly will print warning messages.
81
+
82
+ ```
83
+ ./app/models/account.rb:44:10 p(account.id) Delete debug print
84
+ ./app/controllers/accounts_controller.rb:17:2 pp params: params Delete debug print
85
+ ```
86
+
87
+ ## Configuration
88
+
89
+ Visit our wiki pages for configuration and query language reference.
90
+
91
+ * [Configuration](https://github.com/soutaro/querly/wiki/Configuration)
92
+ * [Patterns](https://github.com/soutaro/querly/wiki/Patterns)
93
+
94
+ Use `querly console` command to test patterns interactively.
95
+
96
+ ## Notes
97
+
98
+ ### Querly's analysis is syntactic
99
+
100
+ The analysis is currently purely syntactic:
101
+
102
+ ```rb
103
+ record.save(validate: false)
104
+ ```
105
+
106
+ and
107
+
108
+ ```rb
109
+ x = false
110
+ record.save(validate: x)
111
+ ```
112
+
113
+ will yield different results.
114
+ This can be improved by doing very primitive data flow analysis, and I'm planning to do that.
115
+
116
+ ### Too many false positives!
117
+
118
+ The analysis itself does not have very good precision.
119
+ There will be many false positives, and *querly warning free code* does not make much sense.
120
+
121
+ * TODO: support to ignore warnings through magic comments in code
122
+
123
+ This is not to ensure *there is nothing wrong in the code*, but just tells you *code fragments you should review with special care*.
124
+ I believe it still improves your software development productivity.
125
+
126
+ ### Incoming updates?
127
+
128
+ The following is the list of updates which would make sense.
129
+
130
+ * Support for importing rule sets, and provide some good default rules
131
+ * Support for ignoring warnings
132
+ * Improve analysis precision by intra procedural data flow analysis
133
+
134
+ ## Development
135
+
136
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
137
+
138
+ 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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
139
+
140
+ ## Contributing
141
+
142
+ Bug reports and pull requests are welcome on GitHub at https://github.com/soutaro/querly.
143
+
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
11
+ task :build => :racc
12
+ task :test => :racc
13
+
14
+ rule /\.rb/ => ".y" do |t|
15
+ sh "racc", "-v", "-o", "#{t.name}", "#{t.source}"
16
+ end
17
+
18
+ task :racc => "lib/querly/pattern/parser.rb"
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "querly"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/querly ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH << File.join(__dir__, "../lib")
4
+
5
+ require "querly"
6
+ require "querly/cli"
7
+
8
+ Querly::CLI.start(ARGV)
data/lib/querly.rb ADDED
@@ -0,0 +1,26 @@
1
+ require 'pathname'
2
+ require "yaml"
3
+ require "rainbow"
4
+ require "parser/current"
5
+ require "set"
6
+ require "open3"
7
+
8
+ Parser::Builders::Default.emit_lambda = true
9
+
10
+ require "querly/version"
11
+ require 'querly/analyzer'
12
+ require 'querly/rule'
13
+ require 'querly/pattern/expr'
14
+ require 'querly/pattern/argument'
15
+ require 'querly/script'
16
+ require 'querly/script_enumerator'
17
+ require 'querly/node_pair'
18
+ require "querly/pattern/parser"
19
+ require 'querly/pattern/kind'
20
+ require "querly/config"
21
+ require "querly/tagging"
22
+ require "querly/preprocessor"
23
+
24
+ module Querly
25
+ # Your code goes here...
26
+ end
@@ -0,0 +1,64 @@
1
+ module Querly
2
+ class Analyzer
3
+ attr_reader :rules
4
+ attr_reader :scripts
5
+ attr_reader :taggings
6
+
7
+ def initialize(taggings:)
8
+ @rules = []
9
+ @scripts = []
10
+ @taggings = taggings
11
+ end
12
+
13
+ #
14
+ # yields(script, rule, node_pair)
15
+ #
16
+ def run
17
+ scripts.each do |script|
18
+ tagging = taggings.find {|tagging| tagging.applicable?(script) }
19
+
20
+ each_subnode script.root_pair do |node_pair|
21
+ rules.each do |rule|
22
+ if applicable_rule?(tagging, rule)
23
+ if rule.patterns.any? {|pattern| test_pair(node_pair, pattern) }
24
+ yield script, rule, node_pair
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ def find(pattern)
33
+ scripts.each do |script|
34
+ each_subnode script.root_pair do |node_pair|
35
+ if test_pair(node_pair, pattern)
36
+ yield script, node_pair
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ def test_pair(node_pair, pattern)
43
+ pattern.expr =~ node_pair && pattern.test_kind(node_pair)
44
+ end
45
+
46
+ def applicable_rule?(tagging, rule)
47
+ if tagging && !rule.tags.empty?
48
+ tagging.tags_set.any? {|set| set.subset?(rule.tags) }
49
+ else
50
+ true
51
+ end
52
+ end
53
+
54
+ def each_subnode(node_pair, &block)
55
+ return unless node_pair.node
56
+
57
+ yield node_pair
58
+
59
+ node_pair.children.each do |child|
60
+ each_subnode child, &block
61
+ end
62
+ end
63
+ end
64
+ end
data/lib/querly/cli.rb ADDED
@@ -0,0 +1,81 @@
1
+ require "thor"
2
+ require "json"
3
+
4
+ module Querly
5
+ class CLI < Thor
6
+ desc "check [paths]", "Check paths based on configuration"
7
+ option :config, default: "querly.yml"
8
+ option :format, default: "text", type: :string, enum: %w(text json)
9
+ def check(*paths)
10
+ require 'querly/cli/formatter'
11
+
12
+ formatter = case options[:format]
13
+ when "text"
14
+ Formatter::Text.new
15
+ when "json"
16
+ Formatter::JSON.new
17
+ end
18
+ formatter.start
19
+
20
+ begin
21
+ unless config_path.file?
22
+ STDERR.puts <<-Message
23
+ Configuration file #{config_path} does not look a file.
24
+ Specify configuration file by --config option.
25
+ Message
26
+ exit 1
27
+ end
28
+
29
+ config = Config.new
30
+ begin
31
+ config.add_file config_path
32
+ rescue => exn
33
+ formatter.config_error config_path, exn
34
+ exit
35
+ end
36
+
37
+ analyzer = Analyzer.new(taggings: config.taggings)
38
+ analyzer.rules.concat config.rules
39
+
40
+ ScriptEnumerator.new(paths: paths.map {|path| Pathname(path) }, preprocessors: config.preprocessors).each do |path, script|
41
+ case script
42
+ when Script
43
+ analyzer.scripts << script
44
+ formatter.script_load script
45
+ when StandardError, LoadError
46
+ formatter.script_error path, script
47
+ end
48
+ end
49
+
50
+ analyzer.run do |script, rule, pair|
51
+ formatter.issue_found script, rule, pair
52
+ end
53
+ rescue => exn
54
+ formatter.fatal_error exn
55
+ exit 1
56
+ ensure
57
+ formatter.finish
58
+ end
59
+ end
60
+
61
+ desc "console [paths]", "Start console for given paths"
62
+ def console(*paths)
63
+ require 'querly/cli/console'
64
+ Console.new(paths: paths.map {|path| Pathname(path) }).start
65
+ end
66
+
67
+ desc "test", "Check configuration"
68
+ option :config, default: "querly.yml"
69
+ def test()
70
+ require "querly/cli/test"
71
+ Test.new(config_path: config_path).run
72
+ end
73
+
74
+ private
75
+
76
+ def config_path
77
+ [Pathname(options[:config]),
78
+ Pathname("querly.yaml")].compact.find(&:file?) || Pathname(options[:config])
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,110 @@
1
+ require 'readline'
2
+
3
+ module Querly
4
+ class CLI
5
+ class Console
6
+ attr_reader :paths
7
+
8
+ def initialize(paths:)
9
+ @paths = paths
10
+ end
11
+
12
+ def start
13
+ puts <<-Message
14
+ Querly #{VERSION}, interactive console
15
+
16
+ Message
17
+
18
+ puts_commands
19
+
20
+ STDOUT.print "Loading..."
21
+ STDOUT.flush
22
+ reload!
23
+ STDOUT.puts " ready!"
24
+
25
+ loop
26
+ end
27
+
28
+ def reload!
29
+ @analyzer = nil
30
+ analyzer
31
+ end
32
+
33
+ def analyzer
34
+ return @analyzer if @analyzer
35
+
36
+ @analyzer = Analyzer.new(taggings: [])
37
+
38
+ ScriptEnumerator.new(paths: paths).each do |path, script|
39
+ case script
40
+ when Script
41
+ @analyzer.scripts << script
42
+ when StandardError
43
+ p script
44
+ end
45
+ end
46
+
47
+ @analyzer
48
+ end
49
+
50
+ def loop
51
+ while line = Readline.readline("> ", true)
52
+ case line
53
+ when "quit"
54
+ exit
55
+ when "reload!"
56
+ STDOUT.print "reloading..."
57
+ STDOUT.flush
58
+ reload!
59
+ STDOUT.puts " done"
60
+ when /^find (.+)/
61
+ begin
62
+ pattern = Pattern::Parser.parse($1)
63
+
64
+ count = 0
65
+
66
+ analyzer.find(pattern) do |script, pair|
67
+ path = script.path.to_s
68
+ line = pair.node.loc.first_line
69
+
70
+ while true
71
+ parent = pair.parent
72
+
73
+ if parent && parent.node.loc.first_line == line
74
+ pair = pair.parent
75
+ else
76
+ break
77
+ end
78
+ end
79
+
80
+ src = Rainbow(pair.node.loc.expression.source.split(/\n/).first).blue
81
+ col = pair.node.loc.column
82
+
83
+ puts " #{path}:#{line}:#{col}\t#{src}"
84
+
85
+ count += 1
86
+ end
87
+
88
+ puts "#{count} results"
89
+ rescue => exn
90
+ STDOUT.puts exn.inspect
91
+ STDOUT.puts exn.backtrace
92
+ end
93
+ else
94
+ puts_commands
95
+ end
96
+ end
97
+ end
98
+
99
+ def puts_commands
100
+ puts <<-Message
101
+ Commands:
102
+ - find PATTERN Find PATTERN from given paths
103
+ - reload! Reload program from paths
104
+ - quit
105
+
106
+ Message
107
+ end
108
+ end
109
+ end
110
+ end