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

Add inline disable configuration #1320

Merged
merged 4 commits into from
Apr 25, 2022
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
# v0.11.7 2022-04-25
# v0.11.8 2022-04-25

* [#1320](https://github.com/mbj/mutant/pull/1320)

Add inline mutant disable configuration. This allows individual subjects to be marked as
disbled directly in the code.

Use:

` ```
class Something
# mutant:disable
def some_method
end
end

# v0.11.7 2022-04-24

* [#1319](https://github.com/mbj/mutant/pull/1319)

Expand Down
8 changes: 4 additions & 4 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
PATH
remote: .
specs:
mutant (0.11.7)
mutant (0.11.8)
diff-lcs (~> 1.3)
parser (~> 3.1.0)
regexp_parser (~> 2.3)
regexp_parser (~> 2.3, >= 2.3.1)
sorbet-runtime (~> 0.5.0)
unparser (~> 0.6.5)

Expand All @@ -19,10 +19,10 @@ GEM
ast (2.4.2)
diff-lcs (1.5.0)
parallel (1.22.1)
parser (3.1.1.0)
parser (3.1.2.0)
ast (~> 2.4.1)
rainbow (3.1.1)
regexp_parser (2.3.0)
regexp_parser (2.3.1)
rexml (3.2.5)
rspec (3.11.0)
rspec-core (~> 3.11.0)
Expand Down
28 changes: 28 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
# Configuration

There are 3 ways of configuring mutant:

1. In the source code via processing comments.
2. Via the CLI
3. Via a config file.

### Processing Comments

Mutant currently only supports the `mutant:disable` directive that can be added in a
source code comment to ignore a specific subject.

Example:

```ruby
class SomeClass
# mutant:disable
def some_method
end
end
```

More inline configuration will be made available over time.

### Configuration File

Mutant can be configured with a config file that can be named one of the following: `.mutant.yml`, `config/mutant.yml`, or `mutant.yml`

The following options can be configured through the config file:
Expand Down Expand Up @@ -92,6 +117,9 @@ matcher:
#
# Note that subject ignores from the command line are added to the subject ignores
# configured on the command line!
#
# Also matcher ignores generally shold be used for entire namespaces, and individual
# methods be disabled directly in source code via `mutant:disable` directives.
ignore:
- Your::App::Namespace::Dirty # ignore all subjects on a specific constant
- Your::App::Namespace::Dirty* # ignore all subjects on a specific constant, recursively
Expand Down
1 change: 1 addition & 0 deletions lib/mutant.rb
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ module Mutant
require 'mutant/context'
require 'mutant/scope'
require 'mutant/subject'
require 'mutant/subject/config'
require 'mutant/subject/method'
require 'mutant/subject/method/instance'
require 'mutant/subject/method/singleton'
Expand Down
14 changes: 3 additions & 11 deletions lib/mutant/ast/regexp/transformer/direct.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,15 @@ def call
class ASTToExpression < Transformer::ASTToExpression
include LookupTable

# rubocop:disable Metrics/BlockLength
properties = ::Regexp::Syntax
.version_class("ruby/#{RUBY_VERSION}")
.features.fetch(:property)
.flat_map do |property|
property_specifier = "\\p{#{property}}"
non_property_specifier = "\\P{#{property}}"

begin
property_regex = /#{property_specifier}/
non_property_regex = /#{non_property_specifier}/
# This is probably a regexp_parser bug, ignoring registration of that property
# See: https://github.com/ammar/regexp_parser/issues/84
rescue RegexpError
next
end
property_regex = /#{property_specifier}/
non_property_regex = /#{non_property_specifier}/

[
[
Expand All @@ -73,8 +66,7 @@ class ASTToExpression < Transformer::ASTToExpression
::Regexp::Parser.parse(non_property_regex).expressions.first.class
]
]
end.compact
# rubocop:enable Metrics/BlockLength
end

# rubocop:disable Layout/LineLength
TABLE = Table.create(
Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/matcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def self.from_config(config)
end

def self.allowed_subject?(config, subject)
select_subject?(config, subject) && !ignore_subject?(config, subject)
select_subject?(config, subject) && !ignore_subject?(config, subject) && !subject.inline_disabled?
end
private_class_method :allowed_subject?

Expand Down
3 changes: 2 additions & 1 deletion lib/mutant/matcher/method.rb
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,15 @@ def subject
node = matched_node_path.last || return

self.class::SUBJECT_CLASS.new(
config: Subject::Config.parse(ast.comment_associations.fetch(node, [])),
context: context,
node: node,
visibility: visibility
)
end

def matched_node_path
AST.find_last_path(ast, &method(:match?))
AST.find_last_path(ast.node, &method(:match?))
end
memoize :matched_node_path

Expand Down
2 changes: 1 addition & 1 deletion lib/mutant/matcher/method/metaclass.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def metaclass_receiver?(node)
end

def metaclass_containing(node)
AST::FindMetaclassContaining.call(ast, node)
AST::FindMetaclassContaining.call(ast.node, node)
end

def line?(node)
Expand Down
20 changes: 19 additions & 1 deletion lib/mutant/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ module Mutant
class Parser
include Adamantium, Equalizer.new

class AST
include Anima.new(
:node,
:comment_associations
)
end

# Initialize object
#
# @return [undefined]
Expand All @@ -18,7 +25,18 @@ def initialize
#
# @return [AST::Node]
def call(path)
@cache[path] ||= Unparser.parse(path.read)
@cache[path] ||= parse(path.read)
end

private

def parse(source)
node, comments = Unparser.parse_with_comments(source)

AST.new(
node: node,
comment_associations: ::Parser::Source::Comment.associate_by_identity(node, comments)
)
end

end # Parser
Expand Down
6 changes: 5 additions & 1 deletion lib/mutant/subject.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Mutant
# Subject of a mutation
class Subject
include AbstractType, Adamantium, Enumerable
include Anima.new(:context, :node)
include Anima.new(:config, :context, :node)

# Mutations for this subject
#
Expand All @@ -19,6 +19,10 @@ def mutations
end
memoize :mutations

def inline_disabled?
config.inline_disable
end

# Source path
#
# @return [Pathname]
Expand Down
25 changes: 25 additions & 0 deletions lib/mutant/subject/config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

module Mutant
class Subject
class Config
include Adamantium, Anima.new(:inline_disable)

DEFAULT = new(inline_disable: false)

DISABLE_REGEXP = /(\s|^)mutant:disable(?:\s|$)/.freeze
SYNTAX_REGEXP = /\A(?:#|=begin\n)/.freeze

def self.parse(comments)
new(
inline_disable: comments.any? { |comment| DISABLE_REGEXP.match?(comment_body(comment)) }
)
end

def self.comment_body(comment)
comment.text.sub(SYNTAX_REGEXP, '')
end
private_class_method :comment_body
end # Config
end # Subject
end # Mutant
2 changes: 1 addition & 1 deletion lib/mutant/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

module Mutant
# Current mutant version
VERSION = '0.11.7'
VERSION = '0.11.8'
end # Mutant
2 changes: 1 addition & 1 deletion mutant.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Gem::Specification.new do |gem|

gem.add_runtime_dependency('diff-lcs', '~> 1.3')
gem.add_runtime_dependency('parser', '~> 3.1.0')
gem.add_runtime_dependency('regexp_parser', '~> 2.3')
gem.add_runtime_dependency('regexp_parser', '~> 2.3', '>= 2.3.1')
gem.add_runtime_dependency('sorbet-runtime', '~> 0.5.0')
gem.add_runtime_dependency('unparser', '~> 0.6.5')

Expand Down
6 changes: 2 additions & 4 deletions spec/unit/mutant/cli_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -496,10 +496,8 @@ def self.main_body

let(:subject_a) do
Mutant::Subject::Method::Instance.new(
context: Mutant::Context.new(
Object,
'subject.rb'
),
config: Mutant::Subject::Config::DEFAULT,
context: Mutant::Context.new(Object, 'subject.rb'),
node: s(:def, :send, s(:args), nil),
visibility: :public
)
Expand Down
1 change: 1 addition & 0 deletions spec/unit/mutant/matcher/descendants_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def apply
let(:expected_subjects) do
[
Mutant::Subject::Method::Instance.new(
config: Mutant::Subject::Config::DEFAULT,
context: Mutant::Context.new(
TestApp::Foo::Bar::Baz,
TestApp::ROOT.join('lib/test_app.rb')
Expand Down
13 changes: 13 additions & 0 deletions spec/unit/mutant/matcher/method/instance_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ def arguments
let(:expected_subjects) do
[
Mutant::Subject::Method::Instance.new(
config: Mutant::Subject::Config::DEFAULT,
context: context,
node: s(:def, :bar, s(:args), nil),
visibility: expected_visibility
Expand Down Expand Up @@ -213,4 +214,16 @@ def arguments

it_should_behave_like 'a method matcher'
end

context 'on inline disabled method' do
let(:scope) { TestApp::InlineDisabled }
let(:method_line) { 148 }
let(:method_arity) { 0 }

it_should_behave_like 'a method matcher' do
it 'returns disabled inline config' do
expect(mutation_subject.config.inline_disable).to be(true)
end
end
end
end
12 changes: 12 additions & 0 deletions spec/unit/mutant/matcher/method/singleton_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,16 @@ def arguments

it_should_behave_like 'a method matcher'
end

context 'on inline disabled method' do
let(:scope) { TestApp::InlineDisabled }
let(:method_line) { 152 }
let(:method_arity) { 0 }

it_should_behave_like 'a method matcher' do
it 'returns disabled inline config' do
expect(mutation_subject.config.inline_disable).to be(true)
end
end
end
end
26 changes: 23 additions & 3 deletions spec/unit/mutant/matcher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,23 +45,43 @@ def apply
end

let(:subject_a) do
instance_double(Mutant::Subject, 'subject a', expression: expression('Foo::Bar#a'))
instance_double(
Mutant::Subject,
'subject a',
expression: expression('Foo::Bar#a'),
inline_disabled?: false
)
end

let(:subject_b) do
instance_double(Mutant::Subject, 'subject b', expression: expression('Foo::Bar#b'))
instance_double(
Mutant::Subject,
'subject b',
expression: expression('Foo::Bar#b'),
inline_disabled?: false
)
end

def expression(input, matcher = anon_matcher)
expression_class.new(parse_expression(input), matcher)
end

context 'empty ignores and empty filter' do
context 'no restrictions of any kinds' do
it 'returns expected subjects' do
expect(apply.call(env)).to eql([subject_a, subject_b])
end
end

context 'with explicit disable' do
before do
allow(subject_b).to receive_messages(inline_disabled?: true)
end

it 'returns expected subjects' do
expect(apply.call(env)).to eql([subject_a])
end
end

context 'with ignore matching a subject' do
let(:ignore_expressions) { [subject_b.expression] }

Expand Down
Loading