-
Notifications
You must be signed in to change notification settings - Fork 4
Add support for disabling mutations #6
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,3 @@ | ||
| --- | ||
| threshold: 16 | ||
| total_score: 1315 | ||
| total_score: 1309 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| module Mutest | ||
| class Ignores | ||
| include Equalizer.new(:disable_lines), Adamantium::Flat | ||
|
|
||
| COMMENT_TEXT = '# mutest:disable'.freeze | ||
|
|
||
| def initialize(comments) | ||
| @comments = comments | ||
| end | ||
|
|
||
| # TODO: Support multiple lines of comments preceeding a disable | ||
| # TODO: Support inline comment disable | ||
| def ignored?(node) | ||
| location = node.location | ||
| return false unless location.expression | ||
|
|
||
| disable_lines.include?(location.line) | ||
| end | ||
|
|
||
| protected | ||
|
|
||
| def disable_lines | ||
| disable_comments.map do |comment| | ||
| comment.location.line + 1 | ||
| end | ||
| end | ||
| memoize :disable_lines | ||
|
|
||
| private | ||
|
|
||
| attr_reader :comments | ||
|
|
||
| def disable_comments | ||
| comments.select do |comment| | ||
| comment.text.eql?(COMMENT_TEXT) | ||
| end | ||
| end | ||
| end # Ignores | ||
| end # Mutest | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,8 @@ | ||
| module Mutest | ||
| class Matcher | ||
| # Abstract base class for method matchers | ||
| # | ||
| # :reek:TooManyMethods { max_methods: 11 } | ||
| class Method < self | ||
| include AbstractType, | ||
| Adamantium::Flat, | ||
|
|
@@ -71,16 +73,24 @@ def method_name | |
| # | ||
| # @return [Context] | ||
| def context | ||
| Context.new(scope, source_path) | ||
| Context.new(scope, source_path, ignores) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the term
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. k I'll rename to |
||
| end | ||
|
|
||
| # Root source node | ||
| # | ||
| # @return [Parser::AST::Node] | ||
| def ast | ||
| env.parser.call(source_path) | ||
| env.parser.parse(source_path) | ||
| end | ||
|
|
||
| # Ignores for source file | ||
| # | ||
| # @return [Array<Mutest::Ignore>] | ||
| def ignores | ||
| Ignores.new(env.parser.comments(source_path)) | ||
| end | ||
| memoize :ignores | ||
|
|
||
| # Path to source | ||
| # | ||
| # @return [Pathname] | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,16 +3,21 @@ module Mutest | |
| class Mutator | ||
| REGISTRY = Registry.new | ||
|
|
||
| include Adamantium::Flat, Concord.new(:input, :parent), AbstractType, Procto.call(:output) | ||
| include Adamantium::Flat, | ||
| Concord.new(:input, :filter, :parent), | ||
| AbstractType, | ||
| Procto.call(:output) | ||
|
|
||
| # Lookup and invoke dedicated AST mutator | ||
| # | ||
| # @param node [Parser::AST::Node] | ||
| # @param parent [nil,Mutest::Mutator::Node] | ||
| # | ||
| # @return [Set<Parser::AST::Node>] | ||
| def self.mutate(node, parent = nil) | ||
| self::REGISTRY.lookup(node.type).call(node, parent) | ||
| # | ||
| # :reek:LongParameterList | ||
| def self.mutate(node, filter = ->(_) {}, parent = nil) | ||
| self::REGISTRY.lookup(node.type).call(node, filter, parent) | ||
| end | ||
|
|
||
| # Register node class handler | ||
|
|
@@ -35,16 +40,21 @@ def self.handle(*types) | |
| # Initialize object | ||
| # | ||
| # @param [Object] input | ||
| # @param [#call] mutation filter | ||
| # @param [Object] parent | ||
| # @param [#call(node)] block | ||
| # | ||
| # @return [undefined] | ||
| def initialize(_input, _parent = nil) | ||
| def initialize(_input, _filter, _parent = nil) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's going on here? Is this because
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah.
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. weird ruby is weird. unfortunate that it's basically a false positive
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah that would be nice |
||
| super | ||
|
|
||
| @output = Set.new | ||
|
|
||
| dispatch | ||
| dispatch unless disabled? | ||
| end | ||
|
|
||
| def disabled? | ||
| filter.call(input) | ||
| end | ||
|
|
||
| # Test if generated object is not guarded from emitting | ||
|
|
@@ -82,8 +92,8 @@ def dup_input | |
| # Mutate child nodes within source path | ||
| # | ||
| # @return [Set<Parser::AST::Node>] | ||
| def mutate(*args) | ||
| self.class.mutate(*args) | ||
| def mutate(node, parent = nil) | ||
| self.class.mutate(node, filter, parent) | ||
| end | ||
|
|
||
| # Run input with mutator | ||
|
|
@@ -101,7 +111,7 @@ def run(mutator) | |
| def mutate_with(mutator, nodes, &block) | ||
| block ||= method(:emit) | ||
|
|
||
| mutator.call(nodes).each(&block) | ||
| mutator.call(nodes, filter).each(&block) | ||
| end | ||
| end # Mutator | ||
| end # Mutest | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,7 +28,7 @@ class Element < Util | |
| # @return [undefined] | ||
| def dispatch | ||
| input.each_with_index do |element, index| | ||
| Mutator.mutate(element).each do |mutation| | ||
| mutate(element).each do |mutation| | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was this
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was a result of that extraction PR you reviewed before
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah. Yeah, I was just trying to see why that would have changed due to this PR. |
||
| dup = dup_input | ||
| dup[index] = mutation | ||
| emit(dup) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| module Mutest | ||
| # A source file representation | ||
| class SourceFile | ||
| include Concord::Public.new(:ast, :comments) | ||
|
|
||
| # Read and parse file with comments | ||
| # | ||
| # @return [undefined] | ||
| def self.parse(source) | ||
| new(*::Parser::CurrentRuby.parse_with_comments(source)) | ||
| end | ||
| end # SourceFile | ||
| end # Mutest |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| RSpec.describe Mutest::Ignores do | ||
| subject(:comments) { described_class.new(parsed.last) } | ||
|
|
||
| let(:ast) { parsed.first } | ||
|
|
||
| let(:parsed) do | ||
| Parser::CurrentRuby.parse_with_comments(<<-RUBY) | ||
| # rubocop:disable | ||
| def foo(bar) | ||
| end | ||
|
|
||
| # mutest:disable | ||
| def bar | ||
| end | ||
| RUBY | ||
| end | ||
|
|
||
| it 'ignores the line after the the comment' do | ||
| foo_method, bar_method = *ast | ||
|
|
||
| aggregate_failures do | ||
| expect(comments.ignored?(foo_method)).to be(false) | ||
| expect(comments.ignored?(bar_method)).to be(true) | ||
| end | ||
| end | ||
|
|
||
| it 'ignores nodes that do not have a location' do | ||
| _, bar_method = *ast | ||
| _, bar_method_args, = *bar_method | ||
|
|
||
| expect(comments.ignored?(bar_method_args)).to be(false) | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,10 @@ | |
| ) | ||
| end | ||
|
|
||
| let(:ignores) do | ||
| Mutest::Ignores.new([]) | ||
| end | ||
|
|
||
| def name | ||
| node.children.fetch(0) | ||
| end | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may be worth converting to a set. Duplicates serve no purpose and repeatedly scanning the list for every node mutated could add up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I can do that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heh hard to prove it is a set since it is a private method. If only we had inline comment disables!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that occurred to me after I said it :P
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heh
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could always initialize a set and add to it, of course.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer to just leave it for now I think and then add an inline disable when I can 😆