From 8099b0e8806cfa37eaf1b7533f665c4ebc89f3e7 Mon Sep 17 00:00:00 2001 From: Nathan Weizenbaum Date: Fri, 7 May 2010 22:25:37 -0700 Subject: [PATCH] [Sass] Fix up a bunch of edge cases for the new @extend semantics. --- lib/haml/util.rb | 42 ++++++---------- lib/sass/selector/sequence.rb | 25 +++++++++- test/haml/util_test.rb | 13 ++--- test/sass/extend_test.rb | 90 +++++++++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+), 38 deletions(-) diff --git a/lib/haml/util.rb b/lib/haml/util.rb index 0081d349..193ede0f 100644 --- a/lib/haml/util.rb +++ b/lib/haml/util.rb @@ -199,22 +199,16 @@ module Haml # # @param x [Array] # @param y [Array] + # @yield [a, b] An optional block to use in place of a check for equality + # between elements of `x` and `y`. + # @yieldreturn [Object, nil] If the two values register as equal, + # this will return the value to use in the LCS array. # @return [Array] The LCS - def lcs(x, y) + def lcs(x, y, &block) x = [nil, *x] y = [nil, *y] - lcs_backtrace(lcs_table(x, y), x, y, x.size-1, y.size-1) - end - - # Computes all single longest common subsequences for `x` and `y`. - # - # @param x [Array] - # @param y [Array] - # @return [Set] The LCSes - def lcs_all(x, y) - x = [nil, *x] - y = [nil, *y] - lcs_backtrace_all(lcs_table(x, y), x, y, x.size-1, y.size-1) + block ||= proc {|a, b| a == b && a} + lcs_backtrace(lcs_table(x, y, &block), x, y, x.size-1, y.size-1, &block) end # Returns information about the caller of the previous method. @@ -600,7 +594,7 @@ METHOD (1...x.size).each do |i| (1...y.size).each do |j| c[i][j] = - if x[i] == y[j] + if yield x[i], y[j] c[i-1][j-1] + 1 else [c[i][j-1], c[i-1][j]].max @@ -612,22 +606,14 @@ METHOD # Computes a single longest common subsequence for arrays x and y. # Algorithm from [Wikipedia](http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Reading_out_an_LCS) - def lcs_backtrace(c, x, y, i, j) + def lcs_backtrace(c, x, y, i, j, &block) return [] if i == 0 || j == 0 - return lcs_backtrace(c, x, y, i-1, j-1) << x[i] if x[i] == y[j] - return lcs_backtrace(c, x, y, i, j-1) if c[i][j-1] > c[i-1][j] - return lcs_backtrace(c, x, y, i-1, j) - end + if v = yield(x[i], y[j]) + return lcs_backtrace(c, x, y, i-1, j-1, &block) << v + end - # Computes all longest common subsequences for arrays x and y. - # Algorithm from [Wikipedia](http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Reading_out_all_LCSs) - def lcs_backtrace_all(c, x, y, i, j) - return Set[[]] if i == 0 || j == 0 - return lcs_backtrace_all(c, x, y, i-1, j-1).map {|z| z << x[i]}.to_set if x[i] == y[j] - r = Set.new - r.merge(lcs_backtrace_all(c, x, y, i, j-1)) if c[i][j-1] >= c[i-1][j] - r.merge(lcs_backtrace_all(c, x, y, i-1, j)) if c[i-1][j] >= c[i][j-1] - r + return lcs_backtrace(c, x, y, i, j-1, &block) if c[i][j-1] > c[i-1][j] + return lcs_backtrace(c, x, y, i-1, j, &block) end end end diff --git a/lib/sass/selector/sequence.rb b/lib/sass/selector/sequence.rb index b13a086e..4e3a6030 100644 --- a/lib/sass/selector/sequence.rb +++ b/lib/sass/selector/sequence.rb @@ -163,11 +163,16 @@ module Sass seq1 = group_selectors(seq1) seq2 = group_selectors(seq2) - lcs = Haml::Util.lcs(seq2, seq1) + lcs = Haml::Util.lcs(seq2, seq1) do |s1, s2| + next s1 if s1 == s2 + next unless s1.first.is_a?(SimpleSequence) && s2.first.is_a?(SimpleSequence) + next s2 if subweave_superselector?(s1, s2) + next s1 if subweave_superselector?(s2, s1) + end diff = [] until lcs.empty? - diff << chunks(seq1, seq2) {|s| s.first == lcs.first} << [lcs.shift] + diff << chunks(seq1, seq2) {|s| subweave_superselector?(s.first, lcs.first)} << [lcs.shift] seq1.shift seq2.shift end @@ -201,6 +206,22 @@ module Sass return newseq end + def subweave_superselector?(sseq1, sseq2) + if sseq1.size > 1 + # More complex selectors are never superselectors of less complex ones + return unless sseq2.size > 1 + # .foo ~ .bar is a superselector of .foo + .bar + return unless sseq1[1] == "~" ? sseq2[1] != ">" : sseq2[1] == sseq1[1] + return sseq1.first.superselector?(sseq2.first) && + subweave_superselector?(sseq1[2..-1], sseq2[2..-1]) + elsif sseq2.size > 1 + return true if sseq2[1] == ">" && sseq1.first.superselector?(sseq2.first) + return subweave_superselector?(sseq1, sseq2[2..-1]) + else + sseq1.first.superselector?(sseq2.first) + end + end + def unify_heads(sseq1, sseq2) return unless sseq2.size == 1 # Can't unify ".foo > .bar" and ".baz > .bang" unified = sseq1.last.unify(sseq2.last.members) unless sseq1.last.is_a?(String) || sseq2.last.is_a?(String) diff --git a/test/haml/util_test.rb b/test/haml/util_test.rb index 967b9d9b..6145aabd 100755 --- a/test/haml/util_test.rb +++ b/test/haml/util_test.rb @@ -110,14 +110,11 @@ class UtilTest < Test::Unit::TestCase assert_equal([1, 2], lcs([1, 2, 3, 4], [3, 4, 1, 2])) end - def test_lcs_all - assert_equal(Set[[1, 2, 3]], lcs_all([1, 2, 3], [1, 2, 3])) - assert_equal(Set[[]], lcs_all([], [1, 2, 3])) - assert_equal(Set[[]], lcs_all([1, 2, 3], [])) - assert_equal(Set[[1, 2, 3]], lcs_all([5, 1, 4, 2, 3, 17], [0, 0, 1, 2, 6, 3])) - - assert_equal(Set[[1], [2], [3], [4]], lcs_all([1, 2, 3, 4], [4, 3, 2, 1])) - assert_equal(Set[[1, 2], [3, 4]], lcs_all([1, 2, 3, 4], [3, 4, 1, 2])) + def test_lcs_with_block + assert_equal(["1", "2", "3"], + lcs([1, 4, 2, 5, 3], [1, 2, 3]) {|a, b| a == b && a.to_s}) + assert_equal([-4, 2, 8], + lcs([-5, 3, 2, 8], [-4, 1, 8]) {|a, b| (a - b).abs <= 1 && [a, b].max}) end def test_silence_warnings diff --git a/test/sass/extend_test.rb b/test/sass/extend_test.rb index fafdf113..612543eb 100755 --- a/test/sass/extend_test.rb +++ b/test/sass/extend_test.rb @@ -1024,6 +1024,26 @@ CSS SCSS end + def test_nested_extender_counts_extended_subselectors + assert_equal < bar { @@ -1034,6 +1054,76 @@ foo > bar {@extend .foo} SCSS end + def test_nested_extender_finds_common_selectors_around_child_selector + assert_equal < b c .c1, a > b c .c2 { + a: b; } +CSS +a > b c .c1 {a: b} +a c .c2 {@extend .c1} +SCSS + + assert_equal < b c .c1, a > b c .c2 { + a: b; } +CSS +a > b c .c1 {a: b} +b c .c2 {@extend .c1} +SCSS + end + + def test_nested_extender_doesnt_find_common_selectors_around_adjacent_sibling_selector + assert_equal < .bap .foo, .bip > .bap .grip > .bap .bar, .grip > .bap .bip > .bap .bar {