diff --git a/CHANGELOG.md b/CHANGELOG.md index 79f329ed3..445b09921 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * [#55](https://github.com/rubocop-hq/rubocop-ast/pull/55): Add `ProcessedSource#line_with_comment?`. ([@marcandre][]) * [#63](https://github.com/rubocop-hq/rubocop-ast/pull/63): NodePattern now supports patterns as arguments to predicate and functions. ([@marcandre][]) * [#64](https://github.com/rubocop-hq/rubocop-ast/pull/64): Add `Node#global_const?`. ([@marcandre][]) +* [#28](https://github.com/rubocop-hq/rubocop-ast/issues/28): Add `struct_constructor?`, `class_definition?` and `module_definition?` matchers. ([@tejasbubane][]) ### Bug fixes diff --git a/lib/rubocop/ast/node.rb b/lib/rubocop/ast/node.rb index 5d05d28c6..ffe3305e7 100644 --- a/lib/rubocop/ast/node.rb +++ b/lib/rubocop/ast/node.rb @@ -510,6 +510,21 @@ def guard_clause? (block (send #global_const?({:Class :Module}) :new ...) ...)} PATTERN + def_node_matcher :struct_constructor?, <<~PATTERN + (block (send #global_const?(:Struct) :new ...) _ $_) + PATTERN + + def_node_matcher :class_definition?, <<~PATTERN + {(class _ _ $_) + (sclass _ $_) + (block (send #global_const?({:Struct :Class}) :new ...) _ $_)} + PATTERN + + def_node_matcher :module_definition?, <<~PATTERN + {(module _ $_) + (block (send #global_const?(:Module) :new ...) _ $_)} + PATTERN + # Some expressions are evaluated for their value, some for their side # effects, and some for both # If we know that an expression is useful only for its side effects, that diff --git a/spec/rubocop/ast/node_spec.rb b/spec/rubocop/ast/node_spec.rb index 4eb67aac6..973c5bda9 100644 --- a/spec/rubocop/ast/node_spec.rb +++ b/spec/rubocop/ast/node_spec.rb @@ -373,4 +373,273 @@ 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(node.body) + end + end + + context 'struct definition without block' do + let(:src) { 'Struct.new(:foo, :bar)' } + + it 'does not match' 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(node.body) + 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(node.body) + end + end + + context 'with inheritance' do + let(:src) { 'class Foo < Bar; a = 42; end' } + + it 'matches' do + expect(node.class_definition?).to eq(node.body) + end + end + + context 'with ::ClassName' do + let(:src) { 'class ::Foo < Bar; a = 42; end' } + + it 'matches' do + expect(node.class_definition?).to eq(node.body) + 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 + class_node = node.children.last + expect(class_node.class_definition?).to eq(class_node.body) + 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 + class_node = node.children.last + expect(class_node.class_definition?).to eq(class_node.body) + 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(node.body) + 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(node.body) + 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(node.body) + 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(node.body) + end + end + + context 'with ::ModuleName' do + let(:src) { 'module ::Foo; A = 42; end' } + + it 'matches' do + expect(node.module_definition?).to eq(node.body) + 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 + module_node = node.children.last + expect(module_node.module_definition?).to eq(module_node.body) + end + end + + context 'prepend Module.new' do + let(:src) do + <<~RUBY + prepend(Module.new do + a = 2 + def details; end + end) + RUBY + end + + it 'matches' do + module_node = node.children.last + expect(module_node.module_definition?).to eq(module_node.body) + 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(node.body) + 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(node.body) + 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 + module_node = node.children.last + expect(module_node.module_definition?).to eq(module_node.body) + end + end + end end