diff --git a/.rubocop.yml b/.rubocop.yml index 0be2dabd2..fca2e0a74 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -16,6 +16,7 @@ AllCops: - 'lib/rubocop/ast/node_pattern/parser.racc.rb' - 'lib/rubocop/ast/node_pattern/lexer.rex.rb' - 'spec/rubocop/ast/node_pattern/parse_helper.rb' + - 'spec/rubocop/ast/fixtures/*' TargetRubyVersion: 2.4 Naming/PredicateName: diff --git a/Rakefile b/Rakefile index 32ba7c7c6..074fb3a0e 100644 --- a/Rakefile +++ b/Rakefile @@ -36,6 +36,7 @@ task :internal_investigation do else 'rubocop' end + ENV['RUBOCOP_DEBUG'] = 't' sh exe end end diff --git a/changelog/fix_refactor_traversal_with_a_simple.md b/changelog/fix_refactor_traversal_with_a_simple.md new file mode 100644 index 000000000..4c8d66fef --- /dev/null +++ b/changelog/fix_refactor_traversal_with_a_simple.md @@ -0,0 +1 @@ +* [#117](https://github.com/rubocop-hq/rubocop-ast/pull/117): All nodes of `break` and `next` are now traversed. ([@marcandre][]) diff --git a/lib/rubocop/ast/traversal.rb b/lib/rubocop/ast/traversal.rb index 2df9e39b0..ae68ca081 100644 --- a/lib/rubocop/ast/traversal.rb +++ b/lib/rubocop/ast/traversal.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -# rubocop:disable Metrics/ModuleLength module RuboCop module AST # Provides methods for traversing an AST. @@ -8,6 +7,11 @@ module AST # Override methods to perform custom processing. Remember to call `super` # if you want to recursively process descendant nodes. module Traversal + # Only for debugging. + # @api private + class DebugError < RuntimeError + end + TYPE_TO_METHOD = Hash.new { |h, type| h[type] = :"on_#{type}" } def walk(node) @@ -17,191 +21,145 @@ def walk(node) nil end - NO_CHILD_NODES = %i[true false nil int float complex - rational str sym regopt self lvar - ivar cvar gvar nth_ref back_ref cbase - arg restarg blockarg shadowarg - kwrestarg zsuper redo retry - forward_args forwarded_args - 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 - match_with_trailing_comma].freeze - MANY_CHILD_NODES = %i[dstr dsym xstr regexp array hash pair - mlhs masgn or_asgn and_asgn rasgn mrasgn - undef alias args super yield or and - while_post until_post iflipflop eflipflop - match_with_lvasgn begin kwbegin return - in_match match_alt - match_as array_pattern array_pattern_with_tail - hash_pattern const_pattern find_pattern - index indexasgn].freeze - SECOND_CHILD_ONLY = %i[lvasgn ivasgn cvasgn gvasgn optarg kwarg - kwoptarg].freeze - private_constant :NO_CHILD_NODES, :ONE_CHILD_NODE, :MANY_CHILD_NODES, :SECOND_CHILD_ONLY - - NO_CHILD_NODES.each do |type| - module_eval("def on_#{type}(node); end", __FILE__, __LINE__) - end - - ONE_CHILD_NODE.each do |type| - module_eval(<<~RUBY, __FILE__, __LINE__ + 1) - def on_#{type}(node) - if (child = node.children[0]) - send(TYPE_TO_METHOD[child.type], child) + # @api private + module CallbackCompiler + SEND = 'send(TYPE_TO_METHOD[child.type], child)' + assign_code = 'child = node.children[%i]' + code = "#{assign_code}\n#{SEND}" + TEMPLATE = { + skip: '', + always: code, + nil?: "#{code} if child" + }.freeze + + def def_callback(type, *signature, + arity: signature.size..signature.size, + arity_check: ENV['RUBOCOP_DEBUG'] && self.arity_check(arity), + body: self.body(signature, arity_check)) + type, *aliases = type + lineno = caller_locations(1, 1).first.lineno + module_eval(<<~RUBY, __FILE__, lineno) # rubocop:disable Style/EvalWithLocation + def on_#{type}(node) + #{body} + nil end + RUBY + aliases.each do |m| + alias_method "on_#{m}", "on_#{type}" end - RUBY - end - - MANY_CHILD_NODES.each do |type| - module_eval(<<~RUBY, __FILE__, __LINE__ + 1) - def on_#{type}(node) - node.children.each { |child| send(TYPE_TO_METHOD[child.type], child) } - nil - end - RUBY - end + end - SECOND_CHILD_ONLY.each do |type| - # Guard clause is for nodes nested within mlhs - module_eval(<<~RUBY, __FILE__, __LINE__ + 1) - def on_#{type}(node) - if (child = node.children[1]) - send(TYPE_TO_METHOD[child.type], child) + def body(signature, prelude) + signature + .map.with_index do |arg, i| + TEMPLATE[arg].gsub('%i', i.to_s) end - end - RUBY - end - - def on_const(node) - return unless (child = node.children[0]) - - send(TYPE_TO_METHOD[child.type], child) - end - - def on_casgn(node) - children = node.children - if (child = children[0]) # always const??? - send(TYPE_TO_METHOD[child.type], child) + .unshift(prelude) + .join("\n") end - return unless (child = children[2]) - - send(TYPE_TO_METHOD[child.type], child) - end - def on_class(node) - children = node.children - child = children[0] # always const??? - send(TYPE_TO_METHOD[child.type], child) - if (child = children[1]) - send(TYPE_TO_METHOD[child.type], child) + def arity_check(range) + <<~RUBY + n = node.children.size + raise DebugError, [ + 'Expected #{range} children, got', + n, 'for', node.inspect + ].join(' ') unless (#{range}).cover?(node.children.size) + RUBY end - return unless (child = children[2]) - - send(TYPE_TO_METHOD[child.type], child) - end - - def on_def(node) - children = node.children - on_args(children[1]) - return unless (child = children[2]) - - send(TYPE_TO_METHOD[child.type], child) end - - def on_send(node) + private_constant :CallbackCompiler + extend CallbackCompiler + send_code = CallbackCompiler::SEND + + ### arity == 0 + no_children = %i[true false nil self cbase zsuper redo retry + forward_args forwarded_args match_nil_pattern + forward_arg lambda empty_else kwnilarg + __FILE__ __LINE__ __ENCODING__] + + ### arity == 0..1 + opt_symbol_child = %i[restarg kwrestarg] + opt_node_child = %i[splat kwsplat match_rest] + + ### arity == 1 + literal_child = %i[int float complex + rational str sym lvar + ivar cvar gvar nth_ref back_ref + arg blockarg shadowarg + kwarg match_var] + + many_symbol_children = %i[regopt] + + node_child = %i[block_pass not + match_current_line defined? + arg_expr pin if_guard unless_guard + match_with_trailing_comma] + node_or_nil_child = %i[preexe postexe] + + NO_CHILD_NODES = (no_children + opt_symbol_child + literal_child).to_set.freeze + private_constant :NO_CHILD_NODES # Used by Commissioner + + ### arity > 1 + symbol_then_opt_node = %i[lvasgn ivasgn cvasgn gvasgn] + symbol_then_node_or_nil = %i[optarg kwoptarg] + node_then_opt_node = %i[while until module sclass] + + ### variable arity + many_node_children = %i[dstr dsym xstr regexp array hash pair + mlhs masgn or_asgn and_asgn rasgn mrasgn + undef alias args super yield or and + while_post until_post iflipflop eflipflop + match_with_lvasgn begin kwbegin return + in_match match_alt break next + match_as array_pattern array_pattern_with_tail + hash_pattern const_pattern find_pattern + index indexasgn procarg0] + many_opt_node_children = %i[case rescue resbody ensure for when + case_match in_pattern irange erange] + + ### Callbacks for above + def_callback no_children + def_callback opt_symbol_child, :skip, arity: 0..1 + def_callback opt_node_child, :nil?, arity: 0..1 + + def_callback literal_child, :skip + def_callback node_child, :always + def_callback node_or_nil_child, :nil? + + def_callback symbol_then_opt_node, :skip, :nil?, arity: 1..2 + def_callback symbol_then_node_or_nil, :skip, :nil? + def_callback node_then_opt_node, :always, :nil? + + def_callback many_symbol_children, :skip, arity_check: nil + def_callback many_node_children, body: <<~RUBY + node.children.each { |child| #{send_code} } + RUBY + def_callback many_opt_node_children, + body: <<~RUBY + node.children.each { |child| #{send_code} if child } + RUBY + + ### Other particular cases + def_callback :const, :nil?, :skip + def_callback :casgn, :nil?, :skip, :nil?, arity: 2..3 + def_callback :class, :always, :nil?, :nil? + def_callback :def, :skip, :always, :nil? + def_callback :op_asgn, :always, :skip, :always + def_callback :if, :always, :nil?, :nil? + def_callback :block, :always, :always, :nil? + def_callback :numblock, :always, :skip, :nil? + def_callback :defs, :always, :skip, :always, :nil? + + def_callback %i[send csend], body: <<~RUBY node.children.each_with_index do |child, i| next if i == 1 - send(TYPE_TO_METHOD[child.type], child) if child + #{send_code} if child end - nil - end - - alias on_csend on_send - - def on_op_asgn(node) - children = node.children - child = children[0] - send(TYPE_TO_METHOD[child.type], child) - child = children[2] - send(TYPE_TO_METHOD[child.type], child) - end - - def on_defs(node) - children = node.children - child = children[0] - send(TYPE_TO_METHOD[child.type], child) - on_args(children[2]) - return unless (child = children[3]) - - send(TYPE_TO_METHOD[child.type], child) - end - - def on_if(node) - children = node.children - child = children[0] - send(TYPE_TO_METHOD[child.type], child) - if (child = children[1]) - send(TYPE_TO_METHOD[child.type], child) - end - return unless (child = children[2]) - - send(TYPE_TO_METHOD[child.type], child) - end - - def on_while(node) - children = node.children - child = children[0] - send(TYPE_TO_METHOD[child.type], child) - return unless (child = children[1]) - - send(TYPE_TO_METHOD[child.type], child) - end - - alias on_until on_while - alias on_module on_while - alias on_sclass on_while - - def on_block(node) - children = node.children - child = children[0] - send(TYPE_TO_METHOD[child.type], child) # can be send, zsuper... - on_args(children[1]) - return unless (child = children[2]) - - send(TYPE_TO_METHOD[child.type], child) - end - - def on_case(node) - node.children.each do |child| - send(TYPE_TO_METHOD[child.type], child) if child - end - nil - end - - alias on_rescue on_case - alias on_resbody on_case - alias on_ensure on_case - alias on_for on_case - alias on_when on_case - alias on_case_match on_case - alias on_in_pattern on_case - alias on_irange on_case - alias on_erange on_case - - def on_numblock(node) - children = node.children - child = children[0] - send(TYPE_TO_METHOD[child.type], child) - return unless (child = children[2]) - - send(TYPE_TO_METHOD[child.type], child) - end + RUBY + ### generic processing of any other node (forward compatibility) defined = instance_methods(false) .grep(/^on_/) .map { |s| s.to_s[3..-1].to_sym } # :on_foo => :foo @@ -211,18 +169,13 @@ def on_numblock(node) to_define -= %i[numargs ident] # transient to_define -= %i[blockarg_expr restarg_expr] # obsolete to_define -= %i[objc_kwarg objc_restarg objc_varargs] # mac_ruby - to_define.each do |type| - module_eval(<<~RUBY, __FILE__, __LINE__ + 1) - def on_#{type}(node) - node.children.each do |child| - next unless child.class == Node - send(TYPE_TO_METHOD[child.type], child) - end - nil - end - RUBY - end + def_callback to_define, body: <<~RUBY + node.children.each do |child| + next unless child.class == Node + #{send_code} + end + RUBY + MISSING = to_define if ENV['RUBOCOP_DEBUG'] end end end -# rubocop:enable Metrics/ModuleLength diff --git a/spec/rubocop/ast/fixtures/code_examples.rb b/spec/rubocop/ast/fixtures/code_examples.rb new file mode 100644 index 000000000..f26c64258 --- /dev/null +++ b/spec/rubocop/ast/fixtures/code_examples.rb @@ -0,0 +1,2365 @@ +# Extracted from `parser` gem. +# Add the following code at the beginning of `def assert_parses`: +# +# File.open('./out.rb', 'a+') do |f| +# f << code << "\n\n#----\n" if versions.include? '2.7' +# end + +alias $a $b + +#---- +alias $a $+ + +#---- +bar unless foo + +#---- +foo[1, 2] + +#---- +Foo = 10 + +#---- +!foo + +#---- +case foo; in A then true; end + +#---- +case foo; in A::B then true; end + +#---- +case foo; in ::A then true; end + +#---- +() + +#---- +begin end + +#---- +foo[:baz => 1,] + +#---- +Bar::Foo = 10 + +#---- +foo += meth rescue bar + +#---- +def foo(_, _); end + +#---- +def foo(_a, _a); end + +#---- +def a b: +return +end + +#---- +o = { +a: +1 +} + +#---- +a b{c d}, :e do end + +#---- +a b{c(d)}, :e do end + +#---- +a b(c d), :e do end + +#---- +a b(c(d)), :e do end + +#---- +a b{c d}, 1 do end + +#---- +a b{c(d)}, 1 do end + +#---- +a b(c d), 1 do end + +#---- +a b(c(d)), 1 do end + +#---- +a b{c d}, 1.0 do end + +#---- +a b{c(d)}, 1.0 do end + +#---- +a b(c d), 1.0 do end + +#---- +a b(c(d)), 1.0 do end + +#---- +a b{c d}, 1.0r do end + +#---- +a b{c(d)}, 1.0r do end + +#---- +a b(c d), 1.0r do end + +#---- +a b(c(d)), 1.0r do end + +#---- +a b{c d}, 1.0i do end + +#---- +a b{c(d)}, 1.0i do end + +#---- +a b(c d), 1.0i do end + +#---- +a b(c(d)), 1.0i do end + +#---- +td (1_500).toString(); td.num do; end + +#---- +-> do rescue; end + +#---- +bar if foo + +#---- +yield(foo) + +#---- +yield foo + +#---- +yield() + +#---- +yield + +#---- +next(foo) + +#---- +next foo + +#---- +next() + +#---- +next + +#---- +1...2 + +#---- +case foo; when 1, *baz; bar; when *foo; end + +#---- +def foo(...); bar(...); end + +#---- +def foo(...); super(...); end + +#---- +def foo(...); end + +#---- +super(foo) + +#---- +super foo + +#---- +super() + +#---- +desc "foo" do end + +#---- +next fun foo do end + +#---- +def f(foo); end + +#---- +def f(foo, bar); end + +#---- +%i[] + +#---- +%I() + +#---- +[1, 2] + +#---- +{a: if true then 42 end} + +#---- +def f(*foo); end + +#---- +<(a; foo, bar) { } + +#---- +42 + +#---- ++42 + +#---- +-42 + +#---- +module ::Foo; end + +#---- +module Bar::Foo; end + +#---- +foo&.bar {} + +#---- +fun(f bar) + +#---- +begin meth end while foo + +#---- +case foo; in 1; end + +#---- +case foo; in ->{ 42 } then true; end + +#---- +begin; meth; rescue; baz; else foo; ensure; bar end + +#---- +super + +#---- +m "#{[]}" + +#---- +foo[1, 2] = 3 + +#---- +foo + 1 + +#---- +foo - 1 + +#---- +foo * 1 + +#---- +foo / 1 + +#---- +foo % 1 + +#---- +foo ** 1 + +#---- +foo | 1 + +#---- +foo ^ 1 + +#---- +foo & 1 + +#---- +foo <=> 1 + +#---- +foo < 1 + +#---- +foo <= 1 + +#---- +foo > 1 + +#---- +foo >= 1 + +#---- +foo == 1 + +#---- +foo != 1 + +#---- +foo === 1 + +#---- +foo =~ 1 + +#---- +foo !~ 1 + +#---- +foo << 1 + +#---- +foo >> 1 + +#---- +tap (proc do end) + +#---- +def foo +=begin +=end +end + +#---- +:foo + +#---- +:'foo' + +#---- +fun(*bar) + +#---- +fun(*bar, &baz) + +#---- +m { _1 + _9 } + +#---- +m do _1 + _9 end + +#---- +-> { _1 + _9} + +#---- +-> do _1 + _9 end + +#---- +case foo; when 'bar', 'baz'; bar; end + +#---- +case foo; in x, then nil; end + +#---- +case foo; in *x then nil; end + +#---- +case foo; in * then nil; end + +#---- +case foo; in x, y then nil; end + +#---- +case foo; in x, y, then nil; end + +#---- +case foo; in x, *y, z then nil; end + +#---- +case foo; in *x, y, z then nil; end + +#---- +case foo; in 1, "a", [], {} then nil; end + +#---- +for a in foo do p a; end + +#---- +for a in foo; p a; end + +#---- +until foo do meth end + +#---- +until foo; meth end + +#---- +def self.foo; end + +#---- +def self::foo; end + +#---- +def (foo).foo; end + +#---- +def String.foo; end + +#---- +def String::foo; end + +#---- +self::A, foo = foo + +#---- +::A, foo = foo + +#---- +fun(&bar) + +#---- +foo[bar, :baz => 1,] + +#---- +{ 1 => 2 } + +#---- +{ 1 => 2, :foo => "bar" } + +#---- +m def x(); end; 1.tap do end + +#---- +true + +#---- +case foo; when 'bar'; bar; end + +#---- +def f(foo:); end + +#---- +f{ |a| } + +#---- +redo + +#---- +__FILE__ + +#---- +42r + +#---- +42.1r + +#---- +def f a, o=1, *r, &b; end + +#---- +def f a, o=1, *r, p, &b; end + +#---- +def f a, o=1, &b; end + +#---- +def f a, o=1, p, &b; end + +#---- +def f a, *r, &b; end + +#---- +def f a, *r, p, &b; end + +#---- +def f a, &b; end + +#---- +def f o=1, *r, &b; end + +#---- +def f o=1, *r, p, &b; end + +#---- +def f o=1, &b; end + +#---- +def f o=1, p, &b; end + +#---- +def f *r, &b; end + +#---- +def f *r, p, &b; end + +#---- +def f &b; end + +#---- +def f ; end + +#---- +{ } + +#---- +p <<~E " y" + x +E + +#---- +class A; _1; end + +#---- +module A; _1; end + +#---- +class << foo; _1; end + +#---- +def self.m; _1; end + +#---- +_1 + +#---- +case foo; in [x] then nil; end + +#---- +case foo; in [x,] then nil; end + +#---- +case foo; in [x, y] then true; end + +#---- +case foo; in [x, y,] then true; end + +#---- +case foo; in [x, y, *] then true; end + +#---- +case foo; in [x, y, *z] then true; end + +#---- +case foo; in [x, *y, z] then true; end + +#---- +case foo; in [x, *, y] then true; end + +#---- +case foo; in [*x, y] then true; end + +#---- +case foo; in [*, x] then true; end + +#---- +case foo; in 1 | 2 then true; end + +#---- +a += 1 + +#---- +@a |= 1 + +#---- +@@var |= 10 + +#---- +def a; @@var |= 10; end + +#---- +def foo() a:b end + +#---- +def foo + a:b end + +#---- +f { || a:b } + +#---- +fun (f bar) + +#---- +__ENCODING__ + +#---- +__ENCODING__ + +#---- +[ 1 => 2 ] + +#---- +[ 1, 2 => 3 ] + +#---- +'a\ +b' + +#---- +<<-'HERE' +a\ +b +HERE + +#---- +%q{a\ +b} + +#---- +"a\ +b" + +#---- +<<-"HERE" +a\ +b +HERE + +#---- +%{a\ +b} + +#---- +%Q{a\ +b} + +#---- +%w{a\ +b} + +#---- +%W{a\ +b} + +#---- +%i{a\ +b} + +#---- +%I{a\ +b} + +#---- +:'a\ +b' + +#---- +%s{a\ +b} + +#---- +:"a\ +b" + +#---- +/a\ +b/ + +#---- +%r{a\ +b} + +#---- +%x{a\ +b} + +#---- +`a\ +b` + +#---- +<<-`HERE` +a\ +b +HERE + +#---- +->(scope) {}; scope + +#---- +while class Foo; tap do end; end; break; end + +#---- +while class Foo a = tap do end; end; break; end + +#---- +while class << self; tap do end; end; break; end + +#---- +while class << self; a = tap do end; end; break; end + +#---- +meth until foo + +#---- +break fun foo do end + +#---- +foo + +#---- +BEGIN { 1 } + +#---- +unless foo then bar; else baz; end + +#---- +unless foo; bar; else baz; end + +#---- +proc {_1 = nil} + +#---- +begin ensure end + +#---- +case 1; in 2; 3; else; 4; end + +#---- +case foo; in x if true; nil; end + +#---- +case foo; in x unless true; nil; end + +#---- +<<~FOO + baz\ + qux +FOO + +#---- +foo = raise(bar) rescue nil + +#---- +foo += raise(bar) rescue nil + +#---- +foo[0] += raise(bar) rescue nil + +#---- +foo.m += raise(bar) rescue nil + +#---- +foo::m += raise(bar) rescue nil + +#---- +foo.C += raise(bar) rescue nil + +#---- +foo::C ||= raise(bar) rescue nil + +#---- +foo = raise bar rescue nil + +#---- +foo += raise bar rescue nil + +#---- +foo[0] += raise bar rescue nil + +#---- +foo.m += raise bar rescue nil + +#---- +foo::m += raise bar rescue nil + +#---- +foo.C += raise bar rescue nil + +#---- +foo::C ||= raise bar rescue nil + +#---- +p p{p(p);p p}, tap do end + +#---- +begin meth end until foo + +#---- +m1 :k => m2 do; m3() do end; end + +#---- +__LINE__ + +#---- +if (bar); foo; end + +#---- +foo.a = 1 + +#---- +foo::a = 1 + +#---- +foo.A = 1 + +#---- +foo::A = 1 + +#---- +$10 + +#---- +1 in [a]; a + +#---- +true ? 1.tap do |n| p n end : 0 + +#---- +false ? raise {} : tap {} + +#---- +false ? raise do end : tap do end + +#---- +Bar::Foo + +#---- +a&.b = 1 + +#---- +break(foo) + +#---- +break foo + +#---- +break() + +#---- +break + +#---- +if foo..bar; end + +#---- +!(foo..bar) + +#---- +Foo + +#---- +END { 1 } + +#---- +class Foo < Bar; end + +#---- +begin; meth; rescue Exception; bar; end + +#---- +fun { } + +#---- +fun() { } + +#---- +fun(1) { } + +#---- +fun do end + +#---- +case foo; when 'bar' then bar; end + +#---- +begin; meth; rescue foo => ex; bar; end + +#---- +@foo + +#---- +if foo +then bar end + +#---- +fun (1).to_i + +#---- +$var = 10 + +#---- +/\xa8/n =~ "" + +#---- +while not (true) do end + +#---- +foo, bar = 1, 2 + +#---- +(foo, bar) = 1, 2 + +#---- +foo, bar, baz = 1, 2 + +#---- +proc {_1 = nil} + +#---- +_2 = 1 + +#---- +proc {|_3|} + +#---- +def x(_4) end + +#---- +def _5; end + +#---- +def self._6; end + +#---- +meth rescue bar + +#---- +self.a, self[1, 2] = foo + +#---- +self::a, foo = foo + +#---- +self.A, foo = foo + +#---- +foo.a += m foo + +#---- +foo::a += m foo + +#---- +foo.A += m foo + +#---- +foo::A += m foo + +#---- +m { |foo| } + +#---- +m { |(foo, bar)| } + +#---- +module Foo; end + +#---- +f{ } + +#---- +f{ | | } + +#---- +f{ |;a| } + +#---- +f{ |; +a +| } + +#---- +f{ || } + +#---- +f{ |a| } + +#---- +f{ |a, c| } + +#---- +f{ |a,| } + +#---- +f{ |a, &b| } + +#---- +f{ |a, *s, &b| } + +#---- +f{ |a, *, &b| } + +#---- +f{ |a, *s| } + +#---- +f{ |a, *| } + +#---- +f{ |*s, &b| } + +#---- +f{ |*, &b| } + +#---- +f{ |*s| } + +#---- +f{ |*| } + +#---- +f{ |&b| } + +#---- +f{ |a, o=1, o1=2, *r, &b| } + +#---- +f{ |a, o=1, *r, p, &b| } + +#---- +f{ |a, o=1, &b| } + +#---- +f{ |a, o=1, p, &b| } + +#---- +f{ |a, *r, p, &b| } + +#---- +f{ |o=1, *r, &b| } + +#---- +f{ |o=1, *r, p, &b| } + +#---- +f{ |o=1, &b| } + +#---- +f{ |o=1, p, &b| } + +#---- +f{ |*r, p, &b| } + +#---- +assert dogs + +#---- +assert do: true + +#---- +f x: -> do meth do end end + +#---- +foo "#{(1+1).to_i}" do; end + +#---- +alias :foo bar + +#---- +..100 + +#---- +...100 + +#---- +case foo; in ^foo then nil; end + +#---- +for a, b in foo; p a, b; end + +#---- +t=1;(foo)?t:T + +#---- +foo[1, 2] + +#---- +f{ |a| } + +#---- +!m foo + +#---- +fun(:foo => 1) + +#---- +fun(:foo => 1, &baz) + +#---- +%W[foo #{bar}] + +#---- +%W[foo #{bar}foo#@baz] + +#---- +fun (1) + +#---- +%w[] + +#---- +%W() + +#---- +`foobar` + +#---- +case foo; in self then true; end + +#---- +a, (b, c) = foo + +#---- +((b, )) = foo + +#---- +class A < B +end + +#---- +bar def foo; self.each do end end + +#---- +case foo; in "a": then true; end + +#---- +case foo; in "#{ 'a' }": then true; end + +#---- +case foo; in "#{ %q{a} }": then true; end + +#---- +case foo; in "#{ %Q{a} }": then true; end + +#---- +case foo; in "a": 1 then true; end + +#---- +case foo; in "#{ 'a' }": 1 then true; end + +#---- +case foo; in "#{ %q{a} }": 1 then true; end + +#---- +case foo; in "#{ %Q{a} }": 1 then true; end + +#---- +a&.b &&= 1 + +#---- +"#{-> foo {}}" + +#---- +-foo + +#---- ++foo + +#---- +~foo + +#---- +meth while foo + +#---- +$+ + +#---- +[1, *foo, 2] + +#---- +[1, *foo] + +#---- +[*foo] + +#---- +f{ |foo: 1, bar: 2, **baz, &b| } + +#---- +f{ |foo: 1, &b| } + +#---- +f{ |**baz, &b| } + +#---- +if foo...bar; end + +#---- +!(foo...bar) + +#---- +fun(foo, *bar) + +#---- +fun(foo, *bar, &baz) + +#---- +foo or bar + +#---- +foo || bar + +#---- +f{ |foo:| } + +#---- +1.33 + +#---- +-1.33 + +#---- +foo[1, 2] = 3 + +#---- +def f foo = 1; end + +#---- +def f(foo=1, bar=2); end + +#---- +case foo; in A(1, 2) then true; end + +#---- +case foo; in A(x:) then true; end + +#---- +case foo; in A() then true; end + +#---- +case foo; in A[1, 2] then true; end + +#---- +case foo; in A[x:] then true; end + +#---- +case foo; in A[] then true; end + +#---- +meth (-1.3).abs + +#---- +foo (-1.3).abs + +#---- +foo[m bar] + +#---- +m a + b do end + +#---- ++2.0 ** 10 + +#---- +-2 ** 10 + +#---- +-2.0 ** 10 + +#---- +class << foo; nil; end + +#---- +def f (((a))); end + +#---- +def f ((a, a1)); end + +#---- +def f ((a, *r)); end + +#---- +def f ((a, *r, p)); end + +#---- +def f ((a, *)); end + +#---- +def f ((a, *, p)); end + +#---- +def f ((*r)); end + +#---- +def f ((*r, p)); end + +#---- +def f ((*)); end + +#---- +def f ((*, p)); end + +#---- +->{ } + +#---- +foo[bar,] + +#---- +->{ } + +#---- +-> * { } + +#---- +-> do end + +#---- +m ->(a = ->{_1}) {a} + +#---- +m ->(a: ->{_1}) {a} + +#---- +%i[foo bar] + +#---- +f (g rescue nil) + +#---- +[/()\1/, ?#] + +#---- +`foo#{bar}baz` + +#---- +"#{1}" + +#---- +%W"#{1}" + +#---- +def f foo: +; end + +#---- +def f foo: -1 +; end + +#---- +foo, bar = m foo + +#---- +foo.a &&= 1 + +#---- +foo[0, 1] &&= 2 + +#---- +->(a) { } + +#---- +-> (a) { } + +#---- +fun(foo, :foo => 1) + +#---- +fun(foo, :foo => 1, &baz) + +#---- +foo[0, 1] += 2 + +#---- +@@foo + +#---- +@foo, @@bar = *foo + +#---- +a, b = *foo, bar + +#---- +a, *b = bar + +#---- +a, *b, c = bar + +#---- +a, * = bar + +#---- +a, *, c = bar + +#---- +*b = bar + +#---- +*b, c = bar + +#---- +* = bar + +#---- +*, c, d = bar + +#---- +42i + +#---- +42ri + +#---- +42.1i + +#---- +42.1ri + +#---- +case; when foo; 'foo'; end + +#---- +f{ |a, b,| } + +#---- +p begin 1.times do 1 end end + +#---- +retry + +#---- +p <<~E +E + +#---- +p <<~E + E + +#---- +p <<~E + x +E + +#---- +p <<~E + x + y +E + +#---- +p <<~E + x + y +E + +#---- +p <<~E + x + y +E + +#---- +p <<~E + x + y +E + +#---- +p <<~E + x + y +E + +#---- +p <<~E + x + +y +E + +#---- +p <<~E + x + + y +E + +#---- +p <<~E + x + \ y +E + +#---- +p <<~E + x + \ y +E + +#---- +p <<~"E" + x + #{foo} +E + +#---- +p <<~`E` + x + #{foo} +E + +#---- +p <<~"E" + x + #{" y"} +E + +#---- +case foo; in 1..2 then true; end + +#---- +case foo; in 1.. then true; end + +#---- +case foo; in ..2 then true; end + +#---- +case foo; in 1...2 then true; end + +#---- +case foo; in 1... then true; end + +#---- +case foo; in ...2 then true; end + +#---- +begin; meth; rescue; foo; else; bar; end + +#---- +m [] do end + +#---- +m [], 1 do end + +#---- +%w[foo bar] + +#---- +return fun foo do end + +#---- +fun (1 +) + +#---- +/foo#{bar}baz/ + +#---- +if (a, b = foo); end + +#---- +foo.(1) + +#---- +foo::(1) + +#---- +1.. + +#---- +1... + +#---- +def foo(...); bar(...); end + +#---- +/#{1}(?bar)/ =~ 'bar' + +#---- +foo = meth rescue bar + +#---- +begin; meth; ensure; bar; end + +#---- +var = 10; var + +#---- +begin; meth; rescue; foo; end + +#---- +begin; meth; rescue => ex; bar; end + +#---- +begin; meth; rescue => @ex; bar; end + +#---- +case foo; in {} then true; end + +#---- +case foo; in a: 1 then true; end + +#---- +case foo; in { a: 1 } then true; end + +#---- +case foo; in { a: 1, } then true; end + +#---- +case foo; in a: then true; end + +#---- +case foo; in **a then true; end + +#---- +case foo; in ** then true; end + +#---- +case foo; in a: 1, b: 2 then true; end + +#---- +case foo; in a:, b: then true; end + +#---- +case foo; in a: 1, _a:, ** then true; end + +#---- +case foo; + in {a: 1 + } + false + ; end + +#---- +case foo; + in {a: + 2} + false + ; end + +#---- +case foo; + in {Foo: 42 + } + false + ; end + +#---- +case foo; + in a: {b:}, c: + p c + ; end + +#---- +case foo; + in {a: + } + true + ; end + +#---- +lambda{|;a|a} + +#---- +-> (arg={}) {} + +#---- + case [__FILE__, __LINE__ + 1, __ENCODING__] + in [__FILE__, __LINE__, __ENCODING__] + end + + +#---- +nil + +#---- +def f (foo: 1, bar: 2, **baz, &b); end + +#---- +def f (foo: 1, &b); end + +#---- +def f **baz, &b; end + +#---- +def f *, **; end + +#---- +false + +#---- +a ||= 1 + +#---- +if foo then bar; else baz; end + +#---- +if foo; bar; else baz; end + +#---- +a b{c d}, "x" do end + +#---- +a b(c d), "x" do end + +#---- +a b{c(d)}, "x" do end + +#---- +a b(c(d)), "x" do end + +#---- +a b{c d}, /x/ do end + +#---- +a b(c d), /x/ do end + +#---- +a b{c(d)}, /x/ do end + +#---- +a b(c(d)), /x/ do end + +#---- +a b{c d}, /x/m do end + +#---- +a b(c d), /x/m do end + +#---- +a b{c(d)}, /x/m do end + +#---- +a b(c(d)), /x/m do end + +#---- +{ foo: 2, **bar } + +#---- +begin; meth; rescue; baz; ensure; bar; end + +#---- +a&.b + +#---- +super foo, bar do end + +#---- +super do end + +#---- +let () { m(a) do; end } + +#---- +"foo#@a" "bar" + +#---- +/#)/x + +#---- +'foobar' + +#---- +%q(foobar) + +#---- +not m foo + +#---- +class Foo < a:b; end + +#---- +?a + +#---- +foo ? 1 : 2 + +#---- +def f(*); end + +#---- +case foo; in **nil then true; end + +#---- +foo.fun + +#---- +foo::fun + +#---- +foo::Fun() + +#---- +if foo then bar; end + +#---- +if foo; bar; end + +#---- +return(foo) + +#---- +return foo + +#---- +return() + +#---- +return + +#---- +undef foo, :bar, :"foo#{1}" + +#---- +f <<-TABLE do +TABLE +end + +#---- +case foo; when 'bar'; bar; else baz; end + +#---- +begin; rescue LoadError; else; end + +#---- +foo += m foo + +#---- +unless foo then bar; end + +#---- +unless foo; bar; end + +#---- +{ foo: 2 } + +#---- +fun (1) {} + +#---- +foo.fun (1) {} + +#---- +foo::fun (1) {} + +#---- +if (bar; a, b = foo); end + +#---- +meth do; foo; rescue; bar; end + +#---- +foo, bar = meth rescue [1, 2] + +#---- + '#@1' + +#---- + '#@@1' + +#---- +<<-'HERE' +#@1 +HERE + +#---- +<<-'HERE' +#@@1 +HERE + +#---- + %q{#@1} + +#---- + %q{#@@1} + +#---- + "#@1" + +#---- + "#@@1" + +#---- +<<-"HERE" +#@1 +HERE + +#---- +<<-"HERE" +#@@1 +HERE + +#---- + %{#@1} + +#---- + %{#@@1} + +#---- + %Q{#@1} + +#---- + %Q{#@@1} + +#---- + %w[ #@1 ] + +#---- + %w[ #@@1 ] + +#---- + %W[#@1] + +#---- + %W[#@@1] + +#---- + %i[ #@1 ] + +#---- + %i[ #@@1 ] + +#---- + %I[#@1] + +#---- + %I[#@@1] + +#---- + :'#@1' + +#---- + :'#@@1' + +#---- + %s{#@1} + +#---- + %s{#@@1} + +#---- + :"#@1" + +#---- + :"#@@1" + +#---- + /#@1/ + +#---- + /#@@1/ + +#---- + %r{#@1} + +#---- + %r{#@@1} + +#---- + %x{#@1} + +#---- + %x{#@@1} + +#---- + `#@1` + +#---- + `#@@1` + +#---- +<<-`HERE` +#@1 +HERE + +#---- +<<-`HERE` +#@@1 +HERE + +#---- +meth[] {} + +#---- +"#@a #@@a #$a" + +#---- +/source/im + +#---- +foo && (a, b = bar) + +#---- +foo || (a, b = bar) + +#---- +if foo; bar; elsif baz; 1; else 2; end + +#---- +def f(&block); end + +#---- +m "#{}#{()}" + +#---- + + +#---- +a &&= 1 + +#---- +::Foo + +#---- +class Foo; end + +#---- +class Foo end + +#---- +begin; meth; rescue Exception, foo; bar; end + +#---- +1..2 + +#---- +case foo; in x then x; end + +#---- +case 1; in 2; 3; else; end + +#---- +-> a: 1 { } + +#---- +-> a: { } + +#---- +def foo; end + +#---- +def String; end + +#---- +def String=; end + +#---- +def until; end + +#---- +def BEGIN; end + +#---- +def END; end + +#---- +foo.fun bar + +#---- +foo::fun bar + +#---- +foo::Fun bar + +#---- +@@var = 10 + +#---- +a = b = raise :x + +#---- +a += b = raise :x + +#---- +a = b += raise :x + +#---- +a += b += raise :x + +#---- +p ->() do a() do end end + +#---- +foo.a ||= 1 + +#---- +foo[0, 1] ||= 2 + +#---- +p -> { :hello }, a: 1 do end + +#---- +foo[0, 1] += m foo + +#---- +{ 'foo': 2 } + +#---- +{ 'foo': 2, 'bar': {}} + +#---- +f(a ? "a":1) + +#---- +while foo do meth end + +#---- +while foo; meth end + +#---- +foo = bar, 1 + +#---- +foo = *bar + +#---- +foo = baz, *bar + +#---- +m = -> *args do end + +#---- +defined? foo + +#---- +defined?(foo) + +#---- +defined? @foo + +#---- +A += 1 + +#---- +::A += 1 + +#---- +B::A += 1 + +#---- +def x; self::A ||= 1; end + +#---- +def x; ::A ||= 1; end + +#---- +foo.a += 1 + +#---- +foo::a += 1 + +#---- +foo.A += 1 + +#---- +p <<~"E" + x\n y +E + +#---- +case foo; in (1) then true; end + +#---- +$foo + +#---- +case; when foo; 'foo'; else 'bar'; end + +#---- +a @b do |c|;end + +#---- +p :foo, {a: proc do end, b: proc do end} + +#---- +p :foo, {:a => proc do end, b: proc do end} + +#---- +p :foo, {"a": proc do end, b: proc do end} + +#---- +p :foo, {proc do end => proc do end, b: proc do end} + +#---- +p :foo, {** proc do end, b: proc do end} + +#---- +<<~E + 1 \ + 2 + 3 +E + + +#---- +<<-E + 1 \ + 2 + 3 +E + + +#---- +def f(**nil); end + +#---- +m { |**nil| } + +#---- +->(**nil) {} + +#---- +a # +# +.foo + + +#---- +a # + # +.foo + + +#---- +a # +# +&.foo + + +#---- +a # + # +&.foo + + +#---- +::Foo = 10 + +#---- +not foo + +#---- +not(foo) + +#---- +not() + +#---- +:"foo#{bar}baz" + +#---- +@var = 10 + +#---- +"foo#{bar}baz" + +#---- +case foo; in 1 => a then true; end + +#---- +def f(foo: 1); end + +#---- +a ? b & '': nil + +#---- +meth 1 do end.fun bar + +#---- +meth 1 do end.fun(bar) + +#---- +meth 1 do end::fun bar + +#---- +meth 1 do end::fun(bar) + +#---- +meth 1 do end.fun bar do end + +#---- +meth 1 do end.fun(bar) {} + +#---- +meth 1 do end.fun {} + +#---- +def f(**); end + +#---- +foo and bar + +#---- +foo && bar + +#---- +!(a, b = foo) + +#---- +def m; class << self; class C; end; end; end + +#---- +def m; class << self; module M; end; end; end + +#---- +def m; class << self; A = nil; end; end + +#---- +begin foo!; bar! end + +#---- +foo = m foo + +#---- +foo = bar = m foo + +#---- +def f(**foo); end + +#---- +%I[foo #{bar}] + +#---- +%I[foo#{bar}] + +#---- +self + +#---- +a = 1; a b: 1 + +#---- +def foo raise; raise A::B, ''; end + +#---- +/(?bar)/ =~ 'bar'; match + +#---- +let (:a) { m do; end } + +#---- +fun + +#---- +fun! + +#---- +fun(1) + +#---- +fun () {} + +#---- +if /wat/; end + +#---- +!/wat/ + +#---- +# coding:utf-8 + "\xD0\xBF\xD1\x80\xD0\xBE\xD0\xB2\xD0\xB5\xD1\x80\xD0\xBA\xD0\xB0" + +#---- +while def foo; tap do end; end; break; end + +#---- +while def self.foo; tap do end; end; break; end + +#---- +while def foo a = tap do end; end; break; end + +#---- +while def self.foo a = tap do end; end; break; end diff --git a/spec/rubocop/ast/traversal_spec.rb b/spec/rubocop/ast/traversal_spec.rb new file mode 100644 index 000000000..266f36c96 --- /dev/null +++ b/spec/rubocop/ast/traversal_spec.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::AST::Traversal do + subject(:traverse) do + instance.walk(node) + instance + end + + let(:ast) { parse_source(source).ast } + let(:instance) { klass.new } + let(:node) { ast } + + context 'when a class defines on_arg', :ruby30 do + let(:klass) do + Class.new do + attr_reader :calls + + include RuboCop::AST::Traversal + def on_arg(node) + (@calls ||= []) << node.children.first + super + end + end + end + + let(:source) { <<~RUBY } + class Foo + def example + 42.times { |x| p x } + end + end + RUBY + + it 'calls it for all arguments', :ruby30 do + expect(traverse.calls).to eq %i[x] + end + end + + File.read("#{__dir__}/fixtures/code_examples.rb") + .split("#----\n") + .each_with_index do |example, _i| + context "for example #{example}", :ruby27 do + let(:klass) do + Struct.new(:hits) do + include RuboCop::AST::Traversal + def initialize + super(0) + end + + instance_methods.grep(/^on_/).each do |m| + define_method(m) do |node| + self.hits += 1 + super(node) + end + end + end + end + + let(:source) { "foo=bar=baz=nil; #{example}" } + + it 'traverses all nodes' do + actual = node.each_node.count + expect(traverse.hits).to eql(actual) + end + end + end + + it 'knows all current node types' do + expect(RuboCop::AST::Traversal::MISSING).to eq [] + end + + # Sanity checking the debugging checks + context 'when given an unexpected AST' do + include RuboCop::AST::Sexp + let(:klass) { Class.new { include RuboCop::AST::Traversal } } + + context 'with too few children' do + let(:node) { s(:int) } + + it 'raises debugging error' do + expect { traverse }.to raise_error(RuboCop::AST::Traversal::DebugError) + end + end + + context 'with too many children' do + let(:node) { s(:int, 1, 2) } + + it 'raises debugging error' do + expect { traverse }.to raise_error(RuboCop::AST::Traversal::DebugError) + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 56cac2e75..a4363e4ad 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -8,6 +8,8 @@ SimpleCov.start end +ENV['RUBOCOP_DEBUG'] = 't' + require 'rubocop-ast' if ENV['MODERNIZE'] RuboCop::AST::Builder.modernize