From ad23e3d861615f8566fda2667b4ccefcbe5973ce Mon Sep 17 00:00:00 2001 From: Tejas Bubane Date: Wed, 10 Jun 2020 23:49:48 +0530 Subject: [PATCH] Add struct_constructor?, class_definition? and module_definition? matchers Closes #28 --- CHANGELOG.md | 2 + lib/rubocop/ast/node.rb | 20 ++- spec/rubocop/ast/node_spec.rb | 264 ++++++++++++++++++++++++++++++++++ 3 files changed, 284 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5de8c081..d78d617c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### New features * [#50](https://github.com/rubocop-hq/rubocop-ast/pull/50): Support find pattern matching for Ruby 2.8 (3.0) parser. ([@koic][]) +* [#28](https://github.com/rubocop-hq/rubocop-ast/issues/28): Add `struct_constructor?`, `class_definition?` and `module_definition?` matchers. ([@tejasbubane][]) ## 0.1.0 (2020-06-26) @@ -21,6 +22,7 @@ * [#46](https://github.com/rubocop-hq/rubocop-ast/pull/46): Basic support for [non-legacy AST output from parser](https://github.com/whitequark/parser/#usage). Note that there is no support (yet) in main RuboCop gem. Expect `emit_forward_arg` to be set to `true` in v1.0 ([@marcandre][]) * [#48](https://github.com/rubocop-hq/rubocop-ast/pull/48): Support `Parser::Ruby28` for Ruby 2.8 (3.0) parser (experimental). ([@koic][]) * [#35](https://github.com/rubocop-hq/rubocop-ast/pull/35): NodePattern now accepts `%named_param` and `%CONST`. The macros `def_node_pattern` and `def_node_search` accept default named parameters. ([@marcandre][]) +* [#31](https://github.com/rubocop-hq/rubocop-ast/pull/31): Use `param === node` to match params, which allows Regexp, Proc, Set, etc. ([@marcandre][]) ## 0.0.3 (2020-05-15) diff --git a/lib/rubocop/ast/node.rb b/lib/rubocop/ast/node.rb index 0ca94ed1e..74a0241da 100644 --- a/lib/rubocop/ast/node.rb +++ b/lib/rubocop/ast/node.rb @@ -504,8 +504,24 @@ def guard_clause? def_node_matcher :lambda_or_proc?, '{lambda? proc?}' def_node_matcher :class_constructor?, <<~PATTERN - { (send (const nil? {:Class :Module}) :new ...) - (block (send (const nil? {:Class :Module}) :new ...) ...)} + { (send (const _ {:Class :Module}) :new ...) + (block (send (const _ {:Class :Module}) :new ...) ...)} + PATTERN + + def_node_matcher :struct_constructor?, <<~PATTERN + (block (send (const _ :Struct) :new ...) ...) + PATTERN + + def_node_matcher :class_definition?, <<~PATTERN + {(class ...) + (sclass ...) + (casgn _ _ (block (send (const _ {:Struct :Class}) :new ...) ...))} + PATTERN + + def_node_matcher :module_definition?, <<~PATTERN + {(module ...) + (casgn _ _ (block (send (const _ :Module) :new ...) ...)) + (send nil? :include (block (send (const _ :Module) :new ...) ...))} PATTERN # Some expressions are evaluated for their value, some for their side diff --git a/spec/rubocop/ast/node_spec.rb b/spec/rubocop/ast/node_spec.rb index 4eb67aac6..673354326 100644 --- a/spec/rubocop/ast/node_spec.rb +++ b/spec/rubocop/ast/node_spec.rb @@ -373,4 +373,268 @@ def used? end end end + + describe '#class_constructor?' do + context 'class definition with a block' do + let(:src) { 'Class.new { a = 42 }' } + + it 'matches' do + expect(node.class_constructor?).to eq(true) + end + end + + context 'module definition with a block' do + let(:src) { 'Module.new { a = 42 }' } + + it 'matches' do + expect(node.class_constructor?).to eq(true) + end + end + + context 'class definition' do + let(:src) { 'class Foo; a = 42; end' } + + it 'does not match' do + expect(node.class_constructor?).to eq(nil) + end + end + + context 'class definition on outer scope' do + let(:src) { '::Class.new { a = 42 }' } + + it 'matches' do + expect(node.class_constructor?).to eq(true) + end + end + end + + describe '#struct_constructor?' do + context 'struct definition with a block' do + let(:src) { 'Struct.new { a = 42 }' } + + it 'matches' do + expect(node.struct_constructor?).to eq(true) + end + end + + context 'struct definition without block' do + let(:src) { 'Struct.new(:foo, :bar)' } + + it 'does not matches' do + expect(node.struct_constructor?).to eq(nil) + end + end + + context '::Struct' do + let(:src) { '::Struct.new { a = 42 }' } + + it 'matches' do + expect(node.struct_constructor?).to eq(true) + end + end + end + + describe '#class_definition?' do + context 'without inheritance' do + let(:src) { 'class Foo; a = 42; end' } + + it 'matches' do + expect(node.class_definition?).to eq(true) + end + end + + context 'with inheritance' do + let(:src) { 'class Foo < Bar; a = 42; end' } + + it 'matches' do + expect(node.class_definition?).to eq(true) + end + end + + context 'with ::ClassName' do + let(:src) { 'class ::Foo < Bar; a = 42; end' } + + it 'matches' do + expect(node.class_definition?).to eq(true) + end + end + + context 'with Struct' do + let(:src) do + <<~RUBY + Person = Struct.new(:name, :age) do + a = 2 + def details; end + end + RUBY + end + + it 'matches' do + expect(node.class_definition?).to eq(true) + end + end + + context 'constant defined as Struct without block' do + let(:src) { 'Person = Struct.new(:name, :age)' } + + it 'does not match' do + expect(node.class_definition?).to eq(nil) + end + end + + context 'with Class.new' do + let(:src) do + <<~RUBY + Person = Class.new do + a = 2 + def details; end + end + RUBY + end + + it 'matches' do + expect(node.class_definition?).to eq(true) + end + end + + context 'namespaced class' do + let(:src) do + <<~RUBY + class Foo::Bar::Baz + BAZ = 2 + def variables; end + end + RUBY + end + + it 'matches' do + expect(node.class_definition?).to eq(true) + end + end + + context 'with self singleton class' do + let(:src) do + <<~RUBY + class << self + BAZ = 2 + def variables; end + end + RUBY + end + + it 'matches' do + expect(node.class_definition?).to eq(true) + end + end + + context 'with object singleton class' do + let(:src) do + <<~RUBY + class << foo + BAZ = 2 + def variables; end + end + RUBY + end + + it 'matches' do + expect(node.class_definition?).to eq(true) + end + end + end + + describe '#module_definition?' do + context 'using module keyword' do + let(:src) { 'module Foo; A = 42; end' } + + it 'matches' do + expect(node.module_definition?).to eq(true) + end + end + + context 'with ::ModuleName' do + let(:src) { 'module ::Foo; A = 42; end' } + + it 'matches' do + expect(node.module_definition?).to eq(true) + end + end + + context 'with Module.new' do + let(:src) do + <<~RUBY + Person = Module.new do + a = 2 + def details; end + end + RUBY + end + + it 'matches' do + expect(node.module_definition?).to eq(true) + end + end + + context 'namespaced with Module.new' do + let(:src) do + <<~RUBY + Foo::Person = Module.new do + a = 2 + def details; end + end + RUBY + end + + it 'matches' do + expect(node.module_definition?).to eq(true) + end + end + + context 'nested modules' do + let(:src) do + <<~RUBY + module Foo + module Bar + BAZ = 2 + def variables; end + end + end + RUBY + end + + it 'matches' do + expect(node.module_definition?).to eq(true) + end + end + + context 'namespaced modules' do + let(:src) do + <<~RUBY + module Foo::Bar::Baz + BAZ = 2 + def variables; end + end + RUBY + end + + it 'matches' do + expect(node.module_definition?).to eq(true) + end + end + + context 'included module definition' do + let(:src) do + <<~RUBY + include(Module.new do + BAZ = 2 + def variables; end + end) + RUBY + end + + it 'matches' do + expect(node.module_definition?).to eq(true) + end + end + end end