Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fixes #43] Basic support for non-legacy emitters #46

Merged
merged 2 commits into from Jun 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/rubocop.yml
Expand Up @@ -20,12 +20,14 @@ jobs:
ruby: [ 2.4, 2.5, 2.6, 2.7, head ]
rubocop: [ master ]
coverage: [ null ]
modern: [ null ]
title: [ null ]
include:
- { os: windows, rubocop: master, ruby: mingw }
- { rubocop: '0.84.0', ruby: 2.4, os: ubuntu }
- { rubocop: '0.84.0', ruby: head, os: ubuntu }
- { rubocop: '0.84.0', ruby: 2.4, os: ubuntu, coverage: true, title: 'Cov' }
- { rubocop: master, ruby: 2.7, os: ubuntu, modern: true, title: 'Modern' }

steps:
- name: windows misc
Expand Down Expand Up @@ -62,6 +64,9 @@ jobs:
with:
coverageCommand: bundle exec rake spec
debug: true
- name: Set modernize mode
if: matrix.modern == true
run: echo '::set-env name=MODERNIZE::true'
- name: spec
if: matrix.coverage != true
run: bundle exec rake spec
Expand Down
10 changes: 10 additions & 0 deletions README.md
Expand Up @@ -30,6 +30,16 @@ gem 'rubocop-ast'

Refer to the documentation of `RuboCop::AST::Node` and [`RuboCop::AST::NodePattern`](docs/modules/ROOT/pages/node_pattern.adoc)

### Parser compatibility switches

The main `RuboCop` gem uses [legacy AST output from parser](https://github.com/whitequark/parser/#usage).
This gem is meant to be compatible with all settings. For example, to have `-> { ... }` emitted
as `LambdaNode` instead of `SendNode`:

```ruby
RuboCop::AST::Builder.emit_lambda = true
```

## Contributing

Checkout the [contribution guidelines](CONTRIBUTING.md).
Expand Down
3 changes: 3 additions & 0 deletions lib/rubocop/ast.rb
Expand Up @@ -35,8 +35,11 @@
require_relative 'ast/node/float_node'
require_relative 'ast/node/hash_node'
require_relative 'ast/node/if_node'
require_relative 'ast/node/index_node'
require_relative 'ast/node/indexasgn_node'
require_relative 'ast/node/int_node'
require_relative 'ast/node/keyword_splat_node'
require_relative 'ast/node/lambda_node'
require_relative 'ast/node/module_node'
require_relative 'ast/node/or_node'
require_relative 'ast/node/pair_node'
Expand Down
3 changes: 3 additions & 0 deletions lib/rubocop/ast/builder.rb
Expand Up @@ -35,9 +35,12 @@ class Builder < Parser::Builders::Default
hash: HashNode,
if: IfNode,
int: IntNode,
index: IndexNode,
indexasgn: IndexasgnNode,
irange: RangeNode,
erange: RangeNode,
kwsplat: KeywordSplatNode,
lambda: LambdaNode,
module: ModuleNode,
or: OrNode,
pair: PairNode,
Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop/ast/node/def_node.rb
Expand Up @@ -24,7 +24,7 @@ def void_context?
#
# @return [Boolean] whether the `def` node uses argument forwarding
def argument_forwarding?
arguments.any?(&:forward_args_type?)
arguments.any?(&:forward_args_type?) || arguments.any?(&:forward_arg_type?)
end

# The name of the defined method as a symbol.
Expand Down
15 changes: 15 additions & 0 deletions lib/rubocop/ast/node/forward_args_node.rb
Expand Up @@ -5,6 +5,21 @@ module AST
# A node extension for `forward-args` nodes. This will be used in place
# of a plain node when the builder constructs the AST, making its methods
# available to all `forward-args` nodes within RuboCop.
#
# Not used with modern emitters:
#
# $ ruby-parse -e "def foo(...); end"
# (def :foo
# (args
# (forward-arg)) nil)
# $ ruby-parse --legacy -e "->(foo) { bar }"
# (def :foo
# (forward-args) nil)
#
# Note the extra 's' with legacy form.
#
# The main RuboCop runs in legacy mode; this node is only used
# if user `AST::Builder.modernize` or `AST::Builder.emit_lambda=true`
class ForwardArgsNode < Node
include CollectionNode

Expand Down
46 changes: 46 additions & 0 deletions lib/rubocop/ast/node/index_node.rb
@@ -0,0 +1,46 @@
# frozen_string_literal: true

module RuboCop
module AST
# Used for modern support only!
# Not as thoroughly tested as legacy equivalent
#
# $ ruby-parse -e "foo[:bar]"
# (index
# (send nil :foo)
# (sym :bar))
# $ ruby-parse --legacy -e "foo[:bar]"
# (send
# (send nil :foo) :[]
# (sym :bar))
#
# The main RuboCop runs in legacy mode; this node is only used
# if user `AST::Builder.modernize` or `AST::Builder.emit_index=true`
class IndexNode < Node
include ParameterizedNode
include MethodDispatchNode

# For similarity with legacy mode
def attribute_accessor?
false
end

# For similarity with legacy mode
def assignment_method?
false
end

# For similarity with legacy mode
def method_name
:[]
end

# An array containing the arguments of the dispatched method.
#
# @return [Array<Node>] the arguments of the dispatched method
def arguments
node_parts[1..-1]
end
end
end
end
48 changes: 48 additions & 0 deletions lib/rubocop/ast/node/indexasgn_node.rb
@@ -0,0 +1,48 @@
# frozen_string_literal: true

module RuboCop
module AST
# Used for modern support only!
# Not as thoroughly tested as legacy equivalent
#
# $ ruby-parse -e "foo[:bar] = :baz"
# (indexasgn
# (send nil :foo)
# (sym :bar)
# (sym :baz))
# $ ruby-parse --legacy -e "foo[:bar] = :baz"
# (send
# (send nil :foo) :[]=
# (sym :bar)
# (sym :baz))
#
# The main RuboCop runs in legacy mode; this node is only used
# if user `AST::Builder.modernize` or `AST::Builder.emit_index=true`
class IndexasgnNode < Node
include ParameterizedNode
include MethodDispatchNode

# For similarity with legacy mode
def attribute_accessor?
false
end

# For similarity with legacy mode
def assignment_method?
true
end

# For similarity with legacy mode
def method_name
:[]=
end

# An array containing the arguments of the dispatched method.
#
# @return [Array<Node>] the arguments of the dispatched method
def arguments
node_parts[1..-1]
end
end
end
end
58 changes: 58 additions & 0 deletions lib/rubocop/ast/node/lambda_node.rb
@@ -0,0 +1,58 @@
# frozen_string_literal: true

module RuboCop
module AST
# Used for modern support only:
# Not as thoroughly tested as legacy equivalent
#
# $ ruby-parse -e "->(foo) { bar }"
# (block
# (lambda)
# (args
# (arg :foo))
# (send nil :bar))
# $ ruby-parse --legacy -e "->(foo) { bar }"
# (block
# (send nil :lambda)
# (args
# (arg :foo))
# (send nil :bar))
#
# The main RuboCop runs in legacy mode; this node is only used
# if user `AST::Builder.modernize` or `AST::Builder.emit_lambda=true`
class LambdaNode < Node
include ParameterizedNode
include MethodDispatchNode

# For similarity with legacy mode
def lambda?
true
end

# For similarity with legacy mode
def lambda_literal?
true
end

# For similarity with legacy mode
def attribute_accessor?
false
end

# For similarity with legacy mode
def assignment_method?
false
end

# For similarity with legacy mode
def method_name
:lambda
end

# For similarity with legacy mode
def arguments
[]
end
end
end
end
3 changes: 2 additions & 1 deletion lib/rubocop/ast/node/mixin/method_dispatch_node.rb
Expand Up @@ -3,7 +3,8 @@
module RuboCop
module AST
# Common functionality for nodes that are a kind of method dispatch:
# `send`, `csend`, `super`, `zsuper`, `yield`, `defined?`
# `send`, `csend`, `super`, `zsuper`, `yield`, `defined?`,
# and (modern only): `index`, `indexasgn`, `lambda`
module MethodDispatchNode
extend NodePattern::Macros
include MethodIdentifierPredicates
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop/ast/node/mixin/parameterized_node.rb
Expand Up @@ -4,6 +4,7 @@ module RuboCop
module AST
# Common functionality for nodes that are parameterized:
# `send`, `super`, `zsuper`, `def`, `defs`
# and (modern only): `index`, `indexasgn`, `lambda`
module ParameterizedNode
# Checks whether this node's arguments are wrapped in parentheses.
#
Expand Down
8 changes: 5 additions & 3 deletions lib/rubocop/ast/traversal.rb
Expand Up @@ -19,9 +19,10 @@ def walk(node)
rational str sym regopt self lvar
ivar cvar gvar nth_ref back_ref cbase
arg restarg blockarg shadowarg
kwrestarg zsuper lambda redo retry
kwrestarg zsuper redo retry
forward_args forwarded_args
match_var match_nil_pattern empty_else].freeze
match_var match_nil_pattern empty_else
forward_arg lambda procarg0 __ENCODING__].freeze
ONE_CHILD_NODE = %i[splat kwsplat block_pass not break next
preexe postexe match_current_line defined?
arg_expr pin match_rest if_guard unless_guard
Expand All @@ -33,7 +34,8 @@ def walk(node)
match_with_lvasgn begin kwbegin return
in_match match_alt
match_as array_pattern array_pattern_with_tail
hash_pattern const_pattern].freeze
hash_pattern const_pattern
index indexasgn].freeze
SECOND_CHILD_ONLY = %i[lvasgn ivasgn cvasgn gvasgn optarg kwarg
kwoptarg].freeze

Expand Down
21 changes: 12 additions & 9 deletions spec/rubocop/ast/forward_args_node_spec.rb
Expand Up @@ -2,18 +2,21 @@

RSpec.describe RuboCop::AST::ForwardArgsNode do
let(:args_node) { parse_source(source).ast.arguments }
let(:source) { 'def foo(...); end' }

context 'when using Ruby 2.7 or newer', :ruby27 do
describe '.new' do
let(:source) { 'def foo(...); end' }
if RuboCop::AST::Builder.emit_forward_arg
describe '#to_a' do
it { expect(args_node.to_a).to contain_exactly(be_forward_arg_type) }
end
else
describe '.new' do
it { expect(args_node.is_a?(described_class)).to be(true) }
end

it { expect(args_node.is_a?(described_class)).to be(true) }
end

describe '#to_a' do
let(:source) { 'def foo(...); end' }

it { expect(args_node.to_a).to contain_exactly(args_node) }
describe '#to_a' do
it { expect(args_node.to_a).to contain_exactly(args_node) }
end
end
end
end
1 change: 1 addition & 0 deletions spec/spec_helper.rb
Expand Up @@ -8,6 +8,7 @@
end

require 'rubocop-ast'
RuboCop::AST::Builder.modernize if ENV['MODERNIZE']

RSpec.shared_context 'ruby 2.3', :ruby23 do
let(:ruby_version) { 2.3 }
Expand Down