Skip to content

Commit

Permalink
[Fixes #43] Basic support for non-legacy emitters
Browse files Browse the repository at this point in the history
  • Loading branch information
marcandre committed Jun 22, 2020
1 parent c6db124 commit c03d6dc
Show file tree
Hide file tree
Showing 13 changed files with 205 additions and 14 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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

0 comments on commit c03d6dc

Please sign in to comment.