mirror of
https://github.com/haml/haml.git
synced 2022-11-09 12:33:31 -05:00
[Sass] Fix up a bunch of edge cases for the new @extend semantics.
This commit is contained in:
parent
84c1170f83
commit
8099b0e880
4 changed files with 132 additions and 38 deletions
|
@ -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<Array>] 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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1024,6 +1024,26 @@ CSS
|
|||
SCSS
|
||||
end
|
||||
|
||||
def test_nested_extender_counts_extended_subselectors
|
||||
assert_equal <<CSS, render(<<SCSS)
|
||||
.a .bip.bop .foo, .a .b .bip.bop .bar, .b .a .bip.bop .bar {
|
||||
a: b; }
|
||||
CSS
|
||||
.a .bip.bop .foo {a: b}
|
||||
.b .bip .bar {@extend .foo}
|
||||
SCSS
|
||||
end
|
||||
|
||||
def test_nested_extender_counts_extended_superselectors
|
||||
assert_equal <<CSS, render(<<SCSS)
|
||||
.a .bip .foo, .a .b .bip.bop .bar, .b .a .bip.bop .bar {
|
||||
a: b; }
|
||||
CSS
|
||||
.a .bip .foo {a: b}
|
||||
.b .bip.bop .bar {@extend .foo}
|
||||
SCSS
|
||||
end
|
||||
|
||||
def test_nested_extender_with_child_selector
|
||||
assert_equal <<CSS, render(<<SCSS)
|
||||
.baz .foo, .baz foo > bar {
|
||||
|
@ -1034,6 +1054,76 @@ foo > bar {@extend .foo}
|
|||
SCSS
|
||||
end
|
||||
|
||||
def test_nested_extender_finds_common_selectors_around_child_selector
|
||||
assert_equal <<CSS, render(<<SCSS)
|
||||
a > b c .c1, a > b c .c2 {
|
||||
a: b; }
|
||||
CSS
|
||||
a > b c .c1 {a: b}
|
||||
a c .c2 {@extend .c1}
|
||||
SCSS
|
||||
|
||||
assert_equal <<CSS, render(<<SCSS)
|
||||
a > 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 <<CSS, render(<<SCSS)
|
||||
a + b c .c1, a + b a c .c2, a a + b c .c2 {
|
||||
a: b; }
|
||||
CSS
|
||||
a + b c .c1 {a: b}
|
||||
a c .c2 {@extend .c1}
|
||||
SCSS
|
||||
|
||||
assert_equal <<CSS, render(<<SCSS)
|
||||
a + b c .c1, a a + b c .c2 {
|
||||
a: b; }
|
||||
CSS
|
||||
a + b c .c1 {a: b}
|
||||
a b .c2 {@extend .c1}
|
||||
SCSS
|
||||
|
||||
assert_equal <<CSS, render(<<SCSS)
|
||||
a + 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_sibling_selector
|
||||
assert_equal <<CSS, render(<<SCSS)
|
||||
a ~ b c .c1, a ~ b a c .c2, a a ~ b c .c2 {
|
||||
a: b; }
|
||||
CSS
|
||||
a ~ b c .c1 {a: b}
|
||||
a c .c2 {@extend .c1}
|
||||
SCSS
|
||||
|
||||
assert_equal <<CSS, render(<<SCSS)
|
||||
a ~ b c .c1, a a ~ b c .c2 {
|
||||
a: b; }
|
||||
CSS
|
||||
a ~ b c .c1 {a: b}
|
||||
a b .c2 {@extend .c1}
|
||||
SCSS
|
||||
|
||||
assert_equal <<CSS, render(<<SCSS)
|
||||
a ~ 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_with_early_child_selectors_doesnt_subseq_them
|
||||
assert_equal <<CSS, render(<<SCSS)
|
||||
.bip > .bap .foo, .bip > .bap .grip > .bap .bar, .grip > .bap .bip > .bap .bar {
|
||||
|
|
Loading…
Reference in a new issue