├── .rspec ├── lib ├── rubocopular │ └── version.rb └── rubocopular.rb ├── .travis.yml ├── Gemfile ├── Rakefile ├── bin ├── setup ├── console └── search ├── .gitignore ├── spec ├── spec_helper.rb └── rubocopular_spec.rb ├── LICENSE.txt ├── rubocopular.gemspec ├── CODE_OF_CONDUCT.md ├── Guardfile └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /lib/rubocopular/version.rb: -------------------------------------------------------------------------------- 1 | module Rubocopular 2 | VERSION = '0.1.0'.freeze 3 | end 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | rvm: 4 | - 2.3.3 5 | before_install: gem install bundler -v 1.14.6 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in rubocopular.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rspec/core/rake_task' 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task default: :spec 7 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 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 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | 11 | # rspec failure tracking 12 | .rspec_status 13 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | require 'rubocopular' 3 | 4 | RSpec.configure do |config| 5 | # Enable flags like --only-failures and --next-failure 6 | config.example_status_persistence_file_path = '.rspec_status' 7 | 8 | config.expect_with :rspec do |c| 9 | c.syntax = :expect 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'bundler/setup' 4 | require 'rubocopular' 5 | require 'pry' 6 | 7 | old_prompt = Pry.config.prompt 8 | Pry.config.prompt = [ 9 | proc { |*a| old_prompt.first.call(*a).gsub(/.*>/, '>') }, 10 | proc { |*a| old_prompt.second.call(*a) } 11 | ] 12 | 13 | Pry.print { |_, res| puts res } 14 | 15 | include RuboCop::AST::Sexp 16 | 17 | def node code 18 | Rubocopular.node code 19 | end 20 | 21 | def test pattern, code 22 | Rubocopular.test pattern, code 23 | end 24 | 25 | def r 26 | load 'lib/rubocopular.rb' 27 | load 'bin/console' 28 | end 29 | 30 | Pry.start 31 | -------------------------------------------------------------------------------- /lib/rubocopular.rb: -------------------------------------------------------------------------------- 1 | require 'rubocopular/version' 2 | require 'rubocop' 3 | require 'coderay' 4 | 5 | module Rubocopular 6 | def self.node(code) 7 | RuboCop::ProcessedSource.new(code.to_s, 2.3).ast 8 | end 9 | 10 | def self.parse_source(path) 11 | RuboCop::ProcessedSource.new(IO.read(path), 2.3, path) 12 | end 13 | 14 | def self.test(pattern, code) 15 | code = node(code) unless code.is_a?(RuboCop::AST::Node) 16 | RuboCop::NodePattern.new(pattern).match(code) 17 | end 18 | 19 | def self.inspect(pattern, code) 20 | RuboCop::NodePattern.new(pattern.gsub(/(\.{3}|_)/, '$\1')).match(node(code)) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /bin/search: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'bundler/setup' 4 | require 'rubocopular' 5 | 6 | pattern = ARGV[0] 7 | files = ARGV[1..-1] || Dir['**/*.rb'] 8 | 9 | include RuboCop::AST::Traversal 10 | 11 | def match pattern, node 12 | match = pattern.match( node) 13 | if match 14 | match == true ? node : [match, node] 15 | else 16 | unless node.children.empty? 17 | node.children.grep(RuboCop::AST::Node).flat_map{|e| match(pattern, e) }.compact 18 | end 19 | end 20 | end 21 | 22 | def line_and_code(result) 23 | if result.is_a?(RuboCop::AST::Node) 24 | range = result.source_range 25 | result = result.source 26 | else 27 | result, node = result 28 | range = node.source_range 29 | end 30 | [ range.line, " #{CodeRay.scan(result, :ruby).term}"] 31 | end 32 | 33 | pattern = RuboCop::NodePattern.new(pattern) 34 | 35 | files.each do |file| 36 | processed_source = Rubocopular.parse_source(file) 37 | results = match(pattern, processed_source.ast) 38 | results.each do |result| 39 | line = [file, *line_and_code(result)] 40 | puts line.join(":") 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Jônatas Davi Paganini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /rubocopular.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'rubocopular/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'rubocopular' 8 | spec.version = Rubocopular::VERSION 9 | spec.authors = ['Jônatas Davi Paganini'] 10 | spec.email = ['jonatas.paganini@toptal.com'] 11 | 12 | spec.summary = 'Playground for RuboCop node matcher.' 13 | spec.description = 'It allows you to inspect your current node search' 14 | spec.homepage = 'http://rubocopular.ideia.me' 15 | spec.license = 'MIT' 16 | 17 | # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host' 18 | # to allow pushing to a single host or delete this section to allow pushing to any host. 19 | if spec.respond_to?(:metadata) 20 | spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'" 21 | else 22 | raise 'RubyGems 2.0 or newer is required to protect against ' \ 23 | 'public gem pushes.' 24 | end 25 | 26 | spec.files = `git ls-files -z`.split("\x0").reject do |f| 27 | f.match(%r{^(test|spec|features)/}) 28 | end 29 | spec.bindir = 'exe' 30 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 31 | spec.require_paths = ['lib'] 32 | 33 | spec.add_development_dependency 'bundler', '~> 1.14' 34 | spec.add_development_dependency 'rake', '~> 10.0' 35 | spec.add_development_dependency 'rspec', '~> 3.0' 36 | spec.add_development_dependency 'rubocop' 37 | spec.add_development_dependency 'coderay' 38 | spec.add_development_dependency 'pry' 39 | end 40 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at jonatas.paganini@toptal.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /spec/rubocopular_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | include RuboCop::AST::Sexp 4 | 5 | RSpec.describe Rubocopular do 6 | 7 | it 'has a version number' do 8 | expect(Rubocopular::VERSION).not_to be nil 9 | end 10 | 11 | describe '.node' do 12 | it { expect(described_class.node(1)).to eq(s(:int,1)) } 13 | it { expect(described_class.node('a string')).to eq(s(:send, nil, :a, s(:send, nil, :string))) } 14 | end 15 | 16 | describe '.test' do 17 | subject { described_class.test(pattern, code) } 18 | 19 | it { expect(described_class.test('(int _)', '1')).to be_truthy } 20 | it { expect(described_class.test('(str _)', '""')).to be_truthy } 21 | it { expect(described_class.test('(str _)', '1')).to be_falsy } 22 | 23 | describe 'capturing' do 24 | it { expect(described_class.test('(:def $_ _ (send _ ... ) )', 'def a; b.c end')).to eq(:a) } 25 | it { expect(described_class.test('(:def _ $_ (send _ ... ) )', 'def a; b.c end')).to eq(s(:args)) } 26 | it { expect(described_class.test('(:def _ _ (send $_ ... ) )', 'def a; b.c end')).to eq(s(:send, nil, :b)) } 27 | it { expect(described_class.test('(:def _ _ (send _ $... ) )', 'def a; b.c end')).to eq([:c]) } 28 | it { expect(described_class.test('(:def _ _ (send _ $_ ) )', 'def a; b.c end')).to eq(:c) } 29 | it { expect(described_class.test('(:def ... (send _ $_ ) )', 'def a; b.c end')).to eq(:c) } 30 | end 31 | 32 | describe 'capturing' do 33 | let(:code) { 'def a(param); b.c.d.e.f end' } 34 | 35 | describe 'method name' do 36 | let(:pattern) { '(:def $_ _args (send (send ...) ... ) )' } 37 | it { is_expected.to eq :a } 38 | end 39 | 40 | describe 'params' do 41 | let(:pattern) { '(:def _ $_ (send (send ...) ... ) )' } 42 | it { is_expected.to eq s(:args, s(:arg, :param)) } 43 | end 44 | 45 | describe 'internal send methods' do 46 | let(:pattern) { '(:def _method _args (send (send $...) ... ) )' } 47 | it { is_expected.to eq [s(:send, s(:send, s(:send, nil, :b), :c), :d), :e] } 48 | end 49 | 50 | describe 'penultimate method name' do 51 | let(:pattern) { '(:def _method _args (send (send _ $...) ... ) )' } 52 | it { is_expected.to eq [:e] } 53 | end 54 | 55 | describe '3 levels up' do 56 | let(:pattern) { '(:def _method _args (send (send (send _ $...) ...) ... ) )' } 57 | it { is_expected.to eq [:d] } 58 | end 59 | 60 | describe 'multiple elements' do 61 | let(:pattern) { '(:def _method _args (send (send (send (send (send _ $...) ...) $...) ...) ... ) )' } 62 | it { is_expected.to eq [[:b],[:d]] } 63 | end 64 | 65 | describe 'all send stuff' do 66 | let(:pattern) { '(:def _method _args (send (send (send (send (send _ $...) $...) $...) $...) $... ) )' } 67 | it { is_expected.to eq [[:b], [:c], [:d], [:e], [:f]] } 68 | end 69 | end 70 | end 71 | 72 | describe '.inspect' do 73 | it 'works as .test but captures _ and ...' do 74 | inspect = described_class.inspect('(int _)', 1) 75 | test = described_class.test('(int $_)', 1) 76 | expect(test).to eq inspect 77 | end 78 | 79 | it { expect(described_class.inspect('(int _)', 1)).to eq 1 } 80 | it { expect(described_class.inspect('(send ...)', 'a.b')).to eq([s(:send, nil, :a), :b]) } 81 | 82 | shared_examples 'captures' do |result| 83 | subject(:capture) { described_class.inspect(pattern, source) } 84 | specify do 85 | expect(capture).to eq result 86 | end 87 | end 88 | 89 | describe 'captures _ and ...' do 90 | let(:source) { 'def b; a.b end' } 91 | let(:pattern) { '(:def _method _args (send _ ... ) )' } 92 | 93 | it_behaves_like 'captures', [:b, s(:args), s(:send, nil, :a), [:b]] 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | 4 | ## Uncomment and set this to only include directories you want to watch 5 | # directories %w(app lib config test spec features) \ 6 | # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")} 7 | 8 | ## Note: if you are using the `directories` clause above and you are not 9 | ## watching the project directory ('.'), then you will want to move 10 | ## the Guardfile to a watched dir and symlink it back, e.g. 11 | # 12 | # $ mkdir config 13 | # $ mv Guardfile config/ 14 | # $ ln -s config/Guardfile . 15 | # 16 | # and, you'll have to watch "config/Guardfile" instead of "Guardfile" 17 | 18 | guard 'livereload' do 19 | extensions = { 20 | css: :css, 21 | scss: :css, 22 | sass: :css, 23 | js: :js, 24 | coffee: :js, 25 | html: :html, 26 | png: :png, 27 | gif: :gif, 28 | jpg: :jpg, 29 | jpeg: :jpeg, 30 | # less: :less, # uncomment if you want LESS stylesheets done in browser 31 | } 32 | 33 | rails_view_exts = %w(erb haml slim) 34 | 35 | # file types LiveReload may optimize refresh for 36 | compiled_exts = extensions.values.uniq 37 | watch(%r{public/.+\.(#{compiled_exts * '|'})}) 38 | 39 | extensions.each do |ext, type| 40 | watch(%r{ 41 | (?:app|vendor) 42 | (?:/assets/\w+/(?[^.]+) # path+base without extension 43 | (?\.#{ext})) # matching extension (must be first encountered) 44 | (?:\.\w+|$) # other extensions 45 | }x) do |m| 46 | path = m[1] 47 | "/assets/#{path}.#{type}" 48 | end 49 | end 50 | 51 | # file needing a full reload of the page anyway 52 | watch(%r{app/views/.+\.(#{rails_view_exts * '|'})$}) 53 | watch(%r{app/helpers/.+\.rb}) 54 | watch(%r{config/locales/.+\.yml}) 55 | end 56 | 57 | # Note: The cmd option is now required due to the increasing number of ways 58 | # rspec may be run, below are examples of the most common uses. 59 | # * bundler: 'bundle exec rspec' 60 | # * bundler binstubs: 'bin/rspec' 61 | # * spring: 'bin/rspec' (This will use spring if running and you have 62 | # installed the spring binstubs per the docs) 63 | # * zeus: 'zeus rspec' (requires the server to be started separately) 64 | # * 'just' rspec: 'rspec' 65 | 66 | guard :rspec, cmd: "bundle exec rspec" do 67 | require "guard/rspec/dsl" 68 | dsl = Guard::RSpec::Dsl.new(self) 69 | 70 | # Feel free to open issues for suggestions and improvements 71 | 72 | # RSpec files 73 | rspec = dsl.rspec 74 | watch(rspec.spec_helper) { rspec.spec_dir } 75 | watch(rspec.spec_support) { rspec.spec_dir } 76 | watch(rspec.spec_files) 77 | 78 | # Ruby files 79 | ruby = dsl.ruby 80 | dsl.watch_spec_files_for(ruby.lib_files) 81 | 82 | # Rails files 83 | rails = dsl.rails(view_extensions: %w(erb haml slim)) 84 | dsl.watch_spec_files_for(rails.app_files) 85 | dsl.watch_spec_files_for(rails.views) 86 | 87 | watch(rails.controllers) do |m| 88 | [ 89 | rspec.spec.call("routing/#{m[1]}_routing"), 90 | rspec.spec.call("controllers/#{m[1]}_controller"), 91 | rspec.spec.call("acceptance/#{m[1]}") 92 | ] 93 | end 94 | 95 | # Rails config changes 96 | watch(rails.spec_helper) { rspec.spec_dir } 97 | watch(rails.routes) { "#{rspec.spec_dir}/routing" } 98 | watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" } 99 | 100 | # Capybara features specs 101 | watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") } 102 | watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") } 103 | 104 | # Turnip features and steps 105 | watch(%r{^spec/acceptance/(.+)\.feature$}) 106 | watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m| 107 | Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance" 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rubocopular 2 | 3 | It's a small gem to help you debug your RuboCop node patterns. 4 | 5 | 6 | ## Try on console 7 | 8 | ```bash 9 | git clone git@github.com:jonatas/rubocopular.git 10 | cd rubocopular 11 | bin/setup 12 | ``` 13 | 14 | ## Usage of `bin/console` 15 | 16 | Try run `bin/console` for an interactive prompt that will allow you to experiment. 17 | 18 | The `node` method will allow you to transform your code into `AST`. 19 | 20 | ```ruby 21 | > Rubocopular.node(1) 22 | => s(:int, 1) 23 | > Rubocopular.node('1') 24 | => s(:int, 1) 25 | > Rubocopular.node("'1'") 26 | => s(:str, "1") 27 | > Rubocopular.node("a = 1") 28 | => s(:lvasgn, :a, 29 | s(:int, 1)) 30 | > Rubocopular.node("def method ; body end") 31 | => s(:def, :method, 32 | s(:args), 33 | s(:send, nil, :body)) 34 | ``` 35 | 36 | If you're using `bin/console` you can also call `node` or `test` directly. 37 | 38 | ``` 39 | > node("a.b.c.d") 40 | => s(:send, 41 | s(:send, 42 | s(:send, 43 | s(:send, nil, :a), :b), :c), :d) 44 | ``` 45 | 46 | You can also navigate on children nodes: 47 | 48 | ```ruby 49 | > nodes = Rubocopular.node('class A; a; b; c -> {} end').children.last 50 | => s(:begin, 51 | s(:send, nil, :a), 52 | s(:send, nil, :b), 53 | s(:send, nil, :c, 54 | s(:block, 55 | s(:send, nil, :lambda), 56 | s(:args), nil))) 57 | > nodes.children.map(&:method_name) 58 | => [:a, :b, :c] 59 | ``` 60 | 61 | You can test your matchers and inspect them: 62 | 63 | ```ruby 64 | > Rubocopular.inspect('(send ...)',"a.b.c.d") 65 | => [s(:send, 66 | s(:send, 67 | s(:send, nil, :a), :b), :c), :d] 68 | ``` 69 | 70 | The `inspect` is just wrapping the `_` and `...` to call `.test` method: 71 | 72 | ```ruby 73 | > Rubocopular.test('(:def _method _args (send (send (send _ $...) ...) ... ) )', 'def a; b.c.d.e.f end') 74 | => [:d] 75 | > Rubocopular.test('(:def _method _args (send (send (send (send _ $...) ...) ...) ... ) )', 'def a; b.c.d.e.f end') 76 | => [:c] 77 | > Rubocopular.test('(:def _method _args (send (send (send (send (send _ $...) $...) ...) ...) ... ) )', 'def a; b.c.d.e.f end') 78 | => [[:b], [:c]] 79 | > Rubocopular.test('(:def _method _args (send (send (send (send (send _ $...) $...) $...) ...) ... ) )', 'def a; b.c.d.e.f end') 80 | => [[:b], [:c], [:d]] 81 | > Rubocopular.test('(:def _method _args (send (send (send (send (send _ $...) $...) $...) $...) $... ) )', 'def a; b.c.d.e.f end') 82 | => [[:b], [:c], [:d], [:e], [:f]] 83 | > Rubocopular.test('(:def $_ _args (send (send (send (send (send _ $_) $_) $_) $_) $_ ) )', 'def a; b.c.d.e.f end') 84 | => [:a, :b, :c, :d, :e, :f] 85 | > Rubocopular.inspect('(:def _ (:args) (send (send (send (send (send _ _) _) _) _) _ ) )', 'def a; b.c.d.e.f end') 86 | => [:a, nil, :b, :c, :d, :e, :f] 87 | ``` 88 | 89 | Check that the examples uses more `...` than `_` to allow more flexibility on the syntax: 90 | 91 | ```ruby 92 | > Rubocopular.test('(:def _method _args (send (send (send (send (send _ $...) $...) $_) $...) $... ) )', 'def a; b.c.d.e(param).f end') 93 | => [[:b], [:c], :d, [:e, s(:send, nil, :param)], [:f]] 94 | > Rubocopular.test('(:def _method _args (send (send (send (send (send _ $...) $...) $_) $...) $... ) )', 'def a; b.c.d(param).e.f end') 95 | => nil 96 | Rubocopular.test('(:def _method _args (send (send (send (send (send _ $...) $...) $_) $...) $... ) )', 'def a; b.c.d.e.f end') 97 | => [[:b], [:c], :d, [:e], [:f]] 98 | ``` 99 | 100 | Keep in mind `...` can be anything and `_` is only one thing. 101 | 102 | You can also use `{}` to wrap different nodes 103 | 104 | ```ruby 105 | > Rubocopular.test('(${def kwbegin} ... (rescue _ $...))','def a ; rescue => e; end') 106 | => [:def, [s(:resbody, nil, 107 | s(:lvasgn, :e), nil), nil]] 108 | > Rubocopular.test('(${def kwbegin} ... (rescue _ $...))','begin ; rescue => e; end') 109 | => [:kwbegin, [s(:resbody, nil, 110 | s(:lvasgn, :e), nil), nil]] 111 | ``` 112 | 113 | In this case the `...` was used only for `:def` but it still needed. 114 | 115 | ```ruby 116 | > Rubocopular.test('(${def kwbegin} _ _ (rescue _ $...))','def a ; rescue => e; end') 117 | => [:def, [s(:resbody, nil, 118 | s(:lvasgn, :e), nil), nil]] 119 | > Rubocopular.test('(${def kwbegin} _ _ (rescue _ $...))','begin ; rescue => e; end') 120 | => nil 121 | ``` 122 | 123 | You can remind that `...` is anything and it includes nothing. While `_` is always one thing. 124 | 125 | ## Usage of `bin/search` 126 | 127 | You can also do like a "grep" using `bin/search`: 128 | 129 | Trying it in this project: 130 | 131 | $ bin/search '(const ... )' lib/*.rb 132 | 133 | It will output something like: 134 | 135 | ``` 136 | lib/rubocopular.rb:5: Rubocopular 137 | lib/rubocopular.rb:7: RuboCop::ProcessedSource 138 | lib/rubocopular.rb:11: RuboCop::ProcessedSource 139 | lib/rubocopular.rb:11: IO 140 | lib/rubocopular.rb:15: RuboCop::AST::Node 141 | lib/rubocopular.rb:16: RuboCop::NodePattern 142 | lib/rubocopular.rb:20: RuboCop::NodePattern 143 | lib/rubocopular.rb:24: RuboCop::AST::Node 144 | ``` 145 | 146 | It prints nodes that matches with the current code: 147 | 148 | $ bin/search '(defs ... )' lib/*.rb 149 | 150 | ``` 151 | lib/rubocopular.rb:6: def self.node(code) 152 | RuboCop::ProcessedSource.new(code.to_s, 2.3).ast 153 | end 154 | lib/rubocopular.rb:10: def self.parse_source(path) 155 | RuboCop::ProcessedSource.new(IO.read(path), 2.3, path) 156 | end 157 | lib/rubocopular.rb:14: def self.test(pattern, code) 158 | code = node(code) unless code.is_a?(RuboCop::AST::Node) 159 | RuboCop::NodePattern.new(pattern).match(code) 160 | end 161 | lib/rubocopular.rb:19: def self.inspect(pattern, code) 162 | RuboCop::NodePattern.new(pattern.gsub(/(\.{3}|_)/, '$\1')).match(node(code)) 163 | end 164 | ``` 165 | 166 | ## Development 167 | 168 | Use `guard` to follow the tests running. 169 | 170 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. 171 | 172 | 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). 173 | 174 | ## Contributing 175 | 176 | Bug reports and pull requests are welcome on GitHub at https://github.com/jonatas/rubocopular. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. 177 | 178 | ## License 179 | 180 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 181 | 182 | --------------------------------------------------------------------------------