rbr 0.1.0 → 0.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 +4 -4
- data/.rubocop.yml +14 -0
- data/Gemfile +3 -1
- data/Gemfile.lock +3 -3
- data/README.md +54 -22
- data/Rakefile +3 -1
- data/TODO +1 -0
- data/exe/rbr +1 -1
- data/lib/rbr/cli.rb +28 -13
- data/lib/rbr/comment_node.rb +18 -0
- data/lib/rbr/matchers.rb +75 -40
- data/lib/rbr/node.rb +35 -7
- data/lib/rbr/query.rb +22 -5
- data/lib/rbr/version.rb +3 -1
- data/rbr.gemspec +2 -1
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8f991cd863713e0995b19ec6dd66772030342e9af5ef18441ed14afe7c68a24d
|
4
|
+
data.tar.gz: a12bb9d633f5b33dbd78034a26915e4168deefa306d5ec567adf672b02d4cf34
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 36c06cff179b606e84e217adfc1233f0fe9b817acb749c780230959445b7585ac6a39ac41e3ddef4a58399935d3ad8602b51fe1d3a5d13e9ea90e7251ff01f68
|
7
|
+
data.tar.gz: 76040e738fb9c97400836106c368eafa9e1a93112622d9f13ef86f77f9cd9d54a477a4bb9f42c7dc5f237c14a99ea4cbe3adfe4d767b354cbd84e3d6a547a80f
|
data/.rubocop.yml
CHANGED
@@ -1,4 +1,18 @@
|
|
1
1
|
---
|
2
|
+
require:
|
3
|
+
- rubocop-minitest
|
4
|
+
- rubocop-performance
|
5
|
+
|
6
|
+
AllCops:
|
7
|
+
NewCops: enable
|
8
|
+
TargetRubyVersion: 2.6
|
9
|
+
Exclude:
|
10
|
+
- "test/fixtures/*"
|
11
|
+
|
12
|
+
Metrics/MethodLength:
|
13
|
+
Exclude:
|
14
|
+
- "test/**"
|
15
|
+
|
2
16
|
Style/Documentation:
|
3
17
|
Exclude:
|
4
18
|
- "test/**"
|
data/Gemfile
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
source "https://rubygems.org"
|
2
4
|
|
3
|
-
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
5
|
+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
4
6
|
|
5
7
|
# Specify your gem's dependencies in rbr.gemspec
|
6
8
|
gemspec
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rbr (0.
|
4
|
+
rbr (0.2.0)
|
5
5
|
parser
|
6
6
|
|
7
7
|
GEM
|
@@ -16,7 +16,7 @@ GEM
|
|
16
16
|
pry (0.13.1)
|
17
17
|
coderay (~> 1.1)
|
18
18
|
method_source (~> 1.0)
|
19
|
-
rake (
|
19
|
+
rake (13.0.1)
|
20
20
|
|
21
21
|
PLATFORMS
|
22
22
|
ruby
|
@@ -25,7 +25,7 @@ DEPENDENCIES
|
|
25
25
|
bundler (~> 1.17)
|
26
26
|
minitest (~> 5.0)
|
27
27
|
pry (~> 0.0)
|
28
|
-
rake (~>
|
28
|
+
rake (~> 13.0)
|
29
29
|
rbr!
|
30
30
|
|
31
31
|
BUNDLED WITH
|
data/README.md
CHANGED
@@ -1,35 +1,67 @@
|
|
1
1
|
# Rbr
|
2
2
|
|
3
|
-
|
3
|
+
Rbr is a code search tool that parses Ruby code so you can query over certain semantic
|
4
|
+
constructs.
|
5
|
+
|
6
|
+
## Usage examples
|
7
|
+
|
8
|
+
```sh
|
9
|
+
# assignment to an lvalue named `@author`
|
10
|
+
$ rbr assignment :@author test/fixtures/book.rb
|
11
|
+
5: @author = author
|
12
|
+
|
13
|
+
# int or float
|
14
|
+
$ rbr number 5 test/fixtures/book.rb
|
15
|
+
12: 5
|
16
|
+
|
17
|
+
# string
|
18
|
+
$ rbr string ring test/fixtures/book.rb
|
19
|
+
13: "a string!"
|
20
|
+
|
21
|
+
# a literal (int, float, or string)
|
22
|
+
$ rbr literal 5 test/fixtures/book.rb
|
23
|
+
12: 5
|
24
|
+
49: "5"
|
25
|
+
|
26
|
+
# comments matching the pattern /great/
|
27
|
+
$ rbr comment great test/fixtures/book.rb
|
28
|
+
1: # This is a great class
|
29
|
+
|
30
|
+
# find all calls of a method named `great_method`
|
31
|
+
$ rbr method_call :great_method test/fixtures/book.rb
|
32
|
+
27: book.update!(title: "Great Title")
|
33
|
+
50: book.send(:update!, title: "Great Title")
|
34
|
+
|
35
|
+
# statements that update an ActiveRecord model attribute named `title`
|
36
|
+
$ rbr ar_update :title test/fixtures/book.rb
|
37
|
+
21: book.title = "Great Title"
|
38
|
+
27: book.update!(title: "Great Title")
|
39
|
+
31: book.send(:update_column, :title, "Great Title")
|
40
|
+
```
|
4
41
|
|
5
|
-
|
42
|
+
rbr is the wrong tool for the following situations:
|
6
43
|
|
7
|
-
|
44
|
+
```sh
|
45
|
+
# find any appearance of the string "author" in the source
|
46
|
+
$ grep "author"
|
8
47
|
|
9
|
-
|
48
|
+
# find a symbol named :author
|
49
|
+
$ grep ":author"
|
10
50
|
|
11
|
-
|
12
|
-
|
51
|
+
# find the definition of any function named "publish"
|
52
|
+
$ grep "def publish"
|
13
53
|
```
|
14
54
|
|
15
|
-
|
16
|
-
|
17
|
-
$ bundle
|
18
|
-
|
19
|
-
Or install it yourself as:
|
20
|
-
|
21
|
-
$ gem install rbr
|
55
|
+
## Installation
|
22
56
|
|
23
|
-
|
57
|
+
Rbr is intended to be used as a command-line program. Install the executable `rbr`
|
58
|
+
with:
|
24
59
|
|
25
|
-
|
60
|
+
```
|
61
|
+
gem install rbr
|
62
|
+
```
|
26
63
|
|
27
64
|
## Development
|
28
65
|
|
29
|
-
After checking out the repo, run `
|
30
|
-
|
31
|
-
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).
|
32
|
-
|
33
|
-
## Contributing
|
34
|
-
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/rbr.
|
66
|
+
After checking out the repo, run `bundle install` to install dependencies. Then,
|
67
|
+
run `rake test` to run the tests or `bundle exec rbr` to run the local copy.
|
data/Rakefile
CHANGED
data/exe/rbr
CHANGED
data/lib/rbr/cli.rb
CHANGED
@@ -1,29 +1,44 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
1
|
# frozen_string_literal: true
|
4
2
|
|
5
3
|
require "parser/current"
|
6
|
-
require "rbr/query"
|
7
4
|
|
8
|
-
|
5
|
+
require "rbr/query"
|
9
6
|
|
10
7
|
module Rbr
|
11
8
|
class CLI
|
12
|
-
def
|
13
|
-
|
9
|
+
def initialize
|
10
|
+
check_arg_count
|
11
|
+
|
12
|
+
@matcher = ARGV[0].to_sym
|
13
|
+
@condition = ARGV[1]
|
14
|
+
end
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
16
|
+
def start
|
17
|
+
filenames.each do |filename|
|
18
|
+
root, comments = Parser::CurrentRuby.parse_file_with_comments(filename)
|
18
19
|
|
19
|
-
|
20
|
+
matching_nodes = Query.new(@matcher, @condition).run(root, comments)
|
20
21
|
|
21
|
-
|
22
|
-
|
22
|
+
matching_nodes.each { |node| puts node.pretty_print }
|
23
|
+
rescue EncodingError
|
24
|
+
warn "# Encoding error parsing #{filename}"
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
26
|
-
def
|
28
|
+
def filenames
|
29
|
+
ARGV[2..].map { |arg| expand_path(arg) }
|
30
|
+
.flatten
|
31
|
+
.select { |filename| File.file?(filename) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def expand_path(path)
|
35
|
+
return [path] if File.file?(path)
|
36
|
+
|
37
|
+
# if path is not a file, then glob all .rb files beneath it
|
38
|
+
Dir.glob("#{path}/**/*.rb")
|
39
|
+
end
|
40
|
+
|
41
|
+
def check_arg_count
|
27
42
|
return true if ARGV.count >= 3
|
28
43
|
|
29
44
|
warn <<~USAGE
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rbr
|
4
|
+
# Wraps a Parser::Source::Comment object and provides convenience methods
|
5
|
+
class CommentNode
|
6
|
+
def initialize(parser_comment)
|
7
|
+
@parser_comment = parser_comment
|
8
|
+
end
|
9
|
+
|
10
|
+
def expression
|
11
|
+
@parser_comment.loc.expression
|
12
|
+
end
|
13
|
+
|
14
|
+
def pretty_print
|
15
|
+
"#{expression.line}: #{@parser_comment.text}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/rbr/matchers.rb
CHANGED
@@ -3,71 +3,106 @@
|
|
3
3
|
module Rbr
|
4
4
|
# methods for determining if a node matches given conditions
|
5
5
|
class Matchers
|
6
|
-
def self.match(node,
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
def self.match(node, matcher, condition)
|
7
|
+
send(matcher, node, condition)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Method call
|
11
|
+
def self.method_call(node, name)
|
12
|
+
name && node.method_call?(name)
|
10
13
|
end
|
11
14
|
|
12
15
|
# Updating an ActiveRecord model attribute
|
13
16
|
def self.ar_update(node, name)
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
node.children[1] == :update &&
|
23
|
-
node.children[1].is_a_hash_containing_node(children: [:name])
|
17
|
+
name &&
|
18
|
+
(
|
19
|
+
ar_update_hash(node, name) ||
|
20
|
+
ar_update_positional(node, name) ||
|
21
|
+
ar_update_dynamic_method(node, name) ||
|
22
|
+
ar_update_attributes(node, name) ||
|
23
|
+
ar_update_hash_element(node, name)
|
24
|
+
)
|
24
25
|
end
|
25
26
|
|
26
27
|
# Assignment to a specified lvalue
|
27
|
-
def self.assignment(node,
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
def self.assignment(node, name)
|
29
|
+
name &&
|
30
|
+
node.assignment? &&
|
31
|
+
node.value == name
|
31
32
|
end
|
32
33
|
|
33
34
|
# Node is a literal int, float, or string
|
34
|
-
|
35
|
-
|
36
|
-
|
35
|
+
def self.literal(node, value)
|
36
|
+
number(node, value) || str(node, value)
|
37
|
+
end
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
39
|
+
# Node is a literal int or float
|
40
|
+
def self.number(node, value)
|
41
|
+
value &&
|
42
|
+
node.number? &&
|
43
|
+
node.value.to_s == value
|
41
44
|
end
|
42
45
|
|
43
46
|
# Node is a Ruby constant
|
44
|
-
def self.const(node,
|
45
|
-
|
46
|
-
|
47
|
-
|
47
|
+
def self.const(node, name)
|
48
|
+
name &&
|
49
|
+
node.const? &&
|
50
|
+
node.children.last == name
|
48
51
|
end
|
49
52
|
|
50
53
|
# Node is a string
|
51
|
-
def self.str(node,
|
52
|
-
|
54
|
+
def self.str(node, pattern)
|
55
|
+
pattern &&
|
56
|
+
node.str? &&
|
57
|
+
node.any_descendant_matches?(
|
58
|
+
->(n) { n.is_a?(String) && n.match?(pattern) }
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
53
63
|
|
54
|
-
|
55
|
-
|
64
|
+
private_class_method def self.ar_update_hash(node, name)
|
65
|
+
return false unless node.method_call?(
|
66
|
+
%i[update update! assign_attributes update_attributes update_attributes!
|
67
|
+
update_columns update_all upsert upsert_all insert insert! insert_all
|
68
|
+
insert_all!]
|
56
69
|
)
|
70
|
+
|
71
|
+
hash_arg = if node.children[3]&.type == :hash
|
72
|
+
node.children[3]
|
73
|
+
else
|
74
|
+
node.children[2]
|
75
|
+
end
|
76
|
+
|
77
|
+
return false unless hash_arg.is_a?(Node)
|
78
|
+
|
79
|
+
hash_arg.children.any? do |child|
|
80
|
+
child.is_a?(Node) && child.children[0].value == name
|
81
|
+
end
|
57
82
|
end
|
58
83
|
|
59
|
-
|
60
|
-
|
61
|
-
|
84
|
+
private_class_method def self.ar_update_positional(node, name)
|
85
|
+
return false unless node.method_call?(
|
86
|
+
%i[write_attribute update_attribute update_column]
|
87
|
+
)
|
62
88
|
|
63
|
-
|
89
|
+
node.children[2].value == name
|
64
90
|
end
|
65
91
|
|
66
|
-
|
67
|
-
|
68
|
-
|
92
|
+
private_class_method def self.ar_update_dynamic_method(node, name)
|
93
|
+
node.method_call?("#{name}=".to_sym)
|
94
|
+
end
|
95
|
+
|
96
|
+
private_class_method def self.ar_update_attributes(node, name)
|
97
|
+
node.method_call?(:attributes=) &&
|
98
|
+
node.children.last.type == :hash &&
|
99
|
+
node.children.last.children.any? do |child|
|
100
|
+
child.is_a?(Node) && child.children[0].value == name
|
101
|
+
end
|
102
|
+
end
|
69
103
|
|
70
|
-
|
104
|
+
private_class_method def self.ar_update_hash_element(node, name)
|
105
|
+
node.method_call?(:[]=) && node.children[-2].value == name
|
71
106
|
end
|
72
107
|
end
|
73
108
|
end
|
data/lib/rbr/node.rb
CHANGED
@@ -12,15 +12,25 @@ module Rbr
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def const?
|
15
|
-
|
15
|
+
@ast_node.type == :const
|
16
16
|
end
|
17
17
|
|
18
18
|
def literal?
|
19
19
|
%i[int float str].include? @ast_node.type
|
20
20
|
end
|
21
21
|
|
22
|
-
def
|
23
|
-
%i[
|
22
|
+
def number?
|
23
|
+
%i[int float].include? @ast_node.type
|
24
|
+
end
|
25
|
+
|
26
|
+
def method_call?(names)
|
27
|
+
%i[send csend].include?(@ast_node.type) &&
|
28
|
+
begin
|
29
|
+
names = [names] unless names.is_a?(Array)
|
30
|
+
|
31
|
+
names.include?(children[1]) ||
|
32
|
+
(children[1] == :send && names.include?(children[2].value))
|
33
|
+
end
|
24
34
|
end
|
25
35
|
|
26
36
|
def nil?
|
@@ -31,19 +41,37 @@ module Rbr
|
|
31
41
|
%i[str dstr xstr].include? @ast_node.type
|
32
42
|
end
|
33
43
|
|
44
|
+
def type
|
45
|
+
@ast_node.type
|
46
|
+
end
|
47
|
+
|
48
|
+
def expression
|
49
|
+
@ast_node.loc.expression
|
50
|
+
end
|
51
|
+
|
34
52
|
def children
|
35
|
-
@ast_node.children
|
53
|
+
@ast_node.children.map do |child|
|
54
|
+
if child.is_a?(Parser::AST::Node)
|
55
|
+
Rbr::Node.new(child)
|
56
|
+
else
|
57
|
+
child
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def value
|
63
|
+
@ast_node.children[0]
|
36
64
|
end
|
37
65
|
|
38
66
|
def pretty_print
|
39
|
-
"#{
|
67
|
+
"#{expression.line}: #{expression.source}"
|
40
68
|
end
|
41
69
|
|
42
70
|
# Call the the proc, passing in this node and all children recursively. Return
|
43
71
|
# true if any call evaluates to true.
|
44
|
-
def
|
72
|
+
def any_descendant_matches?(match_proc, root = self)
|
45
73
|
if root.respond_to?(:children)
|
46
|
-
root.children.any? { |c|
|
74
|
+
root.children.any? { |c| any_descendant_matches?(match_proc, c) }
|
47
75
|
else
|
48
76
|
match_proc.call(root)
|
49
77
|
end
|
data/lib/rbr/query.rb
CHANGED
@@ -1,21 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "rbr/matchers"
|
4
|
+
require "rbr/comment_node"
|
4
5
|
require "rbr/node"
|
5
6
|
|
6
7
|
module Rbr
|
7
8
|
class Query
|
8
|
-
attr_reader :
|
9
|
+
attr_reader :matcher, :condition
|
9
10
|
|
10
|
-
def initialize(
|
11
|
-
@
|
11
|
+
def initialize(matcher, condition)
|
12
|
+
@matcher = matcher
|
13
|
+
@condition = condition
|
14
|
+
|
15
|
+
if matcher == :comment
|
16
|
+
alias run run_comments
|
17
|
+
else
|
18
|
+
alias run run_tree
|
19
|
+
|
20
|
+
if condition.is_a?(String) && condition.start_with?(":")
|
21
|
+
@condition = condition[1..].to_sym
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def run_comments(_node, comments)
|
27
|
+
comments.select { |comment| comment.text.match?(condition) }
|
28
|
+
.map { |comment| CommentNode.new(comment) }
|
12
29
|
end
|
13
30
|
|
14
|
-
def
|
31
|
+
def run_tree(node, _comments = [])
|
15
32
|
node = Node.new(node) unless node.is_a?(Node)
|
16
33
|
return [] if node.nil?
|
17
34
|
|
18
|
-
node_matches = Matchers.match(node,
|
35
|
+
node_matches = Matchers.match(node, matcher, condition)
|
19
36
|
found_children = node.children.map { |child| run(child) }
|
20
37
|
|
21
38
|
(node_matches ? [node] : []) + found_children.flatten
|
data/lib/rbr/version.rb
CHANGED
data/rbr.gemspec
CHANGED
@@ -31,11 +31,12 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.bindir = "exe"
|
32
32
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
33
33
|
spec.require_paths = ["lib"]
|
34
|
+
spec.required_ruby_version = ">= 2.6.0"
|
34
35
|
|
35
36
|
spec.add_dependency "parser"
|
36
37
|
|
37
38
|
spec.add_development_dependency "bundler", "~> 1.17"
|
38
39
|
spec.add_development_dependency "minitest", "~> 5.0"
|
39
40
|
spec.add_development_dependency "pry", "~> 0.0"
|
40
|
-
spec.add_development_dependency "rake", "~>
|
41
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
41
42
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rbr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eddie Lebow
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-06-
|
11
|
+
date: 2020-06-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: parser
|
@@ -72,14 +72,14 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
75
|
+
version: '13.0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
82
|
+
version: '13.0'
|
83
83
|
description:
|
84
84
|
email:
|
85
85
|
- elebow@users.noreply.github.com
|
@@ -94,8 +94,10 @@ files:
|
|
94
94
|
- Gemfile.lock
|
95
95
|
- README.md
|
96
96
|
- Rakefile
|
97
|
+
- TODO
|
97
98
|
- exe/rbr
|
98
99
|
- lib/rbr/cli.rb
|
100
|
+
- lib/rbr/comment_node.rb
|
99
101
|
- lib/rbr/matchers.rb
|
100
102
|
- lib/rbr/node.rb
|
101
103
|
- lib/rbr/query.rb
|
@@ -115,7 +117,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
115
117
|
requirements:
|
116
118
|
- - ">="
|
117
119
|
- !ruby/object:Gem::Version
|
118
|
-
version:
|
120
|
+
version: 2.6.0
|
119
121
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
120
122
|
requirements:
|
121
123
|
- - ">="
|