haml-edge 2.3.93 → 2.3.94

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -39,15 +39,53 @@ module Sass::Tree
39
39
  #
40
40
  # would be
41
41
  #
42
- # [[[:parent, "foo"], ["bar"], ["baz"]],
43
- # [["bip"], [:parent, "bop"], ["bup"]]]
42
+ # [[[:parent, ".foo"], ["bar"], ["baz"]],
43
+ # [["bip"], [:parent, ".bop"], ["bup"]]]
44
44
  #
45
45
  # @return [Array<Array<Array<String|Symbol>>>]
46
46
  attr_accessor :parsed_rules
47
47
 
48
+ # The CSS selectors for this rule,
49
+ # with all nesting and parent references resolved.
50
+ # It's only set once {Tree::Node#cssize} has been called.
51
+ #
52
+ # The first level of arrays represents distinct lines in the Sass file;
53
+ # the second level represents comma-separated selectors.
54
+ # For example,
55
+ #
56
+ # foo bar, baz,
57
+ # bang, bip bop, blip
58
+ #
59
+ # would be
60
+ #
61
+ # [["foo bar", "baz"],
62
+ # ["bang", "bip bop", "blip"]]
63
+ #
64
+ # @return [Array<Array<String>>]
65
+ attr_accessor :resolved_rules
66
+
67
+ # How deep this rule is indented
68
+ # relative to a base-level rule.
69
+ # This is only greater than 0 in the case that:
70
+ #
71
+ # * This node is in a CSS tree
72
+ # * The style is :nested
73
+ # * This is a child rule of another rule
74
+ # * The parent rule has properties, and thus will be rendered
75
+ #
76
+ # @return [Fixnum]
77
+ attr_accessor :tabs
78
+
79
+ # Whether or not this rule is the last rule in a nested group.
80
+ # This is only set in a CSS tree.
81
+ #
82
+ # @return [Boolean]
83
+ attr_accessor :group_end
84
+
48
85
  # @param rule [String] The first CSS rule. See \{#rules}
49
86
  def initialize(rule)
50
87
  @rules = [rule]
88
+ @tabs = 0
51
89
  super()
52
90
  end
53
91
 
@@ -77,15 +115,9 @@ module Sass::Tree
77
115
  # Computes the CSS for the rule.
78
116
  #
79
117
  # @param tabs [Fixnum] The level of indentation for the CSS
80
- # @param super_rules [Array<Array<String>>] The rules for the parent node
81
- # (see \{#rules}), or `nil` if there are no parents
82
118
  # @return [String] The resulting CSS
83
- # @raise [Sass::SyntaxError] if the rule has no parents but uses `&`
84
- def _to_s(tabs, super_rules = nil)
85
- resolved_rules = resolve_parent_refs(super_rules)
86
-
87
- properties = []
88
- sub_rules = []
119
+ def _to_s(tabs)
120
+ tabs = tabs + self.tabs
89
121
 
90
122
  rule_separator = style == :compressed ? ',' : ', '
91
123
  line_separator = [:nested, :expanded].include?(style) ? ",\n" : rule_separator
@@ -96,54 +128,38 @@ module Sass::Tree
96
128
  per_rule_indent + line.join(rule_separator)
97
129
  end.join(line_separator)
98
130
 
99
- children.each do |child|
100
- next if child.invisible?
101
- if child.is_a? RuleNode
102
- sub_rules << child
103
- else
104
- properties << child
105
- end
106
- end
107
-
108
131
  to_return = ''
109
- if !properties.empty?
110
- old_spaces = ' ' * (tabs - 1)
111
- spaces = ' ' * tabs
112
- if @options[:line_comments] && style != :compressed
113
- to_return << "#{old_spaces}/* line #{line}"
114
-
115
- if filename
116
- relative_filename = if @options[:css_filename]
117
- begin
118
- Pathname.new(filename).relative_path_from(
119
- Pathname.new(File.dirname(@options[:css_filename]))).to_s
120
- rescue ArgumentError
121
- nil
122
- end
132
+ old_spaces = ' ' * (tabs - 1)
133
+ spaces = ' ' * tabs
134
+ if @options[:line_comments] && style != :compressed
135
+ to_return << "#{old_spaces}/* line #{line}"
136
+
137
+ if filename
138
+ relative_filename = if @options[:css_filename]
139
+ begin
140
+ Pathname.new(filename).relative_path_from(
141
+ Pathname.new(File.dirname(@options[:css_filename]))).to_s
142
+ rescue ArgumentError
143
+ nil
123
144
  end
124
- relative_filename ||= filename
125
- to_return << ", #{relative_filename}"
126
145
  end
127
-
128
- to_return << " */\n"
146
+ relative_filename ||= filename
147
+ to_return << ", #{relative_filename}"
129
148
  end
130
149
 
131
- if style == :compact
132
- properties = properties.map { |a| a.to_s(1) }.select{|a| a && a.length > 0}.join(' ')
133
- to_return << "#{total_rule} { #{properties} }\n"
134
- elsif style == :compressed
135
- properties = properties.map { |a| a.to_s(1) }.select{|a| a && a.length > 0}.join(';')
136
- to_return << "#{total_rule}{#{properties}}"
137
- else
138
- properties = properties.map { |a| a.to_s(tabs + 1) }.select{|a| a && a.length > 0}.join("\n")
139
- end_props = (style == :expanded ? "\n" + old_spaces : ' ')
140
- to_return << "#{total_rule} {\n#{properties}#{end_props}}\n"
141
- end
150
+ to_return << " */\n"
142
151
  end
143
152
 
144
- tabs += 1 unless properties.empty? || style != :nested
145
- sub_rules.each do |sub|
146
- to_return << sub.to_s(tabs, resolved_rules)
153
+ if style == :compact
154
+ properties = children.map { |a| a.to_s(1) }.select{|a| a && a.length > 0}.join(' ')
155
+ to_return << "#{total_rule} { #{properties} }#{"\n" if group_end}"
156
+ elsif style == :compressed
157
+ properties = children.map { |a| a.to_s(1) }.select{|a| a && a.length > 0}.join(';')
158
+ to_return << "#{total_rule}{#{properties}}"
159
+ else
160
+ properties = children.map { |a| a.to_s(tabs + 1) }.select{|a| a && a.length > 0}.join("\n")
161
+ end_props = (style == :expanded ? "\n" + old_spaces : ' ')
162
+ to_return << "#{total_rule} {\n#{properties}#{end_props}}#{"\n" if group_end}"
147
163
  end
148
164
 
149
165
  to_return
@@ -159,6 +175,37 @@ module Sass::Tree
159
175
  super
160
176
  end
161
177
 
178
+ # Converts nested rules into a flat list of rules.
179
+ #
180
+ # @param parent [RuleNode, nil] The parent node of this node,
181
+ # or nil if the parent isn't a {RuleNode}
182
+ def _cssize(parent)
183
+ node = super
184
+ rules = node.children.select {|c| c.is_a?(RuleNode)}
185
+ props = node.children.reject {|c| c.is_a?(RuleNode) || c.invisible?}
186
+
187
+ unless props.empty?
188
+ node.children = props
189
+ rules.each {|r| r.tabs += 1} if style == :nested
190
+ rules.unshift(node)
191
+ end
192
+
193
+ rules.last.group_end = true unless parent || rules.empty?
194
+
195
+ rules
196
+ end
197
+
198
+ # Resolves parent references and nested selectors,
199
+ # and updates the indentation based on the parent's indentation.
200
+ #
201
+ # @param parent [RuleNode, nil] The parent node of this node,
202
+ # or nil if the parent isn't a {RuleNode}
203
+ # @raise [Sass::SyntaxError] if the rule has no parents but uses `&`
204
+ def cssize!(parent)
205
+ self.resolved_rules = resolve_parent_refs(parent && parent.resolved_rules)
206
+ super
207
+ end
208
+
162
209
  private
163
210
 
164
211
  def resolve_parent_refs(super_rules)
@@ -586,6 +586,16 @@ HTML
586
586
  HAML
587
587
  end
588
588
 
589
+ def test_if_without_content_and_else
590
+ assert_equal(<<HTML, render(<<HAML))
591
+ foo
592
+ HTML
593
+ - if false
594
+ - else
595
+ foo
596
+ HAML
597
+ end
598
+
589
599
  # HTML escaping tests
590
600
 
591
601
  def test_ampersand_equals_should_escape
@@ -54,6 +54,13 @@ class UtilTest < Test::Unit::TestCase
54
54
  powerset([1, 2, 3]))
55
55
  end
56
56
 
57
+ def test_restrict
58
+ assert_equal(0.5, restrict(0.5, 0..1))
59
+ assert_equal(1, restrict(2, 0..1))
60
+ assert_equal(1.3, restrict(2, 0..1.3))
61
+ assert_equal(0, restrict(-1, 0..1))
62
+ end
63
+
57
64
  def test_merge_adjacent_strings
58
65
  assert_equal(["foo bar baz", :bang, "biz bop", 12],
59
66
  merge_adjacent_strings(["foo ", "bar ", "baz", :bang, "biz", " bop", 12]))
@@ -32,11 +32,6 @@ MSG
32
32
  "a\n b=c: d" => 'Invalid property: "b=c: d".',
33
33
  "a: b" => 'Properties aren\'t allowed at the root of a document.',
34
34
  ":a b" => 'Properties aren\'t allowed at the root of a document.',
35
- "a:" => 'Properties aren\'t allowed at the root of a document.',
36
- ":a" => <<MSG,
37
- Properties aren't allowed at the root of a document.
38
- If ":a" should be a selector, use "\\:a" instead.
39
- MSG
40
35
  "!" => 'Invalid variable: "!".',
41
36
  "!a" => 'Invalid variable: "!a".',
42
37
  "! a" => 'Invalid variable: "! a".',
@@ -189,15 +184,15 @@ SASS
189
184
  end
190
185
 
191
186
  def test_imported_exception
192
- [1, 2, 3].each do |i|
187
+ [1, 2, 3, 4].each do |i|
193
188
  begin
194
189
  Sass::Engine.new("@import bork#{i}", :load_paths => [File.dirname(__FILE__) + '/templates/']).render
195
190
  rescue Sass::SyntaxError => err
196
191
  assert_equal(2, err.sass_line)
197
192
  assert_match(/(\/|^)bork#{i}\.sass$/, err.sass_filename)
198
193
 
199
- assert_equal(err.sass_filename, err.sass_backtrace.first[:filename])
200
- assert_equal(err.sass_line, err.sass_backtrace.first[:line])
194
+ assert_hash_has(err.sass_backtrace.first,
195
+ :filename => err.sass_filename, :line => err.sass_line)
201
196
 
202
197
  assert_nil(err.sass_backtrace[1][:filename])
203
198
  assert_equal(1, err.sass_backtrace[1][:line])
@@ -211,15 +206,15 @@ SASS
211
206
  end
212
207
 
213
208
  def test_double_imported_exception
214
- [1, 2, 3].each do |i|
209
+ [1, 2, 3, 4].each do |i|
215
210
  begin
216
211
  Sass::Engine.new("@import nested_bork#{i}", :load_paths => [File.dirname(__FILE__) + '/templates/']).render
217
212
  rescue Sass::SyntaxError => err
218
213
  assert_equal(2, err.sass_line)
219
214
  assert_match(/(\/|^)bork#{i}\.sass$/, err.sass_filename)
220
215
 
221
- assert_equal(err.sass_filename, err.sass_backtrace.first[:filename])
222
- assert_equal(err.sass_line, err.sass_backtrace.first[:line])
216
+ assert_hash_has(err.sass_backtrace.first,
217
+ :filename => err.sass_filename, :line => err.sass_line)
223
218
 
224
219
  assert_match(/(\/|^)nested_bork#{i}\.sass$/, err.sass_backtrace[1][:filename])
225
220
  assert_equal(2, err.sass_backtrace[1][:line])
@@ -236,6 +231,96 @@ SASS
236
231
  end
237
232
  end
238
233
 
234
+ def test_mixin_exception
235
+ render(<<SASS)
236
+ =error-mixin(!a)
237
+ color = !a * 1em * 1px
238
+
239
+ =outer-mixin(!a)
240
+ +error-mixin(!a)
241
+
242
+ .error
243
+ +outer-mixin(12)
244
+ SASS
245
+ assert(false, "Exception not raised")
246
+ rescue Sass::SyntaxError => err
247
+ assert_equal(2, err.sass_line)
248
+ assert_equal(test_filename, err.sass_filename)
249
+ assert_equal("error-mixin", err.sass_mixin)
250
+
251
+ assert_hash_has(err.sass_backtrace.first, :line => err.sass_line,
252
+ :filename => err.sass_filename, :mixin => err.sass_mixin)
253
+ assert_hash_has(err.sass_backtrace[1], :line => 5,
254
+ :filename => test_filename, :mixin => "outer-mixin")
255
+ assert_hash_has(err.sass_backtrace[2], :line => 8,
256
+ :filename => test_filename, :mixin => nil)
257
+
258
+ assert_equal("#{test_filename}:2:in `error-mixin'", err.backtrace.first)
259
+ assert_equal("#{test_filename}:5:in `outer-mixin'", err.backtrace[1])
260
+ assert_equal("#{test_filename}:8", err.backtrace[2])
261
+ end
262
+
263
+ def test_mixin_callsite_exception
264
+ render(<<SASS)
265
+ =one-arg-mixin(!a)
266
+ color = !a
267
+
268
+ =outer-mixin(!a)
269
+ +one-arg-mixin(!a, 12)
270
+
271
+ .error
272
+ +outer-mixin(12)
273
+ SASS
274
+ assert(false, "Exception not raised")
275
+ rescue Sass::SyntaxError => err
276
+ assert_hash_has(err.sass_backtrace.first, :line => 5,
277
+ :filename => test_filename, :mixin => "one-arg-mixin")
278
+ assert_hash_has(err.sass_backtrace[1], :line => 5,
279
+ :filename => test_filename, :mixin => "outer-mixin")
280
+ assert_hash_has(err.sass_backtrace[2], :line => 8,
281
+ :filename => test_filename, :mixin => nil)
282
+ end
283
+
284
+ def test_mixin_exception_cssize
285
+ render(<<SASS)
286
+ =parent-ref-mixin
287
+ & foo
288
+ a: b
289
+
290
+ =outer-mixin
291
+ +parent-ref-mixin
292
+
293
+ +outer-mixin
294
+ SASS
295
+ assert(false, "Exception not raised")
296
+ rescue Sass::SyntaxError => err
297
+ assert_hash_has(err.sass_backtrace.first, :line => 2,
298
+ :filename => test_filename, :mixin => "parent-ref-mixin")
299
+ assert_hash_has(err.sass_backtrace[1], :line => 6,
300
+ :filename => test_filename, :mixin => "outer-mixin")
301
+ assert_hash_has(err.sass_backtrace[2], :line => 8,
302
+ :filename => test_filename, :mixin => nil)
303
+ end
304
+
305
+ def test_mixin_and_import_exception
306
+ Sass::Engine.new("@import nested_mixin_bork", :load_paths => [File.dirname(__FILE__) + '/templates/']).render
307
+ assert(false, "Exception not raised")
308
+ rescue Sass::SyntaxError => err
309
+ assert_match(/(\/|^)nested_mixin_bork\.sass$/, err.sass_backtrace.first[:filename])
310
+ assert_hash_has(err.sass_backtrace.first, :mixin => "error-mixin", :line => 4)
311
+
312
+ assert_match(/(\/|^)mixin_bork\.sass$/, err.sass_backtrace[1][:filename])
313
+ assert_hash_has(err.sass_backtrace[1], :mixin => "outer-mixin", :line => 2)
314
+
315
+ assert_match(/(\/|^)mixin_bork\.sass$/, err.sass_backtrace[2][:filename])
316
+ assert_hash_has(err.sass_backtrace[2], :mixin => nil, :line => 5)
317
+
318
+ assert_match(/(\/|^)nested_mixin_bork\.sass$/, err.sass_backtrace[3][:filename])
319
+ assert_hash_has(err.sass_backtrace[3], :mixin => nil, :line => 6)
320
+
321
+ assert_hash_has(err.sass_backtrace[4], :filename => nil, :mixin => nil, :line => 1)
322
+ end
323
+
239
324
  def test_exception_css_with_offset
240
325
  opts = {:full_exception => true, :line => 362}
241
326
  render(("a\n b: c\n" * 10) + "d\n e:\n" + ("f\n g: h\n" * 10), opts)
@@ -261,6 +346,67 @@ CSS
261
346
  assert(false, "Exception not raised for test_exception_css_with_offset")
262
347
  end
263
348
 
349
+ def test_exception_css_with_mixins
350
+ opts = {:full_exception => true}
351
+ render(<<SASS, opts)
352
+ =error-mixin(!a)
353
+ color = !a * 1em * 1px
354
+
355
+ =outer-mixin(!a)
356
+ +error-mixin(!a)
357
+
358
+ .error
359
+ +outer-mixin(12)
360
+ SASS
361
+ rescue Sass::SyntaxError => e
362
+ assert_equal(<<CSS, Sass::SyntaxError.exception_to_css(e, opts).split("\n")[0..13].join("\n"))
363
+ /*
364
+ Syntax error: 12em*px isn't a valid CSS value.
365
+ on line 2 of test_exception_css_with_mixins_inline.sass, in `error-mixin'
366
+ from line 5 of test_exception_css_with_mixins_inline.sass, in `outer-mixin'
367
+ from line 8 of test_exception_css_with_mixins_inline.sass
368
+
369
+ 1: =error-mixin(!a)
370
+ 2: color = !a * 1em * 1px
371
+ 3:
372
+ 4: =outer-mixin(!a)
373
+ 5: +error-mixin(!a)
374
+ 6:
375
+ 7: .error
376
+ CSS
377
+ else
378
+ assert(false, "Exception not raised")
379
+ end
380
+
381
+ def test_cssize_exception_css
382
+ opts = {:full_exception => true}
383
+ render(<<SASS, opts)
384
+ .filler
385
+ stuff: stuff!
386
+
387
+ a: b
388
+
389
+ .more.filler
390
+ a: b
391
+ SASS
392
+ rescue Sass::SyntaxError => e
393
+ assert_equal(<<CSS, Sass::SyntaxError.exception_to_css(e, opts).split("\n")[0..11].join("\n"))
394
+ /*
395
+ Syntax error: Properties aren't allowed at the root of a document.
396
+ on line 4 of test_cssize_exception_css_inline.sass
397
+
398
+ 1: .filler
399
+ 2: stuff: stuff!
400
+ 3:
401
+ 4: a: b
402
+ 5:
403
+ 6: .more.filler
404
+ 7: a: b
405
+ CSS
406
+ else
407
+ assert(false, "Exception not raised")
408
+ end
409
+
264
410
  def test_css_import
265
411
  assert_equal("@import url(./fonts.css) screen;\n", render("@import url(./fonts.css) screen"))
266
412
  assert_equal("@import \"./fonts.css\" screen;\n", render("@import \"./fonts.css\" screen"))
@@ -1000,7 +1146,11 @@ SASS
1000
1146
  end
1001
1147
 
1002
1148
  private
1003
-
1149
+
1150
+ def assert_hash_has(hash, expected)
1151
+ expected.each {|k, v| assert_equal(v, hash[k])}
1152
+ end
1153
+
1004
1154
  def render(sass, options = {})
1005
1155
  munge_filename options
1006
1156
  Sass::Engine.new(sass, options).render
@@ -196,18 +196,19 @@ class SassFunctionTest < Test::Unit::TestCase
196
196
  end
197
197
 
198
198
  def test_opacify
199
- assert_equal("rgba(0, 0, 0, 0.75)", evaluate("opacify(rgba(0, 0, 0, 0.5), 50%)"))
200
- assert_equal("rgba(0, 0, 0, 0.8)", evaluate("opacify(rgba(0, 0, 0, 0.2), 75)"))
201
- assert_equal("rgba(0, 0, 0, 0.28)", evaluate("fade-in(rgba(0, 0, 0, 0.2), 10px)"))
202
- assert_equal("black", evaluate("fade_in(rgba(0, 0, 0, 0.2), 100%)"))
199
+ assert_equal("rgba(0, 0, 0, 0.75)", evaluate("opacify(rgba(0, 0, 0, 0.5), 0.25)"))
200
+ assert_equal("rgba(0, 0, 0, 0.3)", evaluate("opacify(rgba(0, 0, 0, 0.2), 0.1)"))
201
+ assert_equal("rgba(0, 0, 0, 0.7)", evaluate("fade-in(rgba(0, 0, 0, 0.2), 0.5px)"))
202
+ assert_equal("black", evaluate("fade_in(rgba(0, 0, 0, 0.2), 0.8)"))
203
+ assert_equal("black", evaluate("opacify(rgba(0, 0, 0, 0.2), 1)"))
203
204
  assert_equal("rgba(0, 0, 0, 0.2)", evaluate("opacify(rgba(0, 0, 0, 0.2), 0%)"))
204
205
  end
205
206
 
206
207
  def test_opacify_tests_bounds
207
- assert_error_message("Amount -3012% must be between 0% and 100% for `opacify'",
208
- "opacify(rgba(0, 0, 0, 0.2), -3012%)")
209
- assert_error_message("Amount 101 must be between 0% and 100% for `opacify'",
210
- "opacify(rgba(0, 0, 0, 0.2), 101)")
208
+ assert_error_message("Amount -0.001 must be between 0 and 1 for `opacify'",
209
+ "opacify(rgba(0, 0, 0, 0.2), -0.001)")
210
+ assert_error_message("Amount 1.001 must be between 0 and 1 for `opacify'",
211
+ "opacify(rgba(0, 0, 0, 0.2), 1.001)")
211
212
  end
212
213
 
213
214
  def test_opacify_tests_types
@@ -216,18 +217,19 @@ class SassFunctionTest < Test::Unit::TestCase
216
217
  end
217
218
 
218
219
  def test_transparentize
219
- assert_equal("rgba(0, 0, 0, 0.25)", evaluate("transparentize(rgba(0, 0, 0, 0.5), 50%)"))
220
- assert_equal("rgba(0, 0, 0, 0.05)", evaluate("transparentize(rgba(0, 0, 0, 0.2), 75)"))
221
- assert_equal("rgba(0, 0, 0, 0.18)", evaluate("fade-out(rgba(0, 0, 0, 0.2), 10px)"))
222
- assert_equal("rgba(0, 0, 0, 0)", evaluate("fade_out(rgba(0, 0, 0, 0.2), 100%)"))
223
- assert_equal("rgba(0, 0, 0, 0.2)", evaluate("transparentize(rgba(0, 0, 0, 0.2), 0%)"))
220
+ assert_equal("rgba(0, 0, 0, 0.3)", evaluate("transparentize(rgba(0, 0, 0, 0.5), 0.2)"))
221
+ assert_equal("rgba(0, 0, 0, 0.1)", evaluate("transparentize(rgba(0, 0, 0, 0.2), 0.1)"))
222
+ assert_equal("rgba(0, 0, 0, 0.2)", evaluate("fade-out(rgba(0, 0, 0, 0.5), 0.3px)"))
223
+ assert_equal("rgba(0, 0, 0, 0)", evaluate("fade_out(rgba(0, 0, 0, 0.2), 0.2)"))
224
+ assert_equal("rgba(0, 0, 0, 0)", evaluate("transparentize(rgba(0, 0, 0, 0.2), 1)"))
225
+ assert_equal("rgba(0, 0, 0, 0.2)", evaluate("transparentize(rgba(0, 0, 0, 0.2), 0)"))
224
226
  end
225
227
 
226
228
  def test_transparentize_tests_bounds
227
- assert_error_message("Amount -3012% must be between 0% and 100% for `transparentize'",
228
- "transparentize(rgba(0, 0, 0, 0.2), -3012%)")
229
- assert_error_message("Amount 101 must be between 0% and 100% for `transparentize'",
230
- "transparentize(rgba(0, 0, 0, 0.2), 101)")
229
+ assert_error_message("Amount -0.001 must be between 0 and 1 for `transparentize'",
230
+ "transparentize(rgba(0, 0, 0, 0.2), -0.001)")
231
+ assert_error_message("Amount 1.001 must be between 0 and 1 for `transparentize'",
232
+ "transparentize(rgba(0, 0, 0, 0.2), 1.001)")
231
233
  end
232
234
 
233
235
  def test_transparentize_tests_types