From fad6bd2ecfb4877d52a313420a07d0fd8a36dcb7 Mon Sep 17 00:00:00 2001 From: Ilya Bylich Date: Fri, 3 Apr 2020 23:15:14 +0300 Subject: [PATCH 1/7] Update changelog. --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0968064bc..7392a4052 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ Changelog ========= -Not released (2020-04-03) -------------------------- +v2.7.1.0 (2020-04-03) +--------------------- API modifications: * Bump ruby versions to 2.4.10, 2.5.8, 2.6.6, 2.7.1. (#665) (Ilya Bylich) From a2bda6a031012b6706b5f20234aadad608617f32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Lafortune?= Date: Sat, 11 Apr 2020 11:59:55 -0400 Subject: [PATCH 2/7] Fix TreeRewriter#transaction [DOC] (#671) --- lib/parser/source/tree_rewriter.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/parser/source/tree_rewriter.rb b/lib/parser/source/tree_rewriter.rb index e3ced7b3b..c2d8662e3 100644 --- a/lib/parser/source/tree_rewriter.rb +++ b/lib/parser/source/tree_rewriter.rb @@ -203,10 +203,9 @@ def process ## # Provides a protected block where a sequence of multiple rewrite actions # are handled atomically. If any of the actions failed by clobbering, - # all the actions are rolled back. + # all the actions are rolled back. Transactions can be nested. # # @raise [RuntimeError] when no block is passed - # @raise [RuntimeError] when already in a transaction # def transaction unless block_given? From 15551dbc377468fd1880dd0be20038134121f82d Mon Sep 17 00:00:00 2001 From: Koichi ITO Date: Sun, 12 Apr 2020 01:00:57 +0900 Subject: [PATCH 3/7] CI against Ruby 2.4.10 (#672) Follow https://github.com/whitequark/parser/pull/665/commits/3274464. Currently, Ruby 2.4.10 is available on Travis CI. https://travis-ci.community/t/cannot-find-ruby-2-4-10-and-2-6-6-using-rvm-ubuntu-16-04-xenial/7962 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index e38bf0699..63164edb8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,8 @@ dist: trusty language: ruby matrix: include: - - name: 2.4.9 / Parser tests - rvm: 2.4.9 + - name: 2.4.10 / Parser tests + rvm: 2.4.10 script: bundle exec rake test_cov - name: 2.5.8 / Parser tests rvm: 2.5.8 From f780d9a90538e9ccf6cb13a04ceb08ef36775b0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Lafortune?= Date: Tue, 14 Apr 2020 03:57:23 -0400 Subject: [PATCH 4/7] + Source::TreeRewriter: Add #merge, #merge! and #empty? (#674) --- lib/parser/source/tree_rewriter.rb | 41 +++++++++++ lib/parser/source/tree_rewriter/action.rb | 28 ++++++-- test/test_source_tree_rewriter.rb | 86 ++++++++++++++++++++++- 3 files changed, 146 insertions(+), 9 deletions(-) diff --git a/lib/parser/source/tree_rewriter.rb b/lib/parser/source/tree_rewriter.rb index c2d8662e3..2d4dcdb24 100644 --- a/lib/parser/source/tree_rewriter.rb +++ b/lib/parser/source/tree_rewriter.rb @@ -117,6 +117,43 @@ def initialize(source_buffer, @action_root = TreeRewriter::Action.new(all_encompassing_range, @enforcer) end + ## + # Returns true iff no (non trivial) update has been recorded + # + # @return [Boolean] + # + def empty? + @action_root.empty? + end + + ## + # Merges the updates of argument with the receiver. + # Policies of the receiver are used. + # + # @param [Rewriter] with + # @return [Rewriter] self + # @raise [ClobberingError] when clobbering is detected + # + def merge!(with) + raise 'TreeRewriter are not for the same source_buffer' unless + source_buffer == with.source_buffer + + @action_root = @action_root.combine(with.action_root) + self + end + + ## + # Returns a new rewriter that consists of the updates of the received + # and the given argument. Policies of the receiver are used. + # + # @param [Rewriter] with + # @return [Rewriter] merge of receiver and argument + # @raise [ClobberingError] when clobbering is detected + # + def merge(with) + dup.merge!(with) + end + ## # Replaces the code of the source range `range` with `content`. # @@ -255,6 +292,10 @@ def insert_after_multi(range, text) extend Deprecation + protected + + attr_reader :action_root + private ACTIONS = %i[accept warn raise].freeze diff --git a/lib/parser/source/tree_rewriter/action.rb b/lib/parser/source/tree_rewriter/action.rb index 5aa965d11..a835d83a5 100644 --- a/lib/parser/source/tree_rewriter/action.rb +++ b/lib/parser/source/tree_rewriter/action.rb @@ -25,12 +25,18 @@ def initialize(range, enforcer, freeze end - # Assumes action.children.empty? def combine(action) - return self unless action.insertion? || action.replacement # Ignore empty action + return self if action.empty? # Ignore empty action do_combine(action) end + def empty? + @insert_before.empty? && + @insert_after.empty? && + @children.empty? && + (@replacement == nil || (@replacement.empty? && @range.empty?)) + end + def ordered_replacements reps = [] reps << [@range.begin, @insert_before] unless @insert_before.empty? @@ -46,9 +52,11 @@ def insertion? protected - def with(range: @range, children: @children, insert_before: @insert_before, replacement: @replacement, insert_after: @insert_after) + attr_reader :children + + def with(range: @range, enforcer: @enforcer, children: @children, insert_before: @insert_before, replacement: @replacement, insert_after: @insert_after) children = swallow(children) if replacement - self.class.new(range, @enforcer, children: children, insert_before: insert_before, replacement: replacement, insert_after: insert_after) + self.class.new(range, enforcer, children: children, insert_before: insert_before, replacement: replacement, insert_after: insert_after) end # Assumes range.contains?(action.range) && action.children.empty? @@ -69,7 +77,8 @@ def place_in_hierarchy(action) extra_sibbling = if family[:parent] # action should be a descendant of one of the children family[:parent][0].do_combine(action) elsif family[:child] # or it should become the parent of some of the children, - action.with(children: family[:child]) + action.with(children: family[:child], enforcer: @enforcer) + .combine_children(action.children) else # or else it should become an additional child action end @@ -77,6 +86,13 @@ def place_in_hierarchy(action) end end + # Assumes more_children all contained within @range + def combine_children(more_children) + more_children.inject(self) do |parent, new_child| + parent.place_in_hierarchy(new_child) + end + end + def fuse_deletions(action, fusible, other_sibblings) without_fusible = with(children: other_sibblings) fused_range = [action, *fusible].map(&:range).inject(:join) @@ -109,7 +125,7 @@ def merge(action) insert_before: "#{action.insert_before}#{insert_before}", replacement: action.replacement || @replacement, insert_after: "#{insert_after}#{action.insert_after}", - ) + ).combine_children(action.children) end def call_enforcer_for_merge(action) diff --git a/test/test_source_tree_rewriter.rb b/test/test_source_tree_rewriter.rb index b93e437b5..eadca9287 100644 --- a/test/test_source_tree_rewriter.rb +++ b/test/test_source_tree_rewriter.rb @@ -8,8 +8,10 @@ def setup @buf.source = 'puts(:hello, :world)' @hello = range(5, 6) + @ll = range(7, 2) @comma_space = range(11,2) @world = range(13,6) + @whole = range(0, @buf.source.length) end def range(from, len) @@ -17,11 +19,11 @@ def range(from, len) end # Returns either: - # - String (Normal operation) + # - yield rewriter # - [diagnostic, ...] (Diagnostics) # - Parser::ClobberingError # - def apply(actions, **policy) + def build(actions, **policy) diagnostics = [] diags = -> { diagnostics.flatten.map(&:strip).join("\n") } rewriter = Parser::Source::TreeRewriter.new(@buf, **policy) @@ -30,7 +32,7 @@ def apply(actions, **policy) rewriter.public_send(action, range, *args) end if diagnostics.empty? - rewriter.process + yield rewriter else diags.call end @@ -38,6 +40,15 @@ def apply(actions, **policy) [::Parser::ClobberingError, diags.call] end + # Returns either: + # - String (Normal operation) + # - [diagnostic, ...] (Diagnostics) + # - Parser::ClobberingError + # + def apply(actions, **policy) + build(actions, **policy) { |rewriter| rewriter.process } + end + # Expects ordered actions to be grouped together def check_actions(expected, grouped_actions, **policy) grouped_actions.permutation do |sequence| @@ -170,4 +181,73 @@ def test_out_of_range_ranges rewriter = Parser::Source::TreeRewriter.new(@buf) assert_raises(IndexError) { rewriter.insert_before(range(0, 100), 'hola') } end + + def test_empty + rewriter = Parser::Source::TreeRewriter.new(@buf) + assert_equal true, rewriter.empty? + + # This is a trivial wrap + rewriter.wrap(range(2,3), '', '') + assert_equal true, rewriter.empty? + + # This is a trivial deletion + rewriter.remove(range(2,0)) + assert_equal true, rewriter.empty? + + rewriter.remove(range(2,3)) + assert_equal false, rewriter.empty? + end + + # splits array into two groups, yield all such possible pairs of groups + # each_split([1, 2, 3, 4]) yields [1, 2], [3, 4]; + # then [1, 3], [2, 4] + # ... + # and finally [3, 4], [1, 2] + def each_split(array) + n = array.size + first_split_size = n.div(2) + splitting = (0...n).to_set + splitting.to_a.combination(first_split_size) do |indices| + yield array.values_at(*indices), + array.values_at(*(splitting - indices)) + end + end + + # Checks that `actions+extra` give the same result when + # made in order or from subgroups that are later merged. + # The `extra` actions are always added at the end of the second group. + # + def check_all_merge_possibilities(actions, extra, **policy) + expected = apply(actions + extra, **policy) + + each_split(actions) do |actions_1, actions_2| + build(actions_1, **policy) do |rewriter_1| + build(actions_2 + extra, **policy) do |rewriter_2| + result = rewriter_1.merge(rewriter_2).process + assert_equal(expected, result, + "Group 1: #{actions_1.inspect}\n\n" + + "Group 2: #{(actions_2 + extra).inspect}" + ) + end + end + end + end + + def test_merge + check_all_merge_possibilities([ + [:wrap, @whole, '<', '>'], + [:replace, @comma_space, ' => '], + [:wrap, @hello, '!', '!'], + # Following two wraps must have same value as they + # will be applied in different orders... + [:wrap, @hello.join(@world), '{', '}'], + [:wrap, @hello.join(@world), '{', '}'], + [:remove, @ll], + [:replace, @world, ':everybody'], + [:wrap, @world, '[', ']'] + ], + [ # ... but this one is always going to be applied last (extra) + [:wrap, @hello.join(@world), '@', '@'], + ]) + end end From f403668688560fddd7eb15783e8829357fe661e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Lafortune?= Date: Tue, 14 Apr 2020 17:54:14 -0400 Subject: [PATCH 5/7] + Add Source::Range#eql? and hash (#675) --- lib/parser/source/range.rb | 9 +++++++++ test/test_source_range.rb | 15 +++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/lib/parser/source/range.rb b/lib/parser/source/range.rb index ad421c918..686d0baeb 100644 --- a/lib/parser/source/range.rb +++ b/lib/parser/source/range.rb @@ -298,6 +298,15 @@ def <=>(other) (@end_pos <=> other.end_pos) end + alias_method :eql?, :== + + ## + # Support for Ranges be used in as Hash indices and in Sets. + # + def hash + [@source_buffer, @begin_pos, @end_pos].hash + end + ## # @return [String] a human-readable representation of this range. # diff --git a/test/test_source_range.rb b/test/test_source_range.rb index 2c7cfe72f..605b0132e 100644 --- a/test/test_source_range.rb +++ b/test/test_source_range.rb @@ -169,4 +169,19 @@ def test_with assert_equal 1, sr3.begin_pos assert_equal 4, sr3.end_pos end + + def test_eql_and_hash + assert_equal false, @sr1_3.eql?(@sr3_3) + assert @sr1_3.hash != @sr3_3.hash + + also_1_3 = @sr3_3.with(begin_pos: 1) + assert_equal true, @sr1_3.eql?(also_1_3) + assert_equal @sr1_3.hash, also_1_3.hash + + buf2 = Parser::Source::Buffer.new('(string)') + buf2.source = "foobar\nbaz" + from_other_buf = Parser::Source::Range.new(buf2, 1, 3) + assert_equal false, @sr1_3.eql?(from_other_buf) + assert @sr1_3.hash != from_other_buf.hash + end end From c8369bec859552e82d4ed17bfbcc8ec97125b8c9 Mon Sep 17 00:00:00 2001 From: Ilya Bylich Date: Wed, 15 Apr 2020 00:55:43 +0300 Subject: [PATCH 6/7] Bump version. --- lib/parser/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/parser/version.rb b/lib/parser/version.rb index 3b6f0a35f..18ae9e38f 100644 --- a/lib/parser/version.rb +++ b/lib/parser/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Parser - VERSION = '2.7.1.0' + VERSION = '2.7.1.1' end From 9ea976c0cf695dc1436c99ce7d81266a42a5ce4b Mon Sep 17 00:00:00 2001 From: Ilya Bylich Date: Wed, 15 Apr 2020 00:55:57 +0300 Subject: [PATCH 7/7] Update changelog. --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7392a4052..ee1df42d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ Changelog ========= +Not released (2020-04-15) +------------------------- + +Features implemented: + * Add Source::Range#eql? and hash (#675) (Marc-André Lafortune) + * Source::TreeRewriter: Add #merge, #merge! and #empty? (#674) (Marc-André Lafortune) + v2.7.1.0 (2020-04-03) ---------------------