haml 3.0.0.rc.4 → 3.0.0.rc.5

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of haml might be problematic. Click here for more details.

data/Rakefile CHANGED
@@ -263,7 +263,8 @@ OPTS
263
263
 
264
264
  YARD::Rake::YardocTask.new do |t|
265
265
  t.files = FileList.new(scope('lib/**/*.rb')) do |list|
266
- list.exclude('lib/haml/template/*.rb')
266
+ list.exclude('lib/haml/template/patch.rb')
267
+ list.exclude('lib/haml/template/plugin.rb')
267
268
  list.exclude('lib/haml/railtie.rb')
268
269
  list.exclude('lib/haml/helpers/action_view_mods.rb')
269
270
  list.exclude('lib/haml/helpers/xss_mods.rb')
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.0.0.rc.4
1
+ 3.0.0.rc.5
@@ -193,6 +193,24 @@ module Haml
193
193
  end
194
194
  end
195
195
 
196
+ # Computes a single longest common subsequence for `x` and `y`.
197
+ # If there are more than one longest common subsequences,
198
+ # the one returned is that which starts first in `x`.
199
+ #
200
+ # @param x [Array]
201
+ # @param y [Array]
202
+ # @yield [a, b] An optional block to use in place of a check for equality
203
+ # between elements of `x` and `y`.
204
+ # @yieldreturn [Object, nil] If the two values register as equal,
205
+ # this will return the value to use in the LCS array.
206
+ # @return [Array] The LCS
207
+ def lcs(x, y, &block)
208
+ x = [nil, *x]
209
+ y = [nil, *y]
210
+ block ||= proc {|a, b| a == b && a}
211
+ lcs_backtrace(lcs_table(x, y, &block), x, y, x.size-1, y.size-1, &block)
212
+ end
213
+
196
214
  # Returns information about the caller of the previous method.
197
215
  #
198
216
  # @param entry [String] An entry in the `#caller` list, or a similarly formatted string
@@ -564,5 +582,38 @@ METHOD
564
582
  def static_method_name(name, *vars)
565
583
  "#{name}_#{vars.map {|v| !!v}.join('_')}"
566
584
  end
585
+
586
+ private
587
+
588
+ # Calculates the memoization table for the Least Common Subsequence algorithm.
589
+ # Algorithm from [Wikipedia](http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Computing_the_length_of_the_LCS)
590
+ def lcs_table(x, y)
591
+ c = Array.new(x.size) {[]}
592
+ x.size.times {|i| c[i][0] = 0}
593
+ y.size.times {|j| c[0][j] = 0}
594
+ (1...x.size).each do |i|
595
+ (1...y.size).each do |j|
596
+ c[i][j] =
597
+ if yield x[i], y[j]
598
+ c[i-1][j-1] + 1
599
+ else
600
+ [c[i][j-1], c[i-1][j]].max
601
+ end
602
+ end
603
+ end
604
+ return c
605
+ end
606
+
607
+ # Computes a single longest common subsequence for arrays x and y.
608
+ # Algorithm from [Wikipedia](http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Reading_out_an_LCS)
609
+ def lcs_backtrace(c, x, y, i, j, &block)
610
+ return [] if i == 0 || j == 0
611
+ if v = yield(x[i], y[j])
612
+ return lcs_backtrace(c, x, y, i-1, j-1, &block) << v
613
+ end
614
+
615
+ return lcs_backtrace(c, x, y, i, j-1, &block) if c[i][j-1] > c[i-1][j]
616
+ return lcs_backtrace(c, x, y, i-1, j, &block)
617
+ end
567
618
  end
568
619
  end
@@ -41,23 +41,8 @@ module Sass::Script
41
41
 
42
42
  # @see Node#to_s
43
43
  def to_s(opts = {})
44
- to_sass(opts)
45
- end
46
-
47
- # @param opts [{Symbol => Object}]
48
- # `opts[:type]` -- The type of string to render this as.
49
- # `:string`s have double quotes, `:identifier`s do not.
50
- # Defaults to `:identifier`.
51
- # @see Node#to_sass
52
- def to_sass(opts = {})
53
- type = opts[:type] || self.type
54
- if type == :identifier
55
- if context == :equals && self.value !~ Sass::SCSS::RX::URI &&
56
- Sass::SCSS::RX.escape_ident(self.value).include?(?\\)
57
- return "unquote(#{Sass::Script::String.new(self.value, :string).to_sass})"
58
- elsif context == :equals && self.value.size == 0
59
- return %q{""}
60
- end
44
+ if self.type == :identifier
45
+ return %q{""} if context == :equals && self.value.size == 0
61
46
  return self.value.gsub("\n", " ")
62
47
  end
63
48
 
@@ -67,5 +52,16 @@ module Sass::Script
67
52
  return "'#{value}'" unless value.include?("'")
68
53
  "\"#{value.gsub('"', "\\\"")}\"" #'
69
54
  end
55
+
56
+ # @see Node#to_sass
57
+ def to_sass(opts = {})
58
+ if self.type == :identifier && context == :equals &&
59
+ self.value !~ Sass::SCSS::RX::URI &&
60
+ Sass::SCSS::RX.escape_ident(self.value).include?(?\\)
61
+ return "unquote(#{Sass::Script::String.new(self.value, :string).to_sass})"
62
+ else
63
+ return to_s
64
+ end
65
+ end
70
66
  end
71
67
  end
@@ -411,7 +411,18 @@ module Sass
411
411
  (tok(/\*/) && Selector::Universal.new(nil))
412
412
  res << v
413
413
  end
414
- expected('"{"') if tok?(/&/)
414
+
415
+ if tok?(/&/)
416
+ begin
417
+ expected('"{"')
418
+ rescue Sass::SyntaxError => e
419
+ e.message << "\n\n" << <<MESSAGE
420
+ In Sass 3, the parent selector & can only be used where element names are valid,
421
+ since it could potentially be replaced by an element name.
422
+ MESSAGE
423
+ raise e
424
+ end
425
+ end
415
426
 
416
427
  Selector::SimpleSequence.new(res)
417
428
  end
@@ -35,6 +35,28 @@ module Sass
35
35
  members.each {|m| m.filename = filename}
36
36
  @filename = filename
37
37
  end
38
+
39
+ # Returns a hash code for this sequence.
40
+ #
41
+ # Subclasses should define `#_hash` rather than overriding this method,
42
+ # which automatically handles memoizing the result.
43
+ #
44
+ # @return [Fixnum]
45
+ def hash
46
+ @_hash ||= _hash
47
+ end
48
+
49
+ # Checks equality between this and another object.
50
+ #
51
+ # Subclasses should define `#_eql?` rather than overriding this method,
52
+ # which handles checking class equality and hash equality.
53
+ #
54
+ # @param other [Object] The object to test equality against
55
+ # @return [Boolean] Whether or not this is equal to `other`
56
+ def eql?(other)
57
+ other.class == self.class && other.hash == self.hash && _eql?(other)
58
+ end
59
+ alias_method :==, :eql?
38
60
  end
39
61
  end
40
62
  end
@@ -61,18 +61,13 @@ module Sass
61
61
  members.map {|m| m.inspect}.join(", ")
62
62
  end
63
63
 
64
- # Returns a hash code for this sequence.
65
- #
66
- # @return [Fixnum]
67
- def hash
64
+ private
65
+
66
+ def _hash
68
67
  members.hash
69
68
  end
70
69
 
71
- # Checks equality between this and another object.
72
- #
73
- # @param other [Object] The object to test equality against
74
- # @return [Boolean] Whether or not this is equal to `other`
75
- def eql?(other)
70
+ def _eql?(other)
76
71
  other.class == self.class && other.members.eql?(self.members)
77
72
  end
78
73
  end
@@ -118,22 +118,6 @@ module Sass
118
118
  members.map {|m| m.inspect}.join(" ")
119
119
  end
120
120
 
121
- # Returns a hash code for this sequence.
122
- #
123
- # @return [Fixnum]
124
- def hash
125
- members.reject {|m| m == "\n"}.hash
126
- end
127
-
128
- # Checks equality between this and another object.
129
- #
130
- # @param other [Object] The object to test equality against
131
- # @return [Boolean] Whether or not this is equal to `other`
132
- def eql?(other)
133
- other.class == self.class &&
134
- other.members.reject {|m| m == "\n"}.eql?(self.members.reject {|m| m == "\n"})
135
- end
136
-
137
121
  private
138
122
 
139
123
  # Conceptually, this expands "parenthesized selectors".
@@ -176,37 +160,77 @@ module Sass
176
160
  def subweave(seq1, seq2, cache = {})
177
161
  return [seq2] if seq1.empty?
178
162
  return [seq1] if seq2.empty?
179
- cache[[seq1, seq2]] ||=
180
- begin
181
- sseq1, rest1 = seq_split(seq1)
182
- sseq2, rest2 = seq_split(seq2)
183
-
184
- if sseq1.eql?(sseq2)
185
- subweave(rest1, rest2, cache).map {|subseq| sseq1 + subseq}
186
- else
187
- unified = unify_heads(sseq1, sseq2) || unify_heads(sseq2, sseq1)
188
- res = []
189
- subweave(rest1, seq2, cache).each {|subseq| res << sseq1 + subseq}
190
- subweave(rest1, rest2, cache).each {|subseq| res << unified + subseq} if unified
191
- subweave(seq1, rest2, cache).each {|subseq| res << sseq2 + subseq}
192
- res
193
- end
194
- end
163
+
164
+ seq1 = group_selectors(seq1)
165
+ seq2 = group_selectors(seq2)
166
+ lcs = Haml::Util.lcs(seq2, seq1) do |s1, s2|
167
+ next s1 if s1 == s2
168
+ next unless s1.first.is_a?(SimpleSequence) && s2.first.is_a?(SimpleSequence)
169
+ next s2 if subweave_superselector?(s1, s2)
170
+ next s1 if subweave_superselector?(s2, s1)
171
+ end
172
+
173
+ diff = []
174
+ until lcs.empty?
175
+ diff << chunks(seq1, seq2) {|s| subweave_superselector?(s.first, lcs.first)} << [lcs.shift]
176
+ seq1.shift
177
+ seq2.shift
178
+ end
179
+ diff << chunks(seq1, seq2) {|s| s.empty?}
180
+ diff.reject! {|c| c.empty?}
181
+
182
+ Haml::Util.paths(diff).map {|p| p.flatten}
183
+ end
184
+
185
+ def chunks(seq1, seq2)
186
+ chunk1 = []
187
+ chunk1 << seq1.shift until yield seq1
188
+ chunk2 = []
189
+ chunk2 << seq2.shift until yield seq2
190
+ return [] if chunk1.empty? && chunk2.empty?
191
+ return [chunk2] if chunk1.empty?
192
+ return [chunk1] if chunk2.empty?
193
+ [chunk1 + chunk2, chunk2 + chunk1]
195
194
  end
196
195
 
197
- def seq_split(seq)
196
+ def group_selectors(seq)
197
+ newseq = []
198
198
  tail = seq.dup
199
- head = []
200
- begin
201
- head << tail.shift
202
- end while !tail.empty? && head.last.is_a?(String) || tail.first.is_a?(String)
203
- return head, tail
199
+ until tail.empty?
200
+ head = []
201
+ begin
202
+ head << tail.shift
203
+ end while !tail.empty? && head.last.is_a?(String) || tail.first.is_a?(String)
204
+ newseq << head
205
+ end
206
+ return newseq
207
+ end
208
+
209
+ def subweave_superselector?(sseq1, sseq2)
210
+ if sseq1.size > 1
211
+ # More complex selectors are never superselectors of less complex ones
212
+ return unless sseq2.size > 1
213
+ # .foo ~ .bar is a superselector of .foo + .bar
214
+ return unless sseq1[1] == "~" ? sseq2[1] != ">" : sseq2[1] == sseq1[1]
215
+ return unless sseq1.first.superselector?(sseq2.first)
216
+ return true if sseq1.size == 2
217
+ return false if sseq2.size == 2
218
+ return subweave_superselector?(sseq1[2..-1], sseq2[2..-1])
219
+ elsif sseq2.size > 1
220
+ return true if sseq2[1] == ">" && sseq1.first.superselector?(sseq2.first)
221
+ return false if sseq2.size == 2
222
+ return subweave_superselector?(sseq1, sseq2[2..-1])
223
+ else
224
+ sseq1.first.superselector?(sseq2.first)
225
+ end
226
+ end
227
+
228
+ def _hash
229
+ members.reject {|m| m == "\n"}.hash
204
230
  end
205
231
 
206
- def unify_heads(sseq1, sseq2)
207
- return unless sseq2.size == 1 # Can't unify ".foo > .bar" and ".baz > .bang"
208
- unified = sseq1.last.unify(sseq2.last.members) unless sseq1.last.is_a?(String) || sseq2.last.is_a?(String)
209
- sseq1[0...-1] << unified if unified
232
+ def _eql?(other)
233
+ other.members.reject {|m| m == "\n"}.eql?(self.members.reject {|m| m == "\n"})
210
234
  end
211
235
  end
212
236
  end
@@ -41,7 +41,7 @@ module Sass
41
41
  #
42
42
  # @return [Fixnum]
43
43
  def hash
44
- to_a.hash
44
+ @_hash ||= to_a.hash
45
45
  end
46
46
 
47
47
  # Checks equality between this and another object.
@@ -53,8 +53,9 @@ module Sass
53
53
  # @param other [Object] The object to test equality against
54
54
  # @return [Boolean] Whether or not this is equal to `other`
55
55
  def eql?(other)
56
- other.class == self.class && other.to_a.eql?(to_a)
56
+ other.class == self.class && other.hash == self.hash && other.to_a.eql?(to_a)
57
57
  end
58
+ alias_method :==, :eql?
58
59
 
59
60
  # Unifies this selector with a {SimpleSequence}'s {SimpleSequence#members members array},
60
61
  # returning another `SimpleSequence` members array
@@ -122,20 +122,14 @@ module Sass
122
122
  members.map {|m| m.inspect}.join
123
123
  end
124
124
 
125
- # Returns a hash code for this sequence.
126
- #
127
- # @return [Fixnum]
128
- def hash
125
+ private
126
+
127
+ def _hash
129
128
  [base, Haml::Util.set_hash(rest)].hash
130
129
  end
131
130
 
132
- # Checks equality between this and another object.
133
- #
134
- # @param other [Object] The object to test equality against
135
- # @return [Boolean] Whether or not this is equal to `other`
136
- def eql?(other)
137
- other.class == self.class && other.base.eql?(self.base) &&
138
- Haml::Util.set_eql?(other.rest, self.rest)
131
+ def _eql?(other)
132
+ other.base.eql?(self.base) && Haml::Util.set_eql?(other.rest, self.rest)
139
133
  end
140
134
  end
141
135
  end
@@ -38,6 +38,11 @@ module Sass
38
38
  "#{' ' * tabs}@import \"#{@imported_filename}\";\n"
39
39
  end
40
40
 
41
+ # @see Node#cssize
42
+ def cssize(*args)
43
+ super.first
44
+ end
45
+
41
46
  protected
42
47
 
43
48
  # @see Node#_cssize
@@ -120,17 +120,6 @@ module Sass
120
120
  self.class == other.class && other.children == children
121
121
  end
122
122
 
123
- # Runs the dynamic Sass code *and* computes the CSS for the tree.
124
- #
125
- # @see #perform
126
- # @see #to_s
127
- def render
128
- extends = Haml::Util::SubsetMap.new
129
- result = perform(Environment.new).cssize(extends)
130
- result = result.do_extend(extends) unless extends.empty?
131
- result.to_s
132
- end
133
-
134
123
  # True if \{#to\_s} will return `nil`;
135
124
  # that is, if the node shouldn't be rendered.
136
125
  # Should only be called in a static tree.
@@ -21,6 +21,16 @@ module Sass
21
21
  raise e
22
22
  end
23
23
 
24
+ # Runs the dynamic Sass code *and* computes the CSS for the tree.
25
+ #
26
+ # @see #perform
27
+ # @see #to_s
28
+ def render
29
+ result, extends = perform(Environment.new).cssize
30
+ result = result.do_extend(extends) unless extends.empty?
31
+ result.to_s
32
+ end
33
+
24
34
  # @see Node#perform
25
35
  def perform(environment)
26
36
  environment.options = @options if environment.options.nil? || environment.options.empty?
@@ -30,9 +40,14 @@ module Sass
30
40
  raise e
31
41
  end
32
42
 
33
- # @see Node#cssize
34
- def cssize(*args)
35
- super
43
+ # Like {Node#cssize}, except that this method
44
+ # will create its own `extends` map if necessary,
45
+ # and it returns that map along with the cssized tree.
46
+ #
47
+ # @return [(Tree::Node, Haml::Util::SubsetMap)] The resulting tree of static nodes
48
+ # *and* the extensions defined for this tree
49
+ def cssize(extends = Haml::Util::SubsetMap.new, parent = nil)
50
+ return super(extends), extends
36
51
  rescue Sass::SyntaxError => e
37
52
  e.sass_template = @template
38
53
  raise e
@@ -100,6 +100,23 @@ class UtilTest < Test::Unit::TestCase
100
100
  assert_equal([[1, 2, 3]], paths([[1], [2], [3]]))
101
101
  end
102
102
 
103
+ def test_lcs
104
+ assert_equal([1, 2, 3], lcs([1, 2, 3], [1, 2, 3]))
105
+ assert_equal([], lcs([], [1, 2, 3]))
106
+ assert_equal([], lcs([1, 2, 3], []))
107
+ assert_equal([1, 2, 3], lcs([5, 1, 4, 2, 3, 17], [0, 0, 1, 2, 6, 3]))
108
+
109
+ assert_equal([1], lcs([1, 2, 3, 4], [4, 3, 2, 1]))
110
+ assert_equal([1, 2], lcs([1, 2, 3, 4], [3, 4, 1, 2]))
111
+ end
112
+
113
+ def test_lcs_with_block
114
+ assert_equal(["1", "2", "3"],
115
+ lcs([1, 4, 2, 5, 3], [1, 2, 3]) {|a, b| a == b && a.to_s})
116
+ assert_equal([-4, 2, 8],
117
+ lcs([-5, 3, 2, 8], [-4, 1, 8]) {|a, b| (a - b).abs <= 1 && [a, b].max})
118
+ end
119
+
103
120
  def test_silence_warnings
104
121
  old_stderr, $stderr = $stderr, StringIO.new
105
122
  warn "Out"
@@ -1608,13 +1608,15 @@ foo {
1608
1608
  a: foo;
1609
1609
  b: bar;
1610
1610
  c: foo bar;
1611
- d: foo, bar; }
1611
+ d: foo, bar baz;
1612
+ e: foo bar, bar; }
1612
1613
  CSS
1613
1614
  foo
1614
1615
  a= "foo"
1615
1616
  b= bar
1616
1617
  c= "foo" bar
1617
- d= foo, "bar"
1618
+ d= foo, "bar baz"
1619
+ e= "foo bar", bar
1618
1620
  SASS
1619
1621
  end
1620
1622
  end
@@ -974,34 +974,73 @@ foo bar {@extend .foo}
974
974
  SCSS
975
975
  end
976
976
 
977
- def test_nested_extender_interleaves_parents_with_unification
977
+ def test_nested_extender_alternates_parents
978
978
  assert_equal <<CSS, render(<<SCSS)
979
- .baz .foo, .baz foo bar, foo.baz bar, foo .baz bar {
979
+ .baz .bip .foo, .baz .bip foo .grank bar, foo .grank .baz .bip bar {
980
980
  a: b; }
981
981
  CSS
982
- .baz .foo {a: b}
983
- foo bar {@extend .foo}
982
+ .baz .bip .foo {a: b}
983
+ foo .grank bar {@extend .foo}
984
984
  SCSS
985
985
  end
986
986
 
987
- def test_nested_extender_interleaves_parents_with_aborted_unification
987
+ def test_nested_extender_unifies_identical_parents
988
988
  assert_equal <<CSS, render(<<SCSS)
989
- baz .foo, baz foo bar, foo baz bar {
989
+ .baz .bip .foo, .baz .bip bar {
990
990
  a: b; }
991
991
  CSS
992
- baz .foo {a: b}
993
- foo bar {@extend .foo}
992
+ .baz .bip .foo {a: b}
993
+ .baz .bip bar {@extend .foo}
994
+ SCSS
995
+ end
996
+
997
+ def test_nested_extender_unifies_common_substring
998
+ assert_equal <<CSS, render(<<SCSS)
999
+ .baz .bip .bap .bink .foo, .baz .brat .bip .bap .bink bar, .brat .baz .bip .bap .bink bar {
1000
+ a: b; }
1001
+ CSS
1002
+ .baz .bip .bap .bink .foo {a: b}
1003
+ .brat .bip .bap bar {@extend .foo}
1004
+ SCSS
1005
+ end
1006
+
1007
+ def test_nested_extender_unifies_common_subseq
1008
+ assert_equal <<CSS, render(<<SCSS)
1009
+ .a .x .b .y .foo, .a .x .n .b .y .m bar, .a .n .x .b .y .m bar, .a .x .n .b .m .y bar, .a .n .x .b .m .y bar {
1010
+ a: b; }
1011
+ CSS
1012
+ .a .x .b .y .foo {a: b}
1013
+ .a .n .b .m bar {@extend .foo}
1014
+ SCSS
1015
+ end
1016
+
1017
+ def test_nested_extender_chooses_first_subseq
1018
+ assert_equal <<CSS, render(<<SCSS)
1019
+ .a .b .c .d .foo, .a .b .c .d .a .b .bar {
1020
+ a: b; }
1021
+ CSS
1022
+ .a .b .c .d .foo {a: b}
1023
+ .c .d .a .b .bar {@extend .foo}
1024
+ SCSS
1025
+ end
1026
+
1027
+ def test_nested_extender_counts_extended_subselectors
1028
+ assert_equal <<CSS, render(<<SCSS)
1029
+ .a .bip.bop .foo, .a .b .bip.bop .bar, .b .a .bip.bop .bar {
1030
+ a: b; }
1031
+ CSS
1032
+ .a .bip.bop .foo {a: b}
1033
+ .b .bip .bar {@extend .foo}
994
1034
  SCSS
995
1035
  end
996
1036
 
997
- def test_nested_extender_with_lots_of_interleaving
998
- # Please, never ever do this in a real stylesheet
1037
+ def test_nested_extender_counts_extended_superselectors
999
1038
  assert_equal <<CSS, render(<<SCSS)
1000
- .foo .bar .baz .bang, .foo .bar .baz .foo2 .bar2 .baz2 .bang2, .foo .bar .foo2.baz .bar2 .baz2 .bang2, .foo .bar .foo2 .baz .bar2 .baz2 .bang2, .foo .bar .foo2 .bar2.baz .baz2 .bang2, .foo .bar .foo2 .bar2 .baz .baz2 .bang2, .foo .bar .foo2 .bar2 .baz2.baz .bang2, .foo .bar .foo2 .bar2 .baz2 .baz .bang2, .foo .foo2.bar .baz .bar2 .baz2 .bang2, .foo .foo2.bar .bar2.baz .baz2 .bang2, .foo .foo2.bar .bar2 .baz .baz2 .bang2, .foo .foo2.bar .bar2 .baz2.baz .bang2, .foo .foo2.bar .bar2 .baz2 .baz .bang2, .foo .foo2 .bar .baz .bar2 .baz2 .bang2, .foo .foo2 .bar .bar2.baz .baz2 .bang2, .foo .foo2 .bar .bar2 .baz .baz2 .bang2, .foo .foo2 .bar .bar2 .baz2.baz .bang2, .foo .foo2 .bar .bar2 .baz2 .baz .bang2, .foo .foo2 .bar2.bar .baz .baz2 .bang2, .foo .foo2 .bar2.bar .baz2.baz .bang2, .foo .foo2 .bar2.bar .baz2 .baz .bang2, .foo .foo2 .bar2 .bar .baz .baz2 .bang2, .foo .foo2 .bar2 .bar .baz2.baz .bang2, .foo .foo2 .bar2 .bar .baz2 .baz .bang2, .foo .foo2 .bar2 .baz2.bar .baz .bang2, .foo .foo2 .bar2 .baz2 .bar .baz .bang2, .foo2.foo .bar .baz .bar2 .baz2 .bang2, .foo2.foo .bar .bar2.baz .baz2 .bang2, .foo2.foo .bar .bar2 .baz .baz2 .bang2, .foo2.foo .bar .bar2 .baz2.baz .bang2, .foo2.foo .bar .bar2 .baz2 .baz .bang2, .foo2.foo .bar2.bar .baz .baz2 .bang2, .foo2.foo .bar2.bar .baz2.baz .bang2, .foo2.foo .bar2.bar .baz2 .baz .bang2, .foo2.foo .bar2 .bar .baz .baz2 .bang2, .foo2.foo .bar2 .bar .baz2.baz .bang2, .foo2.foo .bar2 .bar .baz2 .baz .bang2, .foo2.foo .bar2 .baz2.bar .baz .bang2, .foo2.foo .bar2 .baz2 .bar .baz .bang2, .foo2 .foo .bar .baz .bar2 .baz2 .bang2, .foo2 .foo .bar .bar2.baz .baz2 .bang2, .foo2 .foo .bar .bar2 .baz .baz2 .bang2, .foo2 .foo .bar .bar2 .baz2.baz .bang2, .foo2 .foo .bar .bar2 .baz2 .baz .bang2, .foo2 .foo .bar2.bar .baz .baz2 .bang2, .foo2 .foo .bar2.bar .baz2.baz .bang2, .foo2 .foo .bar2.bar .baz2 .baz .bang2, .foo2 .foo .bar2 .bar .baz .baz2 .bang2, .foo2 .foo .bar2 .bar .baz2.baz .bang2, .foo2 .foo .bar2 .bar .baz2 .baz .bang2, .foo2 .foo .bar2 .baz2.bar .baz .bang2, .foo2 .foo .bar2 .baz2 .bar .baz .bang2, .foo2 .bar2.foo .bar .baz .baz2 .bang2, .foo2 .bar2.foo .bar .baz2.baz .bang2, .foo2 .bar2.foo .bar .baz2 .baz .bang2, .foo2 .bar2.foo .baz2.bar .baz .bang2, .foo2 .bar2.foo .baz2 .bar .baz .bang2, .foo2 .bar2 .foo .bar .baz .baz2 .bang2, .foo2 .bar2 .foo .bar .baz2.baz .bang2, .foo2 .bar2 .foo .bar .baz2 .baz .bang2, .foo2 .bar2 .foo .baz2.bar .baz .bang2, .foo2 .bar2 .foo .baz2 .bar .baz .bang2, .foo2 .bar2 .baz2.foo .bar .baz .bang2, .foo2 .bar2 .baz2 .foo .bar .baz .bang2 {
1039
+ .a .bip .foo, .a .b .bip.bop .bar, .b .a .bip.bop .bar {
1001
1040
  a: b; }
1002
1041
  CSS
1003
- .foo .bar .baz .bang {a: b}
1004
- .foo2 .bar2 .baz2 .bang2 {@extend .bang}
1042
+ .a .bip .foo {a: b}
1043
+ .b .bip.bop .bar {@extend .foo}
1005
1044
  SCSS
1006
1045
  end
1007
1046
 
@@ -1015,33 +1054,101 @@ foo > bar {@extend .foo}
1015
1054
  SCSS
1016
1055
  end
1017
1056
 
1018
- def test_nested_extender_with_descendant_and_child_selector
1057
+ def test_nested_extender_finds_common_selectors_around_child_selector
1019
1058
  assert_equal <<CSS, render(<<SCSS)
1020
- .baz .foo, .baz bang foo > bar, bang.baz foo > bar, bang .baz foo > bar {
1059
+ a > b c .c1, a > b c .c2 {
1021
1060
  a: b; }
1022
1061
  CSS
1023
- .baz .foo {a: b}
1024
- bang foo > bar {@extend .foo}
1062
+ a > b c .c1 {a: b}
1063
+ a c .c2 {@extend .c1}
1064
+ SCSS
1065
+
1066
+ assert_equal <<CSS, render(<<SCSS)
1067
+ a > b c .c1, a > b c .c2 {
1068
+ a: b; }
1069
+ CSS
1070
+ a > b c .c1 {a: b}
1071
+ b c .c2 {@extend .c1}
1025
1072
  SCSS
1026
1073
  end
1027
1074
 
1028
- def test_nested_extender_with_child_selector_unifies
1075
+ def test_nested_extender_doesnt_find_common_selectors_around_adjacent_sibling_selector
1029
1076
  assert_equal <<CSS, render(<<SCSS)
1030
- .baz.foo, foo > bar.baz {
1077
+ a + b c .c1, a + b a c .c2, a a + b c .c2 {
1031
1078
  a: b; }
1032
1079
  CSS
1033
- .baz.foo {a: b}
1034
- foo > bar {@extend .foo}
1080
+ a + b c .c1 {a: b}
1081
+ a c .c2 {@extend .c1}
1082
+ SCSS
1083
+
1084
+ assert_equal <<CSS, render(<<SCSS)
1085
+ a + b c .c1, a a + b c .c2 {
1086
+ a: b; }
1087
+ CSS
1088
+ a + b c .c1 {a: b}
1089
+ a b .c2 {@extend .c1}
1090
+ SCSS
1091
+
1092
+ assert_equal <<CSS, render(<<SCSS)
1093
+ a + b c .c1, a + b c .c2 {
1094
+ a: b; }
1095
+ CSS
1096
+ a + b c .c1 {a: b}
1097
+ b c .c2 {@extend .c1}
1035
1098
  SCSS
1036
1099
  end
1037
1100
 
1038
- def test_nested_extender_with_child_selector_and_more
1101
+ def test_nested_extender_doesnt_find_common_selectors_around_sibling_selector
1039
1102
  assert_equal <<CSS, render(<<SCSS)
1040
- .foo .bar, .foo foo > bar baz, foo > bar.foo baz, foo > bar .foo baz {
1103
+ a ~ b c .c1, a ~ b a c .c2, a a ~ b c .c2 {
1041
1104
  a: b; }
1042
1105
  CSS
1043
- .foo .bar {a: b}
1044
- foo > bar baz {@extend .bar}
1106
+ a ~ b c .c1 {a: b}
1107
+ a c .c2 {@extend .c1}
1108
+ SCSS
1109
+
1110
+ assert_equal <<CSS, render(<<SCSS)
1111
+ a ~ b c .c1, a a ~ b c .c2 {
1112
+ a: b; }
1113
+ CSS
1114
+ a ~ b c .c1 {a: b}
1115
+ a b .c2 {@extend .c1}
1116
+ SCSS
1117
+
1118
+ assert_equal <<CSS, render(<<SCSS)
1119
+ a ~ b c .c1, a ~ b c .c2 {
1120
+ a: b; }
1121
+ CSS
1122
+ a ~ b c .c1 {a: b}
1123
+ b c .c2 {@extend .c1}
1124
+ SCSS
1125
+ end
1126
+
1127
+ def test_nested_extender_with_early_child_selectors_doesnt_subseq_them
1128
+ assert_equal <<CSS, render(<<SCSS)
1129
+ .bip > .bap .foo, .bip > .bap .grip > .bap .bar, .grip > .bap .bip > .bap .bar {
1130
+ a: b; }
1131
+ CSS
1132
+ .bip > .bap .foo {a: b}
1133
+ .grip > .bap .bar {@extend .foo}
1134
+ SCSS
1135
+
1136
+ assert_equal <<CSS, render(<<SCSS)
1137
+ .bap > .bip .foo, .bap > .bip .bap > .grip .bar, .bap > .grip .bap > .bip .bar {
1138
+ a: b; }
1139
+ CSS
1140
+ .bap > .bip .foo {a: b}
1141
+ .bap > .grip .bar {@extend .foo}
1142
+ SCSS
1143
+ end
1144
+
1145
+ def test_nested_extender_with_child_selector_unifies
1146
+ assert_equal <<CSS, render(<<SCSS)
1147
+ .baz.foo, foo > bar.baz {
1148
+ a: b; }
1149
+ CSS
1150
+ .baz.foo {a: b}
1151
+ foo > bar {@extend .foo}
1045
1152
  SCSS
1046
1153
  end
1047
1154
 
@@ -37,8 +37,8 @@ LESS
37
37
  end
38
38
 
39
39
  def test_import
40
- path = File.dirname(__FILE__) + "/templates/importee.less"
41
- resolved_path = File.dirname(__FILE__) + "/templates/importee"
40
+ path = relative_path_to(File.dirname(__FILE__) + "/templates/importee.less")
41
+ resolved_path = relative_path_to(File.dirname(__FILE__) + "/templates/importee")
42
42
  assert_renders <<SCSS, <<LESS
43
43
  @import "#{resolved_path}";
44
44
  @import "#{resolved_path}";
@@ -57,8 +57,8 @@ LESS
57
57
  end
58
58
 
59
59
  def test_mixins_found_through_import
60
- path = File.dirname(__FILE__) + "/templates/importee.less"
61
- resolved_path = File.dirname(__FILE__) + "/templates/importee"
60
+ path = relative_path_to(File.dirname(__FILE__) + "/templates/importee.less")
61
+ resolved_path = relative_path_to(File.dirname(__FILE__) + "/templates/importee")
62
62
  assert_renders <<SCSS, <<LESS
63
63
  @import "#{resolved_path}";
64
64
 
@@ -618,6 +618,13 @@ LESS
618
618
  def assert_renders(scss, less)
619
619
  assert_equal(scss, Less::Engine.new(less).to_tree.to_sass_tree.to_scss)
620
620
  end
621
+
622
+ # Necessary because Less can't import absolute files
623
+ def relative_path_to(file)
624
+ file = Pathname.new(file)
625
+ pwd = file.absolute? ? Dir.pwd : "."
626
+ file.relative_path_from(Pathname.new(pwd))
627
+ end
621
628
  end
622
629
 
623
630
  rescue LoadError => e
@@ -950,7 +950,12 @@ SCSS
950
950
  end
951
951
 
952
952
  def test_parent_in_mid_selector_error
953
- assert_raise(Sass::SyntaxError, 'Invalid CSS after ".foo": expected "{", was "&.bar"') {render <<SCSS}
953
+ assert_raise(Sass::SyntaxError, <<MESSAGE) {render <<SCSS}
954
+ Invalid CSS after ".foo": expected "{", was "&.bar"
955
+
956
+ In Sass 3, the parent selector & can only be used where element names are valid,
957
+ since it could potentially be replaced by an element name.
958
+ MESSAGE
954
959
  flim {
955
960
  .foo&.bar {a: b}
956
961
  }
@@ -958,7 +963,12 @@ SCSS
958
963
  end
959
964
 
960
965
  def test_parent_in_mid_selector_error
961
- assert_raise(Sass::SyntaxError, 'Invalid CSS after ".foo.bar": expected "{", was "&"') {render <<SCSS}
966
+ assert_raise(Sass::SyntaxError, <<MESSAGE) {render <<SCSS}
967
+ Invalid CSS after ".foo.bar": expected "{", was "&"
968
+
969
+ In Sass 3, the parent selector & can only be used where element names are valid,
970
+ since it could potentially be replaced by an element name.
971
+ MESSAGE
962
972
  flim {
963
973
  .foo.bar& {a: b}
964
974
  }
@@ -966,7 +976,12 @@ SCSS
966
976
  end
967
977
 
968
978
  def test_double_parent_selector_error
969
- assert_raise(Sass::SyntaxError, 'Invalid CSS after "&": expected "{", was "&"') {render <<SCSS}
979
+ assert_raise(Sass::SyntaxError, <<MESSAGE) {render <<SCSS}
980
+ Invalid CSS after "&": expected "{", was "&"
981
+
982
+ In Sass 3, the parent selector & can only be used where element names are valid,
983
+ since it could potentially be replaced by an element name.
984
+ MESSAGE
970
985
  flim {
971
986
  && {a: b}
972
987
  }
metadata CHANGED
@@ -7,8 +7,8 @@ version: !ruby/object:Gem::Version
7
7
  - 0
8
8
  - 0
9
9
  - rc
10
- - 4
11
- version: 3.0.0.rc.4
10
+ - 5
11
+ version: 3.0.0.rc.5
12
12
  platform: ruby
13
13
  authors:
14
14
  - Nathan Weizenbaum
@@ -17,7 +17,7 @@ autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
19
 
20
- date: 2010-05-03 00:00:00 -07:00
20
+ date: 2010-05-07 00:00:00 -07:00
21
21
  default_executable:
22
22
  dependencies:
23
23
  - !ruby/object:Gem::Dependency