sass 3.3.0.alpha.231 → 3.3.0.alpha.243
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +1 -1
- data/REVISION +1 -1
- data/VERSION +1 -1
- data/VERSION_DATE +1 -1
- data/lib/sass/engine.rb +11 -7
- data/lib/sass/environment.rb +8 -0
- data/lib/sass/script/functions.rb +140 -17
- data/lib/sass/script/parser.rb +28 -5
- data/lib/sass/script/tree.rb +1 -0
- data/lib/sass/script/tree/map_literal.rb +64 -0
- data/lib/sass/script/value.rb +1 -0
- data/lib/sass/script/value/base.rb +29 -1
- data/lib/sass/script/value/color.rb +4 -0
- data/lib/sass/script/value/list.rb +27 -1
- data/lib/sass/script/value/map.rb +70 -0
- data/lib/sass/script/value/number.rb +12 -0
- data/lib/sass/scss/parser.rb +8 -2
- data/lib/sass/stack.rb +9 -0
- data/lib/sass/tree/each_node.rb +6 -6
- data/lib/sass/tree/rule_node.rb +1 -1
- data/lib/sass/tree/visitors/convert.rb +2 -1
- data/lib/sass/tree/visitors/perform.rb +19 -22
- data/lib/sass/tree/visitors/to_css.rb +1 -1
- data/test/sass/conversion_test.rb +10 -0
- data/test/sass/engine_test.rb +21 -0
- data/test/sass/functions_test.rb +126 -0
- data/test/sass/script_conversion_test.rb +16 -0
- data/test/sass/script_test.rb +41 -0
- data/test/sass/scss/scss_test.rb +25 -0
- metadata +11 -9
data/lib/sass/script/value.rb
CHANGED
@@ -158,6 +158,18 @@ MSG
|
|
158
158
|
Sass::Script::Value::String.new("/#{self.to_s}")
|
159
159
|
end
|
160
160
|
|
161
|
+
# Returns the hash code of this value. Two objects' hash codes should be
|
162
|
+
# equal if the objects are equal.
|
163
|
+
#
|
164
|
+
# @return [Fixnum] The hash code.
|
165
|
+
def hash
|
166
|
+
value.hash
|
167
|
+
end
|
168
|
+
|
169
|
+
def eql?(other)
|
170
|
+
self == other
|
171
|
+
end
|
172
|
+
|
161
173
|
# @return [String] A readable representation of the value
|
162
174
|
def inspect
|
163
175
|
value.inspect
|
@@ -185,14 +197,30 @@ MSG
|
|
185
197
|
# @raise [Sass::SyntaxError] if this value isn't an integer
|
186
198
|
def assert_int!; to_i; end
|
187
199
|
|
200
|
+
# Returns the separator for this value. For non-list-like values or the
|
201
|
+
# empty list, this will be `nil`. For lists or maps, it will be `:space` or
|
202
|
+
# `:comma`.
|
203
|
+
#
|
204
|
+
# @return [Symbol]
|
205
|
+
def separator; nil; end
|
206
|
+
|
188
207
|
# Returns the value of this value as a list.
|
189
208
|
# Single values are considered the same as single-element lists.
|
190
209
|
#
|
191
|
-
# @return [Array<Value>]
|
210
|
+
# @return [Array<Value>] This value as a list
|
192
211
|
def to_a
|
193
212
|
[self]
|
194
213
|
end
|
195
214
|
|
215
|
+
# Returns the value of this value as a hash. Most values don't have hash
|
216
|
+
# representations, but [Map]s and empty [List]s do.
|
217
|
+
#
|
218
|
+
# @return [Hash<Value, Value>] This value as a hash
|
219
|
+
# @raise [Sass::SyntaxError] if this value doesn't have a hash representation
|
220
|
+
def to_h
|
221
|
+
raise Sass::SyntaxError.new("#{self.inspect} is not a map.")
|
222
|
+
end
|
223
|
+
|
196
224
|
# Returns the string representation of this value
|
197
225
|
# as it would be output to the CSS document.
|
198
226
|
#
|
@@ -356,6 +356,10 @@ module Sass::Script::Value
|
|
356
356
|
other.is_a?(Color) && rgb == other.rgb && alpha == other.alpha)
|
357
357
|
end
|
358
358
|
|
359
|
+
def hash
|
360
|
+
[rgb, alpha].hash
|
361
|
+
end
|
362
|
+
|
359
363
|
# Returns a copy of this color with one or more channels changed.
|
360
364
|
# RGB or HSL colors may be changed, but not both at once.
|
361
365
|
#
|
@@ -36,6 +36,10 @@ module Sass::Script::Value
|
|
36
36
|
self.separator == other.separator)
|
37
37
|
end
|
38
38
|
|
39
|
+
def hash
|
40
|
+
@hash ||= [value, separator].hash
|
41
|
+
end
|
42
|
+
|
39
43
|
# @see Value#to_s
|
40
44
|
def to_s(opts = {})
|
41
45
|
raise Sass::SyntaxError.new("() isn't a valid CSS value.") if value.empty?
|
@@ -48,7 +52,8 @@ module Sass::Script::Value
|
|
48
52
|
precedence = Sass::Script::Parser.precedence_of(separator)
|
49
53
|
value.reject {|e| e.is_a?(Null)}.map do |v|
|
50
54
|
if v.is_a?(List) && Sass::Script::Parser.precedence_of(v.separator) <= precedence ||
|
51
|
-
separator == :space && v.is_a?(UnaryOperation) &&
|
55
|
+
separator == :space && v.is_a?(Sass::Script::Tree::UnaryOperation) &&
|
56
|
+
(v.operator == :minus || v.operator == :plus)
|
52
57
|
"(#{v.to_sass(opts)})"
|
53
58
|
else
|
54
59
|
v.to_sass(opts)
|
@@ -56,6 +61,27 @@ module Sass::Script::Value
|
|
56
61
|
end.join(sep_str(nil))
|
57
62
|
end
|
58
63
|
|
64
|
+
# @see Value#to_h
|
65
|
+
def to_h
|
66
|
+
return Sass::Util.ordered_hash if value.empty?
|
67
|
+
return @map ||= Sass::Util.to_hash(value.map {|e| e.to_a}) if is_pseudo_map?
|
68
|
+
super
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns whether a warning still needs to be printed for this list being used as a map.
|
72
|
+
#
|
73
|
+
# @return [Boolean]
|
74
|
+
def needs_map_warning?
|
75
|
+
!@value.empty? && !@map
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns whether this is a list of pairs that can be used as a map.
|
79
|
+
#
|
80
|
+
# @return [Boolean]
|
81
|
+
def is_pseudo_map?
|
82
|
+
@is_pseudo_map ||= value.all? {|e| e.is_a?(Sass::Script::Value::List) && e.to_a.length == 2}
|
83
|
+
end
|
84
|
+
|
59
85
|
# @see Value#inspect
|
60
86
|
def inspect
|
61
87
|
"(#{value.map {|e| e.inspect}.join(sep_str(nil))})"
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Sass::Script::Value
|
2
|
+
# A SassScript object representing a map from keys to values. Both keys and
|
3
|
+
# values can be any SassScript object.
|
4
|
+
class Map < Base
|
5
|
+
# The Ruby hash containing the contents of this map.
|
6
|
+
#
|
7
|
+
# @return [Hash<Node, Node>]
|
8
|
+
attr_reader :value
|
9
|
+
alias_method :to_h, :value
|
10
|
+
|
11
|
+
# Creates a new map.
|
12
|
+
#
|
13
|
+
# @param hash [Hash<Node, Node>]
|
14
|
+
def initialize(hash)
|
15
|
+
super(Sass::Util.ordered_hash(hash))
|
16
|
+
end
|
17
|
+
|
18
|
+
# @see Value#options=
|
19
|
+
def options=(options)
|
20
|
+
super
|
21
|
+
value.each do |k, v|
|
22
|
+
k.options = options
|
23
|
+
v.options = options
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# @see Value#separator
|
28
|
+
def separator
|
29
|
+
:comma unless value.empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
# @see Value#to_a
|
33
|
+
def to_a
|
34
|
+
value.map do |k, v|
|
35
|
+
list = List.new([k, v], :space)
|
36
|
+
list.options = options
|
37
|
+
list
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# @see Value#eq
|
42
|
+
def eq(other)
|
43
|
+
Bool.new(other.is_a?(Map) && self.value == other.value)
|
44
|
+
end
|
45
|
+
|
46
|
+
def hash
|
47
|
+
@hash ||= value.hash
|
48
|
+
end
|
49
|
+
|
50
|
+
# @see Value#to_s
|
51
|
+
def to_s(opts = {})
|
52
|
+
raise Sass::SyntaxError.new("#{inspect} isn't a valid CSS value.")
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_sass(opts = {})
|
56
|
+
return "()" if value.empty?
|
57
|
+
|
58
|
+
to_sass = lambda do |value|
|
59
|
+
if value.is_a?(Map) || (value.is_a?(List) && value.separator == :comma)
|
60
|
+
"(#{value.to_sass(opts)})"
|
61
|
+
else
|
62
|
+
value.to_sass(opts)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
"(#{value.map {|(k, v)| "#{to_sass[k]}: #{to_sass[v]}"}.join(', ')})"
|
67
|
+
end
|
68
|
+
alias_method :inspect, :to_sass
|
69
|
+
end
|
70
|
+
end
|
@@ -206,6 +206,18 @@ module Sass::Script::Value
|
|
206
206
|
Bool.new(this.value == other.value)
|
207
207
|
end
|
208
208
|
|
209
|
+
def hash
|
210
|
+
[value, numerator_units, denominator_units].hash
|
211
|
+
end
|
212
|
+
|
213
|
+
# Hash-equality works differently than `==` equality for numbers.
|
214
|
+
# Hash-equality must be transitive, so it just compares the exact value,
|
215
|
+
# numerator units, and denominator units.
|
216
|
+
def eql?(other)
|
217
|
+
self.value == other.value && self.numerator_units == other.numerator_units &&
|
218
|
+
self.denominator_units == other.denominator_units
|
219
|
+
end
|
220
|
+
|
209
221
|
# The SassScript `>` operation.
|
210
222
|
#
|
211
223
|
# @param other [Number] The right-hand side of the operator
|
data/lib/sass/scss/parser.rb
CHANGED
@@ -263,14 +263,20 @@ module Sass
|
|
263
263
|
|
264
264
|
def each_directive(start_pos)
|
265
265
|
tok!(/\$/)
|
266
|
-
|
266
|
+
vars = [tok!(IDENT)]
|
267
267
|
ss
|
268
|
+
while tok(/,/)
|
269
|
+
ss
|
270
|
+
tok!(/\$/)
|
271
|
+
vars << tok!(IDENT)
|
272
|
+
ss
|
273
|
+
end
|
268
274
|
|
269
275
|
tok!(/in/)
|
270
276
|
list = sass_script(:parse)
|
271
277
|
ss
|
272
278
|
|
273
|
-
block(node(Sass::Tree::EachNode.new(
|
279
|
+
block(node(Sass::Tree::EachNode.new(vars, list), start_pos), :directive)
|
274
280
|
end
|
275
281
|
|
276
282
|
def while_directive(start_pos)
|
data/lib/sass/stack.rb
CHANGED
@@ -98,6 +98,15 @@ module Sass
|
|
98
98
|
with_frame(filename, line, :mixin, name) {yield}
|
99
99
|
end
|
100
100
|
|
101
|
+
def to_s
|
102
|
+
Sass::Util.enum_with_index(Sass::Util.enum_cons(frames.reverse + [nil], 2)).
|
103
|
+
map do |(frame, caller), i|
|
104
|
+
"#{i == 0 ? "on" : "from"} line #{frame.line}" +
|
105
|
+
" of #{frame.filename || "an unknown file"}" +
|
106
|
+
(caller && caller.name ? ", in `#{caller.name}'" : "")
|
107
|
+
end.join("\n")
|
108
|
+
end
|
109
|
+
|
101
110
|
private
|
102
111
|
|
103
112
|
def with_frame(filename, line, type, name = nil)
|
data/lib/sass/tree/each_node.rb
CHANGED
@@ -5,18 +5,18 @@ module Sass::Tree
|
|
5
5
|
#
|
6
6
|
# @see Sass::Tree
|
7
7
|
class EachNode < Node
|
8
|
-
# The
|
9
|
-
# @return [String]
|
10
|
-
attr_reader :
|
8
|
+
# The names of the loop variables.
|
9
|
+
# @return [Array<String>]
|
10
|
+
attr_reader :vars
|
11
11
|
|
12
12
|
# The parse tree for the list.
|
13
13
|
# @return [Script::Tree::Node]
|
14
14
|
attr_accessor :list
|
15
15
|
|
16
|
-
# @param
|
16
|
+
# @param vars [Array<String>] The names of the loop variables
|
17
17
|
# @param list [Script::Tree::Node] The parse tree for the list
|
18
|
-
def initialize(
|
19
|
-
@
|
18
|
+
def initialize(vars, list)
|
19
|
+
@vars = vars
|
20
20
|
@list = list
|
21
21
|
super()
|
22
22
|
end
|
data/lib/sass/tree/rule_node.rb
CHANGED
@@ -58,7 +58,7 @@ module Sass::Tree
|
|
58
58
|
# This is only readable in a CSS tree as it is written during the perform step
|
59
59
|
# and only when the :trace_selectors option is set.
|
60
60
|
#
|
61
|
-
# @return [
|
61
|
+
# @return [String]
|
62
62
|
attr_accessor :stack_trace
|
63
63
|
|
64
64
|
# @param rule [Array<String, Sass::Script::Tree::Node>]
|
@@ -104,7 +104,8 @@ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
|
|
104
104
|
end
|
105
105
|
|
106
106
|
def visit_each(node)
|
107
|
-
|
107
|
+
vars = node.vars.map {|var| "$#{dasherize(var)}"}.join(", ")
|
108
|
+
"#{tab_str}@each #{vars} in #{node.list.to_sass(@options)}#{yield}"
|
108
109
|
end
|
109
110
|
|
110
111
|
def visit_extend(node)
|
@@ -91,12 +91,12 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
|
91
91
|
|
92
92
|
def initialize(env)
|
93
93
|
@environment = env
|
94
|
-
@stack = Sass::Stack.new
|
95
94
|
end
|
96
95
|
|
97
96
|
# If an exception is raised, this adds proper metadata to the backtrace.
|
98
97
|
def visit(node)
|
99
|
-
|
98
|
+
return super(node.dup) unless @environment
|
99
|
+
@environment.stack.with_base(node.filename, node.line) {super(node.dup)}
|
100
100
|
rescue Sass::SyntaxError => e
|
101
101
|
e.modify_backtrace(:filename => node.filename, :line => node.line)
|
102
102
|
raise e
|
@@ -155,8 +155,14 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
|
155
155
|
list = node.list.perform(@environment)
|
156
156
|
|
157
157
|
with_environment Sass::Environment.new(@environment) do
|
158
|
-
list.to_a.map do |
|
159
|
-
|
158
|
+
list.to_a.map do |value|
|
159
|
+
if node.vars.length == 1
|
160
|
+
@environment.set_local_var(node.vars.first, value)
|
161
|
+
else
|
162
|
+
node.vars.zip(value.to_a) do |(var, sub_value)|
|
163
|
+
@environment.set_local_var(var, sub_value || Sass::Script::Value::Null.new)
|
164
|
+
end
|
165
|
+
end
|
160
166
|
node.children.map {|c| visit(c)}
|
161
167
|
end.flatten
|
162
168
|
end
|
@@ -220,12 +226,12 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
|
220
226
|
return resolved_node
|
221
227
|
end
|
222
228
|
file = node.imported_file
|
223
|
-
if @stack.frames.any? {|f| f.is_import? && f.filename == file.options[:filename]}
|
229
|
+
if @environment.stack.frames.any? {|f| f.is_import? && f.filename == file.options[:filename]}
|
224
230
|
handle_import_loop!(node)
|
225
231
|
end
|
226
232
|
|
227
233
|
begin
|
228
|
-
@stack.with_import(node.filename, node.line) do
|
234
|
+
@environment.stack.with_import(node.filename, node.line) do
|
229
235
|
root = file.to_tree
|
230
236
|
Sass::Tree::Visitors::CheckNesting.visit(root)
|
231
237
|
node.children = root.children.map {|c| visit(c)}.flatten
|
@@ -249,10 +255,10 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
|
249
255
|
# Runs a mixin.
|
250
256
|
def visit_mixin(node)
|
251
257
|
include_loop = true
|
252
|
-
handle_include_loop!(node) if @stack.frames.any? {|f| f.is_mixin? && f.name == node.name}
|
258
|
+
handle_include_loop!(node) if @environment.stack.frames.any? {|f| f.is_mixin? && f.name == node.name}
|
253
259
|
include_loop = false
|
254
260
|
|
255
|
-
@stack.with_mixin(node.filename, node.line, node.name) do
|
261
|
+
@environment.stack.with_mixin(node.filename, node.line, node.name) do
|
256
262
|
raise Sass::SyntaxError.new("Undefined mixin '#{node.name}'.") unless mixin = @environment.mixin(node.name)
|
257
263
|
|
258
264
|
if node.children.any? && !mixin.has_content
|
@@ -282,7 +288,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
|
282
288
|
|
283
289
|
def visit_content(node)
|
284
290
|
return [] unless content = @environment.content
|
285
|
-
@stack.with_mixin(node.filename, node.line, '@content') do
|
291
|
+
@environment.stack.with_mixin(node.filename, node.line, '@content') do
|
286
292
|
trace_node = Sass::Tree::TraceNode.from_node('@content', node)
|
287
293
|
with_environment(@environment.caller) {trace_node.children = content.map {|c| visit(c.dup)}.flatten}
|
288
294
|
trace_node
|
@@ -315,7 +321,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
|
315
321
|
parser = Sass::SCSS::StaticParser.new(run_interp(node.rule),
|
316
322
|
node.filename, node.options[:importer], node.line)
|
317
323
|
node.parsed_rules ||= parser.parse_selector
|
318
|
-
node.stack_trace =
|
324
|
+
node.stack_trace = @environment.stack.to_s if node.options[:trace_selectors]
|
319
325
|
yield
|
320
326
|
end
|
321
327
|
|
@@ -338,7 +344,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
|
338
344
|
res = node.expr.perform(@environment)
|
339
345
|
res = res.value if res.is_a?(Sass::Script::Value::String)
|
340
346
|
msg = "WARNING: #{res}\n "
|
341
|
-
msg <<
|
347
|
+
msg << @environment.stack.to_s.gsub("\n", "\n ") << "\n"
|
342
348
|
Sass::Util.sass_warn msg
|
343
349
|
[]
|
344
350
|
end
|
@@ -382,15 +388,6 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
|
382
388
|
|
383
389
|
private
|
384
390
|
|
385
|
-
def stack_trace
|
386
|
-
Sass::Util.enum_with_index(Sass::Util.enum_cons(@stack.frames.reverse + [nil], 2)).
|
387
|
-
map do |(frame, caller), i|
|
388
|
-
"#{i == 0 ? "on" : "from"} line #{frame.line}" +
|
389
|
-
" of #{frame.filename || "an unknown file"}" +
|
390
|
-
(caller && caller.name ? ", in `#{caller.name}'" : "")
|
391
|
-
end
|
392
|
-
end
|
393
|
-
|
394
391
|
def run_interp_no_strip(text)
|
395
392
|
text.map do |r|
|
396
393
|
next r if r.is_a?(String)
|
@@ -408,7 +405,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
|
408
405
|
def handle_include_loop!(node)
|
409
406
|
msg = "An @include loop has been found:"
|
410
407
|
content_count = 0
|
411
|
-
mixins = @stack.frames.select {|f| f.is_mixin?}.reverse.map {|f| f.name}.select do |name|
|
408
|
+
mixins = @environment.stack.frames.select {|f| f.is_mixin?}.reverse.map {|f| f.name}.select do |name|
|
412
409
|
if name == '@content'
|
413
410
|
content_count += 1
|
414
411
|
false
|
@@ -431,7 +428,7 @@ class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
|
431
428
|
|
432
429
|
def handle_import_loop!(node)
|
433
430
|
msg = "An @import loop has been found:"
|
434
|
-
files = @stack.frames.select {|f| f.is_import?}.map {|f| f.filename}.compact
|
431
|
+
files = @environment.stack.frames.select {|f| f.is_import?}.map {|f| f.filename}.compact
|
435
432
|
if node.filename == node.imported_file.options[:filename]
|
436
433
|
raise Sass::SyntaxError.new("#{msg} #{node.filename} imports itself")
|
437
434
|
end
|
@@ -280,7 +280,7 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
|
|
280
280
|
output "\n"
|
281
281
|
elsif node.options[:trace_selectors]
|
282
282
|
output("#{old_spaces}/* ")
|
283
|
-
output(node.stack_trace.
|
283
|
+
output(node.stack_trace.gsub("\n", "\n #{old_spaces}"))
|
284
284
|
output(" */\n")
|
285
285
|
elsif node.options[:line_comments]
|
286
286
|
output("#{old_spaces}/* line #{node.line}")
|
@@ -747,6 +747,10 @@ a
|
|
747
747
|
c
|
748
748
|
@each $str in foo, bar, baz, bang
|
749
749
|
d: $str
|
750
|
+
|
751
|
+
c
|
752
|
+
@each $key, $value in (foo: 1, bar: 2, baz: 3)
|
753
|
+
\#{$key}: $value
|
750
754
|
SASS
|
751
755
|
a {
|
752
756
|
@each $number in 1px 2px 3px 4px {
|
@@ -759,6 +763,12 @@ c {
|
|
759
763
|
d: $str;
|
760
764
|
}
|
761
765
|
}
|
766
|
+
|
767
|
+
c {
|
768
|
+
@each $key, $value in (foo: 1, bar: 2, baz: 3) {
|
769
|
+
\#{$key}: $value;
|
770
|
+
}
|
771
|
+
}
|
762
772
|
SCSS
|
763
773
|
end
|
764
774
|
|
data/test/sass/engine_test.rb
CHANGED
@@ -1646,6 +1646,27 @@ a
|
|
1646
1646
|
SASS
|
1647
1647
|
end
|
1648
1648
|
|
1649
|
+
def test_destructuring_each
|
1650
|
+
assert_equal <<CSS, render(<<SCSS)
|
1651
|
+
a {
|
1652
|
+
foo: 1px;
|
1653
|
+
bar: 2px;
|
1654
|
+
baz: 3px; }
|
1655
|
+
|
1656
|
+
c {
|
1657
|
+
foo: "Value is bar";
|
1658
|
+
bar: "Value is baz";
|
1659
|
+
bang: "Value is "; }
|
1660
|
+
CSS
|
1661
|
+
a
|
1662
|
+
@each $name, $number in (foo: 1px, bar: 2px, baz: 3px)
|
1663
|
+
\#{$name}: $number
|
1664
|
+
c
|
1665
|
+
@each $key, $value in (foo bar) (bar, baz) bang
|
1666
|
+
\#{$key}: "Value is \#{$value}"
|
1667
|
+
SCSS
|
1668
|
+
end
|
1669
|
+
|
1649
1670
|
def test_variable_reassignment
|
1650
1671
|
assert_equal(<<CSS, render(<<SASS))
|
1651
1672
|
a {
|